// Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build ignore // +build ignore package main import ( "bytes" "encoding/xml" "errors" "fmt" "io" "log" "regexp" "strings" "text/template" ) var ( ErrUnknownType = errors.New("Unknown field type in gen") ErrUnknownDomain = errors.New("Unknown domain type in gen") ) var amqpTypeToNative = map[string]string{ "bit": "bool", "octet": "byte", "shortshort": "uint8", "short": "uint16", "long": "uint32", "longlong": "uint64", "timestamp": "time.Time", "table": "Table", "shortstr": "string", "longstr": "string", } type Rule struct { Name string `xml:"name,attr"` Docs []string `xml:"doc"` } type Doc struct { Type string `xml:"type,attr"` Body string `xml:",innerxml"` } type Chassis struct { Name string `xml:"name,attr"` Implement string `xml:"implement,attr"` } type Assert struct { Check string `xml:"check,attr"` Value string `xml:"value,attr"` Method string `xml:"method,attr"` } type Field struct { Name string `xml:"name,attr"` Domain string `xml:"domain,attr"` Type string `xml:"type,attr"` Label string `xml:"label,attr"` Reserved bool `xml:"reserved,attr"` Docs []Doc `xml:"doc"` Asserts []Assert `xml:"assert"` } type Response struct { Name string `xml:"name,attr"` } type Method struct { Name string `xml:"name,attr"` Response Response `xml:"response"` Synchronous bool `xml:"synchronous,attr"` Content bool `xml:"content,attr"` Index string `xml:"index,attr"` Label string `xml:"label,attr"` Docs []Doc `xml:"doc"` Rules []Rule `xml:"rule"` Fields []Field `xml:"field"` Chassis []Chassis `xml:"chassis"` } type Class struct { Name string `xml:"name,attr"` Handler string `xml:"handler,attr"` Index string `xml:"index,attr"` Label string `xml:"label,attr"` Docs []Doc `xml:"doc"` Methods []Method `xml:"method"` Chassis []Chassis `xml:"chassis"` } type Domain struct { Name string `xml:"name,attr"` Type string `xml:"type,attr"` Label string `xml:"label,attr"` Rules []Rule `xml:"rule"` Docs []Doc `xml:"doc"` } type Constant struct { Name string `xml:"name,attr"` Value int `xml:"value,attr"` Class string `xml:"class,attr"` Doc string `xml:"doc"` } type Amqp struct { Major int `xml:"major,attr"` Minor int `xml:"minor,attr"` Port int `xml:"port,attr"` Comment string `xml:"comment,attr"` Constants []Constant `xml:"constant"` Domains []Domain `xml:"domain"` Classes []Class `xml:"class"` } type renderer struct { Root Amqp bitcounter int } type fieldset struct { AmqpType string NativeType string Fields []Field *renderer } var ( helpers = template.FuncMap{ "public": public, "private": private, "clean": clean, } packageTemplate = template.Must(template.New("package").Funcs(helpers).Parse(` // Copyright (c) 2021 VMware, Inc. or its affiliates. All Rights Reserved. // Copyright (c) 2012-2021, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* GENERATED FILE - DO NOT EDIT */ /* Rebuild from the spec/gen.go tool */ {{with .Root}} package amqp091 import ( "fmt" "encoding/binary" "io" ) // Error codes that can be sent from the server during a connection or // channel exception or used by the client to indicate a class of error like // ErrCredentials. The text of the error is likely more interesting than // these constants. const ( {{range $c := .Constants}} {{if $c.IsError}}{{.Name | public}}{{else}}{{.Name | private}}{{end}} = {{.Value}}{{end}} ) func isSoftExceptionCode(code int) bool { switch code { {{range $c := .Constants}} {{if $c.IsSoftError}} case {{$c.Value}}: return true {{end}}{{end}} } return false } {{range .Classes}} {{$class := .}} {{range .Methods}} {{$method := .}} {{$struct := $.StructName $class.Name $method.Name}} {{if .Docs}}/* {{range .Docs}} {{.Body | clean}} {{end}} */{{end}} type {{$struct}} struct { {{range .Fields}} {{$.FieldName .}} {{$.FieldType . | $.NativeType}} {{if .Label}}// {{.Label}}{{end}}{{end}} {{if .Content}}Properties properties Body []byte{{end}} } func (msg *{{$struct}}) id() (uint16, uint16) { return {{$class.Index}}, {{$method.Index}} } func (msg *{{$struct}}) wait() (bool) { return {{.Synchronous}}{{if $.HasField "NoWait" .}} && !msg.NoWait{{end}} } {{if .Content}} func (msg *{{$struct}}) getContent() (properties, []byte) { return msg.Properties, msg.Body } func (msg *{{$struct}}) setContent(props properties, body []byte) { msg.Properties, msg.Body = props, body } {{end}} func (msg *{{$struct}}) write(w io.Writer) (err error) { {{if $.HasType "bit" $method}}var bits byte{{end}} {{.Fields | $.Fieldsets | $.Partial "enc-"}} return } func (msg *{{$struct}}) read(r io.Reader) (err error) { {{if $.HasType "bit" $method}}var bits byte{{end}} {{.Fields | $.Fieldsets | $.Partial "dec-"}} return } {{end}} {{end}} func (r *reader) parseMethodFrame(channel uint16, size uint32) (f frame, err error) { mf := &methodFrame { ChannelId: channel, } if err = binary.Read(r.r, binary.BigEndian, &mf.ClassId); err != nil { return } if err = binary.Read(r.r, binary.BigEndian, &mf.MethodId); err != nil { return } switch mf.ClassId { {{range .Classes}} {{$class := .}} case {{.Index}}: // {{.Name}} switch mf.MethodId { {{range .Methods}} case {{.Index}}: // {{$class.Name}} {{.Name}} //fmt.Println("NextMethod: class:{{$class.Index}} method:{{.Index}}") method := &{{$.StructName $class.Name .Name}}{} if err = method.read(r.r); err != nil { return } mf.Method = method {{end}} default: return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) } {{end}} default: return nil, fmt.Errorf("Bad method frame, unknown class %d", mf.ClassId) } return mf, nil } {{end}} {{define "enc-bit"}} {{range $off, $field := .Fields}} if msg.{{$field | $.FieldName}} { bits |= 1 << {{$off}} } {{end}} if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } {{end}} {{define "enc-octet"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-shortshort"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-short"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-long"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-longlong"}} {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-timestamp"}} {{range .Fields}} if err = writeTimestamp(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-shortstr"}} {{range .Fields}} if err = writeShortstr(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-longstr"}} {{range .Fields}} if err = writeLongstr(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "enc-table"}} {{range .Fields}} if err = writeTable(w, msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-bit"}} if err = binary.Read(r, binary.BigEndian, &bits); err != nil { return } {{range $off, $field := .Fields}} msg.{{$field | $.FieldName}} = (bits & (1 << {{$off}}) > 0) {{end}} {{end}} {{define "dec-octet"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-shortshort"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-short"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-long"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-longlong"}} {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } {{end}} {{end}} {{define "dec-timestamp"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readTimestamp(r); err != nil { return } {{end}} {{end}} {{define "dec-shortstr"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readShortstr(r); err != nil { return } {{end}} {{end}} {{define "dec-longstr"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readLongstr(r); err != nil { return } {{end}} {{end}} {{define "dec-table"}} {{range .Fields}} if msg.{{. | $.FieldName}}, err = readTable(r); err != nil { return } {{end}} {{end}} `)) ) func (c *Constant) IsError() bool { return strings.Contains(c.Class, "error") } func (c *Constant) IsSoftError() bool { return c.Class == "soft-error" } func (renderer *renderer) Partial(prefix string, fields []fieldset) (s string, err error) { var buf bytes.Buffer for _, set := range fields { name := prefix + set.AmqpType t := packageTemplate.Lookup(name) if t == nil { return "", errors.New(fmt.Sprintf("Missing template: %s", name)) } if err = t.Execute(&buf, set); err != nil { return } } return string(buf.Bytes()), nil } // Groups the fields so that the right encoder/decoder can be called func (renderer *renderer) Fieldsets(fields []Field) (f []fieldset, err error) { if len(fields) > 0 { for _, field := range fields { cur := fieldset{} cur.AmqpType, err = renderer.FieldType(field) if err != nil { return } cur.NativeType, err = renderer.NativeType(cur.AmqpType) if err != nil { return } cur.Fields = append(cur.Fields, field) f = append(f, cur) } i, j := 0, 1 for j < len(f) { if f[i].AmqpType == f[j].AmqpType { f[i].Fields = append(f[i].Fields, f[j].Fields...) } else { i++ f[i] = f[j] } j++ } return f[:i+1], nil } return } func (renderer *renderer) HasType(typ string, method Method) bool { for _, f := range method.Fields { name, _ := renderer.FieldType(f) if name == typ { return true } } return false } func (renderer *renderer) HasField(field string, method Method) bool { for _, f := range method.Fields { name := renderer.FieldName(f) if name == field { return true } } return false } func (renderer *renderer) Domain(field Field) (domain Domain, err error) { for _, domain = range renderer.Root.Domains { if field.Domain == domain.Name { return } } return domain, nil //return domain, ErrUnknownDomain } func (renderer *renderer) FieldName(field Field) (t string) { t = public(field.Name) if field.Reserved { t = strings.ToLower(t) } return } func (renderer *renderer) FieldType(field Field) (t string, err error) { t = field.Type if t == "" { var domain Domain domain, err = renderer.Domain(field) if err != nil { return "", err } t = domain.Type } return } func (renderer *renderer) NativeType(amqpType string) (t string, err error) { if t, ok := amqpTypeToNative[amqpType]; ok { return t, nil } return "", ErrUnknownType } func (renderer *renderer) Tag(d Domain) string { label := "`" label += `domain:"` + d.Name + `"` if len(d.Type) > 0 { label += `,type:"` + d.Type + `"` } label += "`" return label } func (renderer *renderer) StructName(parts ...string) string { return parts[0] + public(parts[1:]...) } func clean(body string) (res string) { return strings.Replace(body, "\r", "", -1) } func private(parts ...string) string { return export(regexp.MustCompile(`[-_]\w`), parts...) } func public(parts ...string) string { return export(regexp.MustCompile(`^\w|[-_]\w`), parts...) } func export(delim *regexp.Regexp, parts ...string) (res string) { for _, in := range parts { res += delim.ReplaceAllStringFunc(in, func(match string) string { switch len(match) { case 1: return strings.ToUpper(match) case 2: return strings.ToUpper(match[1:]) } panic("unreachable") }) } return } func main() { var r renderer spec, err := io.ReadAll(os.Stdin) if err != nil { log.Fatalln("Please pass spec on stdin", err) } err = xml.Unmarshal(spec, &r.Root) if err != nil { log.Fatalln("Could not parse XML:", err) } if err = packageTemplate.Execute(os.Stdout, &r); err != nil { log.Fatalln("Generate error: ", err) } }