Repository: fxamacker/cbor
Pull request #32 (2019-11-15 18:46:02)

Update README.md
Update summary.  Add TOC that only takes up one line on desktop screens.  Mention new `toarray` struct tag feature. Mention CBOR tags (type 6) is under consideration as new feature.


Patch:
diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index f1d5c0f..9af3795 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -13,9 +13,9 @@ Benchmarks use data representing the following values:
 * Array: `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]`
 * Map: `{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}}`
 
-Go structs are faster than maps:
+Benchmarks data show:
 * decoding into struct is >66% faster than decoding into map.
-* encoding struct is >67% faster than encoding map.
+* encoding struct is >63% faster than encoding map.
 
 Decoding Benchmark | Time | Memory | Allocs 
 --- | ---: | ---: | ---:
@@ -40,12 +40,12 @@ BenchmarkUnmarshal/CBOR_map_to_Go_struct-2 | 1324 ns/op| 224 B/op | 2 allocs/op
 
 Encoding Benchmark | Time | Memory | Allocs 
 --- | ---: | ---: | ---:
-BenchmarkMarshal/Go_bool_to_CBOR_bool-2 | 88.5 ns/op	| 1 B/op | 1 allocs/op
-BenchmarkMarshal/Go_uint64_to_CBOR_positive_int-2 | 99.2 ns/op | 16 B/op | 1 allocs/op
-BenchmarkMarshal/Go_int64_to_CBOR_negative_int-2 | 92.7 ns/op | 3 B/op | 1 allocs/op
-BenchmarkMarshal/Go_float64_to_CBOR_float-2 | 97.2 ns/op	| 16 B/op | 1 allocs/op
-BenchmarkMarshal/Go_[]uint8_to_CBOR_bytes-2 | 121 ns/op | 32 B/op	| 1 allocs/op
+BenchmarkMarshal/Go_bool_to_CBOR_bool-2 | 93.8 ns/op	| 1 B/op | 1 allocs/op
+BenchmarkMarshal/Go_uint64_to_CBOR_positive_int-2 | 103 ns/op | 16 B/op | 1 allocs/op
+BenchmarkMarshal/Go_int64_to_CBOR_negative_int-2 | 96.9 ns/op | 3 B/op | 1 allocs/op
+BenchmarkMarshal/Go_float64_to_CBOR_float-2 | 101 ns/op	| 16 B/op | 1 allocs/op
+BenchmarkMarshal/Go_[]uint8_to_CBOR_bytes-2 | 140 ns/op | 32 B/op	| 1 allocs/op
 BenchmarkMarshal/Go_string_to_CBOR_text-2 | 121 ns/op | 48 B/op | 1 allocs/op
