aboutsummaryrefslogtreecommitdiffstats
path: root/binapigen/vppapi/parse_json.go
diff options
context:
space:
mode:
Diffstat (limited to 'binapigen/vppapi/parse_json.go')
-rw-r--r--binapigen/vppapi/parse_json.go552
1 files changed, 552 insertions, 0 deletions
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
+}