Fix parsing API with removed CRC for types and unions
[govpp.git] / cmd / binapi-generator / generate.go
1 // Copyright (c) 2017 Cisco and/or its affiliates.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package main
16
17 import (
18         "bytes"
19         "fmt"
20         "io"
21         "path/filepath"
22         "sort"
23         "strings"
24         "unicode"
25 )
26
27 // generatedCodeVersion indicates a version of the generated code.
28 // It is incremented whenever an incompatibility between the generated code and
29 // GoVPP api package is introduced; the generated code references
30 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
31 const generatedCodeVersion = 1
32
33 const (
34         inputFileExt  = ".api.json" // file extension of the VPP API files
35         outputFileExt = ".ba.go"    // file extension of the Go generated files
36
37         govppApiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API package
38
39         constModuleName = "ModuleName" // module name constant
40         constAPIVersion = "APIVersion" // API version constant
41         constVersionCrc = "VersionCrc" // version CRC constant
42
43         unionDataField = "XXX_UnionData" // name for the union data field
44 )
45
46 // context is a structure storing data for code generation
47 type context struct {
48         inputFile  string // input file with VPP API in JSON
49         outputFile string // output file with generated Go package
50
51         inputData []byte // contents of the input file
52
53         includeAPIVersion  bool // include constant with API version string
54         includeComments    bool // include parts of original source in comments
55         includeBinapiNames bool // include binary API names as struct tag
56         includeServices    bool // include service interface with client implementation
57
58         moduleName  string // name of the source VPP module
59         packageName string // name of the Go package being generated
60
61         packageData *Package // parsed package data
62 }
63
64 // getContext returns context details of the code generation task
65 func getContext(inputFile, outputDir string) (*context, error) {
66         if !strings.HasSuffix(inputFile, inputFileExt) {
67                 return nil, fmt.Errorf("invalid input file name: %q", inputFile)
68         }
69
70         ctx := &context{
71                 inputFile: inputFile,
72         }
73
74         // package name
75         inputFileName := filepath.Base(inputFile)
76         ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
77
78         // alter package names for modules that are reserved keywords in Go
79         switch ctx.moduleName {
80         case "interface":
81                 ctx.packageName = "interfaces"
82         case "map":
83                 ctx.packageName = "maps"
84         default:
85                 ctx.packageName = ctx.moduleName
86         }
87
88         // output file
89         packageDir := filepath.Join(outputDir, ctx.packageName)
90         outputFileName := ctx.packageName + outputFileExt
91         ctx.outputFile = filepath.Join(packageDir, outputFileName)
92
93         return ctx, nil
94 }
95
96 // generatePackage generates code for the parsed package data and writes it into w
97 func generatePackage(ctx *context, w io.Writer) error {
98         logf("generating package %q", ctx.packageName)
99
100         // generate file header
101         generateHeader(ctx, w)
102         generateImports(ctx, w)
103
104         // generate module desc
105         fmt.Fprintln(w, "const (")
106         fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
107         fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName)
108
109         if ctx.includeAPIVersion {
110                 if ctx.packageData.Version != "" {
111                         fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
112                         fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
113                 }
114                 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
115                 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
116         }
117         fmt.Fprintln(w, ")")
118         fmt.Fprintln(w)
119
120         // generate enums
121         if len(ctx.packageData.Enums) > 0 {
122                 fmt.Fprintf(w, "/* Enums */\n\n")
123
124                 for _, enum := range ctx.packageData.Enums {
125                         generateEnum(ctx, w, &enum)
126                 }
127         }
128
129         // generate aliases
130         if len(ctx.packageData.Aliases) > 0 {
131                 fmt.Fprintf(w, "/* Aliases */\n\n")
132
133                 for _, alias := range ctx.packageData.Aliases {
134                         generateAlias(ctx, w, &alias)
135                 }
136         }
137
138         // generate types
139         if len(ctx.packageData.Types) > 0 {
140                 fmt.Fprintf(w, "/* Types */\n\n")
141
142                 for _, typ := range ctx.packageData.Types {
143                         generateType(ctx, w, &typ)
144                 }
145         }
146
147         // generate unions
148         if len(ctx.packageData.Unions) > 0 {
149                 fmt.Fprintf(w, "/* Unions */\n\n")
150
151                 for _, union := range ctx.packageData.Unions {
152                         generateUnion(ctx, w, &union)
153                 }
154         }
155
156         // generate messages
157         if len(ctx.packageData.Messages) > 0 {
158                 fmt.Fprintf(w, "/* Messages */\n\n")
159
160                 for _, msg := range ctx.packageData.Messages {
161                         generateMessage(ctx, w, &msg)
162                 }
163
164                 // generate message registrations
165                 fmt.Fprintln(w, "func init() {")
166                 for _, msg := range ctx.packageData.Messages {
167                         name := camelCaseName(msg.Name)
168                         fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
169                 }
170                 fmt.Fprintln(w, "}")
171                 fmt.Fprintln(w)
172
173                 // generate list of messages
174                 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
175                 fmt.Fprintln(w, "func AllMessages() []api.Message {")
176                 fmt.Fprintln(w, "\treturn []api.Message{")
177                 for _, msg := range ctx.packageData.Messages {
178                         name := camelCaseName(msg.Name)
179                         fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
180                 }
181                 fmt.Fprintln(w, "}")
182                 fmt.Fprintln(w, "}")
183         }
184
185         if ctx.includeServices {
186                 // generate services
187                 if len(ctx.packageData.Services) > 0 {
188                         generateServices(ctx, w, ctx.packageData.Services)
189                 }
190         }
191
192         return nil
193 }
194
195 // generateHeader writes generated package header into w
196 func generateHeader(ctx *context, w io.Writer) {
197         fmt.Fprintln(w, "// Code generated by GoVPP binapi-generator. DO NOT EDIT.")
198         fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
199         fmt.Fprintln(w)
200
201         fmt.Fprintln(w, "/*")
202         fmt.Fprintf(w, "Package %s is a generated from VPP binary API module '%s'.\n", ctx.packageName, ctx.moduleName)
203         fmt.Fprintln(w)
204         fmt.Fprintf(w, " The %s module consists of:\n", ctx.moduleName)
205         var printObjNum = func(obj string, num int) {
206                 if num > 0 {
207                         if num > 1 {
208                                 if strings.HasSuffix(obj, "s") {
209
210                                         obj += "es"
211                                 } else {
212                                         obj += "s"
213                                 }
214                         }
215                         fmt.Fprintf(w, "\t%3d %s\n", num, obj)
216                 }
217         }
218
219         printObjNum("enum", len(ctx.packageData.Enums))
220         printObjNum("alias", len(ctx.packageData.Aliases))
221         printObjNum("type", len(ctx.packageData.Types))
222         printObjNum("union", len(ctx.packageData.Unions))
223         printObjNum("message", len(ctx.packageData.Messages))
224         printObjNum("service", len(ctx.packageData.Services))
225         fmt.Fprintln(w, "*/")
226
227         fmt.Fprintf(w, "package %s\n", ctx.packageName)
228         fmt.Fprintln(w)
229 }
230
231 // generateImports writes generated package imports into w
232 func generateImports(ctx *context, w io.Writer) {
233         fmt.Fprintf(w, "import api \"%s\"\n", govppApiImportPath)
234         fmt.Fprintf(w, "import bytes \"%s\"\n", "bytes")
235         fmt.Fprintf(w, "import context \"%s\"\n", "context")
236         fmt.Fprintf(w, "import strconv \"%s\"\n", "strconv")
237         fmt.Fprintf(w, "import struc \"%s\"\n", "github.com/lunixbochs/struc")
238         fmt.Fprintln(w)
239
240         fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
241         fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
242         fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
243         fmt.Fprintf(w, "var _ = context.Background\n")
244         fmt.Fprintf(w, "var _ = strconv.Itoa\n")
245         fmt.Fprintf(w, "var _ = struc.Pack\n")
246         fmt.Fprintln(w)
247
248         fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
249         fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
250         fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
251         fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
252         fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
253         fmt.Fprintln(w)
254 }
255
256 // generateComment writes generated comment for the object into w
257 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
258         if objKind == "service" {
259                 fmt.Fprintf(w, "// %s represents VPP binary API services in %s module.\n", goName, ctx.moduleName)
260         } else {
261                 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s':\n", goName, objKind, vppName)
262         }
263
264         if !ctx.includeComments {
265                 return
266         }
267
268         var isNotSpace = func(r rune) bool {
269                 return !unicode.IsSpace(r)
270         }
271
272         // print out the source of the generated object
273         mapType := false
274         objFound := false
275         objTitle := fmt.Sprintf(`"%s",`, vppName)
276         switch objKind {
277         case "alias", "service":
278                 objTitle = fmt.Sprintf(`"%s": {`, vppName)
279                 mapType = true
280         }
281
282         inputBuff := bytes.NewBuffer(ctx.inputData)
283         inputLine := 0
284
285         var trimIndent string
286         var indent int
287         for {
288                 line, err := inputBuff.ReadString('\n')
289                 if err != nil {
290                         break
291                 }
292                 inputLine++
293
294                 noSpaceAt := strings.IndexFunc(line, isNotSpace)
295                 if !objFound {
296                         indent = strings.Index(line, objTitle)
297                         if indent == -1 {
298                                 continue
299                         }
300                         trimIndent = line[:indent]
301                         // If no other non-whitespace character then we are at the message header.
302                         if trimmed := strings.TrimSpace(line); trimmed == objTitle {
303                                 objFound = true
304                                 fmt.Fprintln(w, "//")
305                         }
306                 } else if noSpaceAt < indent {
307                         break // end of the definition in JSON for array types
308                 } else if objFound && mapType && noSpaceAt <= indent {
309                         fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
310                         break // end of the definition in JSON for map types (aliases, services)
311                 }
312                 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
313         }
314
315         fmt.Fprintln(w, "//")
316 }
317
318 // generateServices writes generated code for the Services interface into w
319 func generateServices(ctx *context, w io.Writer, services []Service) {
320         const apiName = "Service"
321         const implName = "service"
322
323         // generate services comment
324         generateComment(ctx, w, apiName, "services", "service")
325
326         // generate interface
327         fmt.Fprintf(w, "type %s interface {\n", apiName)
328         for _, svc := range services {
329                 generateServiceMethod(ctx, w, &svc)
330                 fmt.Fprintln(w)
331         }
332         fmt.Fprintln(w, "}")
333         fmt.Fprintln(w)
334
335         // generate client implementation
336         fmt.Fprintf(w, "type %s struct {\n", implName)
337         fmt.Fprintf(w, "\tch api.Channel\n")
338         fmt.Fprintln(w, "}")
339         fmt.Fprintln(w)
340
341         fmt.Fprintf(w, "func New%[1]s(ch api.Channel) %[1]s {\n", apiName)
342         fmt.Fprintf(w, "\treturn &%s{ch}\n", implName)
343         fmt.Fprintln(w, "}")
344         fmt.Fprintln(w)
345
346         for _, svc := range services {
347                 fmt.Fprintf(w, "func (c *%s) ", implName)
348                 generateServiceMethod(ctx, w, &svc)
349                 fmt.Fprintln(w, " {")
350                 if svc.Stream {
351                         // TODO: stream responses
352                         //fmt.Fprintf(w, "\tstream := make(chan *%s)\n", camelCaseName(svc.ReplyType))
353                         replyTyp := camelCaseName(svc.ReplyType)
354                         fmt.Fprintf(w, "\tvar dump []*%s\n", replyTyp)
355                         fmt.Fprintf(w, "\treq := c.ch.SendMultiRequest(in)\n")
356                         fmt.Fprintf(w, "\tfor {\n")
357                         fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
358                         fmt.Fprintf(w, "\tstop, err := req.ReceiveReply(m)\n")
359                         fmt.Fprintf(w, "\tif stop { break }\n")
360                         fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
361                         fmt.Fprintf(w, "\tdump = append(dump, m)\n")
362                         fmt.Fprintln(w, "}")
363                         fmt.Fprintf(w, "\treturn dump, nil\n")
364                 } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
365                         fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
366                         fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
367                         fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
368                         fmt.Fprintf(w, "\treturn out, nil\n")
369                 } else {
370                         fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
371                         fmt.Fprintf(w, "\treturn nil\n")
372                 }
373                 fmt.Fprintln(w, "}")
374                 fmt.Fprintln(w)
375         }
376
377         fmt.Fprintln(w)
378 }
379
380 // generateServiceMethod writes generated code for the service into w
381 func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
382         reqTyp := camelCaseName(svc.RequestType)
383
384         // method name is same as parameter type name by default
385         method := reqTyp
386         if svc.Stream {
387                 // use Dump as prefix instead of suffix for stream services
388                 if m := strings.TrimSuffix(method, "Dump"); method != m {
389                         method = "Dump" + m
390                 }
391         }
392
393         params := fmt.Sprintf("in *%s", reqTyp)
394         returns := "error"
395         if replyType := camelCaseName(svc.ReplyType); replyType != "" {
396                 replyTyp := fmt.Sprintf("*%s", replyType)
397                 if svc.Stream {
398                         // TODO: stream responses
399                         //replyTyp = fmt.Sprintf("<-chan %s", replyTyp)
400                         replyTyp = fmt.Sprintf("[]%s", replyTyp)
401                 }
402                 returns = fmt.Sprintf("(%s, error)", replyTyp)
403         }
404
405         fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
406 }
407
408 // generateEnum writes generated code for the enum into w
409 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
410         name := camelCaseName(enum.Name)
411         typ := binapiTypes[enum.Type]
412
413         logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
414
415         // generate enum comment
416         generateComment(ctx, w, name, enum.Name, "enum")
417
418         // generate enum definition
419         fmt.Fprintf(w, "type %s %s\n", name, typ)
420         fmt.Fprintln(w)
421
422         // generate enum entries
423         fmt.Fprintln(w, "const (")
424         for _, entry := range enum.Entries {
425                 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
426         }
427         fmt.Fprintln(w, ")")
428         fmt.Fprintln(w)
429
430         // generate enum conversion maps
431         fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
432         for _, entry := range enum.Entries {
433                 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
434         }
435         fmt.Fprintln(w, "}")
436         fmt.Fprintln(w)
437
438         fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
439         for _, entry := range enum.Entries {
440                 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
441         }
442         fmt.Fprintln(w, "}")
443         fmt.Fprintln(w)
444
445         fmt.Fprintf(w, "func (x %s) String() string {\n", name)
446         fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
447         fmt.Fprintf(w, "\tif ok { return s }\n")
448         fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
449         fmt.Fprintln(w, "}")
450         fmt.Fprintln(w)
451 }
452
453 // generateAlias writes generated code for the alias into w
454 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
455         name := camelCaseName(alias.Name)
456
457         logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
458
459         // generate struct comment
460         generateComment(ctx, w, name, alias.Name, "alias")
461
462         // generate struct definition
463         fmt.Fprintf(w, "type %s ", name)
464
465         if alias.Length > 0 {
466                 fmt.Fprintf(w, "[%d]", alias.Length)
467         }
468
469         dataType := convertToGoType(ctx, alias.Type)
470         fmt.Fprintf(w, "%s\n", dataType)
471
472         fmt.Fprintln(w)
473 }
474
475 // generateUnion writes generated code for the union into w
476 func generateUnion(ctx *context, w io.Writer, union *Union) {
477         name := camelCaseName(union.Name)
478
479         logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
480
481         // generate struct comment
482         generateComment(ctx, w, name, union.Name, "union")
483
484         // generate struct definition
485         fmt.Fprintln(w, "type", name, "struct {")
486
487         // maximum size for union
488         maxSize := getUnionSize(ctx, union)
489
490         // generate data field
491         fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
492
493         // generate end of the struct
494         fmt.Fprintln(w, "}")
495
496         // generate name getter
497         generateTypeNameGetter(w, name, union.Name)
498
499         // generate CRC getter
500         if union.CRC != "" {
501                 generateCrcGetter(w, name, union.CRC)
502         }
503
504         // generate getters for fields
505         for _, field := range union.Fields {
506                 fieldName := camelCaseName(field.Name)
507                 fieldType := convertToGoType(ctx, field.Type)
508                 generateUnionGetterSetter(w, name, fieldName, fieldType)
509         }
510
511         // generate union methods
512         //generateUnionMethods(w, name)
513
514         fmt.Fprintln(w)
515 }
516
517 // generateUnionMethods generates methods that implement struc.Custom
518 // interface to allow having XXX_uniondata field unexported
519 // TODO: do more testing when unions are actually used in some messages
520 /*func generateUnionMethods(w io.Writer, structName string) {
521         // generate struc.Custom implementation for union
522         fmt.Fprintf(w, `
523 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
524         var b = new(bytes.Buffer)
525         if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
526                 return 0, err
527         }
528         copy(p, b.Bytes())
529         return b.Len(), nil
530 }
531 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
532         return struc.UnpackWithOptions(r, u.union_data[:], opt)
533 }
534 func (u *%[1]s) Size(opt *struc.Options) int {
535         return len(u.union_data)
536 }
537 func (u *%[1]s) String() string {
538         return string(u.union_data[:])
539 }
540 `, structName)
541 }*/
542
543 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
544         fmt.Fprintf(w, `
545 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
546         u.Set%[2]s(a)
547         return
548 }
549 func (u *%[1]s) Set%[2]s(a %[3]s) {
550         var b = new(bytes.Buffer)
551         if err := struc.Pack(b, &a); err != nil {
552                 return
553         }
554         copy(u.%[4]s[:], b.Bytes())
555 }
556 func (u *%[1]s) Get%[2]s() (a %[3]s) {
557         var b = bytes.NewReader(u.%[4]s[:])
558         struc.Unpack(b, &a)
559         return
560 }
561 `, structName, getterField, getterStruct, unionDataField)
562 }
563
564 // generateType writes generated code for the type into w
565 func generateType(ctx *context, w io.Writer, typ *Type) {
566         name := camelCaseName(typ.Name)
567
568         logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
569
570         // generate struct comment
571         generateComment(ctx, w, name, typ.Name, "type")
572
573         // generate struct definition
574         fmt.Fprintf(w, "type %s struct {\n", name)
575
576         // generate struct fields
577         for i, field := range typ.Fields {
578                 // skip internal fields
579                 switch strings.ToLower(field.Name) {
580                 case crcField, msgIdField:
581                         continue
582                 }
583
584                 generateField(ctx, w, typ.Fields, i)
585         }
586
587         // generate end of the struct
588         fmt.Fprintln(w, "}")
589
590         // generate name getter
591         generateTypeNameGetter(w, name, typ.Name)
592
593         // generate CRC getter
594         if typ.CRC != "" {
595                 generateCrcGetter(w, name, typ.CRC)
596         }
597
598         fmt.Fprintln(w)
599 }
600
601 // generateMessage writes generated code for the message into w
602 func generateMessage(ctx *context, w io.Writer, msg *Message) {
603         name := camelCaseName(msg.Name)
604
605         logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
606
607         // generate struct comment
608         generateComment(ctx, w, name, msg.Name, "message")
609
610         // generate struct definition
611         fmt.Fprintf(w, "type %s struct {", name)
612
613         msgType := otherMessage
614         wasClientIndex := false
615
616         // generate struct fields
617         n := 0
618         for i, field := range msg.Fields {
619                 if i == 1 {
620                         if field.Name == clientIndexField {
621                                 // "client_index" as the second member,
622                                 // this might be an event message or a request
623                                 msgType = eventMessage
624                                 wasClientIndex = true
625                         } else if field.Name == contextField {
626                                 // reply needs "context" as the second member
627                                 msgType = replyMessage
628                         }
629                 } else if i == 2 {
630                         if wasClientIndex && field.Name == contextField {
631                                 // request needs "client_index" as the second member
632                                 // and "context" as the third member
633                                 msgType = requestMessage
634                         }
635                 }
636
637                 // skip internal fields
638                 switch strings.ToLower(field.Name) {
639                 case crcField, msgIdField:
640                         continue
641                 case clientIndexField, contextField:
642                         if n == 0 {
643                                 continue
644                         }
645                 }
646                 n++
647                 if n == 1 {
648                         fmt.Fprintln(w)
649                 }
650
651                 generateField(ctx, w, msg.Fields, i)
652         }
653
654         // generate end of the struct
655         fmt.Fprintln(w, "}")
656
657         // generate name getter
658         generateMessageNameGetter(w, name, msg.Name)
659
660         // generate CRC getter
661         generateCrcGetter(w, name, msg.CRC)
662
663         // generate message type getter method
664         generateMessageTypeGetter(w, name, msgType)
665
666         fmt.Fprintln(w)
667 }
668
669 // generateField writes generated code for the field into w
670 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
671         field := fields[i]
672
673         fieldName := strings.TrimPrefix(field.Name, "_")
674         fieldName = camelCaseName(fieldName)
675
676         // generate length field for strings
677         if field.Type == "string" {
678                 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
679         }
680
681         dataType := convertToGoType(ctx, field.Type)
682         fieldType := dataType
683
684         // check if it is array
685         if field.Length > 0 || field.SizeFrom != "" {
686                 if dataType == "uint8" {
687                         dataType = "byte"
688                 }
689                 fieldType = "[]" + dataType
690         }
691         fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
692
693         fieldTags := map[string]string{}
694
695         if field.Length > 0 {
696                 // fixed size array
697                 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
698         } else {
699                 for _, f := range fields {
700                         if f.SizeFrom == field.Name {
701                                 // variable sized array
702                                 sizeOfName := camelCaseName(f.Name)
703                                 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
704                         }
705                 }
706         }
707
708         if ctx.includeBinapiNames {
709                 fieldTags["binapi"] = field.Name
710         }
711         if field.Meta.Limit > 0 {
712                 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
713         }
714
715         if len(fieldTags) > 0 {
716                 fmt.Fprintf(w, "\t`")
717                 var keys []string
718                 for k := range fieldTags {
719                         keys = append(keys, k)
720                 }
721                 sort.Strings(keys)
722                 var n int
723                 for _, tt := range keys {
724                         t, ok := fieldTags[tt]
725                         if !ok {
726                                 continue
727                         }
728                         if n > 0 {
729                                 fmt.Fprintf(w, " ")
730                         }
731                         n++
732                         fmt.Fprintf(w, `%s:"%s"`, tt, t)
733                 }
734                 fmt.Fprintf(w, "`")
735         }
736
737         fmt.Fprintln(w)
738 }
739
740 // generateMessageNameGetter generates getter for original VPP message name into the provider writer
741 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
742         fmt.Fprintf(w, `func (*%s) GetMessageName() string {
743         return %q
744 }
745 `, structName, msgName)
746 }
747
748 // generateTypeNameGetter generates getter for original VPP type name into the provider writer
749 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
750         fmt.Fprintf(w, `func (*%s) GetTypeName() string {
751         return %q
752 }
753 `, structName, msgName)
754 }
755
756 // generateCrcGetter generates getter for CRC checksum of the message definition into the provider writer
757 func generateCrcGetter(w io.Writer, structName, crc string) {
758         crc = strings.TrimPrefix(crc, "0x")
759         fmt.Fprintf(w, `func (*%s) GetCrcString() string {
760         return %q
761 }
762 `, structName, crc)
763 }
764
765 // generateMessageTypeGetter generates message factory for the generated message into the provider writer
766 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
767         fmt.Fprintln(w, "func (*"+structName+") GetMessageType() api.MessageType {")
768         if msgType == requestMessage {
769                 fmt.Fprintln(w, "\treturn api.RequestMessage")
770         } else if msgType == replyMessage {
771                 fmt.Fprintln(w, "\treturn api.ReplyMessage")
772         } else if msgType == eventMessage {
773                 fmt.Fprintln(w, "\treturn api.EventMessage")
774         } else {
775                 fmt.Fprintln(w, "\treturn api.OtherMessage")
776         }
777         fmt.Fprintln(w, "}")
778 }