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