package binapigen
import (
- "bytes"
"fmt"
"io"
- "os/exec"
- "path"
- "path/filepath"
"sort"
"strings"
"git.fd.io/govpp.git/version"
+ "github.com/sirupsen/logrus"
)
// generatedCodeVersion indicates a version of the generated code.
// a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
const generatedCodeVersion = 2
-// message field names
+// common message fields
const (
msgIdField = "_vl_msg_id"
clientIndexField = "client_index"
retvalField = "retval"
)
+// global API info
const (
- outputFileExt = ".ba.go" // file extension of the Go generated files
- rpcFileSuffix = "_rpc" // file name suffix for the RPC services
-
constModuleName = "ModuleName" // module name constant
constAPIVersion = "APIVersion" // API version constant
constVersionCrc = "VersionCrc" // version CRC constant
+)
+// generated fiels
+const (
unionDataField = "XXX_UnionData" // name for the union data field
-
- serviceApiName = "RPCService" // name for the RPC service interface
- serviceImplName = "serviceClient" // name for the RPC service implementation
- serviceClientName = "ServiceClient" // name for the RPC service client
-
- // TODO: register service descriptor
- //serviceDescType = "ServiceDesc" // name for service descriptor type
- //serviceDescName = "_ServiceRPC_serviceDesc" // name for service descriptor var
)
// MessageType represents the type of a VPP message
otherMessage // other VPP message
)
-type GenFile struct {
- *Generator
- filename string
- file *File
- packageDir string
- buf bytes.Buffer
-}
-
-func generatePackage(ctx *GenFile, w io.Writer) {
+func generateFileBinapi(ctx *GenFile, w io.Writer) {
logf("----------------------------")
- logf("generating binapi package: %q", ctx.file.PackageName)
+ logf("generating BINAPI file package: %q", ctx.file.PackageName)
logf("----------------------------")
- generateHeader(ctx, w)
+ // 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)
+ }
+ fmt.Fprintf(w, "// source: %s\n", ctx.file.Path)
+ fmt.Fprintln(w)
+
+ generatePackageHeader(ctx, w)
generateImports(ctx, w)
+ generateApiInfo(ctx, w)
+ generateTypes(ctx, w)
+ generateMessages(ctx, w)
+
+ generateImportRefs(ctx, w)
+}
+
+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)
+ }
+ }
+ 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)
+ }
+ }
+ 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.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 {
- generateImportedAlias(ctx, w, enum.GoName, imp)
+ if strings.HasSuffix(ctx.file.Name, "_types") {
+ generateImportedAlias(ctx, w, enum.GoName, imp)
+ }
continue
}
generateEnum(ctx, w, enum)
if len(ctx.file.Aliases) > 0 {
for _, alias := range ctx.file.Aliases {
if imp, ok := ctx.file.imports[alias.Name]; ok {
- generateImportedAlias(ctx, w, alias.GoName, imp)
+ if strings.HasSuffix(ctx.file.Name, "_types") {
+ generateImportedAlias(ctx, w, alias.GoName, imp)
+ }
continue
}
generateAlias(ctx, w, alias)
if len(ctx.file.Structs) > 0 {
for _, typ := range ctx.file.Structs {
if imp, ok := ctx.file.imports[typ.Name]; ok {
- generateImportedAlias(ctx, w, typ.GoName, imp)
+ if strings.HasSuffix(ctx.file.Name, "_types") {
+ generateImportedAlias(ctx, w, typ.GoName, imp)
+ }
continue
}
generateStruct(ctx, w, typ)
if len(ctx.file.Unions) > 0 {
for _, union := range ctx.file.Unions {
if imp, ok := ctx.file.imports[union.Name]; ok {
- generateImportedAlias(ctx, w, union.GoName, imp)
+ if strings.HasSuffix(ctx.file.Name, "_types") {
+ generateImportedAlias(ctx, w, union.GoName, imp)
+ }
continue
}
generateUnion(ctx, w, union)
}
}
-
- // generate messages
- if len(ctx.file.Messages) > 0 {
- for _, msg := range ctx.file.Messages {
- generateMessage(ctx, w, msg)
- }
-
- initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName)
-
- // generate message registrations
- fmt.Fprintf(w, "func init() { %s() }\n", initFnName)
- fmt.Fprintf(w, "func %s() {\n", initFnName)
- for _, msg := range ctx.file.Messages {
- fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n",
- msg.GoName, ctx.file.Name+"."+msg.GoName)
- }
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
-
- // generate list of messages
- fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
- fmt.Fprintln(w, "func AllMessages() []api.Message {")
- fmt.Fprintln(w, "\treturn []api.Message{")
- for _, msg := range ctx.file.Messages {
- fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName)
- }
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w, "}")
- }
-
- generateFooter(ctx, w)
-
}
-func generateHeader(ctx *GenFile, w io.Writer) {
- fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
- fmt.Fprintln(w, "// versions:")
- fmt.Fprintf(w, "// binapi-generator: %s\n", version.Version())
- if ctx.IncludeVppVersion {
- fmt.Fprintf(w, "// VPP: %s\n", ctx.VPPVersion)
+func generateMessages(ctx *GenFile, w io.Writer) {
+ if len(ctx.file.Messages) == 0 {
+ return
}
- fmt.Fprintf(w, "// source: %s\n", ctx.file.Path)
- fmt.Fprintln(w)
- fmt.Fprintln(w, "/*")
- fmt.Fprintf(w, "Package %s contains generated code for VPP binary API defined by %s.api (version %s).\n",
- ctx.file.PackageName, ctx.file.Name, ctx.file.Version())
- fmt.Fprintln(w)
- fmt.Fprintln(w, "It consists of:")
- printObjNum := func(obj string, num int) {
- if num > 0 {
- if num > 1 {
- if strings.HasSuffix(obj, "s") {
-
- obj += "es"
- } else {
- obj += "s"
- }
- }
- fmt.Fprintf(w, "\t%3d %s\n", num, obj)
- }
+ for _, msg := range ctx.file.Messages {
+ generateMessage(ctx, w, msg)
}
- //printObjNum("RPC", len(ctx.file.Service.RPCs))
- printObjNum("alias", len(ctx.file.Aliases))
- printObjNum("enum", len(ctx.file.Enums))
- printObjNum("message", len(ctx.file.Messages))
- printObjNum("type", len(ctx.file.Structs))
- printObjNum("union", len(ctx.file.Unions))
- fmt.Fprintln(w, "*/")
- fmt.Fprintf(w, "package %s\n", ctx.file.PackageName)
- fmt.Fprintln(w)
-}
-func generateImports(ctx *GenFile, w io.Writer) {
- fmt.Fprintln(w, "import (")
- fmt.Fprintln(w, ` "bytes"`)
- fmt.Fprintln(w, ` "context"`)
- fmt.Fprintln(w, ` "encoding/binary"`)
- fmt.Fprintln(w, ` "io"`)
- fmt.Fprintln(w, ` "math"`)
- fmt.Fprintln(w, ` "strconv"`)
- fmt.Fprintln(w)
- fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
- fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec")
- fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
- if len(ctx.file.Imports) > 0 {
- fmt.Fprintln(w)
- for _, imp := range ctx.file.Imports {
- importPath := path.Join(ctx.ImportPrefix, imp)
- if ctx.ImportPrefix == "" {
- importPath = getImportPath(ctx.packageDir, imp)
- }
- fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
- }
- }
- fmt.Fprintln(w, ")")
- fmt.Fprintln(w)
+ // generate message registrations
+ initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName)
- 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.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)
-}
-func getImportPath(outputDir string, pkg string) string {
- absPath, err := filepath.Abs(filepath.Join(outputDir, "..", pkg))
- if err != nil {
- panic(err)
- }
- cmd := exec.Command("go", "list", absPath)
- var errbuf, outbuf bytes.Buffer
- cmd.Stdout = &outbuf
- cmd.Stderr = &errbuf
- if err := cmd.Run(); err != nil {
- fmt.Printf("ERR: %v\n", errbuf.String())
- panic(err)
+ // 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)
}
- return outbuf.String()
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w, "}")
}
-func generateFooter(ctx *GenFile, w io.Writer) {
+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 _ = 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 generateComment(ctx *GenFile, w io.Writer, goName string, vppName string, objKind string) {
dataType := convertToGoType(ctx.file, alias.Type)
fmt.Fprintf(w, "%s\n", dataType)
+ // generate alias-specific methods
+ switch alias.Name {
+ case "mac_address":
+ fmt.Fprintln(w)
+ generateMacAddressConversion(w, name)
+ }
+
fmt.Fprintln(w)
}
// generate name getter
generateTypeNameGetter(w, name, typ.Name)
+ // generate type-specific methods
+ switch typ.Name {
+ case "address":
+ fmt.Fprintln(w)
+ generateIPAddressConversion(w, name)
+ case "prefix":
+ fmt.Fprintln(w)
+ generatePrefixConversion(w, name)
+ }
+
fmt.Fprintln(w)
}
// skip internal fields
switch strings.ToLower(field.Name) {
- case /*crcField,*/ msgIdField:
+ case msgIdField:
continue
case clientIndexField, contextField:
if n == 0 {
}
lvl := 0
- var encodeFields func(fields []*Field, parentName string)
- encodeFields = func(fields []*Field, parentName string) {
+ var sizeFields func(fields []*Field, parentName string)
+ sizeFields = func(fields []*Field, parentName string) {
lvl++
defer func() { lvl-- }()
n := 0
for _, field := range fields {
- // skip internal fields
- switch strings.ToLower(field.Name) {
- case /*crcField,*/ msgIdField:
- continue
- case clientIndexField, contextField:
- if n == 0 {
+ if field.ParentMessage != nil {
+ // skip internal fields
+ switch strings.ToLower(field.Name) {
+ case msgIdField:
continue
+ case clientIndexField, contextField:
+ if n == 0 {
+ continue
+ }
}
}
n++
} 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)
+ 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 {
- encodeFields(typ.Fields, name)
+ 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)
}
}
- encodeFields(fields, "m")
+ sizeFields(fields, "m")
fmt.Fprintf(w, "return size\n")
n := 0
for _, field := range fields {
- // skip internal fields
- switch strings.ToLower(field.Name) {
- case /*crcField,*/ msgIdField:
- continue
- case clientIndexField, contextField:
- if n == 0 {
+ if field.ParentMessage != nil {
+ // skip internal fields
+ switch strings.ToLower(field.Name) {
+ case msgIdField:
continue
+ case clientIndexField, contextField:
+ if n == 0 {
+ continue
+ }
}
}
n++
n := 0
for _, field := range fields {
- // skip internal fields
- switch strings.ToLower(field.Name) {
- case /*crcField,*/ msgIdField:
- continue
- case clientIndexField, contextField:
- if n == 0 {
+ if field.ParentMessage != nil {
+ // skip internal fields
+ switch strings.ToLower(field.Name) {
+ case msgIdField:
continue
+ case clientIndexField, contextField:
+ if n == 0 {
+ continue
+ }
}
}
n++
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
+ } 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
+ }
+ return ip
+}`, structName, f2(6, 16), f2(4, 4))
+}
+
+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")
+ }
+
+ // 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)
+}
+
+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)
+}
+
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)
fmt.Fprintln(w, "}")
fmt.Fprintln(w)
}
+
+func logf(f string, v ...interface{}) {
+ logrus.Debugf(f, v...)
+}