From 94620e85f0bdbb054af07ce3670fadc1f76cfdf0 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Thu, 18 Jun 2020 08:22:13 +0200 Subject: Refactored binapi generator with message encoding Change-Id: I5a6abb68b9d058866f94818169300e5c2fc43895 Signed-off-by: Ondrej Fabry --- binapigen/vppapi/parse_json.go | 552 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 552 insertions(+) create mode 100644 binapigen/vppapi/parse_json.go (limited to 'binapigen/vppapi/parse_json.go') diff --git a/binapigen/vppapi/parse_json.go b/binapigen/vppapi/parse_json.go new file mode 100644 index 0000000..45b5796 --- /dev/null +++ b/binapigen/vppapi/parse_json.go @@ -0,0 +1,552 @@ +// Copyright (c) 2020 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vppapi + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/bennyscetbun/jsongo" + "github.com/sirupsen/logrus" +) + +var Logger *logrus.Logger + +func init() { + if strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser") { + Logger = logrus.StandardLogger() + } +} + +func logf(f string, v ...interface{}) { + if Logger != nil { + Logger.Debugf(f, v...) + } +} + +const ( + // file + objAPIVersion = "vl_api_version" + objTypes = "types" + objMessages = "messages" + objUnions = "unions" + objEnums = "enums" + objServices = "services" + objAliases = "aliases" + objOptions = "options" + objImports = "imports" + + // message + messageFieldCrc = "crc" + + // alias + aliasFieldLength = "length" + aliasFieldType = "type" + + // service + serviceFieldReply = "reply" + serviceFieldStream = "stream" + serviceFieldStreamMsg = "stream_msg" + serviceFieldEvents = "events" +) + +const ( + // file + fileOptionVersion = "version" + + // field + fieldOptionLimit = "limit" + fieldOptionDefault = "default" + + // service + serviceReplyNull = "null" +) + +func parseJSON(data []byte) (module *File, err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("recovered panic: %v", e) + } + }() + + // parse JSON data into objects + jsonRoot := new(jsongo.Node) + if err := json.Unmarshal(data, jsonRoot); err != nil { + return nil, fmt.Errorf("unmarshalling JSON failed: %v", err) + } + + logf("file contents:") + for _, key := range jsonRoot.GetKeys() { + if jsonRoot.At(key).Len() > 0 { + logf(" - %2d %s", jsonRoot.At(key).Len(), key) + } + } + + module = new(File) + + // parse CRC + if crc := jsonRoot.At(objAPIVersion); crc.GetType() == jsongo.TypeValue { + module.CRC = crc.Get().(string) + } + + // parse options + opt := jsonRoot.Map(objOptions) + if opt.GetType() == jsongo.TypeMap { + module.Options = make(map[string]string, 0) + for _, key := range opt.GetKeys() { + optionsNode := opt.At(key) + optionKey := key.(string) + optionValue := optionsNode.Get().(string) + module.Options[optionKey] = optionValue + } + } + + // parse imports + imports := jsonRoot.Map(objImports) + module.Imports = make([]string, 0) + imported := make(map[string]struct{}) + for i := 0; i < imports.Len(); i++ { + importNode := imports.At(i) + imp, err := parseImport(importNode) + if err != nil { + return nil, err + } + if _, ok := imported[*imp]; ok { + logf("duplicate import found: %v", *imp) + continue + } + imported[*imp] = struct{}{} + module.Imports = append(module.Imports, *imp) + } + + // avoid duplicate objects + known := make(map[string]struct{}) + exists := func(name string) bool { + if _, ok := known[name]; ok { + logf("duplicate object found: %v", name) + return true + } + known[name] = struct{}{} + return false + } + + // parse enum types + enumsNode := jsonRoot.Map(objEnums) + module.EnumTypes = make([]EnumType, 0) + for i := 0; i < enumsNode.Len(); i++ { + enumNode := enumsNode.At(i) + enum, err := parseEnum(enumNode) + if err != nil { + return nil, err + } + if exists(enum.Name) { + continue + } + module.EnumTypes = append(module.EnumTypes, *enum) + } + + // parse alias types + aliasesNode := jsonRoot.Map(objAliases) + if aliasesNode.GetType() == jsongo.TypeMap { + module.AliasTypes = make([]AliasType, 0) + for _, key := range aliasesNode.GetKeys() { + aliasNode := aliasesNode.At(key) + aliasName := key.(string) + alias, err := parseAlias(aliasName, aliasNode) + if err != nil { + return nil, err + } + if exists(alias.Name) { + continue + } + module.AliasTypes = append(module.AliasTypes, *alias) + } + } + + // parse struct types + typesNode := jsonRoot.Map(objTypes) + module.StructTypes = make([]StructType, 0) + for i := 0; i < typesNode.Len(); i++ { + typNode := typesNode.At(i) + structyp, err := parseStruct(typNode) + if err != nil { + return nil, err + } + if exists(structyp.Name) { + continue + } + module.StructTypes = append(module.StructTypes, *structyp) + } + + // parse union types + unionsNode := jsonRoot.Map(objUnions) + module.UnionTypes = make([]UnionType, 0) + for i := 0; i < unionsNode.Len(); i++ { + unionNode := unionsNode.At(i) + union, err := parseUnion(unionNode) + if err != nil { + return nil, err + } + if exists(union.Name) { + continue + } + module.UnionTypes = append(module.UnionTypes, *union) + } + + // parse messages + messagesNode := jsonRoot.Map(objMessages) + if messagesNode.GetType() == jsongo.TypeArray { + module.Messages = make([]Message, messagesNode.Len()) + for i := 0; i < messagesNode.Len(); i++ { + msgNode := messagesNode.At(i) + msg, err := parseMessage(msgNode) + if err != nil { + return nil, err + } + module.Messages[i] = *msg + } + } + + // parse services + servicesNode := jsonRoot.Map(objServices) + if servicesNode.GetType() == jsongo.TypeMap { + module.Service = &Service{ + RPCs: make([]RPC, servicesNode.Len()), + } + for i, key := range servicesNode.GetKeys() { + rpcNode := servicesNode.At(key) + rpcName := key.(string) + svc, err := parseServiceRPC(rpcName, rpcNode) + if err != nil { + return nil, err + } + module.Service.RPCs[i] = *svc + } + } + + return module, nil +} + +// parseImport parses VPP binary API import from JSON node +func parseImport(importNode *jsongo.Node) (*string, error) { + if importNode.GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for import specified") + } + + importName, ok := importNode.Get().(string) + if !ok { + return nil, fmt.Errorf("import name is %T, not a string", importNode.Get()) + } + + return &importName, nil +} + +// parseEnum parses VPP binary API enum object from JSON node +func parseEnum(enumNode *jsongo.Node) (*EnumType, error) { + if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for enum specified") + } + + enumName, ok := enumNode.At(0).Get().(string) + if !ok { + return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get()) + } + enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string) + if !ok { + return nil, fmt.Errorf("enum type invalid or missing") + } + + enum := EnumType{ + Name: enumName, + Type: enumType, + } + + // loop through enum entries, skip first (name) and last (enumtype) + for j := 1; j < enumNode.Len()-1; j++ { + if enumNode.At(j).GetType() == jsongo.TypeArray { + entry := enumNode.At(j) + + if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for enum entry specified") + } + + entryName, ok := entry.At(0).Get().(string) + if !ok { + return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get()) + } + entryVal := entry.At(1).Get().(float64) + + enum.Entries = append(enum.Entries, EnumEntry{ + Name: entryName, + Value: uint32(entryVal), + }) + } + } + + return &enum, nil +} + +// parseUnion parses VPP binary API union object from JSON node +func parseUnion(unionNode *jsongo.Node) (*UnionType, error) { + if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for union specified") + } + + unionName, ok := unionNode.At(0).Get().(string) + if !ok { + return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get()) + } + + union := UnionType{ + Name: unionName, + } + + // loop through union fields, skip first (name) + for j := 1; j < unionNode.Len(); j++ { + if unionNode.At(j).GetType() == jsongo.TypeArray { + fieldNode := unionNode.At(j) + + field, err := parseField(fieldNode) + if err != nil { + return nil, err + } + + union.Fields = append(union.Fields, *field) + } + } + + return &union, nil +} + +// parseStruct parses VPP binary API type object from JSON node +func parseStruct(typeNode *jsongo.Node) (*StructType, error) { + if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for type specified") + } + + typeName, ok := typeNode.At(0).Get().(string) + if !ok { + return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get()) + } + + typ := StructType{ + Name: typeName, + } + + // loop through type fields, skip first (name) + for j := 1; j < typeNode.Len(); j++ { + if typeNode.At(j).GetType() == jsongo.TypeArray { + fieldNode := typeNode.At(j) + + field, err := parseField(fieldNode) + if err != nil { + return nil, err + } + + typ.Fields = append(typ.Fields, *field) + } + } + + return &typ, nil +} + +// parseAlias parses VPP binary API alias object from JSON node +func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) { + if aliasNode.Len() == 0 || aliasNode.At(aliasFieldType).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for alias specified") + } + + alias := AliasType{ + Name: aliasName, + } + + if typeNode := aliasNode.At(aliasFieldType); typeNode.GetType() == jsongo.TypeValue { + typ, ok := typeNode.Get().(string) + if !ok { + return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get()) + } + if typ != "null" { + alias.Type = typ + } + } + + if lengthNode := aliasNode.At(aliasFieldLength); lengthNode.GetType() == jsongo.TypeValue { + length, ok := lengthNode.Get().(float64) + if !ok { + return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get()) + } + alias.Length = int(length) + } + + return &alias, nil +} + +// parseMessage parses VPP binary API message object from JSON node +func parseMessage(msgNode *jsongo.Node) (*Message, error) { + if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for message specified") + } + + msgName, ok := msgNode.At(0).Get().(string) + if !ok { + return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get()) + } + msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageFieldCrc).Get().(string) + if !ok { + + return nil, fmt.Errorf("message crc invalid or missing") + } + + msg := Message{ + Name: msgName, + CRC: msgCRC, + } + + // loop through message fields, skip first (name) and last (crc) + for j := 1; j < msgNode.Len()-1; j++ { + if msgNode.At(j).GetType() == jsongo.TypeArray { + fieldNode := msgNode.At(j) + + field, err := parseField(fieldNode) + if err != nil { + return nil, err + } + + msg.Fields = append(msg.Fields, *field) + } + } + + return &msg, nil +} + +// parseField parses VPP binary API object field from JSON node +func parseField(field *jsongo.Node) (*Field, error) { + if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for field specified") + } + + fieldType, ok := field.At(0).Get().(string) + if !ok { + return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get()) + } + fieldName, ok := field.At(1).Get().(string) + if !ok { + return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get()) + } + + f := &Field{ + Name: fieldName, + Type: fieldType, + } + + if field.Len() >= 3 { + switch field.At(2).GetType() { + case jsongo.TypeValue: + fieldLength, ok := field.At(2).Get().(float64) + if !ok { + return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get()) + } + f.Length = int(fieldLength) + f.Array = true + + case jsongo.TypeMap: + fieldMeta := field.At(2) + + for _, key := range fieldMeta.GetKeys() { + metaNode := fieldMeta.At(key) + metaName := key.(string) + metaValue := metaNode.Get() + + switch metaName { + case fieldOptionLimit: + metaValue = int(metaNode.Get().(float64)) + case fieldOptionDefault: + metaValue = metaNode.Get() + default: + logrus.Warnf("unknown meta info (%s=%v) for field (%s)", metaName, metaValue, fieldName) + } + + if f.Meta == nil { + f.Meta = map[string]interface{}{} + } + f.Meta[metaName] = metaValue + } + default: + return nil, errors.New("invalid JSON for field specified") + } + } + if field.Len() >= 4 { + fieldLengthFrom, ok := field.At(3).Get().(string) + if !ok { + return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get()) + } + f.SizeFrom = fieldLengthFrom + } + + return f, nil +} + +// parseServiceRPC parses VPP binary API service object from JSON node +func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) { + if rpcNode.Len() == 0 || rpcNode.At(serviceFieldReply).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for service RPC specified") + } + + rpc := RPC{ + Name: rpcName, + RequestMsg: rpcName, + } + + if replyNode := rpcNode.At(serviceFieldReply); replyNode.GetType() == jsongo.TypeValue { + reply, ok := replyNode.Get().(string) + if !ok { + return nil, fmt.Errorf("service RPC reply is %T, not a string", replyNode.Get()) + } + if reply != serviceReplyNull { + rpc.ReplyMsg = reply + } + } + + // is stream (dump) + if streamNode := rpcNode.At(serviceFieldStream); streamNode.GetType() == jsongo.TypeValue { + var ok bool + rpc.Stream, ok = streamNode.Get().(bool) + if !ok { + return nil, fmt.Errorf("service RPC stream is %T, not a boolean", streamNode.Get()) + } + } + + // stream message + if streamMsgNode := rpcNode.At(serviceFieldStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue { + var ok bool + rpc.StreamMsg, ok = streamMsgNode.Get().(string) + if !ok { + return nil, fmt.Errorf("service RPC stream msg is %T, not a string", streamMsgNode.Get()) + } + } + + // events service (event subscription) + if eventsNode := rpcNode.At(serviceFieldEvents); eventsNode.GetType() == jsongo.TypeArray { + for j := 0; j < eventsNode.Len(); j++ { + event := eventsNode.At(j).Get().(string) + rpc.Events = append(rpc.Events, event) + } + } + + return &rpc, nil +} -- cgit 1.2.3-korg