-BenchmarkMarshal/Go_[]int_to_CBOR_array-2 | 480 ns/op | 32 B/op	| 1 allocs/op
-BenchmarkMarshal/Go_map[string]string_to_CBOR_map-2 | 2194 ns/op | 576 B/op | 28 allocs/op
-BenchmarkMarshal/Go_struct_to_CBOR_map-2 | 714 ns/op | 64 B/op | 1 allocs/op
+BenchmarkMarshal/Go_[]int_to_CBOR_array-2 | 599 ns/op | 32 B/op	| 1 allocs/op
+BenchmarkMarshal/Go_map[string]string_to_CBOR_map-2 | 2211 ns/op | 576 B/op | 28 allocs/op
+BenchmarkMarshal/Go_struct_to_CBOR_map-2 | 809 ns/op | 64 B/op | 1 allocs/op
diff --git a/README.md b/README.md
index 0fc3d9c..58bdc79 100644
--- a/README.md
+++ b/README.md
@@ -22,16 +22,22 @@
 # CBOR library in Go
 This library encodes and decodes CBOR ([RFC 7049](https://tools.ietf.org/html/rfc7049)).  CBOR is a concise binary alternative to JSON.
 
-:thumbsup: If you know Go's `encoding/json`, you already know how to use this library.  Existing structs don't require changes.  Go's struct tags like `` `cbor:"name,omitempty"` `` and `` `json:"name,omitempty"` `` both work as expected.
+:hourglass_flowing_sand: This library saves time. It has idiomatic API like Go's `encoding/json`.  Existing structs don't require changes.  Go's struct tags like `` `cbor:"name,omitempty"` `` and `` `json:"name,omitempty"` `` work as expected.
 
 :atom: Your programs won't bloat.  This library compiles to under 0.5 MB, has no external dependencies, and no code gen.
 
 :lock: It avoids crashes on malicious CBOR data by using extensive tests, coverage-guided fuzzing, and data validation.
 
-:rocket: Starting in v1.3, faster speed became a high priority.  Faster libraries will always exist.  However, choose this one if you value your time, program size, and system reliability.
+:rocket: Starting in v1.3, faster speed became a high priority.  Faster libraries will always exist, but speed is only one factor.  Choose this library if you value your time, program size, and system reliability.
 
 Install with ```go get github.com/fxamacker/cbor``` and use it like Go's ```encoding/json```.
 
+<div align="center">
+
+:small_orange_diamond: [Design Goals](#design-goals) :small_orange_diamond: [Features](#features) :small_orange_diamond: [Standards](#standards) :small_orange_diamond: [Fuzzing and Coverage](#fuzzing-and-code-coverage) :small_orange_diamond: [API](#api) :small_orange_diamond: [Security Policy](#security-policy) :small_orange_diamond: [License](#license) :small_orange_diamond:
+
+</div>
+
 ## Current Status
 Version 1.x has:
 * __Stable API__ – won't make breaking API changes.  
@@ -40,7 +46,7 @@ Version 1.x has:
 
 [Release v1.2](https://github.com/fxamacker/cbor/releases) (Nov 05, 2019) adds RawMessage type, Marshaler and Unmarshaler interfaces.  Passed 42+ hrs of fuzzing.
 
-[Milestone v1.3](https://github.com/fxamacker/cbor/milestone/2) has faster speed :rocket:, uses less memory, and simplifies decoding of COSE data (RFC 8152).
+[Milestone v1.3](https://github.com/fxamacker/cbor/milestone/2) has faster speed :rocket:, uses less memory, adds `toarray` struct tag for more compact struct data, and simplifies decoding of COSE data (RFC 8152).
 
 ## Design Goals 
 This CBOR library was created for my [WebAuthn (FIDO2) server library](https://github.com/fxamacker/webauthn), because existing CBOR libraries didn't meet certain criteria.  This library became a good fit for many other projects.
@@ -100,6 +106,8 @@ It also supports [canonical CBOR encodings](https://tools.ietf.org/html/rfc7049#
 * CBOR negative int (type 1) that cannot fit into Go's int64 are not supported, such as RFC 7049 example -18446744073709551616.  Decoding these values returns `cbor.UnmarshalTypeError` like Go's `encoding/json`.
 * CBOR `Undefined` (0xf7) value decodes to Go's `nil` value.  Use CBOR `Null` (0xf6) to round-trip with Go's `nil`.
 
+:mega: CBOR tags (type 6) is being considered for a future release. Please let me know if this feature is important to you.
+
 ## System Requirements
 * Go 1.12 (or newer)
 * Tested and fuzzed on linux_amd64, but it should work on other platforms.
@@ -219,7 +227,7 @@ err = enc.EndIndefinite()
 ## Benchmarks
 In this library, Go structs are faster than maps:
 * decoding into struct is >66% faster than decoding into map.
-* encoding struct is >67% faster than encoding map.
+* encoding struct is >63% faster than encoding map.
 
 See [Benchmarks for fxamacker/cbor](BENCHMARKS.md).
 
@@ -235,7 +243,7 @@ For v1, security fixes are provided only for the latest released version since t
 To report security vulnerabilities, please email [faye.github@gmail.com](mailto:faye.github@gmail.com) and allow time for the problem to be resolved before reporting it to the public.
 
 ## Disclaimers
-Phrases like "no crashes" and "no exploits" mean there are none known to the maintainer based on results of unit tests and coverage-based fuzzing.  It doesn't imply the software is 100% bug-free or 100% invulnerable to all known and unknown attacks.
+Phrases like "no crashes" mean there are none known to the maintainer based on results of unit tests and coverage-based fuzzing.  It doesn't imply the software is 100% bug-free or 100% invulnerable to all known and unknown attacks.
 
 Please read the license for additional disclaimers and terms.
 
diff --git a/cache.go b/cache.go
deleted file mode 100644
index 00832fb..0000000
--- a/cache.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package cbor
-
-import (
-	"bytes"
-	"reflect"
-	"sort"
-	"strconv"
-	"strings"
-	"sync"
-)
-
-type decodingStructType struct {
-	fields  fields
-	err     error
-	toArray bool
-}
-
-type encodingStructType struct {
-	fields          fields
-	canonicalFields fields
-	err             error
-	toArray         bool
-}
-
-// byCanonicalRule sorts fields by field name length and field name.
-type byCanonicalRule struct {
-	fields
-}
-
-func (s byCanonicalRule) Less(i, j int) bool {
-	return bytes.Compare(s.fields[i].cborName, s.fields[j].cborName) <= 0
-}
-
-var (
-	decodingStructTypeCache sync.Map // map[reflect.Type]decodingStructType
-	encodingStructTypeCache sync.Map // map[reflect.Type]encodingStructType
-	encodingTypeCache       sync.Map // map[reflect.Type]encodeFunc
-)
-
-func structToArray(tag string) bool {
-	s := ",toarray"
-	idx := strings.Index(tag, s)
-	return idx >= 0 && (len(tag) == idx+len(s) || tag[idx+len(s)] == ',')
-}
-
-func getDecodingStructType(t reflect.Type) decodingStructType {
-	if v, _ := decodingStructTypeCache.Load(t); v != nil {
-		return v.(decodingStructType)
-	}
-
-	flds, structOptions := getFields(t)
-
-	toArray := structToArray(structOptions)
-
-	for i := 0; i < len(flds); i++ {
-		flds[i].isUnmarshaler = implementsUnmarshaler(flds[i].typ)
-	}
-
-	structType := decodingStructType{fields: flds, toArray: toArray}
-	decodingStructTypeCache.Store(t, structType)
-	return structType
-}
-
-func getEncodingStructType(t reflect.Type) encodingStructType {
-	if v, _ := encodingStructTypeCache.Load(t); v != nil {
-		return v.(encodingStructType)
-	}
-
-	flds, structOptions := getFields(t)
-
-	toArray := structToArray(structOptions)
-
-	var err error
-	es := getEncodeState()
-	for i := 0; i < len(flds); i++ {
-		// Get field's encodeFunc
-		flds[i].ef = getEncodeFunc(flds[i].typ)
-		if flds[i].ef == nil {
-			err = &UnsupportedTypeError{t}
-			break
-		}
-
-		// Encode field name
-		if flds[i].keyasint {
-			nameAsInt, numErr := strconv.Atoi(flds[i].name)
-			if numErr != nil {
-				err = numErr
-				break
-			}
-			if nameAsInt >= 0 {
-				encodeTypeAndAdditionalValue(es, byte(cborTypePositiveInt), uint64(nameAsInt))
-			} else {
-				n := nameAsInt*(-1) - 1
-				encodeTypeAndAdditionalValue(es, byte(cborTypeNegativeInt), uint64(n))
-			}
-			flds[i].cborName = make([]byte, es.Len())
-			copy(flds[i].cborName, es.Bytes())
-			es.Reset()
-		} else {
-			encodeTypeAndAdditionalValue(es, byte(cborTypeTextString), uint64(len(flds[i].name)))
-			flds[i].cborName = make([]byte, es.Len()+len(flds[i].name))
-			n := copy(flds[i].cborName, es.Bytes())
-			copy(flds[i].cborName[n:], flds[i].name)
-			es.Reset()
-		}
-	}
-	putEncodeState(es)
-
-	if err != nil {
-		structType := encodingStructType{err: err}
-		encodingStructTypeCache.Store(t, structType)
-		return structType
-	}
-
-	// Sort fields by canonical order
-	canonicalFields := make(fields, len(flds))
-	copy(canonicalFields, flds)
-	sort.Sort(byCanonicalRule{canonicalFields})
-
-	structType := encodingStructType{fields: flds, canonicalFields: canonicalFields, toArray: toArray}
-	encodingStructTypeCache.Store(t, structType)
-	return structType
-}
-
-func getEncodeFunc(t reflect.Type) encodeFunc {
-	if v, _ := encodingTypeCache.Load(t); v != nil {
-		return v.(encodeFunc)
-	}
-	f := getEncodeFuncInternal(t)
-	encodingTypeCache.Store(t, f)
-	return f
-}
diff --git a/decode.go b/decode.go
index 50a61e7..34e1830 100644
--- a/decode.go
+++ b/decode.go
@@ -279,8 +279,6 @@ func (d *decodeState) parse(v reflect.Value, isUnmarshaler bool) (err error) {
 			return d.parseSlice(t, count, v)
 		} else if v.Kind() == reflect.Array {
 			return d.parseArray(t, count, v)
-		} else if v.Kind() == reflect.Struct {
-			return d.parseStructFromArray(t, count, v)
 		}
 		hasSize := count >= 0
 		for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
@@ -294,7 +292,7 @@ func (d *decodeState) parse(v reflect.Value, isUnmarshaler bool) (err error) {
 			count = -1
 		}
 		if v.Kind() == reflect.Struct {
-			return d.parseStructFromMap(t, count, v)
+			return d.parseStruct(t, count, v)
 		} else if v.Kind() == reflect.Map {
 			return d.parseMap(t, count, v)
 		}
@@ -579,74 +577,14 @@ func (d *decodeState) parseMap(t cborType, count int, v reflect.Value) error {
 	return nil
 }
 
-func (d *decodeState) parseStructFromArray(t cborType, count int, v reflect.Value) error {
-	structType := getDecodingStructType(v.Type())
-
-	if !structType.toArray {
-		return &UnmarshalTypeError{Value: t.String(), Type: v.Type(), errMsg: "cannot decode CBOR array to struct without toarray option"}
-	}
+func (d *decodeState) parseStruct(t cborType, count int, v reflect.Value) error {
+	flds := getStructFields(v.Type())
+	foundFlds := make([]bool, len(flds))
 
 	hasSize := count >= 0
-	if count == -1 {
-		count = d.numOfItemsUntilBreak() // peek ahead to get array size to verify that array size matches number of fields
-	}
-	if count != len(structType.fields) {
-		return &UnmarshalTypeError{Value: t.String(), Type: v.Type(), errMsg: "cannot decode CBOR array to struct with different number of elements"}
-	}
 	for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
-		fv, err := fieldByIndex(v, structType.fields[i].idx)
-		if err != nil {
-			return err
-		}
-		if err := d.parse(fv, structType.fields[i].isUnmarshaler); err != nil {
-			if d.err == nil {
-				if typeError, ok := err.(*UnmarshalTypeError); ok {
-					typeError.Struct = v.Type().String()
-					typeError.Field = structType.fields[i].name
-					d.err = typeError
-				} else {
-					d.err = err
-				}
-			}
-		}
-	}
-	return d.err
-}
-
-func (d *decodeState) parseStructFromMap(t cborType, count int, v reflect.Value) error {
-	structType := getDecodingStructType(v.Type())
-	foundFlds := make([]bool, len(structType.fields))
-
-	hasSize := count >= 0
-	for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
-		var keyBytes []byte
-		var err error
 		t := cborType(d.data[d.offset] & 0xE0)
-		if t == cborTypeTextString {
-			keyBytes, err = d.parseTextString()
-			if err != nil {
-				if d.err == nil {
-					d.err = err
-				}
-				d.skip() // skip value
-				continue
-			}
-		} else if t == cborTypePositiveInt || t == cborTypeNegativeInt {
-			v, err := d.parseInterface()
-			if err != nil {
-				if d.err == nil {
-					d.err = err
-				}
-				d.skip() // skip value
-				continue
-			}
-			switch n := v.(type) {
-			case int64:
-				keyBytes = []byte(strconv.Itoa(int(n)))
-			case uint64:
-				keyBytes = []byte(strconv.Itoa(int(n)))
-			}
-		} else {
+		if t != cborTypeTextString {
 			if d.err == nil {
 				d.err = &UnmarshalTypeError{Value: t.String(), Type: reflect.TypeOf(""), errMsg: "map key is of type " + t.String() + " and cannot be used to match struct " + v.Type().String() + " field name"}
 			}
@@ -654,23 +592,31 @@ func (d *decodeState) parseStructFromMap(t cborType, count int, v reflect.Value)
 			d.skip() // skip value
 			continue
 		}
+		keyBytes, err := d.parseTextString()
+		if err != nil {
+			if d.err == nil {
+				d.err = err
+			}
+			d.skip() // skip value
+			continue
+		}
 		keyLen := len(keyBytes)
 
 		var f *field
-		for i := 0; i < len(structType.fields); i++ {
+		for i := 0; i < len(flds); i++ {
 			// Find field with exact match
-			if !foundFlds[i] && len(structType.fields[i].name) == keyLen && structType.fields[i].name == string(keyBytes) {
-				f = &structType.fields[i]
+			if !foundFlds[i] && len(flds[i].name) == keyLen && flds[i].name == string(keyBytes) {
+				f = &flds[i]
 				foundFlds[i] = true
 				break
 			}
 		}
 		if f == nil {
 			keyString := string(keyBytes)
-			for i := 0; i < len(structType.fields); i++ {
+			for i := 0; i < len(flds); i++ {
 				// Find field with case-insensitive match
-				if !foundFlds[i] && len(structType.fields[i].name) == keyLen && strings.EqualFold(structType.fields[i].name, keyString) {
-					f = &structType.fields[i]
+				if !foundFlds[i] && len(flds[i].name) == keyLen && strings.EqualFold(flds[i].name, keyString) {
+					f = &flds[i]
 					foundFlds[i] = true
 					break
 				}
diff --git a/encode.go b/encode.go
index d13b8d3..3743c1b 100644
--- a/encode.go
+++ b/encode.go
@@ -64,24 +64,22 @@ type EncOptions struct {
 // An encodeState encodes CBOR into a bytes.Buffer.
 type encodeState struct {
 	bytes.Buffer
-	scratch [16]byte
+	scratch [64]byte
 }
 
 // encodeStatePool caches unused encodeState objects for later reuse.
 var encodeStatePool = sync.Pool{
 	New: func() interface{} {
-		es := new(encodeState)
-		es.Grow(32) // TODO: make this configurable
-		return es
+		return new(encodeState)
 	},
 }
 
-func getEncodeState() *encodeState {
+func newEncodeState() *encodeState {
 	return encodeStatePool.Get().(*encodeState)
 }
 
-// putEncodeState returns e to encodeStatePool.
-func putEncodeState(e *encodeState) {
+// returnEncodeState returns e to encodeStatePool.
+func returnEncodeState(e *encodeState) {
 	e.Reset()
 	encodeStatePool.Put(e)
 }
@@ -177,19 +175,12 @@ func encodeFloat(e *encodeState, v reflect.Value, opts EncOptions) (int, error)
 }
 
 func encodeByteString(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	if v.Kind() == reflect.Slice && v.IsNil() {
-		return e.Write(cborNil)
-	}
-	len := v.Len()
-	if len == 0 {
-		return 1, e.WriteByte(byte(cborTypeByteString))
-	}
-	n1 := encodeTypeAndAdditionalValue(e, byte(cborTypeByteString), uint64(len))
+	n1 := encodeTypeAndAdditionalValue(e, byte(cborTypeByteString), uint64(v.Len()))
 	if v.Kind() == reflect.Array {
-		for i := 0; i < len; i++ {
+		for i := 0; i < v.Len(); i++ {
 			e.WriteByte(byte(v.Index(i).Uint()))
 		}
-		return n1 + len, nil
+		return n1 + v.Len(), nil
 	}
 	n2, _ := e.Write(v.Bytes())
 	return n1 + n2, nil
@@ -205,24 +196,27 @@ func encodeStringInternal(e *encodeState, s string, opts EncOptions) (int, error
 	return n1 + n2, nil
 }
 
-type arrayEncoder struct {
-	f encodeFunc
+func encodeSlice(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+	if v.IsNil() {
+		return e.Write(cborNil)
+	}
+	return encodeArray(e, v, opts)
 }
 
-func (ae arrayEncoder) encodeArray(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	if ae.f == nil {
-		return 0, &UnsupportedTypeError{v.Type()}
+func encodeArray(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+	if v.Type().Elem().Kind() == reflect.Uint8 {
+		return encodeByteString(e, v, opts)
 	}
-	if v.Kind() == reflect.Slice && v.IsNil() {
-		return e.Write(cborNil)
+	f := getEncodeFunc(v.Type().Elem())
+	if f == nil {
+		return 0, &UnsupportedTypeError{v.Type()}
 	}
-	len := v.Len()
-	if len == 0 {
-		return 1, e.WriteByte(byte(cborTypeArray))
+	if v.Len() == 0 {
+		return encodeTypeAndAdditionalValue(e, byte(cborTypeArray), uint64(0)), nil
 	}
-	n := encodeTypeAndAdditionalValue(e, byte(cborTypeArray), uint64(len))
-	for i := 0; i < len; i++ {
-		n1, err := ae.f(e, v.Index(i), opts)
+	n := encodeTypeAndAdditionalValue(e, byte(cborTypeArray), uint64(v.Len()))
+	for i := 0; i < v.Len(); i++ {
+		n1, err := f(e, v.Index(i), opts)
 		if err != nil {
 			return 0, err
 		}
@@ -231,32 +225,29 @@ func (ae arrayEncoder) encodeArray(e *encodeState, v reflect.Value, opts EncOpti
 	return n, nil
 }
 
-type mapEncoder struct {
-	kf, ef encodeFunc
-}
-
-func (me mapEncoder) encodeMap(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+func encodeMap(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
 	if opts.Canonical {
-		return me.encodeMapCanonical(e, v, opts)
+		return encodeMapCanonical(e, v, opts)
 	}
-	if me.kf == nil || me.ef == nil {
+	kf := getEncodeFunc(v.Type().Key())
+	ef := getEncodeFunc(v.Type().Elem())
+	if kf == nil || ef == nil {
 		return 0, &UnsupportedTypeError{v.Type()}
 	}
 	if v.IsNil() {
 		return e.Write(cborNil)
 	}
-	len := v.Len()
-	if len == 0 {
-		return 1, e.WriteByte(byte(cborTypeMap))
+	if v.Len() == 0 {
+		return encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(0)), nil
 	}
-	n := encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(len))
+	n := encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(v.Len()))
 	iter := v.MapRange()
 	for iter.Next() {
-		n1, err := me.kf(e, iter.Key(), opts)
+		n1, err := kf(e, iter.Key(), opts)
 		if err != nil {
 			return 0, err
 		}
-		n2, err := me.ef(e, iter.Value(), opts)
+		n2, err := ef(e, iter.Value(), opts)
 		if err != nil {
 			return 0, err
 		}
@@ -288,7 +279,7 @@ func (v byCanonical) Less(i, j int) bool {
 
 var byCanonicalPool = sync.Pool{}
 
-func getByCanonical(length int) *byCanonical {
+func newByCanonical(length int) *byCanonical {
 	v := byCanonicalPool.Get()
 	if v == nil {
 		return &byCanonical{pairs: make([]pair, 0, length)}
@@ -304,36 +295,38 @@ func getByCanonical(length int) *byCanonical {
 	return s
 }
 
-func putByCanonical(s *byCanonical) {
+func returnByCanonical(s *byCanonical) {
 	s.pairs = s.pairs[:0]
 	byCanonicalPool.Put(s)
 }
 
-func (me mapEncoder) encodeMapCanonical(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	if me.kf == nil || me.ef == nil {
+func encodeMapCanonical(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
+	kf := getEncodeFunc(v.Type().Key())
+	ef := getEncodeFunc(v.Type().Elem())
+	if kf == nil || ef == nil {
 		return 0, &UnsupportedTypeError{v.Type()}
 	}
 	if v.IsNil() {
 		return e.Write(cborNil)
 	}
 	if v.Len() == 0 {
-		return 1, e.WriteByte(byte(cborTypeMap))
+		return encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(0)), nil
 	}
-	pairEncodeState := getEncodeState() // accumulated cbor encoded map key-value pairs
-	pairs := getByCanonical(v.Len())    // for sorting keys
+	pairEncodeState := newEncodeState() // accumulated cbor encoded map key-value pairs
+	pairs := newByCanonical(v.Len())    // for sorting keys
 
 	iter := v.MapRange()
 	for iter.Next() {
-		n1, err := me.kf(pairEncodeState, iter.Key(), opts)
+		n1, err := kf(pairEncodeState, iter.Key(), opts)
 		if err != nil {
-			putEncodeState(pairEncodeState)
-			putByCanonical(pairs)
+			returnEncodeState(pairEncodeState)
+			returnByCanonical(pairs)
 			return 0, err
 		}
-		n2, err := me.ef(pairEncodeState, iter.Value(), opts)
+		n2, err := ef(pairEncodeState, iter.Value(), opts)
 		if err != nil {
-			putEncodeState(pairEncodeState)
-			putByCanonical(pairs)
+			returnEncodeState(pairEncodeState)
+			returnByCanonical(pairs)
 			return 0, err
 		}
 		pairs.pairs = append(pairs.pairs, pair{keyLen: n1, pairLen: n1 + n2})
@@ -353,78 +346,36 @@ func (me mapEncoder) encodeMapCanonical(e *encodeState, v reflect.Value, opts En
 		n += n1
 	}
 
-	putEncodeState(pairEncodeState)
-	putByCanonical(pairs)
-	return n, nil
-}
-
-func encodeStructToArray(e *encodeState, v reflect.Value, opts EncOptions, flds fields) (int, error) {
-	n := encodeTypeAndAdditionalValue(e, byte(cborTypeArray), uint64(len(flds)))
-FieldLoop:
-	for i := 0; i < len(flds); i++ {
-		fv := v
-		for k, n := range flds[i].idx {
-			if k > 0 {
-				if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
-					if fv.IsNil() {
-						// Write nil for null pointer to embedded struct
-						e.Write(cborNil)
-						continue FieldLoop
-					}
-					fv = fv.Elem()
-				}
-			}
-			fv = fv.Field(n)
-		}
-		n1, err := flds[i].ef(e, fv, opts)
-		if err != nil {
-			return 0, err
-		}
-		n += n1
-	}
+	returnEncodeState(pairEncodeState)
+	returnByCanonical(pairs)
 	return n, nil
 }
 
 func encodeStruct(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
-	structType := getEncodingStructType(v.Type())
-	if structType.err != nil {
-		return 0, structType.err
-	}
-
-	if structType.toArray {
-		return encodeStructToArray(e, v, opts, structType.fields)
-	}
+	flds := getEncodingStructFields(v.Type(), opts.Canonical)
 
-	flds := structType.fields
-	if opts.Canonical {
-		flds = structType.canonicalFields
-	}
-
-	kve := getEncodeState() // encode key-value pairs based on struct field tag options
+	kve := newEncodeState() // encode key-value pairs based on struct field tag options
 	kvcount := 0
-FieldLoop:
-	for i := 0; i < len(flds); i++ {
-		fv := v
-		for k, n := range flds[i].idx {
-			if k > 0 {
-				if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
-					if fv.IsNil() {
-						// Null pointer to embedded struct
-						continue FieldLoop
-					}
-					fv = fv.Elem()
-				}
-			}
-			fv = fv.Field(n)
+	for _, f := range flds {
+		if f.ef == nil {
+			return 0, &UnsupportedTypeError{v.Type()}
 		}
-		if flds[i].omitempty && isEmptyValue(fv) {
+		fv, err := fieldByIndex(v, f.idx)
+		if err != nil {
+			returnEncodeState(kve)
+			return 0, err
+		}
+		if !fv.IsValid() || (f.omitempty && isEmptyValue(fv)) {
 			continue
 		}
-
-		kve.Write(flds[i].cborName)
-
-		if _, err := flds[i].ef(kve, fv, opts); err != nil {
-			putEncodeState(kve)
+		if f.cborNameLen > 0 {
+			kve.Write(f.cborName[:f.cborNameLen])
+		} else {
+			encodeStringInternal(kve, f.name, opts)
+		}
+		_, err = f.ef(kve, fv, opts)
+		if err != nil {
+			returnEncodeState(kve)
 			return 0, err
 		}
 		kvcount++
@@ -433,7 +384,7 @@ FieldLoop:
 	n := encodeTypeAndAdditionalValue(e, byte(cborTypeMap), uint64(kvcount))
 	n1, err := e.Write(kve.Bytes())
 
-	putEncodeState(kve)
+	returnEncodeState(kve)
 	return n + n1, err
 }
 
@@ -503,7 +454,7 @@ func getEncodeIndirectValueFunc(f encodeFunc) encodeFunc {
 	}
 }
 
-func getEncodeFuncInternal(t reflect.Type) encodeFunc {
+func getEncodeFunc(t reflect.Type) encodeFunc {
 	if t.Kind() == reflect.Ptr {
 		for t.Kind() == reflect.Ptr {
 			t = t.Elem()
@@ -534,13 +485,12 @@ func getEncodeFuncInternal(t reflect.Type) encodeFunc {
 		return encodeFloat
 	case reflect.String:
 		return encodeString
-	case reflect.Slice, reflect.Array:
-		if t.Elem().Kind() == reflect.Uint8 {
-			return encodeByteString
-		}
-		return arrayEncoder{f: getEncodeFunc(t.Elem())}.encodeArray
+	case reflect.Array:
+		return encodeArray
+	case reflect.Slice:
+		return encodeSlice
 	case reflect.Map:
-		return mapEncoder{kf: getEncodeFunc(t.Key()), ef: getEncodeFunc(t.Elem())}.encodeMap
+		return encodeMap
 	case reflect.Struct:
 		return encodeStruct
 	case reflect.Interface:
@@ -606,17 +556,8 @@ func isEmptyValue(v reflect.Value) bool {
 //
 // Marshal supports format string stored under the "cbor" key in the struct
 // field's tag.  CBOR format string can specify the name of the field, "omitempty"
-// and "keyasint" options, and special case "-" for field omission.  If "cbor"
-// key is absent, Marshal uses "json" key.
-//
-// Struct field name is treated as integer if it has "keyasint" option in
-// its format string.  The format string must specify an integer as its
-// field name.
-//
-// Special struct field "_" is used to specify struct level options, such as
-// "toarray". "toarray" option enables Go struct to be encoded as CBOR array.
-// "omitempty" is disabled by "toarray" to ensure that the same number
-// of elements are encoded every time.
+// option, and special case "-" for field omission.  if "cbor" key is absent,
+// Marshal uses "json" key.
 //
 // Anonymous struct fields are usually marshalled as if their exported fields
 // were fields in the outer struct.  Marshal follows the same struct fields
@@ -647,17 +588,17 @@ func isEmptyValue(v reflect.Value) bool {
 //
 // Marshal supports RFC 7049 and CTAP2 canonical CBOR encoding.
 func Marshal(v interface{}, encOpts EncOptions) ([]byte, error) {
-	e := getEncodeState()
+	e := newEncodeState()
 
 	err := e.marshal(v, encOpts)
 	if err != nil {
-		putEncodeState(e)
+		returnEncodeState(e)
 		return nil, err
 	}
 
 	buf := make([]byte, e.Len())
 	copy(buf, e.Bytes())
 
-	putEncodeState(e)
+	returnEncodeState(e)
 	return buf, nil
 }
diff --git a/structfields.go b/structfields.go
index e89ad54..a5a3527 100644
--- a/structfields.go
+++ b/structfields.go
@@ -8,6 +8,7 @@ import (
 	"reflect"
 	"sort"
 	"strings"
+	"sync"
 )
 
 // fieldByIndex returns the nested field corresponding to the index.  It
@@ -32,14 +33,14 @@ func fieldByIndex(v reflect.Value, index []int) (reflect.Value, error) {
 
 type field struct {
 	name          string
-	cborName      []byte
 	idx           []int
 	typ           reflect.Type
-	ef            encodeFunc
 	isUnmarshaler bool
+	ef            encodeFunc
+	cborName      [64]byte
+	cborNameLen   int
 	tagged        bool // used to choose dominant field (at the same level tagged fields dominate untagged fields)
 	omitempty     bool // used to skip empty field
-	keyasint      bool // used to encode/decode field name as int
 }
 
 type fields []field
@@ -92,32 +93,40 @@ func (s byNameLevelAndTag) Less(i, j int) bool {
 	return i < j // Field i and j have the same name, depth, and tagged status. Nothing else matters.
 }
 
-func getFieldNameAndOptionsFromTag(tag string) (name string, omitEmpty bool, keyAsInt bool) {
-	if len(tag) == 0 {
-		return
-	}
-	idx := strings.Index(tag, ",")
-	if idx == -1 {
-		return tag, false, false
+// byCanonicalRule sorts fields by field name length and field name.
+type byCanonicalRule struct {
+	fields
+}
+
+func (s byCanonicalRule) Less(i, j int) bool {
+	if len(s.fields[i].name) != len(s.fields[j].name) {
+		return len(s.fields[i].name) < len(s.fields[j].name)
 	}
-	if idx > 0 {
-		name = tag[:idx]
-		tag = tag[idx:]
+	return s.fields[i].name <= s.fields[j].name
+}
+
+func getFieldNameOptionsFromTag(tag string) (string, string) {
+	if len(tag) == 0 {
+		return "", ""
 	}
-	ss := ",omitempty"
-	if idx = strings.Index(tag, ss); idx >= 0 && (len(tag) == idx+len(ss) || tag[idx+len(ss)] == ',') {
-		omitEmpty = true
+	commaIdx := strings.IndexByte(tag, byte(','))
+	if commaIdx == -1 {
+		return tag, ""
 	}
-	ss = ",keyasint"
-	if idx = strings.Index(tag, ss); idx >= 0 && (len(tag) == idx+len(ss) || tag[idx+len(ss)] == ',') {
-		keyAsInt = true
+	return tag[:commaIdx], tag[commaIdx+1:]
+}
+
+func hasFieldOptionFromTag(options, key string) bool {
+	idx := strings.Index(options, key)
+	if idx == -1 {
+		return false
 	}
-	return
+	return idx+len(key) == len(options) || options[idx+len(key)] == ','
 }
 
 // getFields returns a list of visible fields of struct type typ following Go
 // visibility rules for struct fields.
-func getFields(typ reflect.Type) (flds fields, structOptions string) {
+func getFields(typ reflect.Type) fields {
 	// Inspired by Go JSON encoding package's typeFields() function in encoding/json/encode.go.
 
 	var current map[reflect.Type][][]int // key: struct type, value: field index of this struct type at the same level
@@ -125,6 +134,8 @@ func getFields(typ reflect.Type) (flds fields, structOptions string) {
 
 	visited := map[reflect.Type]bool{} // Inspected struct type at less nested levels.
 
+	var flds fields
+
 	for len(next) > 0 {
 		current, next = next, map[reflect.Type][][]int{}
 
@@ -160,12 +171,6 @@ func getFields(typ reflect.Type) (flds fields, structOptions string) {
 					// Nonexportable anonymous field of struct type can contain exportable fields for serialization.
 				} else if !exportable {
 					// Nonexportable fields are ignored.
-					if f.Name == "_" {
-						tag := f.Tag.Get("cbor")
-						if tag != "-" {
-							structOptions = tag
-						}
-					}
 					continue
 				}
 
@@ -185,7 +190,8 @@ func getFields(typ reflect.Type) (flds fields, structOptions string) {
 				idx[len(fieldIdx)] = i
 
 				tagged := len(tag) > 0
-				tagFieldName, omitempty, keyasint := getFieldNameAndOptionsFromTag(tag)
+				tagFieldName, tagOptions := getFieldNameOptionsFromTag(tag)
+				omitempty := hasFieldOptionFromTag(tagOptions, "omitempty")
 
 				fieldName := tagFieldName
 				if tagFieldName == "" {
@@ -193,7 +199,7 @@ func getFields(typ reflect.Type) (flds fields, structOptions string) {
 				}
 
 				if !f.Anonymous || ft.Kind() != reflect.Struct || len(tagFieldName) > 0 {
-					flds = append(flds, field{name: fieldName, idx: idx, typ: f.Type, tagged: tagged, omitempty: omitempty, keyasint: keyasint})
+					flds = append(flds, field{name: fieldName, idx: idx, typ: f.Type, tagged: tagged, omitempty: omitempty})
 					continue
 				}
 
@@ -221,5 +227,66 @@ func getFields(typ reflect.Type) (flds fields, structOptions string) {
 
 	sort.Sort(byIndex{visibleFields})
 
-	return visibleFields, structOptions
+	return visibleFields
+}
+
+type structFields struct {
+	typ             reflect.Type
+	fields          fields
+	canonicalFields fields
+}
+
+var (
+	cachedStructFields         sync.Map
+	cachedEncodingStructFields sync.Map
+)
+
+func getStructFields(t reflect.Type) fields {
+	if v, _ := cachedStructFields.Load(t); v != nil {
+		return v.(fields)
+	}
+	flds := getFields(t)
+	for i := 0; i < len(flds); i++ {
+		flds[i].isUnmarshaler = implementsUnmarshaler(flds[i].typ)
+	}
+	cachedStructFields.Store(t, flds)
+	return flds
+}
+
+func getEncodingStructFields(t reflect.Type, canonical bool) fields {
+	if v, _ := cachedEncodingStructFields.Load(t); v != nil {
+		if canonical {
+			return v.(structFields).canonicalFields
+		}
+		return v.(structFields).fields
+	}
+
+	fldsOrig := getStructFields(t)
+	flds := make(fields, len(fldsOrig))
+	copy(flds, fldsOrig)
+	for i := 0; i < len(flds); i++ {
+		flds[i].ef = getEncodeFunc(flds[i].typ)
+		nameLen := len(flds[i].name)
+		if nameLen <= 23 {
+			flds[i].cborName[0] = byte(cborTypeTextString) | byte(nameLen)
+			copy(flds[i].cborName[1:], flds[i].name)
+			flds[i].cborNameLen = 1 + nameLen
+		} else if nameLen <= 62 {
+			flds[i].cborName[0] = byte(cborTypeTextString) | byte(24)
+			flds[i].cborName[1] = byte(nameLen)
+			copy(flds[i].cborName[2:], flds[i].name)
+			flds[i].cborNameLen = 2 + nameLen
+		}
+	}
+
+	canonicalFields := make(fields, len(flds))
+	copy(canonicalFields, flds)
+	sort.Sort(byCanonicalRule{canonicalFields})
+
+	cachedEncodingStructFields.Store(t, structFields{typ: t, fields: flds, canonicalFields: canonicalFields})
+
+	if canonical {
+		return canonicalFields
+	}
+	return flds
 }


Test patch:
diff --git a/bench_test.go b/bench_test.go
index 45c8046..63c4ddd 100644
--- a/bench_test.go
+++ b/bench_test.go
@@ -149,42 +149,6 @@ func BenchmarkMarshal(b *testing.B) {
 	}
 }
 
-func BenchmarkMarshalCanonical(b *testing.B) {
-	for _, bm := range []struct {
-		name     string
-		cborData []byte
-		values   []interface{}
-	}{
-		{"map", hexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"), []interface{}{map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}, strc{A: "A", B: "B", C: "C", D: "D", E: "E", F: "F", G: "G", H: "H", I: "I", J: "J", L: "L", M: "M", N: "N"}}},
-	} {
-		for _, v := range bm.values {
-			name := "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name
-			if reflect.TypeOf(v).Kind() == reflect.Struct {
-				name = "Go " + reflect.TypeOf(v).Kind().String() + " to CBOR " + bm.name
-			}
-			b.Run(name, func(b *testing.B) {
-				for i := 0; i < b.N; i++ {
-					if _, err := cbor.Marshal(v, cbor.EncOptions{}); err != nil {
-						b.Fatal("Marshal:", err)
-					}
-				}
-			})
-			// Canonical encoding
-			name = "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name + " canonical"
-			if reflect.TypeOf(v).Kind() == reflect.Struct {
-				name = "Go " + reflect.TypeOf(v).Kind().String() + " to CBOR " + bm.name + " canonical"
-			}
-			b.Run(name, func(b *testing.B) {
-				for i := 0; i < b.N; i++ {
-					if _, err := cbor.Marshal(v, cbor.EncOptions{Canonical: true}); err != nil {
-						b.Fatal("Marshal:", err)
-					}
-				}
-			})
-		}
-	}
-}
-
 func BenchmarkEncode(b *testing.B) {
 	for _, bm := range encodeBenchmarks {
 		for _, v := range bm.values {
@@ -221,27 +185,13 @@ func BenchmarkEncodeStream(b *testing.B) {
 }
 
 func BenchmarkDecodeCOSE(b *testing.B) {
-	type coseKey struct {
-		Kty    int             `cbor:"1,keyasint"`
-		Alg    int             `cbor:"3,keyasint"`
-		CrvOrN cbor.RawMessage `cbor:"-1,keyasint"`
-		XOrE   cbor.RawMessage `cbor:"-2,keyasint"`
-		Y      cbor.RawMessage `cbor:"-3,keyasint"`
-	}
-
-	// Data from https://www.w3.org/TR/webauthn/#sec-attested-credential-data example 2, COSE_Key-encoded ES256 (P-256 curve) in EC2 format.
-	cborData := hexDecode("a501020326200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c")
+	cborData := hexDecode("a50102032620012158205af8047e9085ef79ec321280c7b95844d707d7fe4d73cd648f044c619ee74f6b22582036bb8c00768e90858012dc3831e15a389072bbdbe7e2e19155db9e1197655edf")
 	for _, ctor := range []func() interface{}{
 		func() interface{} { return new(interface{}) },
 		func() interface{} { return new(map[interface{}]interface{}) },
 		func() interface{} { return new(map[int]interface{}) },
-		func() interface{} { return new(coseKey) },
 	} {
-		t := reflect.TypeOf(ctor()).Elem()
-		name := "COSE to Go " + t.String()
-		if t.Kind() == reflect.Struct {
-			name = "COSE to Go " + t.Kind().String()
-		}
+		name := "COSE to Go " + reflect.TypeOf(ctor()).Elem().String()
 		b.Run(name, func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
 				v := ctor()
diff --git a/decode_test.go b/decode_test.go
index 19c59f3..0964ded 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -1072,7 +1072,7 @@ func TestUnmarshalStructError2(t *testing.T) {
 	}
 
 	// Unmarshal returns first error encountered, which is *cbor.UnmarshalTypeError (failed to unmarshal int into Go string)
-	cborData := hexDecode("a3fa47c35000026161614161fe6142") // {100000.0:2, "a":"A", 0xfe: B}
+	cborData := hexDecode("a301026161614161fe6142") // {1:2, "a":"A", 0xfe: B}
 	v := strc{}
 	if err := cbor.Unmarshal(cborData, &v); err == nil {
 		t.Errorf("Unmarshal(0x%0x) doesn't return an error", cborData)
@@ -1080,14 +1080,14 @@ func TestUnmarshalStructError2(t *testing.T) {
 		if typeError, ok := err.(*cbor.UnmarshalTypeError); !ok {
 			t.Errorf("Unmarshal(0x%0x) returns wrong type of error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
 		} else {
-			if typeError.Value != "primitives" {
-				t.Errorf("Unmarshal(0x%0x) (*cbor.UnmarshalTypeError).Value %s, want %s", cborData, typeError.Value, "primitives")
+			if typeError.Value != "positive integer" {
+				t.Errorf("Unmarshal(0x%0x) (*cbor.UnmarshalTypeError).Value %s, want %s", cborData, typeError.Value, "positive integer")
 			}
 			if typeError.Type != typeString {
 				t.Errorf("Unmarshal(0x%0x) (*cbor.UnmarshalTypeError).Type %s, want %s", cborData, typeError.Type, typeString)
 			}
-			if !strings.Contains(err.Error(), "cannot unmarshal primitives into Go value of type string") {
-				t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err.Error(), "cannot unmarshal float into Go value of type string")
+			if !strings.Contains(err.Error(), "cannot unmarshal positive integer into Go value of type string") {
+				t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err.Error(), "cannot unmarshal positive integer into Go value of type string")
 			}
 		}
 	}
@@ -1213,6 +1213,16 @@ func TestValid(t *testing.T) {
 	}
 }
 
+func TestSkipData(t *testing.T) {
+	cborData := hexDecode("850102031bffffffffffffffffc11a514b67b0") // [1, 2, 3, 18446744073709551615, 1363896240]
+	i := 0
+	if err := cbor.Unmarshal(cborData, &i); err == nil {
+		t.Errorf("Unmarshal(0x%0x) doesn't return error, want UnmarshalTypeError", cborData)
+	} else if _, ok := err.(*cbor.UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%0x) returns wrong type of error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
+	}
+}
+
 func TestStructFieldNil(t *testing.T) {
 	type TestStruct struct {
 		I   int
@@ -1313,23 +1323,6 @@ func TestFuzzCrash5(t *testing.T) {
 	}
 }
 
-func TestFuzzCrash6(t *testing.T) {
-	// Crash5: parsing nil into map key.
-	testData := [][]byte{
-		[]byte("\x82\xa1\xf60000"),
-		[]byte("\xa1\xf600"),
-	}
-	for _, data := range testData {
-		var intf interface{}
-		if err := cbor.Unmarshal(data, &intf); err != nil {
-			t.Fatalf("Unmarshal(0x%02x) returns error %s\n", data, err)
-		}
-		if _, err := cbor.Marshal(intf, cbor.EncOptions{Canonical: true}); err != nil {
-			t.Errorf("Marshal(%v) returns error %s", intf, err)
-		}
-	}
-}
-
 func TestDecodeTime(t *testing.T) {
 	testCases := []struct {
 		name            string
@@ -1787,124 +1780,3 @@ func TestCOSEExamples(t *testing.T) {
 		}
 	}
 }
-
-func TestUnmarshalStructKeyAsIntError(t *testing.T) {
-	type T1 struct {
-		F1 int `cbor:"1,keyasint"`
-	}
-	cborData := hexDecode("a13bffffffffffffffff01") // {1: -18446744073709551616}
-	var v T1
-	if err := cbor.Unmarshal(cborData, &v); err == nil {
-		t.Errorf("Unmarshal(0x%0x) returns no error, %v (%T), want (*cbor.UnmarshalTypeError)", cborData, v, v)
-	} else if _, ok := err.(*cbor.UnmarshalTypeError); !ok {
-		t.Errorf("Unmarshal(0x%0x) returns wrong error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
-	} else if !strings.Contains(err.Error(), "cannot unmarshal") {
-		t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err, "cannot unmarshal")
-	}
-}
-
-func TestUnmarshalArrayToStruct(t *testing.T) {
-	type T struct {
-		_ struct{} `cbor:",toarray"`
-		A int
-		B int
-		C int
-	}
-	testCases := []struct {
-		name     string
-		cborData []byte
-	}{
-		{"definite length array", hexDecode("83010203")},
-		{"indefinite length array", hexDecode("9f010203ff")},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			var v T
-			if err := cbor.Unmarshal(tc.cborData, &v); err != nil {
-				t.Errorf("Unmarshal(0x%0x) returns error %s", tc.cborData, err)
-			}
-		})
-	}
-}
-
-func TestUnmarshalArrayToStructNoToArrayOptionError(t *testing.T) {
-	type T struct {
-		A int
-		B int
-		C int
-	}
-	cborData := hexDecode("83010203")
-	var v T
-	if err := cbor.Unmarshal(cborData, &v); err == nil {
-		t.Errorf("Unmarshal(0x%0x) returns no error, %v (%T), want (*cbor.UnmarshalTypeError)", cborData, v, v)
-	} else if _, ok := err.(*cbor.UnmarshalTypeError); !ok {
-		t.Errorf("Unmarshal(0x%0x) returns wrong error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
-	} else if !strings.Contains(err.Error(), "cannot unmarshal") {
-		t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err, "cannot unmarshal")
-	}
-}
-
-func TestUnmarshalArrayToStructWrongSizeError(t *testing.T) {
-	type T struct {
-		_ struct{} `cbor:",toarray"`
-		A int
-		B int
-		C int
-	}
-	cborData := hexDecode("820102")
-	var v T
-	if err := cbor.Unmarshal(cborData, &v); err == nil {
-		t.Errorf("Unmarshal(0x%0x) returns no error, %v (%T), want (*cbor.UnmarshalTypeError)", cborData, v, v)
-	} else if _, ok := err.(*cbor.UnmarshalTypeError); !ok {
-		t.Errorf("Unmarshal(0x%0x) returns wrong error %T, want (*cbor.UnmarshalTypeError)", cborData, err)
-	} else if !strings.Contains(err.Error(), "cannot unmarshal") {
-		t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", cborData, err, "cannot unmarshal")
-	}
-}
-
-func TestUnmarshalArrayToStructWrongFieldTypeError(t *testing.T) {
-	type T struct {
-		_ struct{} `cbor:",toarray"`
-		A int
-		B int
-		C string
-	}
-	testCases := []struct {
-		name        string
-		cborData    []byte
-		wantErroMsg string
-	}{
-		{"wrong field type", hexDecode("83010203"), "cannot unmarshal"},
-		{"invalid UTF-8 string", hexDecode("83010261fe"), "cbor: invalid UTF-8 string"},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			var v T
-			if err := cbor.Unmarshal(tc.cborData, &v); err == nil {
-				t.Errorf("Unmarshal(0x%0x) returns no error, %v (%T), want (*cbor.UnmarshalTypeError)", tc.cborData, v, v)
-			} else if !strings.Contains(err.Error(), tc.wantErroMsg) {
-				t.Errorf("Unmarshal(0x%0x) returns error %s, want error containing %q", tc.cborData, err, tc.wantErroMsg)
-			}
-		})
-	}
-}
-
-func TestUnmarshalArrayToStructCannotSetEmbeddedPointerError(t *testing.T) {
-	type (
-		s1 struct{ x, X int }
-		S2 struct{ y, Y int }
-		S  struct {
-			_ struct{} `cbor:",toarray"`
-			*s1
-			*S2
-		}
-	)
-	cborData := []byte{0x82, 0x02, 0x04} // {2, 4}
-	var v1 S
-	err := cbor.Unmarshal(cborData, &v1)
-	if err == nil {
-		t.Errorf("Unmarshal(%0x) doesn't return error.  want error: 'cannot set embedded pointer to unexported struct'", cborData)
-	} else if !strings.Contains(err.Error(), "cannot set embedded pointer to unexported struct") {
-		t.Errorf("Unmarshal(%0x) returns error '%s'.  want error: 'cannot set embedded pointer to unexported struct'", cborData, err)
-	}
-}
diff --git a/encode_test.go b/encode_test.go
index 322e984..143e7c3 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -9,7 +9,6 @@ import (
 	"io"
 	"math"
 	"reflect"
-	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -419,27 +418,6 @@ func encodeCborHeader(t byte, n uint64) []byte {
 	}
 }
 
-func testMarshal(t *testing.T, testCases []marshalTest) {
-	for _, tc := range testCases {
-		for _, value := range tc.values {
-			if _, err := cbor.Marshal(value, cbor.EncOptions{}); err != nil {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", value, err)
-			}
-			if b, err := cbor.Marshal(value, cbor.EncOptions{Canonical: true}); err != nil {
-				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) returns error %v", value, err)
-			} else if !bytes.Equal(b, tc.cborData) {
-				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) = 0x%0x, want 0x%0x", value, b, tc.cborData)
-			}
-		}
-		r := cbor.RawMessage(tc.cborData)
-		if b, err := cbor.Marshal(r, cbor.EncOptions{}); err != nil {
-			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", r, err)
-		} else if !bytes.Equal(b, r) {
-			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns %v, want %v", r, b, r)
-		}
-	}
-}
-
 func TestMarshalStruct(t *testing.T) {
 	v1 := outer{
 		IntField:          123,
@@ -556,43 +534,24 @@ func TestMarshalStructCanonical(t *testing.T) {
 	}
 }
 
-func TestMarshalNullPointerToEmbeddedStruct(t *testing.T) {
-	type (
-		T1 struct {
-			N int
-		}
-		T2 struct {
-			*T1
-		}
-	)
-	v := T2{}
-	wantCborData := []byte{0xa0} // {}
-	cborData, err := cbor.Marshal(v, cbor.EncOptions{})
-	if err != nil {
-		t.Fatalf("Marshal(%v) returns error %v", v, err)
-	}
-	if !bytes.Equal(wantCborData, cborData) {
-		t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", v, cborData, wantCborData)
-	}
-}
-
-func TestMarshalNullPointerToStruct(t *testing.T) {
-	type (
-		T1 struct {
-			N int
+func testMarshal(t *testing.T, testCases []marshalTest) {
+	for _, tc := range testCases {
+		for _, value := range tc.values {
+			if _, err := cbor.Marshal(value, cbor.EncOptions{}); err != nil {
+				t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", value, err)
+			}
+			if b, err := cbor.Marshal(value, cbor.EncOptions{Canonical: true}); err != nil {
+				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) returns error %v", value, err)
+			} else if !bytes.Equal(b, tc.cborData) {
+				t.Errorf("Marshal(%v, cbor.EncOptions{Canonical: true}) = 0x%0x, want 0x%0x", value, b, tc.cborData)
+			}
 		}
-		T2 struct {
-			T *T1
+		r := cbor.RawMessage(tc.cborData)
+		if b, err := cbor.Marshal(r, cbor.EncOptions{}); err != nil {
+			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns error %v", r, err)
+		} else if !bytes.Equal(b, r) {
+			t.Errorf("Marshal(%v, cbor.EncOptions{}) returns %v, want %v", r, b, r)
 		}
-	)
-	v := T2{}
-	wantCborData := []byte{0xa1, 0x61, 0x54, 0xf6} // {T: nil}
-	cborData, err := cbor.Marshal(v, cbor.EncOptions{})
-	if err != nil {
-		t.Fatalf("Marshal(%v) returns error %v", v, err)
-	}
-	if !bytes.Equal(wantCborData, cborData) {
-		t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", v, cborData, wantCborData)
 	}
 }
 
@@ -1368,180 +1327,3 @@ func TestMarshalRawMessageValue(t *testing.T) {
 		}
 	}
 }
-
-func TestCyclicDataStructure(t *testing.T) {
-	type Node struct {
-		V int   `cbor:"v"`
-		N *Node `cbor:"n,omitempty"`
-	}
-	v := Node{1, &Node{2, &Node{3, nil}}}                                                                                  // linked list: 1, 2, 3
-	wantCborData := []byte{0xa2, 0x61, 0x76, 0x01, 0x61, 0x6e, 0xa2, 0x61, 0x76, 0x02, 0x61, 0x6e, 0xa1, 0x61, 0x76, 0x03} // {v: 1, n: {v: 2, n: {v: 3}}}
-	cborData, err := cbor.Marshal(v, cbor.EncOptions{})
-	if err != nil {
-		t.Fatalf("Marshal(%v) returns error %s", v, err)
-	}
-	if !bytes.Equal(wantCborData, cborData) {
-		t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", v, cborData, wantCborData)
-	}
-	var v1 Node
-	if err = cbor.Unmarshal(cborData, &v1); err != nil {
-		t.Fatalf("Unmarshal(0x%0x) returns error %s", cborData, err)
-	}
-	if !reflect.DeepEqual(v, v1) {
-		t.Errorf("Unmarshal(0x%0x) returns %+v, want %+v", cborData, v1, v)
-	}
-}
-
-func TestMarshalUnmarshalStructKeyAsInt(t *testing.T) {
-	type T struct {
-		F1 int `cbor:"1,omitempty,keyasint"`
-		F2 int `cbor:"2,omitempty"`
-		F3 int `cbor:"-3,omitempty,keyasint"`
-	}
-	testCases := []struct {
-		name         string
-		obj          interface{}
-		wantCborData []byte
-	}{
-		{
-			"Zero value struct",
-			T{},
-			hexDecode("a0"), // {}
-		},
-		{
-			"Initialized value struct",
-			T{F1: 1, F2: 2, F3: 3},
-			hexDecode("a301012203613202"), // {1: 1, -3: 3, "2": 2}
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			b, err := cbor.Marshal(tc.obj, cbor.EncOptions{Canonical: true})
-			if err != nil {
-				t.Errorf("Marshal(%+v) returns error %s", tc.obj, err)
-			}
-			if !bytes.Equal(b, tc.wantCborData) {
-				t.Errorf("Marshal(%+v) = 0x%0x, want 0x%0x", tc.obj, b, tc.wantCborData)
-			}
-
-			var v2 T
-			if err := cbor.Unmarshal(b, &v2); err != nil {
-				t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
-			}
-			if !reflect.DeepEqual(tc.obj, v2) {
-				t.Errorf("Unmarshal(0x%0x) returns %+v, want %+v", b, v2, tc.obj)
-			}
-		})
-	}
-}
-
-func TestMarshalStructKeyAsIntNumError(t *testing.T) {
-	type T1 struct {
-		F1 int `cbor:"2.0,keyasint"`
-	}
-	type T2 struct {
-		F1 int `cbor:"-18446744073709551616,keyasint"`
-	}
-	testCases := []struct {
-		name         string
-		obj          interface{}
-		wantErrorMsg string
-	}{
-		{
-			name:         "float as key",
-			obj:          T1{},
-			wantErrorMsg: "parsing \"2.0\": invalid syntax",
-		},
-		{
-			name:         "out of range int as key",
-			obj:          T2{},
-			wantErrorMsg: "parsing \"-18446744073709551616\": value out of range",
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			b, err := cbor.Marshal(tc.obj, cbor.EncOptions{})
-			if err == nil {
-				t.Errorf("Marshal(%+v, cbor.EncOptions{}) doesn't return an error, want error %q", tc.obj, tc.wantErrorMsg)
-			} else if _, ok := err.(*strconv.NumError); !ok {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) error type %T, want *strconv.NumError", tc.obj, err)
-			} else if !strings.Contains(err.Error(), tc.wantErrorMsg) {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) error %s, want %s", tc.obj, err, tc.wantErrorMsg)
-			} else if b != nil {
-				t.Errorf("Marshal(%v, cbor.EncOptions{}) = 0x%0x, want nil", tc.obj, b)
-			}
-		})
-	}
-}
-
-func TestMarshalUnmarshalStructToArray(t *testing.T) {
-	type T1 struct {
-		M int `cbor:",omitempty"`
-	}
-	type T2 struct {
-		N int `cbor:",omitempty"`
-		O int `cbor:",omitempty"`
-	}
-	type T struct {
-		_   struct{} `cbor:",toarray"`
-		A   int      `cbor:",omitempty"`
-		B   T1       // nested struct
-		T1           // embedded struct
-		*T2          // embedded struct
-	}
-	testCases := []struct {
-		name         string
-		obj          T
-		wantCborData []byte
-	}{
-		{
-			"Zero value struct (test omitempty)",
-			T{},
-			hexDecode("8500a000f6f6"), // [0, {}, 0, nil, nil]
-		},
-		{
-			"Initialized struct",
-			T{A: 24, B: T1{M: 1}, T1: T1{M: 2}, T2: &T2{N: 3, O: 4}},
-			hexDecode("851818a1614d01020304"), // [24, {M: 1}, 2, 3, 4]
-		},
-		{
-			"Null pointer to embedded struct",
-			T{A: 24, B: T1{M: 1}, T1: T1{M: 2}},
-			hexDecode("851818a1614d0102f6f6"), // [24, {M: 1}, 2, nil, nil]
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			b, err := cbor.Marshal(tc.obj, cbor.EncOptions{})
-			if err != nil {
-				t.Errorf("Marshal(%+v) returns error %s", tc.obj, err)
-			}
-			if !bytes.Equal(b, tc.wantCborData) {
-				t.Errorf("Marshal(%+v) = 0x%0x, want 0x%0x", tc.obj, b, tc.wantCborData)
-			}
-
-			// Cannonical setting should be ignored for struct to array encoding
-			b, err = cbor.Marshal(tc.obj, cbor.EncOptions{Canonical: true})
-			if err != nil {
-				t.Errorf("Marshal(%+v) returns error %s", tc.obj, err)
-			}
-			if !bytes.Equal(b, tc.wantCborData) {
-				t.Errorf("Marshal(%+v) = 0x%0x, want 0x%0x", tc.obj, b, tc.wantCborData)
-			}
-
-			var v2 T
-			if err := cbor.Unmarshal(b, &v2); err != nil {
-				t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
-			}
-			if tc.obj.T2 == nil {
-				if !reflect.DeepEqual(*(v2.T2), T2{}) {
-					t.Errorf("Unmarshal(0x%0x) returns %+v, want %+v", b, v2, tc.obj)
-				}
-				v2.T2 = nil
-			}
-			if !reflect.DeepEqual(tc.obj, v2) {
-				t.Errorf("Unmarshal(0x%0x) returns %+v, want %+v", b, v2, tc.obj)
-			}
-		})
-	}
-}