Release 0.3.0
[govpp.git] / cmd / binapi-generator / generate.go
index e29c84d..a8de5d5 100644 (file)
 package main
 
 import (
-       "bufio"
        "bytes"
        "fmt"
        "io"
+       "os/exec"
+       "path"
        "path/filepath"
+       "sort"
        "strings"
        "unicode"
 )
 
+// 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 = 1
+
 const (
+       inputFileExt  = ".api.json" // file extension of the VPP API files
+       outputFileExt = ".ba.go"    // file extension of the Go generated files
+
        govppApiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API package
-       inputFileExt       = ".api.json"               // file extension of the VPP binary API files
-       outputFileExt      = ".ba.go"                  // file extension of the Go generated files
+
+       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
 )
 
 // context is a structure storing data for code generation
@@ -35,9 +54,14 @@ type context struct {
        inputFile  string // input file with VPP API in JSON
        outputFile string // output file with generated Go package
 
-       inputData []byte        // contents of the input file
-       inputBuff *bytes.Buffer // contents of the input file currently being read
-       inputLine int           // currently processed line in the input file
+       importPrefix string // defines import path prefix for importing types
+
+       inputData []byte // contents of the input file
+
+       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
 
        moduleName  string // name of the source VPP module
        packageName string // name of the Go package being generated
@@ -45,8 +69,8 @@ type context struct {
        packageData *Package // parsed package data
 }
 
-// getContext returns context details of the code generation task
-func getContext(inputFile, outputDir string) (*context, error) {
+// newContext returns context details of the code generation task
+func newContext(inputFile, outputDir string) (*context, error) {
        if !strings.HasSuffix(inputFile, inputFileExt) {
                return nil, fmt.Errorf("invalid input file name: %q", inputFile)
        }
@@ -77,183 +101,272 @@ func getContext(inputFile, outputDir string) (*context, error) {
        return ctx, nil
 }
 
-// generatePackage generates code for the parsed package data and writes it into w
-func generatePackage(ctx *context, w *bufio.Writer) error {
+func generatePackage(ctx *context, w io.Writer) error {
+       logf("----------------------------")
        logf("generating package %q", ctx.packageName)
+       logf("----------------------------")
+
+       fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+       fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
+       fmt.Fprintln(w)
 
-       // generate file header
        generateHeader(ctx, w)
        generateImports(ctx, w)
 
-       if *includeAPIVer {
-               const APIVerConstName = "VlAPIVersion"
-               fmt.Fprintf(w, "// %s represents version of the API.\n", APIVerConstName)
-               fmt.Fprintf(w, "const %s = %v\n", APIVerConstName, ctx.packageData.APIVersion)
-               fmt.Fprintln(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.moduleName)
+
+       if ctx.includeAPIVersion {
+               if ctx.packageData.Version != "" {
+                       fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
+                       fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
+               }
+               fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
+               fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
        }
+       fmt.Fprintln(w, ")")
+       fmt.Fprintln(w)
 
        // generate enums
        if len(ctx.packageData.Enums) > 0 {
-               fmt.Fprintf(w, "/* Enums */\n\n")
-
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, enum := range ctx.packageData.Enums {
+                       if imp, ok := ctx.packageData.Imports[enum.Name]; ok {
+                               generateImportedAlias(ctx, w, enum.Name, &imp)
+                               continue
+                       }
                        generateEnum(ctx, w, &enum)
                }
        }
 
+       // generate aliases
+       if len(ctx.packageData.Aliases) > 0 {
+               for _, alias := range ctx.packageData.Aliases {
+                       if imp, ok := ctx.packageData.Imports[alias.Name]; ok {
+                               generateImportedAlias(ctx, w, alias.Name, &imp)
+                               continue
+                       }
+                       generateAlias(ctx, w, &alias)
+               }
+       }
+
        // generate types
        if len(ctx.packageData.Types) > 0 {
-               fmt.Fprintf(w, "/* Types */\n\n")
-
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, typ := range ctx.packageData.Types {
+                       if imp, ok := ctx.packageData.Imports[typ.Name]; ok {
+                               generateImportedAlias(ctx, w, typ.Name, &imp)
+                               continue
+                       }
                        generateType(ctx, w, &typ)
                }
        }
 
        // generate unions
        if len(ctx.packageData.Unions) > 0 {
-               fmt.Fprintf(w, "/* Unions */\n\n")
-
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, union := range ctx.packageData.Unions {
+                       if imp, ok := ctx.packageData.Imports[union.Name]; ok {
+                               generateImportedAlias(ctx, w, union.Name, &imp)
+                               continue
+                       }
                        generateUnion(ctx, w, &union)
                }
        }
 
        // generate messages
        if len(ctx.packageData.Messages) > 0 {
-               fmt.Fprintf(w, "/* Messages */\n\n")
-
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, msg := range ctx.packageData.Messages {
                        generateMessage(ctx, w, &msg)
                }
-       }
 
-       // generate services
-       if len(ctx.packageData.Services) > 0 {
-               fmt.Fprintf(w, "/* Services */\n\n")
+               // generate message registrations
+               fmt.Fprintln(w, "func init() {")
+               for _, msg := range ctx.packageData.Messages {
+                       name := camelCaseName(msg.Name)
+                       fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
+               }
+               fmt.Fprintln(w, "}")
+               fmt.Fprintln(w)
 
-               fmt.Fprintf(w, "type %s interface {\n", "Services")
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
-               for _, svc := range ctx.packageData.Services {
-                       generateService(ctx, w, &svc)
+               // 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.packageData.Messages {
+                       name := camelCaseName(msg.Name)
+                       fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
                }
                fmt.Fprintln(w, "}")
+               fmt.Fprintln(w, "}")
        }
 
-       // TODO: generate implementation for Services interface
-
-       // generate message registrations
-       fmt.Fprintln(w)
-       fmt.Fprintln(w, "func init() {")
-       for _, msg := range ctx.packageData.Messages {
-               name := camelCaseName(msg.Name)
-               fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
+       if ctx.includeServices {
+               // generate services
+               if len(ctx.packageData.Services) > 0 {
+                       generateServices(ctx, w, ctx.packageData.Services)
+               }
        }
-       fmt.Fprintln(w, "}")
 
-       // flush the data:
-       if err := w.Flush(); err != nil {
-               return fmt.Errorf("flushing data to %s failed: %v", ctx.outputFile, err)
-       }
+       generateFooter(ctx, w)
 
        return nil
 }
 
-// generateHeader writes generated package header into w
 func generateHeader(ctx *context, w io.Writer) {
-       fmt.Fprintln(w, "// Code generated by GoVPP binapi-generator. DO NOT EDIT.")
-       fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
-       fmt.Fprintln(w)
-
        fmt.Fprintln(w, "/*")
-       fmt.Fprintf(w, "Package %s is a generated VPP binary API of the '%s' VPP module.\n", ctx.packageName, ctx.moduleName)
-       fmt.Fprintln(w)
-       fmt.Fprintln(w, "It is generated from this file:")
-       fmt.Fprintf(w, "\t%s\n", filepath.Base(ctx.inputFile))
+       fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
        fmt.Fprintln(w)
-       fmt.Fprintln(w, "It contains these VPP binary API objects:")
-       var printObjNum = func(obj string, num int) {
+       fmt.Fprintln(w, "It consists of:")
+       printObjNum := func(obj string, num int) {
                if num > 0 {
                        if num > 1 {
-                               obj += "s"
+                               if strings.HasSuffix(obj, "s") {
+
+                                       obj += "es"
+                               } else {
+                                       obj += "s"
+                               }
                        }
-                       fmt.Fprintf(w, "\t%d %s\n", num, obj)
+                       fmt.Fprintf(w, "\t%3d %s\n", num, obj)
                }
        }
-       printObjNum("message", len(ctx.packageData.Messages))
-       printObjNum("type", len(ctx.packageData.Types))
        printObjNum("enum", len(ctx.packageData.Enums))
+       printObjNum("alias", len(ctx.packageData.Aliases))
+       printObjNum("type", len(ctx.packageData.Types))
        printObjNum("union", len(ctx.packageData.Unions))
+       printObjNum("message", len(ctx.packageData.Messages))
        printObjNum("service", len(ctx.packageData.Services))
        fmt.Fprintln(w, "*/")
        fmt.Fprintf(w, "package %s\n", ctx.packageName)
        fmt.Fprintln(w)
+
 }
 
-// generateImports writes generated package imports into w
 func generateImports(ctx *context, w io.Writer) {
-       fmt.Fprintf(w, "import \"%s\"\n", govppApiImportPath)
-       fmt.Fprintf(w, "import \"%s\"\n", "github.com/lunixbochs/struc")
-       fmt.Fprintf(w, "import \"%s\"\n", "bytes")
+       fmt.Fprintln(w, "import (")
+       fmt.Fprintf(w, "\tapi \"%s\"\n", govppApiImportPath)
+       fmt.Fprintf(w, "\tbytes \"%s\"\n", "bytes")
+       fmt.Fprintf(w, "\tcontext \"%s\"\n", "context")
+       fmt.Fprintf(w, "\tio \"%s\"\n", "io")
+       fmt.Fprintf(w, "\tstrconv \"%s\"\n", "strconv")
+       fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
+       if len(ctx.packageData.Imports) > 0 {
+               fmt.Fprintln(w)
+               for _, imp := range getImports(ctx) {
+                       importPath := path.Join(ctx.importPrefix, imp)
+                       if importPath == "" {
+                               importPath = getImportPath(filepath.Dir(ctx.outputFile), imp)
+                       }
+                       fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
+               }
+       }
+       fmt.Fprintln(w, ")")
+       fmt.Fprintln(w)
+}
+
+func getImportPath(outputDir string, pkg string) string {
+       absPath, _ := filepath.Abs(filepath.Join(outputDir, "..", pkg))
+       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 getImports(ctx *context) (imports []string) {
+       impmap := map[string]struct{}{}
+       for _, imp := range ctx.packageData.Imports {
+               if _, ok := impmap[imp.Package]; !ok {
+                       imports = append(imports, imp.Package)
+                       impmap[imp.Package] = struct{}{}
+               }
+       }
+       sort.Strings(imports)
+       return imports
+}
+
+func generateFooter(ctx *context, w io.Writer) {
+       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)
 
        fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
-       fmt.Fprintf(w, "var _ = struc.Pack\n")
+       fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
        fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
-       fmt.Fprintln(w)
+       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")
 }
 
-// generateComment writes generated comment for the object into w
 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
-       fmt.Fprintf(w, "// %s represents the VPP binary API %s '%s'.\n", goName, objKind, vppName)
+       if objKind == "service" {
+               fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
+       } else {
+               fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
+       }
+
+       if !ctx.includeComments {
+               return
+       }
 
        var isNotSpace = func(r rune) bool {
                return !unicode.IsSpace(r)
        }
 
        // print out the source of the generated object
+       mapType := false
        objFound := false
        objTitle := fmt.Sprintf(`"%s",`, vppName)
+       switch objKind {
+       case "alias", "service":
+               objTitle = fmt.Sprintf(`"%s": {`, vppName)
+               mapType = true
+       }
+
+       inputBuff := bytes.NewBuffer(ctx.inputData)
+       inputLine := 0
+
+       var trimIndent string
        var indent int
        for {
-               line, err := ctx.inputBuff.ReadString('\n')
+               line, err := inputBuff.ReadString('\n')
                if err != nil {
                        break
                }
-               ctx.inputLine++
+               inputLine++
 
+               noSpaceAt := strings.IndexFunc(line, isNotSpace)
                if !objFound {
                        indent = strings.Index(line, objTitle)
                        if indent == -1 {
                                continue
                        }
+                       trimIndent = line[:indent]
                        // If no other non-whitespace character then we are at the message header.
                        if trimmed := strings.TrimSpace(line); trimmed == objTitle {
                                objFound = true
-                               fmt.Fprintf(w, "// Generated from '%s', line %d:\n", filepath.Base(ctx.inputFile), ctx.inputLine)
                                fmt.Fprintln(w, "//")
                        }
-               } else {
-                       if strings.IndexFunc(line, isNotSpace) < indent {
-                               break // end of the object definition in JSON
-                       }
+               } else if noSpaceAt < indent {
+                       break // end of the definition in JSON for array types
+               } else if objFound && mapType && noSpaceAt <= indent {
+                       fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
+                       break // end of the definition in JSON for map types (aliases, services)
                }
-               fmt.Fprint(w, "//", line)
+               fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
        }
 
        fmt.Fprintln(w, "//")
 }
 
-// generateEnum writes generated code for the enum into w
 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
        name := camelCaseName(enum.Name)
        typ := binapiTypes[enum.Type]
@@ -267,54 +380,66 @@ func generateEnum(ctx *context, w io.Writer, enum *Enum) {
        fmt.Fprintf(w, "type %s %s\n", name, typ)
        fmt.Fprintln(w)
 
-       fmt.Fprintln(w, "const (")
-
        // 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.Fprintf(w, "var %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.Fprintln(w)
+
+       fmt.Fprintf(w, "var %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.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 strconv.Itoa(int(x))\n")
+       fmt.Fprintln(w, "}")
        fmt.Fprintln(w)
 }
 
-// generateType writes generated code for the type into w
-func generateType(ctx *context, w io.Writer, typ *Type) {
-       name := camelCaseName(typ.Name)
+func generateImportedAlias(ctx *context, w io.Writer, tName string, imp *Import) {
+       name := camelCaseName(tName)
 
-       logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
+       fmt.Fprintf(w, "type %s = %s.%s\n", name, imp.Package, name)
 
-       // generate struct comment
-       generateComment(ctx, w, name, typ.Name, "type")
+       fmt.Fprintln(w)
+}
 
-       // generate struct definition
-       fmt.Fprintf(w, "type %s struct {\n", name)
+func generateAlias(ctx *context, w io.Writer, alias *Alias) {
+       name := camelCaseName(alias.Name)
 
-       // generate struct fields
-       for i, field := range typ.Fields {
-               // skip internal fields
-               switch strings.ToLower(field.Name) {
-               case "crc", "_vl_msg_id":
-                       continue
-               }
+       logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
 
-               generateField(ctx, w, typ.Fields, i)
-       }
+       // generate struct comment
+       generateComment(ctx, w, name, alias.Name, "alias")
 
-       // generate end of the struct
-       fmt.Fprintln(w, "}")
+       // generate struct definition
+       fmt.Fprintf(w, "type %s ", name)
 
-       // generate name getter
-       generateTypeNameGetter(w, name, typ.Name)
+       if alias.Length > 0 {
+               fmt.Fprintf(w, "[%d]", alias.Length)
+       }
 
-       // generate CRC getter
-       generateCrcGetter(w, name, typ.CRC)
+       dataType := convertToGoType(ctx, alias.Type)
+       fmt.Fprintf(w, "%s\n", dataType)
 
        fmt.Fprintln(w)
 }
 
-// generateUnion writes generated code for the union into w
 func generateUnion(ctx *context, w io.Writer, union *Union) {
        name := camelCaseName(union.Name)
 
@@ -330,8 +455,7 @@ func generateUnion(ctx *context, w io.Writer, union *Union) {
        maxSize := getUnionSize(ctx, union)
 
        // generate data field
-       fieldName := "Union_data"
-       fmt.Fprintf(w, "\t%s [%d]byte\n", fieldName, maxSize)
+       fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
 
        // generate end of the struct
        fmt.Fprintln(w, "}")
@@ -340,7 +464,9 @@ func generateUnion(ctx *context, w io.Writer, union *Union) {
        generateTypeNameGetter(w, name, union.Name)
 
        // generate CRC getter
-       generateCrcGetter(w, name, union.CRC)
+       if union.CRC != "" {
+               generateCrcGetter(w, name, union.CRC)
+       }
 
        // generate getters for fields
        for _, field := range union.Fields {
@@ -356,9 +482,9 @@ func generateUnion(ctx *context, w io.Writer, union *Union) {
 }
 
 // generateUnionMethods generates methods that implement struc.Custom
-// interface to allow having Union_data field unexported
+// 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) {
+/*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) {
@@ -379,26 +505,65 @@ func (u *%[1]s) String() string {
        return string(u.union_data[:])
 }
 `, structName)
-}
+}*/
 
 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.Union_data[:], b.Bytes())
+       copy(u.%[4]s[:], b.Bytes())
 }
 func (u *%[1]s) Get%[2]s() (a %[3]s) {
-       var b = bytes.NewReader(u.Union_data[:])
+       var b = bytes.NewReader(u.%[4]s[:])
        struc.Unpack(b, &a)
        return
 }
-`, structName, getterField, getterStruct)
+`, structName, getterField, getterStruct, unionDataField)
+}
+
+func generateType(ctx *context, w io.Writer, typ *Type) {
+       name := camelCaseName(typ.Name)
+
+       logf(" writing type %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, field := range typ.Fields {
+               // skip internal fields
+               switch strings.ToLower(field.Name) {
+               case crcField, msgIdField:
+                       continue
+               }
+
+               generateField(ctx, w, typ.Fields, i)
+       }
+
+       // generate end of the struct
+       fmt.Fprintln(w, "}")
+
+       // generate name getter
+       generateTypeNameGetter(w, name, typ.Name)
+
+       // generate CRC getter
+       if typ.CRC != "" {
+               generateCrcGetter(w, name, typ.CRC)
+       }
+
+       fmt.Fprintln(w)
 }
 
-// generateMessage writes generated code for the message into w
 func generateMessage(ctx *context, w io.Writer, msg *Message) {
        name := camelCaseName(msg.Name)
 
@@ -417,26 +582,28 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) {
        n := 0
        for i, field := range msg.Fields {
                if i == 1 {
-                       if field.Name == "client_index" {
-                               // "client_index" as the second member, this might be an event message or a request
+                       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 == "context" {
+                       } else if field.Name == contextField {
                                // reply needs "context" as the second member
                                msgType = replyMessage
                        }
                } else if i == 2 {
-                       if wasClientIndex && field.Name == "context" {
-                               // request needs "client_index" as the second member and "context" as the third member
+                       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 "crc", "_vl_msg_id":
+               case crcField, msgIdField:
                        continue
-               case "client_index", "context":
+               case clientIndexField, contextField:
                        if n == 0 {
                                continue
                        }
@@ -452,20 +619,15 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) {
        // generate end of the struct
        fmt.Fprintln(w, "}")
 
-       // generate name getter
+       // generate message methods
+       generateMessageResetMethod(w, name)
        generateMessageNameGetter(w, name, msg.Name)
-
-       // generate CRC getter
        generateCrcGetter(w, name, msg.CRC)
-
-       // generate message type getter method
        generateMessageTypeGetter(w, name, msgType)
 
-       // generate message factory
-       generateMessageFactory(w, name)
+       fmt.Fprintln(w)
 }
 
-// generateField writes generated code for the field into w
 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
        field := fields[i]
 
@@ -473,90 +635,215 @@ func generateField(ctx *context, w io.Writer, fields []Field, i int) {
        fieldName = camelCaseName(fieldName)
 
        dataType := convertToGoType(ctx, field.Type)
-
        fieldType := dataType
-       if field.IsArray() {
+
+       // 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"
                }
-               fieldType = "[]" + dataType
+               if dataType == "string" && field.SpecifiedLen {
+                       fieldType = "string"
+                       dataType = "byte"
+               } else {
+                       fieldType = "[]" + dataType
+               }
        }
        fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
 
-       if field.Length > 0 {
+       fieldTags := map[string]string{}
+
+       if field.Length > 0 && field.SpecifiedLen {
                // fixed size array
-               fmt.Fprintf(w, "\t`struc:\"[%d]%s\"`", field.Length, dataType)
+               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)
-                               fmt.Fprintf(w, "\t`struc:\"sizeof=%s\"`", sizeOfName)
+                               fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
                        }
                }
        }
 
-       fmt.Fprintln(w)
-}
-
-// generateService writes generated code for the service into w
-func generateService(ctx *context, w io.Writer, svc *Service) {
-       reqTyp := camelCaseName(svc.RequestType)
+       if ctx.includeBinapiNames {
+               fieldTags["binapi"] = field.Name
+       }
+       if field.Meta.Limit > 0 {
+               fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
+       }
 
-       // method name is same as parameter type name by default
-       method := svc.MethodName()
-       params := fmt.Sprintf("*%s", reqTyp)
-       returns := "error"
-       if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
-               returns = fmt.Sprintf("(*%s, error)", replyTyp)
+       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.Fprintf(w, "\t%s(%s) %s\n", method, params, returns)
+       fmt.Fprintln(w)
 }
 
-// generateMessageNameGetter generates getter for original VPP message name into the provider writer
-func generateMessageNameGetter(w io.Writer, structName, msgName string) {
-       fmt.Fprintf(w, `func (*%s) GetMessageName() string {
-       return %q
+func generateMessageResetMethod(w io.Writer, structName string) {
+       fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
 }
-`, structName, msgName)
+
+func generateMessageNameGetter(w io.Writer, structName, msgName string) {
+       fmt.Fprintf(w, "func (*%s) GetMessageName() string {    return %q }\n", structName, msgName)
 }
 
-// generateTypeNameGetter generates getter for original VPP type name into the provider writer
 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
-       fmt.Fprintf(w, `func (*%s) GetTypeName() string {
-       return %q
-}
-`, structName, msgName)
+       fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName)
 }
 
-// generateCrcGetter generates getter for CRC checksum of the message definition into the provider writer
 func generateCrcGetter(w io.Writer, structName, crc string) {
        crc = strings.TrimPrefix(crc, "0x")
-       fmt.Fprintf(w, `func (*%s) GetCrcString() string {
-       return %q
-}
-`, structName, crc)
+       fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc)
 }
 
-// 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 {")
+       fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {")
        if msgType == requestMessage {
-               fmt.Fprintln(w, "\treturn api.RequestMessage")
+               fmt.Fprintf(w, "\treturn api.RequestMessage")
        } else if msgType == replyMessage {
-               fmt.Fprintln(w, "\treturn api.ReplyMessage")
+               fmt.Fprintf(w, "\treturn api.ReplyMessage")
        } else if msgType == eventMessage {
-               fmt.Fprintln(w, "\treturn api.EventMessage")
+               fmt.Fprintf(w, "\treturn api.EventMessage")
        } else {
-               fmt.Fprintln(w, "\treturn api.OtherMessage")
+               fmt.Fprintf(w, "\treturn api.OtherMessage")
        }
        fmt.Fprintln(w, "}")
+       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+"{}")
+func generateServices(ctx *context, w io.Writer, services []Service) {
+
+       // generate services comment
+       generateComment(ctx, w, serviceApiName, "services", "service")
+
+       // generate service api
+       fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
+       for _, svc := range services {
+               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 _, svc := range services {
+               method := camelCaseName(svc.RequestType)
+               if m := strings.TrimSuffix(method, "Dump"); method != m {
+                       method = "Dump" + m
+               }
+
+               fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
+               generateServiceMethod(ctx, w, &svc)
+               fmt.Fprintln(w, " {")
+               if svc.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(svc.ReplyType); 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 svc.Stream {
+                       replyTyp := camelCaseName(svc.ReplyType)
+                       method := camelCaseName(svc.RequestType)
+                       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)
+               }
+       }
+
+       fmt.Fprintln(w)
+}
+
+func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
+       reqTyp := camelCaseName(svc.RequestType)
+
+       // method name is same as parameter type name by default
+       method := reqTyp
+       if svc.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(svc.ReplyType); replyType != "" {
+               var replyTyp string
+               if svc.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)
 }