diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..747f056bb7fad70d609828e12e8c6aa2c949b555 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Jeromy Johnson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..cbb8d39026a04ee962cb072b61613d88905e6aa7 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +gentest: + rm -rf ./testing/cbor_gen.go ./testing/cbor_map_gen.go + go run ./testgen/main.go +.PHONY: gentest + +test: gentest + go test ./... +.PHONY: test diff --git a/README.md b/README.md index 6f12ee83ca2160389bc90a4786bc4534c678f3ac..93603a98ddcb2dfe4718707b3a886935b3e8805e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # cbor-gen -dms3 github.com/whyrusleeping/cbor-gen which has dependency on go-cid \ No newline at end of file +Some basic utilities to generate fast path cbor codecs for your types. + +## License +MIT diff --git a/cbor_cid.go b/cbor_cid.go new file mode 100644 index 0000000000000000000000000000000000000000..57d6ec20c24eea41753f213ccba2125d4f6be88d --- /dev/null +++ b/cbor_cid.go @@ -0,0 +1,22 @@ +package typegen + +import ( + "io" + + cid "github.com/ipfs/go-cid" +) + +type CborCid cid.Cid + +func (c CborCid) MarshalCBOR(w io.Writer) error { + return WriteCid(w, cid.Cid(c)) +} + +func (c *CborCid) UnmarshalCBOR(r io.Reader) error { + oc, err := ReadCid(r) + if err != nil { + return err + } + *c = CborCid(oc) + return nil +} diff --git a/gen.go b/gen.go new file mode 100644 index 0000000000000000000000000000000000000000..e93e00c19fa4a6928ac023772bb1be815ef9030f --- /dev/null +++ b/gen.go @@ -0,0 +1,1298 @@ +package typegen + +import ( + "fmt" + "io" + "math/big" + "reflect" + "strings" + "text/template" + + "github.com/ipfs/go-cid" +) + +const MaxLength = 8192 + +const ByteArrayMaxLen = 2 << 20 + +var ( + cidType = reflect.TypeOf(cid.Cid{}) + bigIntType = reflect.TypeOf(big.Int{}) + deferredType = reflect.TypeOf(Deferred{}) +) + +func doTemplate(w io.Writer, info interface{}, templ string) error { + t := template.Must(template.New(""). + Funcs(template.FuncMap{ + "MajorType": func(wname string, tname string, val string) string { + return fmt.Sprintf(`if err := cbg.WriteMajorTypeHeaderBuf(scratch, %s, %s, uint64(%s)); err != nil { + return err +}`, wname, tname, val) + }, + "ReadHeader": func(rdr string) string { + return fmt.Sprintf(`cbg.CborReadHeaderBuf(%s, scratch)`, rdr) + }, + }).Parse(templ)) + + return t.Execute(w, info) +} + +func PrintHeaderAndUtilityMethods(w io.Writer, pkg string, typeInfos []*GenTypeInfo) error { + var imports []Import + for _, gti := range typeInfos { + imports = append(imports, gti.Imports()...) + } + + imports = append(imports, defaultImports...) + imports = dedupImports(imports) + + data := struct { + Package string + Imports []Import + }{pkg, imports} + return doTemplate(w, data, `// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package {{ .Package }} + +import ( + "fmt" + "io" + "math" + "sort" + +{{ range .Imports }}{{ .Name }} "{{ .PkgPath }}" +{{ end }} +) + + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +`) +} + +type Field struct { + Name string + Pointer bool + Type reflect.Type + Pkg string + + IterLabel string +} + +func typeName(pkg string, t reflect.Type) string { + switch t.Kind() { + case reflect.Array: + return fmt.Sprintf("[%d]%s", t.Len(), typeName(pkg, t.Elem())) + case reflect.Slice: + return "[]" + typeName(pkg, t.Elem()) + case reflect.Ptr: + return "*" + typeName(pkg, t.Elem()) + case reflect.Map: + return "map[" + typeName(pkg, t.Key()) + "]" + typeName(pkg, t.Elem()) + default: + pkgPath := t.PkgPath() + if pkgPath == "" { + // It's a built-in. + return t.String() + } else if pkgPath == pkg { + return t.Name() + } + return fmt.Sprintf("%s.%s", resolvePkgName(pkgPath, t.String()), t.Name()) + } +} + +func (f Field) TypeName() string { + return typeName(f.Pkg, f.Type) +} + +func (f Field) ElemName() string { + return typeName(f.Pkg, f.Type.Elem()) +} + +func (f Field) IsArray() bool { + return f.Type.Kind() == reflect.Array +} + +func (f Field) Len() int { + return f.Type.Len() +} + +type GenTypeInfo struct { + Name string + Fields []Field +} + +func (gti *GenTypeInfo) Imports() []Import { + var imports []Import + for _, f := range gti.Fields { + switch f.Type.Kind() { + case reflect.Struct: + if !f.Pointer && f.Type != bigIntType { + continue + } + if f.Type == cidType { + continue + } + case reflect.Bool: + continue + } + imports = append(imports, ImportsForType(f.Pkg, f.Type)...) + } + return imports +} + +func (gti *GenTypeInfo) NeedsScratch() bool { + for _, f := range gti.Fields { + switch f.Type.Kind() { + case reflect.String, + reflect.Uint64, + reflect.Int64, + reflect.Uint8, + reflect.Array, + reflect.Slice, + reflect.Map: + return true + + case reflect.Struct: + if f.Type == bigIntType || f.Type == cidType { + return true + } + // nope + case reflect.Bool: + // nope + } + } + return false +} + +func nameIsExported(name string) bool { + return strings.ToUpper(name[0:1]) == name[0:1] +} + +func ParseTypeInfo(i interface{}) (*GenTypeInfo, error) { + t := reflect.TypeOf(i) + + pkg := t.PkgPath() + + out := GenTypeInfo{ + Name: t.Name(), + } + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if !nameIsExported(f.Name) { + continue + } + + ft := f.Type + var pointer bool + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + pointer = true + } + + out.Fields = append(out.Fields, Field{ + Name: f.Name, + Pointer: pointer, + Type: ft, + Pkg: pkg, + }) + } + + return &out, nil +} + +func (gti GenTypeInfo) TupleHeader() []byte { + return CborEncodeMajorType(MajArray, uint64(len(gti.Fields))) +} + +func (gti GenTypeInfo) TupleHeaderAsByteString() string { + h := gti.TupleHeader() + s := "[]byte{" + for _, b := range h { + s += fmt.Sprintf("%d,", b) + } + s += "}" + return s +} + +func (gti GenTypeInfo) MapHeader() []byte { + return CborEncodeMajorType(MajMap, uint64(len(gti.Fields))) +} + +func (gti GenTypeInfo) MapHeaderAsByteString() string { + h := gti.MapHeader() + s := "[]byte{" + for _, b := range h { + s += fmt.Sprintf("%d,", b) + } + s += "}" + return s +} + +func emitCborMarshalStringField(w io.Writer, f Field) error { + if f.Pointer { + return fmt.Errorf("pointers to strings not supported") + } + + return doTemplate(w, f, ` + if len({{ .Name }}) > cbg.MaxLength { + return xerrors.Errorf("Value in field {{ .Name | js }} was too long") + } + + {{ MajorType "w" "cbg.MajTextString" (print "len(" .Name ")") }} + if _, err := io.WriteString(w, string({{ .Name }})); err != nil { + return err + } +`) +} +func emitCborMarshalStructField(w io.Writer, f Field) error { + switch f.Type { + case bigIntType: + return doTemplate(w, f, ` + { + if err := cbg.CborWriteHeader(w, cbg.MajTag, 2); err != nil { + return err + } + var b []byte + if {{ .Name }} != nil { + b = {{ .Name }}.Bytes() + } + + if err := cbg.CborWriteHeader(w, cbg.MajByteString, uint64(len(b))); err != nil { + return err + } + if _, err := w.Write(b); err != nil { + return err + } + } +`) + + case cidType: + return doTemplate(w, f, ` +{{ if .Pointer }} + if {{ .Name }} == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCidBuf(scratch, w, *{{ .Name }}); err != nil { + return xerrors.Errorf("failed to write cid field {{ .Name }}: %w", err) + } + } +{{ else }} + if err := cbg.WriteCidBuf(scratch, w, {{ .Name }}); err != nil { + return xerrors.Errorf("failed to write cid field {{ .Name }}: %w", err) + } +{{ end }} +`) + default: + return doTemplate(w, f, ` + if err := {{ .Name }}.MarshalCBOR(w); err != nil { + return err + } +`) + } + +} + +func emitCborMarshalUint64Field(w io.Writer, f Field) error { + return doTemplate(w, f, ` +{{ if .Pointer }} + if {{ .Name }} == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + {{ MajorType "w" "cbg.MajUnsignedInt" (print "*" .Name) }} + } +{{ else }} + {{ MajorType "w" "cbg.MajUnsignedInt" .Name }} +{{ end }} +`) +} + +func emitCborMarshalUint8Field(w io.Writer, f Field) error { + if f.Pointer { + return fmt.Errorf("pointers to integers not supported") + } + return doTemplate(w, f, ` +{{ MajorType "w" "cbg.MajUnsignedInt" .Name }} +`) +} + +func emitCborMarshalInt64Field(w io.Writer, f Field) error { + if f.Pointer { + return fmt.Errorf("pointers to integers not supported") + } + + // if negative + // val = -1 - cbor + // cbor = -val -1 + + return doTemplate(w, f, ` + if {{ .Name }} >= 0 { + {{ MajorType "w" "cbg.MajUnsignedInt" .Name }} + } else { + {{ MajorType "w" "cbg.MajNegativeInt" (print "-" .Name "-1") }} + } +`) +} + +func emitCborMarshalBoolField(w io.Writer, f Field) error { + return doTemplate(w, f, ` + if err := cbg.WriteBool(w, {{ .Name }}); err != nil { + return err + } +`) +} + +func emitCborMarshalMapField(w io.Writer, f Field) error { + err := doTemplate(w, f, ` +{ + if len({{ .Name }}) > 4096 { + return xerrors.Errorf("cannot marshal {{ .Name }} map too large") + } + + {{ MajorType "w" "cbg.MajMap" (print "len(" .Name ")") }} + + keys := make([]string, 0, len({{ .Name }})) + for k := range {{ .Name }} { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := {{ .Name }}[k] + +`) + if err != nil { + return err + } + + // Map key + switch f.Type.Key().Kind() { + case reflect.String: + if err := emitCborMarshalStringField(w, Field{Name: "k"}); err != nil { + return err + } + default: + return fmt.Errorf("non-string map keys are not yet supported") + } + + // Map value + switch f.Type.Elem().Kind() { + case reflect.Ptr: + if f.Type.Elem().Elem().Kind() != reflect.Struct { + return fmt.Errorf("unsupported map elem ptr type: %s", f.Type.Elem()) + } + + fallthrough + case reflect.Struct: + if err := emitCborMarshalStructField(w, Field{Name: "v", Type: f.Type.Elem(), Pkg: f.Pkg}); err != nil { + return err + } + default: + return fmt.Errorf("currently unsupported map elem type: %s", f.Type.Elem()) + } + + return doTemplate(w, f, ` + } + } +`) +} + +func emitCborMarshalSliceField(w io.Writer, f Field) error { + if f.Pointer { + return fmt.Errorf("pointers to slices not supported") + } + e := f.Type.Elem() + + // Note: this re-slices the slice to deal with arrays. + if e.Kind() == reflect.Uint8 { + return doTemplate(w, f, ` + if len({{ .Name }}) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field {{ .Name }} was too long") + } + + {{ MajorType "w" "cbg.MajByteString" (print "len(" .Name ")" ) }} + + if _, err := w.Write({{ .Name }}[:]); err != nil { + return err + } +`) + } + + if e.Kind() == reflect.Ptr { + e = e.Elem() + } + + err := doTemplate(w, f, ` + if len({{ .Name }}) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field {{ .Name }} was too long") + } + + {{ MajorType "w" "cbg.MajArray" ( print "len(" .Name ")" ) }} + for _, v := range {{ .Name }} {`) + if err != nil { + return err + } + + switch e.Kind() { + default: + return fmt.Errorf("do not yet support slices of %s yet", e.Kind()) + case reflect.Struct: + switch e { + case cidType: + err := doTemplate(w, f, ` + if err := cbg.WriteCidBuf(scratch, w, v); err != nil { + return xerrors.Errorf("failed writing cid field {{ .Name }}: %w", err) + } +`) + if err != nil { + return err + } + + default: + err := doTemplate(w, f, ` + if err := v.MarshalCBOR(w); err != nil { + return err + } +`) + if err != nil { + return err + } + } + case reflect.Uint64: + err := doTemplate(w, f, ` + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } +`) + if err != nil { + return err + } + case reflect.Uint8: + err := doTemplate(w, f, ` + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } +`) + if err != nil { + return err + } + case reflect.Int64: + subf := Field{Name: "v", Type: e, Pkg: f.Pkg} + if err := emitCborMarshalInt64Field(w, subf); err != nil { + return err + } + + case reflect.Slice: + subf := Field{Name: "v", Type: e, Pkg: f.Pkg} + if err := emitCborMarshalSliceField(w, subf); err != nil { + return err + } + } + + // array end + fmt.Fprintf(w, "\t}\n") + return nil +} + +func emitCborMarshalStructTuple(w io.Writer, gti *GenTypeInfo) error { + // 9 byte buffer to accomodate for the maximum header length (cbor varints are maximum 9 bytes_ + err := doTemplate(w, gti, `var lengthBuf{{ .Name }} = {{ .TupleHeaderAsByteString }} +func (t *{{ .Name }}) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBuf{{ .Name }}); err != nil { + return err + } +{{ if .NeedsScratch }} + scratch := make([]byte, 9) +{{ end }} +`) + if err != nil { + return err + } + + for _, f := range gti.Fields { + fmt.Fprintf(w, "\n\t// t.%s (%s) (%s)", f.Name, f.Type, f.Type.Kind()) + f.Name = "t." + f.Name + + switch f.Type.Kind() { + case reflect.String: + if err := emitCborMarshalStringField(w, f); err != nil { + return err + } + case reflect.Struct: + if err := emitCborMarshalStructField(w, f); err != nil { + return err + } + case reflect.Uint64: + if err := emitCborMarshalUint64Field(w, f); err != nil { + return err + } + case reflect.Uint8: + if err := emitCborMarshalUint8Field(w, f); err != nil { + return err + } + case reflect.Int64: + if err := emitCborMarshalInt64Field(w, f); err != nil { + return err + } + case reflect.Array: + fallthrough + case reflect.Slice: + if err := emitCborMarshalSliceField(w, f); err != nil { + return err + } + case reflect.Bool: + if err := emitCborMarshalBoolField(w, f); err != nil { + return err + } + case reflect.Map: + if err := emitCborMarshalMapField(w, f); err != nil { + return err + } + default: + return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind()) + } + } + + fmt.Fprintf(w, "\treturn nil\n}\n\n") + return nil +} + +func emitCborUnmarshalStringField(w io.Writer, f Field) error { + if f.Pointer { + return fmt.Errorf("pointers to strings not supported") + } + if f.Type == nil { + f.Type = reflect.TypeOf("") + } + return doTemplate(w, f, ` + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + {{ .Name }} = {{ .TypeName }}(sval) + } +`) +} + +func emitCborUnmarshalStructField(w io.Writer, f Field) error { + switch f.Type { + case bigIntType: + return doTemplate(w, f, ` + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } + + if maj != cbg.MajTag || extra != 2 { + return fmt.Errorf("big ints should be cbor bignums") + } + + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } + + if maj != cbg.MajByteString { + return fmt.Errorf("big ints should be tagged cbor byte strings") + } + + if extra > 256 { + return fmt.Errorf("{{ .Name }}: cbor bignum was too large") + } + + if extra > 0 { + buf := make([]byte, extra) + if _, err := io.ReadFull(br, buf); err != nil { + return err + } + {{ .Name }} = big.NewInt(0).SetBytes(buf) + } else { + {{ .Name }} = big.NewInt(0) + } +`) + case cidType: + return doTemplate(w, f, ` + { +{{ if .Pointer }} + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } +{{ end }} + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field {{ .Name }}: %w", err) + } +{{ if .Pointer }} + {{ .Name }} = &c + } +{{ else }} + {{ .Name }} = c +{{ end }} + } +`) + case deferredType: + return doTemplate(w, f, ` + { +{{ if .Pointer }} + {{ .Name }} = new(cbg.Deferred) +{{ end }} + if err := {{ .Name }}.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } +`) + + default: + return doTemplate(w, f, ` + { +{{ if .Pointer }} + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + {{ .Name }} = new({{ .TypeName }}) + if err := {{ .Name }}.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling {{ .Name }} pointer: %w", err) + } + } +{{ else }} + if err := {{ .Name }}.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling {{ .Name }}: %w", err) + } +{{ end }} + } +`) + } +} + +func emitCborUnmarshalInt64Field(w io.Writer, f Field) error { + return doTemplate(w, f, `{ + maj, extra, err := {{ ReadHeader "br" }} + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + {{ .Name }} = {{ .TypeName }}(extraI) +} +`) +} + +func emitCborUnmarshalUint64Field(w io.Writer, f Field) error { + return doTemplate(w, f, ` + { +{{ if .Pointer }} + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + typed := {{ .TypeName }}(extra) + {{ .Name }} = &typed + } +{{ else }} + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + {{ .Name }} = {{ .TypeName }}(extra) +{{ end }} + } +`) +} + +func emitCborUnmarshalUint8Field(w io.Writer, f Field) error { + return doTemplate(w, f, ` + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint8 field") + } + if extra > math.MaxUint8 { + return fmt.Errorf("integer in input was too large for uint8 field") + } + {{ .Name }} = {{ .TypeName }}(extra) +`) +} + +func emitCborUnmarshalBoolField(w io.Writer, f Field) error { + return doTemplate(w, f, ` + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + {{ .Name }} = false + case 21: + {{ .Name }} = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } +`) +} + +func emitCborUnmarshalMapField(w io.Writer, f Field) error { + err := doTemplate(w, f, ` + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("expected a map (major type 5)") + } + if extra > 4096 { + return fmt.Errorf("{{ .Name }}: map too large") + } + + {{ .Name }} = make({{ .TypeName }}, extra) + + + for i, l := 0, int(extra); i < l; i++ { +`) + if err != nil { + return err + } + + switch f.Type.Key().Kind() { + case reflect.String: + if err := doTemplate(w, f, ` + var k string +`); err != nil { + return err + } + if err := emitCborUnmarshalStringField(w, Field{Name: "k"}); err != nil { + return err + } + default: + return fmt.Errorf("maps with non-string keys are not yet supported") + } + + var pointer bool + t := f.Type.Elem() + switch t.Kind() { + case reflect.Ptr: + if t.Elem().Kind() != reflect.Struct { + return fmt.Errorf("unsupported map elem ptr type: %s", t) + } + + pointer = true + fallthrough + case reflect.Struct: + subf := Field{Name: "v", Pointer: pointer, Type: t, Pkg: f.Pkg} + if err := doTemplate(w, subf, ` + var v {{ .TypeName }} +`); err != nil { + return err + } + + if pointer { + subf.Type = subf.Type.Elem() + } + if err := emitCborUnmarshalStructField(w, subf); err != nil { + return err + } + if err := doTemplate(w, f, ` + {{ .Name }}[k] = v +`); err != nil { + return err + } + default: + return fmt.Errorf("currently only support maps of structs") + } + + return doTemplate(w, f, ` + } +`) +} + +func emitCborUnmarshalSliceField(w io.Writer, f Field) error { + if f.IterLabel == "" { + f.IterLabel = "i" + } + + e := f.Type.Elem() + var pointer bool + if e.Kind() == reflect.Ptr { + pointer = true + e = e.Elem() + } + + err := doTemplate(w, f, ` + maj, extra, err = {{ ReadHeader "br" }} + if err != nil { + return err + } +`) + if err != nil { + return err + } + + if e.Kind() == reflect.Uint8 { + return doTemplate(w, f, ` + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("{{ .Name }}: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + {{if .IsArray}} + if extra != {{ .Len }} { + return fmt.Errorf("expected array to have {{ .Len }} elements") + } + + {{ .Name }} = {{ .TypeName }}{} + {{else}} + if extra > 0 { + {{ .Name }} = make({{ .TypeName }}, extra) + } + {{end}} + if _, err := io.ReadFull(br, {{ .Name }}[:]); err != nil { + return err + } +`) + } + + if err := doTemplate(w, f, ` + if extra > cbg.MaxLength { + return fmt.Errorf("{{ .Name }}: array too large (%d)", extra) + } +`); err != nil { + return err + } + + err = doTemplate(w, f, ` + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + {{if .IsArray}} + if extra != {{ .Len }} { + return fmt.Errorf("expected array to have {{ .Len }} elements") + } + + {{ .Name }} = {{ .TypeName }}{} + {{else}} + if extra > 0 { + {{ .Name }} = make({{ .TypeName }}, extra) + } + {{end}} + for {{ .IterLabel }} := 0; {{ .IterLabel }} < int(extra); {{ .IterLabel }}++ { +`) + if err != nil { + return err + } + + switch e.Kind() { + case reflect.Struct: + fname := e.PkgPath() + "." + e.Name() + switch fname { + case "github.com/ipfs/go-cid.Cid": + err := doTemplate(w, f, ` + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("reading cid field {{ .Name }} failed: %w", err) + } + {{ .Name }}[{{ .IterLabel }}] = c +`) + if err != nil { + return err + } + default: + subf := Field{ + Type: e, + Pkg: f.Pkg, + Pointer: pointer, + Name: f.Name + "[" + f.IterLabel + "]", + } + + err := doTemplate(w, subf, ` + var v {{ .TypeName }} + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + {{ .Name }} = {{ if .Pointer }}&{{ end }}v +`) + if err != nil { + return err + } + } + case reflect.Uint64: + err := doTemplate(w, f, ` + maj, val, err := {{ ReadHeader "br" }} + if err != nil { + return xerrors.Errorf("failed to read uint64 for {{ .Name }} slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array {{ .Name }} was not a uint, instead got %d", maj) + } + + {{ .Name }}[{{ .IterLabel}}] = {{ .ElemName }}(val) +`) + if err != nil { + return err + } + case reflect.Int64: + subf := Field{ + Type: e, + Pkg: f.Pkg, + Name: f.Name + "[" + f.IterLabel + "]", + } + err := emitCborUnmarshalInt64Field(w, subf) + if err != nil { + return err + } + case reflect.Array: + fallthrough + case reflect.Slice: + nextIter := string([]byte{f.IterLabel[0] + 1}) + subf := Field{ + Name: fmt.Sprintf("%s[%s]", f.Name, f.IterLabel), + Type: e, + IterLabel: nextIter, + Pkg: f.Pkg, + } + fmt.Fprintf(w, "\t\t{\n\t\t\tvar maj byte\n\t\tvar extra uint64\n\t\tvar err error\n") + if err := emitCborUnmarshalSliceField(w, subf); err != nil { + return err + } + fmt.Fprintf(w, "\t\t}\n") + + default: + return fmt.Errorf("do not yet support slices of %s yet", e.Elem()) + } + fmt.Fprintf(w, "\t}\n\n") + + return nil +} + +func emitCborUnmarshalStructTuple(w io.Writer, gti *GenTypeInfo) error { + err := doTemplate(w, gti, ` +func (t *{{ .Name}}) UnmarshalCBOR(r io.Reader) error { + *t = {{.Name}}{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := {{ ReadHeader "br" }} + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != {{ len .Fields }} { + return fmt.Errorf("cbor input had wrong number of fields") + } + +`) + if err != nil { + return err + } + + for _, f := range gti.Fields { + fmt.Fprintf(w, "\t// t.%s (%s) (%s)\n", f.Name, f.Type, f.Type.Kind()) + f.Name = "t." + f.Name + + switch f.Type.Kind() { + case reflect.String: + if err := emitCborUnmarshalStringField(w, f); err != nil { + return err + } + case reflect.Struct: + if err := emitCborUnmarshalStructField(w, f); err != nil { + return err + } + case reflect.Uint64: + if err := emitCborUnmarshalUint64Field(w, f); err != nil { + return err + } + case reflect.Uint8: + if err := emitCborUnmarshalUint8Field(w, f); err != nil { + return err + } + case reflect.Int64: + if err := emitCborUnmarshalInt64Field(w, f); err != nil { + return err + } + case reflect.Array: + fallthrough + case reflect.Slice: + if err := emitCborUnmarshalSliceField(w, f); err != nil { + return err + } + case reflect.Bool: + if err := emitCborUnmarshalBoolField(w, f); err != nil { + return err + } + case reflect.Map: + if err := emitCborUnmarshalMapField(w, f); err != nil { + return err + } + default: + return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind()) + } + } + + fmt.Fprintf(w, "\treturn nil\n}\n\n") + + return nil +} + +// Generates 'tuple representation' cbor encoders for the given type +func GenTupleEncodersForType(gti *GenTypeInfo, w io.Writer) error { + if err := emitCborMarshalStructTuple(w, gti); err != nil { + return err + } + + if err := emitCborUnmarshalStructTuple(w, gti); err != nil { + return err + } + + return nil +} + +func emitCborMarshalStructMap(w io.Writer, gti *GenTypeInfo) error { + err := doTemplate(w, gti, `func (t *{{ .Name }}) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write({{ .MapHeaderAsByteString }}); err != nil { + return err + } + + scratch := make([]byte, 9) +`) + if err != nil { + return err + } + + for _, f := range gti.Fields { + fmt.Fprintf(w, "\n\t// t.%s (%s) (%s)", f.Name, f.Type, f.Type.Kind()) + + if err := emitCborMarshalStringField(w, Field{ + Name: `"` + f.Name + `"`, + }); err != nil { + return err + } + + f.Name = "t." + f.Name + + switch f.Type.Kind() { + case reflect.String: + if err := emitCborMarshalStringField(w, f); err != nil { + return err + } + case reflect.Struct: + if err := emitCborMarshalStructField(w, f); err != nil { + return err + } + case reflect.Uint64: + if err := emitCborMarshalUint64Field(w, f); err != nil { + return err + } + case reflect.Int64: + if err := emitCborMarshalInt64Field(w, f); err != nil { + return err + } + case reflect.Uint8: + if err := emitCborMarshalUint8Field(w, f); err != nil { + return err + } + case reflect.Array: + fallthrough + case reflect.Slice: + if err := emitCborMarshalSliceField(w, f); err != nil { + return err + } + case reflect.Bool: + if err := emitCborMarshalBoolField(w, f); err != nil { + return err + } + case reflect.Map: + if err := emitCborMarshalMapField(w, f); err != nil { + return err + } + default: + return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind()) + } + } + + fmt.Fprintf(w, "\treturn nil\n}\n\n") + return nil +} + +func emitCborUnmarshalStructMap(w io.Writer, gti *GenTypeInfo) error { + err := doTemplate(w, gti, ` +func (t *{{ .Name}}) UnmarshalCBOR(r io.Reader) error { + *t = {{.Name}}{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := {{ ReadHeader "br" }} + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("{{ .Name }}: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { +`) + if err != nil { + return err + } + + if err := emitCborUnmarshalStringField(w, Field{Name: "name"}); err != nil { + return err + } + + err = doTemplate(w, gti, ` + switch name { +`) + if err != nil { + return err + } + + for _, f := range gti.Fields { + fmt.Fprintf(w, "// t.%s (%s) (%s)", f.Name, f.Type, f.Type.Kind()) + + err := doTemplate(w, f, ` + case "{{ .Name }}": +`) + if err != nil { + return err + } + + f.Name = "t." + f.Name + + switch f.Type.Kind() { + case reflect.String: + if err := emitCborUnmarshalStringField(w, f); err != nil { + return err + } + case reflect.Struct: + if err := emitCborUnmarshalStructField(w, f); err != nil { + return err + } + case reflect.Uint64: + if err := emitCborUnmarshalUint64Field(w, f); err != nil { + return err + } + case reflect.Int64: + if err := emitCborUnmarshalInt64Field(w, f); err != nil { + return err + } + case reflect.Uint8: + if err := emitCborUnmarshalUint8Field(w, f); err != nil { + return err + } + case reflect.Array: + fallthrough + case reflect.Slice: + if err := emitCborUnmarshalSliceField(w, f); err != nil { + return err + } + case reflect.Bool: + if err := emitCborUnmarshalBoolField(w, f); err != nil { + return err + } + case reflect.Map: + if err := emitCborUnmarshalMapField(w, f); err != nil { + return err + } + default: + return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind()) + } + } + + return doTemplate(w, gti, ` + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid){}) + } + } + + return nil +} +`) +} + +// Generates 'tuple representation' cbor encoders for the given type +func GenMapEncodersForType(gti *GenTypeInfo, w io.Writer) error { + if err := emitCborMarshalStructMap(w, gti); err != nil { + return err + } + + if err := emitCborUnmarshalStructMap(w, gti); err != nil { + return err + } + + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..ebbabe44783563fe19a4671625664c72c71a94ec --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/whyrusleeping/cbor-gen + +go 1.12 + +require ( + github.com/google/go-cmp v0.4.0 + github.com/ipfs/go-cid v0.0.6 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..67d9ac6c469d5211e79f7bd4e2025a74b67b9cf8 --- /dev/null +++ b/go.sum @@ -0,0 +1,34 @@ +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/ipfs/go-cid v0.0.6 h1:go0y+GcDOGeJIV01FeBsta4FHngoA4Wz7KMeLkXAhMs= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 h1:MHkK1uRtFbVqvAgvWxafZe54+5uBxLluGylDiKgdhwo= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/package.go b/package.go new file mode 100644 index 0000000000000000000000000000000000000000..7fa26f73d8cf8ce2eafa59de41c54d201439a8e3 --- /dev/null +++ b/package.go @@ -0,0 +1,100 @@ +package typegen + +import ( + "fmt" + "reflect" + "sort" + "strings" + "sync" +) + +var ( + knownPackageNamesMu sync.Mutex + pkgNameToPkgPath = make(map[string]string) + pkgPathToPkgName = make(map[string]string) + + defaultImports = []Import{ + {Name: "cbg", PkgPath: "github.com/whyrusleeping/cbor-gen"}, + {Name: "xerrors", PkgPath: "golang.org/x/xerrors"}, + {Name: "cid", PkgPath: "github.com/ipfs/go-cid"}, + } +) + +func init() { + for _, imp := range defaultImports { + if was, conflict := pkgNameToPkgPath[imp.Name]; conflict { + panic(fmt.Sprintf("reused pkg name %s for %s and %s", imp.Name, imp.PkgPath, was)) + } + if _, conflict := pkgPathToPkgName[imp.Name]; conflict { + panic(fmt.Sprintf("duplicate default import %s", imp.PkgPath)) + } + pkgNameToPkgPath[imp.Name] = imp.PkgPath + pkgPathToPkgName[imp.PkgPath] = imp.Name + } +} + +func resolvePkgName(path, typeName string) string { + parts := strings.Split(typeName, ".") + if len(parts) != 2 { + panic(fmt.Sprintf("expected type to have a package name: %s", typeName)) + } + defaultName := parts[0] + + knownPackageNamesMu.Lock() + defer knownPackageNamesMu.Unlock() + + // Check for a known name and use it. + if name, ok := pkgPathToPkgName[path]; ok { + return name + } + + // Allocate a name. + for i := 0; ; i++ { + tryName := defaultName + if i > 0 { + tryName = fmt.Sprintf("%s%d", defaultName, i) + } + if _, taken := pkgNameToPkgPath[tryName]; !taken { + pkgNameToPkgPath[tryName] = path + pkgPathToPkgName[path] = tryName + return tryName + } + } + +} + +type Import struct { + Name, PkgPath string +} + +func ImportsForType(currPkg string, t reflect.Type) []Import { + switch t.Kind() { + case reflect.Array, reflect.Slice, reflect.Ptr: + return ImportsForType(currPkg, t.Elem()) + case reflect.Map: + return dedupImports(append(ImportsForType(currPkg, t.Key()), ImportsForType(currPkg, t.Elem())...)) + default: + path := t.PkgPath() + if path == "" || path == currPkg { + // built-in or in current package. + return nil + } + + return []Import{{PkgPath: path, Name: resolvePkgName(path, t.String())}} + } +} + +func dedupImports(imps []Import) []Import { + impSet := make(map[string]string, len(imps)) + for _, imp := range imps { + impSet[imp.PkgPath] = imp.Name + } + deduped := make([]Import, 0, len(imps)) + for pkg, name := range impSet { + deduped = append(deduped, Import{Name: name, PkgPath: pkg}) + } + sort.Slice(deduped, func(i, j int) bool { + return deduped[i].PkgPath < deduped[j].PkgPath + }) + return deduped +} diff --git a/peeker.go b/peeker.go new file mode 100644 index 0000000000000000000000000000000000000000..2b0658cc923dd58231169c32009f303fbb4155a4 --- /dev/null +++ b/peeker.go @@ -0,0 +1,80 @@ +package typegen + +import ( + "bufio" + "io" +) + +// BytePeeker combines the Reader and ByteScanner interfaces. +type BytePeeker interface { + io.Reader + io.ByteScanner +} + +func GetPeeker(r io.Reader) BytePeeker { + if r, ok := r.(BytePeeker); ok { + return r + } + return &peeker{reader: r} +} + +// peeker is a non-buffering BytePeeker. +type peeker struct { + reader io.Reader + peekState int + lastByte byte +} + +const ( + peekEmpty = iota + peekSet + peekUnread +) + +func (p *peeker) Read(buf []byte) (n int, err error) { + // Read "nothing". I.e., read an error, maybe. + if len(buf) == 0 { + // There's something pending in the + if p.peekState == peekUnread { + return 0, nil + } + return p.reader.Read(nil) + } + + if p.peekState == peekUnread { + buf[0] = p.lastByte + n, err = p.reader.Read(buf[1:]) + n += 1 + } else { + n, err = p.reader.Read(buf) + } + if n > 0 { + p.peekState = peekSet + p.lastByte = buf[n-1] + } + return n, err +} + +func (p *peeker) ReadByte() (byte, error) { + if p.peekState == peekUnread { + p.peekState = peekSet + return p.lastByte, nil + } + var buf [1]byte + n, err := p.reader.Read(buf[:]) + if n == 0 { + return 0, err + } + b := buf[0] + p.lastByte = b + p.peekState = peekSet + return b, err +} + +func (p *peeker) UnreadByte() error { + if p.peekState != peekSet { + return bufio.ErrInvalidUnreadByte + } + p.peekState = peekUnread + return nil +} diff --git a/peeker_test.go b/peeker_test.go new file mode 100644 index 0000000000000000000000000000000000000000..17df76305b65c8a5d3c3b248866b3cd20124ff0d --- /dev/null +++ b/peeker_test.go @@ -0,0 +1,103 @@ +package typegen + +import ( + "bufio" + "bytes" + "io" + "testing" +) + +func TestPeeker(t *testing.T) { + buf := bytes.NewBuffer([]byte{0, 1, 2, 3}) + p := peeker{reader: buf} + n, err := p.Read(nil) + if err != nil { + t.Fatal(err) + } + if n != 0 { + t.Fatal(err) + } + + err = p.UnreadByte() + if err != bufio.ErrInvalidUnreadByte { + t.Fatal(err) + } + + // read 2 bytes + var out [2]byte + n, err = p.Read(out[:]) + if err != nil { + t.Fatal(err) + } + if n != 2 { + t.Fatalf("expected 2 bytes, got %d", n) + } + if !bytes.Equal(out[:], []byte{0, 1}) { + t.Fatalf("unexpected output") + } + + // unread that last byte and read it again. + err = p.UnreadByte() + if err != nil { + t.Fatal(err) + } + b, err := p.ReadByte() + if err != nil { + t.Fatal(err) + } + if b != 1 { + t.Fatal("expected 1") + } + + // unread that last byte then read 2 + err = p.UnreadByte() + if err != nil { + t.Fatal(err) + } + n, err = p.Read(out[:]) + if err != nil { + t.Fatal(err) + } + if n != 2 { + t.Fatalf("expected 2 bytes, got %d", n) + } + if !bytes.Equal(out[:], []byte{1, 2}) { + t.Fatalf("unexpected output") + } + + // read another byte + b, err = p.ReadByte() + if err != nil { + t.Fatal(err) + } + if b != 3 { + t.Fatal("expected 1") + } + + // Should read eof at end. + n, err = p.Read(out[:]) + if err != io.EOF { + t.Fatal(err) + } + if n != 0 { + t.Fatal("should have been at end") + } + // should unread eof + err = p.UnreadByte() + if err != nil { + t.Fatal(err) + } + + _, err = p.Read(nil) + if err != nil { + t.Fatal(err) + } + + b, err = p.ReadByte() + if err != nil { + t.Fatal(err) + } + if b != 3 { + t.Fatal("expected 1") + } +} diff --git a/testgen/main.go b/testgen/main.go new file mode 100644 index 0000000000000000000000000000000000000000..c94aee7f310b2f81abd6dbc5a0e13fa9a221562f --- /dev/null +++ b/testgen/main.go @@ -0,0 +1,28 @@ +package main + +import ( + cbg "github.com/whyrusleeping/cbor-gen" + types "github.com/whyrusleeping/cbor-gen/testing" +) + +func main() { + if err := cbg.WriteTupleEncodersToFile("testing/cbor_gen.go", "testing", + types.SignedArray{}, + types.SimpleTypeOne{}, + types.SimpleTypeTwo{}, + types.DeferredContainer{}, + types.FixedArrays{}, + types.ThingWithSomeTime{}, + ); err != nil { + panic(err) + } + + if err := cbg.WriteMapEncodersToFile("testing/cbor_map_gen.go", "testing", + types.SimpleTypeTree{}, + types.NeedScratchForMap{}, + types.SimpleStructV1{}, + types.SimpleStructV2{}, + ); err != nil { + panic(err) + } +} diff --git a/testing/bench_test.go b/testing/bench_test.go new file mode 100644 index 0000000000000000000000000000000000000000..88216ed210c6b355b45a8752b4df50d212693252 --- /dev/null +++ b/testing/bench_test.go @@ -0,0 +1,119 @@ +package testing + +import ( + "bytes" + "io" + "io/ioutil" + "math/rand" + "reflect" + "testing" + "testing/quick" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" +) + +func BenchmarkMarshaling(b *testing.B) { + r := rand.New(rand.NewSource(56887)) + val, ok := quick.Value(reflect.TypeOf(SimpleTypeTwo{}), r) + if !ok { + b.Fatal("failed to construct type") + } + + tt := val.Interface().(SimpleTypeTwo) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if err := tt.MarshalCBOR(ioutil.Discard); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkUnmarshaling(b *testing.B) { + r := rand.New(rand.NewSource(123456)) + val, ok := quick.Value(reflect.TypeOf(SimpleTypeTwo{}), r) + if !ok { + b.Fatal("failed to construct type") + } + + tt := val.Interface().(SimpleTypeTwo) + + buf := new(bytes.Buffer) + if err := tt.MarshalCBOR(buf); err != nil { + b.Fatal(err) + } + + reader := bytes.NewReader(buf.Bytes()) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + reader.Seek(0, io.SeekStart) + var tt SimpleTypeTwo + if err := tt.UnmarshalCBOR(reader); err != nil { + b.Fatal(err) + } + } + +} + +func BenchmarkLinkScan(b *testing.B) { + r := rand.New(rand.NewSource(123456)) + val, ok := quick.Value(reflect.TypeOf(SimpleTypeTwo{}), r) + if !ok { + b.Fatal("failed to construct type") + } + + tt := val.Interface().(SimpleTypeTwo) + + buf := new(bytes.Buffer) + if err := tt.MarshalCBOR(buf); err != nil { + b.Fatal(err) + } + + reader := bytes.NewReader(buf.Bytes()) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + reader.Seek(0, io.SeekStart) + if err := cbg.ScanForLinks(reader, func(cid.Cid) {}); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDeferred(b *testing.B) { + r := rand.New(rand.NewSource(123456)) + val, ok := quick.Value(reflect.TypeOf(SimpleTypeTwo{}), r) + if !ok { + b.Fatal("failed to construct type") + } + + tt := val.Interface().(SimpleTypeTwo) + + buf := new(bytes.Buffer) + if err := tt.MarshalCBOR(buf); err != nil { + b.Fatal(err) + } + + var ( + deferred cbg.Deferred + reader = bytes.NewReader(buf.Bytes()) + ) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + reader.Seek(0, io.SeekStart) + if err := deferred.UnmarshalCBOR(reader); err != nil { + b.Fatal(err) + } + } +} diff --git a/testing/cbor_gen.go b/testing/cbor_gen.go new file mode 100644 index 0000000000000000000000000000000000000000..f587961e7b61864e610b21bd44b305c93763124e --- /dev/null +++ b/testing/cbor_gen.go @@ -0,0 +1,1061 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package testing + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +var lengthBufSignedArray = []byte{129} + +func (t *SignedArray) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufSignedArray); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Signed ([]uint64) (slice) + if len(t.Signed) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Signed was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Signed))); err != nil { + return err + } + for _, v := range t.Signed { + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + return nil +} + +func (t *SignedArray) UnmarshalCBOR(r io.Reader) error { + *t = SignedArray{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 1 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Signed ([]uint64) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Signed: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Signed = make([]uint64, extra) + } + + for i := 0; i < int(extra); i++ { + + maj, val, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.Signed slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.Signed was not a uint, instead got %d", maj) + } + + t.Signed[i] = uint64(val) + } + + return nil +} + +var lengthBufSimpleTypeOne = []byte{133} + +func (t *SimpleTypeOne) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufSimpleTypeOne); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Foo (string) (string) + if len(t.Foo) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Foo was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Foo))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Foo)); err != nil { + return err + } + + // t.Value (uint64) (uint64) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Value)); err != nil { + return err + } + + // t.Binary ([]uint8) (slice) + if len(t.Binary) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Binary was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Binary))); err != nil { + return err + } + + if _, err := w.Write(t.Binary[:]); err != nil { + return err + } + + // t.Signed (int64) (int64) + if t.Signed >= 0 { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Signed)); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Signed-1)); err != nil { + return err + } + } + + // t.NString (testing.NamedString) (string) + if len(t.NString) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.NString was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.NString))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.NString)); err != nil { + return err + } + return nil +} + +func (t *SimpleTypeOne) UnmarshalCBOR(r io.Reader) error { + *t = SimpleTypeOne{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 5 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Foo (string) (string) + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Foo = string(sval) + } + // t.Value (uint64) (uint64) + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Value = uint64(extra) + + } + // t.Binary ([]uint8) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Binary: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Binary = make([]uint8, extra) + } + + if _, err := io.ReadFull(br, t.Binary[:]); err != nil { + return err + } + // t.Signed (int64) (int64) + { + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.Signed = int64(extraI) + } + // t.NString (testing.NamedString) (string) + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.NString = NamedString(sval) + } + return nil +} + +var lengthBufSimpleTypeTwo = []byte{137} + +func (t *SimpleTypeTwo) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufSimpleTypeTwo); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Stuff (testing.SimpleTypeTwo) (struct) + if err := t.Stuff.MarshalCBOR(w); err != nil { + return err + } + + // t.Others ([]uint64) (slice) + if len(t.Others) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Others was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Others))); err != nil { + return err + } + for _, v := range t.Others { + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + + // t.SignedOthers ([]int64) (slice) + if len(t.SignedOthers) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.SignedOthers was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SignedOthers))); err != nil { + return err + } + for _, v := range t.SignedOthers { + if v >= 0 { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-v-1)); err != nil { + return err + } + } + } + + // t.Test ([][]uint8) (slice) + if len(t.Test) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Test was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Test))); err != nil { + return err + } + for _, v := range t.Test { + if len(v) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field v was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(v))); err != nil { + return err + } + + if _, err := w.Write(v[:]); err != nil { + return err + } + } + + // t.Dog (string) (string) + if len(t.Dog) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Dog was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Dog))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Dog)); err != nil { + return err + } + + // t.Numbers ([]testing.NamedNumber) (slice) + if len(t.Numbers) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Numbers was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Numbers))); err != nil { + return err + } + for _, v := range t.Numbers { + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + + // t.Pizza (uint64) (uint64) + + if t.Pizza == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(*t.Pizza)); err != nil { + return err + } + } + + // t.PointyPizza (testing.NamedNumber) (uint64) + + if t.PointyPizza == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(*t.PointyPizza)); err != nil { + return err + } + } + + // t.Arrrrrghay ([3]testing.SimpleTypeOne) (array) + if len(t.Arrrrrghay) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Arrrrrghay was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Arrrrrghay))); err != nil { + return err + } + for _, v := range t.Arrrrrghay { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + return nil +} + +func (t *SimpleTypeTwo) UnmarshalCBOR(r io.Reader) error { + *t = SimpleTypeTwo{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 9 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Stuff (testing.SimpleTypeTwo) (struct) + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + t.Stuff = new(SimpleTypeTwo) + if err := t.Stuff.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Stuff pointer: %w", err) + } + } + + } + // t.Others ([]uint64) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Others: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Others = make([]uint64, extra) + } + + for i := 0; i < int(extra); i++ { + + maj, val, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.Others slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.Others was not a uint, instead got %d", maj) + } + + t.Others[i] = uint64(val) + } + + // t.SignedOthers ([]int64) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.SignedOthers: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.SignedOthers = make([]int64, extra) + } + + for i := 0; i < int(extra); i++ { + { + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.SignedOthers[i] = int64(extraI) + } + } + + // t.Test ([][]uint8) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Test: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Test = make([][]uint8, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Test[i]: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Test[i] = make([]uint8, extra) + } + + if _, err := io.ReadFull(br, t.Test[i][:]); err != nil { + return err + } + } + } + + // t.Dog (string) (string) + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Dog = string(sval) + } + // t.Numbers ([]testing.NamedNumber) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Numbers: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Numbers = make([]NamedNumber, extra) + } + + for i := 0; i < int(extra); i++ { + + maj, val, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.Numbers slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.Numbers was not a uint, instead got %d", maj) + } + + t.Numbers[i] = NamedNumber(val) + } + + // t.Pizza (uint64) (uint64) + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + typed := uint64(extra) + t.Pizza = &typed + } + + } + // t.PointyPizza (testing.NamedNumber) (uint64) + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + typed := NamedNumber(extra) + t.PointyPizza = &typed + } + + } + // t.Arrrrrghay ([3]testing.SimpleTypeOne) (array) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Arrrrrghay: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra != 3 { + return fmt.Errorf("expected array to have 3 elements") + } + + t.Arrrrrghay = [3]SimpleTypeOne{} + + for i := 0; i < int(extra); i++ { + + var v SimpleTypeOne + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.Arrrrrghay[i] = v + } + + return nil +} + +var lengthBufDeferredContainer = []byte{131} + +func (t *DeferredContainer) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufDeferredContainer); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Stuff (testing.SimpleTypeOne) (struct) + if err := t.Stuff.MarshalCBOR(w); err != nil { + return err + } + + // t.Deferred (typegen.Deferred) (struct) + if err := t.Deferred.MarshalCBOR(w); err != nil { + return err + } + + // t.Value (uint64) (uint64) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Value)); err != nil { + return err + } + + return nil +} + +func (t *DeferredContainer) UnmarshalCBOR(r io.Reader) error { + *t = DeferredContainer{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Stuff (testing.SimpleTypeOne) (struct) + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + t.Stuff = new(SimpleTypeOne) + if err := t.Stuff.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Stuff pointer: %w", err) + } + } + + } + // t.Deferred (typegen.Deferred) (struct) + + { + + t.Deferred = new(cbg.Deferred) + + if err := t.Deferred.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("failed to read deferred field: %w", err) + } + } + // t.Value (uint64) (uint64) + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Value = uint64(extra) + + } + return nil +} + +var lengthBufFixedArrays = []byte{131} + +func (t *FixedArrays) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufFixedArrays); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Bytes ([20]uint8) (array) + if len(t.Bytes) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Bytes was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Bytes))); err != nil { + return err + } + + if _, err := w.Write(t.Bytes[:]); err != nil { + return err + } + + // t.Uint8 ([20]uint8) (array) + if len(t.Uint8) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Uint8 was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Uint8))); err != nil { + return err + } + + if _, err := w.Write(t.Uint8[:]); err != nil { + return err + } + + // t.Uint64 ([20]uint64) (array) + if len(t.Uint64) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Uint64 was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Uint64))); err != nil { + return err + } + for _, v := range t.Uint64 { + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + return nil +} + +func (t *FixedArrays) UnmarshalCBOR(r io.Reader) error { + *t = FixedArrays{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Bytes ([20]uint8) (array) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Bytes: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra != 20 { + return fmt.Errorf("expected array to have 20 elements") + } + + t.Bytes = [20]uint8{} + + if _, err := io.ReadFull(br, t.Bytes[:]); err != nil { + return err + } + // t.Uint8 ([20]uint8) (array) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Uint8: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra != 20 { + return fmt.Errorf("expected array to have 20 elements") + } + + t.Uint8 = [20]uint8{} + + if _, err := io.ReadFull(br, t.Uint8[:]); err != nil { + return err + } + // t.Uint64 ([20]uint64) (array) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Uint64: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra != 20 { + return fmt.Errorf("expected array to have 20 elements") + } + + t.Uint64 = [20]uint64{} + + for i := 0; i < int(extra); i++ { + + maj, val, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.Uint64 slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.Uint64 was not a uint, instead got %d", maj) + } + + t.Uint64[i] = uint64(val) + } + + return nil +} + +var lengthBufThingWithSomeTime = []byte{131} + +func (t *ThingWithSomeTime) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufThingWithSomeTime); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.When (typegen.CborTime) (struct) + if err := t.When.MarshalCBOR(w); err != nil { + return err + } + + // t.Stuff (int64) (int64) + if t.Stuff >= 0 { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Stuff)); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Stuff-1)); err != nil { + return err + } + } + + // t.CatName (string) (string) + if len(t.CatName) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.CatName was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.CatName))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.CatName)); err != nil { + return err + } + return nil +} + +func (t *ThingWithSomeTime) UnmarshalCBOR(r io.Reader) error { + *t = ThingWithSomeTime{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.When (typegen.CborTime) (struct) + + { + + if err := t.When.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.When: %w", err) + } + + } + // t.Stuff (int64) (int64) + { + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.Stuff = int64(extraI) + } + // t.CatName (string) (string) + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.CatName = string(sval) + } + return nil +} diff --git a/testing/cbor_map_gen.go b/testing/cbor_map_gen.go new file mode 100644 index 0000000000000000000000000000000000000000..b7d9000067647a4efaa8c8c87804ea98e718592f --- /dev/null +++ b/testing/cbor_map_gen.go @@ -0,0 +1,1594 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package testing + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +func (t *SimpleTypeTree) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{167}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Stuff (testing.SimpleTypeTree) (struct) + if len("Stuff") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Stuff\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Stuff"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Stuff")); err != nil { + return err + } + + if err := t.Stuff.MarshalCBOR(w); err != nil { + return err + } + + // t.Stufff (testing.SimpleTypeTwo) (struct) + if len("Stufff") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Stufff\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Stufff"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Stufff")); err != nil { + return err + } + + if err := t.Stufff.MarshalCBOR(w); err != nil { + return err + } + + // t.Others ([]uint64) (slice) + if len("Others") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Others\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Others"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Others")); err != nil { + return err + } + + if len(t.Others) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Others was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Others))); err != nil { + return err + } + for _, v := range t.Others { + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + + // t.Test ([][]uint8) (slice) + if len("Test") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Test\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Test"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Test")); err != nil { + return err + } + + if len(t.Test) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Test was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Test))); err != nil { + return err + } + for _, v := range t.Test { + if len(v) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field v was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(v))); err != nil { + return err + } + + if _, err := w.Write(v[:]); err != nil { + return err + } + } + + // t.Dog (string) (string) + if len("Dog") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Dog\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Dog"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Dog")); err != nil { + return err + } + + if len(t.Dog) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Dog was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Dog))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Dog)); err != nil { + return err + } + + // t.SixtyThreeBitIntegerWithASignBit (int64) (int64) + if len("SixtyThreeBitIntegerWithASignBit") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"SixtyThreeBitIntegerWithASignBit\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("SixtyThreeBitIntegerWithASignBit"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("SixtyThreeBitIntegerWithASignBit")); err != nil { + return err + } + + if t.SixtyThreeBitIntegerWithASignBit >= 0 { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.SixtyThreeBitIntegerWithASignBit)); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.SixtyThreeBitIntegerWithASignBit-1)); err != nil { + return err + } + } + + // t.NotPizza (uint64) (uint64) + if len("NotPizza") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NotPizza\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NotPizza"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NotPizza")); err != nil { + return err + } + + if t.NotPizza == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(*t.NotPizza)); err != nil { + return err + } + } + + return nil +} + +func (t *SimpleTypeTree) UnmarshalCBOR(r io.Reader) error { + *t = SimpleTypeTree{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SimpleTypeTree: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Stuff (testing.SimpleTypeTree) (struct) + case "Stuff": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + t.Stuff = new(SimpleTypeTree) + if err := t.Stuff.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Stuff pointer: %w", err) + } + } + + } + // t.Stufff (testing.SimpleTypeTwo) (struct) + case "Stufff": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + t.Stufff = new(SimpleTypeTwo) + if err := t.Stufff.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Stufff pointer: %w", err) + } + } + + } + // t.Others ([]uint64) (slice) + case "Others": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Others: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Others = make([]uint64, extra) + } + + for i := 0; i < int(extra); i++ { + + maj, val, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.Others slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.Others was not a uint, instead got %d", maj) + } + + t.Others[i] = uint64(val) + } + + // t.Test ([][]uint8) (slice) + case "Test": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Test: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Test = make([][]uint8, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Test[i]: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Test[i] = make([]uint8, extra) + } + + if _, err := io.ReadFull(br, t.Test[i][:]); err != nil { + return err + } + } + } + + // t.Dog (string) (string) + case "Dog": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Dog = string(sval) + } + // t.SixtyThreeBitIntegerWithASignBit (int64) (int64) + case "SixtyThreeBitIntegerWithASignBit": + { + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.SixtyThreeBitIntegerWithASignBit = int64(extraI) + } + // t.NotPizza (uint64) (uint64) + case "NotPizza": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + typed := uint64(extra) + t.NotPizza = &typed + } + + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} +func (t *NeedScratchForMap) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{161}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Thing (bool) (bool) + if len("Thing") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Thing\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Thing"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Thing")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.Thing); err != nil { + return err + } + return nil +} + +func (t *NeedScratchForMap) UnmarshalCBOR(r io.Reader) error { + *t = NeedScratchForMap{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("NeedScratchForMap: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Thing (bool) (bool) + case "Thing": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.Thing = false + case 21: + t.Thing = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} +func (t *SimpleStructV1) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{167}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.OldStr (string) (string) + if len("OldStr") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldStr\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldStr"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldStr")); err != nil { + return err + } + + if len(t.OldStr) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.OldStr was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.OldStr))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.OldStr)); err != nil { + return err + } + + // t.OldBytes ([]uint8) (slice) + if len("OldBytes") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldBytes\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldBytes"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldBytes")); err != nil { + return err + } + + if len(t.OldBytes) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.OldBytes was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.OldBytes))); err != nil { + return err + } + + if _, err := w.Write(t.OldBytes[:]); err != nil { + return err + } + + // t.OldNum (uint64) (uint64) + if len("OldNum") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldNum\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldNum"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldNum")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.OldNum)); err != nil { + return err + } + + // t.OldPtr (cid.Cid) (struct) + if len("OldPtr") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldPtr\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldPtr"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldPtr")); err != nil { + return err + } + + if t.OldPtr == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCidBuf(scratch, w, *t.OldPtr); err != nil { + return xerrors.Errorf("failed to write cid field t.OldPtr: %w", err) + } + } + + // t.OldMap (map[string]testing.SimpleTypeOne) (map) + if len("OldMap") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldMap\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldMap"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldMap")); err != nil { + return err + } + + { + if len(t.OldMap) > 4096 { + return xerrors.Errorf("cannot marshal t.OldMap map too large") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajMap, uint64(len(t.OldMap))); err != nil { + return err + } + + keys := make([]string, 0, len(t.OldMap)) + for k := range t.OldMap { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := t.OldMap[k] + + if len(k) > cbg.MaxLength { + return xerrors.Errorf("Value in field k was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(k))); err != nil { + return err + } + if _, err := io.WriteString(w, string(k)); err != nil { + return err + } + + if err := v.MarshalCBOR(w); err != nil { + return err + } + + } + } + + // t.OldArray ([]testing.SimpleTypeOne) (slice) + if len("OldArray") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldArray\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldArray"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldArray")); err != nil { + return err + } + + if len(t.OldArray) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.OldArray was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.OldArray))); err != nil { + return err + } + for _, v := range t.OldArray { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + + // t.OldStruct (testing.SimpleTypeOne) (struct) + if len("OldStruct") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldStruct\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldStruct"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldStruct")); err != nil { + return err + } + + if err := t.OldStruct.MarshalCBOR(w); err != nil { + return err + } + return nil +} + +func (t *SimpleStructV1) UnmarshalCBOR(r io.Reader) error { + *t = SimpleStructV1{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SimpleStructV1: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.OldStr (string) (string) + case "OldStr": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.OldStr = string(sval) + } + // t.OldBytes ([]uint8) (slice) + case "OldBytes": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.OldBytes: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.OldBytes = make([]uint8, extra) + } + + if _, err := io.ReadFull(br, t.OldBytes[:]); err != nil { + return err + } + // t.OldNum (uint64) (uint64) + case "OldNum": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.OldNum = uint64(extra) + + } + // t.OldPtr (cid.Cid) (struct) + case "OldPtr": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.OldPtr: %w", err) + } + + t.OldPtr = &c + } + + } + // t.OldMap (map[string]testing.SimpleTypeOne) (map) + case "OldMap": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("expected a map (major type 5)") + } + if extra > 4096 { + return fmt.Errorf("t.OldMap: map too large") + } + + t.OldMap = make(map[string]SimpleTypeOne, extra) + + for i, l := 0, int(extra); i < l; i++ { + + var k string + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + k = string(sval) + } + + var v SimpleTypeOne + + { + + if err := v.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling v: %w", err) + } + + } + + t.OldMap[k] = v + + } + // t.OldArray ([]testing.SimpleTypeOne) (slice) + case "OldArray": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.OldArray: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.OldArray = make([]SimpleTypeOne, extra) + } + + for i := 0; i < int(extra); i++ { + + var v SimpleTypeOne + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.OldArray[i] = v + } + + // t.OldStruct (testing.SimpleTypeOne) (struct) + case "OldStruct": + + { + + if err := t.OldStruct.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.OldStruct: %w", err) + } + + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} +func (t *SimpleStructV2) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{174}); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.OldStr (string) (string) + if len("OldStr") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldStr\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldStr"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldStr")); err != nil { + return err + } + + if len(t.OldStr) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.OldStr was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.OldStr))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.OldStr)); err != nil { + return err + } + + // t.NewStr (string) (string) + if len("NewStr") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NewStr\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NewStr"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NewStr")); err != nil { + return err + } + + if len(t.NewStr) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.NewStr was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.NewStr))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.NewStr)); err != nil { + return err + } + + // t.OldBytes ([]uint8) (slice) + if len("OldBytes") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldBytes\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldBytes"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldBytes")); err != nil { + return err + } + + if len(t.OldBytes) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.OldBytes was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.OldBytes))); err != nil { + return err + } + + if _, err := w.Write(t.OldBytes[:]); err != nil { + return err + } + + // t.NewBytes ([]uint8) (slice) + if len("NewBytes") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NewBytes\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NewBytes"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NewBytes")); err != nil { + return err + } + + if len(t.NewBytes) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.NewBytes was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.NewBytes))); err != nil { + return err + } + + if _, err := w.Write(t.NewBytes[:]); err != nil { + return err + } + + // t.OldNum (uint64) (uint64) + if len("OldNum") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldNum\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldNum"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldNum")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.OldNum)); err != nil { + return err + } + + // t.NewNum (uint64) (uint64) + if len("NewNum") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NewNum\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NewNum"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NewNum")); err != nil { + return err + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.NewNum)); err != nil { + return err + } + + // t.OldPtr (cid.Cid) (struct) + if len("OldPtr") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldPtr\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldPtr"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldPtr")); err != nil { + return err + } + + if t.OldPtr == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCidBuf(scratch, w, *t.OldPtr); err != nil { + return xerrors.Errorf("failed to write cid field t.OldPtr: %w", err) + } + } + + // t.NewPtr (cid.Cid) (struct) + if len("NewPtr") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NewPtr\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NewPtr"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NewPtr")); err != nil { + return err + } + + if t.NewPtr == nil { + if _, err := w.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCidBuf(scratch, w, *t.NewPtr); err != nil { + return xerrors.Errorf("failed to write cid field t.NewPtr: %w", err) + } + } + + // t.OldMap (map[string]testing.SimpleTypeOne) (map) + if len("OldMap") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldMap\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldMap"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldMap")); err != nil { + return err + } + + { + if len(t.OldMap) > 4096 { + return xerrors.Errorf("cannot marshal t.OldMap map too large") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajMap, uint64(len(t.OldMap))); err != nil { + return err + } + + keys := make([]string, 0, len(t.OldMap)) + for k := range t.OldMap { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := t.OldMap[k] + + if len(k) > cbg.MaxLength { + return xerrors.Errorf("Value in field k was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(k))); err != nil { + return err + } + if _, err := io.WriteString(w, string(k)); err != nil { + return err + } + + if err := v.MarshalCBOR(w); err != nil { + return err + } + + } + } + + // t.NewMap (map[string]testing.SimpleTypeOne) (map) + if len("NewMap") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NewMap\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NewMap"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NewMap")); err != nil { + return err + } + + { + if len(t.NewMap) > 4096 { + return xerrors.Errorf("cannot marshal t.NewMap map too large") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajMap, uint64(len(t.NewMap))); err != nil { + return err + } + + keys := make([]string, 0, len(t.NewMap)) + for k := range t.NewMap { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := t.NewMap[k] + + if len(k) > cbg.MaxLength { + return xerrors.Errorf("Value in field k was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(k))); err != nil { + return err + } + if _, err := io.WriteString(w, string(k)); err != nil { + return err + } + + if err := v.MarshalCBOR(w); err != nil { + return err + } + + } + } + + // t.OldArray ([]testing.SimpleTypeOne) (slice) + if len("OldArray") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldArray\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldArray"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldArray")); err != nil { + return err + } + + if len(t.OldArray) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.OldArray was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.OldArray))); err != nil { + return err + } + for _, v := range t.OldArray { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + + // t.NewArray ([]testing.SimpleTypeOne) (slice) + if len("NewArray") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NewArray\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NewArray"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NewArray")); err != nil { + return err + } + + if len(t.NewArray) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.NewArray was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.NewArray))); err != nil { + return err + } + for _, v := range t.NewArray { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + + // t.OldStruct (testing.SimpleTypeOne) (struct) + if len("OldStruct") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"OldStruct\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("OldStruct"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("OldStruct")); err != nil { + return err + } + + if err := t.OldStruct.MarshalCBOR(w); err != nil { + return err + } + + // t.NewStruct (testing.SimpleTypeOne) (struct) + if len("NewStruct") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"NewStruct\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("NewStruct"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("NewStruct")); err != nil { + return err + } + + if err := t.NewStruct.MarshalCBOR(w); err != nil { + return err + } + return nil +} + +func (t *SimpleStructV2) UnmarshalCBOR(r io.Reader) error { + *t = SimpleStructV2{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SimpleStructV2: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.OldStr (string) (string) + case "OldStr": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.OldStr = string(sval) + } + // t.NewStr (string) (string) + case "NewStr": + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.NewStr = string(sval) + } + // t.OldBytes ([]uint8) (slice) + case "OldBytes": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.OldBytes: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.OldBytes = make([]uint8, extra) + } + + if _, err := io.ReadFull(br, t.OldBytes[:]); err != nil { + return err + } + // t.NewBytes ([]uint8) (slice) + case "NewBytes": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.NewBytes: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.NewBytes = make([]uint8, extra) + } + + if _, err := io.ReadFull(br, t.NewBytes[:]); err != nil { + return err + } + // t.OldNum (uint64) (uint64) + case "OldNum": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.OldNum = uint64(extra) + + } + // t.NewNum (uint64) (uint64) + case "NewNum": + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.NewNum = uint64(extra) + + } + // t.OldPtr (cid.Cid) (struct) + case "OldPtr": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.OldPtr: %w", err) + } + + t.OldPtr = &c + } + + } + // t.NewPtr (cid.Cid) (struct) + case "NewPtr": + + { + + b, err := br.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := br.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.NewPtr: %w", err) + } + + t.NewPtr = &c + } + + } + // t.OldMap (map[string]testing.SimpleTypeOne) (map) + case "OldMap": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("expected a map (major type 5)") + } + if extra > 4096 { + return fmt.Errorf("t.OldMap: map too large") + } + + t.OldMap = make(map[string]SimpleTypeOne, extra) + + for i, l := 0, int(extra); i < l; i++ { + + var k string + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + k = string(sval) + } + + var v SimpleTypeOne + + { + + if err := v.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling v: %w", err) + } + + } + + t.OldMap[k] = v + + } + // t.NewMap (map[string]testing.SimpleTypeOne) (map) + case "NewMap": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajMap { + return fmt.Errorf("expected a map (major type 5)") + } + if extra > 4096 { + return fmt.Errorf("t.NewMap: map too large") + } + + t.NewMap = make(map[string]SimpleTypeOne, extra) + + for i, l := 0, int(extra); i < l; i++ { + + var k string + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + k = string(sval) + } + + var v SimpleTypeOne + + { + + if err := v.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling v: %w", err) + } + + } + + t.NewMap[k] = v + + } + // t.OldArray ([]testing.SimpleTypeOne) (slice) + case "OldArray": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.OldArray: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.OldArray = make([]SimpleTypeOne, extra) + } + + for i := 0; i < int(extra); i++ { + + var v SimpleTypeOne + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.OldArray[i] = v + } + + // t.NewArray ([]testing.SimpleTypeOne) (slice) + case "NewArray": + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.NewArray: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.NewArray = make([]SimpleTypeOne, extra) + } + + for i := 0; i < int(extra); i++ { + + var v SimpleTypeOne + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.NewArray[i] = v + } + + // t.OldStruct (testing.SimpleTypeOne) (struct) + case "OldStruct": + + { + + if err := t.OldStruct.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.OldStruct: %w", err) + } + + } + // t.NewStruct (testing.SimpleTypeOne) (struct) + case "NewStruct": + + { + + if err := t.NewStruct.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.NewStruct: %w", err) + } + + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} diff --git a/testing/roundtrip_test.go b/testing/roundtrip_test.go new file mode 100644 index 0000000000000000000000000000000000000000..af5be82aa28cf0a4b909b0231f6f9fd3c426e6d6 --- /dev/null +++ b/testing/roundtrip_test.go @@ -0,0 +1,317 @@ +package testing + +import ( + "bytes" + "encoding/json" + "github.com/ipfs/go-cid" + "math/rand" + "reflect" + "testing" + "testing/quick" + "time" + + "github.com/google/go-cmp/cmp" + cbg "github.com/whyrusleeping/cbor-gen" +) + +var alwaysEqual = cmp.Comparer(func(_, _ interface{}) bool { return true }) + +// This option handles slices and maps of any type. +var alwaysEqualOpt = cmp.FilterValues(func(x, y interface{}) bool { + vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) + return (vx.IsValid() && vy.IsValid() && vx.Type() == vy.Type()) && + (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) && + (vx.Len() == 0 && vy.Len() == 0) +}, alwaysEqual) + +func TestSimpleSigned(t *testing.T) { + testTypeRoundtrips(t, reflect.TypeOf(SignedArray{})) +} + +func TestSimpleTypeOne(t *testing.T) { + testTypeRoundtrips(t, reflect.TypeOf(SimpleTypeOne{})) +} + +func TestSimpleTypeTwo(t *testing.T) { + testTypeRoundtrips(t, reflect.TypeOf(SimpleTypeTwo{})) +} + +func TestSimpleTypeTree(t *testing.T) { + testTypeRoundtrips(t, reflect.TypeOf(SimpleTypeTree{})) +} + +func TestNeedScratchForMap(t *testing.T) { + testTypeRoundtrips(t, reflect.TypeOf(NeedScratchForMap{})) +} + +func testValueRoundtrip(t *testing.T, obj cbg.CBORMarshaler, nobj cbg.CBORUnmarshaler) { + + buf := new(bytes.Buffer) + if err := obj.MarshalCBOR(buf); err != nil { + t.Fatal("i guess its fine to fail marshaling") + } + + enc := buf.Bytes() + + if err := nobj.UnmarshalCBOR(bytes.NewReader(enc)); err != nil { + t.Logf("got bad bytes: %x", enc) + t.Fatal("failed to round trip object: ", err) + } + + if !cmp.Equal(obj, nobj, alwaysEqualOpt) { + t.Logf("%#v != %#v", obj, nobj) + t.Log("not equal after round trip!") + } + + nbuf := new(bytes.Buffer) + if err := nobj.(cbg.CBORMarshaler).MarshalCBOR(nbuf); err != nil { + t.Fatal("failed to remarshal object: ", err) + } + + if !bytes.Equal(nbuf.Bytes(), enc) { + t.Fatalf("objects encodings different: %x != %x", nbuf.Bytes(), enc) + } + +} + +func testTypeRoundtrips(t *testing.T, typ reflect.Type) { + r := rand.New(rand.NewSource(56887)) + for i := 0; i < 1000; i++ { + val, ok := quick.Value(typ, r) + if !ok { + t.Fatal("failed to generate test value") + } + + obj := val.Addr().Interface().(cbg.CBORMarshaler) + nobj := reflect.New(typ).Interface().(cbg.CBORUnmarshaler) + testValueRoundtrip(t, obj, nobj) + } +} + +func TestDeferredContainer(t *testing.T) { + zero := &DeferredContainer{} + recepticle := &DeferredContainer{} + testValueRoundtrip(t, zero, recepticle) +} + +func TestNilValueDeferredUnmarshaling(t *testing.T) { + var zero DeferredContainer + zero.Deferred = &cbg.Deferred{Raw: []byte{0xf6}} + + buf := new(bytes.Buffer) + if err := zero.MarshalCBOR(buf); err != nil { + t.Fatal(err) + } + + var n DeferredContainer + if err := n.UnmarshalCBOR(buf); err != nil { + t.Fatal(err) + } + + if n.Deferred == nil { + t.Fatal("shouldnt be nil!") + } +} + +func TestFixedArrays(t *testing.T) { + zero := &FixedArrays{} + recepticle := &FixedArrays{} + testValueRoundtrip(t, zero, recepticle) +} + +func TestTimeIsh(t *testing.T) { + val := &ThingWithSomeTime{ + When: cbg.CborTime(time.Now()), + Stuff: 1234, + CatName: "hank", + } + + buf := new(bytes.Buffer) + if err := val.MarshalCBOR(buf); err != nil { + t.Fatal(err) + } + + out := ThingWithSomeTime{} + if err := out.UnmarshalCBOR(buf); err != nil { + t.Fatal(err) + } + + if out.When.Time().UnixNano() != val.When.Time().UnixNano() { + t.Fatal("time didnt round trip properly", out.When.Time(), val.When.Time()) + } + + if out.Stuff != val.Stuff { + t.Fatal("no") + } + + if out.CatName != val.CatName { + t.Fatal("no") + } + + b, err := json.Marshal(val) + if err != nil { + t.Fatal(err) + } + + var out2 ThingWithSomeTime + if err := json.Unmarshal(b, &out2); err != nil { + t.Fatal(err) + } + + if out2.When != out.When { + t.Fatal(err) + } + +} + +func TestLessToMoreFieldsRoundTrip(t *testing.T) { + dummyCid, _ := cid.Parse("bafkqaaa") + simpleTypeOne := SimpleTypeOne{ + Foo: "foo", + Value: 1, + Binary: []byte("bin"), + Signed: -1, + NString: "namedstr", + } + obj := &SimpleStructV1{ + OldStr: "hello", + OldBytes: []byte("bytes"), + OldNum: 10, + OldPtr: &dummyCid, + OldMap: map[string]SimpleTypeOne{"first": simpleTypeOne}, + OldArray: []SimpleTypeOne{simpleTypeOne}, + OldStruct: simpleTypeOne, + } + + buf := new(bytes.Buffer) + if err := obj.MarshalCBOR(buf); err != nil { + t.Fatal("failed marshaling", err) + } + + enc := buf.Bytes() + + nobj := SimpleStructV2{} + if err := nobj.UnmarshalCBOR(bytes.NewReader(enc)); err != nil { + t.Logf("got bad bytes: %x", enc) + t.Fatal("failed to round trip object: ", err) + } + + if obj.OldStr != nobj.OldStr { + t.Fatal("mismatch ", obj.OldStr, " != ", nobj.OldStr) + } + if nobj.NewStr != "" { + t.Fatal("expected field to be zero value") + } + + if obj.OldNum != nobj.OldNum { + t.Fatal("mismatch ", obj.OldNum, " != ", nobj.OldNum) + } + if nobj.NewNum != 0 { + t.Fatal("expected field to be zero value") + } + + if !bytes.Equal(obj.OldBytes, nobj.OldBytes) { + t.Fatal("mismatch ", obj.OldBytes, " != ", nobj.OldBytes) + } + if nobj.NewBytes != nil { + t.Fatal("expected field to be zero value") + } + + if *obj.OldPtr != *nobj.OldPtr { + t.Fatal("mismatch ", obj.OldPtr, " != ", nobj.OldPtr) + } + if nobj.NewPtr != nil { + t.Fatal("expected field to be zero value") + } + + if !cmp.Equal(obj.OldMap, nobj.OldMap) { + t.Fatal("mismatch map marshal / unmarshal") + } + if len(nobj.NewMap) != 0 { + t.Fatal("expected field to be zero value") + } + + if !cmp.Equal(obj.OldArray, nobj.OldArray) { + t.Fatal("mismatch array marshal / unmarshal") + } + if len(nobj.NewArray) != 0 { + t.Fatal("expected field to be zero value") + } + + if !cmp.Equal(obj.OldStruct, nobj.OldStruct) { + t.Fatal("mismatch struct marshal / unmarshal") + } + if !cmp.Equal(nobj.NewStruct, SimpleTypeOne{}) { + t.Fatal("expected field to be zero value") + } +} + +func TestMoreToLessFieldsRoundTrip(t *testing.T) { + dummyCid1, _ := cid.Parse("bafkqaaa") + dummyCid2, _ := cid.Parse("bafkqaab") + simpleType1 := SimpleTypeOne{ + Foo: "foo", + Value: 1, + Binary: []byte("bin"), + Signed: -1, + NString: "namedstr", + } + simpleType2 := SimpleTypeOne{ + Foo: "bar", + Value: 2, + Binary: []byte("bin2"), + Signed: -2, + NString: "namedstr2", + } + obj := &SimpleStructV2{ + OldStr: "oldstr", + NewStr: "newstr", + OldBytes: []byte("oldbytes"), + NewBytes: []byte("newbytes"), + OldNum: 10, + NewNum: 11, + OldPtr: &dummyCid1, + NewPtr: &dummyCid2, + OldMap: map[string]SimpleTypeOne{"foo": simpleType1}, + NewMap: map[string]SimpleTypeOne{"bar": simpleType2}, + OldArray: []SimpleTypeOne{simpleType1}, + NewArray: []SimpleTypeOne{simpleType1, simpleType2}, + OldStruct: simpleType1, + NewStruct: simpleType2, + } + + buf := new(bytes.Buffer) + if err := obj.MarshalCBOR(buf); err != nil { + t.Fatal("failed marshaling", err) + } + + enc := buf.Bytes() + + nobj := SimpleStructV1{} + if err := nobj.UnmarshalCBOR(bytes.NewReader(enc)); err != nil { + t.Logf("got bad bytes: %x", enc) + t.Fatal("failed to round trip object: ", err) + } + + if obj.OldStr != nobj.OldStr { + t.Fatal("mismatch", obj.OldStr, " != ", nobj.OldStr) + } + if obj.OldNum != nobj.OldNum { + t.Fatal("mismatch ", obj.OldNum, " != ", nobj.OldNum) + } + if !bytes.Equal(obj.OldBytes, nobj.OldBytes) { + t.Fatal("mismatch ", obj.OldBytes, " != ", nobj.OldBytes) + } + if *obj.OldPtr != *nobj.OldPtr { + t.Fatal("mismatch ", obj.OldPtr, " != ", nobj.OldPtr) + } + if !cmp.Equal(obj.OldMap, nobj.OldMap) { + t.Fatal("mismatch map marshal / unmarshal") + } + if !cmp.Equal(obj.OldArray, nobj.OldArray) { + t.Fatal("mismatch array marshal / unmarshal") + } + if !cmp.Equal(obj.OldStruct, nobj.OldStruct) { + t.Fatal("mismatch struct marshal / unmarshal") + } +} diff --git a/testing/types.go b/testing/types.go new file mode 100644 index 0000000000000000000000000000000000000000..09216a9f7519b79fc6d98c372220c07228703b90 --- /dev/null +++ b/testing/types.go @@ -0,0 +1,101 @@ +package testing + +import ( + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" +) + +const Thingc = 3 + +type NamedNumber uint64 +type NamedString string + +type SignedArray struct { + Signed []uint64 +} + +type SimpleTypeOne struct { + Foo string + Value uint64 + Binary []byte + Signed int64 + NString NamedString +} + +type SimpleTypeTwo struct { + Stuff *SimpleTypeTwo + Others []uint64 + SignedOthers []int64 + Test [][]byte + Dog string + Numbers []NamedNumber + Pizza *uint64 + PointyPizza *NamedNumber + Arrrrrghay [Thingc]SimpleTypeOne +} + +type SimpleTypeTree struct { + Stuff *SimpleTypeTree + Stufff *SimpleTypeTwo + Others []uint64 + Test [][]byte + Dog string + SixtyThreeBitIntegerWithASignBit int64 + NotPizza *uint64 +} + +type SimpleStructV1 struct { + OldStr string + OldBytes []byte + OldNum uint64 + OldPtr *cid.Cid + OldMap map[string]SimpleTypeOne + OldArray []SimpleTypeOne + OldStruct SimpleTypeOne +} + +type SimpleStructV2 struct { + OldStr string + NewStr string + + OldBytes []byte + NewBytes []byte + + OldNum uint64 + NewNum uint64 + + OldPtr *cid.Cid + NewPtr *cid.Cid + + OldMap map[string]SimpleTypeOne + NewMap map[string]SimpleTypeOne + + OldArray []SimpleTypeOne + NewArray []SimpleTypeOne + + OldStruct SimpleTypeOne + NewStruct SimpleTypeOne +} + +type DeferredContainer struct { + Stuff *SimpleTypeOne + Deferred *cbg.Deferred + Value uint64 +} + +type FixedArrays struct { + Bytes [20]byte + Uint8 [20]uint8 + Uint64 [20]uint64 +} + +type ThingWithSomeTime struct { + When cbg.CborTime + Stuff int64 + CatName string +} + +// Do not add fields to this type. +type NeedScratchForMap struct { + Thing bool +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..2243ad4d177a3fd51d3dc0c2134a3b54b4975c86 --- /dev/null +++ b/utils.go @@ -0,0 +1,733 @@ +package typegen + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "time" + + cid "github.com/ipfs/go-cid" +) + +const maxCidLength = 100 +const maxHeaderSize = 9 + +// discard is a helper function to discard data from a reader, special-casing +// the most common readers we encounter in this library for a significant +// performance boost. +func discard(br io.Reader, n int) error { + switch r := br.(type) { + case *bytes.Buffer: + buf := r.Next(n) + if len(buf) < n { + return io.ErrUnexpectedEOF + } + return nil + case *bytes.Reader: + if r.Len() < n { + _, _ = r.Seek(0, io.SeekEnd) + return io.ErrUnexpectedEOF + } + _, err := r.Seek(int64(n), io.SeekCurrent) + return err + case *bufio.Reader: + _, err := r.Discard(n) + return err + default: + _, err := io.CopyN(ioutil.Discard, br, int64(n)) + return err + } +} + +func ScanForLinks(br io.Reader, cb func(cid.Cid)) error { + scratch := make([]byte, maxCidLength) + for remaining := uint64(1); remaining > 0; remaining-- { + maj, extra, err := CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + switch maj { + case MajUnsignedInt, MajNegativeInt, MajOther: + case MajByteString, MajTextString: + err := discard(br, int(extra)) + if err != nil { + return err + } + case MajTag: + if extra == 42 { + maj, extra, err = CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if maj != MajByteString { + return fmt.Errorf("expected cbor type 'byte string' in input") + } + + if extra > maxCidLength { + return fmt.Errorf("string in cbor input too long") + } + + if _, err := io.ReadAtLeast(br, scratch[:extra], int(extra)); err != nil { + return err + } + + c, err := cid.Cast(scratch[1:extra]) + if err != nil { + return err + } + cb(c) + + } else { + remaining++ + } + case MajArray: + remaining += extra + case MajMap: + remaining += (extra * 2) + default: + return fmt.Errorf("unhandled cbor type: %d", maj) + } + } + return nil +} + +const ( + MajUnsignedInt = 0 + MajNegativeInt = 1 + MajByteString = 2 + MajTextString = 3 + MajArray = 4 + MajMap = 5 + MajTag = 6 + MajOther = 7 +) + +var maxLengthError = fmt.Errorf("length beyond maximum allowed") + +type CBORUnmarshaler interface { + UnmarshalCBOR(io.Reader) error +} + +type CBORMarshaler interface { + MarshalCBOR(io.Writer) error +} + +type Deferred struct { + Raw []byte +} + +func (d *Deferred) MarshalCBOR(w io.Writer) error { + if d == nil { + _, err := w.Write(CborNull) + return err + } + if d.Raw == nil { + return errors.New("cannot marshal Deferred with nil value for Raw (will not unmarshal)") + } + _, err := w.Write(d.Raw) + return err +} + +func (d *Deferred) UnmarshalCBOR(br io.Reader) error { + // Reuse any existing buffers. + reusedBuf := d.Raw[:0] + d.Raw = nil + buf := bytes.NewBuffer(reusedBuf) + + // Allocate some scratch space. + scratch := make([]byte, maxHeaderSize) + + // Algorithm: + // + // 1. We start off expecting to read one element. + // 2. If we see a tag, we expect to read one more element so we increment "remaining". + // 3. If see an array, we expect to read "extra" elements so we add "extra" to "remaining". + // 4. If see a map, we expect to read "2*extra" elements so we add "2*extra" to "remaining". + // 5. While "remaining" is non-zero, read more elements. + + // define this once so we don't keep allocating it. + limitedReader := io.LimitedReader{R: br} + for remaining := uint64(1); remaining > 0; remaining-- { + maj, extra, err := CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if err := WriteMajorTypeHeaderBuf(scratch, buf, maj, extra); err != nil { + return err + } + + switch maj { + case MajUnsignedInt, MajNegativeInt, MajOther: + // nothing fancy to do + case MajByteString, MajTextString: + if extra > ByteArrayMaxLen { + return maxLengthError + } + // Copy the bytes + limitedReader.N = int64(extra) + buf.Grow(int(extra)) + if n, err := buf.ReadFrom(&limitedReader); err != nil { + return err + } else if n < int64(extra) { + return io.ErrUnexpectedEOF + } + case MajTag: + remaining++ + case MajArray: + if extra > MaxLength { + return maxLengthError + } + remaining += extra + case MajMap: + if extra > MaxLength { + return maxLengthError + } + remaining += extra * 2 + default: + return fmt.Errorf("unhandled deferred cbor type: %d", maj) + } + } + d.Raw = buf.Bytes() + return nil +} + +func readByte(r io.Reader) (byte, error) { + // try to cast to a concrete type, it's much faster than casting to an + // interface. + switch r := r.(type) { + case *bytes.Buffer: + return r.ReadByte() + case *bytes.Reader: + return r.ReadByte() + case *bufio.Reader: + return r.ReadByte() + case *peeker: + return r.ReadByte() + case io.ByteReader: + return r.ReadByte() + } + var buf [1]byte + _, err := io.ReadFull(r, buf[:1]) + return buf[0], err +} + +func CborReadHeader(br io.Reader) (byte, uint64, error) { + first, err := readByte(br) + if err != nil { + return 0, 0, err + } + + maj := (first & 0xe0) >> 5 + low := first & 0x1f + + switch { + case low < 24: + return maj, uint64(low), nil + case low == 24: + next, err := readByte(br) + if err != nil { + return 0, 0, err + } + if next < 24 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 24 with value < 24)") + } + return maj, uint64(next), nil + case low == 25: + scratch := make([]byte, 2) + if _, err := io.ReadAtLeast(br, scratch[:2], 2); err != nil { + return 0, 0, err + } + val := uint64(binary.BigEndian.Uint16(scratch[:2])) + if val <= math.MaxUint8 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 25 with value <= MaxUint8)") + } + return maj, val, nil + case low == 26: + scratch := make([]byte, 4) + if _, err := io.ReadAtLeast(br, scratch[:4], 4); err != nil { + return 0, 0, err + } + val := uint64(binary.BigEndian.Uint32(scratch[:4])) + if val <= math.MaxUint16 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 26 with value <= MaxUint16)") + } + return maj, val, nil + case low == 27: + scratch := make([]byte, 8) + if _, err := io.ReadAtLeast(br, scratch, 8); err != nil { + return 0, 0, err + } + val := binary.BigEndian.Uint64(scratch) + if val <= math.MaxUint32 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 27 with value <= MaxUint32)") + } + return maj, val, nil + default: + return 0, 0, fmt.Errorf("invalid header: (%x)", first) + } +} + +func readByteBuf(r io.Reader, scratch []byte) (byte, error) { + // Reading a single byte from these buffers is much faster than copying + // into a slice. + switch r := r.(type) { + case *bytes.Buffer: + return r.ReadByte() + case *bytes.Reader: + return r.ReadByte() + case *bufio.Reader: + return r.ReadByte() + } + n, err := r.Read(scratch[:1]) + if err != nil { + return 0, err + } + if n != 1 { + return 0, fmt.Errorf("failed to read a byte") + } + return scratch[0], err +} + +// same as the above, just tries to allocate less by using a passed in scratch buffer +func CborReadHeaderBuf(br io.Reader, scratch []byte) (byte, uint64, error) { + first, err := readByteBuf(br, scratch) + if err != nil { + return 0, 0, err + } + + maj := (first & 0xe0) >> 5 + low := first & 0x1f + + switch { + case low < 24: + return maj, uint64(low), nil + case low == 24: + next, err := readByteBuf(br, scratch) + if err != nil { + return 0, 0, err + } + if next < 24 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 24 with value < 24)") + } + return maj, uint64(next), nil + case low == 25: + if _, err := io.ReadAtLeast(br, scratch[:2], 2); err != nil { + return 0, 0, err + } + val := uint64(binary.BigEndian.Uint16(scratch[:2])) + if val <= math.MaxUint8 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 25 with value <= MaxUint8)") + } + return maj, val, nil + case low == 26: + if _, err := io.ReadAtLeast(br, scratch[:4], 4); err != nil { + return 0, 0, err + } + val := uint64(binary.BigEndian.Uint32(scratch[:4])) + if val <= math.MaxUint16 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 26 with value <= MaxUint16)") + } + return maj, val, nil + case low == 27: + if _, err := io.ReadAtLeast(br, scratch[:8], 8); err != nil { + return 0, 0, err + } + val := binary.BigEndian.Uint64(scratch[:8]) + if val <= math.MaxUint32 { + return 0, 0, fmt.Errorf("cbor input was not canonical (lval 27 with value <= MaxUint32)") + } + return maj, val, nil + default: + return 0, 0, fmt.Errorf("invalid header: (%x)", first) + } +} + +func CborWriteHeader(w io.Writer, t byte, l uint64) error { + return WriteMajorTypeHeader(w, t, l) +} + +// TODO: No matter what I do, this function *still* allocates. Its super frustrating. +// See issue: https://github.com/golang/go/issues/33160 +func WriteMajorTypeHeader(w io.Writer, t byte, l uint64) error { + switch { + case l < 24: + _, err := w.Write([]byte{(t << 5) | byte(l)}) + return err + case l < (1 << 8): + _, err := w.Write([]byte{(t << 5) | 24, byte(l)}) + return err + case l < (1 << 16): + var b [3]byte + b[0] = (t << 5) | 25 + binary.BigEndian.PutUint16(b[1:3], uint16(l)) + _, err := w.Write(b[:]) + return err + case l < (1 << 32): + var b [5]byte + b[0] = (t << 5) | 26 + binary.BigEndian.PutUint32(b[1:5], uint32(l)) + _, err := w.Write(b[:]) + return err + default: + var b [9]byte + b[0] = (t << 5) | 27 + binary.BigEndian.PutUint64(b[1:], uint64(l)) + _, err := w.Write(b[:]) + return err + } +} + +// Same as the above, but uses a passed in buffer to avoid allocations +func WriteMajorTypeHeaderBuf(buf []byte, w io.Writer, t byte, l uint64) error { + switch { + case l < 24: + buf[0] = (t << 5) | byte(l) + _, err := w.Write(buf[:1]) + return err + case l < (1 << 8): + buf[0] = (t << 5) | 24 + buf[1] = byte(l) + _, err := w.Write(buf[:2]) + return err + case l < (1 << 16): + buf[0] = (t << 5) | 25 + binary.BigEndian.PutUint16(buf[1:3], uint16(l)) + _, err := w.Write(buf[:3]) + return err + case l < (1 << 32): + buf[0] = (t << 5) | 26 + binary.BigEndian.PutUint32(buf[1:5], uint32(l)) + _, err := w.Write(buf[:5]) + return err + default: + buf[0] = (t << 5) | 27 + binary.BigEndian.PutUint64(buf[1:9], uint64(l)) + _, err := w.Write(buf[:9]) + return err + } +} + +func CborEncodeMajorType(t byte, l uint64) []byte { + switch { + case l < 24: + var b [1]byte + b[0] = (t << 5) | byte(l) + return b[:1] + case l < (1 << 8): + var b [2]byte + b[0] = (t << 5) | 24 + b[1] = byte(l) + return b[:2] + case l < (1 << 16): + var b [3]byte + b[0] = (t << 5) | 25 + binary.BigEndian.PutUint16(b[1:3], uint16(l)) + return b[:3] + case l < (1 << 32): + var b [5]byte + b[0] = (t << 5) | 26 + binary.BigEndian.PutUint32(b[1:5], uint32(l)) + return b[:5] + default: + var b [9]byte + b[0] = (t << 5) | 27 + binary.BigEndian.PutUint64(b[1:], uint64(l)) + return b[:] + } +} + +func ReadTaggedByteArray(br io.Reader, exptag uint64, maxlen uint64) ([]byte, error) { + maj, extra, err := CborReadHeader(br) + if err != nil { + return nil, err + } + + if maj != MajTag { + return nil, fmt.Errorf("expected cbor type 'tag' in input") + } + + if extra != exptag { + return nil, fmt.Errorf("expected tag %d", exptag) + } + + return ReadByteArray(br, maxlen) +} + +func ReadByteArray(br io.Reader, maxlen uint64) ([]byte, error) { + maj, extra, err := CborReadHeader(br) + if err != nil { + return nil, err + } + + if maj != MajByteString { + return nil, fmt.Errorf("expected cbor type 'byte string' in input") + } + + if extra > maxlen { + return nil, fmt.Errorf("string in cbor input too long, maxlen: %d", maxlen) + } + + buf := make([]byte, extra) + if _, err := io.ReadAtLeast(br, buf, int(extra)); err != nil { + return nil, err + } + + return buf, nil +} + +var ( + CborBoolFalse = []byte{0xf4} + CborBoolTrue = []byte{0xf5} + CborNull = []byte{0xf6} +) + +func EncodeBool(b bool) []byte { + if b { + return CborBoolTrue + } + return CborBoolFalse +} + +func WriteBool(w io.Writer, b bool) error { + _, err := w.Write(EncodeBool(b)) + return err +} + +func ReadString(r io.Reader) (string, error) { + maj, l, err := CborReadHeader(r) + if err != nil { + return "", err + } + + if maj != MajTextString { + return "", fmt.Errorf("got tag %d while reading string value (l = %d)", maj, l) + } + + if l > MaxLength { + return "", fmt.Errorf("string in input was too long") + } + + buf := make([]byte, l) + _, err = io.ReadAtLeast(r, buf, int(l)) + if err != nil { + return "", err + } + + return string(buf), nil +} + +func ReadStringBuf(r io.Reader, scratch []byte) (string, error) { + maj, l, err := CborReadHeaderBuf(r, scratch) + if err != nil { + return "", err + } + + if maj != MajTextString { + return "", fmt.Errorf("got tag %d while reading string value (l = %d)", maj, l) + } + + if l > MaxLength { + return "", fmt.Errorf("string in input was too long") + } + + buf := make([]byte, l) + _, err = io.ReadAtLeast(r, buf, int(l)) + if err != nil { + return "", err + } + + return string(buf), nil +} + +func ReadCid(br io.Reader) (cid.Cid, error) { + buf, err := ReadTaggedByteArray(br, 42, 512) + if err != nil { + return cid.Undef, err + } + + return bufToCid(buf) +} + +func bufToCid(buf []byte) (cid.Cid, error) { + + if len(buf) == 0 { + return cid.Undef, fmt.Errorf("undefined cid") + } + + if len(buf) < 2 { + return cid.Undef, fmt.Errorf("cbor serialized CIDs must have at least two bytes") + } + + if buf[0] != 0 { + return cid.Undef, fmt.Errorf("cbor serialized CIDs must have binary multibase") + } + + return cid.Cast(buf[1:]) +} + +var byteArrZero = []byte{0} + +func WriteCid(w io.Writer, c cid.Cid) error { + if err := WriteMajorTypeHeader(w, MajTag, 42); err != nil { + return err + } + if c == cid.Undef { + return fmt.Errorf("undefined cid") + //return CborWriteHeader(w, MajByteString, 0) + } + + if err := WriteMajorTypeHeader(w, MajByteString, uint64(c.ByteLen()+1)); err != nil { + return err + } + + // that binary multibase prefix... + if _, err := w.Write(byteArrZero); err != nil { + return err + } + + if _, err := c.WriteBytes(w); err != nil { + return err + } + + return nil +} + +func WriteCidBuf(buf []byte, w io.Writer, c cid.Cid) error { + if err := WriteMajorTypeHeaderBuf(buf, w, MajTag, 42); err != nil { + return err + } + if c == cid.Undef { + return fmt.Errorf("undefined cid") + //return CborWriteHeader(w, MajByteString, 0) + } + + if err := WriteMajorTypeHeaderBuf(buf, w, MajByteString, uint64(c.ByteLen()+1)); err != nil { + return err + } + + // that binary multibase prefix... + if _, err := w.Write(byteArrZero); err != nil { + return err + } + + if _, err := c.WriteBytes(w); err != nil { + return err + } + + return nil +} + +type CborBool bool + +func (cb CborBool) MarshalCBOR(w io.Writer) error { + return WriteBool(w, bool(cb)) +} + +func (cb *CborBool) UnmarshalCBOR(r io.Reader) error { + t, val, err := CborReadHeader(r) + if err != nil { + return err + } + + if t != MajOther { + return fmt.Errorf("booleans should be major type 7") + } + + switch val { + case 20: + *cb = false + case 21: + *cb = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", val) + } + return nil +} + +type CborInt int64 + +func (ci CborInt) MarshalCBOR(w io.Writer) error { + v := int64(ci) + if v >= 0 { + if err := WriteMajorTypeHeader(w, MajUnsignedInt, uint64(v)); err != nil { + return err + } + } else { + if err := WriteMajorTypeHeader(w, MajNegativeInt, uint64(-v)-1); err != nil { + return err + } + } + return nil +} + +func (ci *CborInt) UnmarshalCBOR(r io.Reader) error { + maj, extra, err := CborReadHeader(r) + if err != nil { + return err + } + var extraI int64 + switch maj { + case MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + *ci = CborInt(extraI) + return nil +} + +type CborTime time.Time + +func (ct CborTime) MarshalCBOR(w io.Writer) error { + nsecs := ct.Time().UnixNano() + + cbi := CborInt(nsecs) + + return cbi.MarshalCBOR(w) +} + +func (ct *CborTime) UnmarshalCBOR(r io.Reader) error { + var cbi CborInt + if err := cbi.UnmarshalCBOR(r); err != nil { + return err + } + + t := time.Unix(0, int64(cbi)) + + *ct = (CborTime)(t) + return nil +} + +func (ct CborTime) Time() time.Time { + return (time.Time)(ct) +} + +func (ct CborTime) MarshalJSON() ([]byte, error) { + return ct.Time().MarshalJSON() +} + +func (ct *CborTime) UnmarshalJSON(b []byte) error { + var t time.Time + if err := t.UnmarshalJSON(b); err != nil { + return err + } + *(*time.Time)(ct) = t + return nil +} diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f220cfd194e68d9045cdefe01dc37a48760f73c1 --- /dev/null +++ b/utils_test.go @@ -0,0 +1,38 @@ +package typegen + +import ( + "bytes" + "encoding/hex" + "testing" + + cid "github.com/ipfs/go-cid" +) + +func TestLinkScan(t *testing.T) { + inp := "82442847c0498ba16131818242000484d82a57000155001266696c2f312f73746f72616765706f776572d82a5827000171a0e40220740d4196aaaee66d8e9b828bc6f9271662096e36782de248e3b3ed28443dbc810040a16131828242000584d82a5818000155001366696c2f312f73746f726167656d61726b6574d82a5827000171a0e402209e59ceb041921650967e8c77d36269c10049140d28d5015165cc8eb897a2555300408242006684d82a52000155000d66696c2f312f6163636f756e74d82a5827000171a0e40220f9556f0d5a735ff53cc327e85a46c7c094028b9da894de73caa88f162429c29d004b000a968163f0a57b400000a16131818242000084d82a51000155000c66696c2f312f73797374656dd82a5827000171a0e4022045b0cfc220ceec5b7c1c62c4d4193d38e4eba48e8815729ce75f9c0ab0e4c1c00040a16131818242006384d82a52000155000d66696c2f312f6163636f756e74d82a5827000171a0e4022045b0cfc220ceec5b7c1c62c4d4193d38e4eba48e8815729ce75f9c0ab0e4c1c00040a16131818242000184d82a4f000155000a66696c2f312f696e6974d82a5827000171a0e4022050f3c45d0e78f04688c6e8cbdc45f71a5cbcec731519ffdcdd92765fc5ba0da30040a16131818242000684d82a581b000155001666696c2f312f76657269666965647265676973747279d82a5827000171a0e40220fd3fee39acd88c8808110d9741149a79939f52c798b6539048e4835aa4d34fd50040a16131818242006584d82a52000155000d66696c2f312f6163636f756e74d82a5827000171a0e402200293716d8503737644624c102d9ba1514d599044a2e6f2038330124bc6f54361004c002116545850052128000000a16131818242000384d82a4f000155000a66696c2f312f63726f6ed82a5827000171a0e4022065d1dad76492ccd5d010197dc26bd5fb07c0cf85ccdcaff21084c0d47bfd17590040a16131818242005084d82a52000155000d66696c2f312f6163636f756e74d82a5827000171a0e402202e12f4eedaac06c2040df4923656f7f2d6c5991b1874a32e9f52b0e48e61d8410040a16131818242006484d82a52000155000d66696c2f312f6163636f756e74d82a5827000171a0e40220bde06af1782cb302e0973658dcb44b2cbd72891598b7a7b02381b563f1dc9c57004c002116545850052128000000a16131818242000284d82a51000155000c66696c2f312f726577617264d82a5827000171a0e4022083c127ddb0ba85f585b06365346eabe5ef98861d85770670dee402dc3810131e004d0004860d8812f0b38878000000" + inpb, err := hex.DecodeString(inp) + if err != nil { + t.Fatal(err) + } + + var cids []cid.Cid + if err := ScanForLinks(bytes.NewReader(inpb), func(c cid.Cid) { + cids = append(cids, c) + }); err != nil { + t.Fatal(err) + } + t.Log(cids) +} + +func TestDeferredMaxLengthSingle(t *testing.T) { + var header bytes.Buffer + if err := WriteMajorTypeHeader(&header, MajByteString, ByteArrayMaxLen+1); err != nil { + t.Fatal("failed to write header") + } + + var deferred Deferred + err := deferred.UnmarshalCBOR(&header) + if err != maxLengthError { + t.Fatal("deferred: allowed more than the maximum allocation supported") + } +} diff --git a/validate.go b/validate.go new file mode 100644 index 0000000000000000000000000000000000000000..04a5e26e6c8882fd1c0d7c1efddcd696156b59e5 --- /dev/null +++ b/validate.go @@ -0,0 +1,59 @@ +package typegen + +import ( + "bytes" + "fmt" + "io" +) + +// ValidateCBOR validates that a byte array is a single valid CBOR object. +func ValidateCBOR(b []byte) error { + // The code here is basically identical to the previous function, it + // just doesn't copy. + + br := bytes.NewReader(b) + + // Allocate some scratch space. + scratch := make([]byte, maxHeaderSize) + + for remaining := uint64(1); remaining > 0; remaining-- { + maj, extra, err := CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + switch maj { + case MajUnsignedInt, MajNegativeInt, MajOther: + // nothing fancy to do + case MajByteString, MajTextString: + if extra > ByteArrayMaxLen { + return maxLengthError + } + if uint64(br.Len()) < extra { + return io.ErrUnexpectedEOF + } + + if _, err := br.Seek(int64(extra), io.SeekCurrent); err != nil { + return err + } + case MajTag: + remaining++ + case MajArray: + if extra > MaxLength { + return maxLengthError + } + remaining += extra + case MajMap: + if extra > MaxLength { + return maxLengthError + } + remaining += extra * 2 + default: + return fmt.Errorf("unhandled deferred cbor type: %d", maj) + } + } + if br.Len() > 0 { + return fmt.Errorf("unexpected %d unread bytes", br.Len()) + } + return nil +} diff --git a/validate_test.go b/validate_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6bf0f9e3eb0f758b7b64a3e245eca7e13205f722 --- /dev/null +++ b/validate_test.go @@ -0,0 +1,42 @@ +package typegen + +import ( + "bytes" + "testing" +) + +func TestValidateShort(t *testing.T) { + var buf bytes.Buffer + if err := WriteMajorTypeHeader(&buf, MajByteString, 100); err != nil { + t.Fatal("failed to write header") + } + + if err := ValidateCBOR(buf.Bytes()); err == nil { + t.Fatal("expected an error checking truncated cbor") + } +} + +func TestValidateDouble(t *testing.T) { + var buf bytes.Buffer + if err := WriteBool(&buf, false); err != nil { + t.Fatal(err) + } + if err := WriteBool(&buf, false); err != nil { + t.Fatal(err) + } + + if err := ValidateCBOR(buf.Bytes()); err == nil { + t.Fatal("expected an error checking cbor with two objects") + } +} + +func TestValidate(t *testing.T) { + var buf bytes.Buffer + if err := WriteBool(&buf, false); err != nil { + t.Fatal(err) + } + + if err := ValidateCBOR(buf.Bytes()); err != nil { + t.Fatal(err) + } +} diff --git a/writefile.go b/writefile.go new file mode 100644 index 0000000000000000000000000000000000000000..1b369dd4b5a05c3c7b782f182b0c344da9512f50 --- /dev/null +++ b/writefile.go @@ -0,0 +1,103 @@ +package typegen + +import ( + "bytes" + "go/format" + "os" + + "golang.org/x/xerrors" +) + +// WriteTupleFileEncodersToFile generates array backed MarshalCBOR and UnmarshalCBOR implementations for the +// given types in the specified file, with the specified package name. +// +// The MarshalCBOR and UnmarshalCBOR implementations will marshal/unmarshal each type's fields as a +// fixed-length CBOR array of field values. +func WriteTupleEncodersToFile(fname, pkg string, types ...interface{}) error { + buf := new(bytes.Buffer) + + typeInfos := make([]*GenTypeInfo, len(types)) + for i, t := range types { + gti, err := ParseTypeInfo(t) + if err != nil { + return xerrors.Errorf("failed to parse type info: %w", err) + } + typeInfos[i] = gti + } + + if err := PrintHeaderAndUtilityMethods(buf, pkg, typeInfos); err != nil { + return xerrors.Errorf("failed to write header: %w", err) + } + + for _, t := range typeInfos { + if err := GenTupleEncodersForType(t, buf); err != nil { + return xerrors.Errorf("failed to generate encoders: %w", err) + } + } + + data, err := format.Source(buf.Bytes()) + if err != nil { + return err + } + + fi, err := os.Create(fname) + if err != nil { + return xerrors.Errorf("failed to open file: %w", err) + } + + _, err = fi.Write(data) + if err != nil { + _ = fi.Close() + return err + } + _ = fi.Close() + + return nil +} + +// WriteMapFileEncodersToFile generates map backed MarshalCBOR and UnmarshalCBOR implementations for +// the given types in the specified file, with the specified package name. +// +// The MarshalCBOR and UnmarshalCBOR implementations will marshal/unmarshal each type's fields as a +// map of field names to field values. +func WriteMapEncodersToFile(fname, pkg string, types ...interface{}) error { + buf := new(bytes.Buffer) + + typeInfos := make([]*GenTypeInfo, len(types)) + for i, t := range types { + gti, err := ParseTypeInfo(t) + if err != nil { + return xerrors.Errorf("failed to parse type info: %w", err) + } + typeInfos[i] = gti + } + + if err := PrintHeaderAndUtilityMethods(buf, pkg, typeInfos); err != nil { + return xerrors.Errorf("failed to write header: %w", err) + } + + for _, t := range typeInfos { + if err := GenMapEncodersForType(t, buf); err != nil { + return xerrors.Errorf("failed to generate encoders: %w", err) + } + } + + data, err := format.Source(buf.Bytes()) + if err != nil { + return err + } + + fi, err := os.Create(fname) + if err != nil { + return xerrors.Errorf("failed to open file: %w", err) + } + + _, err = fi.Write(data) + if err != nil { + _ = fi.Close() + return err + } + _ = fi.Close() + + return nil +}