1 // Copyright (c) 2018 Cisco and/or its affiliates.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
24 "github.com/bennyscetbun/jsongo"
27 // Package represents collection of objects parsed from VPP binary API JSON data
35 RefMap map[string]string
38 // MessageType represents the type of a VPP message
42 requestMessage MessageType = iota // VPP request message
43 replyMessage // VPP reply message
44 eventMessage // VPP event message
45 otherMessage // other VPP message
48 // Message represents VPP binary API message
55 // Type represents VPP binary API type
62 // Union represents VPP binary API union
69 // Field represents VPP binary API object field
77 func (f *Field) IsArray() bool {
78 return f.Length > 0 || f.SizeFrom != ""
81 // Enum represents VPP binary API enum
88 // EnumEntry represents VPP binary API enum entry
89 type EnumEntry struct {
94 // Service represents VPP binary API service
102 func getSizeOfType(typ *Type) (size int) {
103 for _, field := range typ.Fields {
104 if n := getBinapiTypeSize(field.Type); n > 0 {
105 if field.Length > 0 {
106 size += n * field.Length
115 func getTypeByRef(ctx *context, ref string) *Type {
116 for _, typ := range ctx.packageData.Types {
117 if ref == toApiType(typ.Name) {
124 func getUnionSize(ctx *context, union *Union) (maxSize int) {
125 for _, field := range union.Fields {
126 if typ := getTypeByRef(ctx, field.Type); typ != nil {
127 if size := getSizeOfType(typ); size > maxSize {
135 // toApiType returns name that is used as type reference in VPP binary API
136 func toApiType(name string) string {
137 return fmt.Sprintf("vl_api_%s_t", name)
140 // parsePackage parses provided JSON data into objects prepared for code generation
141 func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
142 logf(" %s contains: %d services, %d messages, %d types, %d enums, %d unions (version: %s)",
144 jsonRoot.Map("services").Len(),
145 jsonRoot.Map("messages").Len(),
146 jsonRoot.Map("types").Len(),
147 jsonRoot.Map("enums").Len(),
148 jsonRoot.Map("unions").Len(),
149 jsonRoot.Map("vl_api_version").Get(),
153 APIVersion: jsonRoot.Map("vl_api_version").Get().(string),
154 RefMap: make(map[string]string),
158 enums := jsonRoot.Map("enums")
159 pkg.Enums = make([]Enum, enums.Len())
160 for i := 0; i < enums.Len(); i++ {
161 enumNode := enums.At(i)
163 enum, err := parseEnum(ctx, enumNode)
168 pkg.RefMap[toApiType(enum.Name)] = enum.Name
172 types := jsonRoot.Map("types")
173 pkg.Types = make([]Type, types.Len())
174 for i := 0; i < types.Len(); i++ {
175 typNode := types.At(i)
177 typ, err := parseType(ctx, typNode)
182 pkg.RefMap[toApiType(typ.Name)] = typ.Name
186 unions := jsonRoot.Map("unions")
187 pkg.Unions = make([]Union, unions.Len())
188 for i := 0; i < unions.Len(); i++ {
189 unionNode := unions.At(i)
191 union, err := parseUnion(ctx, unionNode)
195 pkg.Unions[i] = *union
196 pkg.RefMap[toApiType(union.Name)] = union.Name
200 messages := jsonRoot.Map("messages")
201 pkg.Messages = make([]Message, messages.Len())
202 for i := 0; i < messages.Len(); i++ {
203 msgNode := messages.At(i)
205 msg, err := parseMessage(ctx, msgNode)
209 pkg.Messages[i] = *msg
213 services := jsonRoot.Map("services")
214 if services.GetType() == jsongo.TypeMap {
215 pkg.Services = make([]Service, services.Len())
216 for i, key := range services.GetKeys() {
217 svcNode := services.At(key)
219 svc, err := parseService(ctx, key.(string), svcNode)
223 pkg.Services[i] = *svc
227 sort.Slice(pkg.Services, func(i, j int) bool {
229 if pkg.Services[i].Stream != pkg.Services[j].Stream {
230 return pkg.Services[i].Stream
232 return pkg.Services[i].RequestType < pkg.Services[j].RequestType
241 // printPackage prints all loaded objects for package
242 func printPackage(pkg *Package) {
243 if len(pkg.Enums) > 0 {
244 logf("loaded %d enums:", len(pkg.Enums))
245 for k, enum := range pkg.Enums {
246 logf(" - enum #%d\t%+v", k, enum)
249 if len(pkg.Unions) > 0 {
250 logf("loaded %d unions:", len(pkg.Unions))
251 for k, union := range pkg.Unions {
252 logf(" - union #%d\t%+v", k, union)
255 if len(pkg.Types) > 0 {
256 logf("loaded %d types:", len(pkg.Types))
257 for _, typ := range pkg.Types {
258 logf(" - type: %q (%d fields)", typ.Name, len(typ.Fields))
261 if len(pkg.Messages) > 0 {
262 logf("loaded %d messages:", len(pkg.Messages))
263 for _, msg := range pkg.Messages {
264 logf(" - message: %q (%d fields)", msg.Name, len(msg.Fields))
267 if len(pkg.Services) > 0 {
268 logf("loaded %d services:", len(pkg.Services))
269 for _, svc := range pkg.Services {
273 } else if len(svc.Events) > 0 {
274 info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
276 logf(" - service: %q -> %q %s", svc.RequestType, svc.ReplyType, info)
281 // parseEnum parses VPP binary API enum object from JSON node
282 func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
283 if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
284 return nil, errors.New("invalid JSON for enum specified")
287 enumName, ok := enumNode.At(0).Get().(string)
289 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
291 enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
293 return nil, fmt.Errorf("enum type invalid or missing")
301 // loop through enum entries, skip first (name) and last (enumtype)
302 for j := 1; j < enumNode.Len()-1; j++ {
303 if enumNode.At(j).GetType() == jsongo.TypeArray {
304 entry := enumNode.At(j)
306 if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue {
307 return nil, errors.New("invalid JSON for enum entry specified")
310 entryName, ok := entry.At(0).Get().(string)
312 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
314 entryVal := entry.At(1).Get()
316 enum.Entries = append(enum.Entries, EnumEntry{
326 // parseUnion parses VPP binary API union object from JSON node
327 func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
328 if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
329 return nil, errors.New("invalid JSON for union specified")
332 unionName, ok := unionNode.At(0).Get().(string)
334 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
336 unionCRC, ok := unionNode.At(unionNode.Len() - 1).At("crc").Get().(string)
338 return nil, fmt.Errorf("union crc invalid or missing")
346 // loop through union fields, skip first (name) and last (crc)
347 for j := 1; j < unionNode.Len()-1; j++ {
348 if unionNode.At(j).GetType() == jsongo.TypeArray {
349 fieldNode := unionNode.At(j)
351 field, err := parseField(ctx, fieldNode)
356 union.Fields = append(union.Fields, *field)
363 // parseType parses VPP binary API type object from JSON node
364 func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
365 if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
366 return nil, errors.New("invalid JSON for type specified")
369 typeName, ok := typeNode.At(0).Get().(string)
371 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
373 typeCRC, ok := typeNode.At(typeNode.Len() - 1).At("crc").Get().(string)
375 return nil, fmt.Errorf("type crc invalid or missing")
383 // loop through type fields, skip first (name) and last (crc)
384 for j := 1; j < typeNode.Len()-1; j++ {
385 if typeNode.At(j).GetType() == jsongo.TypeArray {
386 fieldNode := typeNode.At(j)
388 field, err := parseField(ctx, fieldNode)
393 typ.Fields = append(typ.Fields, *field)
400 // parseMessage parses VPP binary API message object from JSON node
401 func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
402 if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
403 return nil, errors.New("invalid JSON for message specified")
406 msgName, ok := msgNode.At(0).Get().(string)
408 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
410 msgCRC, ok := msgNode.At(msgNode.Len() - 1).At("crc").Get().(string)
412 return nil, fmt.Errorf("message crc invalid or missing")
420 // loop through message fields, skip first (name) and last (crc)
421 for j := 1; j < msgNode.Len()-1; j++ {
422 if msgNode.At(j).GetType() == jsongo.TypeArray {
423 fieldNode := msgNode.At(j)
425 field, err := parseField(ctx, fieldNode)
430 msg.Fields = append(msg.Fields, *field)
437 // parseField parses VPP binary API object field from JSON node
438 func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
439 if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
440 return nil, errors.New("invalid JSON for field specified")
443 fieldType, ok := field.At(0).Get().(string)
445 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
447 fieldName, ok := field.At(1).Get().(string)
449 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
451 var fieldLength float64
452 if field.Len() >= 3 {
453 fieldLength, ok = field.At(2).Get().(float64)
455 return nil, fmt.Errorf("field length is %T, not an int", field.At(2).Get())
458 var fieldLengthFrom string
459 if field.Len() >= 4 {
460 fieldLengthFrom, ok = field.At(3).Get().(string)
462 return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
469 Length: int(fieldLength),
470 SizeFrom: fieldLengthFrom,
474 // parseService parses VPP binary API service object from JSON node
475 func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
476 if svcNode.Len() == 0 || svcNode.At("reply").GetType() != jsongo.TypeValue {
477 return nil, errors.New("invalid JSON for service specified")
481 RequestType: svcName,
484 if replyNode := svcNode.At("reply"); replyNode.GetType() == jsongo.TypeValue {
485 reply, ok := replyNode.Get().(string)
487 return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
489 // some binapi messages might have `null` reply (for example: memclnt)
491 svc.ReplyType = reply
495 // stream service (dumps)
496 if streamNode := svcNode.At("stream"); streamNode.GetType() == jsongo.TypeValue {
498 svc.Stream, ok = streamNode.Get().(bool)
500 return nil, fmt.Errorf("service stream is %T, not a string", streamNode.Get())
504 // events service (event subscription)
505 if eventsNode := svcNode.At("events"); eventsNode.GetType() == jsongo.TypeArray {
506 for j := 0; j < eventsNode.Len(); j++ {
507 event := eventsNode.At(j).Get().(string)
508 svc.Events = append(svc.Events, event)
514 if !strings.HasSuffix(svc.RequestType, "_dump") ||
515 !strings.HasSuffix(svc.ReplyType, "_details") {
516 fmt.Printf("Invalid STREAM SERVICE: %+v\n", svc)
518 } else if len(svc.Events) > 0 {
519 if (!strings.HasSuffix(svc.RequestType, "_events") &&
520 !strings.HasSuffix(svc.RequestType, "_stats")) ||
521 !strings.HasSuffix(svc.ReplyType, "_reply") {
522 fmt.Printf("Invalid EVENTS SERVICE: %+v\n", svc)
524 } else if svc.ReplyType != "" {
525 if !strings.HasSuffix(svc.ReplyType, "_reply") {
526 fmt.Printf("Invalid SERVICE: %+v\n", svc)
533 // convertToGoType translates the VPP binary API type into Go type
534 func convertToGoType(ctx *context, binapiType string) (typ string) {
535 if t, ok := binapiTypes[binapiType]; ok {
538 } else if r, ok := ctx.packageData.RefMap[binapiType]; ok {
539 // specific types (enums/types/unions)
540 typ = camelCaseName(r)
543 log.Printf("found unknown VPP binary API type %q, using byte", binapiType)