summaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorRastislav Szabo <raszabo@cisco.com>2017-05-22 13:59:34 +0200
committerRastislav Szabo <raszabo@cisco.com>2017-05-22 14:00:46 +0200
commitc38cb25d746736f062ee16e87f553c8a4ec5fced (patch)
tree231a1befaff3b83b020461e584e9de27a39d06a4 /cmd
parentc60a4ee4e6114ff0dc3cbc9fd9a58321ca2a8abc (diff)
binapi-generator renamed & moved, finished documentation
Change-Id: I7d3b53fa238e822b36a6a82c61ffb792da3898bf Signed-off-by: Rastislav Szabo <raszabo@cisco.com>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/binapi-generator/doc.go13
-rw-r--r--cmd/binapi-generator/generator.go626
-rw-r--r--cmd/binapi-generator/generator_test.go441
-rw-r--r--cmd/binapi-generator/testdata/acl.api.json67
-rw-r--r--cmd/binapi-generator/testdata/af_packet.api.json37
-rw-r--r--cmd/binapi-generator/testdata/input-generate-error.json3
-rw-r--r--cmd/binapi-generator/testdata/input-read-json-error.json1
-rw-r--r--cmd/binapi-generator/testdata/input.txt0
-rw-r--r--cmd/binapi-generator/testdata/ip.api.json292
9 files changed, 1480 insertions, 0 deletions
diff --git a/cmd/binapi-generator/doc.go b/cmd/binapi-generator/doc.go
new file mode 100644
index 0000000..e8556ec
--- /dev/null
+++ b/cmd/binapi-generator/doc.go
@@ -0,0 +1,13 @@
+// Generator of Go structs out of the VPP binary API definitions in JSON format.
+//
+// The JSON input can be specified as a single file (using the `input-file`
+// CLI flag), or as a directory that will be scanned for all `.json` files
+// (using the `input-dir` CLI flag). The generated Go bindings will be
+// placed into `output-dir` (by default the current working directory),
+// where each Go package will be placed into its own separate directory,
+// for example:
+//
+// binapi-generator --input-file=examples/bin_api/acl.api.json --output-dir=examples/bin_api
+// binapi-generator --input-dir=examples/bin_api --output-dir=examples/bin_api
+//
+package main
diff --git a/cmd/binapi-generator/generator.go b/cmd/binapi-generator/generator.go
new file mode 100644
index 0000000..22fe3e1
--- /dev/null
+++ b/cmd/binapi-generator/generator.go
@@ -0,0 +1,626 @@
+// Copyright (c) 2017 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 main
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "unicode"
+
+ "github.com/bennyscetbun/jsongo"
+)
+
+// MessageType represents the type of a VPP message.
+type messageType int
+
+const (
+ requestMessage messageType = iota // VPP request message
+ replyMessage // VPP reply message
+ otherMessage // other VPP message
+)
+
+const (
+ apiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API
+ inputFileExt = ".json" // filename extension of files that should be processed as the input
+)
+
+// context is a structure storing details of a particular code generation task
+type context struct {
+ inputFile string // file with input JSON data
+ inputBuff *bytes.Buffer // contents of the input file
+ inputLine int // currently processed line in the input file
+ outputFile string // file with output data
+ packageName string // name of the Go package being generated
+ packageDir string // directory where the package source files are located
+ types map[string]string // map of the VPP typedef names to generated Go typedef names
+}
+
+func main() {
+ inputFile := flag.String("input-file", "", "Input JSON file.")
+ inputDir := flag.String("input-dir", ".", "Input directory with JSON files.")
+ outputDir := flag.String("output-dir", ".", "Output directory where package folders will be generated.")
+ flag.Parse()
+
+ if *inputFile == "" && *inputDir == "" {
+ fmt.Fprintln(os.Stderr, "ERROR: input-file or input-dir must be specified")
+ os.Exit(1)
+ }
+
+ var err, tmpErr error
+ if *inputFile != "" {
+ // process one input file
+ err = generateFromFile(*inputFile, *outputDir)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", *inputFile, err)
+ }
+ } else {
+ // process all files in specified directory
+ files, err := getInputFiles(*inputDir)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "ERROR: code generation failed: %v\n", err)
+ }
+ for _, file := range files {
+ tmpErr = generateFromFile(file, *outputDir)
+ if tmpErr != nil {
+ fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", file, err)
+ err = tmpErr // remember that the error occurred
+ }
+ }
+ }
+ if err != nil {
+ os.Exit(1)
+ }
+}
+
+// getInputFiles returns all input files located in specified directory
+func getInputFiles(inputDir string) ([]string, error) {
+ files, err := ioutil.ReadDir(inputDir)
+ if err != nil {
+ return nil, fmt.Errorf("reading directory %s failed: %v", inputDir, err)
+ }
+ res := make([]string, 0)
+ for _, f := range files {
+ if strings.HasSuffix(f.Name(), inputFileExt) {
+ res = append(res, inputDir+"/"+f.Name())
+ }
+ }
+ return res, nil
+}
+
+// generateFromFile generates Go bindings from one input JSON file
+func generateFromFile(inputFile, outputDir string) error {
+ ctx, err := getContext(inputFile, outputDir)
+ if err != nil {
+ return err
+ }
+ // read the file
+ inputData, err := readFile(inputFile)
+ if err != nil {
+ return err
+ }
+ ctx.inputBuff = bytes.NewBuffer(inputData)
+
+ // parse JSON
+ jsonRoot, err := parseJSON(inputData)
+ if err != nil {
+ return err
+ }
+
+ // create output directory
+ err = os.MkdirAll(ctx.packageDir, 0777)
+ if err != nil {
+ return fmt.Errorf("creating output directory %s failed: %v", ctx.packageDir, err)
+ }
+
+ // open output file
+ f, err := os.Create(ctx.outputFile)
+ defer f.Close()
+ if err != nil {
+ return fmt.Errorf("creating output file %s failed: %v", ctx.outputFile, err)
+ }
+ w := bufio.NewWriter(f)
+
+ // generate Go package code
+ err = generatePackage(ctx, w, jsonRoot)
+ if err != nil {
+ return err
+ }
+
+ // go format the output file (non-fatal if fails)
+ exec.Command("gofmt", "-w", ctx.outputFile).Run()
+
+ return nil
+}
+
+// getContext returns context details of the code generation task
+func getContext(inputFile, outputDir string) (*context, error) {
+ if !strings.HasSuffix(inputFile, inputFileExt) {
+ return nil, fmt.Errorf("invalid input file name %s", inputFile)
+ }
+
+ ctx := &context{inputFile: inputFile}
+ inputFileName := filepath.Base(inputFile)
+
+ ctx.packageName = inputFileName[0:strings.Index(inputFileName, ".")]
+ if ctx.packageName == "interface" {
+ // 'interface' cannot be a package name, it is a go keyword
+ ctx.packageName = "interfaces"
+ }
+
+ ctx.packageDir = outputDir + "/" + ctx.packageName + "/"
+ ctx.outputFile = ctx.packageDir + ctx.packageName + ".go"
+
+ return ctx, nil
+}
+
+// readFile reads content of a file into memory
+func readFile(inputFile string) ([]byte, error) {
+
+ inputData, err := ioutil.ReadFile(inputFile)
+
+ if err != nil {
+ return nil, fmt.Errorf("reading data from file failed: %v", err)
+ }
+
+ return inputData, nil
+}
+
+// parseJSON parses a JSON data into an in-memory tree
+func parseJSON(inputData []byte) (*jsongo.JSONNode, error) {
+ root := jsongo.JSONNode{}
+
+ err := json.Unmarshal(inputData, &root)
+ if err != nil {
+ return nil, fmt.Errorf("JSON unmarshall failed: %v", err)
+ }
+
+ return &root, nil
+
+}
+
+// generatePackage generates Go code of a package from provided JSON
+func generatePackage(ctx *context, w *bufio.Writer, jsonRoot *jsongo.JSONNode) error {
+ // generate file header
+ generatePackageHeader(ctx, w, jsonRoot)
+
+ // generate data types
+ ctx.types = make(map[string]string)
+ types := jsonRoot.Map("types")
+ for i := 0; i < types.Len(); i++ {
+ typ := types.At(i)
+ err := generateMessage(ctx, w, typ, true)
+ if err != nil {
+ return err
+ }
+ }
+
+ // generate messages
+ messages := jsonRoot.Map("messages")
+ for i := 0; i < messages.Len(); i++ {
+ msg := messages.At(i)
+ err := generateMessage(ctx, w, msg, false)
+ if err != nil {
+ return err
+ }
+ }
+
+ // flush the data:
+ err := w.Flush()
+ if err != nil {
+ return fmt.Errorf("flushing data to %s failed: %v", ctx.outputFile, err)
+ }
+
+ return nil
+}
+
+// generateMessage generates Go code of one VPP message encoded in JSON into provided writer
+func generateMessage(ctx *context, w io.Writer, msg *jsongo.JSONNode, isType bool) error {
+ if msg.Len() == 0 || msg.At(0).GetType() != jsongo.TypeValue {
+ return errors.New("invalid JSON for message specified")
+ }
+
+ msgName, ok := msg.At(0).Get().(string)
+ if !ok {
+ return fmt.Errorf("invalid JSON for message specified, message name is %T, not a string", msg.At(0).Get())
+ }
+ structName := camelCaseName(strings.Title(msgName))
+
+ // generate struct fields into the slice & determine message type
+ fields := make([]string, 0)
+ msgType := otherMessage
+ for j := 0; j < msg.Len(); j++ {
+ if jsongo.TypeArray == msg.At(j).GetType() {
+ fld := msg.At(j)
+ err := processMessageField(ctx, &fields, fld)
+ if err != nil {
+ return err
+ }
+ // determine whether ths is a request / reply / other message
+ if j == 2 {
+ fieldName, ok := fld.At(1).Get().(string)
+ if ok {
+ if fieldName == "client_index" {
+ msgType = requestMessage
+ } else if fieldName == "context" {
+ msgType = replyMessage
+ } else {
+ msgType = otherMessage
+ }
+ }
+ }
+ }
+ }
+
+ // generate struct comment
+ generateMessageComment(ctx, w, structName, msgName, isType)
+
+ // generate struct header
+ fmt.Fprintln(w, "type", structName, "struct {")
+
+ // print out the fields
+ for _, field := range fields {
+ fmt.Fprintln(w, field)
+ }
+
+ // generate end of the struct
+ fmt.Fprintln(w, "}")
+
+ // generate name getter
+ if isType {
+ generateTypeNameGetter(w, structName, msgName)
+ } else {
+ generateMessageNameGetter(w, structName, msgName)
+ }
+
+ // generate message type getter method
+ if !isType {
+ generateMessageTypeGetter(w, structName, msgType)
+ }
+
+ // generate CRC getter
+ crcIf := msg.At(msg.Len() - 1).At("crc").Get()
+ if crc, ok := crcIf.(string); ok {
+ generateCrcGetter(w, structName, crc)
+ }
+
+ // generate message factory
+ if !isType {
+ generateMessageFactory(w, structName)
+ }
+
+ // if this is a type, save it in the map for later use
+ if isType {
+ ctx.types[fmt.Sprintf("vl_api_%s_t", msgName)] = structName
+ }
+
+ return nil
+}
+
+// processMessageField process JSON describing one message field into Go code emitted into provided slice of message fields
+func processMessageField(ctx *context, fields *[]string, fld *jsongo.JSONNode) error {
+ if fld.Len() < 2 || fld.At(0).GetType() != jsongo.TypeValue || fld.At(1).GetType() != jsongo.TypeValue {
+ return errors.New("invalid JSON for message field specified")
+ }
+ fieldVppType, ok := fld.At(0).Get().(string)
+ if !ok {
+ return fmt.Errorf("invalid JSON for message specified, field type is %T, not a string", fld.At(0).Get())
+ }
+ fieldName, ok := fld.At(1).Get().(string)
+ if !ok {
+ return fmt.Errorf("invalid JSON for message specified, field name is %T, not a string", fld.At(1).Get())
+ }
+
+ // skip internal fields
+ fieldNameLower := strings.ToLower(fieldName)
+ if fieldNameLower == "crc" || fieldNameLower == "_vl_msg_id" {
+ return nil
+ }
+ if len(*fields) == 0 && (fieldNameLower == "client_index" || fieldNameLower == "context") {
+ return nil
+ }
+
+ fieldName = strings.TrimPrefix(fieldName, "_")
+ fieldName = camelCaseName(strings.Title(fieldName))
+
+ fieldStr := ""
+ isArray := false
+ arraySize := 0
+
+ fieldStr += "\t" + fieldName + " "
+ if fld.Len() > 2 {
+ isArray = true
+ arraySize = int(fld.At(2).Get().(float64))
+ fieldStr += "[]"
+ }
+
+ dataType := translateVppType(ctx, fieldVppType, isArray)
+ fieldStr += dataType
+
+ if isArray {
+ if arraySize == 0 {
+ // variable sized array
+ if fld.Len() > 3 {
+ // array size is specified by another field
+ arraySizeField := string(fld.At(3).Get().(string))
+ arraySizeField = camelCaseName(strings.Title(arraySizeField))
+ // find & update the field that specifies the array size
+ for i, f := range *fields {
+ if strings.Contains(f, fmt.Sprintf("\t%s ", arraySizeField)) {
+ (*fields)[i] += fmt.Sprintf("\t`struc:\"sizeof=%s\"`", fieldName)
+ }
+ }
+ }
+ } else {
+ // fixed size array
+ fieldStr += fmt.Sprintf("\t`struc:\"[%d]%s\"`", arraySize, dataType)
+ }
+ }
+
+ *fields = append(*fields, fieldStr)
+ return nil
+}
+
+// generatePackageHeader generates package header into provider writer
+func generatePackageHeader(ctx *context, w io.Writer, rootNode *jsongo.JSONNode) {
+ fmt.Fprintln(w, "// Package "+ctx.packageName+" represents the VPP binary API of the '"+ctx.packageName+"' VPP module.")
+ fmt.Fprintln(w, "// DO NOT EDIT. Generated from '"+ctx.inputFile+"'")
+
+ fmt.Fprintln(w, "package "+ctx.packageName)
+
+ fmt.Fprintln(w, "import \""+apiImportPath+"\"")
+
+ fmt.Fprintln(w)
+ fmt.Fprintln(w, "// VlApiVersion contains version of the API.")
+ vlAPIVersion := rootNode.Map("vl_api_version")
+ if vlAPIVersion != nil {
+ fmt.Fprintln(w, "const VlAPIVersion = ", vlAPIVersion.Get())
+ }
+ fmt.Fprintln(w)
+}
+
+// generateMessageComment generates comment for a message into provider writer
+func generateMessageComment(ctx *context, w io.Writer, structName string, msgName string, isType bool) {
+ fmt.Fprintln(w)
+ if isType {
+ fmt.Fprintln(w, "// "+structName+" represents the VPP binary API data type '"+msgName+"'.")
+ } else {
+ fmt.Fprintln(w, "// "+structName+" represents the VPP binary API message '"+msgName+"'.")
+ }
+
+ // print out the source of the generated message - the JSON
+ msgFound := false
+ for {
+ lineBuff, err := ctx.inputBuff.ReadBytes('\n')
+ if err != nil {
+ break
+ }
+ ctx.inputLine++
+ line := string(lineBuff)
+
+ if !msgFound {
+ if strings.Contains(line, msgName) {
+ fmt.Fprintf(w, "// Generated from '%s', line %d:\n", ctx.inputFile, ctx.inputLine)
+ fmt.Fprintln(w, "//")
+ fmt.Fprint(w, "//", line)
+ msgFound = true
+ }
+ } else {
+ fmt.Fprint(w, "//", line)
+ if len(strings.Trim(line, " ")) < 4 {
+ break // end of the message in JSON
+ }
+ }
+ }
+ fmt.Fprintln(w, "//")
+}
+
+// generateMessageNameGetter generates getter for original VPP message name into the provider writer
+func generateMessageNameGetter(w io.Writer, structName string, msgName string) {
+ fmt.Fprintln(w, "func (*"+structName+") GetMessageName() string {")
+ fmt.Fprintln(w, "\treturn \""+msgName+"\"")
+ fmt.Fprintln(w, "}")
+}
+
+// generateTypeNameGetter generates getter for original VPP type name into the provider writer
+func generateTypeNameGetter(w io.Writer, structName string, msgName string) {
+ fmt.Fprintln(w, "func (*"+structName+") GetTypeName() string {")
+ fmt.Fprintln(w, "\treturn \""+msgName+"\"")
+ fmt.Fprintln(w, "}")
+}
+
+// generateMessageTypeGetter generates message factory for the generated message into the provider writer
+func generateMessageTypeGetter(w io.Writer, structName string, msgType messageType) {
+ fmt.Fprintln(w, "func (*"+structName+") GetMessageType() api.MessageType {")
+ if msgType == requestMessage {
+ fmt.Fprintln(w, "\treturn api.RequestMessage")
+ } else if msgType == replyMessage {
+ fmt.Fprintln(w, "\treturn api.ReplyMessage")
+ } else {
+ fmt.Fprintln(w, "\treturn api.OtherMessage")
+ }
+ fmt.Fprintln(w, "}")
+}
+
+// generateCrcGetter generates getter for CRC checksum of the message definition into the provider writer
+func generateCrcGetter(w io.Writer, structName string, crc string) {
+ crc = strings.TrimPrefix(crc, "0x")
+ fmt.Fprintln(w, "func (*"+structName+") GetCrcString() string {")
+ fmt.Fprintln(w, "\treturn \""+crc+"\"")
+ fmt.Fprintln(w, "}")
+}
+
+// generateMessageFactory generates message factory for the generated message into the provider writer
+func generateMessageFactory(w io.Writer, structName string) {
+ fmt.Fprintln(w, "func New"+structName+"() api.Message {")
+ fmt.Fprintln(w, "\treturn &"+structName+"{}")
+ fmt.Fprintln(w, "}")
+}
+
+// translateVppType translates the VPP data type into Go data type
+func translateVppType(ctx *context, vppType string, isArray bool) string {
+ // basic types
+ switch vppType {
+ case "u8":
+ if isArray {
+ return "byte"
+ }
+ return "uint8"
+ case "i8":
+ return "int8"
+ case "u16":
+ return "uint16"
+ case "i16":
+ return "int16"
+ case "u32":
+ return "uint32"
+ case "i32":
+ return "int32"
+ case "u64":
+ return "uint64"
+ case "i64":
+ return "int64"
+ case "f64":
+ return "float64"
+ }
+
+ // typedefs
+ typ, ok := ctx.types[vppType]
+ if ok {
+ return typ
+ }
+
+ panic(fmt.Sprintf("Unknown VPP type %s", vppType))
+}
+
+// camelCaseName returns correct name identifier (camelCase).
+func camelCaseName(name string) (should string) {
+ // 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 := strings.ToUpper(word); commonInitialisms[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)
+}
+
+// commonInitialisms is a set of common initialisms that need to stay in upper case.
+var commonInitialisms = map[string]bool{
+ "ACL": true,
+ "API": true,
+ "ASCII": true,
+ "CPU": true,
+ "CSS": true,
+ "DNS": true,
+ "EOF": true,
+ "GUID": true,
+ "HTML": true,
+ "HTTP": true,
+ "HTTPS": true,
+ "ID": true,
+ "IP": true,
+ "ICMP": true,
+ "JSON": true,
+ "LHS": true,
+ "QPS": 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,
+ "XML": true,
+ "XMPP": true,
+ "XSRF": true,
+ "XSS": true,
+}
diff --git a/cmd/binapi-generator/generator_test.go b/cmd/binapi-generator/generator_test.go
new file mode 100644
index 0000000..7527a98
--- /dev/null
+++ b/cmd/binapi-generator/generator_test.go
@@ -0,0 +1,441 @@
+// Copyright (c) 2017 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 main
+
+import (
+ "bufio"
+ "bytes"
+ "os"
+ "testing"
+
+ "github.com/bennyscetbun/jsongo"
+ . "github.com/onsi/gomega"
+)
+
+func TestGetInputFiles(t *testing.T) {
+ RegisterTestingT(t)
+ result, err := getInputFiles("testdata")
+ 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 := getInputFiles("nonexisting_directory")
+ Expect(err).Should(HaveOccurred())
+ Expect(result).To(BeNil())
+}
+
+func TestGenerateFromFile(t *testing.T) {
+ RegisterTestingT(t)
+ outDir := "test_output_directory"
+ // remove directory created during test
+ defer os.RemoveAll(outDir)
+ err := generateFromFile("testdata/acl.api.json", outDir)
+ Expect(err).ShouldNot(HaveOccurred())
+ fileInfo, err := os.Stat(outDir + "/acl/acl.go")
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(fileInfo.IsDir()).To(BeFalse())
+ Expect(fileInfo.Name()).To(BeEquivalentTo("acl.go"))
+}
+
+func TestGenerateFromFileInputError(t *testing.T) {
+ RegisterTestingT(t)
+ outDir := "test_output_directory"
+ err := generateFromFile("testdata/nonexisting.json", outDir)
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("reading data from file failed"))
+}
+
+func TestGenerateFromFileReadJsonError(t *testing.T) {
+ RegisterTestingT(t)
+ outDir := "test_output_directory"
+ err := generateFromFile("testdata/input-read-json-error.json", outDir)
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("JSON unmarshall failed"))
+}
+
+func TestGenerateFromFileGeneratePackageError(t *testing.T) {
+ RegisterTestingT(t)
+ outDir := "test_output_directory"
+ // generate package throws panic, recover after it
+ defer func() {
+ if recovery := recover(); recovery != nil {
+ t.Logf("Recovered from panic: %v", recovery)
+ }
+ os.RemoveAll(outDir)
+ }()
+ err := generateFromFile("testdata/input-generate-error.json", outDir)
+ Expect(err).Should(HaveOccurred())
+}
+
+func TestGetContext(t *testing.T) {
+ RegisterTestingT(t)
+ outDir := "test_output_directory"
+ result, err := getContext("testdata/af_packet.api.json", outDir)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(result).ToNot(BeNil())
+ Expect(result.outputFile).To(BeEquivalentTo(outDir + "/af_packet/af_packet.go"))
+}
+
+func TestGetContextNoJsonFile(t *testing.T) {
+ RegisterTestingT(t)
+ outDir := "test_output_directory"
+ result, err := getContext("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 := getContext("testdata/interface.json", outDir)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(result).ToNot(BeNil())
+ Expect(result.outputFile)
+ Expect(result.outputFile).To(BeEquivalentTo(outDir + "/interfaces/interfaces.go"))
+
+}
+
+func TestReadJson(t *testing.T) {
+ RegisterTestingT(t)
+ inputData, err := readFile("testdata/af_packet.api.json")
+ Expect(err).ShouldNot(HaveOccurred())
+ result, err := parseJSON(inputData)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(result).ToNot(BeNil())
+ Expect(result.Len()).To(BeEquivalentTo(3))
+}
+
+func TestReadJsonError(t *testing.T) {
+ RegisterTestingT(t)
+ inputData, err := readFile("testdata/input-read-json-error.json")
+ Expect(err).ShouldNot(HaveOccurred())
+ result, err := parseJSON(inputData)
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("JSON unmarshall failed"))
+ Expect(result).To(BeNil())
+}
+
+func TestGeneratePackage(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, _ := os.Create(outDir)
+ defer os.RemoveAll(outDir)
+
+ // prepare writer
+ writer := bufio.NewWriter(outFile)
+ Expect(writer.Buffered()).To(BeZero())
+ err = generatePackage(testCtx, writer, inFile)
+ Expect(err).ShouldNot(HaveOccurred())
+}
+
+func TestGenerateMessageType(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, _ := os.Create(outDir)
+ defer os.RemoveAll(outDir)
+
+ // prepare writer
+ writer := bufio.NewWriter(outFile)
+
+ types := inFile.Map("types")
+ testCtx.types = map[string]string{
+ "u32": "sw_if_index",
+ "u8": "weight",
+ }
+ 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, true)
+ Expect(err).ShouldNot(HaveOccurred())
+ 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, _ := 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 jsongo.TypeArray == field.GetType() {
+ err := processMessageField(testCtx, &fields, field)
+ 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
+ expectedTypes := []string{"\tMajor uint32", "\tMinor uint32", "\tACLIndex uint32",
+ "\tTag []byte `struc:\"[64]byte\"`", "\tACLIndex uint32", "\tRetval int32", "\tACLIndex 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 types
+ 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 jsongo.TypeArray == field.GetType() {
+ specificFieldName := field.At(1).Get().(string)
+ if specificFieldName == "crc" || specificFieldName == "_vl_msg_id" ||
+ specificFieldName == "client_index" || specificFieldName == "context" {
+ continue
+ }
+ err := processMessageField(testCtx, &fields, field)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(fields[customIndex]).To(BeEquivalentTo(expectedTypes[customIndex]))
+ customIndex++
+ }
+ }
+ }
+}
+
+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())
+ generatePackageHeader(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"}
+ translated := []string{}
+ for _, value := range typesToTranslate {
+ translated = append(translated, translateVppType(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 := translateVppType(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)
+ translateVppType(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/cmd/binapi-generator/testdata/acl.api.json b/cmd/binapi-generator/testdata/acl.api.json
new file mode 100644
index 0000000..91d3d2f
--- /dev/null
+++ b/cmd/binapi-generator/testdata/acl.api.json
@@ -0,0 +1,67 @@
+{
+ "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" : "0x2715e1c0"}
+ ],
+ ["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" : "0x6723f13e"}
+ ]
+ ],
+ "messages" : [
+ ["acl_plugin_get_version",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ {"crc" : "0xd7c07748"}
+ ],
+ ["acl_plugin_get_version_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["u32", "major"],
+ ["u32", "minor"],
+ {"crc" : "0x43eb59a5"}
+ ],
+ ["acl_add_replace",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "acl_index"],
+ ["u8", "tag", 64],
+ {"crc" : "0x3c317936"}
+ ],
+ ["acl_add_replace_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["u32", "acl_index"],
+ ["i32", "retval"],
+ {"crc" : "0xa5e6d0cf"}
+ ],
+ ["acl_del",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "acl_index"],
+ {"crc" : "0x82cc30ed"}
+ ]
+ ],
+"vl_api_version" :"0x3cd02d84"
+}
diff --git a/cmd/binapi-generator/testdata/af_packet.api.json b/cmd/binapi-generator/testdata/af_packet.api.json
new file mode 100644
index 0000000..211fc3e
--- /dev/null
+++ b/cmd/binapi-generator/testdata/af_packet.api.json
@@ -0,0 +1,37 @@
+{
+ "types" : [
+
+ ],
+ "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" : "0x92768640"}
+ ],
+ ["af_packet_create_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ ["u32", "sw_if_index"],
+ {"crc" : "0x718bac92"}
+ ],
+ ["af_packet_delete",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u8", "host_if_name", 64],
+ {"crc" : "0xc063ce85"}
+ ],
+ ["af_packet_delete_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0x1a80431a"}
+ ]
+ ],
+"vl_api_version" :"0x4ca71f33"
+}
diff --git a/cmd/binapi-generator/testdata/input-generate-error.json b/cmd/binapi-generator/testdata/input-generate-error.json
new file mode 100644
index 0000000..d5df76e
--- /dev/null
+++ b/cmd/binapi-generator/testdata/input-generate-error.json
@@ -0,0 +1,3 @@
+{
+ "key": "value"
+} \ No newline at end of file
diff --git a/cmd/binapi-generator/testdata/input-read-json-error.json b/cmd/binapi-generator/testdata/input-read-json-error.json
new file mode 100644
index 0000000..02691e3
--- /dev/null
+++ b/cmd/binapi-generator/testdata/input-read-json-error.json
@@ -0,0 +1 @@
+% \ No newline at end of file
diff --git a/cmd/binapi-generator/testdata/input.txt b/cmd/binapi-generator/testdata/input.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cmd/binapi-generator/testdata/input.txt
diff --git a/cmd/binapi-generator/testdata/ip.api.json b/cmd/binapi-generator/testdata/ip.api.json
new file mode 100644
index 0000000..970ec4b
--- /dev/null
+++ b/cmd/binapi-generator/testdata/ip.api.json
@@ -0,0 +1,292 @@
+{
+ "types" : [
+ ["fib_path",
+ ["u32", "sw_if_index"],
+ ["u32", "weight"],
+ ["u8", "is_local"],
+ ["u8", "is_drop"],
+ ["u8", "is_unreach"],
+ ["u8", "is_prohibit"],
+ ["u8", "afi"],
+ ["u8", "next_hop", 16],
+ {"crc" : "0x315b1889"}
+ ]
+ ],
+ "messages" : [
+ ["ip_fib_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ {"crc" : "0x5fe56ca3"}
+ ],
+ ["ip_fib_details",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["u32", "table_id"],
+ ["u8", "address_length"],
+ ["u8", "address", 4],
+ ["u32", "count"],
+ ["vl_api_fib_path_t", "path", 0, "count"],
+ {"crc" : "0xfd8c6584"}
+ ],
+ ["ip6_fib_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ {"crc" : "0x25c89676"}
+ ],
+ ["ip6_fib_details",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["u32", "table_id"],
+ ["u8", "address_length"],
+ ["u8", "address", 16],
+ ["u32", "count"],
+ ["vl_api_fib_path_t", "path", 0, "count"],
+ {"crc" : "0xe0825cb5"}
+ ],
+ ["ip_neighbor_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "sw_if_index"],
+ ["u8", "is_ipv6"],
+ {"crc" : "0x3289e160"}
+ ],
+ ["ip_neighbor_details",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["u8", "is_static"],
+ ["u8", "is_ipv6"],
+ ["u8", "mac_address", 6],
+ ["u8", "ip_address", 16],
+ {"crc" : "0x3a00e32a"}
+ ],
+ ["ip_neighbor_add_del",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "vrf_id"],
+ ["u32", "sw_if_index"],
+ ["u8", "is_add"],
+ ["u8", "is_ipv6"],
+ ["u8", "is_static"],
+ ["u8", "mac_address", 6],
+ ["u8", "dst_address", 16],
+ {"crc" : "0x66f2112c"}
+ ],
+ ["ip_neighbor_add_del_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0xe5b0f318"}
+ ],
+ ["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" : "0x92ad3798"}
+ ],
+ ["set_ip_flow_hash_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0x35a9e5eb"}
+ ],
+ ["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" : "0xec4a29f6"}
+ ],
+ ["sw_interface_ip6nd_ra_config_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0x16e25c5b"}
+ ],
+ ["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" : "0x5db6555c"}
+ ],
+ ["sw_interface_ip6nd_ra_prefix_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0x8050adb3"}
+ ],
+ ["sw_interface_ip6_enable_disable",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "sw_if_index"],
+ ["u8", "enable"],
+ {"crc" : "0x4a4e5405"}
+ ],
+ ["sw_interface_ip6_enable_disable_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0xeb8b4a40"}
+ ],
+ ["sw_interface_ip6_set_link_local_address",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "sw_if_index"],
+ ["u8", "address", 16],
+ {"crc" : "0x3db6d52b"}
+ ],
+ ["sw_interface_ip6_set_link_local_address_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0x0a781e17"}
+ ],
+ ["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"],
+ ["u8", "create_vrf_if_needed"],
+ ["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", "not_last"],
+ ["u8", "next_hop_weight"],
+ ["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" : "0xa0ab24bf"}
+ ],
+ ["ip_add_del_route_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0xea57492b"}
+ ],
+ ["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"],
+ ["u16", "grp_address_length"],
+ ["u8", "create_vrf_if_needed"],
+ ["u8", "is_add"],
+ ["u8", "is_ipv6"],
+ ["u8", "is_local"],
+ ["u8", "grp_address", 16],
+ ["u8", "src_address", 16],
+ {"crc" : "0x8312830f"}
+ ],
+ ["ip_mroute_add_del_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0x8cabe02c"}
+ ],
+ ["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" : "0x190d4266"}
+ ],
+ ["ip_address_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "sw_if_index"],
+ ["u8", "is_ipv6"],
+ {"crc" : "0x632e859a"}
+ ],
+ ["ip_details",
+ ["u16", "_vl_msg_id"],
+ ["u32", "sw_if_index"],
+ ["u32", "context"],
+ ["u8", "is_ipv6"],
+ {"crc" : "0x695c8227"}
+ ],
+ ["ip_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u8", "is_ipv6"],
+ {"crc" : "0x3c1e33e0"}
+ ],
+ ["mfib_signal_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ {"crc" : "0xbbbbd40d"}
+ ],
+ ["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" : "0x6ba92c72"}
+ ]
+ ],
+"vl_api_version" :"0x6a819870"
+}