Binary API generator improvements
[govpp.git] / binapigen / generate.go
index 1f9b89a..d35427f 100644 (file)
 package binapigen
 
 import (
-       "bytes"
        "fmt"
        "io"
-       "os/exec"
-       "path"
-       "path/filepath"
        "sort"
        "strings"
 
        "git.fd.io/govpp.git/version"
+       "github.com/sirupsen/logrus"
 )
 
 // generatedCodeVersion indicates a version of the generated code.
@@ -33,7 +30,7 @@ import (
 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
 const generatedCodeVersion = 2
 
-// message field names
+// common message fields
 const (
        msgIdField       = "_vl_msg_id"
        clientIndexField = "client_index"
@@ -41,23 +38,16 @@ const (
        retvalField      = "retval"
 )
 
+// global API info
 const (
-       outputFileExt = ".ba.go" // file extension of the Go generated files
-       rpcFileSuffix = "_rpc"   // file name suffix for the RPC services
-
        constModuleName = "ModuleName" // module name constant
        constAPIVersion = "APIVersion" // API version constant
        constVersionCrc = "VersionCrc" // version CRC constant
+)
 
+// generated fiels
+const (
        unionDataField = "XXX_UnionData" // name for the union data field
-
-       serviceApiName    = "RPCService"    // name for the RPC service interface
-       serviceImplName   = "serviceClient" // name for the RPC service implementation
-       serviceClientName = "ServiceClient" // name for the RPC service client
-
-       // TODO: register service descriptor
-       //serviceDescType = "ServiceDesc"             // name for service descriptor type
-       //serviceDescName = "_ServiceRPC_serviceDesc" // name for service descriptor var
 )
 
 // MessageType represents the type of a VPP message
@@ -70,22 +60,93 @@ const (
        otherMessage                      // other VPP message
 )
 
-type GenFile struct {
-       *Generator
-       filename   string
-       file       *File
-       packageDir string
-       buf        bytes.Buffer
-}
-
-func generatePackage(ctx *GenFile, w io.Writer) {
+func generateFileBinapi(ctx *GenFile, w io.Writer) {
        logf("----------------------------")
-       logf("generating binapi package: %q", ctx.file.PackageName)
+       logf("generating BINAPI file package: %q", ctx.file.PackageName)
        logf("----------------------------")
 
-       generateHeader(ctx, w)
+       // generate file header
+       fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+       fmt.Fprintln(w, "// versions:")
+       fmt.Fprintf(w, "//  binapi-generator: %s\n", version.Version())
+       if ctx.IncludeVppVersion {
+               fmt.Fprintf(w, "//  VPP:              %s\n", ctx.VPPVersion)
+       }
+       fmt.Fprintf(w, "// source: %s\n", ctx.file.Path)
+       fmt.Fprintln(w)
+
+       generatePackageHeader(ctx, w)
        generateImports(ctx, w)
 
+       generateApiInfo(ctx, w)
+       generateTypes(ctx, w)
+       generateMessages(ctx, w)
+
+       generateImportRefs(ctx, w)
+}
+
+func generatePackageHeader(ctx *GenFile, w io.Writer) {
+       fmt.Fprintln(w, "/*")
+       fmt.Fprintf(w, "Package %s contains generated code for VPP API file %s.api (%s).\n",
+               ctx.file.PackageName, ctx.file.Name, ctx.file.Version())
+       fmt.Fprintln(w)
+       fmt.Fprintln(w, "It consists of:")
+       printObjNum := func(obj string, num int) {
+               if num > 0 {
+                       if num > 1 {
+                               if strings.HasSuffix(obj, "s") {
+                                       obj += "es"
+                               } else {
+                                       obj += "s"
+                               }
+                       }
+                       fmt.Fprintf(w, "\t%3d %s\n", num, obj)
+               }
+       }
+       printObjNum("alias", len(ctx.file.Aliases))
+       printObjNum("enum", len(ctx.file.Enums))
+       printObjNum("message", len(ctx.file.Messages))
+       printObjNum("type", len(ctx.file.Structs))
+       printObjNum("union", len(ctx.file.Unions))
+       fmt.Fprintln(w, "*/")
+       fmt.Fprintf(w, "package %s\n", ctx.file.PackageName)
+       fmt.Fprintln(w)
+}
+
+func generateImports(ctx *GenFile, w io.Writer) {
+       fmt.Fprintln(w, "import (")
+       fmt.Fprintln(w, `       "bytes"`)
+       fmt.Fprintln(w, `       "context"`)
+       fmt.Fprintln(w, `       "encoding/binary"`)
+       fmt.Fprintln(w, `       "fmt"`)
+       fmt.Fprintln(w, `       "io"`)
+       fmt.Fprintln(w, `       "math"`)
+       fmt.Fprintln(w, `       "net"`)
+       fmt.Fprintln(w, `       "strconv"`)
+       fmt.Fprintln(w, `       "strings"`)
+       fmt.Fprintln(w)
+       fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
+       fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec")
+       fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
+       imports := listImports(ctx)
+       if len(imports) > 0 {
+               fmt.Fprintln(w)
+               for imp, importPath := range imports {
+                       fmt.Fprintf(w, "\t%s \"%s\"\n", imp, importPath)
+               }
+       }
+       fmt.Fprintln(w, ")")
+       fmt.Fprintln(w)
+
+       fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
+       fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
+       fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
+       fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
+       fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
+       fmt.Fprintln(w)
+}
+
+func generateApiInfo(ctx *GenFile, w io.Writer) {
        // generate module desc
        fmt.Fprintln(w, "const (")
        fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
@@ -99,12 +160,16 @@ func generatePackage(ctx *GenFile, w io.Writer) {
        }
        fmt.Fprintln(w, ")")
        fmt.Fprintln(w)
+}
 
+func generateTypes(ctx *GenFile, w io.Writer) {
        // generate enums
        if len(ctx.file.Enums) > 0 {
                for _, enum := range ctx.file.Enums {
                        if imp, ok := ctx.file.imports[enum.Name]; ok {
-                               generateImportedAlias(ctx, w, enum.GoName, imp)
+                               if strings.HasSuffix(ctx.file.Name, "_types") {
+                                       generateImportedAlias(ctx, w, enum.GoName, imp)
+                               }
                                continue
                        }
                        generateEnum(ctx, w, enum)
@@ -115,7 +180,9 @@ func generatePackage(ctx *GenFile, w io.Writer) {
        if len(ctx.file.Aliases) > 0 {
                for _, alias := range ctx.file.Aliases {
                        if imp, ok := ctx.file.imports[alias.Name]; ok {
-                               generateImportedAlias(ctx, w, alias.GoName, imp)
+                               if strings.HasSuffix(ctx.file.Name, "_types") {
+                                       generateImportedAlias(ctx, w, alias.GoName, imp)
+                               }
                                continue
                        }
                        generateAlias(ctx, w, alias)
@@ -126,7 +193,9 @@ func generatePackage(ctx *GenFile, w io.Writer) {
        if len(ctx.file.Structs) > 0 {
                for _, typ := range ctx.file.Structs {
                        if imp, ok := ctx.file.imports[typ.Name]; ok {
-                               generateImportedAlias(ctx, w, typ.GoName, imp)
+                               if strings.HasSuffix(ctx.file.Name, "_types") {
+                                       generateImportedAlias(ctx, w, typ.GoName, imp)
+                               }
                                continue
                        }
                        generateStruct(ctx, w, typ)
@@ -137,135 +206,49 @@ func generatePackage(ctx *GenFile, w io.Writer) {
        if len(ctx.file.Unions) > 0 {
                for _, union := range ctx.file.Unions {
                        if imp, ok := ctx.file.imports[union.Name]; ok {
-                               generateImportedAlias(ctx, w, union.GoName, imp)
+                               if strings.HasSuffix(ctx.file.Name, "_types") {
+                                       generateImportedAlias(ctx, w, union.GoName, imp)
+                               }
                                continue
                        }
                        generateUnion(ctx, w, union)
                }
        }
-
-       // generate messages
-       if len(ctx.file.Messages) > 0 {
-               for _, msg := range ctx.file.Messages {
-                       generateMessage(ctx, w, msg)
-               }
-
-               initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName)
-
-               // generate message registrations
-               fmt.Fprintf(w, "func init() { %s() }\n", initFnName)
-               fmt.Fprintf(w, "func %s() {\n", initFnName)
-               for _, msg := range ctx.file.Messages {
-                       fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n",
-                               msg.GoName, ctx.file.Name+"."+msg.GoName)
-               }
-               fmt.Fprintln(w, "}")
-               fmt.Fprintln(w)
-
-               // generate list of messages
-               fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
-               fmt.Fprintln(w, "func AllMessages() []api.Message {")
-               fmt.Fprintln(w, "\treturn []api.Message{")
-               for _, msg := range ctx.file.Messages {
-                       fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName)
-               }
-               fmt.Fprintln(w, "}")
-               fmt.Fprintln(w, "}")
-       }
-
-       generateFooter(ctx, w)
-
 }
 
-func generateHeader(ctx *GenFile, w io.Writer) {
-       fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
-       fmt.Fprintln(w, "// versions:")
-       fmt.Fprintf(w, "//  binapi-generator: %s\n", version.Version())
-       if ctx.IncludeVppVersion {
-               fmt.Fprintf(w, "//  VPP:              %s\n", ctx.VPPVersion)
+func generateMessages(ctx *GenFile, w io.Writer) {
+       if len(ctx.file.Messages) == 0 {
+               return
        }
-       fmt.Fprintf(w, "// source: %s\n", ctx.file.Path)
-       fmt.Fprintln(w)
 
-       fmt.Fprintln(w, "/*")
-       fmt.Fprintf(w, "Package %s contains generated code for VPP binary API defined by %s.api (version %s).\n",
-               ctx.file.PackageName, ctx.file.Name, ctx.file.Version())
-       fmt.Fprintln(w)
-       fmt.Fprintln(w, "It consists of:")
-       printObjNum := func(obj string, num int) {
-               if num > 0 {
-                       if num > 1 {
-                               if strings.HasSuffix(obj, "s") {
-
-                                       obj += "es"
-                               } else {
-                                       obj += "s"
-                               }
-                       }
-                       fmt.Fprintf(w, "\t%3d %s\n", num, obj)
-               }
+       for _, msg := range ctx.file.Messages {
+               generateMessage(ctx, w, msg)
        }
-       //printObjNum("RPC", len(ctx.file.Service.RPCs))
-       printObjNum("alias", len(ctx.file.Aliases))
-       printObjNum("enum", len(ctx.file.Enums))
-       printObjNum("message", len(ctx.file.Messages))
-       printObjNum("type", len(ctx.file.Structs))
-       printObjNum("union", len(ctx.file.Unions))
-       fmt.Fprintln(w, "*/")
-       fmt.Fprintf(w, "package %s\n", ctx.file.PackageName)
-       fmt.Fprintln(w)
-}
 
-func generateImports(ctx *GenFile, w io.Writer) {
-       fmt.Fprintln(w, "import (")
-       fmt.Fprintln(w, `       "bytes"`)
-       fmt.Fprintln(w, `       "context"`)
-       fmt.Fprintln(w, `       "encoding/binary"`)
-       fmt.Fprintln(w, `       "io"`)
-       fmt.Fprintln(w, `       "math"`)
-       fmt.Fprintln(w, `       "strconv"`)
-       fmt.Fprintln(w)
-       fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
-       fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec")
-       fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
-       if len(ctx.file.Imports) > 0 {
-               fmt.Fprintln(w)
-               for _, imp := range ctx.file.Imports {
-                       importPath := path.Join(ctx.ImportPrefix, imp)
-                       if ctx.ImportPrefix == "" {
-                               importPath = getImportPath(ctx.packageDir, imp)
-                       }
-                       fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
-               }
-       }
-       fmt.Fprintln(w, ")")
-       fmt.Fprintln(w)
+       // generate message registrations
+       initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName)
 
-       fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
-       fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
-       fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
-       fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
-       fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
+       fmt.Fprintf(w, "func init() { %s() }\n", initFnName)
+       fmt.Fprintf(w, "func %s() {\n", initFnName)
+       for _, msg := range ctx.file.Messages {
+               fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n",
+                       msg.GoName, ctx.file.Name+"."+msg.GoName)
+       }
+       fmt.Fprintln(w, "}")
        fmt.Fprintln(w)
-}
 
-func getImportPath(outputDir string, pkg string) string {
-       absPath, err := filepath.Abs(filepath.Join(outputDir, "..", pkg))
-       if err != nil {
-               panic(err)
-       }
-       cmd := exec.Command("go", "list", absPath)
-       var errbuf, outbuf bytes.Buffer
-       cmd.Stdout = &outbuf
-       cmd.Stderr = &errbuf
-       if err := cmd.Run(); err != nil {
-               fmt.Printf("ERR: %v\n", errbuf.String())
-               panic(err)
+       // generate list of messages
+       fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
+       fmt.Fprintln(w, "func AllMessages() []api.Message {")
+       fmt.Fprintln(w, "\treturn []api.Message{")
+       for _, msg := range ctx.file.Messages {
+               fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName)
        }
-       return outbuf.String()
+       fmt.Fprintln(w, "}")
+       fmt.Fprintln(w, "}")
 }
 
-func generateFooter(ctx *GenFile, w io.Writer) {
+func generateImportRefs(ctx *GenFile, w io.Writer) {
        fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
        fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
        fmt.Fprintf(w, "var _ = codec.DecodeString\n")
@@ -273,9 +256,12 @@ func generateFooter(ctx *GenFile, w io.Writer) {
        fmt.Fprintf(w, "var _ = context.Background\n")
        fmt.Fprintf(w, "var _ = io.Copy\n")
        fmt.Fprintf(w, "var _ = strconv.Itoa\n")
+       fmt.Fprintf(w, "var _ = strings.Contains\n")
        fmt.Fprintf(w, "var _ = struc.Pack\n")
        fmt.Fprintf(w, "var _ = binary.BigEndian\n")
        fmt.Fprintf(w, "var _ = math.Float32bits\n")
+       fmt.Fprintf(w, "var _ = net.ParseIP\n")
+       fmt.Fprintf(w, "var _ = fmt.Errorf\n")
 }
 
 func generateComment(ctx *GenFile, w io.Writer, goName string, vppName string, objKind string) {
@@ -353,6 +339,13 @@ func generateAlias(ctx *GenFile, w io.Writer, alias *Alias) {
        dataType := convertToGoType(ctx.file, alias.Type)
        fmt.Fprintf(w, "%s\n", dataType)
 
+       // generate alias-specific methods
+       switch alias.Name {
+       case "mac_address":
+               fmt.Fprintln(w)
+               generateMacAddressConversion(w, name)
+       }
+
        fmt.Fprintln(w)
 }
 
@@ -384,6 +377,16 @@ func generateStruct(ctx *GenFile, w io.Writer, typ *Struct) {
        // generate name getter
        generateTypeNameGetter(w, name, typ.Name)
 
+       // generate type-specific methods
+       switch typ.Name {
+       case "address":
+               fmt.Fprintln(w)
+               generateIPAddressConversion(w, name)
+       case "prefix":
+               fmt.Fprintln(w)
+               generatePrefixConversion(w, name)
+       }
+
        fmt.Fprintln(w)
 }
 
@@ -522,7 +525,7 @@ func generateMessage(ctx *GenFile, w io.Writer, msg *Message) {
 
                // skip internal fields
                switch strings.ToLower(field.Name) {
-               case /*crcField,*/ msgIdField:
+               case msgIdField:
                        continue
                case clientIndexField, contextField:
                        if n == 0 {
@@ -590,20 +593,22 @@ func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field
        }
 
        lvl := 0
-       var encodeFields func(fields []*Field, parentName string)
-       encodeFields = func(fields []*Field, parentName string) {
+       var sizeFields func(fields []*Field, parentName string)
+       sizeFields = func(fields []*Field, parentName string) {
                lvl++
                defer func() { lvl-- }()
 
                n := 0
                for _, field := range fields {
-                       // skip internal fields
-                       switch strings.ToLower(field.Name) {
-                       case /*crcField,*/ msgIdField:
-                               continue
-                       case clientIndexField, contextField:
-                               if n == 0 {
+                       if field.ParentMessage != nil {
+                               // skip internal fields
+                               switch strings.ToLower(field.Name) {
+                               case msgIdField:
                                        continue
+                               case clientIndexField, contextField:
+                                       if n == 0 {
+                                               continue
+                                       }
                                }
                        }
                        n++
@@ -646,12 +651,12 @@ func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field
                        } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil {
                                if encodeBaseType(alias.Type, name, alias.Length, "") {
                                } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil {
-                                       encodeFields(typ.Fields, name)
+                                       sizeFields(typ.Fields, name)
                                } else {
                                        fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type)
                                }
                        } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil {
-                               encodeFields(typ.Fields, name)
+                               sizeFields(typ.Fields, name)
                        } else if union := getUnionByRef(ctx.file, field.Type); union != nil {
                                maxSize := getUnionSize(ctx.file, union)
                                fmt.Fprintf(w, "\tsize += %d\n", maxSize)
@@ -665,7 +670,7 @@ func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field
                }
        }
 
-       encodeFields(fields, "m")
+       sizeFields(fields, "m")
 
        fmt.Fprintf(w, "return size\n")
 
@@ -786,13 +791,15 @@ func generateMessageMarshal(ctx *GenFile, w io.Writer, name string, fields []*Fi
 
                n := 0
                for _, field := range fields {
-                       // skip internal fields
-                       switch strings.ToLower(field.Name) {
-                       case /*crcField,*/ msgIdField:
-                               continue
-                       case clientIndexField, contextField:
-                               if n == 0 {
+                       if field.ParentMessage != nil {
+                               // skip internal fields
+                               switch strings.ToLower(field.Name) {
+                               case msgIdField:
                                        continue
+                               case clientIndexField, contextField:
+                                       if n == 0 {
+                                               continue
+                                       }
                                }
                        }
                        n++
@@ -1004,13 +1011,15 @@ func generateMessageUnmarshal(ctx *GenFile, w io.Writer, name string, fields []*
 
                n := 0
                for _, field := range fields {
-                       // skip internal fields
-                       switch strings.ToLower(field.Name) {
-                       case /*crcField,*/ msgIdField:
-                               continue
-                       case clientIndexField, contextField:
-                               if n == 0 {
+                       if field.ParentMessage != nil {
+                               // skip internal fields
+                               switch strings.ToLower(field.Name) {
+                               case msgIdField:
                                        continue
+                               case clientIndexField, contextField:
+                                       if n == 0 {
+                                               continue
+                                       }
                                }
                        }
                        n++
@@ -1220,6 +1229,105 @@ 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
+       } else {
+               %[4]s
+       }
+       return address, nil
+}
+`, structName, "%s", f1(6, 16), f1(4, 4))
+       fmt.Fprintln(w)
+
+       // Address to IP
+       fmt.Fprintln(w)
+       fmt.Fprintf(w, `func (a *%[1]s) ToString() string {
+       var ip string
+       if a.Af == ADDRESS_IP6 {
+               %[2]s
+       } else {
+               %[3]s
+       }
+       return ip
+}`, structName, f2(6, 16), f2(4, 4))
+}
+
+func generatePrefixConversion(w io.Writer, structName string) {
+       fErr := func() string {
+               return fmt.Sprintf(`if err != nil {
+                       return Prefix{}, fmt.Errorf("invalid IP %s: %s", ip, err)
+               }`, "%s", "%v")
+       }
+
+       // IP to Prefix
+       fmt.Fprintf(w, `func ParsePrefix(ip string) (prefix %[1]s, err error) {
+       hasPrefix := strings.Contains(ip, "/")
+       if hasPrefix {
+               netIP, network, err := net.ParseCIDR(ip)
+               %[2]s
+       maskSize, _ := network.Mask.Size()
+       prefix.Len = byte(maskSize)
+       prefix.Address, err = ParseAddress(netIP.String())
+               %[2]s
+       } else {
+               netIP := net.ParseIP(ip)
+               defaultMaskSize, _ := net.CIDRMask(32, 32).Size()
+               if netIP.To4() == nil {
+                       defaultMaskSize, _ = net.CIDRMask(128, 128).Size()
+               }
+               prefix.Len = byte(defaultMaskSize)
+               prefix.Address, err = ParseAddress(netIP.String())
+               %[2]s
+       }
+       return prefix, nil
+}`, structName, fErr(), nil)
+       fmt.Fprintln(w)
+
+       // Prefix to IP
+       fmt.Fprintln(w)
+       fmt.Fprintf(w, `func (p *%[1]s) ToString() string {
+               ip := p.Address.ToString()
+               return ip + "/" + strconv.Itoa(int(p.Len))
+       }`, structName)
+}
+
+func generateMacAddressConversion(w io.Writer, structName string) {
+       // string to MAC
+       fmt.Fprintf(w, `func ParseMAC(mac string) (parsed %[1]s, err error) {
+       var hw net.HardwareAddr
+       if hw, err = net.ParseMAC(mac); err != nil {
+               return
+       }
+       copy(parsed[:], hw[:])
+       return
+}`, structName)
+       fmt.Fprintln(w)
+
+       // MAC to string
+       fmt.Fprintln(w)
+       fmt.Fprintf(w, `func (m *%[1]s) ToString() string {
+               return net.HardwareAddr(m[:]).String()
+       }`, structName)
+}
+
 func generateCrcGetter(w io.Writer, structName, crc string) {
        crc = strings.TrimPrefix(crc, "0x")
        fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc)
@@ -1239,3 +1347,7 @@ func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageTy
        fmt.Fprintln(w, "}")
        fmt.Fprintln(w)
 }
+
+func logf(f string, v ...interface{}) {
+       logrus.Debugf(f, v...)
+}