Refactored binapi generator with message encoding
[govpp.git] / binapigen / generate.go
1 //  Copyright (c) 2020 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 binapigen
16
17 import (
18         "bytes"
19         "fmt"
20         "io"
21         "os/exec"
22         "path"
23         "path/filepath"
24         "sort"
25         "strings"
26
27         "git.fd.io/govpp.git/version"
28 )
29
30 // generatedCodeVersion indicates a version of the generated code.
31 // It is incremented whenever an incompatibility between the generated code and
32 // GoVPP api package is introduced; the generated code references
33 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
34 const generatedCodeVersion = 2
35
36 // message field names
37 const (
38         msgIdField       = "_vl_msg_id"
39         clientIndexField = "client_index"
40         contextField     = "context"
41         retvalField      = "retval"
42 )
43
44 const (
45         outputFileExt = ".ba.go" // file extension of the Go generated files
46         rpcFileSuffix = "_rpc"   // file name suffix for the RPC services
47
48         constModuleName = "ModuleName" // module name constant
49         constAPIVersion = "APIVersion" // API version constant
50         constVersionCrc = "VersionCrc" // version CRC constant
51
52         unionDataField = "XXX_UnionData" // name for the union data field
53
54         serviceApiName    = "RPCService"    // name for the RPC service interface
55         serviceImplName   = "serviceClient" // name for the RPC service implementation
56         serviceClientName = "ServiceClient" // name for the RPC service client
57
58         // TODO: register service descriptor
59         //serviceDescType = "ServiceDesc"             // name for service descriptor type
60         //serviceDescName = "_ServiceRPC_serviceDesc" // name for service descriptor var
61 )
62
63 // MessageType represents the type of a VPP message
64 type MessageType int
65
66 const (
67         requestMessage MessageType = iota // VPP request message
68         replyMessage                      // VPP reply message
69         eventMessage                      // VPP event message
70         otherMessage                      // other VPP message
71 )
72
73 type GenFile struct {
74         *Generator
75         filename   string
76         file       *File
77         packageDir string
78         buf        bytes.Buffer
79 }
80
81 func generatePackage(ctx *GenFile, w io.Writer) {
82         logf("----------------------------")
83         logf("generating binapi package: %q", ctx.file.PackageName)
84         logf("----------------------------")
85
86         generateHeader(ctx, w)
87         generateImports(ctx, w)
88
89         // generate module desc
90         fmt.Fprintln(w, "const (")
91         fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
92         fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.file.Name)
93
94         if ctx.IncludeAPIVersion {
95                 fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
96                 fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.file.Version())
97                 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
98                 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.file.CRC)
99         }
100         fmt.Fprintln(w, ")")
101         fmt.Fprintln(w)
102
103         // generate enums
104         if len(ctx.file.Enums) > 0 {
105                 for _, enum := range ctx.file.Enums {
106                         if imp, ok := ctx.file.imports[enum.Name]; ok {
107                                 generateImportedAlias(ctx, w, enum.GoName, imp)
108                                 continue
109                         }
110                         generateEnum(ctx, w, enum)
111                 }
112         }
113
114         // generate aliases
115         if len(ctx.file.Aliases) > 0 {
116                 for _, alias := range ctx.file.Aliases {
117                         if imp, ok := ctx.file.imports[alias.Name]; ok {
118                                 generateImportedAlias(ctx, w, alias.GoName, imp)
119                                 continue
120                         }
121                         generateAlias(ctx, w, alias)
122                 }
123         }
124
125         // generate types
126         if len(ctx.file.Structs) > 0 {
127                 for _, typ := range ctx.file.Structs {
128                         if imp, ok := ctx.file.imports[typ.Name]; ok {
129                                 generateImportedAlias(ctx, w, typ.GoName, imp)
130                                 continue
131                         }
132                         generateStruct(ctx, w, typ)
133                 }
134         }
135
136         // generate unions
137         if len(ctx.file.Unions) > 0 {
138                 for _, union := range ctx.file.Unions {
139                         if imp, ok := ctx.file.imports[union.Name]; ok {
140                                 generateImportedAlias(ctx, w, union.GoName, imp)
141                                 continue
142                         }
143                         generateUnion(ctx, w, union)
144                 }
145         }
146
147         // generate messages
148         if len(ctx.file.Messages) > 0 {
149                 for _, msg := range ctx.file.Messages {
150                         generateMessage(ctx, w, msg)
151                 }
152
153                 initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName)
154
155                 // generate message registrations
156                 fmt.Fprintf(w, "func init() { %s() }\n", initFnName)
157                 fmt.Fprintf(w, "func %s() {\n", initFnName)
158                 for _, msg := range ctx.file.Messages {
159                         fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n",
160                                 msg.GoName, ctx.file.Name+"."+msg.GoName)
161                 }
162                 fmt.Fprintln(w, "}")
163                 fmt.Fprintln(w)
164
165                 // generate list of messages
166                 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
167                 fmt.Fprintln(w, "func AllMessages() []api.Message {")
168                 fmt.Fprintln(w, "\treturn []api.Message{")
169                 for _, msg := range ctx.file.Messages {
170                         fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName)
171                 }
172                 fmt.Fprintln(w, "}")
173                 fmt.Fprintln(w, "}")
174         }
175
176         generateFooter(ctx, w)
177
178 }
179
180 func generateHeader(ctx *GenFile, w io.Writer) {
181         fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
182         fmt.Fprintln(w, "// versions:")
183         fmt.Fprintf(w, "//  binapi-generator: %s\n", version.Version())
184         if ctx.IncludeVppVersion {
185                 fmt.Fprintf(w, "//  VPP:              %s\n", ctx.VPPVersion)
186         }
187         fmt.Fprintf(w, "// source: %s\n", ctx.file.Path)
188         fmt.Fprintln(w)
189
190         fmt.Fprintln(w, "/*")
191         fmt.Fprintf(w, "Package %s contains generated code for VPP binary API defined by %s.api (version %s).\n",
192                 ctx.file.PackageName, ctx.file.Name, ctx.file.Version())
193         fmt.Fprintln(w)
194         fmt.Fprintln(w, "It consists of:")
195         printObjNum := func(obj string, num int) {
196                 if num > 0 {
197                         if num > 1 {
198                                 if strings.HasSuffix(obj, "s") {
199
200                                         obj += "es"
201                                 } else {
202                                         obj += "s"
203                                 }
204                         }
205                         fmt.Fprintf(w, "\t%3d %s\n", num, obj)
206                 }
207         }
208         //printObjNum("RPC", len(ctx.file.Service.RPCs))
209         printObjNum("alias", len(ctx.file.Aliases))
210         printObjNum("enum", len(ctx.file.Enums))
211         printObjNum("message", len(ctx.file.Messages))
212         printObjNum("type", len(ctx.file.Structs))
213         printObjNum("union", len(ctx.file.Unions))
214         fmt.Fprintln(w, "*/")
215         fmt.Fprintf(w, "package %s\n", ctx.file.PackageName)
216         fmt.Fprintln(w)
217 }
218
219 func generateImports(ctx *GenFile, w io.Writer) {
220         fmt.Fprintln(w, "import (")
221         fmt.Fprintln(w, `       "bytes"`)
222         fmt.Fprintln(w, `       "context"`)
223         fmt.Fprintln(w, `       "encoding/binary"`)
224         fmt.Fprintln(w, `       "io"`)
225         fmt.Fprintln(w, `       "math"`)
226         fmt.Fprintln(w, `       "strconv"`)
227         fmt.Fprintln(w)
228         fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
229         fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec")
230         fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
231         if len(ctx.file.Imports) > 0 {
232                 fmt.Fprintln(w)
233                 for _, imp := range ctx.file.Imports {
234                         importPath := path.Join(ctx.ImportPrefix, imp)
235                         if ctx.ImportPrefix == "" {
236                                 importPath = getImportPath(ctx.packageDir, imp)
237                         }
238                         fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
239                 }
240         }
241         fmt.Fprintln(w, ")")
242         fmt.Fprintln(w)
243
244         fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
245         fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
246         fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
247         fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
248         fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
249         fmt.Fprintln(w)
250 }
251
252 func getImportPath(outputDir string, pkg string) string {
253         absPath, err := filepath.Abs(filepath.Join(outputDir, "..", pkg))
254         if err != nil {
255                 panic(err)
256         }
257         cmd := exec.Command("go", "list", absPath)
258         var errbuf, outbuf bytes.Buffer
259         cmd.Stdout = &outbuf
260         cmd.Stderr = &errbuf
261         if err := cmd.Run(); err != nil {
262                 fmt.Printf("ERR: %v\n", errbuf.String())
263                 panic(err)
264         }
265         return outbuf.String()
266 }
267
268 func generateFooter(ctx *GenFile, w io.Writer) {
269         fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
270         fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
271         fmt.Fprintf(w, "var _ = codec.DecodeString\n")
272         fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
273         fmt.Fprintf(w, "var _ = context.Background\n")
274         fmt.Fprintf(w, "var _ = io.Copy\n")
275         fmt.Fprintf(w, "var _ = strconv.Itoa\n")
276         fmt.Fprintf(w, "var _ = struc.Pack\n")
277         fmt.Fprintf(w, "var _ = binary.BigEndian\n")
278         fmt.Fprintf(w, "var _ = math.Float32bits\n")
279 }
280
281 func generateComment(ctx *GenFile, w io.Writer, goName string, vppName string, objKind string) {
282         if objKind == "service" {
283                 fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.file.Name)
284         } else {
285                 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
286         }
287 }
288
289 func generateEnum(ctx *GenFile, w io.Writer, enum *Enum) {
290         name := enum.GoName
291         typ := binapiTypes[enum.Type]
292
293         logf(" writing ENUM %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
294
295         // generate enum comment
296         generateComment(ctx, w, name, enum.Name, "enum")
297
298         // generate enum definition
299         fmt.Fprintf(w, "type %s %s\n", name, typ)
300         fmt.Fprintln(w)
301
302         // generate enum entries
303         fmt.Fprintln(w, "const (")
304         for _, entry := range enum.Entries {
305                 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
306         }
307         fmt.Fprintln(w, ")")
308         fmt.Fprintln(w)
309
310         // generate enum conversion maps
311         fmt.Fprintln(w, "var (")
312         fmt.Fprintf(w, "%s_name = map[%s]string{\n", name, typ)
313         for _, entry := range enum.Entries {
314                 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
315         }
316         fmt.Fprintln(w, "}")
317         fmt.Fprintf(w, "%s_value = map[string]%s{\n", name, typ)
318         for _, entry := range enum.Entries {
319                 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
320         }
321         fmt.Fprintln(w, "}")
322         fmt.Fprintln(w, ")")
323         fmt.Fprintln(w)
324
325         fmt.Fprintf(w, "func (x %s) String() string {\n", name)
326         fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
327         fmt.Fprintf(w, "\tif ok { return s }\n")
328         fmt.Fprintf(w, "\treturn \"%s(\" + strconv.Itoa(int(x)) + \")\"\n", name)
329         fmt.Fprintln(w, "}")
330         fmt.Fprintln(w)
331 }
332
333 func generateImportedAlias(ctx *GenFile, w io.Writer, name string, imp string) {
334         fmt.Fprintf(w, "type %s = %s.%s\n", name, imp, name)
335         fmt.Fprintln(w)
336 }
337
338 func generateAlias(ctx *GenFile, w io.Writer, alias *Alias) {
339         name := alias.GoName
340
341         logf(" writing ALIAS %q (%s), length: %d", alias.Name, name, alias.Length)
342
343         // generate struct comment
344         generateComment(ctx, w, name, alias.Name, "alias")
345
346         // generate struct definition
347         fmt.Fprintf(w, "type %s ", name)
348
349         if alias.Length > 0 {
350                 fmt.Fprintf(w, "[%d]", alias.Length)
351         }
352
353         dataType := convertToGoType(ctx.file, alias.Type)
354         fmt.Fprintf(w, "%s\n", dataType)
355
356         fmt.Fprintln(w)
357 }
358
359 func generateStruct(ctx *GenFile, w io.Writer, typ *Struct) {
360         name := typ.GoName
361
362         logf(" writing STRUCT %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
363
364         // generate struct comment
365         generateComment(ctx, w, name, typ.Name, "type")
366
367         // generate struct definition
368         fmt.Fprintf(w, "type %s struct {\n", name)
369
370         // generate struct fields
371         for i := range typ.Fields {
372                 // skip internal fields
373                 switch strings.ToLower(typ.Name) {
374                 case msgIdField:
375                         continue
376                 }
377
378                 generateField(ctx, w, typ.Fields, i)
379         }
380
381         // generate end of the struct
382         fmt.Fprintln(w, "}")
383
384         // generate name getter
385         generateTypeNameGetter(w, name, typ.Name)
386
387         fmt.Fprintln(w)
388 }
389
390 // generateUnionMethods generates methods that implement struc.Custom
391 // interface to allow having XXX_uniondata field unexported
392 // TODO: do more testing when unions are actually used in some messages
393 /*func generateUnionMethods(w io.Writer, structName string) {
394         // generate struc.Custom implementation for union
395         fmt.Fprintf(w, `
396 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
397         var b = new(bytes.Buffer)
398         if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
399                 return 0, err
400         }
401         copy(p, b.Bytes())
402         return b.Len(), nil
403 }
404 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
405         return struc.UnpackWithOptions(r, u.union_data[:], opt)
406 }
407 func (u *%[1]s) Size(opt *struc.Options) int {
408         return len(u.union_data)
409 }
410 func (u *%[1]s) String() string {
411         return string(u.union_data[:])
412 }
413 `, structName)
414 }*/
415
416 /*func generateUnionGetterSetterNew(w io.Writer, structName string, getterField, getterStruct string) {
417         fmt.Fprintf(w, `
418 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
419         u.Set%[2]s(a)
420         return
421 }
422 func (u *%[1]s) Set%[2]s(a %[3]s) {
423         copy(u.%[4]s[:], a[:])
424 }
425 func (u *%[1]s) Get%[2]s() (a %[3]s) {
426         copy(a[:], u.%[4]s[:])
427         return
428 }
429 `, structName, getterField, getterStruct, unionDataField)
430 }*/
431
432 func generateUnion(ctx *GenFile, w io.Writer, union *Union) {
433         name := union.GoName
434
435         logf(" writing UNION %q (%s) with %d fields", union.Name, name, len(union.Fields))
436
437         // generate struct comment
438         generateComment(ctx, w, name, union.Name, "union")
439
440         // generate struct definition
441         fmt.Fprintln(w, "type", name, "struct {")
442
443         // maximum size for union
444         maxSize := getUnionSize(ctx.file, union)
445
446         // generate data field
447         fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
448
449         // generate end of the struct
450         fmt.Fprintln(w, "}")
451
452         // generate name getter
453         generateTypeNameGetter(w, name, union.Name)
454
455         // generate getters for fields
456         for _, field := range union.Fields {
457                 fieldType := convertToGoType(ctx.file, field.Type)
458                 generateUnionGetterSetter(w, name, field.GoName, fieldType)
459         }
460
461         // generate union methods
462         //generateUnionMethods(w, name)
463
464         fmt.Fprintln(w)
465 }
466
467 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
468         fmt.Fprintf(w, `
469 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
470         u.Set%[2]s(a)
471         return
472 }
473 func (u *%[1]s) Set%[2]s(a %[3]s) {
474         var b = new(bytes.Buffer)
475         if err := struc.Pack(b, &a); err != nil {
476                 return
477         }
478         copy(u.%[4]s[:], b.Bytes())
479 }
480 func (u *%[1]s) Get%[2]s() (a %[3]s) {
481         var b = bytes.NewReader(u.%[4]s[:])
482         struc.Unpack(b, &a)
483         return
484 }
485 `, structName, getterField, getterStruct, unionDataField)
486 }
487
488 func generateMessage(ctx *GenFile, w io.Writer, msg *Message) {
489         name := msg.GoName
490
491         logf(" writing MESSAGE %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
492
493         // generate struct comment
494         generateComment(ctx, w, name, msg.Name, "message")
495
496         // generate struct definition
497         fmt.Fprintf(w, "type %s struct {", name)
498
499         msgType := otherMessage
500         wasClientIndex := false
501
502         // generate struct fields
503         n := 0
504         for i, field := range msg.Fields {
505                 if i == 1 {
506                         if field.Name == clientIndexField {
507                                 // "client_index" as the second member,
508                                 // this might be an event message or a request
509                                 msgType = eventMessage
510                                 wasClientIndex = true
511                         } else if field.Name == contextField {
512                                 // reply needs "context" as the second member
513                                 msgType = replyMessage
514                         }
515                 } else if i == 2 {
516                         if wasClientIndex && field.Name == contextField {
517                                 // request needs "client_index" as the second member
518                                 // and "context" as the third member
519                                 msgType = requestMessage
520                         }
521                 }
522
523                 // skip internal fields
524                 switch strings.ToLower(field.Name) {
525                 case /*crcField,*/ msgIdField:
526                         continue
527                 case clientIndexField, contextField:
528                         if n == 0 {
529                                 continue
530                         }
531                 }
532                 n++
533                 if n == 1 {
534                         fmt.Fprintln(w)
535                 }
536
537                 generateField(ctx, w, msg.Fields, i)
538         }
539
540         // generate end of the struct
541         fmt.Fprintln(w, "}")
542
543         // generate message methods
544         generateMessageResetMethod(w, name)
545         generateMessageNameGetter(w, name, msg.Name)
546         generateCrcGetter(w, name, msg.CRC)
547         generateMessageTypeGetter(w, name, msgType)
548         generateMessageSize(ctx, w, name, msg.Fields)
549         generateMessageMarshal(ctx, w, name, msg.Fields)
550         generateMessageUnmarshal(ctx, w, name, msg.Fields)
551
552         fmt.Fprintln(w)
553 }
554
555 func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field) {
556         fmt.Fprintf(w, "func (m *%[1]s) Size() int {\n", name)
557
558         fmt.Fprintf(w, "\tif m == nil { return 0 }\n")
559         fmt.Fprintf(w, "\tvar size int\n")
560
561         encodeBaseType := func(typ, name string, length int, sizefrom string) bool {
562                 t, ok := BaseTypeNames[typ]
563                 if !ok {
564                         return false
565                 }
566
567                 var s = BaseTypeSizes[t]
568                 switch t {
569                 case STRING:
570                         if length > 0 {
571                                 s = length
572                                 fmt.Fprintf(w, "\tsize += %d\n", s)
573                         } else {
574                                 s = 4
575                                 fmt.Fprintf(w, "\tsize += %d + len(%s)\n", s, name)
576                         }
577                 default:
578                         if sizefrom != "" {
579                                 //fmt.Fprintf(w, "\tsize += %d * int(%s)\n", s, sizefrom)
580                                 fmt.Fprintf(w, "\tsize += %d * len(%s)\n", s, name)
581                         } else {
582                                 if length > 0 {
583                                         s = BaseTypeSizes[t] * length
584                                 }
585                                 fmt.Fprintf(w, "\tsize += %d\n", s)
586                         }
587                 }
588
589                 return true
590         }
591
592         lvl := 0
593         var encodeFields func(fields []*Field, parentName string)
594         encodeFields = func(fields []*Field, parentName string) {
595                 lvl++
596                 defer func() { lvl-- }()
597
598                 n := 0
599                 for _, field := range fields {
600                         // skip internal fields
601                         switch strings.ToLower(field.Name) {
602                         case /*crcField,*/ msgIdField:
603                                 continue
604                         case clientIndexField, contextField:
605                                 if n == 0 {
606                                         continue
607                                 }
608                         }
609                         n++
610
611                         fieldName := field.GoName //camelCaseName(strings.TrimPrefix(field.Name, "_"))
612                         name := fmt.Sprintf("%s.%s", parentName, fieldName)
613                         sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_"))
614                         var sizeFromName string
615                         if sizeFrom != "" {
616                                 sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom)
617                         }
618
619                         fmt.Fprintf(w, "\t// field[%d] %s\n", lvl, name)
620
621                         if encodeBaseType(field.Type, name, field.Length, sizeFromName) {
622                                 continue
623                         }
624
625                         char := fmt.Sprintf("s%d", lvl)
626                         index := fmt.Sprintf("j%d", lvl)
627
628                         if field.Array {
629                                 if field.Length > 0 {
630                                         fmt.Fprintf(w, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index)
631                                 } else if field.SizeFrom != "" {
632                                         //fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom)
633                                         fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < len(%[2]s); %[1]s++ {\n", index, name)
634                                 }
635
636                                 fmt.Fprintf(w, "\tvar %[1]s %[2]s\n_ = %[1]s\n", char, convertToGoType(ctx.file, field.Type))
637                                 fmt.Fprintf(w, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char)
638                                 name = char
639                         }
640
641                         if enum := getEnumByRef(ctx.file, field.Type); enum != nil {
642                                 if encodeBaseType(enum.Type, name, 0, "") {
643                                 } else {
644                                         fmt.Fprintf(w, "\t// ??? ENUM %s %s\n", name, enum.Type)
645                                 }
646                         } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil {
647                                 if encodeBaseType(alias.Type, name, alias.Length, "") {
648                                 } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil {
649                                         encodeFields(typ.Fields, name)
650                                 } else {
651                                         fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type)
652                                 }
653                         } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil {
654                                 encodeFields(typ.Fields, name)
655                         } else if union := getUnionByRef(ctx.file, field.Type); union != nil {
656                                 maxSize := getUnionSize(ctx.file, union)
657                                 fmt.Fprintf(w, "\tsize += %d\n", maxSize)
658                         } else {
659                                 fmt.Fprintf(w, "\t// ??? buf[pos] = (%s)\n", name)
660                         }
661
662                         if field.Array {
663                                 fmt.Fprintf(w, "\t}\n")
664                         }
665                 }
666         }
667
668         encodeFields(fields, "m")
669
670         fmt.Fprintf(w, "return size\n")
671
672         fmt.Fprintf(w, "}\n")
673 }
674
675 func generateMessageMarshal(ctx *GenFile, w io.Writer, name string, fields []*Field) {
676         fmt.Fprintf(w, "func (m *%[1]s) Marshal(b []byte) ([]byte, error) {\n", name)
677
678         fmt.Fprintf(w, "\to := binary.BigEndian\n")
679         fmt.Fprintf(w, "\t_ = o\n")
680         fmt.Fprintf(w, "\tpos := 0\n")
681         fmt.Fprintf(w, "\t_ = pos\n")
682
683         var buf = new(strings.Builder)
684
685         encodeBaseType := func(typ, name string, length int, sizefrom string) bool {
686                 t, ok := BaseTypeNames[typ]
687                 if !ok {
688                         return false
689                 }
690
691                 isArray := length > 0 || sizefrom != ""
692
693                 switch t {
694                 case I8, U8, I16, U16, I32, U32, I64, U64, F64:
695                         if isArray {
696                                 if length != 0 {
697                                         fmt.Fprintf(buf, "\tfor i := 0; i < %d; i++ {\n", length)
698                                 } else if sizefrom != "" {
699                                         //fmt.Fprintf(buf, "\tfor i := 0; i < int(%s); i++ {\n", sizefrom)
700                                         fmt.Fprintf(buf, "\tfor i := 0; i < len(%s); i++ {\n", name)
701                                 }
702                         }
703                 }
704
705                 switch t {
706                 case I8, U8:
707                         if isArray {
708                                 fmt.Fprintf(buf, "\tvar x uint8\n")
709                                 fmt.Fprintf(buf, "\tif i < len(%s) { x = uint8(%s[i]) }\n", name, name)
710                                 name = "x"
711                         }
712                         fmt.Fprintf(buf, "\tbuf[pos] = uint8(%s)\n", name)
713                         fmt.Fprintf(buf, "\tpos += 1\n")
714                         if isArray {
715                                 fmt.Fprintf(buf, "\t}\n")
716                         }
717                 case I16, U16:
718                         if isArray {
719                                 fmt.Fprintf(buf, "\tvar x uint16\n")
720                                 fmt.Fprintf(buf, "\tif i < len(%s) { x = uint16(%s[i]) }\n", name, name)
721                                 name = "x"
722                         }
723                         fmt.Fprintf(buf, "\to.PutUint16(buf[pos:pos+2], uint16(%s))\n", name)
724                         fmt.Fprintf(buf, "\tpos += 2\n")
725                         if isArray {
726                                 fmt.Fprintf(buf, "\t}\n")
727                         }
728                 case I32, U32:
729                         if isArray {
730                                 fmt.Fprintf(buf, "\tvar x uint32\n")
731                                 fmt.Fprintf(buf, "\tif i < len(%s) { x = uint32(%s[i]) }\n", name, name)
732                                 name = "x"
733                         }
734                         fmt.Fprintf(buf, "\to.PutUint32(buf[pos:pos+4], uint32(%s))\n", name)
735                         fmt.Fprintf(buf, "\tpos += 4\n")
736                         if isArray {
737                                 fmt.Fprintf(buf, "\t}\n")
738                         }
739                 case I64, U64:
740                         if isArray {
741                                 fmt.Fprintf(buf, "\tvar x uint64\n")
742                                 fmt.Fprintf(buf, "\tif i < len(%s) { x = uint64(%s[i]) }\n", name, name)
743                                 name = "x"
744                         }
745                         fmt.Fprintf(buf, "\to.PutUint64(buf[pos:pos+8], uint64(%s))\n", name)
746                         fmt.Fprintf(buf, "\tpos += 8\n")
747                         if isArray {
748                                 fmt.Fprintf(buf, "\t}\n")
749                         }
750                 case F64:
751                         if isArray {
752                                 fmt.Fprintf(buf, "\tvar x float64\n")
753                                 fmt.Fprintf(buf, "\tif i < len(%s) { x = float64(%s[i]) }\n", name, name)
754                                 name = "x"
755                         }
756                         fmt.Fprintf(buf, "\to.PutUint64(buf[pos:pos+8], math.Float64bits(float64(%s)))\n", name)
757                         fmt.Fprintf(buf, "\tpos += 8\n")
758                         if isArray {
759                                 fmt.Fprintf(buf, "\t}\n")
760                         }
761                 case BOOL:
762                         fmt.Fprintf(buf, "\tif %s { buf[pos] = 1 }\n", name)
763                         fmt.Fprintf(buf, "\tpos += 1\n")
764                 case STRING:
765                         if length != 0 {
766                                 fmt.Fprintf(buf, "\tcopy(buf[pos:pos+%d], %s)\n", length, name)
767                                 fmt.Fprintf(buf, "\tpos += %d\n", length)
768                         } else {
769                                 fmt.Fprintf(buf, "\to.PutUint32(buf[pos:pos+4], uint32(len(%s)))\n", name)
770                                 fmt.Fprintf(buf, "\tpos += 4\n")
771                                 fmt.Fprintf(buf, "\tcopy(buf[pos:pos+len(%s)], %s[:])\n", name, name)
772                                 fmt.Fprintf(buf, "\tpos += len(%s)\n", name)
773                         }
774                 default:
775                         fmt.Fprintf(buf, "\t// ??? %s %s\n", name, typ)
776                         return false
777                 }
778                 return true
779         }
780
781         lvl := 0
782         var encodeFields func(fields []*Field, parentName string)
783         encodeFields = func(fields []*Field, parentName string) {
784                 lvl++
785                 defer func() { lvl-- }()
786
787                 n := 0
788                 for _, field := range fields {
789                         // skip internal fields
790                         switch strings.ToLower(field.Name) {
791                         case /*crcField,*/ msgIdField:
792                                 continue
793                         case clientIndexField, contextField:
794                                 if n == 0 {
795                                         continue
796                                 }
797                         }
798                         n++
799
800                         getFieldName := func(name string) string {
801                                 fieldName := camelCaseName(strings.TrimPrefix(name, "_"))
802                                 return fmt.Sprintf("%s.%s", parentName, fieldName)
803                         }
804
805                         fieldName := camelCaseName(strings.TrimPrefix(field.Name, "_"))
806                         name := fmt.Sprintf("%s.%s", parentName, fieldName)
807                         sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_"))
808                         var sizeFromName string
809                         if sizeFrom != "" {
810                                 sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom)
811                         }
812
813                         fmt.Fprintf(buf, "\t// field[%d] %s\n", lvl, name)
814
815                         getSizeOfField := func() *Field {
816                                 for _, f := range fields {
817                                         if f.SizeFrom == field.Name {
818                                                 return f
819                                         }
820                                 }
821                                 return nil
822                         }
823                         if f := getSizeOfField(); f != nil {
824                                 if encodeBaseType(field.Type, fmt.Sprintf("len(%s)", getFieldName(f.Name)), field.Length, "") {
825                                         continue
826                                 }
827                                 panic(fmt.Sprintf("failed to encode base type of sizefrom field: %s", field.Name))
828                         }
829
830                         if encodeBaseType(field.Type, name, field.Length, sizeFromName) {
831                                 continue
832                         }
833
834                         char := fmt.Sprintf("v%d", lvl)
835                         index := fmt.Sprintf("j%d", lvl)
836
837                         if field.Array {
838                                 if field.Length > 0 {
839                                         fmt.Fprintf(buf, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index)
840                                 } else if field.SizeFrom != "" {
841                                         //fmt.Fprintf(buf, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom)
842                                         fmt.Fprintf(buf, "\tfor %[1]s := 0; %[1]s < len(%[2]s); %[1]s++ {\n", index, name)
843                                 }
844
845                                 fmt.Fprintf(buf, "\tvar %s %s\n", char, convertToGoType(ctx.file, field.Type))
846                                 fmt.Fprintf(buf, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char)
847                                 name = char
848                         }
849
850                         if enum := getEnumByRef(ctx.file, field.Type); enum != nil {
851                                 if encodeBaseType(enum.Type, name, 0, "") {
852                                 } else {
853                                         fmt.Fprintf(buf, "\t// ??? ENUM %s %s\n", name, enum.Type)
854                                 }
855                         } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil {
856                                 if encodeBaseType(alias.Type, name, alias.Length, "") {
857                                 } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil {
858                                         encodeFields(typ.Fields, name)
859                                 } else {
860                                         fmt.Fprintf(buf, "\t// ??? ALIAS %s %s\n", name, alias.Type)
861                                 }
862                         } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil {
863                                 encodeFields(typ.Fields, name)
864                         } else if union := getUnionByRef(ctx.file, field.Type); union != nil {
865                                 maxSize := getUnionSize(ctx.file, union)
866                                 fmt.Fprintf(buf, "\tcopy(buf[pos:pos+%d], %s.%s[:])\n", maxSize, name, unionDataField)
867                                 fmt.Fprintf(buf, "\tpos += %d\n", maxSize)
868                         } else {
869                                 fmt.Fprintf(buf, "\t// ??? buf[pos] = (%s)\n", name)
870                         }
871
872                         if field.Array {
873                                 fmt.Fprintf(buf, "\t}\n")
874                         }
875                 }
876         }
877
878         encodeFields(fields, "m")
879
880         fmt.Fprintf(w, "\tvar buf []byte\n")
881         fmt.Fprintf(w, "\tif b == nil {\n")
882         fmt.Fprintf(w, "\tbuf = make([]byte, m.Size())\n")
883         fmt.Fprintf(w, "\t} else {\n")
884         fmt.Fprintf(w, "\tbuf = b\n")
885         fmt.Fprintf(w, "\t}\n")
886         fmt.Fprint(w, buf.String())
887
888         fmt.Fprintf(w, "return buf, nil\n")
889
890         fmt.Fprintf(w, "}\n")
891 }
892
893 func generateMessageUnmarshal(ctx *GenFile, w io.Writer, name string, fields []*Field) {
894         fmt.Fprintf(w, "func (m *%[1]s) Unmarshal(tmp []byte) error {\n", name)
895
896         fmt.Fprintf(w, "\to := binary.BigEndian\n")
897         fmt.Fprintf(w, "\t_ = o\n")
898         fmt.Fprintf(w, "\tpos := 0\n")
899         fmt.Fprintf(w, "\t_ = pos\n")
900
901         decodeBaseType := func(typ, orig, name string, length int, sizefrom string, alloc bool) bool {
902                 t, ok := BaseTypeNames[typ]
903                 if !ok {
904                         return false
905                 }
906
907                 isArray := length > 0 || sizefrom != ""
908
909                 switch t {
910                 case I8, U8, I16, U16, I32, U32, I64, U64, F64:
911                         if isArray {
912                                 if alloc {
913                                         if length != 0 {
914                                                 fmt.Fprintf(w, "\t%s = make([]%s, %d)\n", name, orig, length)
915                                         } else if sizefrom != "" {
916                                                 fmt.Fprintf(w, "\t%s = make([]%s, %s)\n", name, orig, sizefrom)
917                                         }
918                                 }
919                                 fmt.Fprintf(w, "\tfor i := 0; i < len(%s); i++ {\n", name)
920                         }
921                 }
922
923                 switch t {
924                 case I8, U8:
925                         if isArray {
926                                 fmt.Fprintf(w, "\t%s[i] = %s(tmp[pos])\n", name, convertToGoType(ctx.file, typ))
927                         } else {
928                                 fmt.Fprintf(w, "\t%s = %s(tmp[pos])\n", name, orig)
929                         }
930                         fmt.Fprintf(w, "\tpos += 1\n")
931                         if isArray {
932                                 fmt.Fprintf(w, "\t}\n")
933                         }
934                 case I16, U16:
935                         if isArray {
936                                 fmt.Fprintf(w, "\t%s[i] = %s(o.Uint16(tmp[pos:pos+2]))\n", name, orig)
937                         } else {
938                                 fmt.Fprintf(w, "\t%s = %s(o.Uint16(tmp[pos:pos+2]))\n", name, orig)
939                         }
940                         fmt.Fprintf(w, "\tpos += 2\n")
941                         if isArray {
942                                 fmt.Fprintf(w, "\t}\n")
943                         }
944                 case I32, U32:
945                         if isArray {
946                                 fmt.Fprintf(w, "\t%s[i] = %s(o.Uint32(tmp[pos:pos+4]))\n", name, orig)
947                         } else {
948                                 fmt.Fprintf(w, "\t%s = %s(o.Uint32(tmp[pos:pos+4]))\n", name, orig)
949                         }
950                         fmt.Fprintf(w, "\tpos += 4\n")
951                         if isArray {
952                                 fmt.Fprintf(w, "\t}\n")
953                         }
954                 case I64, U64:
955                         if isArray {
956                                 fmt.Fprintf(w, "\t%s[i] = %s(o.Uint64(tmp[pos:pos+8]))\n", name, orig)
957                         } else {
958                                 fmt.Fprintf(w, "\t%s = %s(o.Uint64(tmp[pos:pos+8]))\n", name, orig)
959                         }
960                         fmt.Fprintf(w, "\tpos += 8\n")
961                         if isArray {
962                                 fmt.Fprintf(w, "\t}\n")
963                         }
964                 case F64:
965                         if isArray {
966                                 fmt.Fprintf(w, "\t%s[i] = %s(math.Float64frombits(o.Uint64(tmp[pos:pos+8])))\n", name, orig)
967                         } else {
968                                 fmt.Fprintf(w, "\t%s = %s(math.Float64frombits(o.Uint64(tmp[pos:pos+8])))\n", name, orig)
969                         }
970                         fmt.Fprintf(w, "\tpos += 8\n")
971                         if isArray {
972                                 fmt.Fprintf(w, "\t}\n")
973                         }
974                 case BOOL:
975                         fmt.Fprintf(w, "\t%s = tmp[pos] != 0\n", name)
976                         fmt.Fprintf(w, "\tpos += 1\n")
977                 case STRING:
978                         if length != 0 {
979                                 fmt.Fprintf(w, "\t{\n")
980                                 fmt.Fprintf(w, "\tnul := bytes.Index(tmp[pos:pos+%d], []byte{0x00})\n", length)
981                                 fmt.Fprintf(w, "\t%[1]s = codec.DecodeString(tmp[pos:pos+nul])\n", name)
982                                 fmt.Fprintf(w, "\tpos += %d\n", length)
983                                 fmt.Fprintf(w, "\t}\n")
984                         } else {
985                                 fmt.Fprintf(w, "\t{\n")
986                                 fmt.Fprintf(w, "\tsiz := o.Uint32(tmp[pos:pos+4])\n")
987                                 fmt.Fprintf(w, "\tpos += 4\n")
988                                 fmt.Fprintf(w, "\t%[1]s = codec.DecodeString(tmp[pos:pos+int(siz)])\n", name)
989                                 fmt.Fprintf(w, "\tpos += len(%s)\n", name)
990                                 fmt.Fprintf(w, "\t}\n")
991                         }
992                 default:
993                         fmt.Fprintf(w, "\t// ??? %s %s\n", name, typ)
994                         return false
995                 }
996                 return true
997         }
998
999         lvl := 0
1000         var decodeFields func(fields []*Field, parentName string)
1001         decodeFields = func(fields []*Field, parentName string) {
1002                 lvl++
1003                 defer func() { lvl-- }()
1004
1005                 n := 0
1006                 for _, field := range fields {
1007                         // skip internal fields
1008                         switch strings.ToLower(field.Name) {
1009                         case /*crcField,*/ msgIdField:
1010                                 continue
1011                         case clientIndexField, contextField:
1012                                 if n == 0 {
1013                                         continue
1014                                 }
1015                         }
1016                         n++
1017
1018                         fieldName := camelCaseName(strings.TrimPrefix(field.Name, "_"))
1019                         name := fmt.Sprintf("%s.%s", parentName, fieldName)
1020                         sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_"))
1021                         var sizeFromName string
1022                         if sizeFrom != "" {
1023                                 sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom)
1024                         }
1025
1026                         fmt.Fprintf(w, "\t// field[%d] %s\n", lvl, name)
1027
1028                         if decodeBaseType(field.Type, convertToGoType(ctx.file, field.Type), name, field.Length, sizeFromName, true) {
1029                                 continue
1030                         }
1031
1032                         //char := fmt.Sprintf("v%d", lvl)
1033                         index := fmt.Sprintf("j%d", lvl)
1034
1035                         if field.Array {
1036                                 if field.Length > 0 {
1037                                         fmt.Fprintf(w, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index)
1038                                 } else if field.SizeFrom != "" {
1039                                         fieldType := getFieldType(ctx, field)
1040                                         if strings.HasPrefix(fieldType, "[]") {
1041                                                 fmt.Fprintf(w, "\t%s = make(%s, int(%s.%s))\n", name, fieldType, parentName, sizeFrom)
1042                                         }
1043                                         fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom)
1044                                 }
1045
1046                                 /*fmt.Fprintf(w, "\tvar %s %s\n", char, convertToGoType(ctx, field.Type))
1047                                 fmt.Fprintf(w, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char)
1048                                 name = char*/
1049                                 name = fmt.Sprintf("%s[%s]", name, index)
1050                         }
1051
1052                         if enum := getEnumByRef(ctx.file, field.Type); enum != nil {
1053                                 if decodeBaseType(enum.Type, convertToGoType(ctx.file, field.Type), name, 0, "", false) {
1054                                 } else {
1055                                         fmt.Fprintf(w, "\t// ??? ENUM %s %s\n", name, enum.Type)
1056                                 }
1057                         } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil {
1058                                 if decodeBaseType(alias.Type, convertToGoType(ctx.file, field.Type), name, alias.Length, "", false) {
1059                                 } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil {
1060                                         decodeFields(typ.Fields, name)
1061                                 } else {
1062                                         fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type)
1063                                 }
1064                         } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil {
1065                                 decodeFields(typ.Fields, name)
1066                         } else if union := getUnionByRef(ctx.file, field.Type); union != nil {
1067                                 maxSize := getUnionSize(ctx.file, union)
1068                                 fmt.Fprintf(w, "\tcopy(%s.%s[:], tmp[pos:pos+%d])\n", name, unionDataField, maxSize)
1069                                 fmt.Fprintf(w, "\tpos += %d\n", maxSize)
1070                         } else {
1071                                 fmt.Fprintf(w, "\t// ??? buf[pos] = (%s)\n", name)
1072                         }
1073
1074                         if field.Array {
1075                                 fmt.Fprintf(w, "\t}\n")
1076                         }
1077                 }
1078         }
1079
1080         decodeFields(fields, "m")
1081
1082         fmt.Fprintf(w, "return nil\n")
1083
1084         fmt.Fprintf(w, "}\n")
1085 }
1086
1087 func getFieldType(ctx *GenFile, field *Field) string {
1088         //fieldName := strings.TrimPrefix(field.Name, "_")
1089         //fieldName = camelCaseName(fieldName)
1090         //fieldName := field.GoName
1091
1092         dataType := convertToGoType(ctx.file, field.Type)
1093         fieldType := dataType
1094
1095         // check if it is array
1096         if field.Length > 0 || field.SizeFrom != "" {
1097                 if dataType == "uint8" {
1098                         dataType = "byte"
1099                 }
1100                 if dataType == "string" && field.Array {
1101                         fieldType = "string"
1102                         dataType = "byte"
1103                 } else if _, ok := BaseTypeNames[field.Type]; !ok && field.SizeFrom == "" {
1104                         fieldType = fmt.Sprintf("[%d]%s", field.Length, dataType)
1105                 } else {
1106                         fieldType = "[]" + dataType
1107                 }
1108         }
1109
1110         return fieldType
1111 }
1112
1113 func generateField(ctx *GenFile, w io.Writer, fields []*Field, i int) {
1114         field := fields[i]
1115
1116         //fieldName := strings.TrimPrefix(field.Name, "_")
1117         //fieldName = camelCaseName(fieldName)
1118         fieldName := field.GoName
1119
1120         dataType := convertToGoType(ctx.file, field.Type)
1121         fieldType := dataType
1122
1123         // generate length field for strings
1124         if field.Type == "string" && field.Length == 0 {
1125                 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
1126         }
1127
1128         // check if it is array
1129         if field.Length > 0 || field.SizeFrom != "" {
1130                 if dataType == "uint8" {
1131                         dataType = "byte"
1132                 }
1133                 if dataType == "string" && field.Array {
1134                         fieldType = "string"
1135                         dataType = "byte"
1136                 } else if _, ok := BaseTypeNames[field.Type]; !ok && field.SizeFrom == "" {
1137                         fieldType = fmt.Sprintf("[%d]%s", field.Length, dataType)
1138                 } else {
1139                         fieldType = "[]" + dataType
1140                 }
1141         }
1142         fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
1143
1144         fieldTags := map[string]string{}
1145
1146         if field.Length > 0 && field.Array {
1147                 // fixed size array
1148                 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
1149         } else {
1150                 for _, f := range fields {
1151                         if f.SizeFrom == field.Name {
1152                                 // variable sized array
1153                                 //sizeOfName := camelCaseName(f.Name)
1154                                 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", f.GoName)
1155                         }
1156                 }
1157         }
1158
1159         if ctx.IncludeBinapiNames {
1160                 typ := fromApiType(field.Type)
1161                 if field.Array {
1162                         if field.Length > 0 {
1163                                 fieldTags["binapi"] = fmt.Sprintf("%s[%d],name=%s", typ, field.Length, field.Name)
1164                         } else if field.SizeFrom != "" {
1165                                 fieldTags["binapi"] = fmt.Sprintf("%s[%s],name=%s", typ, field.SizeFrom, field.Name)
1166                         }
1167                 } else {
1168                         fieldTags["binapi"] = fmt.Sprintf("%s,name=%s", typ, field.Name)
1169                 }
1170         }
1171         if limit, ok := field.Meta["limit"]; ok && limit.(int) > 0 {
1172                 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], limit)
1173         }
1174         if def, ok := field.Meta["default"]; ok && def != nil {
1175                 actual := getActualType(ctx.file, fieldType)
1176                 if t, ok := binapiTypes[actual]; ok && t != "float64" {
1177                         defnum := int(def.(float64))
1178                         fieldTags["binapi"] = fmt.Sprintf("%s,default=%d", fieldTags["binapi"], defnum)
1179                 } else {
1180                         fieldTags["binapi"] = fmt.Sprintf("%s,default=%v", fieldTags["binapi"], def)
1181                 }
1182         }
1183
1184         fieldTags["json"] = fmt.Sprintf("%s,omitempty", field.Name)
1185
1186         if len(fieldTags) > 0 {
1187                 fmt.Fprintf(w, "\t`")
1188                 var keys []string
1189                 for k := range fieldTags {
1190                         keys = append(keys, k)
1191                 }
1192                 sort.Strings(keys)
1193                 var n int
1194                 for _, tt := range keys {
1195                         t, ok := fieldTags[tt]
1196                         if !ok {
1197                                 continue
1198                         }
1199                         if n > 0 {
1200                                 fmt.Fprintf(w, " ")
1201                         }
1202                         n++
1203                         fmt.Fprintf(w, `%s:"%s"`, tt, t)
1204                 }
1205                 fmt.Fprintf(w, "`")
1206         }
1207
1208         fmt.Fprintln(w)
1209 }
1210
1211 func generateMessageResetMethod(w io.Writer, structName string) {
1212         fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
1213 }
1214
1215 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
1216         fmt.Fprintf(w, "func (*%s) GetMessageName() string {    return %q }\n", structName, msgName)
1217 }
1218
1219 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
1220         fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName)
1221 }
1222
1223 func generateCrcGetter(w io.Writer, structName, crc string) {
1224         crc = strings.TrimPrefix(crc, "0x")
1225         fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc)
1226 }
1227
1228 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
1229         fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {")
1230         if msgType == requestMessage {
1231                 fmt.Fprintf(w, "\treturn api.RequestMessage")
1232         } else if msgType == replyMessage {
1233                 fmt.Fprintf(w, "\treturn api.ReplyMessage")
1234         } else if msgType == eventMessage {
1235                 fmt.Fprintf(w, "\treturn api.EventMessage")
1236         } else {
1237                 fmt.Fprintf(w, "\treturn api.OtherMessage")
1238         }
1239         fmt.Fprintln(w, "}")
1240         fmt.Fprintln(w)
1241 }