Repository: fxamacker/cbor
Pull request #33 (2019-11-16 05:08:30)

Update README.md and create CBOR.md
Resolve "know Go" vs "no go" with "use Go's" in the summary.  

Link to CBOR.md.  Add TOC to bottom of page.  Mention io.LimitReader in Usage section.  Add milestones under Features section.  For consistency, mention CBOR Tags (major type 6) being under consideration in Features section (already mentioned in Limitations section.)


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/CBOR.md b/CBOR.md
new file mode 100644
index 0000000..d085676
--- /dev/null
+++ b/CBOR.md
@@ -0,0 +1,33 @@
+<div align="center">
+
+:small_orange_diamond: [Design Goals](https://github.com/fxamacker/cbor#design-goals) :small_orange_diamond: [Features](https://github.com/fxamacker/cbor#features) :small_orange_diamond: [Standards](https://github.com/fxamacker/cbor#standards) :small_orange_diamond: [Fuzzing and Coverage](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) :small_orange_diamond: [API](https://github.com/fxamacker/cbor#api) :small_orange_diamond: [Security Policy](https://github.com/fxamacker/cbor#security-policy) :small_orange_diamond:
+
+</div>
+
+# CBOR
+[CBOR](https://en.wikipedia.org/wiki/CBOR) is a data format designed for small code size and small message size. CBOR is defined in [RFC 7049 Concise Binary Object Representation](https://tools.ietf.org/html/rfc7049).
+
+While JSON uses text, CBOR uses binary. CDDL can be used to express CBOR (and JSON) in an easy and unambiguous way.  CDDL is defined in (RFC 8610 Concise Data Definition Language).
+
+## CBOR in Golang (Go)
+[Golang](https://golang.org/) is a nickname for the Go programming language.  Go is specified in [The Go Programming Language Specification](https://golang.org/ref/spec).
+
+__[fxamacker/cbor](https://github.com/fxamacker/cbor)__ is a library (written in Go) that encodes and decodes CBOR. The API design of fxamacker/cbor is based on Go's [`encoding/json`](https://golang.org/pkg/encoding/json/).  The design and reliability of fxamacker/cbor makes it ideal for encoding and decoding COSE.
+
+## COSE
+COSE is a protocol using CBOR for basic security services. COSE is defined in ([RFC 8152 CBOR Object Signing and Encryption](https://tools.ietf.org/html/rfc8152)).
+
+COSE describes how to create and process signatures, message authentication codes, and encryption using CBOR for serialization.  COSE specification also describes how to represent cryptographic keys using CBOR.  COSE is used by WebAuthn.
+
+## WebAuthn
+[WebAuthn](https://en.wikipedia.org/wiki/WebAuthn) (Web Authentication) is a web standard for authenticating users to web-based apps and services. It's a core component of FIDO2, the successor of FIDO U2F legacy protocol.
+
+__[fxamacker/webauthn](https://github.com/fxamacker/webauthn)__ is a library (written in Go) that performs server-side authentication for clients using FIDO2 keys, legacy FIDO U2F keys, tpm, and etc.
+
+Copyright (c) 2019 Faye Amacker and contributors.
+<hr>
+<div align="center">
+
+:small_orange_diamond: [Design Goals](https://github.com/fxamacker/cbor#design-goals) :small_orange_diamond: [Features](https://github.com/fxamacker/cbor#features) :small_orange_diamond: [Standards](https://github.com/fxamacker/cbor#standards) :small_orange_diamond: [Fuzzing and Coverage](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) :small_orange_diamond: [API](https://github.com/fxamacker/cbor#api) :small_orange_diamond: [Security Policy](https://github.com/fxamacker/cbor#security-policy) :small_orange_diamond:
+
+</div>
diff --git a/README.md b/README.md
index 0aaaafd..82e5825 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,9 @@
 <!-- [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/fxamacker/cbor/master/LICENSE) -->
 
 # 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.
+This library encodes and decodes [CBOR](CBOR.md) ([RFC 7049](https://tools.ietf.org/html/rfc7049)), a concise binary alternative to JSON and other data formats. 
 
-: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.
+:hourglass_flowing_sand: If you used [Go](https://golang.org)'s `encoding/json`, you already know how to use this library.  Existing structs don't require changes.  Go 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.
 
@@ -34,7 +34,7 @@ Install with ```go get github.com/fxamacker/cbor``` and use it like Go's ```enco
 
 <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:
+: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:
 
 </div>
 
@@ -46,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, adds `toarray` struct tag for more compact struct data, and simplifies decoding of COSE data (RFC 8152).
+[Milestone v1.3](https://github.com/fxamacker/cbor/milestone/2) is faster :rocket: and uses less memory :recycle:. New `toarray` struct tag allows more compact struct data.  Maps with int keys can encode and decode to structs, which makes using COSE (RFC 8152) data faster and easier.
 
 ## 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.
@@ -77,11 +77,14 @@ Libraries and programs were compiled for linux_amd64 using Go 1.12.
 * Support canonical CBOR encoding for map/struct.
 * Encode anonymous struct fields by `encoding/json` package struct fields visibility rules.
 * Encode and decode nil slice/map/pointer/interface values correctly.
-* Encode and decode indefinite length bytes/string/array/map (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)).
 * Encode and decode time.Time as RFC 3339 formatted text string or Unix time.
+* Encode and decode indefinite length bytes/string/array/map (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)).
 * :tada: v1.1 -- Support `encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler` interfaces.
 * :tada: v1.2 -- `cbor.RawMessage` can delay CBOR decoding or precompute CBOR encoding.
 * :tada: v1.2 -- User-defined types can have custom CBOR encoding and decoding by implementing `cbor.Marshaler` and `cbor.Unmarshaler` interfaces. 
+* :truck: [Milestone v1.3](https://github.com/fxamacker/cbor/milestone/2) -- Encode and decode struct to array (using `toarray` struct tag) for more compact data
+* :truck: [Milestone v1.3](https://github.com/fxamacker/cbor/milestone/2) -- Encode and decode maps with int keys to structs. This makes encoding and decoding COSE data (RFC 8152) much faster and simpler.
+* :balloon: Milestone v1.4 -- Maybe add support CBOR tags (major type 6.)
 
 ## Fuzzing and Code Coverage
 Each release passes coverage-guided fuzzing using [fxamacker/cbor-fuzz](https://github.com/fxamacker/cbor-fuzz).  Default corpus has:
@@ -106,7 +109,7 @@ 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.
+:balloon: 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)
@@ -151,9 +154,10 @@ type UnsupportedTypeError struct{ ... }
 ```
 go get github.com/fxamacker/cbor
 ```
+For production, use a tagged release to benefit from longer fuzz tests.
 
 ## Usage
-See [examples](example_test.go).
+Data validation prevents crashes and resource exhaustion attacks from malformed CBOR data.  Use `io.LimitReader` with this library to limit size of large CBOR data.
 
 Decoding:
 
@@ -224,10 +228,12 @@ err = enc.EndIndefinite()
 err = enc.EndIndefinite()
 ```
 
+More [examples](example_test.go).
+
 ## 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).
 
@@ -251,3 +257,10 @@ Please read the license for additional disclaimers and terms.
 Copyright (c) 2019 [Faye Amacker](https://github.com/fxamacker)
 
 Licensed under [MIT License](LICENSE)
+
+<hr>
+<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:
+
+</div>
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)
-			}
-		})
-	}
-}