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/binapigen.go | 369 +++ binapigen/definitions.go | 153 + binapigen/definitions_test.go | 39 + binapigen/generate.go | 1241 ++++++++ binapigen/generate_rpc.go | 188 ++ binapigen/generate_test.go | 446 +++ binapigen/generator.go | 172 ++ binapigen/generator_test.go | 110 + binapigen/run.go | 89 + binapigen/types.go | 271 ++ binapigen/validate.go | 66 + binapigen/vppapi/api.go | 94 + binapigen/vppapi/integration_test.go | 43 + binapigen/vppapi/parse_json.go | 552 ++++ binapigen/vppapi/parser.go | 111 + binapigen/vppapi/parser_test.go | 114 + binapigen/vppapi/testdata/acl.api.json | 929 ++++++ binapigen/vppapi/testdata/af_packet.api.json | 157 + .../vppapi/testdata/input-generate-error.json | 3 + .../vppapi/testdata/input-read-json-error.json | 1 + binapigen/vppapi/testdata/input.txt | 0 binapigen/vppapi/testdata/ip.api.json | 2246 ++++++++++++++ binapigen/vppapi/testdata/test-all.api.json | 3240 ++++++++++++++++++++ binapigen/vppapi/testdata/vpe.api.json | 775 +++++ 24 files changed, 11409 insertions(+) create mode 100644 binapigen/binapigen.go create mode 100644 binapigen/definitions.go create mode 100644 binapigen/definitions_test.go create mode 100644 binapigen/generate.go create mode 100644 binapigen/generate_rpc.go create mode 100644 binapigen/generate_test.go create mode 100644 binapigen/generator.go create mode 100644 binapigen/generator_test.go create mode 100644 binapigen/run.go create mode 100644 binapigen/types.go create mode 100644 binapigen/validate.go create mode 100644 binapigen/vppapi/api.go create mode 100644 binapigen/vppapi/integration_test.go create mode 100644 binapigen/vppapi/parse_json.go create mode 100644 binapigen/vppapi/parser.go create mode 100644 binapigen/vppapi/parser_test.go create mode 100644 binapigen/vppapi/testdata/acl.api.json create mode 100644 binapigen/vppapi/testdata/af_packet.api.json create mode 100644 binapigen/vppapi/testdata/input-generate-error.json create mode 100644 binapigen/vppapi/testdata/input-read-json-error.json create mode 100644 binapigen/vppapi/testdata/input.txt create mode 100644 binapigen/vppapi/testdata/ip.api.json create mode 100644 binapigen/vppapi/testdata/test-all.api.json create mode 100644 binapigen/vppapi/testdata/vpe.api.json (limited to 'binapigen') diff --git a/binapigen/binapigen.go b/binapigen/binapigen.go new file mode 100644 index 0000000..0178476 --- /dev/null +++ b/binapigen/binapigen.go @@ -0,0 +1,369 @@ +// 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 binapigen + +import ( + "fmt" + "path" + "sort" + "strings" + + "git.fd.io/govpp.git/binapigen/vppapi" +) + +type File struct { + vppapi.File + + Generate bool + + PackageName string + Imports []string + + Enums []*Enum + Unions []*Union + Structs []*Struct + Aliases []*Alias + Messages []*Message + + imports map[string]string + refmap map[string]string +} + +func newFile(gen *Generator, apifile *vppapi.File) (*File, error) { + file := &File{ + File: *apifile, + PackageName: sanitizedName(apifile.Name), + imports: make(map[string]string), + refmap: make(map[string]string), + } + + sortFileObjects(&file.File) + + for _, imp := range apifile.Imports { + file.Imports = append(file.Imports, normalizeImport(imp)) + } + for _, enum := range apifile.EnumTypes { + file.Enums = append(file.Enums, newEnum(gen, file, enum)) + } + for _, alias := range apifile.AliasTypes { + file.Aliases = append(file.Aliases, newAlias(gen, file, alias)) + } + for _, structType := range apifile.StructTypes { + file.Structs = append(file.Structs, newStruct(gen, file, structType)) + } + for _, union := range apifile.UnionTypes { + file.Unions = append(file.Unions, newUnion(gen, file, union)) + } + for _, msg := range apifile.Messages { + file.Messages = append(file.Messages, newMessage(gen, file, msg)) + } + + return file, nil +} + +func (file *File) isTypes() bool { + return strings.HasSuffix(file.File.Name, "_types") +} + +func (file *File) hasService() bool { + return file.Service != nil && len(file.Service.RPCs) > 0 +} + +func (file *File) addRef(typ string, name string, ref interface{}) { + apiName := toApiType(name) + if _, ok := file.refmap[apiName]; ok { + logf("%s type %v already in refmap", typ, apiName) + return + } + file.refmap[apiName] = name +} + +func (file *File) importedFiles(gen *Generator) []*File { + var files []*File + for _, imp := range file.Imports { + impFile, ok := gen.FilesByName[imp] + if !ok { + logf("file %s import %s not found API files", file.Name, imp) + continue + } + //if gen.ImportTypes || impFile.Generate { + files = append(files, impFile) + //} + } + return files +} + +func (file *File) loadTypeImports(gen *Generator, typeFiles []*File) { + if len(typeFiles) == 0 { + return + } + for _, t := range file.Structs { + for _, imp := range typeFiles { + if _, ok := file.imports[t.Name]; ok { + break + } + for _, at := range imp.File.StructTypes { + if at.Name != t.Name { + continue + } + if len(at.Fields) != len(t.Fields) { + continue + } + file.imports[t.Name] = imp.PackageName + } + } + } + for _, t := range file.AliasTypes { + for _, imp := range typeFiles { + if _, ok := file.imports[t.Name]; ok { + break + } + for _, at := range imp.File.AliasTypes { + if at.Name != t.Name { + continue + } + if at.Length != t.Length { + continue + } + if at.Type != t.Type { + continue + } + file.imports[t.Name] = imp.PackageName + } + } + } + for _, t := range file.EnumTypes { + for _, imp := range typeFiles { + if _, ok := file.imports[t.Name]; ok { + break + } + for _, at := range imp.File.EnumTypes { + if at.Name != t.Name { + continue + } + if at.Type != t.Type { + continue + } + file.imports[t.Name] = imp.PackageName + } + } + } + for _, t := range file.UnionTypes { + for _, imp := range typeFiles { + if _, ok := file.imports[t.Name]; ok { + break + } + for _, at := range imp.File.UnionTypes { + if at.Name != t.Name { + continue + } + file.imports[t.Name] = imp.PackageName + /*if gen.ImportTypes { + imp.Generate = true + }*/ + } + } + } +} + +type Enum struct { + vppapi.EnumType + + GoName string +} + +func newEnum(gen *Generator, file *File, apitype vppapi.EnumType) *Enum { + typ := &Enum{ + EnumType: apitype, + GoName: camelCaseName(apitype.Name), + } + gen.enumsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ + file.addRef("enum", typ.Name, typ) + return typ +} + +type Alias struct { + vppapi.AliasType + + GoName string +} + +func newAlias(gen *Generator, file *File, apitype vppapi.AliasType) *Alias { + typ := &Alias{ + AliasType: apitype, + GoName: camelCaseName(apitype.Name), + } + gen.aliasesByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ + file.addRef("alias", typ.Name, typ) + return typ +} + +type Struct struct { + vppapi.StructType + + GoName string + + Fields []*Field +} + +func newStruct(gen *Generator, file *File, apitype vppapi.StructType) *Struct { + typ := &Struct{ + StructType: apitype, + GoName: camelCaseName(apitype.Name), + } + for _, fieldType := range apitype.Fields { + field := newField(gen, file, fieldType) + field.ParentStruct = typ + typ.Fields = append(typ.Fields, field) + } + gen.structsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ + file.addRef("struct", typ.Name, typ) + return typ +} + +type Union struct { + vppapi.UnionType + + GoName string + + Fields []*Field +} + +func newUnion(gen *Generator, file *File, apitype vppapi.UnionType) *Union { + typ := &Union{ + UnionType: apitype, + GoName: camelCaseName(apitype.Name), + } + gen.unionsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ + for _, fieldType := range apitype.Fields { + field := newField(gen, file, fieldType) + field.ParentUnion = typ + typ.Fields = append(typ.Fields, field) + } + file.addRef("union", typ.Name, typ) + return typ +} + +type Message struct { + vppapi.Message + + GoName string + + Fields []*Field +} + +func newMessage(gen *Generator, file *File, apitype vppapi.Message) *Message { + msg := &Message{ + Message: apitype, + GoName: camelCaseName(apitype.Name), + } + for _, fieldType := range apitype.Fields { + field := newField(gen, file, fieldType) + field.ParentMessage = msg + msg.Fields = append(msg.Fields, field) + } + return msg +} + +type Field struct { + vppapi.Field + + GoName string + + // Field parent + ParentMessage *Message + ParentStruct *Struct + ParentUnion *Union + + // Type reference + Enum *Enum + Alias *Alias + Struct *Struct + Union *Union +} + +func newField(gen *Generator, file *File, apitype vppapi.Field) *Field { + typ := &Field{ + Field: apitype, + GoName: camelCaseName(apitype.Name), + } + return typ +} + +func (f *Field) resolveType(gen *Generator) error { + switch { + + } + return nil +} + +type Service = vppapi.Service +type RPC = vppapi.RPC + +func sortFileObjects(file *vppapi.File) { + // sort imports + sort.SliceStable(file.Imports, func(i, j int) bool { + return file.Imports[i] < file.Imports[j] + }) + // sort enum types + sort.SliceStable(file.EnumTypes, func(i, j int) bool { + return file.EnumTypes[i].Name < file.EnumTypes[j].Name + }) + // sort alias types + sort.Slice(file.AliasTypes, func(i, j int) bool { + return file.AliasTypes[i].Name < file.AliasTypes[j].Name + }) + // sort struct types + sort.SliceStable(file.StructTypes, func(i, j int) bool { + return file.StructTypes[i].Name < file.StructTypes[j].Name + }) + // sort union types + sort.SliceStable(file.UnionTypes, func(i, j int) bool { + return file.UnionTypes[i].Name < file.UnionTypes[j].Name + }) + // sort messages + sort.SliceStable(file.Messages, func(i, j int) bool { + return file.Messages[i].Name < file.Messages[j].Name + }) + // sort services + if file.Service != nil { + sort.Slice(file.Service.RPCs, func(i, j int) bool { + // dumps first + if file.Service.RPCs[i].Stream != file.Service.RPCs[j].Stream { + return file.Service.RPCs[i].Stream + } + return file.Service.RPCs[i].RequestMsg < file.Service.RPCs[j].RequestMsg + }) + } +} + +func sanitizedName(name string) string { + switch name { + case "interface": + return "interfaces" + case "map": + return "maps" + default: + return name + } +} + +func normalizeImport(imp string) string { + imp = path.Base(imp) + if idx := strings.Index(imp, "."); idx >= 0 { + imp = imp[:idx] + } + return imp +} diff --git a/binapigen/definitions.go b/binapigen/definitions.go new file mode 100644 index 0000000..3c8a874 --- /dev/null +++ b/binapigen/definitions.go @@ -0,0 +1,153 @@ +// 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 binapigen + +import ( + "strings" + "unicode" +) + +// commonInitialisms is a set of common initialisms that need to stay in upper case. +var commonInitialisms = map[string]bool{ + "ACL": true, + "API": true, + // NOTE: There are only two occurences of the word 'ascii' and + // these already have initialism before and after ASCII part, + // thus disabling initialism for this case. + "ASCII": false, + "CPU": true, + "CSS": true, + "DNS": true, + "DHCP": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "ICMP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "PID": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SQL": true, + "SSH": true, + "TCP": true, + "TLS": true, + "TTL": true, + "UDP": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "VPN": true, + "XML": true, + "XMPP": true, + "XSRF": true, + "XSS": true, +} + +// specialInitialisms is a set of special initialisms that need part to stay in upper case. +var specialInitialisms = map[string]string{ + "IPV": "IPv", +} + +func usesInitialism(s string) string { + if u := strings.ToUpper(s); commonInitialisms[u] { + return u + } else if su, ok := specialInitialisms[u]; ok { + return su + } + return "" +} + +// camelCaseName returns correct name identifier (camelCase). +func camelCaseName(name string) (should string) { + name = strings.Title(name) + + // Fast path for simple cases: "_" and all lowercase. + if name == "_" { + return name + } + allLower := true + for _, r := range name { + if !unicode.IsLower(r) { + allLower = false + break + } + } + if allLower { + return name + } + + // Split camelCase at any lower->upper transition, and split on underscores. + // Check each word for common initialisms. + runes := []rune(name) + w, i := 0, 0 // index of start of word, scan + for i+1 <= len(runes) { + eow := false // whether we hit the end of a word + if i+1 == len(runes) { + eow = true + } else if runes[i+1] == '_' { + // underscore; shift the remainder forward over any run of underscores + eow = true + n := 1 + for i+n+1 < len(runes) && runes[i+n+1] == '_' { + n++ + } + + // Leave at most one underscore if the underscore is between two digits + if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { + n-- + } + + copy(runes[i+1:], runes[i+n+1:]) + runes = runes[:len(runes)-n] + } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { + // lower->non-lower + eow = true + } + i++ + if !eow { + continue + } + + // [w,i) is a word. + word := string(runes[w:i]) + if u := usesInitialism(word); u != "" { + // Keep consistent case, which is lowercase only at the start. + if w == 0 && unicode.IsLower(runes[w]) { + u = strings.ToLower(u) + } + // All the common initialisms are ASCII, + // so we can replace the bytes exactly. + copy(runes[w:], []rune(u)) + } else if w > 0 && strings.ToLower(word) == word { + // already all lowercase, and not the first word, so uppercase the first character. + runes[w] = unicode.ToUpper(runes[w]) + } + w = i + } + return string(runes) +} diff --git a/binapigen/definitions_test.go b/binapigen/definitions_test.go new file mode 100644 index 0000000..761c95f --- /dev/null +++ b/binapigen/definitions_test.go @@ -0,0 +1,39 @@ +// 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 binapigen + +import ( + "testing" +) + +func TestInitialism(t *testing.T) { + tests := []struct { + name string + input string + expOutput string + }{ + {name: "id", input: "id", expOutput: "ID"}, + {name: "ipv6", input: "is_ipv6", expOutput: "IsIPv6"}, + {name: "ip6", input: "is_ip6", expOutput: "IsIP6"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + output := camelCaseName(test.input) + if output != test.expOutput { + t.Errorf("expected %q, got %q", test.expOutput, output) + } + }) + } +} diff --git a/binapigen/generate.go b/binapigen/generate.go new file mode 100644 index 0000000..1f9b89a --- /dev/null +++ b/binapigen/generate.go @@ -0,0 +1,1241 @@ +// 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 binapigen + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "path" + "path/filepath" + "sort" + "strings" + + "git.fd.io/govpp.git/version" +) + +// generatedCodeVersion indicates a version of the generated code. +// It is incremented whenever an incompatibility between the generated code and +// GoVPP api package is introduced; the generated code references +// a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion). +const generatedCodeVersion = 2 + +// message field names +const ( + msgIdField = "_vl_msg_id" + clientIndexField = "client_index" + contextField = "context" + retvalField = "retval" +) + +const ( + outputFileExt = ".ba.go" // file extension of the Go generated files + rpcFileSuffix = "_rpc" // file name suffix for the RPC services + + constModuleName = "ModuleName" // module name constant + constAPIVersion = "APIVersion" // API version constant + constVersionCrc = "VersionCrc" // version CRC constant + + unionDataField = "XXX_UnionData" // name for the union data field + + serviceApiName = "RPCService" // name for the RPC service interface + serviceImplName = "serviceClient" // name for the RPC service implementation + serviceClientName = "ServiceClient" // name for the RPC service client + + // TODO: register service descriptor + //serviceDescType = "ServiceDesc" // name for service descriptor type + //serviceDescName = "_ServiceRPC_serviceDesc" // name for service descriptor var +) + +// MessageType represents the type of a VPP message +type MessageType int + +const ( + requestMessage MessageType = iota // VPP request message + replyMessage // VPP reply message + eventMessage // VPP event message + otherMessage // other VPP message +) + +type GenFile struct { + *Generator + filename string + file *File + packageDir string + buf bytes.Buffer +} + +func generatePackage(ctx *GenFile, w io.Writer) { + logf("----------------------------") + logf("generating binapi package: %q", ctx.file.PackageName) + logf("----------------------------") + + generateHeader(ctx, w) + generateImports(ctx, w) + + // generate module desc + fmt.Fprintln(w, "const (") + fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName) + fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.file.Name) + + if ctx.IncludeAPIVersion { + fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion) + fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.file.Version()) + fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc) + fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.file.CRC) + } + fmt.Fprintln(w, ")") + fmt.Fprintln(w) + + // generate enums + if len(ctx.file.Enums) > 0 { + for _, enum := range ctx.file.Enums { + if imp, ok := ctx.file.imports[enum.Name]; ok { + generateImportedAlias(ctx, w, enum.GoName, imp) + continue + } + generateEnum(ctx, w, enum) + } + } + + // generate aliases + if len(ctx.file.Aliases) > 0 { + for _, alias := range ctx.file.Aliases { + if imp, ok := ctx.file.imports[alias.Name]; ok { + generateImportedAlias(ctx, w, alias.GoName, imp) + continue + } + generateAlias(ctx, w, alias) + } + } + + // generate types + if len(ctx.file.Structs) > 0 { + for _, typ := range ctx.file.Structs { + if imp, ok := ctx.file.imports[typ.Name]; ok { + generateImportedAlias(ctx, w, typ.GoName, imp) + continue + } + generateStruct(ctx, w, typ) + } + } + + // generate unions + if len(ctx.file.Unions) > 0 { + for _, union := range ctx.file.Unions { + if imp, ok := ctx.file.imports[union.Name]; ok { + generateImportedAlias(ctx, w, union.GoName, imp) + continue + } + generateUnion(ctx, w, union) + } + } + + // generate messages + if len(ctx.file.Messages) > 0 { + for _, msg := range ctx.file.Messages { + generateMessage(ctx, w, msg) + } + + initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName) + + // generate message registrations + fmt.Fprintf(w, "func init() { %s() }\n", initFnName) + fmt.Fprintf(w, "func %s() {\n", initFnName) + for _, msg := range ctx.file.Messages { + fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", + msg.GoName, ctx.file.Name+"."+msg.GoName) + } + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + + // generate list of messages + fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n") + fmt.Fprintln(w, "func AllMessages() []api.Message {") + fmt.Fprintln(w, "\treturn []api.Message{") + for _, msg := range ctx.file.Messages { + fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName) + } + fmt.Fprintln(w, "}") + fmt.Fprintln(w, "}") + } + + generateFooter(ctx, w) + +} + +func generateHeader(ctx *GenFile, w io.Writer) { + fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") + fmt.Fprintln(w, "// versions:") + fmt.Fprintf(w, "// binapi-generator: %s\n", version.Version()) + if ctx.IncludeVppVersion { + fmt.Fprintf(w, "// VPP: %s\n", ctx.VPPVersion) + } + fmt.Fprintf(w, "// source: %s\n", ctx.file.Path) + fmt.Fprintln(w) + + fmt.Fprintln(w, "/*") + fmt.Fprintf(w, "Package %s contains generated code for VPP binary API defined by %s.api (version %s).\n", + ctx.file.PackageName, ctx.file.Name, ctx.file.Version()) + fmt.Fprintln(w) + fmt.Fprintln(w, "It consists of:") + printObjNum := func(obj string, num int) { + if num > 0 { + if num > 1 { + if strings.HasSuffix(obj, "s") { + + obj += "es" + } else { + obj += "s" + } + } + fmt.Fprintf(w, "\t%3d %s\n", num, obj) + } + } + //printObjNum("RPC", len(ctx.file.Service.RPCs)) + printObjNum("alias", len(ctx.file.Aliases)) + printObjNum("enum", len(ctx.file.Enums)) + printObjNum("message", len(ctx.file.Messages)) + printObjNum("type", len(ctx.file.Structs)) + printObjNum("union", len(ctx.file.Unions)) + fmt.Fprintln(w, "*/") + fmt.Fprintf(w, "package %s\n", ctx.file.PackageName) + fmt.Fprintln(w) +} + +func generateImports(ctx *GenFile, w io.Writer) { + fmt.Fprintln(w, "import (") + fmt.Fprintln(w, ` "bytes"`) + fmt.Fprintln(w, ` "context"`) + fmt.Fprintln(w, ` "encoding/binary"`) + fmt.Fprintln(w, ` "io"`) + fmt.Fprintln(w, ` "math"`) + fmt.Fprintln(w, ` "strconv"`) + fmt.Fprintln(w) + fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api") + fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec") + fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc") + if len(ctx.file.Imports) > 0 { + fmt.Fprintln(w) + for _, imp := range ctx.file.Imports { + importPath := path.Join(ctx.ImportPrefix, imp) + if ctx.ImportPrefix == "" { + importPath = getImportPath(ctx.packageDir, imp) + } + fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath)) + } + } + fmt.Fprintln(w, ")") + fmt.Fprintln(w) + + fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file") + fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.") + fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the") + fmt.Fprintln(w, "// GoVPP api package needs to be updated.") + fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion) + fmt.Fprintln(w) +} + +func getImportPath(outputDir string, pkg string) string { + absPath, err := filepath.Abs(filepath.Join(outputDir, "..", pkg)) + if err != nil { + panic(err) + } + cmd := exec.Command("go", "list", absPath) + var errbuf, outbuf bytes.Buffer + cmd.Stdout = &outbuf + cmd.Stderr = &errbuf + if err := cmd.Run(); err != nil { + fmt.Printf("ERR: %v\n", errbuf.String()) + panic(err) + } + return outbuf.String() +} + +func generateFooter(ctx *GenFile, w io.Writer) { + fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n") + fmt.Fprintf(w, "var _ = api.RegisterMessage\n") + fmt.Fprintf(w, "var _ = codec.DecodeString\n") + fmt.Fprintf(w, "var _ = bytes.NewBuffer\n") + fmt.Fprintf(w, "var _ = context.Background\n") + fmt.Fprintf(w, "var _ = io.Copy\n") + fmt.Fprintf(w, "var _ = strconv.Itoa\n") + fmt.Fprintf(w, "var _ = struc.Pack\n") + fmt.Fprintf(w, "var _ = binary.BigEndian\n") + fmt.Fprintf(w, "var _ = math.Float32bits\n") +} + +func generateComment(ctx *GenFile, w io.Writer, goName string, vppName string, objKind string) { + if objKind == "service" { + fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.file.Name) + } else { + fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName) + } +} + +func generateEnum(ctx *GenFile, w io.Writer, enum *Enum) { + name := enum.GoName + typ := binapiTypes[enum.Type] + + logf(" writing ENUM %q (%s) with %d entries", enum.Name, name, len(enum.Entries)) + + // generate enum comment + generateComment(ctx, w, name, enum.Name, "enum") + + // generate enum definition + fmt.Fprintf(w, "type %s %s\n", name, typ) + fmt.Fprintln(w) + + // generate enum entries + fmt.Fprintln(w, "const (") + for _, entry := range enum.Entries { + fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value) + } + fmt.Fprintln(w, ")") + fmt.Fprintln(w) + + // generate enum conversion maps + fmt.Fprintln(w, "var (") + fmt.Fprintf(w, "%s_name = map[%s]string{\n", name, typ) + for _, entry := range enum.Entries { + fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name) + } + fmt.Fprintln(w, "}") + fmt.Fprintf(w, "%s_value = map[string]%s{\n", name, typ) + for _, entry := range enum.Entries { + fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value) + } + fmt.Fprintln(w, "}") + fmt.Fprintln(w, ")") + fmt.Fprintln(w) + + fmt.Fprintf(w, "func (x %s) String() string {\n", name) + fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ) + fmt.Fprintf(w, "\tif ok { return s }\n") + fmt.Fprintf(w, "\treturn \"%s(\" + strconv.Itoa(int(x)) + \")\"\n", name) + fmt.Fprintln(w, "}") + fmt.Fprintln(w) +} + +func generateImportedAlias(ctx *GenFile, w io.Writer, name string, imp string) { + fmt.Fprintf(w, "type %s = %s.%s\n", name, imp, name) + fmt.Fprintln(w) +} + +func generateAlias(ctx *GenFile, w io.Writer, alias *Alias) { + name := alias.GoName + + logf(" writing ALIAS %q (%s), length: %d", alias.Name, name, alias.Length) + + // generate struct comment + generateComment(ctx, w, name, alias.Name, "alias") + + // generate struct definition + fmt.Fprintf(w, "type %s ", name) + + if alias.Length > 0 { + fmt.Fprintf(w, "[%d]", alias.Length) + } + + dataType := convertToGoType(ctx.file, alias.Type) + fmt.Fprintf(w, "%s\n", dataType) + + fmt.Fprintln(w) +} + +func generateStruct(ctx *GenFile, w io.Writer, typ *Struct) { + name := typ.GoName + + logf(" writing STRUCT %q (%s) with %d fields", typ.Name, name, len(typ.Fields)) + + // generate struct comment + generateComment(ctx, w, name, typ.Name, "type") + + // generate struct definition + fmt.Fprintf(w, "type %s struct {\n", name) + + // generate struct fields + for i := range typ.Fields { + // skip internal fields + switch strings.ToLower(typ.Name) { + case msgIdField: + continue + } + + generateField(ctx, w, typ.Fields, i) + } + + // generate end of the struct + fmt.Fprintln(w, "}") + + // generate name getter + generateTypeNameGetter(w, name, typ.Name) + + fmt.Fprintln(w) +} + +// generateUnionMethods generates methods that implement struc.Custom +// interface to allow having XXX_uniondata field unexported +// TODO: do more testing when unions are actually used in some messages +/*func generateUnionMethods(w io.Writer, structName string) { + // generate struc.Custom implementation for union + fmt.Fprintf(w, ` +func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) { + var b = new(bytes.Buffer) + if err := struc.PackWithOptions(b, u.union_data, opt); err != nil { + return 0, err + } + copy(p, b.Bytes()) + return b.Len(), nil +} +func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error { + return struc.UnpackWithOptions(r, u.union_data[:], opt) +} +func (u *%[1]s) Size(opt *struc.Options) int { + return len(u.union_data) +} +func (u *%[1]s) String() string { + return string(u.union_data[:]) +} +`, structName) +}*/ + +/*func generateUnionGetterSetterNew(w io.Writer, structName string, getterField, getterStruct string) { + fmt.Fprintf(w, ` +func %[1]s%[2]s(a %[3]s) (u %[1]s) { + u.Set%[2]s(a) + return +} +func (u *%[1]s) Set%[2]s(a %[3]s) { + copy(u.%[4]s[:], a[:]) +} +func (u *%[1]s) Get%[2]s() (a %[3]s) { + copy(a[:], u.%[4]s[:]) + return +} +`, structName, getterField, getterStruct, unionDataField) +}*/ + +func generateUnion(ctx *GenFile, w io.Writer, union *Union) { + name := union.GoName + + logf(" writing UNION %q (%s) with %d fields", union.Name, name, len(union.Fields)) + + // generate struct comment + generateComment(ctx, w, name, union.Name, "union") + + // generate struct definition + fmt.Fprintln(w, "type", name, "struct {") + + // maximum size for union + maxSize := getUnionSize(ctx.file, union) + + // generate data field + fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize) + + // generate end of the struct + fmt.Fprintln(w, "}") + + // generate name getter + generateTypeNameGetter(w, name, union.Name) + + // generate getters for fields + for _, field := range union.Fields { + fieldType := convertToGoType(ctx.file, field.Type) + generateUnionGetterSetter(w, name, field.GoName, fieldType) + } + + // generate union methods + //generateUnionMethods(w, name) + + fmt.Fprintln(w) +} + +func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) { + fmt.Fprintf(w, ` +func %[1]s%[2]s(a %[3]s) (u %[1]s) { + u.Set%[2]s(a) + return +} +func (u *%[1]s) Set%[2]s(a %[3]s) { + var b = new(bytes.Buffer) + if err := struc.Pack(b, &a); err != nil { + return + } + copy(u.%[4]s[:], b.Bytes()) +} +func (u *%[1]s) Get%[2]s() (a %[3]s) { + var b = bytes.NewReader(u.%[4]s[:]) + struc.Unpack(b, &a) + return +} +`, structName, getterField, getterStruct, unionDataField) +} + +func generateMessage(ctx *GenFile, w io.Writer, msg *Message) { + name := msg.GoName + + logf(" writing MESSAGE %q (%s) with %d fields", msg.Name, name, len(msg.Fields)) + + // generate struct comment + generateComment(ctx, w, name, msg.Name, "message") + + // generate struct definition + fmt.Fprintf(w, "type %s struct {", name) + + msgType := otherMessage + wasClientIndex := false + + // generate struct fields + n := 0 + for i, field := range msg.Fields { + if i == 1 { + if field.Name == clientIndexField { + // "client_index" as the second member, + // this might be an event message or a request + msgType = eventMessage + wasClientIndex = true + } else if field.Name == contextField { + // reply needs "context" as the second member + msgType = replyMessage + } + } else if i == 2 { + if wasClientIndex && field.Name == contextField { + // request needs "client_index" as the second member + // and "context" as the third member + msgType = requestMessage + } + } + + // skip internal fields + switch strings.ToLower(field.Name) { + case /*crcField,*/ msgIdField: + continue + case clientIndexField, contextField: + if n == 0 { + continue + } + } + n++ + if n == 1 { + fmt.Fprintln(w) + } + + generateField(ctx, w, msg.Fields, i) + } + + // generate end of the struct + fmt.Fprintln(w, "}") + + // generate message methods + generateMessageResetMethod(w, name) + generateMessageNameGetter(w, name, msg.Name) + generateCrcGetter(w, name, msg.CRC) + generateMessageTypeGetter(w, name, msgType) + generateMessageSize(ctx, w, name, msg.Fields) + generateMessageMarshal(ctx, w, name, msg.Fields) + generateMessageUnmarshal(ctx, w, name, msg.Fields) + + fmt.Fprintln(w) +} + +func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field) { + fmt.Fprintf(w, "func (m *%[1]s) Size() int {\n", name) + + fmt.Fprintf(w, "\tif m == nil { return 0 }\n") + fmt.Fprintf(w, "\tvar size int\n") + + encodeBaseType := func(typ, name string, length int, sizefrom string) bool { + t, ok := BaseTypeNames[typ] + if !ok { + return false + } + + var s = BaseTypeSizes[t] + switch t { + case STRING: + if length > 0 { + s = length + fmt.Fprintf(w, "\tsize += %d\n", s) + } else { + s = 4 + fmt.Fprintf(w, "\tsize += %d + len(%s)\n", s, name) + } + default: + if sizefrom != "" { + //fmt.Fprintf(w, "\tsize += %d * int(%s)\n", s, sizefrom) + fmt.Fprintf(w, "\tsize += %d * len(%s)\n", s, name) + } else { + if length > 0 { + s = BaseTypeSizes[t] * length + } + fmt.Fprintf(w, "\tsize += %d\n", s) + } + } + + return true + } + + lvl := 0 + var encodeFields func(fields []*Field, parentName string) + encodeFields = func(fields []*Field, parentName string) { + lvl++ + defer func() { lvl-- }() + + n := 0 + for _, field := range fields { + // skip internal fields + switch strings.ToLower(field.Name) { + case /*crcField,*/ msgIdField: + continue + case clientIndexField, contextField: + if n == 0 { + continue + } + } + n++ + + fieldName := field.GoName //camelCaseName(strings.TrimPrefix(field.Name, "_")) + name := fmt.Sprintf("%s.%s", parentName, fieldName) + sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_")) + var sizeFromName string + if sizeFrom != "" { + sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom) + } + + fmt.Fprintf(w, "\t// field[%d] %s\n", lvl, name) + + if encodeBaseType(field.Type, name, field.Length, sizeFromName) { + continue + } + + char := fmt.Sprintf("s%d", lvl) + index := fmt.Sprintf("j%d", lvl) + + if field.Array { + if field.Length > 0 { + fmt.Fprintf(w, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index) + } else if field.SizeFrom != "" { + //fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom) + fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < len(%[2]s); %[1]s++ {\n", index, name) + } + + fmt.Fprintf(w, "\tvar %[1]s %[2]s\n_ = %[1]s\n", char, convertToGoType(ctx.file, field.Type)) + fmt.Fprintf(w, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char) + name = char + } + + if enum := getEnumByRef(ctx.file, field.Type); enum != nil { + if encodeBaseType(enum.Type, name, 0, "") { + } else { + fmt.Fprintf(w, "\t// ??? ENUM %s %s\n", name, enum.Type) + } + } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil { + if encodeBaseType(alias.Type, name, alias.Length, "") { + } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil { + encodeFields(typ.Fields, name) + } else { + fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type) + } + } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil { + encodeFields(typ.Fields, name) + } else if union := getUnionByRef(ctx.file, field.Type); union != nil { + maxSize := getUnionSize(ctx.file, union) + fmt.Fprintf(w, "\tsize += %d\n", maxSize) + } else { + fmt.Fprintf(w, "\t// ??? buf[pos] = (%s)\n", name) + } + + if field.Array { + fmt.Fprintf(w, "\t}\n") + } + } + } + + encodeFields(fields, "m") + + fmt.Fprintf(w, "return size\n") + + fmt.Fprintf(w, "}\n") +} + +func generateMessageMarshal(ctx *GenFile, w io.Writer, name string, fields []*Field) { + fmt.Fprintf(w, "func (m *%[1]s) Marshal(b []byte) ([]byte, error) {\n", name) + + fmt.Fprintf(w, "\to := binary.BigEndian\n") + fmt.Fprintf(w, "\t_ = o\n") + fmt.Fprintf(w, "\tpos := 0\n") + fmt.Fprintf(w, "\t_ = pos\n") + + var buf = new(strings.Builder) + + encodeBaseType := func(typ, name string, length int, sizefrom string) bool { + t, ok := BaseTypeNames[typ] + if !ok { + return false + } + + isArray := length > 0 || sizefrom != "" + + switch t { + case I8, U8, I16, U16, I32, U32, I64, U64, F64: + if isArray { + if length != 0 { + fmt.Fprintf(buf, "\tfor i := 0; i < %d; i++ {\n", length) + } else if sizefrom != "" { + //fmt.Fprintf(buf, "\tfor i := 0; i < int(%s); i++ {\n", sizefrom) + fmt.Fprintf(buf, "\tfor i := 0; i < len(%s); i++ {\n", name) + } + } + } + + switch t { + case I8, U8: + if isArray { + fmt.Fprintf(buf, "\tvar x uint8\n") + fmt.Fprintf(buf, "\tif i < len(%s) { x = uint8(%s[i]) }\n", name, name) + name = "x" + } + fmt.Fprintf(buf, "\tbuf[pos] = uint8(%s)\n", name) + fmt.Fprintf(buf, "\tpos += 1\n") + if isArray { + fmt.Fprintf(buf, "\t}\n") + } + case I16, U16: + if isArray { + fmt.Fprintf(buf, "\tvar x uint16\n") + fmt.Fprintf(buf, "\tif i < len(%s) { x = uint16(%s[i]) }\n", name, name) + name = "x" + } + fmt.Fprintf(buf, "\to.PutUint16(buf[pos:pos+2], uint16(%s))\n", name) + fmt.Fprintf(buf, "\tpos += 2\n") + if isArray { + fmt.Fprintf(buf, "\t}\n") + } + case I32, U32: + if isArray { + fmt.Fprintf(buf, "\tvar x uint32\n") + fmt.Fprintf(buf, "\tif i < len(%s) { x = uint32(%s[i]) }\n", name, name) + name = "x" + } + fmt.Fprintf(buf, "\to.PutUint32(buf[pos:pos+4], uint32(%s))\n", name) + fmt.Fprintf(buf, "\tpos += 4\n") + if isArray { + fmt.Fprintf(buf, "\t}\n") + } + case I64, U64: + if isArray { + fmt.Fprintf(buf, "\tvar x uint64\n") + fmt.Fprintf(buf, "\tif i < len(%s) { x = uint64(%s[i]) }\n", name, name) + name = "x" + } + fmt.Fprintf(buf, "\to.PutUint64(buf[pos:pos+8], uint64(%s))\n", name) + fmt.Fprintf(buf, "\tpos += 8\n") + if isArray { + fmt.Fprintf(buf, "\t}\n") + } + case F64: + if isArray { + fmt.Fprintf(buf, "\tvar x float64\n") + fmt.Fprintf(buf, "\tif i < len(%s) { x = float64(%s[i]) }\n", name, name) + name = "x" + } + fmt.Fprintf(buf, "\to.PutUint64(buf[pos:pos+8], math.Float64bits(float64(%s)))\n", name) + fmt.Fprintf(buf, "\tpos += 8\n") + if isArray { + fmt.Fprintf(buf, "\t}\n") + } + case BOOL: + fmt.Fprintf(buf, "\tif %s { buf[pos] = 1 }\n", name) + fmt.Fprintf(buf, "\tpos += 1\n") + case STRING: + if length != 0 { + fmt.Fprintf(buf, "\tcopy(buf[pos:pos+%d], %s)\n", length, name) + fmt.Fprintf(buf, "\tpos += %d\n", length) + } else { + fmt.Fprintf(buf, "\to.PutUint32(buf[pos:pos+4], uint32(len(%s)))\n", name) + fmt.Fprintf(buf, "\tpos += 4\n") + fmt.Fprintf(buf, "\tcopy(buf[pos:pos+len(%s)], %s[:])\n", name, name) + fmt.Fprintf(buf, "\tpos += len(%s)\n", name) + } + default: + fmt.Fprintf(buf, "\t// ??? %s %s\n", name, typ) + return false + } + return true + } + + lvl := 0 + var encodeFields func(fields []*Field, parentName string) + encodeFields = func(fields []*Field, parentName string) { + lvl++ + defer func() { lvl-- }() + + n := 0 + for _, field := range fields { + // skip internal fields + switch strings.ToLower(field.Name) { + case /*crcField,*/ msgIdField: + continue + case clientIndexField, contextField: + if n == 0 { + continue + } + } + n++ + + getFieldName := func(name string) string { + fieldName := camelCaseName(strings.TrimPrefix(name, "_")) + return fmt.Sprintf("%s.%s", parentName, fieldName) + } + + fieldName := camelCaseName(strings.TrimPrefix(field.Name, "_")) + name := fmt.Sprintf("%s.%s", parentName, fieldName) + sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_")) + var sizeFromName string + if sizeFrom != "" { + sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom) + } + + fmt.Fprintf(buf, "\t// field[%d] %s\n", lvl, name) + + getSizeOfField := func() *Field { + for _, f := range fields { + if f.SizeFrom == field.Name { + return f + } + } + return nil + } + if f := getSizeOfField(); f != nil { + if encodeBaseType(field.Type, fmt.Sprintf("len(%s)", getFieldName(f.Name)), field.Length, "") { + continue + } + panic(fmt.Sprintf("failed to encode base type of sizefrom field: %s", field.Name)) + } + + if encodeBaseType(field.Type, name, field.Length, sizeFromName) { + continue + } + + char := fmt.Sprintf("v%d", lvl) + index := fmt.Sprintf("j%d", lvl) + + if field.Array { + if field.Length > 0 { + fmt.Fprintf(buf, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index) + } else if field.SizeFrom != "" { + //fmt.Fprintf(buf, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom) + fmt.Fprintf(buf, "\tfor %[1]s := 0; %[1]s < len(%[2]s); %[1]s++ {\n", index, name) + } + + fmt.Fprintf(buf, "\tvar %s %s\n", char, convertToGoType(ctx.file, field.Type)) + fmt.Fprintf(buf, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char) + name = char + } + + if enum := getEnumByRef(ctx.file, field.Type); enum != nil { + if encodeBaseType(enum.Type, name, 0, "") { + } else { + fmt.Fprintf(buf, "\t// ??? ENUM %s %s\n", name, enum.Type) + } + } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil { + if encodeBaseType(alias.Type, name, alias.Length, "") { + } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil { + encodeFields(typ.Fields, name) + } else { + fmt.Fprintf(buf, "\t// ??? ALIAS %s %s\n", name, alias.Type) + } + } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil { + encodeFields(typ.Fields, name) + } else if union := getUnionByRef(ctx.file, field.Type); union != nil { + maxSize := getUnionSize(ctx.file, union) + fmt.Fprintf(buf, "\tcopy(buf[pos:pos+%d], %s.%s[:])\n", maxSize, name, unionDataField) + fmt.Fprintf(buf, "\tpos += %d\n", maxSize) + } else { + fmt.Fprintf(buf, "\t// ??? buf[pos] = (%s)\n", name) + } + + if field.Array { + fmt.Fprintf(buf, "\t}\n") + } + } + } + + encodeFields(fields, "m") + + fmt.Fprintf(w, "\tvar buf []byte\n") + fmt.Fprintf(w, "\tif b == nil {\n") + fmt.Fprintf(w, "\tbuf = make([]byte, m.Size())\n") + fmt.Fprintf(w, "\t} else {\n") + fmt.Fprintf(w, "\tbuf = b\n") + fmt.Fprintf(w, "\t}\n") + fmt.Fprint(w, buf.String()) + + fmt.Fprintf(w, "return buf, nil\n") + + fmt.Fprintf(w, "}\n") +} + +func generateMessageUnmarshal(ctx *GenFile, w io.Writer, name string, fields []*Field) { + fmt.Fprintf(w, "func (m *%[1]s) Unmarshal(tmp []byte) error {\n", name) + + fmt.Fprintf(w, "\to := binary.BigEndian\n") + fmt.Fprintf(w, "\t_ = o\n") + fmt.Fprintf(w, "\tpos := 0\n") + fmt.Fprintf(w, "\t_ = pos\n") + + decodeBaseType := func(typ, orig, name string, length int, sizefrom string, alloc bool) bool { + t, ok := BaseTypeNames[typ] + if !ok { + return false + } + + isArray := length > 0 || sizefrom != "" + + switch t { + case I8, U8, I16, U16, I32, U32, I64, U64, F64: + if isArray { + if alloc { + if length != 0 { + fmt.Fprintf(w, "\t%s = make([]%s, %d)\n", name, orig, length) + } else if sizefrom != "" { + fmt.Fprintf(w, "\t%s = make([]%s, %s)\n", name, orig, sizefrom) + } + } + fmt.Fprintf(w, "\tfor i := 0; i < len(%s); i++ {\n", name) + } + } + + switch t { + case I8, U8: + if isArray { + fmt.Fprintf(w, "\t%s[i] = %s(tmp[pos])\n", name, convertToGoType(ctx.file, typ)) + } else { + fmt.Fprintf(w, "\t%s = %s(tmp[pos])\n", name, orig) + } + fmt.Fprintf(w, "\tpos += 1\n") + if isArray { + fmt.Fprintf(w, "\t}\n") + } + case I16, U16: + if isArray { + fmt.Fprintf(w, "\t%s[i] = %s(o.Uint16(tmp[pos:pos+2]))\n", name, orig) + } else { + fmt.Fprintf(w, "\t%s = %s(o.Uint16(tmp[pos:pos+2]))\n", name, orig) + } + fmt.Fprintf(w, "\tpos += 2\n") + if isArray { + fmt.Fprintf(w, "\t}\n") + } + case I32, U32: + if isArray { + fmt.Fprintf(w, "\t%s[i] = %s(o.Uint32(tmp[pos:pos+4]))\n", name, orig) + } else { + fmt.Fprintf(w, "\t%s = %s(o.Uint32(tmp[pos:pos+4]))\n", name, orig) + } + fmt.Fprintf(w, "\tpos += 4\n") + if isArray { + fmt.Fprintf(w, "\t}\n") + } + case I64, U64: + if isArray { + fmt.Fprintf(w, "\t%s[i] = %s(o.Uint64(tmp[pos:pos+8]))\n", name, orig) + } else { + fmt.Fprintf(w, "\t%s = %s(o.Uint64(tmp[pos:pos+8]))\n", name, orig) + } + fmt.Fprintf(w, "\tpos += 8\n") + if isArray { + fmt.Fprintf(w, "\t}\n") + } + case F64: + if isArray { + fmt.Fprintf(w, "\t%s[i] = %s(math.Float64frombits(o.Uint64(tmp[pos:pos+8])))\n", name, orig) + } else { + fmt.Fprintf(w, "\t%s = %s(math.Float64frombits(o.Uint64(tmp[pos:pos+8])))\n", name, orig) + } + fmt.Fprintf(w, "\tpos += 8\n") + if isArray { + fmt.Fprintf(w, "\t}\n") + } + case BOOL: + fmt.Fprintf(w, "\t%s = tmp[pos] != 0\n", name) + fmt.Fprintf(w, "\tpos += 1\n") + case STRING: + if length != 0 { + fmt.Fprintf(w, "\t{\n") + fmt.Fprintf(w, "\tnul := bytes.Index(tmp[pos:pos+%d], []byte{0x00})\n", length) + fmt.Fprintf(w, "\t%[1]s = codec.DecodeString(tmp[pos:pos+nul])\n", name) + fmt.Fprintf(w, "\tpos += %d\n", length) + fmt.Fprintf(w, "\t}\n") + } else { + fmt.Fprintf(w, "\t{\n") + fmt.Fprintf(w, "\tsiz := o.Uint32(tmp[pos:pos+4])\n") + fmt.Fprintf(w, "\tpos += 4\n") + fmt.Fprintf(w, "\t%[1]s = codec.DecodeString(tmp[pos:pos+int(siz)])\n", name) + fmt.Fprintf(w, "\tpos += len(%s)\n", name) + fmt.Fprintf(w, "\t}\n") + } + default: + fmt.Fprintf(w, "\t// ??? %s %s\n", name, typ) + return false + } + return true + } + + lvl := 0 + var decodeFields func(fields []*Field, parentName string) + decodeFields = func(fields []*Field, parentName string) { + lvl++ + defer func() { lvl-- }() + + n := 0 + for _, field := range fields { + // skip internal fields + switch strings.ToLower(field.Name) { + case /*crcField,*/ msgIdField: + continue + case clientIndexField, contextField: + if n == 0 { + continue + } + } + n++ + + fieldName := camelCaseName(strings.TrimPrefix(field.Name, "_")) + name := fmt.Sprintf("%s.%s", parentName, fieldName) + sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_")) + var sizeFromName string + if sizeFrom != "" { + sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom) + } + + fmt.Fprintf(w, "\t// field[%d] %s\n", lvl, name) + + if decodeBaseType(field.Type, convertToGoType(ctx.file, field.Type), name, field.Length, sizeFromName, true) { + continue + } + + //char := fmt.Sprintf("v%d", lvl) + index := fmt.Sprintf("j%d", lvl) + + if field.Array { + if field.Length > 0 { + fmt.Fprintf(w, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index) + } else if field.SizeFrom != "" { + fieldType := getFieldType(ctx, field) + if strings.HasPrefix(fieldType, "[]") { + fmt.Fprintf(w, "\t%s = make(%s, int(%s.%s))\n", name, fieldType, parentName, sizeFrom) + } + fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom) + } + + /*fmt.Fprintf(w, "\tvar %s %s\n", char, convertToGoType(ctx, field.Type)) + fmt.Fprintf(w, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char) + name = char*/ + name = fmt.Sprintf("%s[%s]", name, index) + } + + if enum := getEnumByRef(ctx.file, field.Type); enum != nil { + if decodeBaseType(enum.Type, convertToGoType(ctx.file, field.Type), name, 0, "", false) { + } else { + fmt.Fprintf(w, "\t// ??? ENUM %s %s\n", name, enum.Type) + } + } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil { + if decodeBaseType(alias.Type, convertToGoType(ctx.file, field.Type), name, alias.Length, "", false) { + } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil { + decodeFields(typ.Fields, name) + } else { + fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type) + } + } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil { + decodeFields(typ.Fields, name) + } else if union := getUnionByRef(ctx.file, field.Type); union != nil { + maxSize := getUnionSize(ctx.file, union) + fmt.Fprintf(w, "\tcopy(%s.%s[:], tmp[pos:pos+%d])\n", name, unionDataField, maxSize) + fmt.Fprintf(w, "\tpos += %d\n", maxSize) + } else { + fmt.Fprintf(w, "\t// ??? buf[pos] = (%s)\n", name) + } + + if field.Array { + fmt.Fprintf(w, "\t}\n") + } + } + } + + decodeFields(fields, "m") + + fmt.Fprintf(w, "return nil\n") + + fmt.Fprintf(w, "}\n") +} + +func getFieldType(ctx *GenFile, field *Field) string { + //fieldName := strings.TrimPrefix(field.Name, "_") + //fieldName = camelCaseName(fieldName) + //fieldName := field.GoName + + dataType := convertToGoType(ctx.file, field.Type) + fieldType := dataType + + // check if it is array + if field.Length > 0 || field.SizeFrom != "" { + if dataType == "uint8" { + dataType = "byte" + } + if dataType == "string" && field.Array { + fieldType = "string" + dataType = "byte" + } else if _, ok := BaseTypeNames[field.Type]; !ok && field.SizeFrom == "" { + fieldType = fmt.Sprintf("[%d]%s", field.Length, dataType) + } else { + fieldType = "[]" + dataType + } + } + + return fieldType +} + +func generateField(ctx *GenFile, w io.Writer, fields []*Field, i int) { + field := fields[i] + + //fieldName := strings.TrimPrefix(field.Name, "_") + //fieldName = camelCaseName(fieldName) + fieldName := field.GoName + + dataType := convertToGoType(ctx.file, field.Type) + fieldType := dataType + + // generate length field for strings + if field.Type == "string" && field.Length == 0 { + fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName) + } + + // check if it is array + if field.Length > 0 || field.SizeFrom != "" { + if dataType == "uint8" { + dataType = "byte" + } + if dataType == "string" && field.Array { + fieldType = "string" + dataType = "byte" + } else if _, ok := BaseTypeNames[field.Type]; !ok && field.SizeFrom == "" { + fieldType = fmt.Sprintf("[%d]%s", field.Length, dataType) + } else { + fieldType = "[]" + dataType + } + } + fmt.Fprintf(w, "\t%s %s", fieldName, fieldType) + + fieldTags := map[string]string{} + + if field.Length > 0 && field.Array { + // fixed size array + fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType) + } else { + for _, f := range fields { + if f.SizeFrom == field.Name { + // variable sized array + //sizeOfName := camelCaseName(f.Name) + fieldTags["struc"] = fmt.Sprintf("sizeof=%s", f.GoName) + } + } + } + + if ctx.IncludeBinapiNames { + typ := fromApiType(field.Type) + if field.Array { + if field.Length > 0 { + fieldTags["binapi"] = fmt.Sprintf("%s[%d],name=%s", typ, field.Length, field.Name) + } else if field.SizeFrom != "" { + fieldTags["binapi"] = fmt.Sprintf("%s[%s],name=%s", typ, field.SizeFrom, field.Name) + } + } else { + fieldTags["binapi"] = fmt.Sprintf("%s,name=%s", typ, field.Name) + } + } + if limit, ok := field.Meta["limit"]; ok && limit.(int) > 0 { + fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], limit) + } + if def, ok := field.Meta["default"]; ok && def != nil { + actual := getActualType(ctx.file, fieldType) + if t, ok := binapiTypes[actual]; ok && t != "float64" { + defnum := int(def.(float64)) + fieldTags["binapi"] = fmt.Sprintf("%s,default=%d", fieldTags["binapi"], defnum) + } else { + fieldTags["binapi"] = fmt.Sprintf("%s,default=%v", fieldTags["binapi"], def) + } + } + + fieldTags["json"] = fmt.Sprintf("%s,omitempty", field.Name) + + if len(fieldTags) > 0 { + fmt.Fprintf(w, "\t`") + var keys []string + for k := range fieldTags { + keys = append(keys, k) + } + sort.Strings(keys) + var n int + for _, tt := range keys { + t, ok := fieldTags[tt] + if !ok { + continue + } + if n > 0 { + fmt.Fprintf(w, " ") + } + n++ + fmt.Fprintf(w, `%s:"%s"`, tt, t) + } + fmt.Fprintf(w, "`") + } + + fmt.Fprintln(w) +} + +func generateMessageResetMethod(w io.Writer, structName string) { + fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName) +} + +func generateMessageNameGetter(w io.Writer, structName, msgName string) { + fmt.Fprintf(w, "func (*%s) GetMessageName() string { return %q }\n", structName, msgName) +} + +func generateTypeNameGetter(w io.Writer, structName, msgName string) { + fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName) +} + +func generateCrcGetter(w io.Writer, structName, crc string) { + crc = strings.TrimPrefix(crc, "0x") + fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc) +} + +func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) { + fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {") + if msgType == requestMessage { + fmt.Fprintf(w, "\treturn api.RequestMessage") + } else if msgType == replyMessage { + fmt.Fprintf(w, "\treturn api.ReplyMessage") + } else if msgType == eventMessage { + fmt.Fprintf(w, "\treturn api.EventMessage") + } else { + fmt.Fprintf(w, "\treturn api.OtherMessage") + } + fmt.Fprintln(w, "}") + fmt.Fprintln(w) +} diff --git a/binapigen/generate_rpc.go b/binapigen/generate_rpc.go new file mode 100644 index 0000000..b480f4a --- /dev/null +++ b/binapigen/generate_rpc.go @@ -0,0 +1,188 @@ +// 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 binapigen + +import ( + "fmt" + "io" + "strings" +) + +func generatePackageRPC(ctx *GenFile, w io.Writer) { + logf("----------------------------") + logf("generating RPC package: %q", ctx.file.PackageName) + logf("----------------------------") + + fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") + fmt.Fprintln(w) + + fmt.Fprintf(w, "package %s\n", ctx.file.PackageName) + fmt.Fprintln(w) + + fmt.Fprintln(w, "import (") + fmt.Fprintln(w, ` "context"`) + fmt.Fprintln(w, ` "io"`) + fmt.Fprintln(w) + fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api") + fmt.Fprintln(w, ")") + fmt.Fprintln(w) + + // generate services + if ctx.file.Service != nil && len(ctx.file.Service.RPCs) > 0 { + generateServiceMethods(ctx, w, ctx.file.Service.RPCs) + } + + // generate message registrations + /*fmt.Fprintln(w, "var _RPCService_desc = api.RPCDesc{") + + fmt.Fprintln(w, "}") + fmt.Fprintln(w)*/ + + fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n") + fmt.Fprintf(w, "var _ = api.RegisterMessage\n") + fmt.Fprintf(w, "var _ = context.Background\n") + fmt.Fprintf(w, "var _ = io.Copy\n") + +} + +func generateServiceMethods(ctx *GenFile, w io.Writer, methods []RPC) { + // generate services comment + generateComment(ctx, w, serviceApiName, "services", "service") + + // generate service api + fmt.Fprintf(w, "type %s interface {\n", serviceApiName) + for _, svc := range methods { + generateServiceMethod(ctx, w, &svc) + fmt.Fprintln(w) + } + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + + // generate client implementation + fmt.Fprintf(w, "type %s struct {\n", serviceImplName) + fmt.Fprintf(w, "\tch api.Channel\n") + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + + // generate client constructor + fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName) + fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName) + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + + for _, met := range methods { + method := camelCaseName(met.RequestMsg) + if m := strings.TrimSuffix(method, "Dump"); method != m { + method = "Dump" + m + } + + fmt.Fprintf(w, "func (c *%s) ", serviceImplName) + generateServiceMethod(ctx, w, &met) + fmt.Fprintln(w, " {") + if met.Stream { + streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method) + fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n") + fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl) + fmt.Fprintf(w, "\treturn x, nil\n") + } else if replyTyp := camelCaseName(met.ReplyMsg); replyTyp != "" { + fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp) + fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n") + fmt.Fprintf(w, "\tif err != nil { return nil, err }\n") + fmt.Fprintf(w, "\treturn out, nil\n") + } else { + fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n") + fmt.Fprintf(w, "\treturn nil\n") + } + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + + if met.Stream { + replyTyp := camelCaseName(met.ReplyMsg) + method := camelCaseName(met.RequestMsg) + if m := strings.TrimSuffix(method, "Dump"); method != m { + method = "Dump" + m + } + streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method) + + fmt.Fprintf(w, "type %s interface {\n", streamApi) + fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp) + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + + streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method) + fmt.Fprintf(w, "type %s struct {\n", streamImpl) + fmt.Fprintf(w, "\tapi.MultiRequestCtx\n") + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + + fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp) + fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp) + fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n") + fmt.Fprintf(w, "\tif err != nil { return nil, err }\n") + fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n") + fmt.Fprintf(w, "\treturn m, nil\n") + fmt.Fprintln(w, "}") + fmt.Fprintln(w) + } + } + + // TODO: generate service descriptor + /*fmt.Fprintf(w, "var %s = api.%s{\n", serviceDescName, serviceDescType) + fmt.Fprintf(w, "\tServiceName: \"%s\",\n", ctx.moduleName) + fmt.Fprintf(w, "\tHandlerType: (*%s)(nil),\n", serviceApiName) + fmt.Fprintf(w, "\tMethods: []api.MethodDesc{\n") + for _, method := range methods { + fmt.Fprintf(w, "\t {\n") + fmt.Fprintf(w, "\t MethodName: \"%s\",\n", method.Name) + fmt.Fprintf(w, "\t },\n") + } + fmt.Fprintf(w, "\t},\n") + //fmt.Fprintf(w, "\tCompatibility: %s,\n", messageCrcName) + //fmt.Fprintf(w, "\tMetadata: reflect.TypeOf((*%s)(nil)).Elem().PkgPath(),\n", serviceApiName) + fmt.Fprintf(w, "\tMetadata: \"%s\",\n", ctx.inputFile) + fmt.Fprintln(w, "}")*/ + + fmt.Fprintln(w) +} + +func generateServiceMethod(ctx *GenFile, w io.Writer, rpc *RPC) { + reqTyp := camelCaseName(rpc.RequestMsg) + + logf(" writing RPC: %+v", reqTyp) + + // method name is same as parameter type name by default + method := reqTyp + if rpc.Stream { + // use Dump as prefix instead of suffix for stream services + if m := strings.TrimSuffix(method, "Dump"); method != m { + method = "Dump" + m + } + } + + params := fmt.Sprintf("in *%s", reqTyp) + returns := "error" + + if replyType := camelCaseName(rpc.ReplyMsg); replyType != "" { + var replyTyp string + if rpc.Stream { + replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method) + } else { + replyTyp = fmt.Sprintf("*%s", replyType) + } + returns = fmt.Sprintf("(%s, error)", replyTyp) + } + + fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns) +} diff --git a/binapigen/generate_test.go b/binapigen/generate_test.go new file mode 100644 index 0000000..5a2a07a --- /dev/null +++ b/binapigen/generate_test.go @@ -0,0 +1,446 @@ +// 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 binapigen + +import ( + "os" + "testing" + + . "github.com/onsi/gomega" + + "git.fd.io/govpp.git/binapigen/vppapi" +) + +const testOutputDir = "test_output_directory" + +func GenerateFromFile(apiDir, outputDir string, opts Options) error { + // parse API files + apifiles, err := vppapi.ParseDir(apiDir) + if err != nil { + return err + } + + g, err := New(opts, apifiles) + if err != nil { + return err + } + for _, file := range g.Files { + if !file.Generate { + continue + } + GenerateBinapiFile(g, file, outputDir) + if file.Service != nil { + GenerateRPC(g, file, outputDir) + } + } + + if err = g.Generate(); err != nil { + return err + } + + return nil +} + +func TestGenerateFromFile(t *testing.T) { + RegisterTestingT(t) + + // remove directory created during test + defer os.RemoveAll(testOutputDir) + + err := GenerateFromFile("testdata/acl.api.json", testOutputDir, Options{}) + Expect(err).ShouldNot(HaveOccurred()) + fileInfo, err := os.Stat(testOutputDir + "/acl/acl.ba.go") + Expect(err).ShouldNot(HaveOccurred()) + Expect(fileInfo.IsDir()).To(BeFalse()) + Expect(fileInfo.Name()).To(BeEquivalentTo("acl.ba.go")) +} + +func TestGenerateFromFileInputError(t *testing.T) { + RegisterTestingT(t) + + err := GenerateFromFile("testdata/nonexisting.json", testOutputDir, Options{}) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid input file name")) +} + +func TestGenerateFromFileReadJsonError(t *testing.T) { + RegisterTestingT(t) + + err := GenerateFromFile("testdata/input-read-json-error.json", testOutputDir, Options{}) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid input file name")) +} + +func TestGenerateFromFileGeneratePackageError(t *testing.T) { + RegisterTestingT(t) + + // generate package throws panic, recover after it + defer func() { + if recovery := recover(); recovery != nil { + t.Logf("Recovered from panic: %v", recovery) + } + os.RemoveAll(testOutputDir) + }() + + err := GenerateFromFile("testdata/input-generate-error.json", testOutputDir, Options{}) + Expect(err).Should(HaveOccurred()) +} + +/*func TestGetContext(t *testing.T) { + RegisterTestingT(t) + outDir := "test_output_directory" + result, err := newContext("testdata/af_packet.api.json", outDir) + Expect(err).ShouldNot(HaveOccurred()) + Expect(result).ToNot(BeNil()) + Expect(result.outputFile).To(BeEquivalentTo(outDir + "/af_packet/af_packet.ba.go")) +} + +func TestGetContextNoJsonFile(t *testing.T) { + RegisterTestingT(t) + outDir := "test_output_directory" + result, err := newContext("testdata/input.txt", outDir) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid input file name")) + Expect(result).To(BeNil()) +} + +func TestGetContextInterfaceJson(t *testing.T) { + RegisterTestingT(t) + outDir := "test_output_directory" + result, err := newContext("testdata/ip.api.json", outDir) + Expect(err).ShouldNot(HaveOccurred()) + Expect(result).ToNot(BeNil()) + Expect(result.outputFile) + Expect(result.outputFile).To(BeEquivalentTo(outDir + "/ip/ip.ba.go")) +}*/ + +/*func TestGeneratePackage(t *testing.T) { + RegisterTestingT(t) + // prepare context + testCtx := new(GenFile) + testCtx.packageName = "test-package-name" + + // prepare input/output output files + inputData, err := ioutil.ReadFile("testdata/ip.api.json") + Expect(err).ShouldNot(HaveOccurred()) + jsonRoot, err := parseInputJSON(inputData) + Expect(err).ShouldNot(HaveOccurred()) + testCtx.file, err = parseModule(testCtx, jsonRoot) + Expect(err).ShouldNot(HaveOccurred()) + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + defer os.RemoveAll(outDir) + + // prepare writer + writer := bufio.NewWriter(outFile) + Expect(writer.Buffered()).To(BeZero()) + err = generatePackage(testCtx, writer) + Expect(err).ShouldNot(HaveOccurred()) +} + +func TestGenerateMessageType(t *testing.T) { + RegisterTestingT(t) + // prepare context + testCtx := new(GenFile) + testCtx.packageName = "test-package-name" + + // prepare input/output output files + inputData, err := ioutil.ReadFile("testdata/ip.api.json") + Expect(err).ShouldNot(HaveOccurred()) + jsonRoot, err := parseInputJSON(inputData) + Expect(err).ShouldNot(HaveOccurred()) + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + testCtx.file, err = parseModule(testCtx, jsonRoot) + Expect(err).ShouldNot(HaveOccurred()) + defer os.RemoveAll(outDir) + + // prepare writer + writer := bufio.NewWriter(outFile) + + for _, msg := range testCtx.file.Messages { + generateMessage(testCtx, writer, &msg) + Expect(writer.Buffered()).ToNot(BeZero()) + } +}*/ + +/*func TestGenerateMessageName(t *testing.T) { + RegisterTestingT(t) + // prepare context + testCtx := new(context) + testCtx.packageName = "test-package-name" + + // prepare input/output output files + inputData, err := readFile("testdata/ip.api.json") + Expect(err).ShouldNot(HaveOccurred()) + testCtx.inputBuff = bytes.NewBuffer(inputData) + inFile, _ := parseJSON(inputData) + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + defer os.RemoveAll(outDir) + + // prepare writer + writer := bufio.NewWriter(outFile) + + types := inFile.Map("types") + Expect(types.Len()).To(BeEquivalentTo(1)) + for i := 0; i < types.Len(); i++ { + typ := types.At(i) + Expect(writer.Buffered()).To(BeZero()) + err := generateMessage(testCtx, writer, typ, false) + Expect(err).ShouldNot(HaveOccurred()) + Expect(writer.Buffered()).ToNot(BeZero()) + + } +} + +func TestGenerateMessageFieldTypes(t *testing.T) { + // expected results according to acl.api.json in testdata + expectedTypes := []string{ + "\tIsPermit uint8", + "\tIsIpv6 uint8", + "\tSrcIPAddr []byte `struc:\"[16]byte\"`", + "\tSrcIPPrefixLen uint8", + "\tDstIPAddr []byte `struc:\"[16]byte\"`", + "\tDstIPPrefixLen uint8", + "\tProto uint8", + "\tSrcportOrIcmptypeFirst uint16", + "\tSrcportOrIcmptypeLast uint16", + "\tDstportOrIcmpcodeFirst uint16", + "\tDstportOrIcmpcodeLast uint16", + "\tTCPFlagsMask uint8", + "\tTCPFlagsValue uint8"} + RegisterTestingT(t) + // prepare context + testCtx := new(context) + testCtx.packageName = "test-package-name" + + // prepare input/output output files + inputData, err := readFile("testdata/acl.api.json") + Expect(err).ShouldNot(HaveOccurred()) + inFile, err := parseJSON(inputData) + Expect(err).ShouldNot(HaveOccurred()) + Expect(inFile).ToNot(BeNil()) + + // test types + types := inFile.Map("types") + fields := make([]string, 0) + for i := 0; i < types.Len(); i++ { + for j := 0; j < types.At(i).Len(); j++ { + field := types.At(i).At(j) + if field.GetType() == jsongo.TypeArray { + err := processMessageField(testCtx, &fields, field, false) + Expect(err).ShouldNot(HaveOccurred()) + Expect(fields[j-1]).To(BeEquivalentTo(expectedTypes[j-1])) + } + } + } +} + +func TestGenerateMessageFieldMessages(t *testing.T) { + // expected results according to acl.api.json in testdata + expectedFields := []string{"\tMajor uint32", "\tMinor uint32", "\tRetval int32", + "\tVpePid uint32", "\tACLIndex uint32", "\tTag []byte `struc:\"[64]byte\"`", + "\tCount uint32"} + RegisterTestingT(t) + // prepare context + testCtx := new(context) + testCtx.packageName = "test-package-name" + + // prepare input/output output files + inputData, err := readFile("testdata/acl.api.json") + Expect(err).ShouldNot(HaveOccurred()) + inFile, err := parseJSON(inputData) + Expect(err).ShouldNot(HaveOccurred()) + Expect(inFile).ToNot(BeNil()) + + // test message fields + messages := inFile.Map("messages") + customIndex := 0 + fields := make([]string, 0) + for i := 0; i < messages.Len(); i++ { + for j := 0; j < messages.At(i).Len(); j++ { + field := messages.At(i).At(j) + if field.GetType() == jsongo.TypeArray { + specificFieldName := field.At(1).Get().(string) + if specificFieldName == "crc" || specificFieldName == "_vl_msg_id" || + specificFieldName == "client_index" || specificFieldName == "context" { + continue + } + err := processMessageField(testCtx, &fields, field, false) + Expect(err).ShouldNot(HaveOccurred()) + Expect(fields[customIndex]).To(BeEquivalentTo(expectedFields[customIndex])) + customIndex++ + if customIndex >= len(expectedFields) { + // there is too much fields now for one UT... + return + } + } + } + } +} + +func TestGeneratePackageHeader(t *testing.T) { + RegisterTestingT(t) + // prepare context + testCtx := new(context) + testCtx.packageName = "test-package-name" + + // prepare input/output output files + inputData, err := readFile("testdata/acl.api.json") + Expect(err).ShouldNot(HaveOccurred()) + inFile, err := parseJSON(inputData) + Expect(err).ShouldNot(HaveOccurred()) + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + defer os.RemoveAll(outDir) + // prepare writer + writer := bufio.NewWriter(outFile) + Expect(writer.Buffered()).To(BeZero()) + generateHeader(testCtx, writer, inFile) + Expect(writer.Buffered()).ToNot(BeZero()) +} + +func TestGenerateMessageCommentType(t *testing.T) { + RegisterTestingT(t) + // prepare context + testCtx := new(context) + testCtx.packageName = "test-package-name" + testCtx.inputBuff = bytes.NewBuffer([]byte("test content")) + + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + writer := bufio.NewWriter(outFile) + defer os.RemoveAll(outDir) + Expect(writer.Buffered()).To(BeZero()) + generateMessageComment(testCtx, writer, "test-struct", "msg-name", true) + Expect(writer.Buffered()).ToNot(BeZero()) +} + +func TestGenerateMessageCommentMessage(t *testing.T) { + RegisterTestingT(t) + // prepare context + testCtx := new(context) + testCtx.packageName = "test-package-name" + testCtx.inputBuff = bytes.NewBuffer([]byte("test content")) + + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + writer := bufio.NewWriter(outFile) + defer os.RemoveAll(outDir) + Expect(writer.Buffered()).To(BeZero()) + generateMessageComment(testCtx, writer, "test-struct", "msg-name", false) + Expect(writer.Buffered()).ToNot(BeZero()) +} + +func TestGenerateMessageNameGetter(t *testing.T) { + RegisterTestingT(t) + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + writer := bufio.NewWriter(outFile) + defer os.RemoveAll(outDir) + Expect(writer.Buffered()).To(BeZero()) + generateMessageNameGetter(writer, "test-struct", "msg-name") + Expect(writer.Buffered()).ToNot(BeZero()) +} + +func TestGenerateTypeNameGetter(t *testing.T) { + RegisterTestingT(t) + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + writer := bufio.NewWriter(outFile) + defer os.RemoveAll(outDir) + Expect(writer.Buffered()).To(BeZero()) + generateTypeNameGetter(writer, "test-struct", "msg-name") + Expect(writer.Buffered()).ToNot(BeZero()) +} + +func TestGenerateCrcGetter(t *testing.T) { + RegisterTestingT(t) + outDir := "test_output_directory" + outFile, err := os.Create(outDir) + Expect(err).ShouldNot(HaveOccurred()) + writer := bufio.NewWriter(outFile) + defer os.RemoveAll(outDir) + Expect(writer.Buffered()).To(BeZero()) + generateCrcGetter(writer, "test-struct", "msg-name") + Expect(writer.Buffered()).ToNot(BeZero()) +} + +func TestTranslateVppType(t *testing.T) { + RegisterTestingT(t) + context := new(context) + typesToTranslate := []string{"u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "f64"} + expected := []string{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "float64"} + var translated []string + for _, value := range typesToTranslate { + translated = append(translated, convertToGoType(context, value, false)) + } + for index, value := range expected { + Expect(value).To(BeEquivalentTo(translated[index])) + } + +} + +func TestTranslateVppTypeArray(t *testing.T) { + RegisterTestingT(t) + context := new(context) + translated := convertToGoType(context, "u8", true) + Expect(translated).To(BeEquivalentTo("byte")) +} + +func TestTranslateVppUnknownType(t *testing.T) { + defer func() { + if recovery := recover(); recovery != nil { + t.Logf("Recovered from panic: %v", recovery) + } + }() + context := new(context) + convertToGoType(context, "?", false) +} + +func TestCamelCase(t *testing.T) { + RegisterTestingT(t) + // test camel case functionality + expected := "allYourBaseAreBelongToUs" + result := camelCaseName("all_your_base_are_belong_to_us") + Expect(expected).To(BeEquivalentTo(result)) + // test underscore + expected = "_" + result = camelCaseName(expected) + Expect(expected).To(BeEquivalentTo(result)) + // test all lower + expected = "lower" + result = camelCaseName(expected) + Expect(expected).To(BeEquivalentTo(result)) +} + +func TestCommonInitialisms(t *testing.T) { + RegisterTestingT(t) + + for key, value := range commonInitialisms { + Expect(value).ShouldNot(BeFalse()) + Expect(key).ShouldNot(BeEmpty()) + } +} +*/ diff --git a/binapigen/generator.go b/binapigen/generator.go new file mode 100644 index 0000000..9471462 --- /dev/null +++ b/binapigen/generator.go @@ -0,0 +1,172 @@ +// 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 binapigen + +import ( + "bytes" + "fmt" + "go/format" + "io/ioutil" + "os" + "path/filepath" + + "github.com/sirupsen/logrus" + + "git.fd.io/govpp.git/binapigen/vppapi" +) + +type Options struct { + VPPVersion string // version of VPP that produced API files + + FilesToGenerate []string // list of API files to generate + + ImportPrefix string // defines import path prefix for importing types + ImportTypes bool // generate packages for import types + IncludeAPIVersion bool // include constant with API version string + IncludeComments bool // include parts of original source in comments + IncludeBinapiNames bool // include binary API names as struct tag + IncludeServices bool // include service interface with client implementation + IncludeVppVersion bool // include info about used VPP version +} + +type Generator struct { + Options + + Files []*File + FilesByPath map[string]*File + FilesByName map[string]*File + + enumsByName map[string]*Enum + aliasesByName map[string]*Alias + structsByName map[string]*Struct + unionsByName map[string]*Union + + genfiles []*GenFile +} + +func New(opts Options, apifiles []*vppapi.File) (*Generator, error) { + g := &Generator{ + Options: opts, + FilesByPath: make(map[string]*File), + FilesByName: make(map[string]*File), + enumsByName: map[string]*Enum{}, + aliasesByName: map[string]*Alias{}, + structsByName: map[string]*Struct{}, + unionsByName: map[string]*Union{}, + } + + logrus.Debugf("adding %d VPP API files to generator", len(apifiles)) + for _, apifile := range apifiles { + filename := apifile.Path + if filename == "" { + filename = apifile.Name + } + if _, ok := g.FilesByPath[filename]; ok { + return nil, fmt.Errorf("duplicate file name: %q", filename) + } + if _, ok := g.FilesByName[apifile.Name]; ok { + return nil, fmt.Errorf("duplicate file: %q", apifile.Name) + } + + file, err := newFile(g, apifile) + if err != nil { + return nil, err + } + g.Files = append(g.Files, file) + g.FilesByPath[filename] = file + g.FilesByName[apifile.Name] = file + + logrus.Debugf("added file %q (path: %v)", apifile.Name, apifile.Path) + if len(file.Imports) > 0 { + logrus.Debugf(" - %d imports: %v", len(file.Imports), file.Imports) + } + } + + logrus.Debugf("Checking %d files to generate: %v", len(opts.FilesToGenerate), opts.FilesToGenerate) + for _, genfile := range opts.FilesToGenerate { + file, ok := g.FilesByPath[genfile] + if !ok { + file, ok = g.FilesByName[genfile] + if !ok { + return nil, fmt.Errorf("no API file found for: %v", genfile) + } + } + file.Generate = true + if opts.ImportTypes { + for _, impFile := range file.importedFiles(g) { + impFile.Generate = true + } + } + } + + logrus.Debugf("Resolving imported types") + for _, file := range g.Files { + if !file.Generate { + continue + } + importedFiles := file.importedFiles(g) + file.loadTypeImports(g, importedFiles) + } + + return g, nil +} + +func (g *Generator) Generate() error { + if len(g.genfiles) == 0 { + return fmt.Errorf("no files to generate") + } + + logrus.Infof("Generating %d files", len(g.genfiles)) + for _, genfile := range g.genfiles { + if err := writeSourceTo(genfile.filename, genfile.buf.Bytes()); err != nil { + return fmt.Errorf("writing source for RPC package %s failed: %v", genfile.filename, err) + } + } + return nil +} + +func (g *Generator) NewGenFile(filename string) *GenFile { + f := &GenFile{ + Generator: g, + filename: filename, + } + g.genfiles = append(g.genfiles, f) + return f +} + +func writeSourceTo(outputFile string, b []byte) error { + // create output directory + packageDir := filepath.Dir(outputFile) + if err := os.MkdirAll(packageDir, 0775); err != nil { + return fmt.Errorf("creating output dir %s failed: %v", packageDir, err) + } + + // format generated source code + gosrc, err := format.Source(b) + if err != nil { + _ = ioutil.WriteFile(outputFile, b, 0666) + return fmt.Errorf("formatting source code failed: %v", err) + } + + // write generated code to output file + if err := ioutil.WriteFile(outputFile, gosrc, 0666); err != nil { + return fmt.Errorf("writing to output file %s failed: %v", outputFile, err) + } + + lines := bytes.Count(gosrc, []byte("\n")) + logf("wrote %d lines (%d bytes) of code to: %q", lines, len(gosrc), outputFile) + + return nil +} diff --git a/binapigen/generator_test.go b/binapigen/generator_test.go new file mode 100644 index 0000000..ddbda99 --- /dev/null +++ b/binapigen/generator_test.go @@ -0,0 +1,110 @@ +// 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 binapigen + +import ( + "testing" +) + +func TestBinapiTypeSizes(t *testing.T) { + tests := []struct { + name string + input string + expsize int + }{ + {name: "basic1", input: "u8", expsize: 1}, + {name: "basic2", input: "i8", expsize: 1}, + {name: "basic3", input: "u16", expsize: 2}, + {name: "basic4", input: "i32", expsize: 4}, + {name: "string", input: "string", expsize: 1}, + {name: "invalid1", input: "x", expsize: 0}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + size := getBinapiTypeSize(test.input) + if size != test.expsize { + t.Errorf("expected %d, got %d", test.expsize, size) + } + }) + } +} + +/*func TestSizeOfType(t *testing.T) { + tests := []struct { + name string + input StructType + expsize int + }{ + { + name: "basic1", + input: StructType{ + Fields: []Field{ + {Type: "u8"}, + }, + }, + expsize: 1, + }, + { + name: "basic2", + input: Type{ + Fields: []Field{ + {Type: "u8", Length: 4}, + }, + }, + expsize: 4, + }, + { + name: "basic3", + input: Type{ + Fields: []Field{ + {Type: "u8", Length: 16}, + }, + }, + expsize: 16, + }, + { + name: "withEnum", + input: Type{ + Fields: []Field{ + {Type: "u16"}, + {Type: "vl_api_myenum_t"}, + }, + }, + expsize: 6, + }, + { + name: "invalid1", + input: Type{ + Fields: []Field{ + {Type: "x", Length: 16}, + }, + }, + expsize: 0, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + module := &File{ + Enums: []Enum{ + {Name: "myenum", Type: "u32"}, + }, + } + size := getSizeOfType(module, &test.input) + if size != test.expsize { + t.Errorf("expected %d, got %d", test.expsize, size) + } + }) + } +}*/ diff --git a/binapigen/run.go b/binapigen/run.go new file mode 100644 index 0000000..441c43d --- /dev/null +++ b/binapigen/run.go @@ -0,0 +1,89 @@ +// 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 binapigen + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/sirupsen/logrus" + + "git.fd.io/govpp.git/binapigen/vppapi" +) + +var debugMode = true + +func logf(f string, v ...interface{}) { + if debugMode { + logrus.Debugf(f, v...) + } +} + +func GenerateBinapiFile(gen *Generator, file *File, outputDir string) *GenFile { + packageDir := filepath.Join(outputDir, file.PackageName) + filename := filepath.Join(packageDir, file.PackageName+outputFileExt) + + g := gen.NewGenFile(filename) + g.file = file + g.packageDir = filepath.Join(outputDir, file.PackageName) + + generatePackage(g, &g.buf) + + return g +} + +func GenerateRPC(gen *Generator, file *File, outputDir string) *GenFile { + packageDir := filepath.Join(outputDir, file.PackageName) + filename := filepath.Join(packageDir, file.PackageName+rpcFileSuffix+outputFileExt) + + g := gen.NewGenFile(filename) + g.file = file + g.packageDir = filepath.Join(outputDir, file.PackageName) + + generatePackageRPC(g, &g.buf) + + return g +} + +func Run(apiDir string, opts Options, f func(*Generator) error) { + if err := run(apiDir, opts, f); err != nil { + fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err) + os.Exit(1) + } +} + +func run(apiDir string, opts Options, f func(*Generator) error) error { + // parse API files + apifiles, err := vppapi.ParseDir(apiDir) + if err != nil { + return err + } + + g, err := New(opts, apifiles) + if err != nil { + return err + } + + if err := f(g); err != nil { + return err + } + + if err = g.Generate(); err != nil { + return err + } + + return nil +} diff --git a/binapigen/types.go b/binapigen/types.go new file mode 100644 index 0000000..0dbbeb1 --- /dev/null +++ b/binapigen/types.go @@ -0,0 +1,271 @@ +// 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 binapigen + +import ( + "strings" + + "github.com/sirupsen/logrus" +) + +// define api +const ( + defineApiPrefix = "vl_api_" + defineApiSuffix = "_t" +) + +// BaseType represents base types in VPP binary API. +type BaseType int + +const ( + U8 BaseType = iota + 1 + I8 + U16 + I16 + U32 + I32 + U64 + I64 + F64 + BOOL + STRING +) + +var ( + BaseTypes = map[BaseType]string{ + U8: "u8", + I8: "i8", + U16: "u16", + I16: "i16", + U32: "u32", + I32: "i32", + U64: "u64", + I64: "i64", + F64: "f64", + BOOL: "bool", + STRING: "string", + } + BaseTypeNames = map[string]BaseType{ + "u8": U8, + "i8": I8, + "u16": U16, + "i16": I16, + "u32": U32, + "i32": I32, + "u64": U64, + "i64": I64, + "f64": F64, + "bool": BOOL, + "string": STRING, + } +) + +var BaseTypeSizes = map[BaseType]int{ + U8: 1, + I8: 1, + U16: 2, + I16: 2, + U32: 4, + I32: 4, + U64: 8, + I64: 8, + F64: 8, + BOOL: 1, + STRING: 1, +} + +type Kind int + +const ( + _ = iota + Uint8Kind + Int8Kind + Uint16Kind + Int16Kind + Uint32Kind + Int32Kind + Uint64Kind + Int64Kind + Float64Kind + BoolKind + StringKind + EnumKind + AliasKind + StructKind + UnionKind + MessageKind +) + +// toApiType returns name that is used as type reference in VPP binary API +func toApiType(name string) string { + return defineApiPrefix + name + defineApiSuffix +} + +func fromApiType(typ string) string { + name := typ + name = strings.TrimPrefix(name, defineApiPrefix) + name = strings.TrimSuffix(name, defineApiSuffix) + return name +} + +func getSizeOfType(module *File, typ *Struct) (size int) { + for _, field := range typ.Fields { + enum := getEnumByRef(module, field.Type) + if enum != nil { + size += getSizeOfBinapiTypeLength(enum.Type, field.Length) + continue + } + size += getSizeOfBinapiTypeLength(field.Type, field.Length) + } + return size +} + +func getEnumByRef(file *File, ref string) *Enum { + for _, typ := range file.Enums { + if ref == toApiType(typ.Name) { + return typ + } + } + return nil +} + +func getTypeByRef(file *File, ref string) *Struct { + for _, typ := range file.Structs { + if ref == toApiType(typ.Name) { + return typ + } + } + return nil +} + +func getAliasByRef(file *File, ref string) *Alias { + for _, alias := range file.Aliases { + if ref == toApiType(alias.Name) { + return alias + } + } + return nil +} + +func getUnionByRef(file *File, ref string) *Union { + for _, union := range file.Unions { + if ref == toApiType(union.Name) { + return union + } + } + return nil +} + +func getBinapiTypeSize(binapiType string) (size int) { + typName := BaseTypeNames[binapiType] + return BaseTypeSizes[typName] +} + +// binapiTypes is a set of types used VPP binary API for translation to Go types +var binapiTypes = map[string]string{ + "u8": "uint8", + "i8": "int8", + "u16": "uint16", + "i16": "int16", + "u32": "uint32", + "i32": "int32", + "u64": "uint64", + "i64": "int64", + "f64": "float64", +} +var BaseTypesGo = map[BaseType]string{ + U8: "uint8", + I8: "int8", + U16: "uint16", + I16: "int16", + U32: "uint32", + I32: "int32", + U64: "uint64", + I64: "int64", + F64: "float64", + BOOL: "bool", + STRING: "string", +} + +func getActualType(file *File, typ string) (actual string) { + for _, enum := range file.Enums { + if enum.GoName == typ { + return enum.Type + } + } + for _, alias := range file.Aliases { + if alias.GoName == typ { + return alias.Type + } + } + return typ +} + +// convertToGoType translates the VPP binary API type into Go type +func convertToGoType(file *File, binapiType string) (typ string) { + if t, ok := binapiTypes[binapiType]; ok { + // basic types + typ = t + } else if r, ok := file.refmap[binapiType]; ok { + // specific types (enums/types/unions) + typ = camelCaseName(r) + } else { + switch binapiType { + case "bool", "string": + typ = binapiType + default: + // fallback type + logrus.Warnf("found unknown VPP binary API type %q, using byte", binapiType) + typ = "byte" + } + } + return typ +} + +func getSizeOfBinapiTypeLength(typ string, length int) (size int) { + if n := getBinapiTypeSize(typ); n > 0 { + if length > 0 { + return n * length + } else { + return n + } + } + + return +} + +func getUnionSize(file *File, union *Union) (maxSize int) { + for _, field := range union.Fields { + typ := getTypeByRef(file, field.Type) + if typ != nil { + if size := getSizeOfType(file, typ); size > maxSize { + maxSize = size + } + continue + } + alias := getAliasByRef(file, field.Type) + if alias != nil { + if size := getSizeOfBinapiTypeLength(alias.Type, alias.Length); size > maxSize { + maxSize = size + } + continue + } else { + logf("no type or alias found for union %s field type %q", union.Name, field.Type) + continue + } + } + logf("getUnionSize: %s %+v max=%v", union.Name, union.Fields, maxSize) + return +} diff --git a/binapigen/validate.go b/binapigen/validate.go new file mode 100644 index 0000000..2dae903 --- /dev/null +++ b/binapigen/validate.go @@ -0,0 +1,66 @@ +// 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 binapigen + +import ( + "strings" + + "git.fd.io/govpp.git/binapigen/vppapi" + "github.com/sirupsen/logrus" +) + +const ( + serviceEventPrefix = "want_" + serviceDumpSuffix = "_dump" + serviceDetailsSuffix = "_details" + serviceReplySuffix = "_reply" +) + +func validateService(svc vppapi.Service) { + for _, rpc := range svc.RPCs { + validateRPC(rpc) + } +} + +func validateRPC(rpc vppapi.RPC) { + if len(rpc.Events) > 0 { + // EVENT service + if !strings.HasPrefix(rpc.RequestMsg, serviceEventPrefix) { + logrus.Warnf("unusual EVENTS service: %+v\n"+ + "- events service %q does not have %q prefix in request.", + rpc, rpc.Name, serviceEventPrefix) + } + } else if rpc.Stream { + // STREAM service + if !strings.HasSuffix(rpc.RequestMsg, serviceDumpSuffix) { + logrus.Warnf("unusual STREAM service: %+v\n"+ + "- stream service %q does not have %q suffix in request.", + rpc, rpc.Name, serviceDumpSuffix) + } + if !strings.HasSuffix(rpc.ReplyMsg, serviceDetailsSuffix) && !strings.HasSuffix(rpc.StreamMsg, serviceDetailsSuffix) { + logrus.Warnf("unusual STREAM service: %+v\n"+ + "- stream service %q does not have %q suffix in reply or stream msg.", + rpc, rpc.Name, serviceDetailsSuffix) + } + } else if rpc.ReplyMsg != "" { + // REQUEST service + // some messages might have `null` reply (for example: memclnt) + if !strings.HasSuffix(rpc.ReplyMsg, serviceReplySuffix) { + logrus.Warnf("unusual REQUEST service: %+v\n"+ + "- service %q does not have %q suffix in reply.", + rpc, rpc.Name, serviceReplySuffix) + } + } +} diff --git a/binapigen/vppapi/api.go b/binapigen/vppapi/api.go new file mode 100644 index 0000000..06d9046 --- /dev/null +++ b/binapigen/vppapi/api.go @@ -0,0 +1,94 @@ +// 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 + +type File struct { + Name string + Path string + + CRC string + Options map[string]string `json:",omitempty"` + + Imports []string `json:",omitempty"` + + AliasTypes []AliasType `json:",omitempty"` + EnumTypes []EnumType `json:",omitempty"` + StructTypes []StructType `json:",omitempty"` + UnionTypes []UnionType `json:",omitempty"` + Messages []Message `json:",omitempty"` + Service *Service `json:",omitempty"` +} + +func (x File) Version() string { + if x.Options != nil { + return x.Options[fileOptionVersion] + } + return "" +} + +type AliasType struct { + Name string + Type string + Length int `json:",omitempty"` +} + +type EnumType struct { + Name string + Type string + Entries []EnumEntry +} + +type EnumEntry struct { + Name string + Value uint32 +} + +type StructType struct { + Name string + Fields []Field +} + +type UnionType struct { + Name string + Fields []Field +} + +type Message struct { + Name string + Fields []Field + CRC string +} + +type Field struct { + Name string + Type string + Length int `json:",omitempty"` + Array bool `json:",omitempty"` + SizeFrom string `json:",omitempty"` + Meta map[string]interface{} `json:",omitempty"` +} + +type Service struct { + RPCs []RPC `json:",omitempty"` +} + +type RPC struct { + Name string + RequestMsg string + ReplyMsg string + Stream bool `json:",omitempty"` + StreamMsg string `json:",omitempty"` + Events []string `json:",omitempty"` +} diff --git a/binapigen/vppapi/integration_test.go b/binapigen/vppapi/integration_test.go new file mode 100644 index 0000000..142017a --- /dev/null +++ b/binapigen/vppapi/integration_test.go @@ -0,0 +1,43 @@ +// 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. + +// +build integration + +package vppapi_test + +import ( + "encoding/json" + "testing" + + "git.fd.io/govpp.git/binapigen/vppapi" +) + +func TestParse(t *testing.T) { + files, err := vppapi.Parse() + if err != nil { + t.Fatal(err) + } + + for _, file := range files { + //t.Logf(" - %s: %+v", path, module) + b, err := json.MarshalIndent(file, "\t", " ") + if err != nil { + t.Fatal(err) + } + t.Logf(" - %s:\n%s", file.Name, b) + } + + t.Logf("parsed %d files", len(files)) + +} 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 +} diff --git a/binapigen/vppapi/parser.go b/binapigen/vppapi/parser.go new file mode 100644 index 0000000..312dd0e --- /dev/null +++ b/binapigen/vppapi/parser.go @@ -0,0 +1,111 @@ +// 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 ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" +) + +const ( + DefaultAPIDir = "/usr/share/vpp/api" +) + +const apifileSuffixJson = ".api.json" + +// FindFiles returns all input files located in specified directory +func FindFiles(dir string, deep int) (paths []string, err error) { + entries, err := ioutil.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf("reading directory %s failed: %v", dir, err) + } + for _, e := range entries { + if e.IsDir() && deep > 0 { + nestedDir := filepath.Join(dir, e.Name()) + if nested, err := FindFiles(nestedDir, deep-1); err != nil { + return nil, err + } else { + paths = append(paths, nested...) + } + } else if strings.HasSuffix(e.Name(), apifileSuffixJson) { + paths = append(paths, filepath.Join(dir, e.Name())) + } + } + return paths, nil +} + +func Parse() ([]*File, error) { + return ParseDir(DefaultAPIDir) +} + +func ParseDir(apidir string) ([]*File, error) { + files, err := FindFiles(apidir, 1) + if err != nil { + return nil, err + } + + logrus.Infof("found %d files in API dir %q", len(files), apidir) + + var modules []*File + + for _, file := range files { + module, err := ParseFile(file) + if err != nil { + return nil, err + } + modules = append(modules, module) + } + + return modules, nil +} + +// ParseFile parses API file contents and returns File. +func ParseFile(apifile string) (*File, error) { + if !strings.HasSuffix(apifile, apifileSuffixJson) { + return nil, fmt.Errorf("unsupported file format: %q", apifile) + } + + data, err := ioutil.ReadFile(apifile) + if err != nil { + return nil, fmt.Errorf("reading file %s failed: %v", apifile, err) + } + + base := filepath.Base(apifile) + name := base[:strings.Index(base, ".")] + + logf("parsing file %q", base) + + module, err := ParseRaw(data) + if err != nil { + return nil, fmt.Errorf("parsing file %s failed: %v", base, err) + } + module.Name = name + module.Path = apifile + + return module, nil +} + +func ParseRaw(data []byte) (file *File, err error) { + file, err = parseJSON(data) + if err != nil { + return nil, err + } + + return file, nil +} diff --git a/binapigen/vppapi/parser_test.go b/binapigen/vppapi/parser_test.go new file mode 100644 index 0000000..2dc82e4 --- /dev/null +++ b/binapigen/vppapi/parser_test.go @@ -0,0 +1,114 @@ +// 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" + "io/ioutil" + "testing" + + . "github.com/onsi/gomega" +) + +func TestGetInputFiles(t *testing.T) { + RegisterTestingT(t) + + result, err := FindFiles("testdata", 1) + Expect(err).ShouldNot(HaveOccurred()) + Expect(result).To(HaveLen(5)) + for _, file := range result { + Expect(file).To(BeAnExistingFile()) + } +} + +func TestGetInputFilesError(t *testing.T) { + RegisterTestingT(t) + + result, err := FindFiles("nonexisting_directory", 1) + Expect(err).Should(HaveOccurred()) + Expect(result).To(BeNil()) +} + +func TestReadJson(t *testing.T) { + RegisterTestingT(t) + + inputData, err := ioutil.ReadFile("testdata/af_packet.api.json") + Expect(err).ShouldNot(HaveOccurred()) + result, err := parseJSON(inputData) + Expect(err).ShouldNot(HaveOccurred()) + Expect(result).ToNot(BeNil()) + Expect(result.EnumTypes).To(HaveLen(0)) + Expect(result.StructTypes).To(HaveLen(0)) + Expect(result.Messages).To(HaveLen(6)) + Expect(result.Service.RPCs).To(HaveLen(3)) +} + +func TestReadJsonError(t *testing.T) { + RegisterTestingT(t) + + inputData, err := ioutil.ReadFile("testdata/input-read-json-error.json") + Expect(err).ShouldNot(HaveOccurred()) + result, err := parseJSON(inputData) + Expect(err).Should(HaveOccurred()) + Expect(result).To(BeNil()) +} + +func TestParseFile(t *testing.T) { + module, err := ParseFile("testdata/vpe.api.json") + if err != nil { + t.Fatal("unexpected error:", err) + } + + b, err := json.MarshalIndent(module, "", " ") + if err != nil { + t.Fatal(err) + } + t.Logf("parsed module: %s", b) + + if module.Name != "vpe" { + t.Errorf("expected Name=%s, got %v", "vpe", module.Name) + } + if module.CRC != "0xbd2c94f4" { + t.Errorf("expected CRC=%s, got %v", "0xbd2c94f4", module.CRC) + } + if module.Version() != "1.6.1" { + t.Errorf("expected Version=%s, got %v", "1.6.1", module.Version()) + } + if len(module.Imports) == 0 { + t.Errorf("expected imports, got none") + } + if len(module.Options) == 0 { + t.Errorf("expected options, got none") + } + if len(module.AliasTypes) == 0 { + t.Errorf("expected aliases, got none") + } + if len(module.StructTypes) == 0 { + t.Errorf("expected types, got none") + } + if len(module.Service.RPCs) == 0 { + t.Errorf("expected service method, got none") + } + if len(module.Messages) == 0 { + t.Errorf("expected messages, got none") + } +} + +func TestParseFileUnsupported(t *testing.T) { + _, err := ParseFile("testdata/input.txt") + if err == nil { + t.Fatal("expected error") + } +} diff --git a/binapigen/vppapi/testdata/acl.api.json b/binapigen/vppapi/testdata/acl.api.json new file mode 100644 index 0000000..4c6653c --- /dev/null +++ b/binapigen/vppapi/testdata/acl.api.json @@ -0,0 +1,929 @@ +{ + "services": [ + { + "acl_interface_add_del": { + "reply": "acl_interface_add_del_reply" + } + }, + { + "acl_del": { + "reply": "acl_del_reply" + } + }, + { + "macip_acl_del": { + "reply": "macip_acl_del_reply" + } + }, + { + "acl_plugin_get_version": { + "reply": "acl_plugin_get_version_reply" + } + }, + { + "macip_acl_interface_add_del": { + "reply": "macip_acl_interface_add_del_reply" + } + }, + { + "acl_interface_set_acl_list": { + "reply": "acl_interface_set_acl_list_reply" + } + }, + { + "acl_dump": { + "reply": "acl_details", + "stream": true + } + }, + { + "acl_interface_list_dump": { + "reply": "acl_interface_list_details", + "stream": true + } + }, + { + "macip_acl_interface_list_dump": { + "reply": "macip_acl_interface_list_details", + "stream": true + } + }, + { + "acl_add_replace": { + "reply": "acl_add_replace_reply" + } + }, + { + "acl_plugin_control_ping": { + "reply": "acl_plugin_control_ping_reply" + } + }, + { + "macip_acl_interface_get": { + "reply": "macip_acl_interface_get_reply" + } + }, + { + "macip_acl_add": { + "reply": "macip_acl_add_reply" + } + }, + { + "macip_acl_add_replace": { + "reply": "macip_acl_add_replace_reply" + } + }, + { + "macip_acl_dump": { + "reply": "macip_acl_details", + "stream": true + } + } + ], + "vl_api_version": "0x1db2ece9", + "enums": [], + "messages": [ + [ + "acl_plugin_get_version", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "acl_plugin_get_version_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "major" + ], + [ + "u32", + "minor" + ], + { + "crc": "0x9b32cf86" + } + ], + [ + "acl_plugin_control_ping", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "acl_plugin_control_ping_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "vpe_pid" + ], + { + "crc": "0xf6b0b8ca" + } + ], + [ + "acl_add_replace", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + [ + "u8", + "tag", + 64 + ], + [ + "u32", + "count" + ], + [ + "vl_api_acl_rule_t", + "r", + 0, + "count" + ], + { + "crc": "0xe839997e" + } + ], + [ + "acl_add_replace_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xac407b0c" + } + ], + [ + "acl_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + { + "crc": "0xef34fea4" + } + ], + [ + "acl_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "acl_interface_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "is_input" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "acl_index" + ], + { + "crc": "0x0b2aedd1" + } + ], + [ + "acl_interface_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "acl_interface_set_acl_list", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "count" + ], + [ + "u8", + "n_input" + ], + [ + "u32", + "acls", + 0, + "count" + ], + { + "crc": "0x8baece38" + } + ], + [ + "acl_interface_set_acl_list_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "acl_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + { + "crc": "0xef34fea4" + } + ], + [ + "acl_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + [ + "u8", + "tag", + 64 + ], + [ + "u32", + "count" + ], + [ + "vl_api_acl_rule_t", + "r", + 0, + "count" + ], + { + "crc": "0x5bd895be" + } + ], + [ + "acl_interface_list_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + { + "crc": "0x529cb13f" + } + ], + [ + "acl_interface_list_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "count" + ], + [ + "u8", + "n_input" + ], + [ + "u32", + "acls", + 0, + "count" + ], + { + "crc": "0xd5e80809" + } + ], + [ + "macip_acl_add", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "tag", + 64 + ], + [ + "u32", + "count" + ], + [ + "vl_api_macip_acl_rule_t", + "r", + 0, + "count" + ], + { + "crc": "0xb3d3d65a" + } + ], + [ + "macip_acl_add_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xac407b0c" + } + ], + [ + "macip_acl_add_replace", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + [ + "u8", + "tag", + 64 + ], + [ + "u32", + "count" + ], + [ + "vl_api_macip_acl_rule_t", + "r", + 0, + "count" + ], + { + "crc": "0xa0e8c01b" + } + ], + [ + "macip_acl_add_replace_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xac407b0c" + } + ], + [ + "macip_acl_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + { + "crc": "0xef34fea4" + } + ], + [ + "macip_acl_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "macip_acl_interface_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "is_add" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "acl_index" + ], + { + "crc": "0x6a6be97c" + } + ], + [ + "macip_acl_interface_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "macip_acl_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + { + "crc": "0xef34fea4" + } + ], + [ + "macip_acl_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "acl_index" + ], + [ + "u8", + "tag", + 64 + ], + [ + "u32", + "count" + ], + [ + "vl_api_macip_acl_rule_t", + "r", + 0, + "count" + ], + { + "crc": "0xdd2b55ba" + } + ], + [ + "macip_acl_interface_get", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "macip_acl_interface_get_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "count" + ], + [ + "u32", + "acls", + 0, + "count" + ], + { + "crc": "0xaccf9b05" + } + ], + [ + "macip_acl_interface_list_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + { + "crc": "0x529cb13f" + } + ], + [ + "macip_acl_interface_list_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "count" + ], + [ + "u32", + "acls", + 0, + "count" + ], + { + "crc": "0x29783fa0" + } + ] + ], + "types": [ + [ + "acl_rule", + [ + "u8", + "is_permit" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "src_ip_addr", + 16 + ], + [ + "u8", + "src_ip_prefix_len" + ], + [ + "u8", + "dst_ip_addr", + 16 + ], + [ + "u8", + "dst_ip_prefix_len" + ], + [ + "u8", + "proto" + ], + [ + "u16", + "srcport_or_icmptype_first" + ], + [ + "u16", + "srcport_or_icmptype_last" + ], + [ + "u16", + "dstport_or_icmpcode_first" + ], + [ + "u16", + "dstport_or_icmpcode_last" + ], + [ + "u8", + "tcp_flags_mask" + ], + [ + "u8", + "tcp_flags_value" + ], + { + "crc": "0x6f99bf4d" + } + ], + [ + "macip_acl_rule", + [ + "u8", + "is_permit" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "src_mac", + 6 + ], + [ + "u8", + "src_mac_mask", + 6 + ], + [ + "u8", + "src_ip_addr", + 16 + ], + [ + "u8", + "src_ip_prefix_len" + ], + { + "crc": "0x70589f1e" + } + ] + ] +} diff --git a/binapigen/vppapi/testdata/af_packet.api.json b/binapigen/vppapi/testdata/af_packet.api.json new file mode 100644 index 0000000..dc0de16 --- /dev/null +++ b/binapigen/vppapi/testdata/af_packet.api.json @@ -0,0 +1,157 @@ +{ + "services": { + "af_packet_delete": { + "reply": "af_packet_delete_reply" + }, + "af_packet_create": { + "reply": "af_packet_create_reply" + }, + "af_packet_set_l4_cksum_offload": { + "reply": "af_packet_set_l4_cksum_offload_reply" + } + }, + "vl_api_version": "0x8957ca8b", + "enums": [], + "messages": [ + [ + "af_packet_create", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "host_if_name", + 64 + ], + [ + "u8", + "hw_addr", + 6 + ], + [ + "u8", + "use_random_hw_addr" + ], + { + "crc": "0x6d5d30d6" + } + ], + [ + "af_packet_create_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "sw_if_index" + ], + { + "crc": "0xfda5941f" + } + ], + [ + "af_packet_delete", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "host_if_name", + 64 + ], + { + "crc": "0x3efceda3" + } + ], + [ + "af_packet_delete_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "af_packet_set_l4_cksum_offload", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "sw_if_index" + ], + [ + "u8", + "set" + ], + { + "crc": "0x86538585" + } + ], + [ + "af_packet_set_l4_cksum_offload_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ] + ], + "types": [] +} diff --git a/binapigen/vppapi/testdata/input-generate-error.json b/binapigen/vppapi/testdata/input-generate-error.json new file mode 100644 index 0000000..d5df76e --- /dev/null +++ b/binapigen/vppapi/testdata/input-generate-error.json @@ -0,0 +1,3 @@ +{ + "key": "value" +} \ No newline at end of file diff --git a/binapigen/vppapi/testdata/input-read-json-error.json b/binapigen/vppapi/testdata/input-read-json-error.json new file mode 100644 index 0000000..02691e3 --- /dev/null +++ b/binapigen/vppapi/testdata/input-read-json-error.json @@ -0,0 +1 @@ +% \ No newline at end of file diff --git a/binapigen/vppapi/testdata/input.txt b/binapigen/vppapi/testdata/input.txt new file mode 100644 index 0000000..e69de29 diff --git a/binapigen/vppapi/testdata/ip.api.json b/binapigen/vppapi/testdata/ip.api.json new file mode 100644 index 0000000..530b6d6 --- /dev/null +++ b/binapigen/vppapi/testdata/ip.api.json @@ -0,0 +1,2246 @@ +{ + "services": [ + { + "ip_source_and_port_range_check_add_del": { + "reply": "ip_source_and_port_range_check_add_del_reply" + } + }, + { + "ip6_fib_dump": { + "reply": "ip6_fib_details", + "stream": true + } + }, + { + "want_ip6_nd_events": { + "reply": "want_ip6_nd_events_reply" + } + }, + { + "ip_punt_police": { + "reply": "ip_punt_police_reply" + } + }, + { + "set_arp_neighbor_limit": { + "reply": "set_arp_neighbor_limit_reply" + } + }, + { + "ip6nd_proxy_add_del": { + "reply": "ip6nd_proxy_add_del_reply" + } + }, + { + "ioam_disable": { + "reply": "ioam_disable_reply" + } + }, + { + "ip_table_add_del": { + "reply": "ip_table_add_del_reply" + } + }, + { + "ip_neighbor_dump": { + "reply": "ip_neighbor_details", + "stream": true + } + }, + { + "ip4_arp_event": { + "reply": null + } + }, + { + "ip_punt_redirect": { + "reply": "ip_punt_redirect_reply" + } + }, + { + "sw_interface_ip6nd_ra_prefix": { + "reply": "sw_interface_ip6nd_ra_prefix_reply" + } + }, + { + "reset_fib": { + "reply": "reset_fib_reply" + } + }, + { + "ip6_mfib_dump": { + "reply": "ip6_mfib_details", + "stream": true + } + }, + { + "sw_interface_ip6nd_ra_config": { + "reply": "sw_interface_ip6nd_ra_config_reply" + } + }, + { + "sw_interface_ip6_enable_disable": { + "reply": "sw_interface_ip6_enable_disable_reply" + } + }, + { + "sw_interface_ip6_set_link_local_address": { + "reply": "sw_interface_ip6_set_link_local_address_reply" + } + }, + { + "mfib_signal_dump": { + "reply": "mfib_signal_details", + "stream": true + } + }, + { + "ip_container_proxy_add_del": { + "reply": "ip_container_proxy_add_del_reply" + } + }, + { + "ip_mfib_dump": { + "reply": "ip_mfib_details", + "stream": true + } + }, + { + "ip_address_dump": { + "reply": "ip_address_details", + "stream": true + } + }, + { + "ip_dump": { + "reply": "ip_details", + "stream": true + } + }, + { + "ip_neighbor_add_del": { + "reply": "ip_neighbor_add_del_reply" + } + }, + { + "proxy_arp_intfc_enable_disable": { + "reply": "proxy_arp_intfc_enable_disable_reply" + } + }, + { + "proxy_arp_add_del": { + "reply": "proxy_arp_add_del_reply" + } + }, + { + "ip_add_del_route": { + "reply": "ip_add_del_route_reply" + } + }, + { + "ip6nd_proxy_dump": { + "reply": "ip6nd_proxy_details", + "stream": true + } + }, + { + "ip_fib_dump": { + "reply": "ip_fib_details", + "stream": true + } + }, + { + "want_ip4_arp_events": { + "reply": "want_ip4_arp_events_reply" + } + }, + { + "ioam_enable": { + "reply": "ioam_enable_reply" + } + }, + { + "ip6_nd_event": { + "reply": null + } + }, + { + "ip_mroute_add_del": { + "reply": "ip_mroute_add_del_reply" + } + }, + { + "ip_source_and_port_range_check_interface_add_del": { + "reply": "ip_source_and_port_range_check_interface_add_del_reply" + } + }, + { + "set_ip_flow_hash": { + "reply": "set_ip_flow_hash_reply" + } + } + ], + "vl_api_version": "0xb395c625", + "enums": [], + "messages": [ + [ + "ip_table_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "table_id" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "name", + 64 + ], + { + "crc": "0x0240c89d" + } + ], + [ + "ip_table_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_fib_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "ip_fib_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "table_id" + ], + [ + "u8", + "table_name", + 64 + ], + [ + "u8", + "address_length" + ], + [ + "u8", + "address", + 4 + ], + [ + "u32", + "count" + ], + [ + "vl_api_fib_path_t", + "path", + 0, + "count" + ], + { + "crc": "0x99dfd73b" + } + ], + [ + "ip6_fib_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "ip6_fib_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "table_id" + ], + [ + "u8", + "table_name", + 64 + ], + [ + "u8", + "address_length" + ], + [ + "u8", + "address", + 16 + ], + [ + "u32", + "count" + ], + [ + "vl_api_fib_path_t", + "path", + 0, + "count" + ], + { + "crc": "0xabd0060e" + } + ], + [ + "ip_neighbor_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "is_ipv6" + ], + { + "crc": "0x6b7bcd0a" + } + ], + [ + "ip_neighbor_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "is_static" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "mac_address", + 6 + ], + [ + "u8", + "ip_address", + 16 + ], + { + "crc": "0x85e32a72" + } + ], + [ + "ip_neighbor_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "is_static" + ], + [ + "u8", + "is_no_adj_fib" + ], + [ + "u8", + "mac_address", + 6 + ], + [ + "u8", + "dst_address", + 16 + ], + { + "crc": "0x4711eb25" + } + ], + [ + "ip_neighbor_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "set_ip_flow_hash", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "vrf_id" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "src" + ], + [ + "u8", + "dst" + ], + [ + "u8", + "sport" + ], + [ + "u8", + "dport" + ], + [ + "u8", + "proto" + ], + [ + "u8", + "reverse" + ], + { + "crc": "0x32ebf737" + } + ], + [ + "set_ip_flow_hash_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "sw_interface_ip6nd_ra_config", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "suppress" + ], + [ + "u8", + "managed" + ], + [ + "u8", + "other" + ], + [ + "u8", + "ll_option" + ], + [ + "u8", + "send_unicast" + ], + [ + "u8", + "cease" + ], + [ + "u8", + "is_no" + ], + [ + "u8", + "default_router" + ], + [ + "u32", + "max_interval" + ], + [ + "u32", + "min_interval" + ], + [ + "u32", + "lifetime" + ], + [ + "u32", + "initial_count" + ], + [ + "u32", + "initial_interval" + ], + { + "crc": "0xc3f02daa" + } + ], + [ + "sw_interface_ip6nd_ra_config_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "sw_interface_ip6nd_ra_prefix", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "address", + 16 + ], + [ + "u8", + "address_length" + ], + [ + "u8", + "use_default" + ], + [ + "u8", + "no_advertise" + ], + [ + "u8", + "off_link" + ], + [ + "u8", + "no_autoconfig" + ], + [ + "u8", + "no_onlink" + ], + [ + "u8", + "is_no" + ], + [ + "u32", + "val_lifetime" + ], + [ + "u32", + "pref_lifetime" + ], + { + "crc": "0xca763c9a" + } + ], + [ + "sw_interface_ip6nd_ra_prefix_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip6nd_proxy_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "is_del" + ], + [ + "u8", + "address", + 16 + ], + { + "crc": "0xd95f0fa0" + } + ], + [ + "ip6nd_proxy_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip6nd_proxy_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "address", + 16 + ], + { + "crc": "0xd73bf1ab" + } + ], + [ + "ip6nd_proxy_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "sw_interface_ip6_enable_disable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "enable" + ], + { + "crc": "0xa36fadc0" + } + ], + [ + "sw_interface_ip6_enable_disable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "sw_interface_ip6_set_link_local_address", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "address", + 16 + ], + { + "crc": "0xd73bf1ab" + } + ], + [ + "sw_interface_ip6_set_link_local_address_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_add_del_route", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "next_hop_sw_if_index" + ], + [ + "u32", + "table_id" + ], + [ + "u32", + "classify_table_index" + ], + [ + "u32", + "next_hop_table_id" + ], + [ + "u32", + "next_hop_id" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "is_drop" + ], + [ + "u8", + "is_unreach" + ], + [ + "u8", + "is_prohibit" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "is_local" + ], + [ + "u8", + "is_classify" + ], + [ + "u8", + "is_multipath" + ], + [ + "u8", + "is_resolve_host" + ], + [ + "u8", + "is_resolve_attached" + ], + [ + "u8", + "is_dvr" + ], + [ + "u8", + "is_source_lookup" + ], + [ + "u8", + "is_udp_encap" + ], + [ + "u8", + "next_hop_weight" + ], + [ + "u8", + "next_hop_preference" + ], + [ + "u8", + "next_hop_proto" + ], + [ + "u8", + "dst_address_length" + ], + [ + "u8", + "dst_address", + 16 + ], + [ + "u8", + "next_hop_address", + 16 + ], + [ + "u8", + "next_hop_n_out_labels" + ], + [ + "u32", + "next_hop_via_label" + ], + [ + "u32", + "next_hop_out_label_stack", + 0, + "next_hop_n_out_labels" + ], + { + "crc": "0xc85f8290" + } + ], + [ + "ip_add_del_route_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_mroute_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "next_hop_sw_if_index" + ], + [ + "u32", + "table_id" + ], + [ + "u32", + "entry_flags" + ], + [ + "u32", + "itf_flags" + ], + [ + "u32", + "rpf_id" + ], + [ + "u32", + "bier_imp" + ], + [ + "u16", + "grp_address_length" + ], + [ + "u8", + "next_hop_afi" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "is_local" + ], + [ + "u8", + "grp_address", + 16 + ], + [ + "u8", + "src_address", + 16 + ], + { + "crc": "0xc37112f7" + } + ], + [ + "ip_mroute_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_mfib_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "ip_mfib_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "table_id" + ], + [ + "u32", + "entry_flags" + ], + [ + "u32", + "rpf_id" + ], + [ + "u8", + "address_length" + ], + [ + "u8", + "grp_address", + 4 + ], + [ + "u8", + "src_address", + 4 + ], + [ + "u32", + "count" + ], + [ + "vl_api_fib_path_t", + "path", + 0, + "count" + ], + { + "crc": "0x5e530d5e" + } + ], + [ + "ip6_mfib_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "ip6_mfib_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "table_id" + ], + [ + "u8", + "address_length" + ], + [ + "u8", + "grp_address", + 16 + ], + [ + "u8", + "src_address", + 16 + ], + [ + "u32", + "count" + ], + [ + "vl_api_fib_path_t", + "path", + 0, + "count" + ], + { + "crc": "0xe02dcb4b" + } + ], + [ + "ip_address_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "ip", + 16 + ], + [ + "u8", + "prefix_length" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "is_ipv6" + ], + { + "crc": "0xbc7442f2" + } + ], + [ + "ip_address_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "is_ipv6" + ], + { + "crc": "0x6b7bcd0a" + } + ], + [ + "ip_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "is_ipv6" + ], + { + "crc": "0x452ffc5a" + } + ], + [ + "ip_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "is_ipv6" + ], + { + "crc": "0xde883da4" + } + ], + [ + "mfib_signal_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "mfib_signal_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "table_id" + ], + [ + "u16", + "grp_address_len" + ], + [ + "u8", + "grp_address", + 16 + ], + [ + "u8", + "src_address", + 16 + ], + [ + "u16", + "ip_packet_len" + ], + [ + "u8", + "ip_packet_data", + 256 + ], + { + "crc": "0x791bbeab" + } + ], + [ + "ip_punt_police", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "policer_index" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "is_ip6" + ], + { + "crc": "0x38691592" + } + ], + [ + "ip_punt_police_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_punt_redirect", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "rx_sw_if_index" + ], + [ + "u32", + "tx_sw_if_index" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "is_ip6" + ], + [ + "u8", + "nh", + 16 + ], + { + "crc": "0x996b6603" + } + ], + [ + "ip_punt_redirect_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_container_proxy_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "ip", + 16 + ], + [ + "u8", + "is_ip4" + ], + [ + "u8", + "plen" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "is_add" + ], + { + "crc": "0x0a355d39" + } + ], + [ + "ip_container_proxy_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_source_and_port_range_check_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "mask_length" + ], + [ + "u8", + "address", + 16 + ], + [ + "u8", + "number_of_ranges" + ], + [ + "u16", + "low_ports", + 32 + ], + [ + "u16", + "high_ports", + 32 + ], + [ + "u32", + "vrf_id" + ], + { + "crc": "0x03d6b03a" + } + ], + [ + "ip_source_and_port_range_check_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_source_and_port_range_check_interface_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "is_add" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "tcp_in_vrf_id" + ], + [ + "u32", + "tcp_out_vrf_id" + ], + [ + "u32", + "udp_in_vrf_id" + ], + [ + "u32", + "udp_out_vrf_id" + ], + { + "crc": "0x6966bc44" + } + ], + [ + "ip_source_and_port_range_check_interface_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "want_ip4_arp_events", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "enable_disable" + ], + [ + "u32", + "pid" + ], + [ + "u32", + "address" + ], + { + "crc": "0x77e06379" + } + ], + [ + "want_ip4_arp_events_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip4_arp_event", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "address" + ], + [ + "u32", + "pid" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "new_mac", + 6 + ], + [ + "u8", + "mac_ip" + ], + { + "crc": "0xef7235f7" + } + ], + [ + "want_ip6_nd_events", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "enable_disable" + ], + [ + "u32", + "pid" + ], + [ + "u8", + "address", + 16 + ], + { + "crc": "0x1cf65fbb" + } + ], + [ + "want_ip6_nd_events_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip6_nd_event", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "pid" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "address", + 16 + ], + [ + "u8", + "new_mac", + 6 + ], + [ + "u8", + "mac_ip" + ], + { + "crc": "0x96ab2fdd" + } + ], + [ + "proxy_arp_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "vrf_id" + ], + [ + "u8", + "is_add" + ], + [ + "u8", + "low_address", + 4 + ], + [ + "u8", + "hi_address", + 4 + ], + { + "crc": "0xc2442918" + } + ], + [ + "proxy_arp_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "proxy_arp_intfc_enable_disable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "sw_if_index" + ], + [ + "u8", + "enable_disable" + ], + { + "crc": "0x69d24598" + } + ], + [ + "proxy_arp_intfc_enable_disable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "reset_fib", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "vrf_id" + ], + [ + "u8", + "is_ipv6" + ], + { + "crc": "0x8553ebd9" + } + ], + [ + "reset_fib_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "set_arp_neighbor_limit", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u8", + "is_ipv6" + ], + [ + "u32", + "arp_neighbor_limit" + ], + { + "crc": "0x97d01fd6" + } + ], + [ + "set_arp_neighbor_limit_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ioam_enable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u16", + "id" + ], + [ + "u8", + "seqno" + ], + [ + "u8", + "analyse" + ], + [ + "u8", + "pot_enable" + ], + [ + "u8", + "trace_enable" + ], + [ + "u32", + "node_id" + ], + { + "crc": "0x9392e032" + } + ], + [ + "ioam_enable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ioam_disable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u16", + "id" + ], + { + "crc": "0x6b16a45e" + } + ], + [ + "ioam_disable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ] + ], + "types": [ + [ + "fib_path", + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "table_id" + ], + [ + "u8", + "weight" + ], + [ + "u8", + "preference" + ], + [ + "u8", + "is_local" + ], + [ + "u8", + "is_drop" + ], + [ + "u8", + "is_unreach" + ], + [ + "u8", + "is_prohibit" + ], + [ + "u8", + "afi" + ], + [ + "u8", + "next_hop", + 16 + ], + { + "crc": "0xcd899e0a" + } + ] + ] +} diff --git a/binapigen/vppapi/testdata/test-all.api.json b/binapigen/vppapi/testdata/test-all.api.json new file mode 100644 index 0000000..4e858b8 --- /dev/null +++ b/binapigen/vppapi/testdata/test-all.api.json @@ -0,0 +1,3240 @@ +[ + { + "types": [ + [ + "address", + [ + "vl_api_address_family_t", + "af" + ], + [ + "vl_api_address_union_t", + "un" + ] + ], + [ + "prefix", + [ + "vl_api_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "mprefix", + [ + "vl_api_address_family_t", + "af" + ], + [ + "u16", + "grp_address_length" + ], + [ + "vl_api_address_union_t", + "grp_address" + ], + [ + "vl_api_address_union_t", + "src_address" + ] + ], + [ + "ip6_prefix", + [ + "vl_api_ip6_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "ip4_prefix", + [ + "vl_api_ip4_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "prefix_matcher", + [ + "u8", + "le" + ], + [ + "u8", + "ge" + ] + ], + [ + "fib_mpls_label", + [ + "u8", + "is_uniform" + ], + [ + "u32", + "label" + ], + [ + "u8", + "ttl" + ], + [ + "u8", + "exp" + ] + ], + [ + "fib_path_nh", + [ + "vl_api_address_union_t", + "address" + ], + [ + "u32", + "via_label" + ], + [ + "u32", + "obj_id" + ], + [ + "u32", + "classify_table_index" + ] + ], + [ + "fib_path", + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "table_id" + ], + [ + "u32", + "rpf_id" + ], + [ + "u8", + "weight" + ], + [ + "u8", + "preference" + ], + [ + "vl_api_fib_path_type_t", + "type" + ], + [ + "vl_api_fib_path_flags_t", + "flags" + ], + [ + "vl_api_fib_path_nh_proto_t", + "proto" + ], + [ + "vl_api_fib_path_nh_t", + "nh" + ], + [ + "u8", + "n_labels" + ], + [ + "vl_api_fib_mpls_label_t", + "label_stack", + 16 + ] + ], + [ + "address", + [ + "vl_api_address_family_t", + "af" + ], + [ + "vl_api_address_union_t", + "un" + ] + ], + [ + "prefix", + [ + "vl_api_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "mprefix", + [ + "vl_api_address_family_t", + "af" + ], + [ + "u16", + "grp_address_length" + ], + [ + "vl_api_address_union_t", + "grp_address" + ], + [ + "vl_api_address_union_t", + "src_address" + ] + ], + [ + "ip6_prefix", + [ + "vl_api_ip6_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "ip4_prefix", + [ + "vl_api_ip4_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "prefix_matcher", + [ + "u8", + "le" + ], + [ + "u8", + "ge" + ] + ], + [ + "fib_mpls_label", + [ + "u8", + "is_uniform" + ], + [ + "u32", + "label" + ], + [ + "u8", + "ttl" + ], + [ + "u8", + "exp" + ] + ], + [ + "fib_path_nh", + [ + "vl_api_address_union_t", + "address" + ], + [ + "u32", + "via_label" + ], + [ + "u32", + "obj_id" + ], + [ + "u32", + "classify_table_index" + ] + ], + [ + "fib_path", + [ + "u32", + "sw_if_index" + ], + [ + "u32", + "table_id" + ], + [ + "u32", + "rpf_id" + ], + [ + "u8", + "weight" + ], + [ + "u8", + "preference" + ], + [ + "vl_api_fib_path_type_t", + "type" + ], + [ + "vl_api_fib_path_flags_t", + "flags" + ], + [ + "vl_api_fib_path_nh_proto_t", + "proto" + ], + [ + "vl_api_fib_path_nh_t", + "nh" + ], + [ + "u8", + "n_labels" + ], + [ + "vl_api_fib_mpls_label_t", + "label_stack", + 16 + ] + ], + [ + "address", + [ + "vl_api_address_family_t", + "af" + ], + [ + "vl_api_address_union_t", + "un" + ] + ], + [ + "prefix", + [ + "vl_api_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "mprefix", + [ + "vl_api_address_family_t", + "af" + ], + [ + "u16", + "grp_address_length" + ], + [ + "vl_api_address_union_t", + "grp_address" + ], + [ + "vl_api_address_union_t", + "src_address" + ] + ], + [ + "ip6_prefix", + [ + "vl_api_ip6_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "ip4_prefix", + [ + "vl_api_ip4_address_t", + "address" + ], + [ + "u8", + "len" + ] + ], + [ + "prefix_matcher", + [ + "u8", + "le" + ], + [ + "u8", + "ge" + ] + ], + [ + "mfib_path", + [ + "vl_api_mfib_itf_flags_t", + "itf_flags" + ], + [ + "vl_api_fib_path_t", + "path" + ] + ], + [ + "ip_table", + [ + "u32", + "table_id" + ], + [ + "bool", + "is_ip6" + ], + [ + "string", + "name", + 64 + ] + ], + [ + "ip_route", + [ + "u32", + "table_id" + ], + [ + "u32", + "stats_index" + ], + [ + "vl_api_prefix_t", + "prefix" + ], + [ + "u8", + "n_paths" + ], + [ + "vl_api_fib_path_t", + "paths", + 0, + "n_paths" + ] + ], + [ + "ip_mroute", + [ + "u32", + "table_id" + ], + [ + "u32", + "entry_flags" + ], + [ + "u32", + "rpf_id" + ], + [ + "vl_api_mprefix_t", + "prefix" + ], + [ + "u8", + "n_paths" + ], + [ + "vl_api_mfib_path_t", + "paths", + 0, + "n_paths" + ] + ], + [ + "punt_redirect", + [ + "vl_api_interface_index_t", + "rx_sw_if_index" + ], + [ + "vl_api_interface_index_t", + "tx_sw_if_index" + ], + [ + "vl_api_address_t", + "nh" + ] + ] + ], + "messages": [ + [ + "ip_table_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0x0ffdaec0" + } + ], + [ + "ip_table_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_table_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "ip_table_replace_begin", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0xb9d2e09e" + } + ], + [ + "ip_table_replace_begin_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_table_replace_end", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0xb9d2e09e" + } + ], + [ + "ip_table_replace_end_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_table_flush", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0xb9d2e09e" + } + ], + [ + "ip_table_flush_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_table_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0xc79fca0f" + } + ], + [ + "ip_route_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + [ + "bool", + "is_multipath" + ], + [ + "vl_api_ip_route_t", + "route" + ], + { + "crc": "0xc1ff832d" + } + ], + [ + "ip_route_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "stats_index" + ], + { + "crc": "0x1992deab" + } + ], + [ + "ip_route_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0xb9d2e09e" + } + ], + [ + "ip_route_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_route_t", + "route" + ], + { + "crc": "0xd1ffaae1" + } + ], + [ + "ip_route_lookup", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "table_id" + ], + [ + "u8", + "exact" + ], + [ + "vl_api_prefix_t", + "prefix" + ], + { + "crc": "0xe2986185" + } + ], + [ + "ip_route_lookup_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "vl_api_ip_route_t", + "route" + ], + { + "crc": "0xae99de8e" + } + ], + [ + "set_ip_flow_hash", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "vrf_id" + ], + [ + "bool", + "is_ipv6" + ], + [ + "bool", + "src" + ], + [ + "bool", + "dst" + ], + [ + "bool", + "sport" + ], + [ + "bool", + "dport" + ], + [ + "bool", + "proto" + ], + [ + "bool", + "reverse" + ], + [ + "bool", + "symmetric" + ], + { + "crc": "0x084ee09e" + } + ], + [ + "set_ip_flow_hash_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "sw_interface_ip6_enable_disable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "bool", + "enable" + ], + { + "crc": "0xae6cfcfb" + } + ], + [ + "sw_interface_ip6_enable_disable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_mtable_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "ip_mtable_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0xb9d2e09e" + } + ], + [ + "ip_mroute_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + [ + "bool", + "is_multipath" + ], + [ + "vl_api_ip_mroute_t", + "route" + ], + { + "crc": "0xf6627d17" + } + ], + [ + "ip_mroute_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "stats_index" + ], + { + "crc": "0x1992deab" + } + ], + [ + "ip_mroute_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_table_t", + "table" + ], + { + "crc": "0xb9d2e09e" + } + ], + [ + "ip_mroute_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_ip_mroute_t", + "route" + ], + { + "crc": "0xc1cb4b44" + } + ], + [ + "ip_address_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "vl_api_address_with_prefix_t", + "prefix" + ], + { + "crc": "0xb1199745" + } + ], + [ + "ip_address_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "bool", + "is_ipv6" + ], + { + "crc": "0x2d033de4" + } + ], + [ + "ip_unnumbered_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "vl_api_interface_index_t", + "ip_sw_if_index" + ], + { + "crc": "0xaa12a483" + } + ], + [ + "ip_unnumbered_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index", + { + "default": 4294967295 + } + ], + { + "crc": "0xf9e6675e" + } + ], + [ + "ip_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "bool", + "is_ipv6" + ], + { + "crc": "0xeb152d07" + } + ], + [ + "ip_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "bool", + "is_ipv6" + ], + { + "crc": "0x98d231ca" + } + ], + [ + "mfib_signal_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "mfib_signal_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "u32", + "table_id" + ], + [ + "vl_api_mprefix_t", + "prefix" + ], + [ + "u16", + "ip_packet_len" + ], + [ + "u8", + "ip_packet_data", + 256 + ], + { + "crc": "0x64398a9a" + } + ], + [ + "ip_punt_police", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "policer_index" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + [ + "bool", + "is_ip6" + ], + { + "crc": "0xdb867cea" + } + ], + [ + "ip_punt_police_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_punt_redirect", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_punt_redirect_t", + "punt" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + { + "crc": "0xa9a5592c" + } + ], + [ + "ip_punt_redirect_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_punt_redirect_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "bool", + "is_ipv6" + ], + { + "crc": "0x2d033de4" + } + ], + [ + "ip_punt_redirect_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_punt_redirect_t", + "punt" + ], + { + "crc": "0x3924f5d3" + } + ], + [ + "ip_container_proxy_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_prefix_t", + "pfx" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + { + "crc": "0x91189f40" + } + ], + [ + "ip_container_proxy_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_container_proxy_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "ip_container_proxy_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "vl_api_prefix_t", + "prefix" + ], + { + "crc": "0x0ee460e8" + } + ], + [ + "ip_source_and_port_range_check_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + [ + "vl_api_prefix_t", + "prefix" + ], + [ + "u8", + "number_of_ranges" + ], + [ + "u16", + "low_ports", + 32 + ], + [ + "u16", + "high_ports", + 32 + ], + [ + "u32", + "vrf_id" + ], + { + "crc": "0x8bfc76f2" + } + ], + [ + "ip_source_and_port_range_check_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_source_and_port_range_check_interface_add_del", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "bool", + "is_add", + { + "default": "true" + } + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "u32", + "tcp_in_vrf_id" + ], + [ + "u32", + "tcp_out_vrf_id" + ], + [ + "u32", + "udp_in_vrf_id" + ], + [ + "u32", + "udp_out_vrf_id" + ], + { + "crc": "0xe1ba8987" + } + ], + [ + "ip_source_and_port_range_check_interface_add_del_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "sw_interface_ip6_set_link_local_address", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "vl_api_ip6_address_t", + "ip" + ], + { + "crc": "0x2931d9fa" + } + ], + [ + "sw_interface_ip6_set_link_local_address_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ioam_enable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u16", + "id" + ], + [ + "bool", + "seqno" + ], + [ + "bool", + "analyse" + ], + [ + "bool", + "pot_enable" + ], + [ + "bool", + "trace_enable" + ], + [ + "u32", + "node_id" + ], + { + "crc": "0x51ccd868" + } + ], + [ + "ioam_enable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ioam_disable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u16", + "id" + ], + { + "crc": "0x6b16a45e" + } + ], + [ + "ioam_disable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_reassembly_set", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "timeout_ms" + ], + [ + "u32", + "max_reassemblies" + ], + [ + "u32", + "max_reassembly_length" + ], + [ + "u32", + "expire_walk_interval_ms" + ], + [ + "bool", + "is_ip6" + ], + [ + "vl_api_ip_reass_type_t", + "type" + ], + { + "crc": "0x16467d25" + } + ], + [ + "ip_reassembly_set_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ], + [ + "ip_reassembly_get", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "bool", + "is_ip6" + ], + [ + "vl_api_ip_reass_type_t", + "type" + ], + { + "crc": "0xea13ff63" + } + ], + [ + "ip_reassembly_get_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "timeout_ms" + ], + [ + "u32", + "max_reassemblies" + ], + [ + "u32", + "max_reassembly_length" + ], + [ + "u32", + "expire_walk_interval_ms" + ], + [ + "bool", + "is_ip6" + ], + { + "crc": "0xd5eb8d34" + } + ], + [ + "ip_reassembly_enable_disable", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_interface_index_t", + "sw_if_index" + ], + [ + "bool", + "enable_ip4" + ], + [ + "bool", + "enable_ip6" + ], + [ + "vl_api_ip_reass_type_t", + "type" + ], + { + "crc": "0x885c85a6" + } + ], + [ + "ip_reassembly_enable_disable_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804" + } + ] + ], + "unions": [ + [ + "address_union", + [ + "vl_api_ip4_address_t", + "ip4" + ], + [ + "vl_api_ip6_address_t", + "ip6" + ] + ], + [ + "address_union", + [ + "vl_api_ip4_address_t", + "ip4" + ], + [ + "vl_api_ip6_address_t", + "ip6" + ] + ], + [ + "address_union", + [ + "vl_api_ip4_address_t", + "ip4" + ], + [ + "vl_api_ip6_address_t", + "ip6" + ] + ] + ], + "enums": [ + [ + "if_status_flags", + [ + "IF_STATUS_API_FLAG_ADMIN_UP", + 1 + ], + [ + "IF_STATUS_API_FLAG_LINK_UP", + 2 + ], + { + "enumtype": "u32" + } + ], + [ + "mtu_proto", + [ + "MTU_PROTO_API_L3", + 0 + ], + [ + "MTU_PROTO_API_IP4", + 1 + ], + [ + "MTU_PROTO_API_IP6", + 2 + ], + [ + "MTU_PROTO_API_MPLS", + 3 + ], + { + "enumtype": "u32" + } + ], + [ + "link_duplex", + [ + "LINK_DUPLEX_API_UNKNOWN", + 0 + ], + [ + "LINK_DUPLEX_API_HALF", + 1 + ], + [ + "LINK_DUPLEX_API_FULL", + 2 + ], + { + "enumtype": "u32" + } + ], + [ + "sub_if_flags", + [ + "SUB_IF_API_FLAG_NO_TAGS", + 1 + ], + [ + "SUB_IF_API_FLAG_ONE_TAG", + 2 + ], + [ + "SUB_IF_API_FLAG_TWO_TAGS", + 4 + ], + [ + "SUB_IF_API_FLAG_DOT1AD", + 8 + ], + [ + "SUB_IF_API_FLAG_EXACT_MATCH", + 16 + ], + [ + "SUB_IF_API_FLAG_DEFAULT", + 32 + ], + [ + "SUB_IF_API_FLAG_OUTER_VLAN_ID_ANY", + 64 + ], + [ + "SUB_IF_API_FLAG_INNER_VLAN_ID_ANY", + 128 + ], + [ + "SUB_IF_API_FLAG_MASK_VNET", + 254 + ], + [ + "SUB_IF_API_FLAG_DOT1AH", + 256 + ], + { + "enumtype": "u32" + } + ], + [ + "rx_mode", + [ + "RX_MODE_API_UNKNOWN", + 0 + ], + [ + "RX_MODE_API_POLLING", + 1 + ], + [ + "RX_MODE_API_INTERRUPT", + 2 + ], + [ + "RX_MODE_API_ADAPTIVE", + 3 + ], + [ + "RX_MODE_API_DEFAULT", + 4 + ], + { + "enumtype": "u32" + } + ], + [ + "if_type", + [ + "IF_API_TYPE_HARDWARE", + 0 + ], + [ + "IF_API_TYPE_SUB", + 1 + ], + [ + "IF_API_TYPE_P2P", + 2 + ], + [ + "IF_API_TYPE_PIPE", + 3 + ], + { + "enumtype": "u32" + } + ], + [ + "address_family", + [ + "ADDRESS_IP4", + 0 + ], + [ + "ADDRESS_IP6", + 1 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_ecn", + [ + "IP_API_ECN_NONE", + 0 + ], + [ + "IP_API_ECN_ECT0", + 1 + ], + [ + "IP_API_ECN_ECT1", + 2 + ], + [ + "IP_API_ECN_CE", + 3 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_dscp", + [ + "IP_API_DSCP_CS0", + 0 + ], + [ + "IP_API_DSCP_CS1", + 8 + ], + [ + "IP_API_DSCP_AF11", + 10 + ], + [ + "IP_API_DSCP_AF12", + 12 + ], + [ + "IP_API_DSCP_AF13", + 14 + ], + [ + "IP_API_DSCP_CS2", + 16 + ], + [ + "IP_API_DSCP_AF21", + 18 + ], + [ + "IP_API_DSCP_AF22", + 20 + ], + [ + "IP_API_DSCP_AF23", + 22 + ], + [ + "IP_API_DSCP_CS3", + 24 + ], + [ + "IP_API_DSCP_AF31", + 26 + ], + [ + "IP_API_DSCP_AF32", + 28 + ], + [ + "IP_API_DSCP_AF33", + 30 + ], + [ + "IP_API_DSCP_CS4", + 32 + ], + [ + "IP_API_DSCP_AF41", + 34 + ], + [ + "IP_API_DSCP_AF42", + 36 + ], + [ + "IP_API_DSCP_AF43", + 38 + ], + [ + "IP_API_DSCP_CS5", + 40 + ], + [ + "IP_API_DSCP_EF", + 46 + ], + [ + "IP_API_DSCP_CS6", + 48 + ], + [ + "IP_API_DSCP_CS7", + 50 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_proto", + [ + "IP_API_PROTO_HOPOPT", + 0 + ], + [ + "IP_API_PROTO_ICMP", + 1 + ], + [ + "IP_API_PROTO_IGMP", + 2 + ], + [ + "IP_API_PROTO_TCP", + 6 + ], + [ + "IP_API_PROTO_UDP", + 17 + ], + [ + "IP_API_PROTO_GRE", + 47 + ], + [ + "IP_API_PROTO_ESP", + 50 + ], + [ + "IP_API_PROTO_AH", + 51 + ], + [ + "IP_API_PROTO_ICMP6", + 58 + ], + [ + "IP_API_PROTO_EIGRP", + 88 + ], + [ + "IP_API_PROTO_OSPF", + 89 + ], + [ + "IP_API_PROTO_SCTP", + 132 + ], + [ + "IP_API_PROTO_RESERVED", + 255 + ], + { + "enumtype": "u8" + } + ], + [ + "fib_path_nh_proto", + [ + "FIB_API_PATH_NH_PROTO_IP4", + 0 + ], + [ + "FIB_API_PATH_NH_PROTO_IP6", + 1 + ], + [ + "FIB_API_PATH_NH_PROTO_MPLS", + 2 + ], + [ + "FIB_API_PATH_NH_PROTO_ETHERNET", + 3 + ], + [ + "FIB_API_PATH_NH_PROTO_BIER", + 4 + ], + { + "enumtype": "u32" + } + ], + [ + "fib_path_flags", + [ + "FIB_API_PATH_FLAG_NONE", + 0 + ], + [ + "FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED", + 1 + ], + [ + "FIB_API_PATH_FLAG_RESOLVE_VIA_HOST", + 2 + ], + [ + "FIB_API_PATH_FLAG_POP_PW_CW", + 4 + ], + { + "enumtype": "u32" + } + ], + [ + "fib_path_type", + [ + "FIB_API_PATH_TYPE_NORMAL", + 0 + ], + [ + "FIB_API_PATH_TYPE_LOCAL", + 1 + ], + [ + "FIB_API_PATH_TYPE_DROP", + 2 + ], + [ + "FIB_API_PATH_TYPE_UDP_ENCAP", + 3 + ], + [ + "FIB_API_PATH_TYPE_BIER_IMP", + 4 + ], + [ + "FIB_API_PATH_TYPE_ICMP_UNREACH", + 5 + ], + [ + "FIB_API_PATH_TYPE_ICMP_PROHIBIT", + 6 + ], + [ + "FIB_API_PATH_TYPE_SOURCE_LOOKUP", + 7 + ], + [ + "FIB_API_PATH_TYPE_DVR", + 8 + ], + [ + "FIB_API_PATH_TYPE_INTERFACE_RX", + 9 + ], + [ + "FIB_API_PATH_TYPE_CLASSIFY", + 10 + ], + { + "enumtype": "u32" + } + ], + [ + "address_family", + [ + "ADDRESS_IP4", + 0 + ], + [ + "ADDRESS_IP6", + 1 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_ecn", + [ + "IP_API_ECN_NONE", + 0 + ], + [ + "IP_API_ECN_ECT0", + 1 + ], + [ + "IP_API_ECN_ECT1", + 2 + ], + [ + "IP_API_ECN_CE", + 3 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_dscp", + [ + "IP_API_DSCP_CS0", + 0 + ], + [ + "IP_API_DSCP_CS1", + 8 + ], + [ + "IP_API_DSCP_AF11", + 10 + ], + [ + "IP_API_DSCP_AF12", + 12 + ], + [ + "IP_API_DSCP_AF13", + 14 + ], + [ + "IP_API_DSCP_CS2", + 16 + ], + [ + "IP_API_DSCP_AF21", + 18 + ], + [ + "IP_API_DSCP_AF22", + 20 + ], + [ + "IP_API_DSCP_AF23", + 22 + ], + [ + "IP_API_DSCP_CS3", + 24 + ], + [ + "IP_API_DSCP_AF31", + 26 + ], + [ + "IP_API_DSCP_AF32", + 28 + ], + [ + "IP_API_DSCP_AF33", + 30 + ], + [ + "IP_API_DSCP_CS4", + 32 + ], + [ + "IP_API_DSCP_AF41", + 34 + ], + [ + "IP_API_DSCP_AF42", + 36 + ], + [ + "IP_API_DSCP_AF43", + 38 + ], + [ + "IP_API_DSCP_CS5", + 40 + ], + [ + "IP_API_DSCP_EF", + 46 + ], + [ + "IP_API_DSCP_CS6", + 48 + ], + [ + "IP_API_DSCP_CS7", + 50 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_proto", + [ + "IP_API_PROTO_HOPOPT", + 0 + ], + [ + "IP_API_PROTO_ICMP", + 1 + ], + [ + "IP_API_PROTO_IGMP", + 2 + ], + [ + "IP_API_PROTO_TCP", + 6 + ], + [ + "IP_API_PROTO_UDP", + 17 + ], + [ + "IP_API_PROTO_GRE", + 47 + ], + [ + "IP_API_PROTO_ESP", + 50 + ], + [ + "IP_API_PROTO_AH", + 51 + ], + [ + "IP_API_PROTO_ICMP6", + 58 + ], + [ + "IP_API_PROTO_EIGRP", + 88 + ], + [ + "IP_API_PROTO_OSPF", + 89 + ], + [ + "IP_API_PROTO_SCTP", + 132 + ], + [ + "IP_API_PROTO_RESERVED", + 255 + ], + { + "enumtype": "u8" + } + ], + [ + "fib_path_nh_proto", + [ + "FIB_API_PATH_NH_PROTO_IP4", + 0 + ], + [ + "FIB_API_PATH_NH_PROTO_IP6", + 1 + ], + [ + "FIB_API_PATH_NH_PROTO_MPLS", + 2 + ], + [ + "FIB_API_PATH_NH_PROTO_ETHERNET", + 3 + ], + [ + "FIB_API_PATH_NH_PROTO_BIER", + 4 + ], + { + "enumtype": "u32" + } + ], + [ + "fib_path_flags", + [ + "FIB_API_PATH_FLAG_NONE", + 0 + ], + [ + "FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED", + 1 + ], + [ + "FIB_API_PATH_FLAG_RESOLVE_VIA_HOST", + 2 + ], + [ + "FIB_API_PATH_FLAG_POP_PW_CW", + 4 + ], + { + "enumtype": "u32" + } + ], + [ + "fib_path_type", + [ + "FIB_API_PATH_TYPE_NORMAL", + 0 + ], + [ + "FIB_API_PATH_TYPE_LOCAL", + 1 + ], + [ + "FIB_API_PATH_TYPE_DROP", + 2 + ], + [ + "FIB_API_PATH_TYPE_UDP_ENCAP", + 3 + ], + [ + "FIB_API_PATH_TYPE_BIER_IMP", + 4 + ], + [ + "FIB_API_PATH_TYPE_ICMP_UNREACH", + 5 + ], + [ + "FIB_API_PATH_TYPE_ICMP_PROHIBIT", + 6 + ], + [ + "FIB_API_PATH_TYPE_SOURCE_LOOKUP", + 7 + ], + [ + "FIB_API_PATH_TYPE_DVR", + 8 + ], + [ + "FIB_API_PATH_TYPE_INTERFACE_RX", + 9 + ], + [ + "FIB_API_PATH_TYPE_CLASSIFY", + 10 + ], + { + "enumtype": "u32" + } + ], + [ + "address_family", + [ + "ADDRESS_IP4", + 0 + ], + [ + "ADDRESS_IP6", + 1 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_ecn", + [ + "IP_API_ECN_NONE", + 0 + ], + [ + "IP_API_ECN_ECT0", + 1 + ], + [ + "IP_API_ECN_ECT1", + 2 + ], + [ + "IP_API_ECN_CE", + 3 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_dscp", + [ + "IP_API_DSCP_CS0", + 0 + ], + [ + "IP_API_DSCP_CS1", + 8 + ], + [ + "IP_API_DSCP_AF11", + 10 + ], + [ + "IP_API_DSCP_AF12", + 12 + ], + [ + "IP_API_DSCP_AF13", + 14 + ], + [ + "IP_API_DSCP_CS2", + 16 + ], + [ + "IP_API_DSCP_AF21", + 18 + ], + [ + "IP_API_DSCP_AF22", + 20 + ], + [ + "IP_API_DSCP_AF23", + 22 + ], + [ + "IP_API_DSCP_CS3", + 24 + ], + [ + "IP_API_DSCP_AF31", + 26 + ], + [ + "IP_API_DSCP_AF32", + 28 + ], + [ + "IP_API_DSCP_AF33", + 30 + ], + [ + "IP_API_DSCP_CS4", + 32 + ], + [ + "IP_API_DSCP_AF41", + 34 + ], + [ + "IP_API_DSCP_AF42", + 36 + ], + [ + "IP_API_DSCP_AF43", + 38 + ], + [ + "IP_API_DSCP_CS5", + 40 + ], + [ + "IP_API_DSCP_EF", + 46 + ], + [ + "IP_API_DSCP_CS6", + 48 + ], + [ + "IP_API_DSCP_CS7", + 50 + ], + { + "enumtype": "u8" + } + ], + [ + "ip_proto", + [ + "IP_API_PROTO_HOPOPT", + 0 + ], + [ + "IP_API_PROTO_ICMP", + 1 + ], + [ + "IP_API_PROTO_IGMP", + 2 + ], + [ + "IP_API_PROTO_TCP", + 6 + ], + [ + "IP_API_PROTO_UDP", + 17 + ], + [ + "IP_API_PROTO_GRE", + 47 + ], + [ + "IP_API_PROTO_ESP", + 50 + ], + [ + "IP_API_PROTO_AH", + 51 + ], + [ + "IP_API_PROTO_ICMP6", + 58 + ], + [ + "IP_API_PROTO_EIGRP", + 88 + ], + [ + "IP_API_PROTO_OSPF", + 89 + ], + [ + "IP_API_PROTO_SCTP", + 132 + ], + [ + "IP_API_PROTO_RESERVED", + 255 + ], + { + "enumtype": "u8" + } + ], + [ + "mfib_itf_flags", + [ + "MFIB_API_ITF_FLAG_NONE", + 0 + ], + [ + "MFIB_API_ITF_FLAG_NEGATE_SIGNAL", + 1 + ], + [ + "MFIB_API_ITF_FLAG_ACCEPT", + 2 + ], + [ + "MFIB_API_ITF_FLAG_FORWARD", + 4 + ], + [ + "MFIB_API_ITF_FLAG_SIGNAL_PRESENT", + 8 + ], + [ + "MFIB_API_ITF_FLAG_DONT_PRESERVE", + 16 + ], + { + "enumtype": "u32" + } + ], + [ + "if_status_flags", + [ + "IF_STATUS_API_FLAG_ADMIN_UP", + 1 + ], + [ + "IF_STATUS_API_FLAG_LINK_UP", + 2 + ], + { + "enumtype": "u32" + } + ], + [ + "mtu_proto", + [ + "MTU_PROTO_API_L3", + 0 + ], + [ + "MTU_PROTO_API_IP4", + 1 + ], + [ + "MTU_PROTO_API_IP6", + 2 + ], + [ + "MTU_PROTO_API_MPLS", + 3 + ], + { + "enumtype": "u32" + } + ], + [ + "link_duplex", + [ + "LINK_DUPLEX_API_UNKNOWN", + 0 + ], + [ + "LINK_DUPLEX_API_HALF", + 1 + ], + [ + "LINK_DUPLEX_API_FULL", + 2 + ], + { + "enumtype": "u32" + } + ], + [ + "sub_if_flags", + [ + "SUB_IF_API_FLAG_NO_TAGS", + 1 + ], + [ + "SUB_IF_API_FLAG_ONE_TAG", + 2 + ], + [ + "SUB_IF_API_FLAG_TWO_TAGS", + 4 + ], + [ + "SUB_IF_API_FLAG_DOT1AD", + 8 + ], + [ + "SUB_IF_API_FLAG_EXACT_MATCH", + 16 + ], + [ + "SUB_IF_API_FLAG_DEFAULT", + 32 + ], + [ + "SUB_IF_API_FLAG_OUTER_VLAN_ID_ANY", + 64 + ], + [ + "SUB_IF_API_FLAG_INNER_VLAN_ID_ANY", + 128 + ], + [ + "SUB_IF_API_FLAG_MASK_VNET", + 254 + ], + [ + "SUB_IF_API_FLAG_DOT1AH", + 256 + ], + { + "enumtype": "u32" + } + ], + [ + "rx_mode", + [ + "RX_MODE_API_UNKNOWN", + 0 + ], + [ + "RX_MODE_API_POLLING", + 1 + ], + [ + "RX_MODE_API_INTERRUPT", + 2 + ], + [ + "RX_MODE_API_ADAPTIVE", + 3 + ], + [ + "RX_MODE_API_DEFAULT", + 4 + ], + { + "enumtype": "u32" + } + ], + [ + "if_type", + [ + "IF_API_TYPE_HARDWARE", + 0 + ], + [ + "IF_API_TYPE_SUB", + 1 + ], + [ + "IF_API_TYPE_P2P", + 2 + ], + [ + "IF_API_TYPE_PIPE", + 3 + ], + { + "enumtype": "u32" + } + ], + [ + "ip_reass_type", + [ + "IP_REASS_TYPE_FULL", + 0 + ], + [ + "IP_REASS_TYPE_SHALLOW_VIRTUAL", + 1 + ], + { + "enumtype": "u32" + } + ] + ], + "services": { + "ip_table_add_del": { + "reply": "ip_table_add_del_reply" + }, + "ip_table_dump": { + "reply": "ip_table_details", + "stream": true + }, + "ip_table_replace_begin": { + "reply": "ip_table_replace_begin_reply" + }, + "ip_table_replace_end": { + "reply": "ip_table_replace_end_reply" + }, + "ip_table_flush": { + "reply": "ip_table_flush_reply" + }, + "ip_route_add_del": { + "reply": "ip_route_add_del_reply" + }, + "ip_route_dump": { + "reply": "ip_route_details", + "stream": true + }, + "ip_route_lookup": { + "reply": "ip_route_lookup_reply" + }, + "set_ip_flow_hash": { + "reply": "set_ip_flow_hash_reply" + }, + "sw_interface_ip6_enable_disable": { + "reply": "sw_interface_ip6_enable_disable_reply" + }, + "ip_mtable_dump": { + "reply": "ip_mtable_details", + "stream": true + }, + "ip_mroute_add_del": { + "reply": "ip_mroute_add_del_reply" + }, + "ip_mroute_dump": { + "reply": "ip_mroute_details", + "stream": true + }, + "ip_address_dump": { + "reply": "ip_address_details", + "stream": true + }, + "ip_unnumbered_dump": { + "reply": "ip_unnumbered_details", + "stream": true + }, + "ip_dump": { + "reply": "ip_details", + "stream": true + }, + "mfib_signal_dump": { + "reply": "mfib_signal_details", + "stream": true + }, + "ip_punt_police": { + "reply": "ip_punt_police_reply" + }, + "ip_punt_redirect": { + "reply": "ip_punt_redirect_reply" + }, + "ip_punt_redirect_dump": { + "reply": "ip_punt_redirect_details", + "stream": true + }, + "ip_container_proxy_add_del": { + "reply": "ip_container_proxy_add_del_reply" + }, + "ip_container_proxy_dump": { + "reply": "ip_container_proxy_details", + "stream": true + }, + "ip_source_and_port_range_check_add_del": { + "reply": "ip_source_and_port_range_check_add_del_reply" + }, + "ip_source_and_port_range_check_interface_add_del": { + "reply": "ip_source_and_port_range_check_interface_add_del_reply" + }, + "sw_interface_ip6_set_link_local_address": { + "reply": "sw_interface_ip6_set_link_local_address_reply" + }, + "ioam_enable": { + "reply": "ioam_enable_reply" + }, + "ioam_disable": { + "reply": "ioam_disable_reply" + }, + "ip_reassembly_set": { + "reply": "ip_reassembly_set_reply" + }, + "ip_reassembly_get": { + "reply": "ip_reassembly_get_reply" + }, + "ip_reassembly_enable_disable": { + "reply": "ip_reassembly_enable_disable_reply" + } + }, + "options": { + "version": "3.0.1" + }, + "aliases": { + "interface_index": { + "type": "u32" + }, + "ip4_address": { + "type": "u8", + "length": 4 + }, + "ip6_address": { + "type": "u8", + "length": 16 + }, + "address_with_prefix": { + "type": "vl_api_prefix_t" + }, + "ip4_address_with_prefix": { + "type": "vl_api_ip4_prefix_t" + }, + "ip6_address_with_prefix": { + "type": "vl_api_ip6_prefix_t" + }, + "mac_address": { + "type": "u8", + "length": 6 + } + }, + "vl_api_version": "0x765d74b1", + "imports": [ + "vnet/interface_types.api", + "vnet/fib/fib_types.api", + "vnet/ip/ip_types.api", + "vnet/ethernet/ethernet_types.api", + "vnet/mfib/mfib_types.api", + "vnet/fib/fib_types.api", + "vnet/ip/ip_types.api", + "vnet/ip/ip_types.api", + "vnet/interface_types.api" + ] + } +] diff --git a/binapigen/vppapi/testdata/vpe.api.json b/binapigen/vppapi/testdata/vpe.api.json new file mode 100644 index 0000000..960ba12 --- /dev/null +++ b/binapigen/vppapi/testdata/vpe.api.json @@ -0,0 +1,775 @@ +{ + "types": [ + [ + "version", + [ + "u32", + "major" + ], + [ + "u32", + "minor" + ], + [ + "u32", + "patch" + ], + [ + "u8", + "pre_release", + 17 + ], + [ + "u8", + "build_metadata", + 17 + ] + ], + [ + "thread_data", + [ + "u32", + "id" + ], + [ + "string", + "name", + 64 + ], + [ + "string", + "type", + 64 + ], + [ + "u32", + "pid" + ], + [ + "u32", + "cpu_id" + ], + [ + "u32", + "core" + ], + [ + "u32", + "cpu_socket" + ] + ] + ], + "messages": [ + [ + "control_ping", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "control_ping_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "vpe_pid" + ], + { + "crc": "0xf6b0b8ca" + } + ], + [ + "cli", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u64", + "cmd_in_shmem" + ], + { + "crc": "0x23bfbfff" + } + ], + [ + "cli_inband", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "string", + "cmd", + 0 + ], + { + "crc": "0xf8377302" + } + ], + [ + "cli_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u64", + "reply_in_shmem" + ], + { + "crc": "0x06d68297" + } + ], + [ + "cli_inband_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "string", + "reply", + 0 + ], + { + "crc": "0x05879051" + } + ], + [ + "get_node_index", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "string", + "node_name", + 64 + ], + { + "crc": "0xf1984c64" + } + ], + [ + "get_node_index_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "node_index" + ], + { + "crc": "0xa8600b89" + } + ], + [ + "add_node_next", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "string", + "node_name", + 64 + ], + [ + "string", + "next_name", + 64 + ], + { + "crc": "0x2457116d" + } + ], + [ + "add_node_next_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "next_index" + ], + { + "crc": "0x2ed75f32" + } + ], + [ + "show_version", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "show_version_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "string", + "program", + 32 + ], + [ + "string", + "version", + 32 + ], + [ + "string", + "build_date", + 32 + ], + [ + "string", + "build_directory", + 256 + ], + { + "crc": "0xc919bde1" + } + ], + [ + "show_threads", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "show_threads_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "count" + ], + [ + "vl_api_thread_data_t", + "thread_data", + 0, + "count" + ], + { + "crc": "0xefd78e83" + } + ], + [ + "get_node_graph", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "get_node_graph_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u64", + "reply_in_shmem" + ], + { + "crc": "0x06d68297" + } + ], + [ + "get_next_index", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "string", + "node_name", + 64 + ], + [ + "string", + "next_name", + 64 + ], + { + "crc": "0x2457116d" + } + ], + [ + "get_next_index_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "next_index" + ], + { + "crc": "0x2ed75f32" + } + ], + [ + "log_dump", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "vl_api_timestamp_t", + "start_timestamp" + ], + { + "crc": "0x6ab31753" + } + ], + [ + "log_details", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "vl_api_timestamp_t", + "timestamp" + ], + [ + "vl_api_log_level_t", + "level" + ], + [ + "string", + "msg_class", + 32 + ], + [ + "string", + "message", + 256 + ], + { + "crc": "0x255827a1" + } + ], + [ + "show_vpe_system_time", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14" + } + ], + [ + "show_vpe_system_time_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "vl_api_timestamp_t", + "vpe_system_time" + ], + { + "crc": "0x7ffd8193" + } + ], + [ + "get_f64_endian_value", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "f64", + "f64_one", + { + "default": 1.0 + } + ], + { + "crc": "0x809fcd44" + } + ], + [ + "get_f64_endian_value_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "retval" + ], + [ + "f64", + "f64_one_result" + ], + { + "crc": "0x7e02e404" + } + ], + [ + "get_f64_increment_by_one", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "f64", + "f64_value", + { + "default": 1.0 + } + ], + { + "crc": "0xb64f027e" + } + ], + [ + "get_f64_increment_by_one_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "u32", + "retval" + ], + [ + "f64", + "f64_value" + ], + { + "crc": "0xd25dbaa3" + } + ] + ], + "unions": [], + "enums": [ + [ + "log_level", + [ + "VPE_API_LOG_LEVEL_EMERG", + 0 + ], + [ + "VPE_API_LOG_LEVEL_ALERT", + 1 + ], + [ + "VPE_API_LOG_LEVEL_CRIT", + 2 + ], + [ + "VPE_API_LOG_LEVEL_ERR", + 3 + ], + [ + "VPE_API_LOG_LEVEL_WARNING", + 4 + ], + [ + "VPE_API_LOG_LEVEL_NOTICE", + 5 + ], + [ + "VPE_API_LOG_LEVEL_INFO", + 6 + ], + [ + "VPE_API_LOG_LEVEL_DEBUG", + 7 + ], + [ + "VPE_API_LOG_LEVEL_DISABLED", + 8 + ], + { + "enumtype": "u32" + } + ] + ], + "services": { + "control_ping": { + "reply": "control_ping_reply" + }, + "cli": { + "reply": "cli_reply" + }, + "cli_inband": { + "reply": "cli_inband_reply" + }, + "get_node_index": { + "reply": "get_node_index_reply" + }, + "add_node_next": { + "reply": "add_node_next_reply" + }, + "show_version": { + "reply": "show_version_reply" + }, + "show_threads": { + "reply": "show_threads_reply" + }, + "get_node_graph": { + "reply": "get_node_graph_reply" + }, + "get_next_index": { + "reply": "get_next_index_reply" + }, + "log_dump": { + "reply": "log_details", + "stream": true + }, + "show_vpe_system_time": { + "reply": "show_vpe_system_time_reply" + }, + "get_f64_endian_value": { + "reply": "get_f64_endian_value_reply" + }, + "get_f64_increment_by_one": { + "reply": "get_f64_increment_by_one_reply" + } + }, + "options": { + "version": "1.6.1" + }, + "aliases": { + "timestamp": { + "type": "f64" + }, + "timedelta": { + "type": "f64" + } + }, + "vl_api_version": "0xbd2c94f4", + "imports": [ + "vpp/api/vpe_types.api" + ] +} -- cgit 1.2.3-korg