Improve binapi generator
[govpp.git] / binapigen / generate.go
index d35427f..689463e 100644 (file)
@@ -16,1338 +16,468 @@ package binapigen
 
 import (
        "fmt"
-       "io"
+       "path"
        "sort"
+       "strconv"
        "strings"
 
-       "git.fd.io/govpp.git/version"
-       "github.com/sirupsen/logrus"
+       "git.fd.io/govpp.git/internal/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
-
-// common message fields
+// library dependencies
 const (
-       msgIdField       = "_vl_msg_id"
-       clientIndexField = "client_index"
-       contextField     = "context"
-       retvalField      = "retval"
-)
+       strconvPkg = GoImportPath("strconv")
 
-// global API info
-const (
-       constModuleName = "ModuleName" // module name constant
-       constAPIVersion = "APIVersion" // API version constant
-       constVersionCrc = "VersionCrc" // version CRC constant
+       govppApiPkg   = GoImportPath("git.fd.io/govpp.git/api")
+       govppCodecPkg = GoImportPath("git.fd.io/govpp.git/codec")
 )
 
-// generated fiels
+// generated names
 const (
-       unionDataField = "XXX_UnionData" // name for the union data field
-)
-
-// MessageType represents the type of a VPP message
-type MessageType int
+       apiName    = "APIFile"    // API file name
+       apiVersion = "APIVersion" // API version number
+       apiCrc     = "VersionCrc" // version checksum
 
-const (
-       requestMessage MessageType = iota // VPP request message
-       replyMessage                      // VPP reply message
-       eventMessage                      // VPP event message
-       otherMessage                      // other VPP message
+       fieldUnionData = "XXX_UnionData" // name for the union data field
 )
 
-func generateFileBinapi(ctx *GenFile, w io.Writer) {
+func GenerateAPI(gen *Generator, file *File) *GenFile {
        logf("----------------------------")
-       logf("generating BINAPI file package: %q", ctx.file.PackageName)
+       logf(" Generate API - %s", file.Desc.Name)
        logf("----------------------------")
 
-       // generate file header
-       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)
+       filename := path.Join(file.FilenamePrefix, file.Desc.Name+".ba.go")
+       g := gen.NewGenFile(filename, file.GoImportPath)
+       g.file = file
+
+       g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+       if !gen.opts.NoVersionInfo {
+               g.P("// versions:")
+               g.P("//  binapi-generator: ", version.Version())
+               g.P("//  VPP:              ", g.gen.vppVersion)
+               g.P("// source: ", g.file.Desc.Path)
        }
-       fmt.Fprintf(w, "// source: %s\n", ctx.file.Path)
-       fmt.Fprintln(w)
+       g.P()
 
-       generatePackageHeader(ctx, w)
-       generateImports(ctx, w)
+       genPackageComment(g)
+       g.P("package ", file.PackageName)
+       g.P()
 
-       generateApiInfo(ctx, w)
-       generateTypes(ctx, w)
-       generateMessages(ctx, w)
+       for _, imp := range g.file.Imports {
+               genImport(g, imp)
+       }
 
-       generateImportRefs(ctx, w)
-}
+       // generate version assertion
+       g.P("// This is a compile-time assertion to ensure that this generated file")
+       g.P("// is compatible with the GoVPP api package it is being compiled against.")
+       g.P("// A compilation error at this line likely means your copy of the")
+       g.P("// GoVPP api package needs to be updated.")
+       g.P("const _ = ", govppApiPkg.Ident("GoVppAPIPackageIsVersion"), generatedCodeVersion)
+       g.P()
 
-func generatePackageHeader(ctx *GenFile, w io.Writer) {
-       fmt.Fprintln(w, "/*")
-       fmt.Fprintf(w, "Package %s contains generated code for VPP API file %s.api (%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)
-               }
+       if !file.isTypesFile() {
+               g.P("const (")
+               g.P(apiName, " = ", strconv.Quote(g.file.Desc.Name))
+               g.P(apiVersion, " = ", strconv.Quote(g.file.Version))
+               g.P(apiCrc, " = ", g.file.Desc.CRC)
+               g.P(")")
+               g.P()
        }
-       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, `       "fmt"`)
-       fmt.Fprintln(w, `       "io"`)
-       fmt.Fprintln(w, `       "math"`)
-       fmt.Fprintln(w, `       "net"`)
-       fmt.Fprintln(w, `       "strconv"`)
-       fmt.Fprintln(w, `       "strings"`)
-       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")
-       imports := listImports(ctx)
-       if len(imports) > 0 {
-               fmt.Fprintln(w)
-               for imp, importPath := range imports {
-                       fmt.Fprintf(w, "\t%s \"%s\"\n", imp, importPath)
-               }
+       for _, enum := range g.file.Enums {
+               genEnum(g, enum)
        }
-       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 generateApiInfo(ctx *GenFile, w io.Writer) {
-       // 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)
+       for _, alias := range g.file.Aliases {
+               genAlias(g, alias)
        }
-       fmt.Fprintln(w, ")")
-       fmt.Fprintln(w)
-}
-
-func generateTypes(ctx *GenFile, w io.Writer) {
-       // generate enums
-       if len(ctx.file.Enums) > 0 {
-               for _, enum := range ctx.file.Enums {
-                       if imp, ok := ctx.file.imports[enum.Name]; ok {
-                               if strings.HasSuffix(ctx.file.Name, "_types") {
-                                       generateImportedAlias(ctx, w, enum.GoName, imp)
-                               }
-                               continue
-                       }
-                       generateEnum(ctx, w, enum)
-               }
+       for _, typ := range g.file.Structs {
+               genStruct(g, typ)
        }
-
-       // generate aliases
-       if len(ctx.file.Aliases) > 0 {
-               for _, alias := range ctx.file.Aliases {
-                       if imp, ok := ctx.file.imports[alias.Name]; ok {
-                               if strings.HasSuffix(ctx.file.Name, "_types") {
-                                       generateImportedAlias(ctx, w, alias.GoName, imp)
-                               }
-                               continue
-                       }
-                       generateAlias(ctx, w, alias)
-               }
+       for _, union := range g.file.Unions {
+               genUnion(g, union)
        }
+       genMessages(g)
 
-       // generate types
-       if len(ctx.file.Structs) > 0 {
-               for _, typ := range ctx.file.Structs {
-                       if imp, ok := ctx.file.imports[typ.Name]; ok {
-                               if strings.HasSuffix(ctx.file.Name, "_types") {
-                                       generateImportedAlias(ctx, w, typ.GoName, imp)
-                               }
-                               continue
-                       }
-                       generateStruct(ctx, w, typ)
-               }
-       }
+       return g
+}
 
-       // generate unions
-       if len(ctx.file.Unions) > 0 {
-               for _, union := range ctx.file.Unions {
-                       if imp, ok := ctx.file.imports[union.Name]; ok {
-                               if strings.HasSuffix(ctx.file.Name, "_types") {
-                                       generateImportedAlias(ctx, w, union.GoName, imp)
+func genPackageComment(g *GenFile) {
+       apifile := g.file.Desc.Name + ".api"
+       g.P("// Package ", g.file.PackageName, " contains generated bindings for API file ", apifile, ".")
+       g.P("//")
+       g.P("// Contents:")
+       printObjNum := func(obj string, num int) {
+               if num > 0 {
+                       if num > 1 {
+                               if strings.HasSuffix(obj, "s") {
+                                       obj += "es"
+                               } else {
+                                       obj += "s"
                                }
-                               continue
                        }
-                       generateUnion(ctx, w, union)
+                       g.P("// ", fmt.Sprintf("%3d", num), " ", obj)
                }
        }
+       printObjNum("alias", len(g.file.Aliases))
+       printObjNum("enum", len(g.file.Enums))
+       printObjNum("struct", len(g.file.Structs))
+       printObjNum("union", len(g.file.Unions))
+       printObjNum("message", len(g.file.Messages))
+       g.P("//")
 }
 
-func generateMessages(ctx *GenFile, w io.Writer) {
-       if len(ctx.file.Messages) == 0 {
+func genImport(g *GenFile, imp string) {
+       impFile, ok := g.gen.FilesByName[imp]
+       if !ok {
                return
        }
-
-       for _, msg := range ctx.file.Messages {
-               generateMessage(ctx, w, msg)
-       }
-
-       // generate message registrations
-       initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName)
-
-       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)
+       if impFile.GoImportPath == g.file.GoImportPath {
+               // Skip generating imports for types in the same package
+               return
        }
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w, "}")
+       // Generate imports for all dependencies, even if not used
+       g.Import(impFile.GoImportPath)
 }
 
-func generateImportRefs(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 _ = strings.Contains\n")
-       fmt.Fprintf(w, "var _ = struc.Pack\n")
-       fmt.Fprintf(w, "var _ = binary.BigEndian\n")
-       fmt.Fprintf(w, "var _ = math.Float32bits\n")
-       fmt.Fprintf(w, "var _ = net.ParseIP\n")
-       fmt.Fprintf(w, "var _ = fmt.Errorf\n")
+func genTypeComment(g *GenFile, goName string, vppName string, objKind string) {
+       g.P("// ", goName, " defines ", objKind, " '", vppName, "'.")
 }
 
-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 genEnum(g *GenFile, enum *Enum) {
+       logf("gen ENUM %s (%s) - %d entries", enum.GoName, enum.Name, len(enum.Entries))
 
-func generateEnum(ctx *GenFile, w io.Writer, enum *Enum) {
-       name := enum.GoName
-       typ := binapiTypes[enum.Type]
+       genTypeComment(g, enum.GoName, enum.Name, "enum")
 
-       logf(" writing ENUM %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
+       gotype := BaseTypesGo[enum.Type]
 
-       // 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)
+       g.P("type ", enum.GoName, " ", gotype)
+       g.P()
 
        // generate enum entries
-       fmt.Fprintln(w, "const (")
+       g.P("const (")
        for _, entry := range enum.Entries {
-               fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
+               g.P(entry.Name, " ", enum.GoName, " = ", entry.Value)
        }
-       fmt.Fprintln(w, ")")
-       fmt.Fprintln(w)
+       g.P(")")
+       g.P()
 
        // generate enum conversion maps
-       fmt.Fprintln(w, "var (")
-       fmt.Fprintf(w, "%s_name = map[%s]string{\n", name, typ)
+       g.P("var (")
+       g.P(enum.GoName, "_name = map[", gotype, "]string{")
        for _, entry := range enum.Entries {
-               fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
+               g.P(entry.Value, ": ", strconv.Quote(entry.Name), ",")
        }
-       fmt.Fprintln(w, "}")
-       fmt.Fprintf(w, "%s_value = map[string]%s{\n", name, typ)
+       g.P("}")
+       g.P(enum.GoName, "_value = map[string]", gotype, "{")
        for _, entry := range enum.Entries {
-               fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
+               g.P(strconv.Quote(entry.Name), ": ", entry.Value, ",")
+       }
+       g.P("}")
+       g.P(")")
+       g.P()
+
+       if isEnumFlag(enum) {
+               size := BaseTypeSizes[enum.Type] * 8
+               g.P("func (x ", enum.GoName, ") String() string {")
+               g.P("   s, ok := ", enum.GoName, "_name[", gotype, "(x)]")
+               g.P("   if ok { return s }")
+               g.P("   str := func(n ", gotype, ") string {")
+               g.P("           s, ok := ", enum.GoName, "_name[", gotype, "(n)]")
+               g.P("           if ok {")
+               g.P("                   return s")
+               g.P("           }")
+               g.P("           return \"", enum.GoName, "(\" + ", strconvPkg.Ident("Itoa"), "(int(n)) + \")\"")
+               g.P("   }")
+               g.P("   for i := ", gotype, "(0); i <= ", size, "; i++ {")
+               g.P("           val := ", gotype, "(x)")
+               g.P("           if val&(1<<i) != 0 {")
+               g.P("                   if s != \"\" {")
+               g.P("                           s += \"|\"")
+               g.P("                   }")
+               g.P("                   s += str(1<<i)")
+               g.P("           }")
+               g.P("   }")
+               g.P("   if s == \"\" {")
+               g.P("           return str(", gotype, "(x))")
+               g.P("   }")
+               g.P("   return s")
+               g.P("}")
+               g.P()
+       } else {
+               g.P("func (x ", enum.GoName, ") String() string {")
+               g.P("   s, ok := ", enum.GoName, "_name[", gotype, "(x)]")
+               g.P("   if ok { return s }")
+               g.P("   return \"", enum.GoName, "(\" + ", strconvPkg.Ident("Itoa"), "(int(x)) + \")\"")
+               g.P("}")
+               g.P()
        }
-       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)
+func genAlias(g *GenFile, alias *Alias) {
+       logf("gen ALIAS %s (%s) - type: %s length: %d", alias.GoName, alias.Name, alias.Type, alias.Length)
 
-       // generate struct comment
-       generateComment(ctx, w, name, alias.Name, "alias")
-
-       // generate struct definition
-       fmt.Fprintf(w, "type %s ", name)
+       genTypeComment(g, alias.GoName, alias.Name, "alias")
 
+       var gotype string
+       switch {
+       case alias.TypeStruct != nil:
+               gotype = g.GoIdent(alias.TypeStruct.GoIdent)
+       case alias.TypeUnion != nil:
+               gotype = g.GoIdent(alias.TypeUnion.GoIdent)
+       default:
+               gotype = BaseTypesGo[alias.Type]
+       }
        if alias.Length > 0 {
-               fmt.Fprintf(w, "[%d]", alias.Length)
+               gotype = fmt.Sprintf("[%d]%s", alias.Length, gotype)
        }
 
-       dataType := convertToGoType(ctx.file, alias.Type)
-       fmt.Fprintf(w, "%s\n", dataType)
+       g.P("type ", alias.GoName, " ", gotype)
+       g.P()
 
        // generate alias-specific methods
        switch alias.Name {
+       case "ip4_address":
+               generateIPConversion(g, alias.GoName, 4)
+       case "ip6_address":
+               generateIPConversion(g, alias.GoName, 16)
+       case "address_with_prefix":
+               generateAddressWithPrefixConversion(g, alias.GoName)
        case "mac_address":
-               fmt.Fprintln(w)
-               generateMacAddressConversion(w, name)
+               generateMacAddressConversion(g, alias.GoName)
        }
-
-       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))
+func genStruct(g *GenFile, typ *Struct) {
+       logf("gen STRUCT %s (%s) - %d fields", typ.GoName, typ.Name, len(typ.Fields))
 
-       // generate struct comment
-       generateComment(ctx, w, name, typ.Name, "type")
+       genTypeComment(g, typ.GoName, 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
+       if len(typ.Fields) == 0 {
+               g.P("type ", typ.GoName, " struct {}")
+       } else {
+               g.P("type ", typ.GoName, " struct {")
+               for i := range typ.Fields {
+                       generateField(g, typ.Fields, i)
                }
-
-               generateField(ctx, w, typ.Fields, i)
+               g.P("}")
        }
-
-       // generate end of the struct
-       fmt.Fprintln(w, "}")
-
-       // generate name getter
-       generateTypeNameGetter(w, name, typ.Name)
+       g.P()
 
        // generate type-specific methods
        switch typ.Name {
        case "address":
-               fmt.Fprintln(w)
-               generateIPAddressConversion(w, name)
+               generateAddressConversion(g, typ.GoName)
        case "prefix":
-               fmt.Fprintln(w)
-               generatePrefixConversion(w, name)
+               generatePrefixConversion(g, typ.GoName)
+       case "ip4_prefix":
+               generateIPPrefixConversion(g, typ.GoName, 4)
+       case "ip6_prefix":
+               generateIPPrefixConversion(g, typ.GoName, 6)
        }
-
-       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
+func genUnion(g *GenFile, union *Union) {
+       logf("gen UNION %s (%s) - %d fields", union.GoName, union.Name, len(union.Fields))
 
-       logf(" writing UNION %q (%s) with %d fields", union.Name, name, len(union.Fields))
+       genTypeComment(g, union.GoName, union.Name, "union")
 
-       // generate struct comment
-       generateComment(ctx, w, name, union.Name, "union")
+       g.P("type ", union.GoName, " struct {")
 
-       // generate struct definition
-       fmt.Fprintln(w, "type", name, "struct {")
-
-       // maximum size for union
-       maxSize := getUnionSize(ctx.file, union)
+       for _, field := range union.Fields {
+               g.P("// ", field.GoName, " *", getFieldType(g, field))
+       }
 
        // generate data field
-       fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
+       maxSize := getUnionSize(union)
+       g.P(fieldUnionData, " [", maxSize, "]byte")
 
        // generate end of the struct
-       fmt.Fprintln(w, "}")
+       g.P("}")
+       g.P()
 
-       // generate name getter
-       generateTypeNameGetter(w, name, union.Name)
-
-       // generate getters for fields
+       // generate methods for fields
        for _, field := range union.Fields {
-               fieldType := convertToGoType(ctx.file, field.Type)
-               generateUnionGetterSetter(w, name, field.GoName, fieldType)
+               genUnionFieldMethods(g, union.GoName, field)
        }
-
-       // 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
+       g.P()
 }
-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))
+func genUnionFieldMethods(g *GenFile, structName string, field *Field) {
+       getterStruct := fieldGoType(g, field)
 
-       // generate struct comment
-       generateComment(ctx, w, name, msg.Name, "message")
+       // Constructor
+       g.P("func ", structName, field.GoName, "(a ", getterStruct, ") (u ", structName, ") {")
+       g.P("   u.Set", field.GoName, "(a)")
+       g.P("   return")
+       g.P("}")
 
-       // generate struct definition
-       fmt.Fprintf(w, "type %s struct {", name)
+       // Setter
+       g.P("func (u *", structName, ") Set", field.GoName, "(a ", getterStruct, ") {")
+       g.P("   var buf = ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])")
+       encodeField(g, field, "a", func(name string) string {
+               return "a." + name
+       }, 0)
+       g.P("}")
 
-       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 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)
+       // Getter
+       g.P("func (u *", structName, ") Get", field.GoName, "() (a ", getterStruct, ") {")
+       g.P("   var buf = ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])")
+       decodeField(g, field, "a", func(name string) string {
+               return "a." + name
+       }, 0)
+       g.P("   return")
+       g.P("}")
+       g.P()
 }
 
-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
-               }
+func generateField(g *GenFile, fields []*Field, i int) {
+       field := fields[i]
 
-               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)
-                       }
-               }
+       logf(" gen FIELD[%d] %s (%s) - type: %q (array: %v/%v)", i, field.GoName, field.Name, field.Type, field.Array, field.Length)
 
-               return true
+       gotype := getFieldType(g, field)
+       tags := structTags{
+               "binapi": fieldTagJSON(field),
+               "json":   fieldTagBinapi(field),
        }
 
-       lvl := 0
-       var sizeFields func(fields []*Field, parentName string)
-       sizeFields = func(fields []*Field, parentName string) {
-               lvl++
-               defer func() { lvl-- }()
-
-               n := 0
-               for _, field := range fields {
-                       if field.ParentMessage != nil {
-                               // skip internal fields
-                               switch strings.ToLower(field.Name) {
-                               case 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 {
-                                       sizeFields(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 {
-                               sizeFields(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)
-                       }
+       g.P(field.GoName, " ", gotype, tags)
+}
 
-                       if field.Array {
-                               fmt.Fprintf(w, "\t}\n")
-                       }
-               }
+func fieldTagBinapi(field *Field) string {
+       if field.FieldSizeOf != nil {
+               return "-"
        }
-
-       sizeFields(fields, "m")
-
-       fmt.Fprintf(w, "return size\n")
-
-       fmt.Fprintf(w, "}\n")
+       return fmt.Sprintf("%s,omitempty", field.Name)
 }
 
-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
+func fieldTagJSON(field *Field) string {
+       typ := fromApiType(field.Type)
+       if field.Array {
+               if field.Length > 0 {
+                       typ = fmt.Sprintf("%s[%d]", typ, field.Length)
+               } else if field.SizeFrom != "" {
+                       typ = fmt.Sprintf("%s[%s]", typ, field.SizeFrom)
+               } else {
+                       typ = fmt.Sprintf("%s[]", typ)
                }
-               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 {
-                       if field.ParentMessage != nil {
-                               // skip internal fields
-                               switch strings.ToLower(field.Name) {
-                               case 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")
-                       }
-               }
+       tag := []string{
+               typ,
+               fmt.Sprintf("name=%s", field.Name),
        }
-
-       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
+       if limit, ok := field.Meta["limit"]; ok && limit.(int) > 0 {
+               tag = append(tag, fmt.Sprintf("limit=%s", limit))
        }
-
-       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 {
-                       if field.ParentMessage != nil {
-                               // skip internal fields
-                               switch strings.ToLower(field.Name) {
-                               case 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")
+       if def, ok := field.Meta["default"]; ok && def != nil {
+               actual := fieldActualType(field)
+               if t, ok := BaseTypesGo[actual]; ok {
+                       switch t {
+                       case I8, I16, I32, I64:
+                               def = int(def.(float64))
+                       case U8, U16, U32, U64:
+                               def = uint(def.(float64))
+                       case F64:
+                               def = def.(float64)
                        }
                }
+               tag = append(tag, fmt.Sprintf("default=%s", def))
        }
-
-       decodeFields(fields, "m")
-
-       fmt.Fprintf(w, "return nil\n")
-
-       fmt.Fprintf(w, "}\n")
+       return strings.Join(tag, ",")
 }
 
-func getFieldType(ctx *GenFile, field *Field) string {
-       //fieldName := strings.TrimPrefix(field.Name, "_")
-       //fieldName = camelCaseName(fieldName)
-       //fieldName := field.GoName
+type structTags map[string]string
 
-       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
-               }
+func (tags structTags) String() string {
+       if len(tags) == 0 {
+               return ""
        }
-
-       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)
+       var keys []string
+       for k := range tags {
+               keys = append(keys, k)
        }
-
-       // 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
-               }
+       sort.Strings(keys)
+       var ss []string
+       for _, key := range keys {
+               tag := tags[key]
+               ss = append(ss, fmt.Sprintf(`%s:%s`, key, strconv.Quote(tag)))
        }
-       fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
-
-       fieldTags := map[string]string{}
+       return "`" + strings.Join(ss, " ") + "`"
+}
 
-       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)
-                       }
-               }
+func genMessages(g *GenFile) {
+       if len(g.file.Messages) == 0 {
+               return
        }
 
-       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)
-               }
+       for _, msg := range g.file.Messages {
+               genMessage(g, msg)
        }
 
-       fieldTags["json"] = fmt.Sprintf("%s,omitempty", field.Name)
+       // generate registrations
+       initFnName := fmt.Sprintf("file_%s_binapi_init", g.file.PackageName)
 
-       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, "`")
+       g.P("func init() { ", initFnName, "() }")
+       g.P("func ", initFnName, "() {")
+       for _, msg := range g.file.Messages {
+               id := fmt.Sprintf("%s_%s", msg.Name, msg.CRC)
+               g.P(govppApiPkg.Ident("RegisterMessage"), "((*", msg.GoIdent, ")(nil), ", strconv.Quote(id), ")")
        }
+       g.P("}")
+       g.P()
 
-       fmt.Fprintln(w)
+       // generate list of messages
+       g.P("// Messages returns list of all messages in this module.")
+       g.P("func AllMessages() []", govppApiPkg.Ident("Message"), " {")
+       g.P("return []", govppApiPkg.Ident("Message"), "{")
+       for _, msg := range g.file.Messages {
+               g.P("(*", msg.GoIdent, ")(nil),")
+       }
+       g.P("}")
+       g.P("}")
 }
 
-func generateMessageResetMethod(w io.Writer, structName string) {
-       fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
-}
+func genMessage(g *GenFile, msg *Message) {
+       logf("gen MESSAGE %s (%s) - %d fields", msg.GoName, msg.Name, len(msg.Fields))
 
-func generateMessageNameGetter(w io.Writer, structName, msgName string) {
-       fmt.Fprintf(w, "func (*%s) GetMessageName() string {    return %q }\n", structName, msgName)
-}
+       genTypeComment(g, msg.GoIdent.GoName, msg.Name, "message")
 
-func generateTypeNameGetter(w io.Writer, structName, msgName string) {
-       fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName)
-}
-
-func generateIPAddressConversion(w io.Writer, structName string) {
-       f1 := func(ipVer, ipVerExt int) string {
-               return fmt.Sprintf(`address.Af = ADDRESS_IP%[1]d
-               var ip%[1]daddr IP%[1]dAddress
-               copy(ip%[1]daddr[:], netIP.To%[2]d())
-               address.Un.SetIP%[1]d(ip%[1]daddr)`, ipVer, ipVerExt)
-       }
-       f2 := func(ipVer, ipVerExt int) string {
-               return fmt.Sprintf("ip%[1]dAddress := a.Un.GetIP%[1]d()\nip = net.IP(ip%[1]dAddress[:]).To%[2]d().String()",
-                       ipVer, ipVerExt)
-       }
-       // IP to Address
-       fmt.Fprintf(w, `func ParseAddress(ip string) (%[1]s, error) {
-       var address %[1]s
-       netIP := net.ParseIP(ip)
-       if netIP == nil {
-               return address, fmt.Errorf("invalid address: %[2]s", ip)
-       }
-       if ip4 := netIP.To4(); ip4 == nil {
-               %[3]s
+       // generate message definition
+       if len(msg.Fields) == 0 {
+               g.P("type ", msg.GoIdent, " struct {}")
        } else {
-               %[4]s
-       }
-       return address, nil
-}
-`, structName, "%s", f1(6, 16), f1(4, 4))
-       fmt.Fprintln(w)
-
-       // Address to IP
-       fmt.Fprintln(w)
-       fmt.Fprintf(w, `func (a *%[1]s) ToString() string {
-       var ip string
-       if a.Af == ADDRESS_IP6 {
-               %[2]s
-       } else {
-               %[3]s
+               g.P("type ", msg.GoIdent, " struct {")
+               for i := range msg.Fields {
+                       generateField(g, msg.Fields, i)
+               }
+               g.P("}")
        }
-       return ip
-}`, structName, f2(6, 16), f2(4, 4))
-}
+       g.P()
 
-func generatePrefixConversion(w io.Writer, structName string) {
-       fErr := func() string {
-               return fmt.Sprintf(`if err != nil {
-                       return Prefix{}, fmt.Errorf("invalid IP %s: %s", ip, err)
-               }`, "%s", "%v")
-       }
+       generateMessageMethods(g, msg)
 
-       // IP to Prefix
-       fmt.Fprintf(w, `func ParsePrefix(ip string) (prefix %[1]s, err error) {
-       hasPrefix := strings.Contains(ip, "/")
-       if hasPrefix {
-               netIP, network, err := net.ParseCIDR(ip)
-               %[2]s
-       maskSize, _ := network.Mask.Size()
-       prefix.Len = byte(maskSize)
-       prefix.Address, err = ParseAddress(netIP.String())
-               %[2]s
-       } else {
-               netIP := net.ParseIP(ip)
-               defaultMaskSize, _ := net.CIDRMask(32, 32).Size()
-               if netIP.To4() == nil {
-                       defaultMaskSize, _ = net.CIDRMask(128, 128).Size()
-               }
-               prefix.Len = byte(defaultMaskSize)
-               prefix.Address, err = ParseAddress(netIP.String())
-               %[2]s
-       }
-       return prefix, nil
-}`, structName, fErr(), nil)
-       fmt.Fprintln(w)
-
-       // Prefix to IP
-       fmt.Fprintln(w)
-       fmt.Fprintf(w, `func (p *%[1]s) ToString() string {
-               ip := p.Address.ToString()
-               return ip + "/" + strconv.Itoa(int(p.Len))
-       }`, structName)
-}
+       // encoding methods
+       generateMessageSize(g, msg.GoIdent.GoName, msg.Fields)
+       generateMessageMarshal(g, msg.GoIdent.GoName, msg.Fields)
+       generateMessageUnmarshal(g, msg.GoIdent.GoName, msg.Fields)
 
-func generateMacAddressConversion(w io.Writer, structName string) {
-       // string to MAC
-       fmt.Fprintf(w, `func ParseMAC(mac string) (parsed %[1]s, err error) {
-       var hw net.HardwareAddr
-       if hw, err = net.ParseMAC(mac); err != nil {
-               return
-       }
-       copy(parsed[:], hw[:])
-       return
-}`, structName)
-       fmt.Fprintln(w)
-
-       // MAC to string
-       fmt.Fprintln(w)
-       fmt.Fprintf(w, `func (m *%[1]s) ToString() string {
-               return net.HardwareAddr(m[:]).String()
-       }`, structName)
+       g.P()
 }
 
-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 generateMessageMethods(g *GenFile, msg *Message) {
+       // Reset method
+       g.P("func (m *", msg.GoIdent.GoName, ") Reset() { *m = ", msg.GoIdent.GoName, "{} }")
 
-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)
-}
+       // GetMessageName method
+       g.P("func (*", msg.GoIdent.GoName, ") GetMessageName() string { return ", strconv.Quote(msg.Name), " }")
+
+       // GetCrcString method
+       g.P("func (*", msg.GoIdent.GoName, ") GetCrcString() string { return ", strconv.Quote(msg.CRC), " }")
+
+       // GetMessageType method
+       g.P("func (*", msg.GoIdent.GoName, ") GetMessageType() api.MessageType {")
+       g.P("   return ", apiMsgType(msg.msgType))
+       g.P("}")
 
-func logf(f string, v ...interface{}) {
-       logrus.Debugf(f, v...)
+       g.P()
 }