summaryrefslogtreecommitdiffstats
path: root/binapigen
diff options
context:
space:
mode:
authorOndrej Fabry <ofabry@cisco.com>2020-07-17 10:36:28 +0200
committerOndrej Fabry <ofabry@cisco.com>2020-07-17 11:43:41 +0200
commitd1f24d37bd447b64e402298bb8eb2479681facf9 (patch)
treea3fc21ba730a91d8a402c7a5bf9c614e3677c4fc /binapigen
parent1548c7e12531e3d055567d761c580a1c7ff0ac40 (diff)
Improve binapi generator
- simplified Size/Marshal/Unmarshal methods - replace struc in unions with custom marshal/unmarshal - fix imports in generated files - fix mock adapter - generate rpc service using low-level stream API (dumps generate control ping or stream msg..) - move examples/binapi to binapi and generate all API for latest release - add binapigen.Plugin for developing custom generator plugins - optionally generate HTTP handlers (REST API) for RPC services - add govpp program for browsing VPP API Change-Id: I092e9ed2b0c17972b3476463c3d4b14dd76ed42b Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
Diffstat (limited to 'binapigen')
-rw-r--r--binapigen/binapigen.go572
-rw-r--r--binapigen/binapigen_test.go55
-rw-r--r--binapigen/gen_encoding.go375
-rw-r--r--binapigen/gen_helpers.go348
-rw-r--r--binapigen/gen_helpers_test.go156
-rw-r--r--binapigen/gen_rest.go103
-rw-r--r--binapigen/gen_rpc.go204
-rw-r--r--binapigen/generate.go1518
-rw-r--r--binapigen/generate_rpc.go203
-rw-r--r--binapigen/generate_test.go175
-rw-r--r--binapigen/generator.go425
-rw-r--r--binapigen/generator_test.go76
-rw-r--r--binapigen/plugin.go51
-rw-r--r--binapigen/run.go132
-rw-r--r--binapigen/types.go267
-rw-r--r--binapigen/validate.go67
-rw-r--r--binapigen/vppapi.go211
-rw-r--r--binapigen/vppapi/api.go94
-rw-r--r--binapigen/vppapi/api_schema.go89
-rw-r--r--binapigen/vppapi/parse_json.go210
-rw-r--r--binapigen/vppapi/util.go112
-rw-r--r--binapigen/vppapi/vppapi.go (renamed from binapigen/vppapi/parser.go)49
-rw-r--r--binapigen/vppapi/vppapi_test.go (renamed from binapigen/vppapi/parser_test.go)22
23 files changed, 3014 insertions, 2500 deletions
diff --git a/binapigen/binapigen.go b/binapigen/binapigen.go
index c5a976b..1b4c7e5 100644
--- a/binapigen/binapigen.go
+++ b/binapigen/binapigen.go
@@ -17,85 +17,123 @@ package binapigen
import (
"fmt"
"path"
- "sort"
"strings"
"git.fd.io/govpp.git/binapigen/vppapi"
)
+// 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
+
+// file options
+const (
+ optFileVersion = "version"
+)
+
type File struct {
- vppapi.File
+ Desc vppapi.File
- Generate bool
+ Generate bool
+ FilenamePrefix string
+ PackageName GoPackageName
+ GoImportPath GoImportPath
- PackageName string
- Imports []string
+ Version string
+ Imports []string
- Enums []*Enum
- Unions []*Union
- Structs []*Struct
- Aliases []*Alias
- Messages []*Message
+ Enums []*Enum
+ Unions []*Union
+ Structs []*Struct
+ Aliases []*Alias
- imports map[string]string
- refmap map[string]string
+ Messages []*Message
+ Service *Service
}
-func newFile(gen *Generator, apifile *vppapi.File) (*File, error) {
+func newFile(gen *Generator, apifile *vppapi.File, packageName GoPackageName, importPath GoImportPath) (*File, error) {
file := &File{
- File: *apifile,
- PackageName: sanitizedName(apifile.Name),
- imports: make(map[string]string),
- refmap: make(map[string]string),
+ Desc: *apifile,
+ PackageName: packageName,
+ GoImportPath: importPath,
+ }
+ if apifile.Options != nil {
+ file.Version = apifile.Options[optFileVersion]
}
- sortFileObjects(&file.File)
+ file.FilenamePrefix = path.Join(gen.opts.OutputDir, file.Desc.Name)
for _, imp := range apifile.Imports {
file.Imports = append(file.Imports, normalizeImport(imp))
}
- for _, enum := range apifile.EnumTypes {
- file.Enums = append(file.Enums, newEnum(gen, file, enum))
+
+ for _, enumType := range apifile.EnumTypes {
+ file.Enums = append(file.Enums, newEnum(gen, file, enumType))
}
- for _, alias := range apifile.AliasTypes {
- file.Aliases = append(file.Aliases, newAlias(gen, file, alias))
+ for _, aliasType := range apifile.AliasTypes {
+ file.Aliases = append(file.Aliases, newAlias(gen, file, aliasType))
}
for _, structType := range apifile.StructTypes {
file.Structs = append(file.Structs, newStruct(gen, file, structType))
}
- for _, union := range apifile.UnionTypes {
- file.Unions = append(file.Unions, newUnion(gen, file, union))
+ for _, unionType := range apifile.UnionTypes {
+ file.Unions = append(file.Unions, newUnion(gen, file, unionType))
}
+
for _, msg := range apifile.Messages {
file.Messages = append(file.Messages, newMessage(gen, file, msg))
}
+ if apifile.Service != nil {
+ file.Service = newService(gen, file, *apifile.Service)
+ }
+
+ for _, t := range file.Aliases {
+ if err := t.resolveDependencies(gen); err != nil {
+ return nil, err
+ }
+ }
+ for _, t := range file.Structs {
+ if err := t.resolveDependencies(gen); err != nil {
+ return nil, err
+ }
+ }
+ for _, t := range file.Unions {
+ if err := t.resolveDependencies(gen); err != nil {
+ return nil, err
+ }
+ }
+ for _, m := range file.Messages {
+ if err := m.resolveDependencies(gen); err != nil {
+ return nil, err
+ }
+ }
+ if file.Service != nil {
+ for _, rpc := range file.Service.RPCs {
+ if err := rpc.resolveMessages(gen); err != nil {
+ return nil, err
+ }
+ }
+ }
return file, nil
}
-func (file *File) isTypes() bool {
- return strings.HasSuffix(file.File.Name, "_types")
+func (file *File) isTypesFile() bool {
+ return strings.HasSuffix(file.Desc.Name, "_types")
}
func (file *File) hasService() bool {
return file.Service != nil && len(file.Service.RPCs) > 0
}
-func (file *File) addRef(typ string, name string, ref interface{}) {
- apiName := toApiType(name)
- if _, ok := file.refmap[apiName]; ok {
- logf("%s type %v already in refmap", typ, apiName)
- return
- }
- file.refmap[apiName] = name
-}
-
func (file *File) importedFiles(gen *Generator) []*File {
var files []*File
for _, imp := range file.Imports {
impFile, ok := gen.FilesByName[imp]
if !ok {
- logf("file %s import %s not found API files", file.Name, imp)
+ logf("file %s import %s not found API files", file.Desc.Name, imp)
continue
}
files = append(files, impFile)
@@ -103,115 +141,102 @@ func (file *File) importedFiles(gen *Generator) []*File {
return files
}
-func (file *File) loadTypeImports(gen *Generator, typeFiles []*File) {
- if len(typeFiles) == 0 {
- return
- }
- for _, t := range file.Structs {
- for _, imp := range typeFiles {
- if _, ok := file.imports[t.Name]; ok {
- break
- }
- for _, at := range imp.File.StructTypes {
- if at.Name != t.Name {
- continue
- }
- if len(at.Fields) != len(t.Fields) {
- continue
- }
- file.imports[t.Name] = imp.PackageName
- }
- }
- }
- for _, t := range file.AliasTypes {
- for _, imp := range typeFiles {
- if _, ok := file.imports[t.Name]; ok {
- break
- }
- for _, at := range imp.File.AliasTypes {
- if at.Name != t.Name {
- continue
- }
- if at.Length != t.Length {
- continue
- }
- if at.Type != t.Type {
- continue
- }
- file.imports[t.Name] = imp.PackageName
- }
+func (file *File) dependsOnFile(gen *Generator, dep string) bool {
+ for _, imp := range file.Imports {
+ if imp == dep {
+ return true
}
- }
- for _, t := range file.EnumTypes {
- for _, imp := range typeFiles {
- if _, ok := file.imports[t.Name]; ok {
- break
- }
- for _, at := range imp.File.EnumTypes {
- if at.Name != t.Name {
- continue
- }
- if at.Type != t.Type {
- continue
- }
- file.imports[t.Name] = imp.PackageName
- }
+ impFile, ok := gen.FilesByName[imp]
+ if ok && impFile.dependsOnFile(gen, dep) {
+ return true
}
}
- for _, t := range file.UnionTypes {
- for _, imp := range typeFiles {
- if _, ok := file.imports[t.Name]; ok {
- break
- }
- for _, at := range imp.File.UnionTypes {
- if at.Name != t.Name {
- continue
- }
- file.imports[t.Name] = imp.PackageName
- /*if gen.ImportTypes {
- imp.Generate = true
- }*/
- }
- }
+ return false
+}
+
+func normalizeImport(imp string) string {
+ imp = path.Base(imp)
+ if idx := strings.Index(imp, "."); idx >= 0 {
+ imp = imp[:idx]
}
+ return imp
+}
+
+const (
+ enumFlagSuffix = "_flags"
+)
+
+func isEnumFlag(enum *Enum) bool {
+ return strings.HasSuffix(enum.Name, enumFlagSuffix)
}
type Enum struct {
vppapi.EnumType
- GoName string
+ GoIdent
}
func newEnum(gen *Generator, file *File, apitype vppapi.EnumType) *Enum {
typ := &Enum{
EnumType: apitype,
- GoName: camelCaseName(apitype.Name),
+ GoIdent: GoIdent{
+ GoName: camelCaseName(apitype.Name),
+ GoImportPath: file.GoImportPath,
+ },
}
- gen.enumsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
- file.addRef("enum", typ.Name, typ)
+ gen.enumsByName[typ.Name] = typ
return typ
}
type Alias struct {
vppapi.AliasType
- GoName string
+ GoIdent
+
+ TypeBasic *string
+ TypeStruct *Struct
+ TypeUnion *Union
}
func newAlias(gen *Generator, file *File, apitype vppapi.AliasType) *Alias {
typ := &Alias{
AliasType: apitype,
- GoName: camelCaseName(apitype.Name),
+ GoIdent: GoIdent{
+ GoName: camelCaseName(apitype.Name),
+ GoImportPath: file.GoImportPath,
+ },
}
- gen.aliasesByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
- file.addRef("alias", typ.Name, typ)
+ gen.aliasesByName[typ.Name] = typ
return typ
}
+func (a *Alias) resolveDependencies(gen *Generator) error {
+ if err := a.resolveType(gen); err != nil {
+ return fmt.Errorf("unable to resolve field: %w", err)
+ }
+ return nil
+}
+
+func (a *Alias) resolveType(gen *Generator) error {
+ if _, ok := BaseTypesGo[a.Type]; ok {
+ return nil
+ }
+ typ := fromApiType(a.Type)
+ if t, ok := gen.structsByName[typ]; ok {
+ a.TypeStruct = t
+ return nil
+ }
+ if t, ok := gen.unionsByName[typ]; ok {
+ a.TypeUnion = t
+ return nil
+ }
+ return fmt.Errorf("unknown type: %q", a.Type)
+}
+
type Struct struct {
vppapi.StructType
- GoName string
+ GoIdent
Fields []*Field
}
@@ -219,22 +244,33 @@ type Struct struct {
func newStruct(gen *Generator, file *File, apitype vppapi.StructType) *Struct {
typ := &Struct{
StructType: apitype,
- GoName: camelCaseName(apitype.Name),
+ GoIdent: GoIdent{
+ GoName: camelCaseName(apitype.Name),
+ GoImportPath: file.GoImportPath,
+ },
}
+ gen.structsByName[typ.Name] = typ
for _, fieldType := range apitype.Fields {
field := newField(gen, file, fieldType)
field.ParentStruct = typ
typ.Fields = append(typ.Fields, field)
}
- gen.structsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
- file.addRef("struct", typ.Name, typ)
return typ
}
+func (m *Struct) resolveDependencies(gen *Generator) (err error) {
+ for _, field := range m.Fields {
+ if err := field.resolveDependencies(gen); err != nil {
+ return fmt.Errorf("unable to resolve for struct %s: %w", m.Name, err)
+ }
+ }
+ return nil
+}
+
type Union struct {
vppapi.UnionType
- GoName string
+ GoIdent
Fields []*Field
}
@@ -242,32 +278,96 @@ type Union struct {
func newUnion(gen *Generator, file *File, apitype vppapi.UnionType) *Union {
typ := &Union{
UnionType: apitype,
- GoName: camelCaseName(apitype.Name),
+ GoIdent: GoIdent{
+ GoName: camelCaseName(apitype.Name),
+ GoImportPath: file.GoImportPath,
+ },
}
- gen.unionsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
+ gen.unionsByName[typ.Name] = typ
for _, fieldType := range apitype.Fields {
field := newField(gen, file, fieldType)
field.ParentUnion = typ
typ.Fields = append(typ.Fields, field)
}
- file.addRef("union", typ.Name, typ)
return typ
}
+func (m *Union) resolveDependencies(gen *Generator) (err error) {
+ for _, field := range m.Fields {
+ if err := field.resolveDependencies(gen); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// msgType determines message header fields
+type msgType int
+
+const (
+ msgTypeBase msgType = iota // msg_id
+ msgTypeRequest // msg_id, client_index, context
+ msgTypeReply // msg_id, context
+ msgTypeEvent // msg_id, client_index
+)
+
+func apiMsgType(t msgType) GoIdent {
+ switch t {
+ case msgTypeRequest:
+ return govppApiPkg.Ident("RequestMessage")
+ case msgTypeReply:
+ return govppApiPkg.Ident("ReplyMessage")
+ case msgTypeEvent:
+ return govppApiPkg.Ident("EventMessage")
+ default:
+ return govppApiPkg.Ident("OtherMessage")
+ }
+}
+
+// message fields
+const (
+ fieldMsgID = "_vl_msg_id"
+ fieldClientIndex = "client_index"
+ fieldContext = "context"
+ fieldRetval = "retval"
+)
+
+// field options
+const (
+ optFieldDefault = "default"
+)
+
type Message struct {
vppapi.Message
- GoName string
+ CRC string
+
+ GoIdent
Fields []*Field
+
+ msgType msgType
}
func newMessage(gen *Generator, file *File, apitype vppapi.Message) *Message {
msg := &Message{
Message: apitype,
- GoName: camelCaseName(apitype.Name),
+ CRC: strings.TrimPrefix(apitype.CRC, "0x"),
+ GoIdent: newGoIdent(file, apitype.Name),
}
+ gen.messagesByName[apitype.Name] = msg
+ n := 0
for _, fieldType := range apitype.Fields {
+ // skip internal fields
+ switch strings.ToLower(fieldType.Name) {
+ case fieldMsgID:
+ continue
+ case fieldClientIndex, fieldContext:
+ if n == 0 {
+ continue
+ }
+ }
+ n++
field := newField(gen, file, fieldType)
field.ParentMessage = msg
msg.Fields = append(msg.Fields, field)
@@ -275,21 +375,71 @@ func newMessage(gen *Generator, file *File, apitype vppapi.Message) *Message {
return msg
}
+func (m *Message) resolveDependencies(gen *Generator) (err error) {
+ if m.msgType, err = getMsgType(m.Message); err != nil {
+ return err
+ }
+ for _, field := range m.Fields {
+ if err := field.resolveDependencies(gen); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func getMsgType(m vppapi.Message) (msgType, error) {
+ if len(m.Fields) == 0 {
+ return msgType(0), fmt.Errorf("message %s has no fields", m.Name)
+ }
+ typ := msgTypeBase
+ wasClientIndex := false
+ for i, field := range m.Fields {
+ if i == 0 {
+ if field.Name != fieldMsgID {
+ return msgType(0), fmt.Errorf("message %s is missing ID field", m.Name)
+ }
+ } else if i == 1 {
+ if field.Name == fieldClientIndex {
+ // "client_index" as the second member,
+ // this might be an event message or a request
+ typ = msgTypeEvent
+ wasClientIndex = true
+ } else if field.Name == fieldContext {
+ // reply needs "context" as the second member
+ typ = msgTypeReply
+ }
+ } else if i == 2 {
+ if wasClientIndex && field.Name == fieldContext {
+ // request needs "client_index" as the second member
+ // and "context" as the third member
+ typ = msgTypeRequest
+ }
+ }
+ }
+ return typ, nil
+}
+
type Field struct {
vppapi.Field
GoName string
- // Field parent
+ DefaultValue interface{}
+
+ // Reference to actual type of this field
+ TypeEnum *Enum
+ TypeAlias *Alias
+ TypeStruct *Struct
+ TypeUnion *Union
+
+ // Parent in which this field is declared
ParentMessage *Message
ParentStruct *Struct
ParentUnion *Union
- // Type reference
- Enum *Enum
- Alias *Alias
- Struct *Struct
- Union *Union
+ // Field reference for fields determining size
+ FieldSizeOf *Field
+ FieldSizeFrom *Field
}
func newField(gen *Generator, file *File, apitype vppapi.Field) *Field {
@@ -297,64 +447,134 @@ func newField(gen *Generator, file *File, apitype vppapi.Field) *Field {
Field: apitype,
GoName: camelCaseName(apitype.Name),
}
+ if apitype.Meta != nil {
+ if val, ok := apitype.Meta[optFieldDefault]; ok {
+ typ.DefaultValue = val
+ }
+ }
return typ
}
-type Service = vppapi.Service
-type RPC = vppapi.RPC
-
-func sortFileObjects(file *vppapi.File) {
- // sort imports
- sort.SliceStable(file.Imports, func(i, j int) bool {
- return file.Imports[i] < file.Imports[j]
- })
- // sort enum types
- sort.SliceStable(file.EnumTypes, func(i, j int) bool {
- return file.EnumTypes[i].Name < file.EnumTypes[j].Name
- })
- // sort alias types
- sort.Slice(file.AliasTypes, func(i, j int) bool {
- return file.AliasTypes[i].Name < file.AliasTypes[j].Name
- })
- // sort struct types
- sort.SliceStable(file.StructTypes, func(i, j int) bool {
- return file.StructTypes[i].Name < file.StructTypes[j].Name
- })
- // sort union types
- sort.SliceStable(file.UnionTypes, func(i, j int) bool {
- return file.UnionTypes[i].Name < file.UnionTypes[j].Name
- })
- // sort messages
- sort.SliceStable(file.Messages, func(i, j int) bool {
- return file.Messages[i].Name < file.Messages[j].Name
- })
- // sort services
- if file.Service != nil {
- sort.Slice(file.Service.RPCs, func(i, j int) bool {
- // dumps first
- if file.Service.RPCs[i].Stream != file.Service.RPCs[j].Stream {
- return file.Service.RPCs[i].Stream
+func (f *Field) resolveDependencies(gen *Generator) error {
+ if err := f.resolveType(gen); err != nil {
+ return fmt.Errorf("unable to resolve field type: %w", err)
+ }
+ if err := f.resolveFields(gen); err != nil {
+ return fmt.Errorf("unable to resolve fields: %w", err)
+ }
+ return nil
+}
+
+func (f *Field) resolveType(gen *Generator) error {
+ if _, ok := BaseTypesGo[f.Type]; ok {
+ return nil
+ }
+ typ := fromApiType(f.Type)
+ if t, ok := gen.structsByName[typ]; ok {
+ f.TypeStruct = t
+ return nil
+ }
+ if t, ok := gen.enumsByName[typ]; ok {
+ f.TypeEnum = t
+ return nil
+ }
+ if t, ok := gen.aliasesByName[typ]; ok {
+ f.TypeAlias = t
+ return nil
+ }
+ if t, ok := gen.unionsByName[typ]; ok {
+ f.TypeUnion = t
+ return nil
+ }
+ return fmt.Errorf("unknown type: %q", f.Type)
+}
+
+func (f *Field) resolveFields(gen *Generator) error {
+ var fields []*Field
+ if f.ParentMessage != nil {
+ fields = f.ParentMessage.Fields
+ } else if f.ParentStruct != nil {
+ fields = f.ParentStruct.Fields
+ }
+ if f.SizeFrom != "" {
+ for _, field := range fields {
+ if field.Name == f.SizeFrom {
+ f.FieldSizeFrom = field
+ break
+ }
+ }
+ } else {
+ for _, field := range fields {
+ if field.SizeFrom == f.Name {
+ f.FieldSizeOf = field
+ break
}
- return file.Service.RPCs[i].RequestMsg < file.Service.RPCs[j].RequestMsg
- })
+ }
}
+ return nil
}
-func sanitizedName(name string) string {
- switch name {
- case "interface":
- return "interfaces"
- case "map":
- return "maps"
- default:
- return name
+type Service struct {
+ vppapi.Service
+
+ RPCs []*RPC
+}
+
+func newService(gen *Generator, file *File, apitype vppapi.Service) *Service {
+ svc := &Service{
+ Service: apitype,
}
+ for _, rpc := range apitype.RPCs {
+ svc.RPCs = append(svc.RPCs, newRpc(file, svc, rpc))
+ }
+ return svc
}
-func normalizeImport(imp string) string {
- imp = path.Base(imp)
- if idx := strings.Index(imp, "."); idx >= 0 {
- imp = imp[:idx]
+const (
+ serviceNoReply = "null"
+)
+
+type RPC struct {
+ VPP vppapi.RPC
+
+ GoName string
+
+ Service *Service
+
+ MsgRequest *Message
+ MsgReply *Message
+ MsgStream *Message
+}
+
+func newRpc(file *File, service *Service, apitype vppapi.RPC) *RPC {
+ rpc := &RPC{
+ VPP: apitype,
+ GoName: camelCaseName(apitype.Request),
+ Service: service,
}
- return imp
+ return rpc
+}
+
+func (rpc *RPC) resolveMessages(gen *Generator) error {
+ msg, ok := gen.messagesByName[rpc.VPP.Request]
+ if !ok {
+ return fmt.Errorf("rpc %v: no message for request type %v", rpc.GoName, rpc.VPP.Request)
+ }
+ rpc.MsgRequest = msg
+
+ if rpc.VPP.Reply != "" && rpc.VPP.Reply != serviceNoReply {
+ msg, ok := gen.messagesByName[rpc.VPP.Reply]
+ if !ok {
+ return fmt.Errorf("rpc %v: no message for reply type %v", rpc.GoName, rpc.VPP.Reply)
+ }
+ rpc.MsgReply = msg
+ }
+ if rpc.VPP.StreamMsg != "" {
+ msg, ok := gen.messagesByName[rpc.VPP.StreamMsg]
+ if !ok {
+ return fmt.Errorf("rpc %v: no message for stream type %v", rpc.GoName, rpc.VPP.StreamMsg)
+ }
+ rpc.MsgStream = msg
+ }
+ return nil
}
diff --git a/binapigen/binapigen_test.go b/binapigen/binapigen_test.go
new file mode 100644
index 0000000..2fbd163
--- /dev/null
+++ b/binapigen/binapigen_test.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+import (
+ "testing"
+
+ . "github.com/onsi/gomega"
+
+ "git.fd.io/govpp.git/binapigen/vppapi"
+)
+
+func TestGenerator(t *testing.T) {
+ tests := []struct {
+ name string
+ file *vppapi.File
+ expectPackage string
+ }{
+ {name: "vpe", file: &vppapi.File{
+ Name: "vpe",
+ Path: "/usr/share/vpp/api/core/vpe.api.json",
+ CRC: "0x12345678",
+ },
+ expectPackage: "vpe",
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ RegisterTestingT(t)
+
+ apifiles := []*vppapi.File{test.file}
+
+ gen, err := New(Options{
+ ImportPrefix: "test",
+ }, apifiles, nil)
+ Expect(err).ToNot(HaveOccurred(), "unexpected generator error: %v", err)
+
+ Expect(gen.Files).To(HaveLen(1))
+ Expect(gen.Files[0].PackageName).To(BeEquivalentTo(test.expectPackage))
+ Expect(gen.Files[0].GoImportPath).To(BeEquivalentTo("test/" + test.expectPackage))
+ })
+ }
+}
diff --git a/binapigen/gen_encoding.go b/binapigen/gen_encoding.go
new file mode 100644
index 0000000..1cd3eb3
--- /dev/null
+++ b/binapigen/gen_encoding.go
@@ -0,0 +1,375 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ //RegisterPlugin("encoding", GenerateEncoding)
+}
+
+func generateMessageSize(g *GenFile, name string, fields []*Field) {
+ g.P("func (m *", name, ") Size() int {")
+ g.P("if m == nil { return 0 }")
+ g.P("var size int")
+
+ sizeBaseType := func(typ, name string, length int, sizefrom string) {
+ switch typ {
+ case STRING:
+ if length > 0 {
+ g.P("size += ", length, " // ", name)
+ } else {
+ g.P("size += 4 + len(", name, ")", " // ", name)
+ }
+ default:
+ var size = BaseTypeSizes[typ]
+ if sizefrom != "" {
+ g.P("size += ", size, " * len(", name, ")", " // ", name)
+ } else {
+ if length > 0 {
+ g.P("size += ", size, " * ", length, " // ", name)
+ } else {
+ g.P("size += ", size, " // ", name)
+ }
+ }
+ }
+ }
+
+ lvl := 0
+ var sizeFields func(fields []*Field, parentName string)
+ sizeFields = func(fields []*Field, parentName string) {
+ lvl++
+ defer func() { lvl-- }()
+
+ getFieldName := func(name string) string {
+ return fmt.Sprintf("%s.%s", parentName, name)
+ }
+
+ for _, field := range fields {
+ name := getFieldName(field.GoName)
+
+ var sizeFromName string
+ if field.FieldSizeFrom != nil {
+ sizeFromName = getFieldName(field.FieldSizeFrom.GoName)
+ }
+
+ if _, ok := BaseTypesGo[field.Type]; ok {
+ sizeBaseType(field.Type, name, field.Length, sizeFromName)
+ continue
+ }
+
+ if field.Array {
+ char := fmt.Sprintf("s%d", lvl)
+ index := fmt.Sprintf("j%d", lvl)
+ if field.Length > 0 {
+ g.P("for ", index, " := 0; ", index, " < ", field.Length, "; ", index, "++ {")
+ } else if field.FieldSizeFrom != nil {
+ g.P("for ", index, " := 0; ", index, " < len(", name, "); ", index, "++ {")
+ }
+ g.P("var ", char, " ", fieldGoType(g, field))
+ g.P("_ = ", char)
+ g.P("if ", index, " < len(", name, ") { ", char, " = ", name, "[", index, "] }")
+ name = char
+ }
+
+ switch {
+ case field.TypeEnum != nil:
+ enum := field.TypeEnum
+ if _, ok := BaseTypesGo[enum.Type]; ok {
+ sizeBaseType(enum.Type, name, 0, "")
+ } else {
+ logrus.Panicf("\t// ??? ENUM %s %s\n", name, enum.Type)
+ }
+ case field.TypeAlias != nil:
+ alias := field.TypeAlias
+ if typ := alias.TypeStruct; typ != nil {
+ sizeFields(typ.Fields, name)
+ } else {
+ sizeBaseType(alias.Type, name, alias.Length, "")
+ }
+ case field.TypeStruct != nil:
+ typ := field.TypeStruct
+ sizeFields(typ.Fields, name)
+ case field.TypeUnion != nil:
+ union := field.TypeUnion
+ maxSize := getUnionSize(union)
+ sizeBaseType("u8", name, maxSize, "")
+ default:
+ logrus.Panicf("\t// ??? buf[pos] = %s (%s)\n", name, field.Type)
+ }
+
+ if field.Array {
+ g.P("}")
+ }
+ }
+ }
+ sizeFields(fields, "m")
+
+ g.P("return size")
+ g.P("}")
+}
+
+func encodeBaseType(g *GenFile, typ, name string, length int, sizefrom string) {
+ isArray := length > 0 || sizefrom != ""
+ if isArray {
+ switch typ {
+ case U8:
+ g.P("buf.EncodeBytes(", name, "[:], ", length, ")")
+ return
+ case I8, I16, U16, I32, U32, I64, U64, F64:
+ gotype := BaseTypesGo[typ]
+ if length != 0 {
+ g.P("for i := 0; i < ", length, "; i++ {")
+ } else if sizefrom != "" {
+ g.P("for i := 0; i < len(", name, "); i++ {")
+ }
+ g.P("var x ", gotype)
+ g.P("if i < len(", name, ") { x = ", gotype, "(", name, "[i]) }")
+ name = "x"
+ }
+ }
+ switch typ {
+ case I8, U8, I16, U16, I32, U32, I64, U64:
+ typsize := BaseTypeSizes[typ]
+ g.P("buf.EncodeUint", typsize*8, "(uint", typsize*8, "(", name, "))")
+ case F64:
+ g.P("buf.EncodeFloat64(float64(", name, "))")
+ case BOOL:
+ g.P("buf.EncodeBool(", name, ")")
+ case STRING:
+ g.P("buf.EncodeString(", name, ", ", length, ")")
+ default:
+ logrus.Panicf("// ??? %s %s\n", name, typ)
+ }
+ if isArray {
+ switch typ {
+ case I8, U8, I16, U16, I32, U32, I64, U64, F64:
+ g.P("}")
+ }
+ }
+}
+
+func encodeFields(g *GenFile, fields []*Field, parentName string, lvl int) {
+ getFieldName := func(name string) string {
+ return fmt.Sprintf("%s.%s", parentName, name)
+ }
+
+ for _, field := range fields {
+ name := getFieldName(field.GoName)
+
+ encodeField(g, field, name, getFieldName, lvl)
+ }
+}
+
+func encodeField(g *GenFile, field *Field, name string, getFieldName func(name string) string, lvl int) {
+ if f := field.FieldSizeOf; f != nil {
+ if _, ok := BaseTypesGo[field.Type]; ok {
+ encodeBaseType(g, field.Type, fmt.Sprintf("len(%s)", getFieldName(f.GoName)), field.Length, "")
+ return
+ } else {
+ panic(fmt.Sprintf("failed to encode base type of sizefrom field: %s (%s)", field.Name, field.Type))
+ }
+ }
+ var sizeFromName string
+ if field.FieldSizeFrom != nil {
+ sizeFromName = getFieldName(field.FieldSizeFrom.GoName)
+ }
+
+ if _, ok := BaseTypesGo[field.Type]; ok {
+ encodeBaseType(g, field.Type, name, field.Length, sizeFromName)
+ return
+ }
+
+ if field.Array {
+ char := fmt.Sprintf("v%d", lvl)
+ index := fmt.Sprintf("j%d", lvl)
+ if field.Length > 0 {
+ g.P("for ", index, " := 0; ", index, " < ", field.Length, "; ", index, "++ {")
+ } else if field.SizeFrom != "" {
+ g.P("for ", index, " := 0; ", index, " < len(", name, "); ", index, "++ {")
+ }
+ g.P("var ", char, " ", fieldGoType(g, field))
+ g.P("if ", index, " < len(", name, ") { ", char, " = ", name, "[", index, "] }")
+ name = char
+ }
+
+ switch {
+ case field.TypeEnum != nil:
+ encodeBaseType(g, field.TypeEnum.Type, name, 0, "")
+ case field.TypeAlias != nil:
+ alias := field.TypeAlias
+ if typ := alias.TypeStruct; typ != nil {
+ encodeFields(g, typ.Fields, name, lvl+1)
+ } else {
+ encodeBaseType(g, alias.Type, name, alias.Length, "")
+ }
+ case field.TypeStruct != nil:
+ encodeFields(g, field.TypeStruct.Fields, name, lvl+1)
+ case field.TypeUnion != nil:
+ g.P("buf.EncodeBytes(", name, ".", fieldUnionData, "[:], 0)")
+ default:
+ logrus.Panicf("\t// ??? buf[pos] = %s (%s)\n", name, field.Type)
+ }
+
+ if field.Array {
+ g.P("}")
+ }
+}
+
+func generateMessageMarshal(g *GenFile, name string, fields []*Field) {
+ g.P("func (m *", name, ") Marshal(b []byte) ([]byte, error) {")
+ g.P("var buf *", govppCodecPkg.Ident("Buffer"))
+ g.P("if b == nil {")
+ g.P("buf = ", govppCodecPkg.Ident("NewBuffer"), "(make([]byte, m.Size()))")
+ g.P("} else {")
+ g.P("buf = ", govppCodecPkg.Ident("NewBuffer"), "(b)")
+ g.P("}")
+
+ encodeFields(g, fields, "m", 0)
+
+ g.P("return buf.Bytes(), nil")
+ g.P("}")
+}
+
+func decodeBaseType(g *GenFile, typ, orig, name string, length int, sizefrom string, alloc bool) {
+ isArray := length > 0 || sizefrom != ""
+ if isArray {
+ switch typ {
+ case U8:
+ g.P("copy(", name, "[:], buf.DecodeBytes(", length, "))")
+ return
+ case I8, I16, U16, I32, U32, I64, U64, F64:
+ if alloc {
+ var size string
+ switch {
+ case length > 0:
+ size = strconv.Itoa(length)
+ case sizefrom != "":
+ size = sizefrom
+ }
+ if size != "" {
+ g.P(name, " = make([]", orig, ", ", size, ")")
+ }
+ }
+ g.P("for i := 0; i < len(", name, "); i++ {")
+ name = fmt.Sprintf("%s[i]", name)
+ }
+ }
+ switch typ {
+ case I8, U8, I16, U16, I32, U32, I64, U64:
+ typsize := BaseTypeSizes[typ]
+ if gotype, ok := BaseTypesGo[typ]; !ok || gotype != orig || strings.HasPrefix(orig, "i") {
+ g.P(name, " = ", orig, "(buf.DecodeUint", typsize*8, "())")
+ } else {
+ g.P(name, " = buf.DecodeUint", typsize*8, "()")
+ }
+ case F64:
+ g.P(name, " = ", orig, "(buf.DecodeFloat64())")
+ case BOOL:
+ g.P(name, " = buf.DecodeBool()")
+ case STRING:
+ g.P(name, " = buf.DecodeString(", length, ")")
+ default:
+ logrus.Panicf("\t// ??? %s %s\n", name, typ)
+ }
+ if isArray {
+ switch typ {
+ case I8, U8, I16, U16, I32, U32, I64, U64, F64:
+ g.P("}")
+ }
+ }
+}
+
+func generateMessageUnmarshal(g *GenFile, name string, fields []*Field) {
+ g.P("func (m *", name, ") Unmarshal(b []byte) error {")
+
+ if len(fields) > 0 {
+ g.P("buf := ", govppCodecPkg.Ident("NewBuffer"), "(b)")
+ decodeFields(g, fields, "m", 0)
+ }
+
+ g.P("return nil")
+ g.P("}")
+}
+
+func decodeFields(g *GenFile, fields []*Field, parentName string, lvl int) {
+ getFieldName := func(name string) string {
+ return fmt.Sprintf("%s.%s", parentName, name)
+ }
+
+ for _, field := range fields {
+ name := getFieldName(field.GoName)
+
+ decodeField(g, field, name, getFieldName, lvl)
+ }
+}
+
+func decodeField(g *GenFile, field *Field, name string, getFieldName func(string) string, lvl int) {
+ var sizeFromName string
+ if field.FieldSizeFrom != nil {
+ sizeFromName = getFieldName(field.FieldSizeFrom.GoName)
+ }
+
+ if _, ok := BaseTypesGo[field.Type]; ok {
+ decodeBaseType(g, field.Type, fieldGoType(g, field), name, field.Length, sizeFromName, true)
+ return
+ }
+
+ if field.Array {
+ index := fmt.Sprintf("j%d", lvl)
+ if field.Length > 0 {
+ g.P("for ", index, " := 0; ", index, " < ", field.Length, ";", index, "++ {")
+ } else if field.SizeFrom != "" {
+ g.P(name, " = make(", getFieldType(g, field), ", int(", sizeFromName, "))")
+ g.P("for ", index, " := 0; ", index, " < len(", name, ");", index, "++ {")
+ }
+ name = fmt.Sprintf("%s[%s]", name, index)
+ }
+
+ if enum := field.TypeEnum; enum != nil {
+ if _, ok := BaseTypesGo[enum.Type]; ok {
+ decodeBaseType(g, enum.Type, fieldGoType(g, field), name, 0, "", false)
+ } else {
+ logrus.Panicf("\t// ??? ENUM %s %s\n", name, enum.Type)
+ }
+ } else if alias := field.TypeAlias; alias != nil {
+ if typ := alias.TypeStruct; typ != nil {
+ decodeFields(g, typ.Fields, name, lvl+1)
+ } else {
+ if alias.Length > 0 {
+ decodeBaseType(g, alias.Type, BaseTypesGo[alias.Type], name, alias.Length, "", false)
+ } else {
+ decodeBaseType(g, alias.Type, fieldGoType(g, field), name, alias.Length, "", false)
+ }
+ }
+ } else if typ := field.TypeStruct; typ != nil {
+ decodeFields(g, typ.Fields, name, lvl+1)
+ } else if union := field.TypeUnion; union != nil {
+ maxSize := getUnionSize(union)
+ g.P("copy(", name, ".", fieldUnionData, "[:], buf.DecodeBytes(", maxSize, "))")
+ } else {
+ logrus.Panicf("\t// ??? %s (%v)\n", field.GoName, field.Type)
+ }
+
+ if field.Array {
+ g.P("}")
+ }
+}
diff --git a/binapigen/gen_helpers.go b/binapigen/gen_helpers.go
new file mode 100644
index 0000000..a22f1c6
--- /dev/null
+++ b/binapigen/gen_helpers.go
@@ -0,0 +1,348 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+func init() {
+ //RegisterPlugin("convert", GenerateConvert)
+}
+
+// library dependencies
+const (
+ fmtPkg = GoImportPath("fmt")
+ netPkg = GoImportPath("net")
+ stringsPkg = GoImportPath("strings")
+)
+
+func generateIPConversion(g *GenFile, structName string, ipv int) {
+ // ParseIPXAddress method
+ g.P("func Parse", structName, "(s string) (", structName, ", error) {")
+ if ipv == 4 {
+ g.P(" ip := ", netPkg.Ident("ParseIP"), "(s).To4()")
+ } else {
+ g.P(" ip := ", netPkg.Ident("ParseIP"), "(s).To16()")
+ }
+ g.P(" if ip == nil {")
+ g.P(" return ", structName, "{}, ", fmtPkg.Ident("Errorf"), "(\"invalid IP address: %s\", s)")
+ g.P(" }")
+ g.P(" var ipaddr ", structName)
+ if ipv == 4 {
+ g.P(" copy(ipaddr[:], ip.To4())")
+ } else {
+ g.P(" copy(ipaddr[:], ip.To16())")
+ }
+ g.P(" return ipaddr, nil")
+ g.P("}")
+ g.P()
+
+ // ToIP method
+ g.P("func (x ", structName, ") ToIP() ", netPkg.Ident("IP"), " {")
+ if ipv == 4 {
+ g.P(" return ", netPkg.Ident("IP"), "(x[:]).To4()")
+ } else {
+ g.P(" return ", netPkg.Ident("IP"), "(x[:]).To16()")
+ }
+ g.P("}")
+
+ // String method
+ g.P("func (x ", structName, ") String() string {")
+ g.P(" return x.ToIP().String()")
+ g.P("}")
+
+ // MarshalText method
+ g.P("func (x *", structName, ") MarshalText() ([]byte, error) {")
+ g.P(" return []byte(x.String()), nil")
+ g.P("}")
+
+ // UnmarshalText method
+ g.P("func (x *", structName, ") UnmarshalText(text []byte) error {")
+ g.P(" ipaddr, err := Parse", structName, "(string(text))")
+ g.P(" if err !=nil {")
+ g.P(" return err")
+ g.P(" }")
+ g.P(" *x = ipaddr")
+ g.P(" return nil")
+ g.P("}")
+ g.P()
+}
+
+func generateAddressConversion(g *GenFile, structName string) {
+ // ParseAddress method
+ g.P("func Parse", structName, "(s string) (", structName, ", error) {")
+ g.P(" ip := ", netPkg.Ident("ParseIP"), "(s)")
+ g.P(" if ip == nil {")
+ g.P(" return ", structName, "{}, ", fmtPkg.Ident("Errorf"), "(\"invalid address: %s\", s)")
+ g.P(" }")
+ g.P(" var addr ", structName)
+ g.P(" if ip.To4() == nil {")
+ g.P(" addr.Af = ADDRESS_IP6")
+ g.P(" var ip6 IP6Address")
+ g.P(" copy(ip6[:], ip.To16())")
+ g.P(" addr.Un.SetIP6(ip6)")
+ g.P(" } else {")
+ g.P(" addr.Af = ADDRESS_IP4")
+ g.P(" var ip4 IP4Address")
+ g.P(" copy(ip4[:], ip.To4())")
+ g.P(" addr.Un.SetIP4(ip4)")
+ g.P(" }")
+ g.P(" return addr, nil")
+ g.P("}")
+
+ // ToIP method
+ g.P("func (x ", structName, ") ToIP() ", netPkg.Ident("IP"), " {")
+ g.P(" if x.Af == ADDRESS_IP6 {")
+ g.P(" ip6 := x.Un.GetIP6()")
+ g.P(" return ", netPkg.Ident("IP"), "(ip6[:]).To16()")
+ g.P(" } else {")
+ g.P(" ip4 := x.Un.GetIP4()")
+ g.P(" return ", netPkg.Ident("IP"), "(ip4[:]).To4()")
+ g.P(" }")
+ g.P("}")
+
+ // String method
+ g.P("func (x ", structName, ") String() string {")
+ g.P(" return x.ToIP().String()")
+ g.P("}")
+
+ // MarshalText method
+ g.P("func (x *", structName, ") MarshalText() ([]byte, error) {")
+ g.P(" return []byte(x.String()), nil")
+ g.P("}")
+
+ // UnmarshalText method
+ g.P("func (x *", structName, ") UnmarshalText(text []byte) error {")
+ g.P(" addr, err := Parse", structName, "(string(text))")
+ g.P(" if err != nil {")
+ g.P(" return err")
+ g.P(" }")
+ g.P(" *x = addr")
+ g.P(" return nil")
+ g.P("}")
+ g.P()
+}
+
+func generateIPPrefixConversion(g *GenFile, structName string, ipv int) {
+ // ParsePrefix method
+ g.P("func Parse", structName, "(s string) (prefix ", structName, ", err error) {")
+ g.P(" hasPrefix := ", stringsPkg.Ident("Contains"), "(s, \"/\")")
+ g.P(" if hasPrefix {")
+ g.P(" ip, network, err := ", netPkg.Ident("ParseCIDR"), "(s)")
+ g.P(" if err != nil {")
+ g.P(" return ", structName, "{}, ", fmtPkg.Ident("Errorf"), "(\"invalid IP %s: %s\", s, err)")
+ g.P(" }")
+ g.P(" maskSize, _ := network.Mask.Size()")
+ g.P(" prefix.Len = byte(maskSize)")
+ if ipv == 4 {
+ g.P(" prefix.Address, err = ParseIP4Address(ip.String())")
+ } else {
+ g.P(" prefix.Address, err = ParseIP6Address(ip.String())")
+ }
+ g.P(" if err != nil {")
+ g.P(" return ", structName, "{}, ", fmtPkg.Ident("Errorf"), "(\"invalid IP %s: %s\", s, err)")
+ g.P(" }")
+ g.P(" } else {")
+ g.P(" ip := ", netPkg.Ident("ParseIP"), "(s)")
+ g.P(" defaultMaskSize, _ := ", netPkg.Ident("CIDRMask"), "(32, 32).Size()")
+ g.P(" if ip.To4() == nil {")
+ g.P(" defaultMaskSize, _ =", netPkg.Ident("CIDRMask"), "(128, 128).Size()")
+ g.P(" }")
+ g.P(" prefix.Len = byte(defaultMaskSize)")
+ if ipv == 4 {
+ g.P(" prefix.Address, err = ParseIP4Address(ip.String())")
+ } else {
+ g.P(" prefix.Address, err = ParseIP6Address(ip.String())")
+ }
+ g.P(" if err != nil {")
+ g.P(" return ", structName, "{}, ", fmtPkg.Ident("Errorf"), "(\"invalid IP %s: %s\", s, err)")
+ g.P(" }")
+ g.P(" }")
+ g.P(" return prefix, nil")
+ g.P("}")
+
+ // ToIPNet method
+ g.P("func (x ", structName, ") ToIPNet() *", netPkg.Ident("IPNet"), " {")
+ if ipv == 4 {
+ g.P(" mask := ", netPkg.Ident("CIDRMask"), "(int(x.Len), 32)")
+ } else {
+ g.P(" mask := ", netPkg.Ident("CIDRMask"), "(int(x.Len), 128)")
+ }
+ g.P(" ipnet := &", netPkg.Ident("IPNet"), "{IP: x.Address.ToIP(), Mask: mask}")
+ g.P(" return ipnet")
+ g.P("}")
+
+ // String method
+ g.P("func (x ", structName, ") String() string {")
+ g.P(" ip := x.Address.String()")
+ g.P(" return ip + \"/\" + ", strconvPkg.Ident("Itoa"), "(int(x.Len))")
+ /*if ipv == 4 {
+ g.P(" mask := ", netPkg.Ident("CIDRMask"), "(int(x.Len), 32)")
+ } else {
+ g.P(" mask := ", netPkg.Ident("CIDRMask"), "(int(x.Len), 128)")
+ }
+ g.P(" ipnet := &", netPkg.Ident("IPNet"), "{IP: x.Address.ToIP(), Mask: mask}")
+ g.P(" return ipnet.String()")*/
+ g.P("}")
+
+ // MarshalText method
+ g.P("func (x *", structName, ") MarshalText() ([]byte, error) {")
+ g.P(" return []byte(x.String()), nil")
+ g.P("}")
+
+ // UnmarshalText method
+ g.P("func (x *", structName, ") UnmarshalText(text []byte) error {")
+ g.P(" prefix, err := Parse", structName, "(string(text))")
+ g.P(" if err != nil {")
+ g.P(" return err")
+ g.P(" }")
+ g.P(" *x = prefix")
+ g.P(" return nil")
+ g.P("}")
+ g.P()
+}
+
+func generatePrefixConversion(g *GenFile, structName string) {
+ // ParsePrefix method
+ g.P("func Parse", structName, "(ip string) (prefix ", structName, ", err error) {")
+ g.P(" hasPrefix := ", stringsPkg.Ident("Contains"), "(ip, \"/\")")
+ g.P(" if hasPrefix {")
+ g.P(" netIP, network, err := ", netPkg.Ident("ParseCIDR"), "(ip)")
+ g.P(" if err != nil {")
+ g.P(" return Prefix{}, ", fmtPkg.Ident("Errorf"), "(\"invalid IP %s: %s\", ip, err)")
+ g.P(" }")
+ g.P(" maskSize, _ := network.Mask.Size()")
+ g.P(" prefix.Len = byte(maskSize)")
+ g.P(" prefix.Address, err = ParseAddress(netIP.String())")
+ g.P(" if err != nil {")
+ g.P(" return Prefix{}, ", fmtPkg.Ident("Errorf"), "(\"invalid IP %s: %s\", ip, err)")
+ g.P(" }")
+ g.P(" } else {")
+ g.P(" netIP := ", netPkg.Ident("ParseIP"), "(ip)")
+ g.P(" defaultMaskSize, _ := ", netPkg.Ident("CIDRMask"), "(32, 32).Size()")
+ g.P(" if netIP.To4() == nil {")
+ g.P(" defaultMaskSize, _ =", netPkg.Ident("CIDRMask"), "(128, 128).Size()")
+ g.P(" }")
+ g.P(" prefix.Len = byte(defaultMaskSize)")
+ g.P(" prefix.Address, err = ParseAddress(netIP.String())")
+ g.P(" if err != nil {")
+ g.P(" return Prefix{}, ", fmtPkg.Ident("Errorf"), "(\"invalid IP %s: %s\", ip, err)")
+ g.P(" }")
+ g.P(" }")
+ g.P(" return prefix, nil")
+ g.P("}")
+
+ // ToIPNet method
+ g.P("func (x ", structName, ") ToIPNet() *", netPkg.Ident("IPNet"), " {")
+ g.P(" var mask ", netPkg.Ident("IPMask"))
+ g.P(" if x.Address.Af == ADDRESS_IP4 {")
+ g.P(" mask = ", netPkg.Ident("CIDRMask"), "(int(x.Len), 32)")
+ g.P(" } else {")
+ g.P(" mask = ", netPkg.Ident("CIDRMask"), "(int(x.Len), 128)")
+ g.P(" }")
+ g.P(" ipnet := &", netPkg.Ident("IPNet"), "{IP: x.Address.ToIP(), Mask: mask}")
+ g.P(" return ipnet")
+ g.P("}")
+
+ // String method
+ g.P("func (x ", structName, ") String() string {")
+ g.P(" ip := x.Address.String()")
+ g.P(" return ip + \"/\" + ", strconvPkg.Ident("Itoa"), "(int(x.Len))")
+ g.P("}")
+
+ // MarshalText method
+ g.P("func (x *", structName, ") MarshalText() ([]byte, error) {")
+ g.P(" return []byte(x.String()), nil")
+ g.P("}")
+
+ // UnmarshalText method
+ g.P("func (x *", structName, ") UnmarshalText(text []byte) error {")
+ g.P(" prefix, err := Parse", structName, "(string(text))")
+ g.P(" if err !=nil {")
+ g.P(" return err")
+ g.P(" }")
+ g.P(" *x = prefix")
+ g.P(" return nil")
+ g.P("}")
+ g.P()
+}
+
+func generateAddressWithPrefixConversion(g *GenFile, structName string) {
+ // ParseAddressWithPrefix method
+ g.P("func Parse", structName, "(s string) (", structName, ", error) {")
+ g.P(" prefix, err := ParsePrefix(s)")
+ g.P(" if err != nil {")
+ g.P(" return ", structName, "{}, err")
+ g.P(" }")
+ g.P(" return ", structName, "(prefix), nil")
+ g.P("}")
+
+ // String method
+ g.P("func (x ", structName, ") String() string {")
+ g.P(" return Prefix(x).String()")
+ g.P("}")
+
+ // MarshalText method
+ g.P("func (x *", structName, ") MarshalText() ([]byte, error) {")
+ g.P(" return []byte(x.String()), nil")
+ g.P("}")
+
+ // UnmarshalText method
+ g.P("func (x *", structName, ") UnmarshalText(text []byte) error {")
+ g.P(" prefix, err := Parse", structName, "(string(text))")
+ g.P(" if err != nil {")
+ g.P(" return err")
+ g.P(" }")
+ g.P(" *x = prefix")
+ g.P(" return nil")
+ g.P("}")
+ g.P()
+}
+
+func generateMacAddressConversion(g *GenFile, structName string) {
+ // ParseMAC method
+ g.P("func Parse", structName, "(s string) (", structName, ", error) {")
+ g.P(" var macaddr ", structName)
+ g.P(" mac, err := ", netPkg.Ident("ParseMAC"), "(s)")
+ g.P(" if err != nil {")
+ g.P(" return macaddr, err")
+ g.P(" }")
+ g.P(" copy(macaddr[:], mac[:])")
+ g.P(" return macaddr, nil")
+ g.P("}")
+
+ // ToMAC method
+ g.P("func (x ", structName, ") ToMAC() ", netPkg.Ident("HardwareAddr"), " {")
+ g.P(" return ", netPkg.Ident("HardwareAddr"), "(x[:])")
+ g.P("}")
+
+ // String method
+ g.P("func (x ", structName, ") String() string {")
+ g.P(" return x.ToMAC().String()")
+ g.P("}")
+
+ // MarshalText method
+ g.P("func (x *", structName, ") MarshalText() ([]byte, error) {")
+ g.P(" return []byte(x.String()), nil")
+ g.P("}")
+
+ // UnmarshalText method
+ g.P("func (x *", structName, ") UnmarshalText(text []byte) error {")
+ g.P(" mac, err := Parse", structName, "(string(text))")
+ g.P(" if err != nil {")
+ g.P(" return err")
+ g.P(" }")
+ g.P(" *x = mac")
+ g.P(" return nil")
+ g.P("}")
+ g.P()
+}
diff --git a/binapigen/gen_helpers_test.go b/binapigen/gen_helpers_test.go
new file mode 100644
index 0000000..371fd6c
--- /dev/null
+++ b/binapigen/gen_helpers_test.go
@@ -0,0 +1,156 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+import (
+ "strings"
+ "testing"
+
+ . "github.com/onsi/gomega"
+
+ "git.fd.io/govpp.git/binapi/ethernet_types"
+ "git.fd.io/govpp.git/binapi/ip_types"
+)
+
+func TestGeneratedParseAddress(t *testing.T) {
+ RegisterTestingT(t)
+
+ var data = []struct {
+ input string
+ result ip_types.Address
+ }{
+ {"192.168.0.1", ip_types.Address{
+ Af: ip_types.ADDRESS_IP4,
+ Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
+ }},
+ {"aac1:0:ab45::", ip_types.Address{
+ Af: ip_types.ADDRESS_IP6,
+ Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
+ }},
+ }
+
+ for _, entry := range data {
+ t.Run(entry.input, func(t *testing.T) {
+ parsedAddress, err := ip_types.ParseAddress(entry.input)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(parsedAddress).To(Equal(entry.result))
+
+ originAddress := parsedAddress.String()
+ Expect(originAddress).To(Equal(entry.input))
+ })
+ }
+}
+
+func TestGeneratedParseAddressError(t *testing.T) {
+ RegisterTestingT(t)
+
+ _, err := ip_types.ParseAddress("malformed_ip")
+ Expect(err).Should(HaveOccurred())
+}
+
+func TestGeneratedParsePrefix(t *testing.T) {
+ RegisterTestingT(t)
+
+ var data = []struct {
+ input string
+ result ip_types.Prefix
+ }{
+ {"192.168.0.1/24", ip_types.Prefix{
+ Address: ip_types.Address{
+ Af: ip_types.ADDRESS_IP4,
+ Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
+ },
+ Len: 24,
+ }},
+ {"192.168.0.1", ip_types.Prefix{
+ Address: ip_types.Address{
+ Af: ip_types.ADDRESS_IP4,
+ Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
+ },
+ Len: 32,
+ }},
+ {"aac1:0:ab45::/96", ip_types.Prefix{
+ Address: ip_types.Address{
+ Af: ip_types.ADDRESS_IP6,
+ Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
+ },
+ Len: 96,
+ }},
+ {"aac1:0:ab45::", ip_types.Prefix{
+ Address: ip_types.Address{
+ Af: ip_types.ADDRESS_IP6,
+ Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
+ },
+ Len: 128,
+ }},
+ }
+
+ for _, entry := range data {
+ t.Run(entry.input, func(t *testing.T) {
+ parsedAddress, err := ip_types.ParsePrefix(entry.input)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(parsedAddress).To(Equal(entry.result))
+
+ // Parsed IP without prefix receives a default one
+ // so the input data must be adjusted
+ if entry.result.Address.Af == ip_types.ADDRESS_IP4 && !strings.Contains(entry.input, "/") {
+ entry.input = entry.input + "/32"
+ }
+ if entry.result.Address.Af == ip_types.ADDRESS_IP6 && !strings.Contains(entry.input, "/") {
+ entry.input = entry.input + "/128"
+ }
+ originAddress := parsedAddress.String()
+ Expect(originAddress).To(Equal(entry.input))
+ })
+ }
+}
+
+func TestGeneratedParsePrefixError(t *testing.T) {
+ RegisterTestingT(t)
+
+ _, err := ip_types.ParsePrefix("malformed_ip")
+ Expect(err).Should(HaveOccurred())
+}
+
+func TestGeneratedParseMAC(t *testing.T) {
+ RegisterTestingT(t)
+
+ var data = []struct {
+ input string
+ result ethernet_types.MacAddress
+ }{
+ {"b7:b9:bb:a1:5c:af", ethernet_types.MacAddress{183, 185, 187, 161, 92, 175}},
+ {"47:4b:c7:3e:06:c8", ethernet_types.MacAddress{71, 75, 199, 62, 6, 200}},
+ {"a7:cc:9f:10:18:e3", ethernet_types.MacAddress{167, 204, 159, 16, 24, 227}},
+ }
+
+ for _, entry := range data {
+ t.Run(entry.input, func(t *testing.T) {
+ parsedMac, err := ethernet_types.ParseMacAddress(entry.input)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(parsedMac).To(Equal(entry.result))
+
+ originAddress := parsedMac.String()
+ Expect(originAddress).To(Equal(entry.input))
+ })
+ }
+}
+
+func TestGeneratedParseMACError(t *testing.T) {
+ RegisterTestingT(t)
+
+ _, err := ethernet_types.ParseMacAddress("malformed_mac")
+ Expect(err).Should(HaveOccurred())
+}
diff --git a/binapigen/gen_rest.go b/binapigen/gen_rest.go
new file mode 100644
index 0000000..6ddb57a
--- /dev/null
+++ b/binapigen/gen_rest.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+import (
+ "path"
+ "strconv"
+)
+
+func init() {
+ RegisterPlugin("rest", GenerateREST)
+}
+
+// library dependencies
+const (
+ httpPkg = GoImportPath("net/http")
+ ioutilPkg = GoImportPath("io/ioutil")
+ jsonPkg = GoImportPath("encoding/json")
+)
+
+func GenerateREST(gen *Generator, file *File) *GenFile {
+ if file.Service == nil {
+ return nil
+ }
+
+ logf("----------------------------")
+ logf(" Generate REST - %s", file.Desc.Name)
+ logf("----------------------------")
+
+ filename := path.Join(file.FilenamePrefix, file.Desc.Name+"_rest.ba.go")
+ g := gen.NewGenFile(filename, file.GoImportPath)
+ g.file = file
+
+ // generate file header
+ g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+ g.P()
+ g.P("package ", file.PackageName)
+ g.P()
+
+ // generate RPC service
+ if len(file.Service.RPCs) > 0 {
+ genRESTHandler(g, file.Service)
+ }
+
+ return g
+}
+
+func genRESTHandler(g *GenFile, svc *Service) {
+ // generate handler constructor
+ g.P("func RESTHandler(rpc ", serviceApiName, ") ", httpPkg.Ident("Handler"), " {")
+ g.P(" mux := ", httpPkg.Ident("NewServeMux"), "()")
+
+ // generate http handlers for rpc
+ for _, rpc := range svc.RPCs {
+ if rpc.MsgReply == nil {
+ continue
+ }
+ if rpc.VPP.Stream {
+ continue // TODO: implement handler for streaming messages
+ }
+ g.P("mux.HandleFunc(", strconv.Quote("/"+rpc.VPP.Request), ", func(w ", httpPkg.Ident("ResponseWriter"), ", req *", httpPkg.Ident("Request"), ") {")
+ g.P("var request = new(", rpc.MsgRequest.GoName, ")")
+ if len(rpc.MsgRequest.Fields) > 0 {
+ g.P("b, err := ", ioutilPkg.Ident("ReadAll"), "(req.Body)")
+ g.P("if err != nil {")
+ g.P(" ", httpPkg.Ident("Error"), "(w, \"read body failed\", ", httpPkg.Ident("StatusBadRequest"), ")")
+ g.P(" return")
+ g.P("}")
+ g.P("if err := ", jsonPkg.Ident("Unmarshal"), "(b, request); err != nil {")
+ g.P(" ", httpPkg.Ident("Error"), "(w, \"unmarshal data failed\", ", httpPkg.Ident("StatusBadRequest"), ")")
+ g.P(" return")
+ g.P("}")
+ }
+ g.P("reply, err := rpc.", rpc.GoName, "(req.Context(), request)")
+ g.P("if err != nil {")
+ g.P(" ", httpPkg.Ident("Error"), "(w, \"request failed: \"+err.Error(), ", httpPkg.Ident("StatusInternalServerError"), ")")
+ g.P(" return")
+ g.P("}")
+ g.P("rep, err := ", jsonPkg.Ident("MarshalIndent"), "(reply, \"\", \" \")")
+ g.P("if err != nil {")
+ g.P(" ", httpPkg.Ident("Error"), "(w, \"marshal failed: \"+err.Error(), ", httpPkg.Ident("StatusInternalServerError"), ")")
+ g.P(" return")
+ g.P("}")
+ g.P("w.Write(rep)")
+ g.P("})")
+ }
+
+ g.P("return ", httpPkg.Ident("HandlerFunc"), "(mux.ServeHTTP)")
+ g.P("}")
+ g.P()
+}
diff --git a/binapigen/gen_rpc.go b/binapigen/gen_rpc.go
new file mode 100644
index 0000000..ba23f4a
--- /dev/null
+++ b/binapigen/gen_rpc.go
@@ -0,0 +1,204 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+import (
+ "fmt"
+ "path"
+
+ "github.com/sirupsen/logrus"
+)
+
+func init() {
+ RegisterPlugin("rpc", GenerateRPC)
+}
+
+// library dependencies
+const (
+ contextPkg = GoImportPath("context")
+ ioPkg = GoImportPath("io")
+)
+
+// generated names
+const (
+ 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
+)
+
+func GenerateRPC(gen *Generator, file *File) *GenFile {
+ if file.Service == nil {
+ return nil
+ }
+
+ logf("----------------------------")
+ logf(" Generate RPC - %s", file.Desc.Name)
+ logf("----------------------------")
+
+ filename := path.Join(file.FilenamePrefix, file.Desc.Name+"_rpc.ba.go")
+ g := gen.NewGenFile(filename, file.GoImportPath)
+ g.file = file
+
+ // generate file header
+ g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+ g.P()
+ g.P("package ", file.PackageName)
+ g.P()
+
+ // generate RPC service
+ if len(file.Service.RPCs) > 0 {
+ genService(g, file.Service)
+ }
+
+ return g
+}
+
+func genService(g *GenFile, svc *Service) {
+ // generate comment
+ g.P("// ", serviceApiName, " defines RPC service ", g.file.Desc.Name, ".")
+
+ // generate service interface
+ g.P("type ", serviceApiName, " interface {")
+ for _, rpc := range svc.RPCs {
+ g.P(rpcMethodSignature(g, rpc))
+ }
+ g.P("}")
+ g.P()
+
+ // generate client implementation
+ g.P("type ", serviceImplName, " struct {")
+ g.P("conn ", govppApiPkg.Ident("Connection"))
+ g.P("}")
+ g.P()
+
+ // generate client constructor
+ g.P("func New", serviceClientName, "(conn ", govppApiPkg.Ident("Connection"), ") ", serviceApiName, " {")
+ g.P("return &", serviceImplName, "{conn}")
+ g.P("}")
+ g.P()
+
+ msgControlPingReply, ok := g.gen.messagesByName["control_ping_reply"]
+ if !ok {
+ logrus.Fatalf("no message for %v", "control_ping_reply")
+ }
+ msgControlPing, ok := g.gen.messagesByName["control_ping"]
+ if !ok {
+ logrus.Fatalf("no message for %v", "control_ping")
+ }
+
+ for _, rpc := range svc.RPCs {
+ logf(" gen RPC: %v (%s)", rpc.GoName, rpc.VPP.Request)
+
+ g.P("func (c *", serviceImplName, ") ", rpcMethodSignature(g, rpc), " {")
+ if rpc.VPP.Stream {
+ streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, rpc.GoName)
+ streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, rpc.GoName)
+
+ msgDetails := rpc.MsgReply
+ var msgReply *Message
+ if rpc.MsgStream != nil {
+ msgDetails = rpc.MsgStream
+ msgReply = rpc.MsgReply
+ } else {
+ msgDetails = rpc.MsgReply
+ msgReply = msgControlPingReply
+ }
+
+ g.P("stream, err := c.conn.NewStream(ctx)")
+ g.P("if err != nil { return nil, err }")
+ g.P("x := &", streamImpl, "{stream}")
+ g.P("if err := x.Stream.SendMsg(in); err != nil {")
+ g.P(" return nil, err")
+ g.P("}")
+ if rpc.MsgStream == nil {
+ g.P("if err = x.Stream.SendMsg(&", msgControlPing.GoIdent, "{}); err != nil {")
+ g.P(" return nil, err")
+ g.P("}")
+ }
+ g.P("return x, nil")
+ g.P("}")
+ g.P()
+ g.P("type ", streamApi, " interface {")
+ g.P(" Recv() (*", msgDetails.GoIdent, ", error)")
+ g.P(" ", govppApiPkg.Ident("Stream"))
+ g.P("}")
+ g.P()
+
+ g.P("type ", streamImpl, " struct {")
+ g.P(" ", govppApiPkg.Ident("Stream"))
+ g.P("}")
+ g.P()
+
+ g.P("func (c *", streamImpl, ") Recv() (*", msgDetails.GoIdent, ", error) {")
+ g.P(" msg, err := c.Stream.RecvMsg()")
+ g.P(" if err != nil { return nil, err }")
+ g.P(" switch m := msg.(type) {")
+ g.P(" case *", msgDetails.GoIdent, ":")
+ g.P(" return m, nil")
+ g.P(" case *", msgReply.GoIdent, ":")
+ g.P(" return nil, ", ioPkg.Ident("EOF"))
+ g.P(" default:")
+ g.P(" return nil, ", fmtPkg.Ident("Errorf"), "(\"unexpected message: %T %v\", m, m)")
+ g.P("}")
+ } else if rpc.MsgReply != nil {
+ g.P("out := new(", rpc.MsgReply.GoIdent, ")")
+ g.P("err := c.conn.Invoke(ctx, in, out)")
+ g.P("if err != nil { return nil, err }")
+ g.P("return out, nil")
+ } else {
+ g.P("stream, err := c.conn.NewStream(ctx)")
+ g.P("if err != nil { return err }")
+ g.P("err = stream.SendMsg(in)")
+ g.P("if err != nil { return err }")
+ g.P("return nil")
+ }
+ g.P("}")
+ g.P()
+ }
+
+ // TODO: generate service descriptor
+ /*fmt.Fprintf(w, "var %s = api.%s{\n", serviceDescName, serviceDescType)
+ fmt.Fprintf(w, "\tServiceName: \"%s\",\n", ctx.moduleName)
+ fmt.Fprintf(w, "\tHandlerType: (*%s)(nil),\n", serviceApiName)
+ fmt.Fprintf(w, "\tMethods: []api.MethodDesc{\n")
+ for _, method := range rpcs {
+ fmt.Fprintf(w, "\t {\n")
+ fmt.Fprintf(w, "\t MethodName: \"%s\",\n", method.Name)
+ fmt.Fprintf(w, "\t },\n")
+ }
+ fmt.Fprintf(w, "\t},\n")
+ //fmt.Fprintf(w, "\tCompatibility: %s,\n", messageCrcName)
+ //fmt.Fprintf(w, "\tMetadata: reflect.TypeOf((*%s)(nil)).Elem().PkgPath(),\n", serviceApiName)
+ fmt.Fprintf(w, "\tMetadata: \"%s\",\n", ctx.inputFile)
+ fmt.Fprintln(w, "}")*/
+
+ g.P()
+}
+
+func rpcMethodSignature(g *GenFile, rpc *RPC) string {
+ s := rpc.GoName + "(ctx " + g.GoIdent(contextPkg.Ident("Context"))
+ s += ", in *" + g.GoIdent(rpc.MsgRequest.GoIdent) + ") ("
+ if rpc.VPP.Stream {
+ s += serviceApiName + "_" + rpc.GoName + "Client, "
+ } else if rpc.MsgReply != nil {
+ s += "*" + g.GoIdent(rpc.MsgReply.GoIdent) + ", "
+ }
+ s += "error)"
+ return s
+}
diff --git a/binapigen/generate.go b/binapigen/generate.go
index d35427f..689463e 100644
--- a/binapigen/generate.go
+++ b/binapigen/generate.go
@@ -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()
}
diff --git a/binapigen/generate_rpc.go b/binapigen/generate_rpc.go
deleted file mode 100644
index 4beec04..0000000
--- a/binapigen/generate_rpc.go
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright (c) 2020 Cisco and/or its affiliates.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at:
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package binapigen
-
-import (
- "fmt"
- "io"
- "strings"
-)
-
-// generated service names
-const (
- 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
-)
-
-func generateFileRPC(ctx *GenFile, w io.Writer) {
- logf("----------------------------")
- logf("generating RPC file package: %q", ctx.file.PackageName)
- logf("----------------------------")
-
- // generate file header
- fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
- fmt.Fprintln(w)
-
- // generate package header
- fmt.Fprintf(w, "package %s\n", ctx.file.PackageName)
- fmt.Fprintln(w)
-
- // generate imports
- fmt.Fprintln(w, "import (")
- fmt.Fprintln(w, ` "context"`)
- fmt.Fprintln(w, ` "io"`)
- fmt.Fprintln(w)
- fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
- fmt.Fprintln(w, ")")
- fmt.Fprintln(w)
-
- // generate RPC service
- if ctx.file.Service != nil && len(ctx.file.Service.RPCs) > 0 {
- generateService(ctx, w, ctx.file.Service)
- }
-
- // generate message registrations
- /*fmt.Fprintln(w, "var _RPCService_desc = api.RPCDesc{")
-
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)*/
-
- // generate import refs
- fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
- fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
- fmt.Fprintf(w, "var _ = context.Background\n")
- fmt.Fprintf(w, "var _ = io.Copy\n")
-
-}
-
-func generateService(ctx *GenFile, w io.Writer, svc *Service) {
- // generate services comment
- generateComment(ctx, w, serviceApiName, "services", "service")
-
- // generate service api
- fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
- for _, rpc := range svc.RPCs {
- generateRPCMethod(ctx, w, &rpc)
- 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 _, rpc := range svc.RPCs {
- method := camelCaseName(rpc.RequestMsg)
- if m := strings.TrimSuffix(method, "Dump"); method != m {
- method = "Dump" + m
- }
-
- fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
- generateRPCMethod(ctx, w, &rpc)
- fmt.Fprintln(w, " {")
- if rpc.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(rpc.ReplyMsg); replyTyp != "" {
- fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
- fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
- fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
- fmt.Fprintf(w, "\treturn out, nil\n")
- } else {
- fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
- fmt.Fprintf(w, "\treturn nil\n")
- }
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
-
- if rpc.Stream {
- replyTyp := camelCaseName(rpc.ReplyMsg)
- method := camelCaseName(rpc.RequestMsg)
- if m := strings.TrimSuffix(method, "Dump"); method != m {
- method = "Dump" + m
- }
- streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
-
- fmt.Fprintf(w, "type %s interface {\n", streamApi)
- fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
-
- streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
- fmt.Fprintf(w, "type %s struct {\n", streamImpl)
- fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
-
- fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
- fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
- fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
- fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
- fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
- fmt.Fprintf(w, "\treturn m, nil\n")
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
- }
- }
-
- // TODO: generate service descriptor
- /*fmt.Fprintf(w, "var %s = api.%s{\n", serviceDescName, serviceDescType)
- fmt.Fprintf(w, "\tServiceName: \"%s\",\n", ctx.moduleName)
- fmt.Fprintf(w, "\tHandlerType: (*%s)(nil),\n", serviceApiName)
- fmt.Fprintf(w, "\tMethods: []api.MethodDesc{\n")
- for _, method := range rpcs {
- fmt.Fprintf(w, "\t {\n")
- fmt.Fprintf(w, "\t MethodName: \"%s\",\n", method.Name)
- fmt.Fprintf(w, "\t },\n")
- }
- fmt.Fprintf(w, "\t},\n")
- //fmt.Fprintf(w, "\tCompatibility: %s,\n", messageCrcName)
- //fmt.Fprintf(w, "\tMetadata: reflect.TypeOf((*%s)(nil)).Elem().PkgPath(),\n", serviceApiName)
- fmt.Fprintf(w, "\tMetadata: \"%s\",\n", ctx.inputFile)
- fmt.Fprintln(w, "}")*/
-
- fmt.Fprintln(w)
-}
-
-func generateRPCMethod(ctx *GenFile, w io.Writer, rpc *RPC) {
- reqTyp := camelCaseName(rpc.RequestMsg)
-
- logf(" writing RPC: %+v", reqTyp)
-
- // method name is same as parameter type name by default
- method := reqTyp
- if rpc.Stream {
- // use Dump as prefix instead of suffix for stream services
- if m := strings.TrimSuffix(method, "Dump"); method != m {
- method = "Dump" + m
- }
- }
-
- params := fmt.Sprintf("in *%s", reqTyp)
- returns := "error"
-
- if replyType := camelCaseName(rpc.ReplyMsg); replyType != "" {
- var replyTyp string
- if rpc.Stream {
- replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
- } else {
- replyTyp = fmt.Sprintf("*%s", replyType)
- }
- returns = fmt.Sprintf("(%s, error)", replyTyp)
- }
-
- fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
-}
diff --git a/binapigen/generate_test.go b/binapigen/generate_test.go
index 46cc5eb..2fa5dc6 100644
--- a/binapigen/generate_test.go
+++ b/binapigen/generate_test.go
@@ -15,43 +15,35 @@
package binapigen
import (
- "git.fd.io/govpp.git/examples/binapi/interfaces"
- "git.fd.io/govpp.git/examples/binapi/ip_types"
"os"
- "strings"
"testing"
. "github.com/onsi/gomega"
+ "git.fd.io/govpp.git/binapi/ip_types"
"git.fd.io/govpp.git/binapigen/vppapi"
)
-const testOutputDir = "test_output_directory"
+const testOutputDir = "test_output_dir"
-func GenerateFromFile(file, outputDir string, opts Options) error {
+func GenerateFromFile(file string, opts Options) error {
apifile, err := vppapi.ParseFile(file)
if err != nil {
return err
}
-
- g, err := New(opts, []*vppapi.File{apifile})
+ gen, err := New(opts, []*vppapi.File{apifile}, nil)
if err != nil {
return err
}
- for _, file := range g.Files {
+ for _, file := range gen.Files {
if !file.Generate {
continue
}
- GenerateBinapi(g, file, outputDir)
- if file.Service != nil {
- GenerateRPC(g, file, outputDir)
- }
+ GenerateAPI(gen, file)
}
-
- if err = g.Generate(); err != nil {
+ if err = gen.Generate(); err != nil {
return err
}
-
return nil
}
@@ -61,7 +53,8 @@ func TestGenerateFromFile(t *testing.T) {
// remove directory created during test
defer os.RemoveAll(testOutputDir)
- err := GenerateFromFile("vppapi/testdata/acl.api.json", testOutputDir, Options{FilesToGenerate: []string{"acl"}})
+ opts := Options{OutputDir: testOutputDir}
+ err := GenerateFromFile("vppapi/testdata/acl.api.json", opts)
Expect(err).ShouldNot(HaveOccurred())
fileInfo, err := os.Stat(testOutputDir + "/acl/acl.ba.go")
Expect(err).ShouldNot(HaveOccurred())
@@ -72,7 +65,8 @@ func TestGenerateFromFile(t *testing.T) {
func TestGenerateFromFileInputError(t *testing.T) {
RegisterTestingT(t)
- err := GenerateFromFile("vppapi/testdata/nonexisting.json", testOutputDir, Options{})
+ opts := Options{OutputDir: testOutputDir}
+ err := GenerateFromFile("vppapi/testdata/nonexisting.json", opts)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("unsupported"))
}
@@ -80,7 +74,8 @@ func TestGenerateFromFileInputError(t *testing.T) {
func TestGenerateFromFileReadJsonError(t *testing.T) {
RegisterTestingT(t)
- err := GenerateFromFile("vppapi/testdata/input-read-json-error.json", testOutputDir, Options{})
+ opts := Options{OutputDir: testOutputDir}
+ err := GenerateFromFile("vppapi/testdata/input-read-json-error.json", opts)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("unsupported"))
}
@@ -96,139 +91,23 @@ func TestGenerateFromFileGeneratePackageError(t *testing.T) {
os.RemoveAll(testOutputDir)
}()
- err := GenerateFromFile("vppapi/testdata/input-generate-error.json", testOutputDir, Options{})
- Expect(err).Should(HaveOccurred())
-}
-
-func TestGeneratedParseAddress(t *testing.T) {
- RegisterTestingT(t)
-
- var data = []struct {
- input string
- result ip_types.Address
- }{
- {"192.168.0.1", ip_types.Address{
- Af: ip_types.ADDRESS_IP4,
- Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
- }},
- {"aac1:0:ab45::", ip_types.Address{
- Af: ip_types.ADDRESS_IP6,
- Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
- }},
- }
-
- for _, entry := range data {
- t.Run(entry.input, func(t *testing.T) {
- parsedAddress, err := ip_types.ParseAddress(entry.input)
- Expect(err).ShouldNot(HaveOccurred())
- Expect(parsedAddress).To(Equal(entry.result))
-
- originAddress := parsedAddress.ToString()
- Expect(originAddress).To(Equal(entry.input))
- })
- }
-}
-
-func TestGeneratedParseAddressError(t *testing.T) {
- RegisterTestingT(t)
-
- _, err := ip_types.ParseAddress("malformed_ip")
+ opts := Options{OutputDir: testOutputDir}
+ err := GenerateFromFile("vppapi/testdata/input-generate-error.json", opts)
Expect(err).Should(HaveOccurred())
}
-func TestGeneratedParsePrefix(t *testing.T) {
+func TestAddress(t *testing.T) {
RegisterTestingT(t)
- var data = []struct {
- input string
- result ip_types.Prefix
- }{
- {"192.168.0.1/24", ip_types.Prefix{
- Address: ip_types.Address{
- Af: ip_types.ADDRESS_IP4,
- Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
- },
- Len: 24,
- }},
- {"192.168.0.1", ip_types.Prefix{
- Address: ip_types.Address{
- Af: ip_types.ADDRESS_IP4,
- Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
- },
- Len: 32,
- }},
- {"aac1:0:ab45::/96", ip_types.Prefix{
- Address: ip_types.Address{
- Af: ip_types.ADDRESS_IP6,
- Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
- },
- Len: 96,
- }},
- {"aac1:0:ab45::", ip_types.Prefix{
- Address: ip_types.Address{
- Af: ip_types.ADDRESS_IP6,
- Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
- },
- Len: 128,
- }},
- }
+ addr := ip_types.AddressUnionIP4(ip_types.IP4Address{10, 20, 0, 1})
+ t.Logf("addr: %v (%#v)", addr, addr)
- for _, entry := range data {
- t.Run(entry.input, func(t *testing.T) {
- parsedAddress, err := ip_types.ParsePrefix(entry.input)
- Expect(err).ShouldNot(HaveOccurred())
- Expect(parsedAddress).To(Equal(entry.result))
+ ip4 := addr.GetIP4()
+ t.Logf("ip4: %v", ip4)
+ addr.SetIP4(ip_types.IP4Address{192, 168, 1, 1})
+ t.Logf("ip4: %v", addr.GetIP4())
- // Parsed IP without prefix receives a default one
- // so the input data must be adjusted
- if entry.result.Address.Af == ip_types.ADDRESS_IP4 && !strings.Contains(entry.input, "/") {
- entry.input = entry.input + "/32"
- }
- if entry.result.Address.Af == ip_types.ADDRESS_IP6 && !strings.Contains(entry.input, "/") {
- entry.input = entry.input + "/128"
- }
- originAddress := parsedAddress.ToString()
- Expect(originAddress).To(Equal(entry.input))
- })
- }
-}
-
-func TestGeneratedParsePrefixError(t *testing.T) {
- RegisterTestingT(t)
-
- _, err := ip_types.ParsePrefix("malformed_ip")
- Expect(err).Should(HaveOccurred())
-}
-
-func TestGeneratedParseMAC(t *testing.T) {
- RegisterTestingT(t)
-
- var data = []struct {
- input string
- result interfaces.MacAddress
- }{
- {"b7:b9:bb:a1:5c:af", interfaces.MacAddress{183, 185, 187, 161, 92, 175}},
- {"47:4b:c7:3e:06:c8", interfaces.MacAddress{71, 75, 199, 62, 6, 200}},
- {"a7:cc:9f:10:18:e3", interfaces.MacAddress{167, 204, 159, 16, 24, 227}},
- }
-
- for _, entry := range data {
- t.Run(entry.input, func(t *testing.T) {
- parsedMac, err := interfaces.ParseMAC(entry.input)
- Expect(err).ShouldNot(HaveOccurred())
- Expect(parsedMac).To(Equal(entry.result))
-
- originAddress := parsedMac.ToString()
- Expect(originAddress).To(Equal(entry.input))
- })
- }
-}
-
-func TestGeneratedParseMACError(t *testing.T) {
- RegisterTestingT(t)
-
- _, err := interfaces.ParseMAC("malformed_mac")
- Expect(err).Should(HaveOccurred())
+ Expect(addr.GetIP4()).To(Equal(ip_types.IP4Address{192, 168, 1, 1}))
}
/*func TestGetContext(t *testing.T) {
@@ -280,7 +159,7 @@ func TestGetContextInterfaceJson(t *testing.T) {
// prepare writer
writer := bufio.NewWriter(outFile)
Expect(writer.Buffered()).To(BeZero())
- err = generateFileBinapi(testCtx, writer)
+ err = GenerateFileBinapi(testCtx, writer)
Expect(err).ShouldNot(HaveOccurred())
}
@@ -306,7 +185,7 @@ func TestGenerateMessageType(t *testing.T) {
writer := bufio.NewWriter(outFile)
for _, msg := range testCtx.file.Messages {
- generateMessage(testCtx, writer, &msg)
+ genMessage(testCtx, writer, &msg)
Expect(writer.Buffered()).ToNot(BeZero())
}
}*/
@@ -335,7 +214,7 @@ func TestGenerateMessageType(t *testing.T) {
for i := 0; i < types.Len(); i++ {
typ := types.At(i)
Expect(writer.Buffered()).To(BeZero())
- err := generateMessage(testCtx, writer, typ, false)
+ err := genMessage(testCtx, writer, typ, false)
Expect(err).ShouldNot(HaveOccurred())
Expect(writer.Buffered()).ToNot(BeZero())
@@ -446,7 +325,7 @@ func TestGeneratePackageHeader(t *testing.T) {
// prepare writer
writer := bufio.NewWriter(outFile)
Expect(writer.Buffered()).To(BeZero())
- generatePackageHeader(testCtx, writer, inFile)
+ genPackageComment(testCtx, writer, inFile)
Expect(writer.Buffered()).ToNot(BeZero())
}
diff --git a/binapigen/generator.go b/binapigen/generator.go
index 07c1b13..e42e7fb 100644
--- a/binapigen/generator.go
+++ b/binapigen/generator.go
@@ -15,130 +15,122 @@
package binapigen
import (
+ "bufio"
"bytes"
"fmt"
+ "go/ast"
"go/format"
+ "go/parser"
+ "go/printer"
+ "go/token"
"io/ioutil"
"os"
"path"
"path/filepath"
- "regexp"
+ "sort"
+ "strconv"
+ "strings"
"github.com/sirupsen/logrus"
"git.fd.io/govpp.git/binapigen/vppapi"
)
-type Options struct {
- VPPVersion string // version of VPP that produced API files
-
- FilesToGenerate []string // list of API files to generate
-
- ImportPrefix string // defines import path prefix for importing types
- ImportTypes bool // generate packages for import types
- IncludeAPIVersion bool // include constant with API version string
- IncludeComments bool // include parts of original source in comments
- IncludeBinapiNames bool // include binary API names as struct tag
- IncludeServices bool // include service interface with client implementation
- IncludeVppVersion bool // include info about used VPP version
-}
-
type Generator struct {
- Options
-
Files []*File
- FilesByPath map[string]*File
FilesByName map[string]*File
- enumsByName map[string]*Enum
- aliasesByName map[string]*Alias
- structsByName map[string]*Struct
- unionsByName map[string]*Union
+ opts Options
+ apifiles []*vppapi.File
+ vppVersion string
+
+ filesToGen []string
+ genfiles []*GenFile
- genfiles []*GenFile
+ enumsByName map[string]*Enum
+ aliasesByName map[string]*Alias
+ structsByName map[string]*Struct
+ unionsByName map[string]*Union
+ messagesByName map[string]*Message
}
-func New(opts Options, apifiles []*vppapi.File) (*Generator, error) {
- g := &Generator{
- Options: opts,
- FilesByPath: make(map[string]*File),
- FilesByName: make(map[string]*File),
- enumsByName: map[string]*Enum{},
- aliasesByName: map[string]*Alias{},
- structsByName: map[string]*Struct{},
- unionsByName: map[string]*Union{},
+func New(opts Options, apifiles []*vppapi.File, filesToGen []string) (*Generator, error) {
+ gen := &Generator{
+ FilesByName: make(map[string]*File),
+ opts: opts,
+ apifiles: apifiles,
+ filesToGen: filesToGen,
+ enumsByName: map[string]*Enum{},
+ aliasesByName: map[string]*Alias{},
+ structsByName: map[string]*Struct{},
+ unionsByName: map[string]*Union{},
+ messagesByName: map[string]*Message{},
}
- logrus.Debugf("adding %d VPP API files to generator", len(apifiles))
+ // Normalize API files
+ SortFilesByImports(gen.apifiles)
for _, apifile := range apifiles {
- filename := apifile.Path
- if filename == "" {
- filename = apifile.Name
- }
- if _, ok := g.FilesByPath[filename]; ok {
- return nil, fmt.Errorf("duplicate file name: %q", filename)
- }
- if _, ok := g.FilesByName[apifile.Name]; ok {
+ RemoveImportedTypes(gen.apifiles, apifile)
+ SortFileObjectsByName(apifile)
+ }
+
+ // prepare package names and import paths
+ packageNames := make(map[string]GoPackageName)
+ importPaths := make(map[string]GoImportPath)
+ for _, apifile := range gen.apifiles {
+ filename := getFilename(apifile)
+ packageNames[filename] = cleanPackageName(apifile.Name)
+ importPaths[filename] = GoImportPath(path.Join(gen.opts.ImportPrefix, baseName(apifile.Name)))
+ }
+
+ logrus.Debugf("adding %d VPP API files to generator", len(gen.apifiles))
+
+ for _, apifile := range gen.apifiles {
+ filename := getFilename(apifile)
+
+ if _, ok := gen.FilesByName[apifile.Name]; ok {
return nil, fmt.Errorf("duplicate file: %q", apifile.Name)
}
- file, err := newFile(g, apifile)
+ file, err := newFile(gen, apifile, packageNames[filename], importPaths[filename])
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("loading file %s failed: %w", apifile.Name, err)
}
- g.Files = append(g.Files, file)
- g.FilesByPath[filename] = file
- g.FilesByName[apifile.Name] = file
+ gen.Files = append(gen.Files, file)
+ gen.FilesByName[apifile.Name] = file
logrus.Debugf("added file %q (path: %v)", apifile.Name, apifile.Path)
- if len(file.Imports) > 0 {
- logrus.Debugf(" - %d imports: %v", len(file.Imports), file.Imports)
- }
}
- if len(opts.FilesToGenerate) > 0 {
- logrus.Debugf("Checking %d files to generate: %v", len(opts.FilesToGenerate), opts.FilesToGenerate)
- for _, genfile := range opts.FilesToGenerate {
- file, ok := g.FilesByPath[genfile]
+ // mark files for generation
+ if len(gen.filesToGen) > 0 {
+ logrus.Debugf("Checking %d files to generate: %v", len(gen.filesToGen), gen.filesToGen)
+ for _, genfile := range gen.filesToGen {
+ file, ok := gen.FilesByName[genfile]
if !ok {
- file, ok = g.FilesByName[genfile]
- if !ok {
- return nil, fmt.Errorf("no API file found for: %v", genfile)
- }
+ return nil, fmt.Errorf("no API file found for: %v", genfile)
}
file.Generate = true
- if opts.ImportTypes {
- // generate all imported files
- for _, impFile := range file.importedFiles(g) {
- impFile.Generate = true
- }
+ // generate all imported files
+ for _, impFile := range file.importedFiles(gen) {
+ impFile.Generate = true
}
}
} else {
- logrus.Debugf("Files to generate not specified, marking all %d files to generate", len(g.Files))
- for _, file := range g.Files {
+ logrus.Debugf("Files to generate not specified, marking all %d files for generate", len(gen.Files))
+ for _, file := range gen.Files {
file.Generate = true
}
}
- logrus.Debugf("Resolving imported types")
- for _, file := range g.Files {
- if !file.Generate {
- // skip resolving for non-generated files
- continue
- }
- var importedFiles []*File
- for _, impFile := range file.importedFiles(g) {
- if !impFile.Generate {
- // exclude imports of non-generated files
- continue
- }
- importedFiles = append(importedFiles, impFile)
- }
- file.loadTypeImports(g, importedFiles)
- }
+ return gen, nil
+}
- return g, nil
+func getFilename(file *vppapi.File) string {
+ if file.Path == "" {
+ return file.Name
+ }
+ return file.Path
}
func (g *Generator) Generate() error {
@@ -147,127 +139,238 @@ func (g *Generator) Generate() error {
}
logrus.Infof("Generating %d files", len(g.genfiles))
+
for _, genfile := range g.genfiles {
- if err := writeSourceTo(genfile.filename, genfile.Content()); err != nil {
- return fmt.Errorf("writing source for RPC package %s failed: %v", genfile.filename, err)
+ content, err := genfile.Content()
+ if err != nil {
+ return err
+ }
+ if err := writeSourceTo(genfile.filename, content); err != nil {
+ return fmt.Errorf("writing source package %s failed: %v", genfile.filename, err)
}
}
return nil
}
type GenFile struct {
- *Generator
- filename string
- file *File
- outputDir string
- buf bytes.Buffer
+ gen *Generator
+ file *File
+ filename string
+ goImportPath GoImportPath
+ buf bytes.Buffer
+ manualImports map[GoImportPath]bool
+ packageNames map[GoImportPath]GoPackageName
}
-func (g *Generator) NewGenFile(filename string) *GenFile {
+func (g *Generator) NewGenFile(filename string, importPath GoImportPath) *GenFile {
f := &GenFile{
- Generator: g,
- filename: filename,
+ gen: g,
+ filename: filename,
+ goImportPath: importPath,
+ manualImports: make(map[GoImportPath]bool),
+ packageNames: make(map[GoImportPath]GoPackageName),
}
g.genfiles = append(g.genfiles, f)
return f
}
-func (f *GenFile) Content() []byte {
- return f.buf.Bytes()
+func (g *GenFile) Write(p []byte) (n int, err error) {
+ return g.buf.Write(p)
}
-func writeSourceTo(outputFile string, b []byte) error {
- // create output directory
- packageDir := filepath.Dir(outputFile)
- if err := os.MkdirAll(packageDir, 0775); err != nil {
- return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
- }
+func (g *GenFile) Import(importPath GoImportPath) {
+ g.manualImports[importPath] = true
+}
- // format generated source code
- gosrc, err := format.Source(b)
- if err != nil {
- _ = ioutil.WriteFile(outputFile, b, 0666)
- return fmt.Errorf("formatting source code failed: %v", err)
+func (g *GenFile) GoIdent(ident GoIdent) string {
+ if ident.GoImportPath == g.goImportPath {
+ return ident.GoName
}
-
- // write generated code to output file
- if err := ioutil.WriteFile(outputFile, gosrc, 0666); err != nil {
- return fmt.Errorf("writing to output file %s failed: %v", outputFile, err)
+ if packageName, ok := g.packageNames[ident.GoImportPath]; ok {
+ return string(packageName) + "." + ident.GoName
}
+ packageName := cleanPackageName(baseName(string(ident.GoImportPath)))
+ g.packageNames[ident.GoImportPath] = packageName
+ return string(packageName) + "." + ident.GoName
+}
- lines := bytes.Count(gosrc, []byte("\n"))
- logf("wrote %d lines (%d bytes) of code to: %q", lines, len(gosrc), outputFile)
+func (g *GenFile) P(v ...interface{}) {
+ for _, x := range v {
+ switch x := x.(type) {
+ case GoIdent:
+ fmt.Fprint(&g.buf, g.GoIdent(x))
+ default:
+ fmt.Fprint(&g.buf, x)
+ }
+ }
+ fmt.Fprintln(&g.buf)
+}
- return nil
+func (g *GenFile) Content() ([]byte, error) {
+ if !strings.HasSuffix(g.filename, ".go") {
+ return g.buf.Bytes(), nil
+ }
+ return g.injectImports(g.buf.Bytes())
}
-func listImports(genfile *GenFile) map[string]string {
- var importPath = genfile.ImportPrefix
- if importPath == "" {
- importPath = resolveImportPath(genfile.outputDir)
- logrus.Debugf("resolved import path: %s", importPath)
+func (g *GenFile) injectImports(original []byte) ([]byte, error) {
+ // Parse source code
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, "", original, parser.ParseComments)
+ if err != nil {
+ var src bytes.Buffer
+ s := bufio.NewScanner(bytes.NewReader(original))
+ for line := 1; s.Scan(); line++ {
+ fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes())
+ }
+ return nil, fmt.Errorf("%v: unparsable Go source: %v\n%v", g.filename, err, src.String())
+ }
+ type Import struct {
+ Name string
+ Path string
+ }
+ // Prepare list of all imports
+ var importPaths []Import
+ for importPath := range g.packageNames {
+ importPaths = append(importPaths, Import{
+ Name: string(g.packageNames[GoImportPath(importPath)]),
+ Path: string(importPath),
+ })
}
- imports := map[string]string{}
- for _, imp := range genfile.file.imports {
- if _, ok := imports[imp]; !ok {
- imports[imp] = path.Join(importPath, imp)
+ for importPath := range g.manualImports {
+ if _, ok := g.packageNames[importPath]; ok {
+ continue
}
+ importPaths = append(importPaths, Import{
+ Name: "_",
+ Path: string(importPath),
+ })
}
- return imports
-}
+ // Sort imports by import path
+ sort.Slice(importPaths, func(i, j int) bool {
+ return importPaths[i].Path < importPaths[j].Path
+ })
+ // Inject new import block into parsed AST
+ if len(importPaths) > 0 {
+ // Find import block position
+ pos := file.Package
+ tokFile := fset.File(file.Package)
+ pkgLine := tokFile.Line(file.Package)
+ for _, c := range file.Comments {
+ if tokFile.Line(c.Pos()) > pkgLine {
+ break
+ }
+ pos = c.End()
+ }
+ // Prepare the import block
+ impDecl := &ast.GenDecl{Tok: token.IMPORT, TokPos: pos, Lparen: pos, Rparen: pos}
+ for _, importPath := range importPaths {
+ var name *ast.Ident
+ if importPath.Name == "_" || strings.Contains(importPath.Path, ".") {
+ name = &ast.Ident{Name: importPath.Name, NamePos: pos}
+ }
+ impDecl.Specs = append(impDecl.Specs, &ast.ImportSpec{
+ Name: name,
+ Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(importPath.Path), ValuePos: pos},
+ EndPos: pos,
+ })
+ }
-func resolveImportPath(outputDir string) string {
- absPath, err := filepath.Abs(outputDir)
- if err != nil {
- panic(err)
+ file.Decls = append([]ast.Decl{impDecl}, file.Decls...)
}
- modRoot := findModuleRoot(absPath)
- if modRoot == "" {
- logrus.Fatalf("module root not found at: %s", absPath)
+ // Reformat source code
+ var out bytes.Buffer
+ cfg := &printer.Config{
+ Mode: printer.TabIndent | printer.UseSpaces,
+ Tabwidth: 8,
}
- modPath := findModulePath(path.Join(modRoot, "go.mod"))
- if modPath == "" {
- logrus.Fatalf("module path not found")
+ if err = cfg.Fprint(&out, fset, file); err != nil {
+ return nil, fmt.Errorf("%v: can not reformat Go source: %v", g.filename, err)
}
- relDir, err := filepath.Rel(modRoot, absPath)
- if err != nil {
- panic(err)
+ return out.Bytes(), nil
+}
+
+// GoIdent is a Go identifier, consisting of a name and import path.
+// The name is a single identifier and may not be a dot-qualified selector.
+type GoIdent struct {
+ GoName string
+ GoImportPath GoImportPath
+}
+
+func (id GoIdent) String() string {
+ return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName)
+}
+
+func newGoIdent(f *File, fullName string) GoIdent {
+ name := strings.TrimPrefix(fullName, string(f.PackageName)+".")
+ return GoIdent{
+ GoName: camelCaseName(name),
+ GoImportPath: f.GoImportPath,
}
- return filepath.Join(modPath, relDir)
}
-func findModuleRoot(dir string) (root string) {
- if dir == "" {
- panic("dir not set")
+// GoImportPath is a Go import path for a package.
+type GoImportPath string
+
+func (p GoImportPath) String() string {
+ return strconv.Quote(string(p))
+}
+
+func (p GoImportPath) Ident(s string) GoIdent {
+ return GoIdent{GoName: s, GoImportPath: p}
+}
+
+type GoPackageName string
+
+func cleanPackageName(name string) GoPackageName {
+ return GoPackageName(sanitizedName(name))
+}
+
+func sanitizedName(name string) string {
+ switch name {
+ case "interface":
+ return "interfaces"
+ case "map":
+ return "maps"
+ default:
+ return name
}
- dir = filepath.Clean(dir)
+}
- // Look for enclosing go.mod.
- for {
- if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
- return dir
- }
- d := filepath.Dir(dir)
- if d == dir {
- break
- }
- dir = d
+// baseName returns the last path element of the name, with the last dotted suffix removed.
+func baseName(name string) string {
+ // First, find the last element
+ if i := strings.LastIndex(name, "/"); i >= 0 {
+ name = name[i+1:]
+ }
+ // Now drop the suffix
+ if i := strings.LastIndex(name, "."); i >= 0 {
+ name = name[:i]
}
- return ""
+ return name
}
-var (
- modulePathRE = regexp.MustCompile(`module[ \t]+([^ \t\r\n]+)`)
-)
+func writeSourceTo(outputFile string, b []byte) error {
+ // create output directory
+ packageDir := filepath.Dir(outputFile)
+ if err := os.MkdirAll(packageDir, 0775); err != nil {
+ return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
+ }
-func findModulePath(file string) string {
- data, err := ioutil.ReadFile(file)
+ // format generated source code
+ gosrc, err := format.Source(b)
if err != nil {
- return ""
+ _ = ioutil.WriteFile(outputFile, b, 0666)
+ return fmt.Errorf("formatting source code failed: %v", err)
}
- m := modulePathRE.FindSubmatch(data)
- if m == nil {
- return ""
+
+ // write generated code to output file
+ if err := ioutil.WriteFile(outputFile, gosrc, 0666); err != nil {
+ return fmt.Errorf("writing to output file %s failed: %v", outputFile, err)
}
- return string(m[1])
+
+ lines := bytes.Count(gosrc, []byte("\n"))
+ logf("wrote %d lines (%d bytes) to: %q", lines, len(gosrc), outputFile)
+
+ return nil
}
diff --git a/binapigen/generator_test.go b/binapigen/generator_test.go
index 9e5b342..aa4ee04 100644
--- a/binapigen/generator_test.go
+++ b/binapigen/generator_test.go
@@ -18,10 +18,10 @@ import (
"testing"
)
-func TestModule(t *testing.T) {
- const expected = "git.fd.io/govpp.git/examples/binapi"
+func TestGoModule(t *testing.T) {
+ const expected = "git.fd.io/govpp.git/binapi"
- impPath := resolveImportPath("../examples/binapi")
+ impPath := resolveImportPath("../binapi")
if impPath != expected {
t.Fatalf("expected: %q, got: %q", expected, impPath)
}
@@ -42,78 +42,10 @@ func TestBinapiTypeSizes(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
- size := getBinapiTypeSize(test.input)
+ size := getSizeOfBinapiTypeLength(test.input, 1)
if size != test.expsize {
t.Errorf("expected %d, got %d", test.expsize, size)
}
})
}
}
-
-/*func TestSizeOfType(t *testing.T) {
- tests := []struct {
- name string
- input StructType
- expsize int
- }{
- {
- name: "basic1",
- input: StructType{
- Fields: []Field{
- {Type: "u8"},
- },
- },
- expsize: 1,
- },
- {
- name: "basic2",
- input: Type{
- Fields: []Field{
- {Type: "u8", Length: 4},
- },
- },
- expsize: 4,
- },
- {
- name: "basic3",
- input: Type{
- Fields: []Field{
- {Type: "u8", Length: 16},
- },
- },
- expsize: 16,
- },
- {
- name: "withEnum",
- input: Type{
- Fields: []Field{
- {Type: "u16"},
- {Type: "vl_api_myenum_t"},
- },
- },
- expsize: 6,
- },
- {
- name: "invalid1",
- input: Type{
- Fields: []Field{
- {Type: "x", Length: 16},
- },
- },
- expsize: 0,
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- module := &File{
- Enums: []Enum{
- {Name: "myenum", Type: "u32"},
- },
- }
- size := getSizeOfType(module, &test.input)
- if size != test.expsize {
- t.Errorf("expected %d, got %d", test.expsize, size)
- }
- })
- }
-}*/
diff --git a/binapigen/plugin.go b/binapigen/plugin.go
new file mode 100644
index 0000000..b57cc68
--- /dev/null
+++ b/binapigen/plugin.go
@@ -0,0 +1,51 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+import "fmt"
+
+type Plugin struct {
+ Name string
+ GenerateFile func(*Generator, *File) *GenFile
+}
+
+var Plugins = map[string]*Plugin{}
+var plugins []*Plugin
+
+func RegisterPlugin(name string, genfn func(*Generator, *File) *GenFile) {
+ if name == "" {
+ panic("plugin name empty")
+ }
+ for _, p := range plugins {
+ if p.Name == name {
+ panic("duplicate plugin name: " + name)
+ }
+ }
+ plugin := &Plugin{
+ Name: name,
+ GenerateFile: genfn,
+ }
+ plugins = append(plugins, plugin)
+ Plugins[name] = plugin
+}
+
+func RunPlugin(name string, gen *Generator, file *File) error {
+ p, ok := Plugins[name]
+ if !ok {
+ return fmt.Errorf("plugin not found: %q", name)
+ }
+ p.GenerateFile(gen, file)
+ return nil
+}
diff --git a/binapigen/run.go b/binapigen/run.go
index e6086ee..88e32b7 100644
--- a/binapigen/run.go
+++ b/binapigen/run.go
@@ -16,69 +16,145 @@ package binapigen
import (
"fmt"
+ "io/ioutil"
"os"
+ "path"
"path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/sirupsen/logrus"
"git.fd.io/govpp.git/binapigen/vppapi"
)
-const (
- outputFileExt = ".ba.go" // file extension of the Go generated files
- rpcFileSuffix = "_rpc" // file name suffix for the RPC services
-)
+type Options struct {
+ OutputDir string // output directory for generated files
+ ImportPrefix string // prefix for import paths
+ NoVersionInfo bool // disables generating version info
+}
-func Run(apiDir string, opts Options, f func(*Generator) error) {
- if err := run(apiDir, opts, f); err != nil {
+func Run(apiDir string, filesToGenerate []string, opts Options, f func(*Generator) error) {
+ if err := run(apiDir, filesToGenerate, opts, f); err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err)
os.Exit(1)
}
}
-func run(apiDir string, opts Options, f func(*Generator) error) error {
- // parse API files
+func run(apiDir string, filesToGenerate []string, opts Options, fn func(*Generator) error) error {
apifiles, err := vppapi.ParseDir(apiDir)
if err != nil {
return err
}
- g, err := New(opts, apifiles)
+ if opts.ImportPrefix == "" {
+ opts.ImportPrefix = resolveImportPath(opts.OutputDir)
+ logrus.Debugf("resolved import prefix: %s", opts.ImportPrefix)
+ }
+
+ gen, err := New(opts, apifiles, filesToGenerate)
if err != nil {
return err
}
- if err := f(g); err != nil {
- return err
+ gen.vppVersion = vppapi.ResolveVPPVersion(apiDir)
+ if gen.vppVersion == "" {
+ gen.vppVersion = "unknown"
}
- if err = g.Generate(); err != nil {
+ if fn == nil {
+ GenerateDefault(gen)
+ } else {
+ if err := fn(gen); err != nil {
+ return err
+ }
+ }
+
+ if err = gen.Generate(); err != nil {
return err
}
return nil
}
-func GenerateBinapi(gen *Generator, file *File, outputDir string) *GenFile {
- packageDir := filepath.Join(outputDir, file.PackageName)
- filename := filepath.Join(packageDir, file.PackageName+outputFileExt)
+func GenerateDefault(gen *Generator) {
+ for _, file := range gen.Files {
+ if !file.Generate {
+ continue
+ }
+ GenerateAPI(gen, file)
+ GenerateRPC(gen, file)
+ }
+}
- g := gen.NewGenFile(filename)
- g.file = file
- g.outputDir = outputDir
+var Logger = logrus.New()
- generateFileBinapi(g, &g.buf)
+func init() {
+ if debug := os.Getenv("DEBUG_GOVPP"); strings.Contains(debug, "binapigen") {
+ Logger.SetLevel(logrus.DebugLevel)
+ logrus.SetLevel(logrus.DebugLevel)
+ } else if debug != "" {
+ Logger.SetLevel(logrus.InfoLevel)
+ } else {
+ Logger.SetLevel(logrus.WarnLevel)
+ }
+}
- return g
+func logf(f string, v ...interface{}) {
+ Logger.Debugf(f, v...)
}
-func GenerateRPC(gen *Generator, file *File, outputDir string) *GenFile {
- packageDir := filepath.Join(outputDir, file.PackageName)
- filename := filepath.Join(packageDir, file.PackageName+rpcFileSuffix+outputFileExt)
+func resolveImportPath(dir string) string {
+ absPath, err := filepath.Abs(dir)
+ if err != nil {
+ panic(err)
+ }
+ modRoot := findGoModuleRoot(absPath)
+ if modRoot == "" {
+ logrus.Fatalf("module root not found at: %s", absPath)
+ }
+ modPath := findModulePath(path.Join(modRoot, "go.mod"))
+ if modPath == "" {
+ logrus.Fatalf("module path not found")
+ }
+ relDir, err := filepath.Rel(modRoot, absPath)
+ if err != nil {
+ panic(err)
+ }
+ return filepath.Join(modPath, relDir)
+}
- g := gen.NewGenFile(filename)
- g.file = file
- g.outputDir = outputDir
+func findGoModuleRoot(dir string) (root string) {
+ if dir == "" {
+ panic("dir not set")
+ }
+ dir = filepath.Clean(dir)
+ // Look for enclosing go.mod.
+ for {
+ if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
+ return dir
+ }
+ d := filepath.Dir(dir)
+ if d == dir {
+ break
+ }
+ dir = d
+ }
+ return ""
+}
- generateFileRPC(g, &g.buf)
+var (
+ modulePathRE = regexp.MustCompile(`module[ \t]+([^ \t\r\n]+)`)
+)
- return g
+func findModulePath(file string) string {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return ""
+ }
+ m := modulePathRE.FindSubmatch(data)
+ if m == nil {
+ return ""
+ }
+ return string(m[1])
}
diff --git a/binapigen/types.go b/binapigen/types.go
index 96ae870..0a21622 100644
--- a/binapigen/types.go
+++ b/binapigen/types.go
@@ -27,53 +27,33 @@ const (
defineApiSuffix = "_t"
)
-// BaseType represents base types in VPP binary API.
-type BaseType int
+// toApiType returns name that is used as type reference in VPP binary API
+func toApiType(name string) string {
+ return defineApiPrefix + name + defineApiSuffix
+}
-const (
- U8 BaseType = iota + 1
- I8
- U16
- I16
- U32
- I32
- U64
- I64
- F64
- BOOL
- STRING
-)
+func fromApiType(typ string) string {
+ name := typ
+ name = strings.TrimPrefix(name, defineApiPrefix)
+ name = strings.TrimSuffix(name, defineApiSuffix)
+ return name
+}
-var (
- BaseTypes = map[BaseType]string{
- U8: "u8",
- I8: "i8",
- U16: "u16",
- I16: "i16",
- U32: "u32",
- I32: "i32",
- U64: "u64",
- I64: "i64",
- F64: "f64",
- BOOL: "bool",
- STRING: "string",
- }
- BaseTypeNames = map[string]BaseType{
- "u8": U8,
- "i8": I8,
- "u16": U16,
- "i16": I16,
- "u32": U32,
- "i32": I32,
- "u64": U64,
- "i64": I64,
- "f64": F64,
- "bool": BOOL,
- "string": STRING,
- }
+const (
+ U8 = "u8"
+ I8 = "i8"
+ U16 = "u16"
+ I16 = "i16"
+ U32 = "u32"
+ I32 = "i32"
+ U64 = "u64"
+ I64 = "i64"
+ F64 = "f64"
+ BOOL = "bool"
+ STRING = "string"
)
-var BaseTypeSizes = map[BaseType]int{
+var BaseTypeSizes = map[string]int{
U8: 1,
I8: 1,
U16: 2,
@@ -87,106 +67,7 @@ var BaseTypeSizes = map[BaseType]int{
STRING: 1,
}
-type Kind int
-
-const (
- _ = iota
- Uint8Kind
- Int8Kind
- Uint16Kind
- Int16Kind
- Uint32Kind
- Int32Kind
- Uint64Kind
- Int64Kind
- Float64Kind
- BoolKind
- StringKind
- EnumKind
- AliasKind
- StructKind
- UnionKind
- MessageKind
-)
-
-// toApiType returns name that is used as type reference in VPP binary API
-func toApiType(name string) string {
- return defineApiPrefix + name + defineApiSuffix
-}
-
-func fromApiType(typ string) string {
- name := typ
- name = strings.TrimPrefix(name, defineApiPrefix)
- name = strings.TrimSuffix(name, defineApiSuffix)
- return name
-}
-
-func getSizeOfType(module *File, typ *Struct) (size int) {
- for _, field := range typ.Fields {
- enum := getEnumByRef(module, field.Type)
- if enum != nil {
- size += getSizeOfBinapiTypeLength(enum.Type, field.Length)
- continue
- }
- size += getSizeOfBinapiTypeLength(field.Type, field.Length)
- }
- return size
-}
-
-func getEnumByRef(file *File, ref string) *Enum {
- for _, typ := range file.Enums {
- if ref == toApiType(typ.Name) {
- return typ
- }
- }
- return nil
-}
-
-func getTypeByRef(file *File, ref string) *Struct {
- for _, typ := range file.Structs {
- if ref == toApiType(typ.Name) {
- return typ
- }
- }
- return nil
-}
-
-func getAliasByRef(file *File, ref string) *Alias {
- for _, alias := range file.Aliases {
- if ref == toApiType(alias.Name) {
- return alias
- }
- }
- return nil
-}
-
-func getUnionByRef(file *File, ref string) *Union {
- for _, union := range file.Unions {
- if ref == toApiType(union.Name) {
- return union
- }
- }
- return nil
-}
-
-func getBinapiTypeSize(binapiType string) (size int) {
- typName := BaseTypeNames[binapiType]
- return BaseTypeSizes[typName]
-}
-
-// binapiTypes is a set of types used VPP binary API for translation to Go types
-var binapiTypes = map[string]string{
- "u8": "uint8",
- "i8": "int8",
- "u16": "uint16",
- "i16": "int16",
- "u32": "uint32",
- "i32": "int32",
- "u64": "uint64",
- "i64": "int64",
- "f64": "float64",
-}
-var BaseTypesGo = map[BaseType]string{
+var BaseTypesGo = map[string]string{
U8: "uint8",
I8: "int8",
U16: "uint16",
@@ -200,82 +81,90 @@ var BaseTypesGo = map[BaseType]string{
STRING: "string",
}
-func getActualType(file *File, typ string) (actual string) {
- for _, enum := range file.Enums {
- if enum.GoName == typ {
- return enum.Type
- }
+func fieldActualType(field *Field) (actual string) {
+ switch {
+ case field.TypeAlias != nil:
+ actual = field.TypeAlias.Type
+ case field.TypeEnum != nil:
+ actual = field.TypeEnum.Type
}
- for _, alias := range file.Aliases {
- if alias.GoName == typ {
- return alias.Type
- }
+ return field.Type
+}
+
+func fieldGoType(g *GenFile, field *Field) string {
+ switch {
+ case field.TypeAlias != nil:
+ return g.GoIdent(field.TypeAlias.GoIdent)
+ case field.TypeEnum != nil:
+ return g.GoIdent(field.TypeEnum.GoIdent)
+ case field.TypeStruct != nil:
+ return g.GoIdent(field.TypeStruct.GoIdent)
+ case field.TypeUnion != nil:
+ return g.GoIdent(field.TypeUnion.GoIdent)
+ }
+ t, ok := BaseTypesGo[field.Type]
+ if !ok {
+ logrus.Panicf("type %s is not base type", field.Type)
}
- return typ
+ return t
}
-// convertToGoType translates the VPP binary API type into Go type.
-// Imported types are with import prefix.
-func convertToGoType(file *File, binapiType string) (typ string) {
- if t, ok := binapiTypes[binapiType]; ok {
- // basic types
- typ = t
- } else if r, ok := file.refmap[binapiType]; ok {
- // specific types (enums/types/unions)
- var prefix string
- typ = camelCaseName(r)
- // look in imports using name and type name eventually
- if imp, ok := file.imports[typ]; ok {
- prefix = fmt.Sprintf("%s.", imp)
- } else if imp, ok := file.imports[fromApiType(binapiType)]; ok {
- prefix = fmt.Sprintf("%s.", imp)
+func getFieldType(g *GenFile, field *Field) string {
+ gotype := fieldGoType(g, field)
+ if field.Array {
+ switch gotype {
+ case "uint8":
+ return "[]byte"
+ case "string":
+ return "string"
}
- typ = fmt.Sprintf("%s%s", prefix, typ)
- } else {
- switch binapiType {
- case "bool", "string":
- typ = binapiType
- default:
- // fallback type
- logrus.Warnf("found unknown VPP binary API type %q, using byte", binapiType)
- typ = "byte"
+ if _, ok := BaseTypesGo[field.Type]; !ok && field.Length > 0 {
+ return fmt.Sprintf("[%d]%s", field.Length, gotype)
}
+ return "[]" + gotype
}
- return typ
+ return gotype
}
func getSizeOfBinapiTypeLength(typ string, length int) (size int) {
- if n := getBinapiTypeSize(typ); n > 0 {
+ if n := BaseTypeSizes[typ]; n > 0 {
if length > 0 {
return n * length
} else {
return n
}
}
-
return
}
-func getUnionSize(file *File, union *Union) (maxSize int) {
+func getSizeOfType(typ *Struct) (size int) {
+ for _, field := range typ.Fields {
+ if enum := field.TypeEnum; enum != nil {
+ size += getSizeOfBinapiTypeLength(enum.Type, field.Length)
+ continue
+ }
+ size += getSizeOfBinapiTypeLength(field.Type, field.Length)
+ }
+ return size
+}
+
+func getUnionSize(union *Union) (maxSize int) {
for _, field := range union.Fields {
- typ := getTypeByRef(file, field.Type)
- if typ != nil {
- if size := getSizeOfType(file, typ); size > maxSize {
+ if typ := field.TypeStruct; typ != nil {
+ if size := getSizeOfType(typ); size > maxSize {
maxSize = size
}
continue
}
- alias := getAliasByRef(file, field.Type)
- if alias != nil {
+ if alias := field.TypeAlias; alias != nil {
if size := getSizeOfBinapiTypeLength(alias.Type, alias.Length); size > maxSize {
maxSize = size
}
continue
} else {
- logf("no type or alias found for union %s field type %q", union.Name, field.Type)
- continue
+ logrus.Panicf("no type or alias found for union %s field type %q", union.Name, field.Type)
}
}
- logf("getUnionSize: %s %+v max=%v", union.Name, union.Fields, maxSize)
+ //logf("getUnionSize: %s %+v max=%v", union.Name, union.Fields, maxSize)
return
}
diff --git a/binapigen/validate.go b/binapigen/validate.go
deleted file mode 100644
index a79e148..0000000
--- a/binapigen/validate.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2020 Cisco and/or its affiliates.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at:
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package binapigen
-
-import (
- "strings"
-
- "github.com/sirupsen/logrus"
-
- "git.fd.io/govpp.git/binapigen/vppapi"
-)
-
-const (
- serviceEventPrefix = "want_"
- serviceDumpSuffix = "_dump"
- serviceDetailsSuffix = "_details"
- serviceReplySuffix = "_reply"
-)
-
-func validateService(svc vppapi.Service) {
- for _, rpc := range svc.RPCs {
- validateRPC(rpc)
- }
-}
-
-func validateRPC(rpc vppapi.RPC) {
- if len(rpc.Events) > 0 {
- // EVENT service
- if !strings.HasPrefix(rpc.RequestMsg, serviceEventPrefix) {
- logrus.Warnf("unusual EVENTS service: %+v\n"+
- "- events service %q does not have %q prefix in request.",
- rpc, rpc.Name, serviceEventPrefix)
- }
- } else if rpc.Stream {
- // STREAM service
- if !strings.HasSuffix(rpc.RequestMsg, serviceDumpSuffix) {
- logrus.Warnf("unusual STREAM service: %+v\n"+
- "- stream service %q does not have %q suffix in request.",
- rpc, rpc.Name, serviceDumpSuffix)
- }
- if !strings.HasSuffix(rpc.ReplyMsg, serviceDetailsSuffix) && !strings.HasSuffix(rpc.StreamMsg, serviceDetailsSuffix) {
- logrus.Warnf("unusual STREAM service: %+v\n"+
- "- stream service %q does not have %q suffix in reply or stream msg.",
- rpc, rpc.Name, serviceDetailsSuffix)
- }
- } else if rpc.ReplyMsg != "" {
- // REQUEST service
- // some messages might have `null` reply (for example: memclnt)
- if !strings.HasSuffix(rpc.ReplyMsg, serviceReplySuffix) {
- logrus.Warnf("unusual REQUEST service: %+v\n"+
- "- service %q does not have %q suffix in reply.",
- rpc, rpc.Name, serviceReplySuffix)
- }
- }
-}
diff --git a/binapigen/vppapi.go b/binapigen/vppapi.go
new file mode 100644
index 0000000..7388ad5
--- /dev/null
+++ b/binapigen/vppapi.go
@@ -0,0 +1,211 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binapigen
+
+import (
+ "log"
+ "sort"
+
+ "git.fd.io/govpp.git/binapigen/vppapi"
+)
+
+func SortFileObjectsByName(file *vppapi.File) {
+ sort.SliceStable(file.Imports, func(i, j int) bool {
+ return file.Imports[i] < file.Imports[j]
+ })
+ sort.SliceStable(file.EnumTypes, func(i, j int) bool {
+ return file.EnumTypes[i].Name < file.EnumTypes[j].Name
+ })
+ sort.Slice(file.AliasTypes, func(i, j int) bool {
+ return file.AliasTypes[i].Name < file.AliasTypes[j].Name
+ })
+ sort.SliceStable(file.StructTypes, func(i, j int) bool {
+ return file.StructTypes[i].Name < file.StructTypes[j].Name
+ })
+ sort.SliceStable(file.UnionTypes, func(i, j int) bool {
+ return file.UnionTypes[i].Name < file.UnionTypes[j].Name
+ })
+ sort.SliceStable(file.Messages, func(i, j int) bool {
+ return file.Messages[i].Name < file.Messages[j].Name
+ })
+ if file.Service != nil {
+ sort.Slice(file.Service.RPCs, func(i, j int) bool {
+ return file.Service.RPCs[i].Request < file.Service.RPCs[j].Request
+ })
+ }
+}
+
+func importedFiles(files []*vppapi.File, file *vppapi.File) []*vppapi.File {
+ var list []*vppapi.File
+ byName := func(s string) *vppapi.File {
+ for _, f := range files {
+ if f.Name == s {
+ return f
+ }
+ }
+ return nil
+ }
+ imported := map[string]struct{}{}
+ for _, imp := range file.Imports {
+ imp = normalizeImport(imp)
+ impFile := byName(imp)
+ if impFile == nil {
+ log.Fatalf("file %q not found", imp)
+ }
+ for _, nest := range importedFiles(files, impFile) {
+ if _, ok := imported[nest.Name]; !ok {
+ list = append(list, nest)
+ imported[nest.Name] = struct{}{}
+ }
+ }
+ if _, ok := imported[impFile.Name]; !ok {
+ list = append(list, impFile)
+ imported[impFile.Name] = struct{}{}
+ }
+ }
+ return list
+}
+
+func SortFilesByImports(apifiles []*vppapi.File) {
+ dependsOn := func(file *vppapi.File, dep string) bool {
+ for _, imp := range importedFiles(apifiles, file) {
+ if imp.Name == dep {
+ return true
+ }
+ }
+ return false
+ }
+ sort.Slice(apifiles, func(i, j int) bool {
+ a := apifiles[i]
+ b := apifiles[j]
+ if dependsOn(a, b.Name) {
+ return false
+ }
+ if dependsOn(b, a.Name) {
+ return true
+ }
+ return len(b.Imports) > len(a.Imports)
+ })
+}
+
+func ListImportedTypes(apifiles []*vppapi.File, file *vppapi.File) []string {
+ var importedTypes []string
+ typeFiles := importedFiles(apifiles, file)
+ for _, t := range file.StructTypes {
+ var imported bool
+ for _, imp := range typeFiles {
+ for _, at := range imp.StructTypes {
+ if at.Name != t.Name {
+ continue
+ }
+ importedTypes = append(importedTypes, t.Name)
+ imported = true
+ break
+ }
+ if imported {
+ break
+ }
+ }
+ }
+ for _, t := range file.AliasTypes {
+ var imported bool
+ for _, imp := range typeFiles {
+ for _, at := range imp.AliasTypes {
+ if at.Name != t.Name {
+ continue
+ }
+ importedTypes = append(importedTypes, t.Name)
+ imported = true
+ break
+ }
+ if imported {
+ break
+ }
+ }
+ }
+ for _, t := range file.EnumTypes {
+ var imported bool
+ for _, imp := range typeFiles {
+ for _, at := range imp.EnumTypes {
+ if at.Name != t.Name {
+ continue
+ }
+ importedTypes = append(importedTypes, t.Name)
+ imported = true
+ break
+ }
+ if imported {
+ break
+ }
+ }
+ }
+ for _, t := range file.UnionTypes {
+ var imported bool
+ for _, imp := range typeFiles {
+ for _, at := range imp.UnionTypes {
+ if at.Name != t.Name {
+ continue
+ }
+ importedTypes = append(importedTypes, t.Name)
+ imported = true
+ break
+ }
+ if imported {
+ break
+ }
+ }
+ }
+ return importedTypes
+}
+
+func RemoveImportedTypes(apifiles []*vppapi.File, apifile *vppapi.File) {
+ importedTypes := ListImportedTypes(apifiles, apifile)
+ isImportedType := func(s string) bool {
+ for _, t := range importedTypes {
+ if t == s {
+ return true
+ }
+ }
+ return false
+ }
+ var enums []vppapi.EnumType
+ for _, enumType := range apifile.EnumTypes {
+ if !isImportedType(enumType.Name) {
+ enums = append(enums, enumType)
+ }
+ }
+ var aliases []vppapi.AliasType
+ for _, aliasType := range apifile.AliasTypes {
+ if !isImportedType(aliasType.Name) {
+ aliases = append(aliases, aliasType)
+ }
+ }
+ var structs []vppapi.StructType
+ for _, structType := range apifile.StructTypes {
+ if !isImportedType(structType.Name) {
+ structs = append(structs, structType)
+ }
+ }
+ var unions []vppapi.UnionType
+ for _, unionType := range apifile.UnionTypes {
+ if !isImportedType(unionType.Name) {
+ unions = append(unions, unionType)
+ }
+ }
+ apifile.EnumTypes = enums
+ apifile.AliasTypes = aliases
+ apifile.StructTypes = structs
+ apifile.UnionTypes = unions
+}
diff --git a/binapigen/vppapi/api.go b/binapigen/vppapi/api.go
deleted file mode 100644
index 06d9046..0000000
--- a/binapigen/vppapi/api.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2020 Cisco and/or its affiliates.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at:
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vppapi
-
-type File struct {
- Name string
- Path string
-
- CRC string
- Options map[string]string `json:",omitempty"`
-
- Imports []string `json:",omitempty"`
-
- AliasTypes []AliasType `json:",omitempty"`
- EnumTypes []EnumType `json:",omitempty"`
- StructTypes []StructType `json:",omitempty"`
- UnionTypes []UnionType `json:",omitempty"`
- Messages []Message `json:",omitempty"`
- Service *Service `json:",omitempty"`
-}
-
-func (x File) Version() string {
- if x.Options != nil {
- return x.Options[fileOptionVersion]
- }
- return ""
-}
-
-type AliasType struct {
- Name string
- Type string
- Length int `json:",omitempty"`
-}
-
-type EnumType struct {
- Name string
- Type string
- Entries []EnumEntry
-}
-
-type EnumEntry struct {
- Name string
- Value uint32
-}
-
-type StructType struct {
- Name string
- Fields []Field
-}
-
-type UnionType struct {
- Name string
- Fields []Field
-}
-
-type Message struct {
- Name string
- Fields []Field
- CRC string
-}
-
-type Field struct {
- Name string
- Type string
- Length int `json:",omitempty"`
- Array bool `json:",omitempty"`
- SizeFrom string `json:",omitempty"`
- Meta map[string]interface{} `json:",omitempty"`
-}
-
-type Service struct {
- RPCs []RPC `json:",omitempty"`
-}
-
-type RPC struct {
- Name string
- RequestMsg string
- ReplyMsg string
- Stream bool `json:",omitempty"`
- StreamMsg string `json:",omitempty"`
- Events []string `json:",omitempty"`
-}
diff --git a/binapigen/vppapi/api_schema.go b/binapigen/vppapi/api_schema.go
new file mode 100644
index 0000000..7eceab3
--- /dev/null
+++ b/binapigen/vppapi/api_schema.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package vppapi parses VPP API files without any additional processing.
+package vppapi
+
+type (
+ File struct {
+ Name string
+ Path string
+ CRC string
+
+ Options map[string]string `json:",omitempty"`
+ Imports []string `json:",omitempty"`
+
+ AliasTypes []AliasType `json:",omitempty"`
+ EnumTypes []EnumType `json:",omitempty"`
+ StructTypes []StructType `json:",omitempty"`
+ UnionTypes []UnionType `json:",omitempty"`
+
+ Messages []Message `json:",omitempty"`
+ Service *Service `json:",omitempty"`
+ }
+
+ AliasType struct {
+ Name string
+ Type string
+ Length int `json:",omitempty"`
+ }
+
+ EnumType struct {
+ Name string
+ Type string
+ Entries []EnumEntry
+ }
+
+ EnumEntry struct {
+ Name string
+ Value uint32
+ }
+
+ StructType struct {
+ Name string
+ Fields []Field
+ }
+
+ UnionType struct {
+ Name string
+ Fields []Field
+ }
+
+ Message struct {
+ Name string
+ Fields []Field
+ CRC string
+ }
+
+ Field struct {
+ Name string
+ Type string
+ Length int `json:",omitempty"`
+ Array bool `json:",omitempty"`
+ SizeFrom string `json:",omitempty"`
+ Meta map[string]interface{} `json:",omitempty"`
+ }
+
+ Service struct {
+ RPCs []RPC `json:",omitempty"`
+ }
+
+ RPC struct {
+ Request string
+ Reply string
+ Stream bool `json:",omitempty"`
+ StreamMsg string `json:",omitempty"`
+ Events []string `json:",omitempty"`
+ }
+)
diff --git a/binapigen/vppapi/parse_json.go b/binapigen/vppapi/parse_json.go
index 45b5796..d14865c 100644
--- a/binapigen/vppapi/parse_json.go
+++ b/binapigen/vppapi/parse_json.go
@@ -18,79 +18,52 @@ import (
"encoding/json"
"errors"
"fmt"
+ "log"
"os"
"strings"
"github.com/bennyscetbun/jsongo"
- "github.com/sirupsen/logrus"
)
-var Logger *logrus.Logger
-
-func init() {
- if strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser") {
- Logger = logrus.StandardLogger()
- }
-}
+var debug = strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser")
func logf(f string, v ...interface{}) {
- if Logger != nil {
- Logger.Debugf(f, v...)
+ if debug {
+ log.Printf(f, v...)
}
}
const (
- // file
- objAPIVersion = "vl_api_version"
- objTypes = "types"
- objMessages = "messages"
- objUnions = "unions"
- objEnums = "enums"
- objServices = "services"
- objAliases = "aliases"
- objOptions = "options"
- objImports = "imports"
-
- // message
- messageFieldCrc = "crc"
-
- // alias
- aliasFieldLength = "length"
- aliasFieldType = "type"
-
- // service
- serviceFieldReply = "reply"
- serviceFieldStream = "stream"
- serviceFieldStreamMsg = "stream_msg"
- serviceFieldEvents = "events"
-)
-
-const (
- // file
- fileOptionVersion = "version"
-
- // field
- fieldOptionLimit = "limit"
- fieldOptionDefault = "default"
-
+ // root keys
+ fileAPIVersion = "vl_api_version"
+ fileOptions = "options"
+ fileTypes = "types"
+ fileMessages = "messages"
+ fileUnions = "unions"
+ fileEnums = "enums"
+ fileAliases = "aliases"
+ fileServices = "services"
+ fileImports = "imports"
+ // type keys
+ messageCrc = "crc"
+ enumType = "enumtype"
+ aliasLength = "length"
+ aliasType = "type"
// service
- serviceReplyNull = "null"
+ serviceReply = "reply"
+ serviceStream = "stream"
+ serviceStreamMsg = "stream_msg"
+ serviceEvents = "events"
)
func parseJSON(data []byte) (module *File, err error) {
- defer func() {
- if e := recover(); e != nil {
- err = fmt.Errorf("recovered panic: %v", e)
- }
- }()
-
- // parse JSON data into objects
+ // parse root
jsonRoot := new(jsongo.Node)
if err := json.Unmarshal(data, jsonRoot); err != nil {
return nil, fmt.Errorf("unmarshalling JSON failed: %v", err)
}
- logf("file contents:")
+ logf("file contains:")
for _, key := range jsonRoot.GetKeys() {
if jsonRoot.At(key).Len() > 0 {
logf(" - %2d %s", jsonRoot.At(key).Len(), key)
@@ -100,38 +73,35 @@ func parseJSON(data []byte) (module *File, err error) {
module = new(File)
// parse CRC
- if crc := jsonRoot.At(objAPIVersion); crc.GetType() == jsongo.TypeValue {
- module.CRC = crc.Get().(string)
+ crc := jsonRoot.At(fileAPIVersion)
+ if crc.GetType() == jsongo.TypeValue {
+ module.CRC = crc.MustGetString()
}
// parse options
- opt := jsonRoot.Map(objOptions)
+ opt := jsonRoot.Map(fileOptions)
if opt.GetType() == jsongo.TypeMap {
- module.Options = make(map[string]string, 0)
+ module.Options = make(map[string]string)
for _, key := range opt.GetKeys() {
- optionsNode := opt.At(key)
optionKey := key.(string)
- optionValue := optionsNode.Get().(string)
- module.Options[optionKey] = optionValue
+ optionVal := opt.At(key).MustGetString()
+ module.Options[optionKey] = optionVal
}
}
// parse imports
- imports := jsonRoot.Map(objImports)
- module.Imports = make([]string, 0)
- imported := make(map[string]struct{})
- for i := 0; i < imports.Len(); i++ {
- importNode := imports.At(i)
- imp, err := parseImport(importNode)
- if err != nil {
- return nil, err
- }
- if _, ok := imported[*imp]; ok {
- logf("duplicate import found: %v", *imp)
+ importsNode := jsonRoot.Map(fileImports)
+ module.Imports = make([]string, 0, importsNode.Len())
+ uniq := make(map[string]struct{})
+ for i := 0; i < importsNode.Len(); i++ {
+ importNode := importsNode.At(i)
+ imp := importNode.MustGetString()
+ if _, ok := uniq[imp]; ok {
+ logf("duplicate import found: %v", imp)
continue
}
- imported[*imp] = struct{}{}
- module.Imports = append(module.Imports, *imp)
+ uniq[imp] = struct{}{}
+ module.Imports = append(module.Imports, imp)
}
// avoid duplicate objects
@@ -146,11 +116,10 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse enum types
- enumsNode := jsonRoot.Map(objEnums)
+ enumsNode := jsonRoot.Map(fileEnums)
module.EnumTypes = make([]EnumType, 0)
for i := 0; i < enumsNode.Len(); i++ {
- enumNode := enumsNode.At(i)
- enum, err := parseEnum(enumNode)
+ enum, err := parseEnum(enumsNode.At(i))
if err != nil {
return nil, err
}
@@ -161,13 +130,12 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse alias types
- aliasesNode := jsonRoot.Map(objAliases)
+ aliasesNode := jsonRoot.Map(fileAliases)
if aliasesNode.GetType() == jsongo.TypeMap {
module.AliasTypes = make([]AliasType, 0)
for _, key := range aliasesNode.GetKeys() {
- aliasNode := aliasesNode.At(key)
aliasName := key.(string)
- alias, err := parseAlias(aliasName, aliasNode)
+ alias, err := parseAlias(aliasName, aliasesNode.At(key))
if err != nil {
return nil, err
}
@@ -179,11 +147,10 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse struct types
- typesNode := jsonRoot.Map(objTypes)
+ typesNode := jsonRoot.Map(fileTypes)
module.StructTypes = make([]StructType, 0)
for i := 0; i < typesNode.Len(); i++ {
- typNode := typesNode.At(i)
- structyp, err := parseStruct(typNode)
+ structyp, err := parseStruct(typesNode.At(i))
if err != nil {
return nil, err
}
@@ -194,11 +161,10 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse union types
- unionsNode := jsonRoot.Map(objUnions)
+ unionsNode := jsonRoot.Map(fileUnions)
module.UnionTypes = make([]UnionType, 0)
for i := 0; i < unionsNode.Len(); i++ {
- unionNode := unionsNode.At(i)
- union, err := parseUnion(unionNode)
+ union, err := parseUnion(unionsNode.At(i))
if err != nil {
return nil, err
}
@@ -209,12 +175,11 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse messages
- messagesNode := jsonRoot.Map(objMessages)
+ messagesNode := jsonRoot.Map(fileMessages)
if messagesNode.GetType() == jsongo.TypeArray {
module.Messages = make([]Message, messagesNode.Len())
for i := 0; i < messagesNode.Len(); i++ {
- msgNode := messagesNode.At(i)
- msg, err := parseMessage(msgNode)
+ msg, err := parseMessage(messagesNode.At(i))
if err != nil {
return nil, err
}
@@ -223,15 +188,14 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse services
- servicesNode := jsonRoot.Map(objServices)
+ servicesNode := jsonRoot.Map(fileServices)
if servicesNode.GetType() == jsongo.TypeMap {
module.Service = &Service{
RPCs: make([]RPC, servicesNode.Len()),
}
for i, key := range servicesNode.GetKeys() {
- rpcNode := servicesNode.At(key)
rpcName := key.(string)
- svc, err := parseServiceRPC(rpcName, rpcNode)
+ svc, err := parseServiceRPC(rpcName, servicesNode.At(key))
if err != nil {
return nil, err
}
@@ -242,20 +206,6 @@ func parseJSON(data []byte) (module *File, err error) {
return module, nil
}
-// parseImport parses VPP binary API import from JSON node
-func parseImport(importNode *jsongo.Node) (*string, error) {
- if importNode.GetType() != jsongo.TypeValue {
- return nil, errors.New("invalid JSON for import specified")
- }
-
- importName, ok := importNode.Get().(string)
- if !ok {
- return nil, fmt.Errorf("import name is %T, not a string", importNode.Get())
- }
-
- return &importName, nil
-}
-
// parseEnum parses VPP binary API enum object from JSON node
func parseEnum(enumNode *jsongo.Node) (*EnumType, error) {
if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
@@ -266,7 +216,7 @@ func parseEnum(enumNode *jsongo.Node) (*EnumType, error) {
if !ok {
return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
}
- enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
+ enumType, ok := enumNode.At(enumNode.Len() - 1).At(enumType).Get().(string)
if !ok {
return nil, fmt.Errorf("enum type invalid or missing")
}
@@ -367,7 +317,7 @@ func parseStruct(typeNode *jsongo.Node) (*StructType, error) {
// parseAlias parses VPP binary API alias object from JSON node
func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
- if aliasNode.Len() == 0 || aliasNode.At(aliasFieldType).GetType() != jsongo.TypeValue {
+ if aliasNode.Len() == 0 || aliasNode.At(aliasType).GetType() != jsongo.TypeValue {
return nil, errors.New("invalid JSON for alias specified")
}
@@ -375,7 +325,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
Name: aliasName,
}
- if typeNode := aliasNode.At(aliasFieldType); typeNode.GetType() == jsongo.TypeValue {
+ if typeNode := aliasNode.At(aliasType); typeNode.GetType() == jsongo.TypeValue {
typ, ok := typeNode.Get().(string)
if !ok {
return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
@@ -385,7 +335,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
}
}
- if lengthNode := aliasNode.At(aliasFieldLength); lengthNode.GetType() == jsongo.TypeValue {
+ if lengthNode := aliasNode.At(aliasLength); lengthNode.GetType() == jsongo.TypeValue {
length, ok := lengthNode.Get().(float64)
if !ok {
return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
@@ -398,7 +348,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
// parseMessage parses VPP binary API message object from JSON node
func parseMessage(msgNode *jsongo.Node) (*Message, error) {
- if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
+ if msgNode.Len() < 2 || msgNode.At(0).GetType() != jsongo.TypeValue {
return nil, errors.New("invalid JSON for message specified")
}
@@ -406,9 +356,8 @@ func parseMessage(msgNode *jsongo.Node) (*Message, error) {
if !ok {
return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
}
- msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageFieldCrc).Get().(string)
+ msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageCrc).Get().(string)
if !ok {
-
return nil, fmt.Errorf("message crc invalid or missing")
}
@@ -466,26 +415,16 @@ func parseField(field *jsongo.Node) (*Field, error) {
case jsongo.TypeMap:
fieldMeta := field.At(2)
-
+ if fieldMeta.Len() == 0 {
+ break
+ }
+ f.Meta = map[string]interface{}{}
for _, key := range fieldMeta.GetKeys() {
- metaNode := fieldMeta.At(key)
metaName := key.(string)
- metaValue := metaNode.Get()
-
- switch metaName {
- case fieldOptionLimit:
- metaValue = int(metaNode.Get().(float64))
- case fieldOptionDefault:
- metaValue = metaNode.Get()
- default:
- logrus.Warnf("unknown meta info (%s=%v) for field (%s)", metaName, metaValue, fieldName)
- }
-
- if f.Meta == nil {
- f.Meta = map[string]interface{}{}
- }
+ metaValue := fieldMeta.At(key).Get()
f.Meta[metaName] = metaValue
}
+
default:
return nil, errors.New("invalid JSON for field specified")
}
@@ -503,27 +442,24 @@ func parseField(field *jsongo.Node) (*Field, error) {
// parseServiceRPC parses VPP binary API service object from JSON node
func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) {
- if rpcNode.Len() == 0 || rpcNode.At(serviceFieldReply).GetType() != jsongo.TypeValue {
+ if rpcNode.Len() == 0 || rpcNode.At(serviceReply).GetType() != jsongo.TypeValue {
return nil, errors.New("invalid JSON for service RPC specified")
}
rpc := RPC{
- Name: rpcName,
- RequestMsg: rpcName,
+ Request: rpcName,
}
- if replyNode := rpcNode.At(serviceFieldReply); replyNode.GetType() == jsongo.TypeValue {
+ if replyNode := rpcNode.At(serviceReply); replyNode.GetType() == jsongo.TypeValue {
reply, ok := replyNode.Get().(string)
if !ok {
return nil, fmt.Errorf("service RPC reply is %T, not a string", replyNode.Get())
}
- if reply != serviceReplyNull {
- rpc.ReplyMsg = reply
- }
+ rpc.Reply = reply
}
// is stream (dump)
- if streamNode := rpcNode.At(serviceFieldStream); streamNode.GetType() == jsongo.TypeValue {
+ if streamNode := rpcNode.At(serviceStream); streamNode.GetType() == jsongo.TypeValue {
var ok bool
rpc.Stream, ok = streamNode.Get().(bool)
if !ok {
@@ -532,7 +468,7 @@ func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) {
}
// stream message
- if streamMsgNode := rpcNode.At(serviceFieldStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue {
+ if streamMsgNode := rpcNode.At(serviceStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue {
var ok bool
rpc.StreamMsg, ok = streamMsgNode.Get().(string)
if !ok {
@@ -541,7 +477,7 @@ func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) {
}
// events service (event subscription)
- if eventsNode := rpcNode.At(serviceFieldEvents); eventsNode.GetType() == jsongo.TypeArray {
+ if eventsNode := rpcNode.At(serviceEvents); eventsNode.GetType() == jsongo.TypeArray {
for j := 0; j < eventsNode.Len(); j++ {
event := eventsNode.At(j).Get().(string)
rpc.Events = append(rpc.Events, event)
diff --git a/binapigen/vppapi/util.go b/binapigen/vppapi/util.go
new file mode 100644
index 0000000..87f2e55
--- /dev/null
+++ b/binapigen/vppapi/util.go
@@ -0,0 +1,112 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package vppapi
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ VPPVersionEnvVar = "VPP_VERSION"
+)
+
+// ResolveVPPVersion resolves version of the VPP for target directory.
+//
+// Version resolved here can be overriden by setting VPP_VERSION env var.
+func ResolveVPPVersion(apidir string) string {
+ // check env variable override
+ if ver := os.Getenv(VPPVersionEnvVar); ver != "" {
+ logrus.Debugf("VPP version was manually set to %q via %s env var", ver, VPPVersionEnvVar)
+ return ver
+ }
+
+ // assuming VPP package is installed
+ if path.Clean(apidir) == DefaultDir {
+ version, err := GetVPPVersionInstalled()
+ if err != nil {
+ logrus.Warnf("resolving VPP version from installed package failed: %v", err)
+ } else {
+ logrus.Debugf("resolved VPP version from installed package: %v", version)
+ return version
+ }
+ }
+
+ // check if inside VPP repo
+ repoDir, err := findGitRepoRootDir(apidir)
+ if err != nil {
+ logrus.Warnf("checking VPP git repo failed: %v", err)
+ } else {
+ logrus.Debugf("resolved git repo root directory: %v", repoDir)
+ version, err := GetVPPVersionRepo(repoDir)
+ if err != nil {
+ logrus.Warnf("resolving VPP version from version script failed: %v", err)
+ } else {
+ logrus.Debugf("resolved VPP version from version script: %v", version)
+ return version
+ }
+ }
+
+ // try to read VPP_VERSION file
+ data, err := ioutil.ReadFile(path.Join(repoDir, "VPP_VERSION"))
+ if err == nil {
+ return strings.TrimSpace(string(data))
+ }
+
+ logrus.Warnf("VPP version could not be resolved, you can set it manually using %s env var", VPPVersionEnvVar)
+ return ""
+}
+
+// GetVPPVersionInstalled retrieves VPP version of installed package using dpkg-query.
+func GetVPPVersionInstalled() (string, error) {
+ cmd := exec.Command("dpkg-query", "-f", "${Version}", "-W", "vpp")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("dpkg-query command failed: %v\noutput: %s", err, out)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+const versionScriptPath = "./src/scripts/version"
+
+// GetVPPVersionRepo retrieves VPP version using script in repo directory.
+func GetVPPVersionRepo(repoDir string) (string, error) {
+ if _, err := os.Stat(versionScriptPath); err != nil {
+ return "", err
+ }
+ cmd := exec.Command(versionScriptPath)
+ cmd.Dir = repoDir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("version script failed: %v\noutput: %s", err, out)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+func findGitRepoRootDir(dir string) (string, error) {
+ cmd := exec.Command("git", "rev-parse", "--show-toplevel")
+ cmd.Dir = dir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("git command failed: %v\noutput: %s", err, out)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
diff --git a/binapigen/vppapi/parser.go b/binapigen/vppapi/vppapi.go
index 312dd0e..665fa81 100644
--- a/binapigen/vppapi/parser.go
+++ b/binapigen/vppapi/vppapi.go
@@ -19,18 +19,15 @@ import (
"io/ioutil"
"path/filepath"
"strings"
-
- "github.com/sirupsen/logrus"
)
const (
- DefaultAPIDir = "/usr/share/vpp/api"
+ // DefaultDir is default location of API files.
+ DefaultDir = "/usr/share/vpp/api"
)
-const apifileSuffixJson = ".api.json"
-
-// FindFiles returns all input files located in specified directory
-func FindFiles(dir string, deep int) (paths []string, err error) {
+// FindFiles finds API files located in dir or in a nested directory that is not nested deeper than deep.
+func FindFiles(dir string, deep int) (files []string, err error) {
entries, err := ioutil.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("reading directory %s failed: %v", dir, err)
@@ -41,43 +38,44 @@ func FindFiles(dir string, deep int) (paths []string, err error) {
if nested, err := FindFiles(nestedDir, deep-1); err != nil {
return nil, err
} else {
- paths = append(paths, nested...)
+ files = append(files, nested...)
}
- } else if strings.HasSuffix(e.Name(), apifileSuffixJson) {
- paths = append(paths, filepath.Join(dir, e.Name()))
+ } else if !e.IsDir() && strings.HasSuffix(e.Name(), ".api.json") {
+ files = append(files, filepath.Join(dir, e.Name()))
}
}
- return paths, nil
+ return files, nil
}
+// Parse parses API files in directory DefaultDir.
func Parse() ([]*File, error) {
- return ParseDir(DefaultAPIDir)
+ return ParseDir(DefaultDir)
}
+// ParseDir finds and parses API files in given directory and returns parsed files.
+// Supports API files in JSON format (.api.json) only.
func ParseDir(apidir string) ([]*File, error) {
- files, err := FindFiles(apidir, 1)
+ list, err := FindFiles(apidir, 1)
if err != nil {
return nil, err
}
- logrus.Infof("found %d files in API dir %q", len(files), apidir)
-
- var modules []*File
+ logf("found %d files in API dir %q", len(list), apidir)
- for _, file := range files {
+ var files []*File
+ for _, file := range list {
module, err := ParseFile(file)
if err != nil {
return nil, err
}
- modules = append(modules, module)
+ files = append(files, module)
}
-
- return modules, nil
+ return files, nil
}
-// ParseFile parses API file contents and returns File.
+// ParseFile parses API file and returns File.
func ParseFile(apifile string) (*File, error) {
- if !strings.HasSuffix(apifile, apifileSuffixJson) {
+ if !strings.HasSuffix(apifile, ".api.json") {
return nil, fmt.Errorf("unsupported file format: %q", apifile)
}
@@ -101,7 +99,14 @@ func ParseFile(apifile string) (*File, error) {
return module, nil
}
+// ParseRaw parses raw API file data and returns File.
func ParseRaw(data []byte) (file *File, err error) {
+ defer func() {
+ if e := recover(); e != nil {
+ err = fmt.Errorf("panic occurred: %v", e)
+ }
+ }()
+
file, err = parseJSON(data)
if err != nil {
return nil, err
diff --git a/binapigen/vppapi/parser_test.go b/binapigen/vppapi/vppapi_test.go
index 2dc82e4..027cc1f 100644
--- a/binapigen/vppapi/parser_test.go
+++ b/binapigen/vppapi/vppapi_test.go
@@ -46,7 +46,7 @@ func TestReadJson(t *testing.T) {
inputData, err := ioutil.ReadFile("testdata/af_packet.api.json")
Expect(err).ShouldNot(HaveOccurred())
- result, err := parseJSON(inputData)
+ result, err := ParseRaw(inputData)
Expect(err).ShouldNot(HaveOccurred())
Expect(result).ToNot(BeNil())
Expect(result.EnumTypes).To(HaveLen(0))
@@ -60,7 +60,7 @@ func TestReadJsonError(t *testing.T) {
inputData, err := ioutil.ReadFile("testdata/input-read-json-error.json")
Expect(err).ShouldNot(HaveOccurred())
- result, err := parseJSON(inputData)
+ result, err := ParseRaw(inputData)
Expect(err).Should(HaveOccurred())
Expect(result).To(BeNil())
}
@@ -80,17 +80,21 @@ func TestParseFile(t *testing.T) {
if module.Name != "vpe" {
t.Errorf("expected Name=%s, got %v", "vpe", module.Name)
}
+ if module.Path != "testdata/vpe.api.json" {
+ t.Errorf("expected Path=%s, got %v", "testdata/vpe.api.json", module.Path)
+ }
if module.CRC != "0xbd2c94f4" {
t.Errorf("expected CRC=%s, got %v", "0xbd2c94f4", module.CRC)
}
- if module.Version() != "1.6.1" {
- t.Errorf("expected Version=%s, got %v", "1.6.1", module.Version())
+
+ if version := module.Options["version"]; version != "1.6.1" {
+ t.Errorf("expected option[version]=%s, got %v", "1.6.1", version)
}
if len(module.Imports) == 0 {
t.Errorf("expected imports, got none")
}
- if len(module.Options) == 0 {
- t.Errorf("expected options, got none")
+ if len(module.EnumTypes) == 0 {
+ t.Errorf("expected enums, got none")
}
if len(module.AliasTypes) == 0 {
t.Errorf("expected aliases, got none")
@@ -98,12 +102,12 @@ func TestParseFile(t *testing.T) {
if len(module.StructTypes) == 0 {
t.Errorf("expected types, got none")
}
- if len(module.Service.RPCs) == 0 {
- t.Errorf("expected service method, got none")
- }
if len(module.Messages) == 0 {
t.Errorf("expected messages, got none")
}
+ if len(module.Service.RPCs) == 0 {
+ t.Errorf("expected service RPCs, got none")
+ }
}
func TestParseFileUnsupported(t *testing.T) {