Repository: fxamacker/cbor
Pull request #153 (2020-02-17 00:10:19)

Update README.md for CBOR tags & Duplicate Map Key Options
- Add CBOR tags (major type 6).
- Add option to detect duplicate map keys. 
- Add new encoding and decoding options like TimeTag that control whether time values should have tag numbers.
- Use "standard API" to describe API with function signatures identical to encoding/json.
- Split Features section into groups instead of having one massive list.
- Add a caveat to Versions and API Changes regarding new API that has never been release tagged.
- Thank Laurence Lundblade for his help on CBORbis issue regarding CBOR Null vs CBOR Undefined for time values.


Patch:
diff --git a/README.md b/README.md
index 324997c..2a90684 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-[![CBOR Library - Slideshow and Latest Docs.](https://github.com/fxamacker/images/raw/master/cbor/v2.0.0/cbor_slides.gif)](https://github.com/fxamacker/cbor/blob/master/README.md)
+[![CBOR Library - Slideshow and Latest Docs.](https://github.com/fxamacker/images/raw/master/cbor/v2.1.0/cbor_slides.gif)](https://github.com/fxamacker/cbor/blob/master/README.md)
 
 # CBOR library in Go
-[__`fxamacker/cbor`__](https://github.com/fxamacker/cbor) is a CBOR encoder and decoder.  CBOR codec functions have identical signatures to `json/encoding`.  Each release passes 375+ tests and 250+ million execs fuzzing.
+[__`fxamacker/cbor`__](https://github.com/fxamacker/cbor) is a CBOR encoder and decoder in [Go](https://golang.org).  v2.1 has a standard API, CBOR tags, 16/32/64-bit floats, options for duplicate map keys and more.  Each release passes 375+ tests and 250+ million execs fuzzing.
 
 [![](https://github.com/fxamacker/cbor/workflows/ci/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3Aci)
 [![](https://github.com/fxamacker/cbor/workflows/cover%20%E2%89%A597%25/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A597%25%22)
@@ -12,33 +12,34 @@
 
 __What is CBOR__?  [CBOR](CBOR_GOLANG.md) ([RFC 7049](https://tools.ietf.org/html/rfc7049)) is a binary data format inspired by JSON and MessagePack.  CBOR is used in [IETF](https://www.ietf.org) Internet Standards such as COSE ([RFC 8152](https://tools.ietf.org/html/rfc8152)) and CWT ([RFC 8392 CBOR Web Token](https://tools.ietf.org/html/rfc8392)). WebAuthn also uses CBOR.
 
-__Why this CBOR library?__ It doesn't crash and it has well-balanced qualities: small, fast, safe and easy. It also supports "preferred serialization" by encoding integers and floats to their smallest forms when values fit.
+__Why this CBOR library?__ It doesn't crash and it has well-balanced qualities: small, fast, safe and easy. It also has a standard API, CBOR tags (built-in and user-defined), 16/32/64-bit floats, and duplicate map key options.
+
+* __Standard API__. Codec functions with signatures identical to [`encoding/json`](https://golang.org/pkg/encoding/json/) include:  
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
+
+* __Customizable__. Standard interfaces are provided to allow user-implemented encoding or decoding:  
+`BinaryMarshaler`, `BinaryUnmarshaler`, `Marshaler`, and `Unmarshaler`.
 
 * __Small apps__.  Same programs are 4-9 MB smaller by switching to this library.  No code gen and the only imported pkg is [x448/float16](https://github.com/x448/float16) which is maintained by the same team as this library.
 
-* __Small data__.  The `toarray`, `keyasint`, and `omitempty` struct tags shrink size of Go structs encoded to CBOR.  Integers encode to smallest form that fits.  Floats can shrink from float64 -> float32 -> float16 if values can round-trip.
+* __Small data__.  The `toarray`, `keyasint`, and `omitempty` struct tags shrink size of Go structs encoded to CBOR.  Integers encode to smallest form that fits.  Floats can shrink from float64 -> float32 -> float16 if values fit.
 
 * __Fast__. v1.3 became faster than a well-known library that uses `unsafe` optimizations and code gen.  Faster libraries will always exist, but speed is only one factor.  This library doesn't use `unsafe` optimizations or code gen.  
 
 * __Safe__ and reliable. It prevents crashes on malicious CBOR data by using extensive tests, coverage-guided fuzzing, data validation, and avoiding Go's [`unsafe`](https://golang.org/pkg/unsafe/) pkg. Nested levels for CBOR arrays, maps, and tags are limited to 32.
 
-* __Easy__ and saves time.  It has the same API as [Go](https://golang.org)'s [`encoding/json`](https://golang.org/pkg/encoding/json/) when possible.  Existing structs don't require changes.  Go struct tags like `` `cbor:"name,omitempty"` `` and `` `json:"name,omitempty"` `` work as expected.  
-
-* __Stable API__. Six codec functions have signatures identical to encoding/json and they will never change:  
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
-
-Codec functions at package-level use default options. Same codec functions are exported by EncMode and DecMode.  EncMode and DecMode are interfaces created from EncOptions or DecOptions structs.
+* __Easy__ and saves time. Simple (no param) functions return preset `EncOptions` so you don't have to know the differences between Canonical CBOR and CTAP2 Canonical CBOR to use those standards.
 
-Predefined options make it easier to comply with standards like Canonical CBOR, CTAP2 Canonical CBOR, etc.
-
-Customized options are easy.  For example, EncOptions.Time can be set to:  
+Modifying EncOptions is also easy.  For example, EncOptions.Time can be set to:  
 `TimeUnix`, `TimeUnixMicro`, `TimeUnixDynamic`, `TimeRFC3339`, or `TimeRFC3339Nano`.
 
-Struct tags like __`keyasint`__ and __`toarray`__ make compact CBOR data such as COSE, CWT, and SenML easier to use.
+Go struct tags for CBOR and JSON like `` `cbor:"name,omitempty"` `` and `` `json:"name,omitempty"` `` are supported so you can leverage your existing code.  If both `cbor:` and `json:` tags exist then it will use `cbor:`.
+
+Struct tags like __`keyasint`__ and __`toarray`__ make compact CBOR data such as COSE, CWT, and SenML easier to use. 
 
 <hr>
 
-[![CBOR API](https://github.com/fxamacker/images/raw/master/cbor/v2.0.0/cbor_easy_api.png)](#usage)
+[![CBOR API](https://github.com/fxamacker/images/raw/master/cbor/v2.1.0/cbor_api_struct_tags.png)](#usage)
 
 <hr>
 
@@ -50,22 +51,30 @@ Comparisons are between this newer library and a well-known library that had 1,0
 
 __This library is safer__.  Small malicious CBOR messages are rejected quickly before they exhaust system resources.
 
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.0.0/cbor_safety_comparison.png "CBOR library safety comparison")
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.1.0/cbor_safety_comparison.png "CBOR library safety comparison")
 
 __This library is smaller__. Programs like senmlCat can be 4 MB smaller by switching to this library.  Programs using more complex CBOR data types can be 9.2 MB smaller.
 
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.0.0/cbor_size_comparison.png "CBOR library and program size comparison chart")
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.1.0/cbor_size_comparison.png "CBOR library and program size comparison chart")
 
 __This library is faster__ for encoding and decoding CBOR Web Token (CWT).  However, speed is only one factor and it can vary depending on data types and sizes.  Unlike the other library, this one doesn't use Go's ```unsafe``` package or code gen.
 
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.0.0/cbor_speed_comparison.png "CBOR library speed comparison chart")
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.1.0/cbor_speed_comparison.png "CBOR library speed comparison chart")
 
 The resource intensive `codec.CborHandle` initialization (in the other library) was placed outside the benchmark loop to make sure their library wasn't penalized.
 
+__This library uses less memory__ for encoding and decoding CBOR Web Token (CWT). Test data is from RFC 8392 A.1.
+
+| benchmark       | fxamacker/cbor 2.1.0          |ugorji/go 1.1.7                   |
+|------------------|-------------------------------|----------------------------------|
+| Encode CWT claims| 176 B/op, 2 allocs/op | 1424 B/op, 4 allocs/op
+| Decode CWT claims| 176 B/op, 6 allocs/op | 568 B/op, 6 allocs/op
+
+
 Doing your own comparisons is highly recommended.  Use your most common message sizes and data types.
 
 ## Current Status
-Latest version is v2.0, which has:
+Latest version is v2.x, which has:
 
 * __Stable API__ –  Six codec function signatures will never change.  No breaking API changes for other funcs in same major version.    These two functions are subject to change until the draft RFC is approved by IETF (est. "later in 2020"):
   * CoreDetEncOptions() is subject to change because it uses draft standard not yet approved by IETF.
@@ -75,22 +84,36 @@ Latest version is v2.0, which has:
 
 Recent activity:
 
-* [x] [Release v1.5](https://github.com/fxamacker/cbor/releases) -- Add option to shrink floating-point values to smaller sizes like float16 (if they preserve value).
-* [x] [Release v1.5](https://github.com/fxamacker/cbor/releases) -- Add options for encoding floating-point NaN values: NaNConvertNone, NaNConvert7e00, NaNConvertQuiet, or NaNConvertPreserveSignal.
 * [x] [Release v2.0](https://github.com/fxamacker/cbor/releases) -- Improved API, faster performance, less memory allocs, five options for encoding time and cleaner code. Decode NaN or Infinity time values as if they were NULL (to Go's "zero time".)
 
 __Why v2.0?__:
 
-v1 required breaking API changes to support upcoming new features like CBOR tags.  v2.0 has other benefits like reduced memory and including more functions with signatures identical to `encoding/json`. See [Design Goals](#design-goals) for more.
+v1 required breaking API changes to support upcoming new features like CBOR tags, detection of duplicate map keys, and having more functions with identical signatures to `encoding/json`.
+
+v2.x is faster and more memory efficient than v1. Here's how it compares to another library:
+
+| benchmark        | fxamacker/cbor 2.1.0          |ugorji/go 1.1.7                   |
+|------------------|-------------------------------|----------------------------------|
+| Encode CWT claims| 457 ns/op, 176 B/op, 2 allocs/op | 995 ns/op, 1424 B/op, 4 allocs/op
+| Decode CWT claims| 796 ns/op, 176 B/op, 6 allocs/op | 1105 ns/op, 568 B/op, 6 allocs/op
 
 __Roadmap__:
 
- * v2.1 (est. Feb. 16-17, 2020) 
-   - [x] CBOR tags (major type 6) for encoding and decoding
-   - [x] Decoding optimizations
-   - [ ] Options for duplicate map keys
+* v2.1 (Feb. 17, 2020) 
+   - [x] CBOR tags (major type 6) for encoding and decoding (pushed to master branch)
+   - [x] Decoding optimizations (pushed to master branch). Structs using keyasint tag (like COSE and CWT) is  
+   24-28% faster and 53-61% fewer allocs than v1.5 and v2.0.1.
+   - [x] Options for duplicate map key detection: `DupMapKeyQuiet` (on) and `DupMapKeyEnforcedAPF` (off)
+
+* v2.2
+   - [ ] CBOR BSTR <--> Go byte array. In the meantime, use CBOR BSTR with Go byte slice.
+   - [ ] If time allows, add more CBOR tags as default or optional built-in tags.
 
 ## Installation
+
+Using Go modules is recommended. If Go modules aren't used, modify example_test.go as follows:  
+From `"github.com/fxamacker/cbor/v2"` to `"github.com/fxamacker/cbor"`
+
 ```
 $ GO111MODULE=on go get github.com/fxamacker/cbor/v2
 ```
@@ -105,7 +128,10 @@ import (
 
 ## System Requirements
 
-* Go 1.12 (or newer)
+Using Go modules is recommended but not required. If Go modules aren't used, modify example_test.go as follows.  
+From `"github.com/fxamacker/cbor/v2"` to `"github.com/fxamacker/cbor"`
+
+* Go 1.12 (or newer).
 * amd64, arm64, ppc64le and s390x. Other architectures may also work but they are not tested as frequently. 
 
 <hr>
@@ -144,28 +170,105 @@ Features not in Go's standard library are usually not added.  However, the __`to
 
 ## Features
 
-* In v2, many function signatures are identical to encoding/json, including:  
+### Standard API
+
+Many function signatures are identical to encoding/json, including:  
 `Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, `decoder.Decode`.
-* Support customizable options to create encoding and decoding modes (interfaces) that are safe for concurrent use.  Once created, encoding and decoding modes cannot accidentally change their behavior at runtime.
-* Support "cbor" and "json" keys in Go's struct tags. If both are specified, then "cbor" is used.
+
+`RawMessage` can be used to delay CBOR decoding or precompute CBOR encoding, like `encoding/json`.
+
+Standard interfaces allow user-defined types to have custom CBOR encoding and decoding.  They include:  
+`BinaryMarshaler`, `BinaryUnmarshaler`, `Marshaler`, and `Unmarshaler`
+
+`Marshaler` and `Unmarshaler` interfaces are satisfied by `MarshalCBOR` and `UnmarshalCBOR` functions using same params and return types as Go's MarshalJSON and UnmarshalJSON.
+
+### Struct Tags
+
+Support "cbor" and "json" keys in Go's struct tags. If both are specified, then "cbor" is used.
+
 * `toarray` struct tag allows named struct fields for elements of CBOR arrays.
 * `keyasint` struct tag allows named struct fields for elements of CBOR maps with int keys.
-* Encoder has easy functions that return predefined configurations:  
-  `CanonicalEncOptions`, `CTAP2EncOptions`, `CoreDetEncOptions`, `PreferredUnsortedEncOptions`
-* Integers always encode to the smallest number of bytes.
-* Encoder floating-point options:
-  * ShortestFloatMode: `ShortestFloatNone` or `ShortestFloat16` (IEEE 754 binary16, etc. if value fits).
-  * InfConvertMode: `InfConvertNone` or `InfConvertFloat16`.
-  * NaNConvertMode: `NaNConvertNone`, `NaNConvert7e00`, `NaNConvertQuiet`, or `NaNConvertPreserveSignal`.
-* Encoder sort options: `SortNone`, `SortBytewiseLexical`, `SortCanonical`, `SortCTAP2`, `SortCoreDeterministic`.
-* Encoder time options: `TimeUnix`, `TimeUnixMicro`, `TimeUnixDynamic`, `TimeRFC3339`, `TimeRFC3339Nano`.
-* Support `cbor.RawMessage` which can delay CBOR decoding or precompute CBOR encoding.
-* Support Go's `encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler` interfaces.
-* Support `cbor.Marshaler` and `cbor.Unmarshaler` interfaces to allow user-defined types to have custom CBOR encoding and decoding.
-* Support indefinite length CBOR data (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)).  For decoding, each indefinite length "container" must fit into memory to perform well-formedness checks that prevent exploits. Go's `io.LimitReader` can be used to limit sizes.
+* `omitempty` struct tag excludes empty field values from being encoded.
+
+See [Usage](#usage).
+
+### Predefined Encoding Options
+
+Easy-to-use functions (no params) return preset EncOptions struct:  
+`CanonicalEncOptions`, `CTAP2EncOptions`, `CoreDetEncOptions`, `PreferredUnsortedEncOptions`
+
+### Concurrency Safe Encoding and Decoding Modes
+
+EncMode and DecMode interfaces provide reusable, concurrency safe, and standard API for CBOR encoding and decoding.  They are designed to avoid side-effects when possible so users can spend less time debugging.
+
+EncMode and DecMode are created from options struct and an optional set of tags (CBOR tags).  Once created, EncMode and DecMode cannot accidentally change their behavior at runtime.  
+
+Modes are provided to use either immutable private CBOR tags or mutable shared CBOR tags (like a tag registry in other libraries) -- all modes in this library are safe for concurrent use.  See [API](#api).
+
+### CBOR Tags (New in v2.1)
+
+There are three broad categories of CBOR tags:
+
+* __Default built-in CBOR tags__ currently include tag numbers 0 and 1 (Time).  Additional default built-in tags in future releases may include tag numbers 2 and 3 (Bignum).  
+
+* __Optional built-in CBOR tags__ may be provided in the future via build flags or optional package(s) to help reduce bloat.
+
+* __User-defined CBOR tags__ are easy by using TagSet to associate tag numbers to user-defined Go types.
+
+`TagSet` can be used to associate a user-defined Go type(s) to tag number(s).  It's also used to create EncMode or DecMode. For example, `em := EncOptions{...}.EncModeWithTags(ts)` or `em := EncOptions{...}.EncModeWithSharedTags(ts)`. This allows every standard API exported by em (like `Marshal` and `NewEncoder`) to use the specified tags automatically.
+
+`Tag` and `RawTag` can be used to encode/decode a tag number with a Go value, but `TagSet` is generally recommended.
+
+### Duplicate Map Key Options
+
+There are currently two options:
+
+* `DupMapKeyQuiet` turns off detection of duplicate map keys. It tries to use a "keep fastest" method by choosing either "keep first" or "keep last" depending on the Go data type.
+
+* `DupMapKeyEnforcedAPF` enforces detection and rejection of duplidate map keys. Decoding stops immediately and returns `DupMapKeyError` when the first duplicate key is detected. The error includes the duplicate map key and the index number. 
+
+APF suffix means "Allow Partial Fill" so the destination map or struct can contain some decoded values at the time of error.  Users can respond to the `DupMapKeyError` by discarding the partially filled result if that's required by their protocol.
+
+### Preferred Serialization
+
+Preferred serialization encodes integers and floating-point values using the fewest bytes possible.
+
+* Integers are always encoded using the fewest bytes possible.
+* Floating-point values can optionally encode from float64->float32->float16 when values fit.
+
+### Compact Data Size
+
+The combination of preferred serialization and struct tags (toarray, keyasint, omitempty) allows very compact data size.
+
+### Floating-point Options
+
+Encoding options for floating-points:
+
+  * ShortestFloatMode: `ShortestFloatNone` (default) or `ShortestFloat16` (float64->float32->float16 if value fits).
+  * InfConvertMode: `InfConvertFloat16` (default) or `InfConvertNone`.
+  * NaNConvertMode: `NaNConvert7e00` (default), `NaNConvertNone`, `NaNConvertQuiet`, or `NaNConvertPreserveSignal`.
+
+### Sorting Options
+
+Encoding sort options include `SortNone`, `SortLengthFirst`, and `SortBytewiseLexical`.
+
+Aliases for sort options include `SortCanonical`, `SortCTAP2`, and `SortCoreDeterministic`.
+
+### Time Options
+
+Encoding time options include: `TimeUnix`, `TimeUnixMicro`, `TimeUnixDynamic`, `TimeRFC3339`, `TimeRFC3339Nano`.
+
+### Indefinite Length CBOR Data
+
+Indefinite length CBOR data (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)) is supported for encoding and decoding.
+
+For decoding, each indefinite length CBOR "container" must fit into memory to perform well-formedness checks that prevent exploits. Go's `io.LimitReader` can be used to limit sizes.
+
+### Additional Features
+
 * Decoder always checks for invalid UTF-8 string errors.
 * Decoder always decodes in-place to slices, maps, and structs.
-* Decoder uses case-insensitive field name match when decoding to structs. 
+* Decoder tries case-sensitive first and falls back to case-insensitive field name match when decoding to structs. 
 * Both encoder and decoder correctly handles nil slice, map, pointer, and interface values.
 
 See [milestones](https://github.com/fxamacker/cbor/milestones) for upcoming features.
@@ -175,19 +278,34 @@ See [milestones](https://github.com/fxamacker/cbor/milestones) for upcoming feat
 👉  [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [Install](#installation) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
 
 ## Standards
-This library implements CBOR as specified in [RFC 7049](https://tools.ietf.org/html/rfc7049) with minor [limitations](#limitations).
+This library is a small, fast, safe, and full-featured generic CBOR [(RFC 7049)](https://tools.ietf.org/html/rfc7049) encoder and decoder.  Notable features include:
+
+* CBOR tags (API supports built-in and user-defined) for both encoder and decoder
+* Definite and indefinite length data for both encoder and decoder
+* Encoder option for preferred serialization, including IEEE 754 binary16
+* Encoder option for sorting map keys: unsorted, length-first (Canonical CBOR), and bytewise-lexical (CTAP2 & 7049bis)
+* Encoder option to encode or exclude specific tag numbers
+* Separate encoder options for floating-point shortest form, NaN, and Infinity
+* Decoder option to detect and reject duplicate map keys
+* Decoder option to handle tag validation: ignored, optional, or required
+* Decoder checks for well-formedness and validates data
+* Decoder sanitizes time values of NaN and Infinity as if they were nil values
+* Decoder doesn't crash and avoids resource exhaustion from malformed/malicious CBOR data
+
+Known limitations are noted in the [Limitations section](#limitations). 
 
 Integers always encode to the shortest form that preserves value.  By default, time values are encoded without tags.
 
 Encoding of other data types and map key sort order are determined by encoder options.
 
-|Option                       |Available Settings (defaults in bold, aliases in italics)                                |
+|Option                       |Available Settings (defaults in bold, aliases in italics)                              |
 |-----------------------------|---------------------------------------------------------------------------------------|
 |EncOptions.Sort |__`SortNone`__, `SortLengthFirst`, `SortBytewiseLexical`, _`SortCanonical`_, _`SortCTAP2Canonical`_, _`SortCoreDeterministic`_ |
 |EncOptions.Time |__`TimeUnix`__, `TimeUnixMicro`, `TimeUnixDynamic`, `TimeRFC3339`, `TimeRFC3339Nano` |
 |EncOptions.ShortestFloat |__`ShortestFloatNone`__, `ShortestFloat16` |
 |EncOptions.InfConvert |__`InfConvertFloat16`__, `InfConvertNone` |
 |EncOptions.NaNConvert |__`NaNConvert7e00`__, `NaNConvertNone`, `NaNConvertQuiet`, `NaNConvertPreserveSignal` |
+|EncOptions.TimeTag |__`EncTagNone`__, `EncTagRequired` |
 
 See the end of [API section](#api) for more info about each encoding option.
 
@@ -204,26 +322,41 @@ After well-formedness is verified, basic validity errors are handled as follows:
 
 When decoding well-formed CBOR arrays and maps, decoder saves the first error it encounters and continues with the next item.  Options to handle this differently may be added in the future.
 
-## Limitations
-:tada: CBOR tags (type 6) is implemented and will be in the next release ([milestone v2.1](https://github.com/fxamacker/cbor/milestone/11)).  
-:tada: Options for duplicate map keys will be in the next release ([milestone v2.1](https://github.com/fxamacker/cbor/milestone/11)).
+__Duplicate Map Key Options__
+
+This library provides options for fast detection and rejection of duplicate map keys based on applying a Go-specific data model to CBOR's extended generic data model in order to determine duplicate vs distinct map keys. Detection relies on whether the CBOR map key would be a duplicate "key" when decoded and applied to the user-provided Go map or struct. 
+
+`DupMapKeyQuiet` turns off detection of duplicate map keys. It tries to use a "keep fastest" method by choosing either "keep first" or "keep last" depending on the Go data type.
 
+`DupMapKeyEnforcedAPF` enforces detection and rejection of duplidate map keys. Decoding stops immediately and returns `DupMapKeyError` when the first duplicate key is detected. The error includes the duplicate map key and the index number. 
+
+APF suffix means "Allow Partial Fill" so the destination map or struct can contain some decoded values at the time of error. It is the caller's responsibility to respond to the `DupMapKeyError` by discarding the partially filled result if that's required by their protocol. 
+
+## Limitations
+🎉 CBOR tags feature was pushed to master branch and will officially be released in v2.1.  
+🎉 Duplicate map key option was pushed to master branch and will officially be released in v2.1.
 
 Known limitations:
 
-* Currently, CBOR tag numbers are ignored.  Decoder simply decodes tag content. (Coming soon in v2.1)
-* Currently, duplicate map keys are not checked during decoding. (Coming soon in v2.1)
-* Nested levels for CBOR arrays, maps, and tags are limited to 32 to quickly reject potentially malicious data.  This limit will be reconsidered upon request.
+* Nested levels for CBOR arrays, maps, and tags are intentionally limited to 32 to quickly reject potentially malicious data.  This limit may be turned into a user-configurable setting in a future release.
 * 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`.
+However, this may be resolved in a future release by adding support for `big.Int`. Until then,
+users can either use custom decoding allowed by the API or use user-defined CBOR tags to handle these values.
 * CBOR `Undefined` (0xf7) value decodes to Go's `nil` value.  CBOR `Null` (0xf6) more closely matches Go's `nil`.
+* Decoding CBOR map into Go struct has more limitations on key's type than decoding into Go map.  This is because map keys need to identify Go struct fields (UTF-8 string and also integer when using "keyasint" struct tag).
+* CBOR map keys with data types not supported by Go for map keys are ignored and the key/value pair is discarded and an error is returned after continuing to decode remaining items.  
+* When using io.Reader interface to read very large or indefinite length CBOR data, Go's `io.LimitReader` should be used to limit size. This library doesn't set a limit because `io.LimitReader` to makes it easy for the caller.
 
-Like Go's `encoding/json`, data validation checks the entire message to prevent partially filled (corrupted) data. This library also prevents crashes and resource exhaustion attacks from malicious CBOR data. Use Go's `io.LimitReader` when decoding very large data to limit size.
+## API
+In v2.0+, many function signatures are identical to Go's encoding/json, such as:  
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
 
-## API 
-In v2, many function signatures are identical to encoding/json, including:  
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, `decoder.Decode`.
+Also, interfaces identical or comparable to Go's encoding, encoding/json, or encoding/gob include:  
+`Marshaler`, `UnMarshaler`, `BinaryMarshaler`, and `BinaryUnmarshaler`.
+
+Like `encoding/json`, `RawMessage` can be used to delay CBOR decoding or precompute CBOR encoding.
 
-"Mode" in this API means definite way of encoding or decoding.
+"Mode" in this API means definite way of encoding or decoding -- it links the standard API to CBOR options and CBOR tags.
 
 EncMode and DecMode are interfaces created from EncOptions or DecOptions structs.  
 For example, `em := cbor.EncOptions{...}.EncMode()` or `em := cbor.CanonicalEncOptions().EncMode()`.
@@ -287,11 +420,25 @@ func (opts DecOptions) DecMode() (DecMode, error)
 
 The `keyasint` and `toarray` struct tags are worth knowing.  They can reduce programming effort, improve system performance, and reduce the size of serialized data.  
 
-See [API docs (godoc.org)](https://godoc.org/github.com/fxamacker/cbor) for more details and more functions.  See [Usage section](#usage) for usage and code examples.  Options for the encoder are listed here.
+See [API docs (godoc.org)](https://godoc.org/github.com/fxamacker/cbor) for more details and more functions.  See [Usage section](#usage) for usage and code examples.  Options for the decoding and encoding are listed here.
 
-__Integers always encode to the shortest form that preserves value__.  Encoding of other data types and map key sort order are determined by encoding options.
+### Decoding Options
+
+|DecOptions.TimeTag           |Description                                                                            |
+|-----------------------------|---------------------------------------------------------------------------------------|
+|DecTagIgnored (default)      |Tag numbers are ignored (if present) for time values.|
+|DecTagOptional               |Tag numbers are only checked for validity if present for time values.|
+|DecTagRequired               |Tag numbers must be provided for time values except for CBOR Null and CBOR Undefined.|
+
+DecTagOptional and DecTagRequired will check if tag numbers are 0 and 1 (if present) and treat other tag numbers as an error for time values.
+
+__Undefined Time Values__
 
-__Encoding Options__:
+CBOR Null and CBOR Undefined are silently treated as Go's zero time instant.  Go's `time` package provides `IsZero` function, which reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC. 
+
+### Encoding Options
+
+__Integers always encode to the shortest form that preserves value__.  Encoding of other data types and map key sort order are determined by encoding options.
 
 Encoder has options that can be set individually. These functions are provided to create and return a modifiable EncOptions struct with predefined settings.
 
@@ -322,7 +469,18 @@ Encoder has options that can be set individually. These functions are provided t
 |TimeRFC3339 |(seconds) Encode as RFC 3339 formatted string.|
 |TimeRFC3339Nano |(nanoseconds) Encode as RFC3339 formatted string.|
 
-By default, time values are encoded without tags.  Option to override this will be added in v2.1.
+|EncOptions.TimeTag           |Description                                                                            |
+|-----------------------------|---------------------------------------------------------------------------------------|
+|EncTagNone (default)         |Tag number will not be encoded for time values.|
+|EncTagRequired               |Tag number (0 or 1) will be encoded unless time value is undefined/zero-instant.|
+
+__Undefined Time Values__
+
+By default, undefined (zero instant) time values will encode as CBOR Null without tag number for both EncTagNone and EncTagRequired.  Although CBOR Undefined might be technically more correct for EncTagRequired, CBOR Undefined might not be supported by other generic decoders and it isn't supported by JSON.
+
+Go's `time` package provides `IsZero` function, which reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC. 
+
+__Floating-Point Options__
 
 Encoder has 3 types of options for floating-point data: ShortestFloatMode, InfConvertMode, and NaNConvertMode.
 
@@ -393,7 +551,7 @@ The `keyasint`, `toarray`, and `omitempty` struct tags make it easy to use compa
 
 <hr>
 
-[![CBOR API](https://github.com/fxamacker/images/raw/master/cbor/v2.0.0/cbor_easy_api.png)](#usage)
+[![CBOR API](https://github.com/fxamacker/images/raw/master/cbor/v2.1.0/cbor_api_struct_tags.png)](#usage)
 
 <hr>
 
@@ -521,9 +679,13 @@ __Coverage-guided fuzzing__ must pass 250+ million execs before tagging a releas
 Over 1,100 files (corpus) are used for fuzzing because it includes fuzz-generated corpus.
 
 ## Versions and API Changes
-This project uses [Semantic Versioning](https://semver.org), so the API is always backwards compatible unless the major version number changes.  Newly added API documented as "subject to change" are excluded from this rule.
+This project uses [Semantic Versioning](https://semver.org), so the API is always backwards compatible unless the major version number changes.  
+
+These functions have signatures identical to encoding/json and they will likely never change even after major new releases:  `Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
+
+Newly added API documented as "subject to change" are excluded from SemVer.
 
-Six codec functions have signatures identical to encoding/json and they will never change even after major new releases:  `Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
+Newly added API in the master branch that has never been release tagged are excluded from SemVer.
 
 ## Code of Conduct 
 This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md).  Contact [faye.github@gmail.com](mailto:faye.github@gmail.com) with any questions or comments.
@@ -545,7 +707,7 @@ Please read the license for additional disclaimers and terms.
 
 __Making this library better__  
 
-* Montgomery Edwards⁴⁴⁸ for contributing [float16 conversion code](https://github.com/x448/float16), updating the docs, creating comparison charts & slideshow, filing issues, nudging me to ask for feedback from users, and helping with design of v2.0 and v2.1 API.
+* Montgomery Edwards⁴⁴⁸ for contributing float16 conversion code, updating the docs, creating comparison charts & slideshow, filing issues, nudging me to ask for feedback from users, and helping with design of v2.0 and v2.1 API.
 * Stefan Tatschner for using this library in [sep](https://git.sr.ht/~rumpelsepp/sep), being the 1st to discover my CBOR library, requesting time.Time in issue #1, and submitting this library in a [PR to cbor.io](https://github.com/cbor/cbor.github.io/pull/56) on Aug 12, 2019.
 * Yawning Angel for using this library to [oasis-core](https://github.com/oasislabs/oasis-core), and requesting BinaryMarshaler in issue #5.
 * Jernej Kos for requesting RawMessage in issue #11 and offering feedback on v2.1 API for CBOR tags.
@@ -555,8 +717,8 @@ __Making this library better__
 __Help clarifying CBOR RFC 7049 or 7049bis__
 
 * Carsten Bormann for RFC 7049 (CBOR), his fast confirmation to my RFC 7049 errata, approving my pull request to 7049bis, and his patience when I misread a line in 7049bis.
+* Laurence Lundblade for his help on the IETF mailing list for 7049bis and for pointing out on a CBORbis issue that CBOR Undefined might be problematic translating to JSON.
 * Jeffrey Yasskin for his help on the IETF mailing list for 7049bis.
-* Laurence Lundblade for his help on the IETF mailing list for 7049bis.
 
 __Words of encouragement and support__
 
diff --git a/decode.go b/decode.go
index 641fe94..9ec2902 100644
--- a/decode.go
+++ b/decode.go
@@ -7,6 +7,7 @@ import (
 	"encoding"
 	"encoding/binary"
 	"errors"
+	"fmt"
 	"io"
 	"math"
 	"reflect"
@@ -142,8 +143,44 @@ func (e *UnmarshalTypeError) Error() string {
 	return s
 }
 
+// DupMapKeyError describes detected duplicate map key in CBOR map.
+type DupMapKeyError struct {
+	Key   interface{}
+	Index int
+}
+
+func (e *DupMapKeyError) Error() string {
+	return fmt.Sprintf("cbor: found duplicate map key \"%v\" at map element index %d", e.Key, e.Index)
+}
+
+// DupMapKeyMode specifies how to enforce duplicate map key.
+type DupMapKeyMode int
+
+const (
+	// DupMapKeyQuiet doesn't enforce duplicate map key. Decoder quietly (no error)
+	// uses faster of "keep first" or "keep last" depending on Go data type and other factors.
+	DupMapKeyQuiet DupMapKeyMode = iota
+
+	// DupMapKeyEnforcedAPF enforces detection and rejection of duplicate map keys.
+	// APF means "Allow Partial Fill" and the destination map or struct can be partially filled.
+	// If a duplicate map key is detected, DupMapKeyError is returned without further decoding
+	// of the map. It's the caller's responsibility to respond to DupMapKeyError by
+	// discarding the partially filled result if their protocol requires it.
+	// WARNING: using DupMapKeyEnforcedAPF will decrease performance and increase memory use.
+	DupMapKeyEnforcedAPF
+
+	maxDupMapKeyMode
+)
+
+func (dmkm DupMapKeyMode) valid() bool {
+	return dmkm < maxDupMapKeyMode
+}
+
 // DecOptions specifies decoding options.
 type DecOptions struct {
+	// DupMapKey specifies whether to enforce duplicate map key.
+	DupMapKey DupMapKeyMode
+
 	// TimeTag specifies whether to check validity of time.Time (e.g. valid tag number and tag content type).
 	// For now, valid tag number means 0 or 1 as specified in RFC 7049 if the Go type is time.Time.
 	TimeTag DecTagMode
@@ -197,10 +234,13 @@ func (opts DecOptions) DecModeWithSharedTags(tags TagSet) (DecMode, error) {
 }
 
 func (opts DecOptions) decMode() (*decMode, error) {
+	if !opts.DupMapKey.valid() {
+		return nil, errors.New("cbor: invalid DupMapKey " + strconv.Itoa(int(opts.DupMapKey)))
+	}
 	if !opts.TimeTag.valid() {
 		return nil, errors.New("cbor: invalid TimeTag " + strconv.Itoa(int(opts.TimeTag)))
 	}
-	dm := decMode{timeTag: opts.TimeTag}
+	dm := decMode{dupMapKey: opts.DupMapKey, timeTag: opts.TimeTag}
 	return &dm, nil
 }
 
@@ -212,15 +252,16 @@ type DecMode interface {
 }
 
 type decMode struct {
-	tags    tagProvider
-	timeTag DecTagMode
+	tags      tagProvider
+	dupMapKey DupMapKeyMode
+	timeTag   DecTagMode
 }
 
 var defaultDecMode = &decMode{}
 
 // DecOptions returns user specified options used to create this DecMode.
 func (dm *decMode) DecOptions() DecOptions {
-	return DecOptions{TimeTag: dm.timeTag}
+	return DecOptions{DupMapKey: dm.dupMapKey, TimeTag: dm.timeTag}
 }
 
 // Unmarshal parses the CBOR-encoded data and stores the result in the value
@@ -800,7 +841,9 @@ func (d *decodeState) parseMap() (map[interface{}]interface{}, error) {
 	m := make(map[interface{}]interface{})
 	var k, e interface{}
 	var err, lastErr error
+	keyCount := 0
 	for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
+		// Parse CBOR map key.
 		if k, lastErr = d.parse(); lastErr != nil {
 			if err == nil {
 				err = lastErr
@@ -808,6 +851,8 @@ func (d *decodeState) parseMap() (map[interface{}]interface{}, error) {
 			d.skip()
 			continue
 		}
+
+		// Detect if CBOR map key can be used as Go map key.
 		kkind := reflect.ValueOf(k).Kind()
 		if tag, ok := k.(Tag); ok {
 			kkind = tag.contentKind()
@@ -819,18 +864,39 @@ func (d *decodeState) parseMap() (map[interface{}]interface{}, error) {
 			d.skip()
 			continue
 		}
+
+		// Parse CBOR map value.
 		if e, lastErr = d.parse(); lastErr != nil {
 			if err == nil {
 				err = lastErr
 			}
 			continue
 		}
+
+		// Add key-value pair to Go map.
 		m[k] = e
+
+		// Detect duplicate map key.
+		if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+			newKeyCount := len(m)
+			if newKeyCount == keyCount {
+				m[k] = nil
+				err = &DupMapKeyError{k, i}
+				i++
+				// skip the rest of the map
+				for ; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
+					d.skip() // Skip map key
+					d.skip() // Skip map value
+				}
+				return m, err
+			}
+			keyCount = newKeyCount
+		}
 	}
 	return m, err
 }
 
-func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error {
+func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
 	_, ai, val := d.getHead()
 	hasSize := (ai != 31)
 	count := int(val)
@@ -846,7 +912,19 @@ func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error {
 	var keyValue, eleValue, zeroKeyValue, zeroEleValue reflect.Value
 	keyIsInterfaceType := keyType == typeIntf // If key type is interface{}, need to check if key value is hashable.
 	var err, lastErr error
+	keyCount := v.Len()
+	var existingKeys map[interface{}]bool // Store existing map keys, used for detecting duplicate map key.
+	if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+		existingKeys = make(map[interface{}]bool, keyCount)
+		if keyCount > 0 {
+			vKeys := v.MapKeys()
+			for i := 0; i < len(vKeys); i++ {
+				existingKeys[vKeys[i].Interface()] = true
+			}
+		}
+	}
 	for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
+		// Parse CBOR map key.
 		if !keyValue.IsValid() {
 			keyValue = reflect.New(keyType).Elem()
 		} else if !reuseKey {
@@ -862,6 +940,8 @@ func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error {
 			d.skip()
 			continue
 		}
+
+		// Detect if CBOR map key can be used as Go map key.
 		if keyIsInterfaceType {
 			kkind := keyValue.Elem().Kind()
 			if keyValue.Elem().IsValid() {
@@ -878,6 +958,7 @@ func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error {
 			}
 		}
 
+		// Parse CBOR map value.
 		if !eleValue.IsValid() {
 			eleValue = reflect.New(eleType).Elem()
 		} else if !reuseEle {
@@ -893,7 +974,29 @@ func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error {
 			continue
 		}
 
+		// Add key-value pair to Go map.
 		v.SetMapIndex(keyValue, eleValue)
+
+		// Detect duplicate map key.
+		if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+			newKeyCount := v.Len()
+			if newKeyCount == keyCount {
+				kvi := keyValue.Interface()
+				if !existingKeys[kvi] {
+					v.SetMapIndex(keyValue, reflect.New(eleType).Elem())
+					err = &DupMapKeyError{kvi, i}
+					i++
+					// skip the rest of the map
+					for ; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
+						d.skip() // skip map key
+						d.skip() // skip map value
+					}
+					return err
+				}
+				delete(existingKeys, kvi)
+			}
+			keyCount = newKeyCount
+		}
 	}
 	return err
 }
@@ -978,8 +1081,14 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 	hasSize := (ai != 31)
 	count := int(val)
 	var err, lastErr error
-	for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
+	keyCount := 0
+	var mapKeys map[interface{}]struct{} // Store map keys, used for detecting duplicate map key.
+	if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+		mapKeys = make(map[interface{}]struct{}, len(structType.fields))
+	}
+	for j := 0; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
 		var f *field
+		var k interface{} // Used by duplicate map key detection
 
 		t := d.nextCBORType()
 		if t == cborTypeTextString {
@@ -994,8 +1103,8 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 			}
 
 			keyLen := len(keyBytes)
+			// Find field with exact match
 			for i := 0; i < len(structType.fields); i++ {
-				// Find field with exact match
 				fld := structType.fields[i]
 				if !foundFldIdx[i] && len(fld.name) == keyLen && fld.name == string(keyBytes) {
 					f = fld
@@ -1003,10 +1112,10 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 					break
 				}
 			}
+			// Find field with case-insensitive match
 			if f == nil {
 				keyString := string(keyBytes)
 				for i := 0; i < len(structType.fields); i++ {
-					// Find field with case-insensitive match
 					fld := structType.fields[i]
 					if !foundFldIdx[i] && len(fld.name) == keyLen && strings.EqualFold(fld.name, keyString) {
 						f = fld
@@ -1015,6 +1124,10 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 					}
 				}
 			}
+
+			if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+				k = string(keyBytes)
+			}
 		} else if t <= cborTypeNegativeInt { // uint/int
 			var nameAsInt int64
 
@@ -1037,6 +1150,7 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 				nameAsInt = int64(-1) ^ int64(val)
 			}
 
+			// Find field
 			for i := 0; i < len(structType.fields); i++ {
 				fld := structType.fields[i]
 				if !foundFldIdx[i] && fld.keyAsInt && fld.nameAsInt == nameAsInt {
@@ -1045,6 +1159,10 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 					break
 				}
 			}
+
+			if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+				k = nameAsInt
+			}
 		} else {
 			if err == nil {
 				err = &UnmarshalTypeError{
@@ -1053,13 +1171,46 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 					errMsg: "map key is of type " + t.String() + " and cannot be used to match struct field name",
 				}
 			}
-			d.skip() // skip key
-			d.skip() // skip value
-			continue
+			if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+				// parse key
+				k, lastErr = d.parse()
+				if lastErr != nil {
+					d.skip() // skip value
+					continue
+				}
+				// Detect if CBOR map key can be used as Go map key.
+				kkind := reflect.ValueOf(k).Kind()
+				if tag, ok := k.(Tag); ok {
+					kkind = tag.contentKind()
+				}
+				if !isHashableKind(kkind) {
+					d.skip() // skip value
+					continue
+				}
+			} else {
+				d.skip() // skip key
+			}
+		}
+
+		if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
+			mapKeys[k] = struct{}{}
+			newKeyCount := len(mapKeys)
+			if newKeyCount == keyCount {
+				err = &DupMapKeyError{k, j}
+				d.skip() // skip value
+				j++
+				// skip the rest of the map
+				for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
+					d.skip()
+					d.skip()
+				}
+				return err
+			}
+			keyCount = newKeyCount
 		}
 
 		if f == nil {
-			d.skip()
+			d.skip() // Skip value
 			continue
 		}
 		// reflect.Value.FieldByIndex() panics at nil pointer to unexported
@@ -1091,7 +1242,7 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
 // validRegisteredTagNums assumes next CBOR data type is tag.  It scans all tag numbers, and stops at tag content.
 func (d *decodeState) validRegisteredTagNums(t reflect.Type, registeredTagNums []uint64) error {
 	// Scan until next cbor data is tag content.
-	tagNums := make([]uint64, 0, 1)
+	tagNums := make([]uint64, 0, 2)
 	for d.nextCBORType() == cborTypeTag {
 		_, _, val := d.getHead()
 		tagNums = append(tagNums, val)
diff --git a/encode.go b/encode.go
index 3b712c7..50621d6 100644
--- a/encode.go
+++ b/encode.go
@@ -372,7 +372,7 @@ func (opts EncOptions) EncModeWithTags(tags TagSet) (EncMode, error) {
 	syncTags := tags.(*syncTagSet)
 	syncTags.RLock()
 	for contentType, tag := range syncTags.t {
-		if tag.opts.EncTag != EncTagIgnored {
+		if tag.opts.EncTag != EncTagNone {
 			ts[contentType] = tag
 		}
 	}
@@ -523,7 +523,6 @@ var (
 	cborFalse            = []byte{0xf4}
 	cborTrue             = []byte{0xf5}
 	cborNil              = []byte{0xf6}
-	cborUndefined        = []byte{0xf7}
 	cborNaN              = []byte{0xf9, 0x7e, 0x00}
 	cborPositiveInfinity = []byte{0xf9, 0x7c, 0x00}
 	cborNegativeInfinity = []byte{0xf9, 0xfc, 0x00}
@@ -1042,11 +1041,7 @@ func encodeIntf(e *encodeState, em *encMode, v reflect.Value) error {
 func encodeTime(e *encodeState, em *encMode, v reflect.Value) error {
 	t := v.Interface().(time.Time)
 	if t.IsZero() {
-		if em.timeTag == EncTagIgnored {
-			e.Write(cborNil)
-			return nil
-		}
-		e.Write(cborUndefined)
+		e.Write(cborNil) // Even if tag is required, encode as CBOR null.
 		return nil
 	}
 	if em.timeTag == EncTagRequired {
diff --git a/tag.go b/tag.go
index 18bba47..a812182 100644
--- a/tag.go
+++ b/tag.go
@@ -91,8 +91,8 @@ func (dtm DecTagMode) valid() bool {
 type EncTagMode int
 
 const (
-	// EncTagIgnored makes encoder not encode tag number.
-	EncTagIgnored EncTagMode = iota
+	// EncTagNone makes encoder not encode tag number.
+	EncTagNone EncTagMode = iota
 
 	// EncTagRequired makes encoder encode tag number.
 	EncTagRequired
@@ -190,8 +190,8 @@ func (t *syncTagSet) get(typ reflect.Type) *tagItem {
 }
 
 func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) (*tagItem, error) {
-	if opts.DecTag == DecTagIgnored && opts.EncTag == EncTagIgnored {
-		return nil, errors.New("cbor: cannot add tag with DecTagIgnored and EncTagIgnored options to TagSet")
+	if opts.DecTag == DecTagIgnored && opts.EncTag == EncTagNone {
+		return nil, errors.New("cbor: cannot add tag with DecTagIgnored and EncTagNone options to TagSet")
 	}
 	if contentType.PkgPath() == "" || contentType.Kind() == reflect.Interface {
 		return nil, errors.New("cbor: can only add named types to TagSet, got " + contentType.String())
@@ -208,8 +208,8 @@ func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum
 	if num == 0 || num == 1 {
 		return nil, errors.New("cbor: cannot add tag number 0 or 1 to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead")
 	}
-	if reflect.PtrTo(contentType).Implements(typeMarshaler) && opts.EncTag != EncTagIgnored {
-		return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagIgnored")
+	if reflect.PtrTo(contentType).Implements(typeMarshaler) && opts.EncTag != EncTagNone {
+		return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone")
 	}
 	if reflect.PtrTo(contentType).Implements(typeUnmarshaler) && opts.DecTag != DecTagIgnored {
 		return nil, errors.New("cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored")


Test patch:
diff --git a/bench_test.go b/bench_test.go
index 564dd89..cd36ee4 100644
--- a/bench_test.go
+++ b/bench_test.go
@@ -22,6 +22,20 @@ type claims struct {
 	Cti []byte `cbor:"7,keyasint"`
 }
 
+type coseHeader struct {
+	Alg int    `cbor:"1,keyasint,omitempty"`
+	Kid []byte `cbor:"4,keyasint,omitempty"`
+	IV  []byte `cbor:"5,keyasint,omitempty"`
+}
+
+type macedCOSE struct {
+	_           struct{} `cbor:",toarray"`
+	Protected   []byte
+	Unprotected coseHeader
+	Payload     []byte
+	Tag         []byte
+}
+
 type coseKey struct {
 	Kty       int        `cbor:"1,keyasint,omitempty"`
 	Kid       []byte     `cbor:"2,keyasint,omitempty"`
@@ -494,6 +508,18 @@ func BenchmarkUnmarshalCWTClaims(b *testing.B) {
 	}
 }
 
+func BenchmarkUnmarshalCWTClaimsWithDupMapKeyOpt(b *testing.B) {
+	// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
+	cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	for i := 0; i < b.N; i++ {
+		var v claims
+		if err := dm.Unmarshal(cborData, &v); err != nil {
+			b.Fatal("Unmarshal:", err)
+		}
+	}
+}
+
 func BenchmarkMarshalCWTClaims(b *testing.B) {
 	// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
 	cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
@@ -572,3 +598,77 @@ func BenchmarkMarshalWebAuthn(b *testing.B) {
 		}
 	}
 }
+
+func BenchmarkUnmarshalCOSEMAC(b *testing.B) {
+	// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
+	cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
+
+	for i := 0; i < b.N; i++ {
+		var v macedCOSE
+		if err := Unmarshal(cborData, &v); err != nil {
+			b.Fatal("Unmarshal:", err)
+		}
+	}
+}
+
+func BenchmarkUnmarshalCOSEMACWithTag(b *testing.B) {
+	// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
+	cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
+
+	// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
+	tags := NewTagSet()
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(macedCOSE{}), 61, 17); err != nil {
+		b.Fatal("TagSet.Add:", err)
+	}
+
+	// Create DecMode with tags
+	dm, _ := DecOptions{}.DecModeWithTags(tags)
+
+	for i := 0; i < b.N; i++ {
+		var v macedCOSE
+		if err := dm.Unmarshal(cborData, &v); err != nil {
+			b.Fatal("Unmarshal:", err)
+		}
+	}
+}
+func BenchmarkMarshalCOSEMAC(b *testing.B) {
+	// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
+	cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
+
+	var v macedCOSE
+	if err := Unmarshal(cborData, &v); err != nil {
+		b.Fatal("Unmarshal():", err)
+	}
+
+	for i := 0; i < b.N; i++ {
+		if _, err := Marshal(v); err != nil {
+			b.Fatal("Marshal():", v, err)
+		}
+	}
+}
+
+func BenchmarkMarshalCOSEMACWithTag(b *testing.B) {
+	// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
+	cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
+
+	// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
+	tags := NewTagSet()
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(macedCOSE{}), 61, 17); err != nil {
+		b.Fatal("TagSet.Add:", err)
+	}
+
+	// Create EncMode with tags.
+	dm, _ := DecOptions{}.DecModeWithTags(tags)
+	em, _ := EncOptions{}.EncModeWithTags(tags)
+
+	var v macedCOSE
+	if err := dm.Unmarshal(cborData, &v); err != nil {
+		b.Fatal("Unmarshal():", err)
+	}
+
+	for i := 0; i < b.N; i++ {
+		if _, err := em.Marshal(v); err != nil {
+			b.Fatal("Marshal():", v, err)
+		}
+	}
+}
diff --git a/decode_test.go b/decode_test.go
index ec5fc01..cc4a7ab 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -9,6 +9,7 @@ import (
 	"encoding/hex"
 	"errors"
 	"fmt"
+	"io"
 	"math"
 	"reflect"
 	"strings"
@@ -2852,26 +2853,8 @@ func TestUnmarshalToNotNilInterface(t *testing.T) {
 	}
 }
 
-func TestDuplicateMapKeys(t *testing.T) {
-	cborData := hexDecode("a6616161416162614261636143616461446165614561636146") // map with duplicate key "c": {"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "c": "F"}
-	wantV := map[interface{}]interface{}{"a": "A", "b": "B", "c": "F", "d": "D", "e": "E"}
-	wantM := map[string]string{"a": "A", "b": "B", "c": "F", "d": "D", "e": "E"}
-	var v interface{}
-	if err := Unmarshal(cborData, &v); err != nil {
-		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
-	} else if !reflect.DeepEqual(v, wantV) {
-		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
-	}
-	var m map[string]string
-	if err := Unmarshal(cborData, &m); err != nil {
-		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
-	} else if !reflect.DeepEqual(m, wantM) {
-		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, m, m, wantM, wantM)
-	}
-}
-
 func TestDecOptions(t *testing.T) {
-	opts1 := DecOptions{TimeTag: DecTagRequired}
+	opts1 := DecOptions{TimeTag: DecTagRequired, DupMapKey: DupMapKeyEnforcedAPF}
 	dm, err := opts1.DecMode()
 	if err != nil {
 		t.Errorf("DecMode() returned an error %v", err)
@@ -2920,6 +2903,16 @@ func TestDecModeInvalidTimeTag(t *testing.T) {
 	}
 }
 
+func TestDecModeInvalidDuplicateMapKey(t *testing.T) {
+	wantErrorMsg := "cbor: invalid DupMapKey 101"
+	_, err := DecOptions{DupMapKey: 101}.DecMode()
+	if err == nil {
+		t.Errorf("DecMode() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("DecMode() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+}
+
 func TestUnmarshalStructKeyAsIntNumError(t *testing.T) {
 	type T1 struct {
 		F1 int `cbor:"a,keyasint"`
@@ -2958,3 +2951,925 @@ func TestUnmarshalStructKeyAsIntNumError(t *testing.T) {
 		})
 	}
 }
+
+func TestUnmarshalEmptyMapWithDupMapKeyOpt(t *testing.T) {
+	testCases := []struct {
+		name     string
+		cborData []byte
+		wantV    interface{}
+	}{
+		{
+			name:     "empty map",
+			cborData: hexDecode("a0"),
+			wantV:    map[interface{}]interface{}{},
+		},
+		{
+			name:     "indefinite empty map",
+			cborData: hexDecode("bfff"),
+			wantV:    map[interface{}]interface{}{},
+		},
+	}
+
+	dm, err := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	if err != nil {
+		t.Errorf("DecMode() returned error %v", err)
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			var v interface{}
+			if err := dm.Unmarshal(tc.cborData, &v); err != nil {
+				t.Errorf("Unmarshal(0x%x) returned error %v", tc.cborData, err)
+			}
+			if !reflect.DeepEqual(v, tc.wantV) {
+				t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", tc.cborData, v, v, tc.wantV, tc.wantV)
+			}
+		})
+	}
+}
+
+func TestUnmarshalDupMapKeyToEmptyInterface(t *testing.T) {
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	// Duplicate key overwrites previous value (default).
+	wantV := map[interface{}]interface{}{"a": "F", "b": "B", "c": "C", "d": "D", "e": "E"}
+	var v interface{}
+	if err := Unmarshal(cborData, &v); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
+	}
+	if !reflect.DeepEqual(v, wantV) {
+		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
+	}
+
+	// Duplicate key triggers error.
+	wantV = map[interface{}]interface{}{"a": nil, "b": "B", "c": "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var v2 interface{}
+	if err := dm.Unmarshal(cborData, &v2); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(v2, wantV) {
+		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v2, v2, wantV, wantV)
+	}
+}
+
+func TestStreamDupMapKeyToEmptyInterface(t *testing.T) {
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // map with duplicate key "c": {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	// Duplicate key overwrites previous value (default).
+	wantV := map[interface{}]interface{}{"a": "F", "b": "B", "c": "C", "d": "D", "e": "E"}
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var v1 interface{}
+		if err := dec.Decode(&v1); err != nil {
+			t.Errorf("Decode() returned error %v", err)
+		}
+		if !reflect.DeepEqual(v1, wantV) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", v1, v1, wantV, wantV)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantV = map[interface{}]interface{}{"a": nil, "b": "B", "c": "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var v2 interface{}
+		if err := dec.Decode(&v2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(v2, wantV) {
+			t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v2, v2, wantV, wantV)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+func TestUnmarshalDupMapKeyToEmptyMap(t *testing.T) {
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	// Duplicate key overwrites previous value (default).
+	wantM := map[string]string{"a": "F", "b": "B", "c": "C", "d": "D", "e": "E"}
+	var m map[string]string
+	if err := Unmarshal(cborData, &m); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
+	}
+	if !reflect.DeepEqual(m, wantM) {
+		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, m, m, wantM, wantM)
+	}
+
+	// Duplicate key triggers error.
+	wantM = map[string]string{"a": "", "b": "B", "c": "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var m2 map[string]string
+	if err := dm.Unmarshal(cborData, &m2); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(m2, wantM) {
+		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, m2, m2, wantM, wantM)
+	}
+}
+
+func TestStreamDupMapKeyToEmptyMap(t *testing.T) {
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	// Duplicate key overwrites previous value (default).
+	wantM := map[string]string{"a": "F", "b": "B", "c": "C", "d": "D", "e": "E"}
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var m1 map[string]string
+		if err := dec.Decode(&m1); err != nil {
+			t.Errorf("Decode() returned error %v", err)
+		}
+		if !reflect.DeepEqual(m1, wantM) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", m1, m1, wantM, wantM)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantM = map[string]string{"a": "", "b": "B", "c": "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var m2 map[string]string
+		if err := dec.Decode(&m2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(m2, wantM) {
+			t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, m2, m2, wantM, wantM)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+func TestUnmarshalDupMapKeyToNotEmptyMap(t *testing.T) {
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	// Duplicate key overwrites previous value (default).
+	m := map[string]string{"a": "Z", "b": "Z", "c": "Z", "d": "Z", "e": "Z", "f": "Z"}
+	wantM := map[string]string{"a": "F", "b": "B", "c": "C", "d": "D", "e": "E", "f": "Z"}
+	if err := Unmarshal(cborData, &m); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
+	}
+	if !reflect.DeepEqual(m, wantM) {
+		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, m, m, wantM, wantM)
+	}
+
+	// Duplicate key triggers error.
+	m2 := map[string]string{"a": "Z", "b": "Z", "c": "Z", "d": "Z", "e": "Z", "f": "Z"}
+	wantM = map[string]string{"a": "", "b": "B", "c": "C", "d": "Z", "e": "Z", "f": "Z"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	if err := dm.Unmarshal(cborData, &m2); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(m2, wantM) {
+		t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, m2, m2, wantM, wantM)
+	}
+}
+
+func TestStreamDupMapKeyToNotEmptyMap(t *testing.T) {
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	// Duplicate key overwrites previous value (default).
+	wantM := map[string]string{"a": "F", "b": "B", "c": "C", "d": "D", "e": "E", "f": "Z"}
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		m1 := map[string]string{"a": "Z", "b": "Z", "c": "Z", "d": "Z", "e": "Z", "f": "Z"}
+		if err := dec.Decode(&m1); err != nil {
+			t.Errorf("Decode() returned error %v", err)
+		}
+		if !reflect.DeepEqual(m1, wantM) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", m1, m1, wantM, wantM)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantM = map[string]string{"a": "", "b": "B", "c": "C", "d": "Z", "e": "Z", "f": "Z"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		m2 := map[string]string{"a": "Z", "b": "Z", "c": "Z", "d": "Z", "e": "Z", "f": "Z"}
+		if err := dec.Decode(&m2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(m2, wantM) {
+			t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, m2, m2, wantM, wantM)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStruct(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	// Duplicate key doesn't overwrite previous value (default).
+	wantS := s{A: "A", B: "B", C: "C", D: "D", E: "E"}
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error.
+	wantS = s{A: "A", B: "B", C: "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestStreamDupMapKeyToStruct(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	// Duplicate key overwrites previous value (default).
+	wantS := s{A: "A", B: "B", C: "C", D: "D", E: "E"}
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s1 s
+		if err := dec.Decode(&s1); err != nil {
+			t.Errorf("Decode() returned error %v", err)
+		}
+		if !reflect.DeepEqual(s1, wantS) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", s1, s1, wantS, wantS)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantS = s{A: "A", B: "B", C: "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s2 s
+		if err := dec.Decode(&s2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(s2, wantS) {
+			t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+// dupl map key is a struct field
+func TestUnmarshalDupMapKeyToStructKeyAsInt(t *testing.T) {
+	type s struct {
+		A int `cbor:"1,keyasint"`
+		B int `cbor:"3,keyasint"`
+		C int `cbor:"5,keyasint"`
+	}
+	cborData := hexDecode("a40102030401030506") // {1:2, 3:4, 1:3, 5:6}
+
+	// Duplicate key doesn't overwrite previous value (default).
+	wantS := s{A: 2, B: 4, C: 6}
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error.
+	wantS = s{A: 2, B: 4}
+	wantErrorMsg := "cbor: found duplicate map key \"1\" at map element index 2"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestStreamDupMapKeyToStructKeyAsInt(t *testing.T) {
+	type s struct {
+		A int `cbor:"1,keyasint"`
+		B int `cbor:"3,keyasint"`
+		C int `cbor:"5,keyasint"`
+	}
+	cborData := hexDecode("a40102030401030506") // {1:2, 3:4, 1:3, 5:6}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	// Duplicate key overwrites previous value (default).
+	wantS := s{A: 2, B: 4, C: 6}
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s1 s
+		if err := dec.Decode(&s1); err != nil {
+			t.Errorf("Decode() returned error %v", err)
+		}
+		if !reflect.DeepEqual(s1, wantS) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", s1, s1, wantS, wantS)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantS = s{A: 2, B: 4}
+	wantErrorMsg := "cbor: found duplicate map key \"1\" at map element index 2"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s2 s
+		if err := dec.Decode(&s2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(s2, wantS) {
+			t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructNoMatchingField(t *testing.T) {
+	type s struct {
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	wantS := s{B: "B", C: "C", D: "D", E: "E"}
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error even though map key "a" doesn't have a corresponding struct field.
+	wantS = s{B: "B", C: "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestStreamDupMapKeyToStructNoMatchingField(t *testing.T) {
+	type s struct {
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a6616161416162614261636143616161466164614461656145") // {"a": "A", "b": "B", "c": "C", "a": "F", "d": "D", "e": "E"}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	// Duplicate key overwrites previous value (default).
+	wantS := s{B: "B", C: "C", D: "D", E: "E"}
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s1 s
+		if err := dec.Decode(&s1); err != nil {
+			t.Errorf("Decode() returned error %v", err)
+		}
+		if !reflect.DeepEqual(s1, wantS) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", s1, s1, wantS, wantS)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantS = s{B: "B", C: "C"}
+	wantErrorMsg := "cbor: found duplicate map key \"a\" at map element index 3"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s2 s
+		if err := dec.Decode(&s2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(s2, wantS) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", s2, s2, wantS, wantS)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructKeyAsIntNoMatchingField(t *testing.T) {
+	type s struct {
+		B int `cbor:"3,keyasint"`
+		C int `cbor:"5,keyasint"`
+	}
+	cborData := hexDecode("a40102030401030506") // {1:2, 3:4, 1:3, 5:6}
+
+	wantS := s{B: 4, C: 6}
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error even though map key "a" doesn't have a corresponding struct field.
+	wantS = s{B: 4}
+	wantErrorMsg := "cbor: found duplicate map key \"1\" at map element index 2"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestStreamDupMapKeyToStructKeyAsIntNoMatchingField(t *testing.T) {
+	type s struct {
+		B int `cbor:"3,keyasint"`
+		C int `cbor:"5,keyasint"`
+	}
+	cborData := hexDecode("a40102030401030506") // {1:2, 3:4, 1:3, 5:6}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	// Duplicate key overwrites previous value (default).
+	wantS := s{B: 4, C: 6}
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s1 s
+		if err := dec.Decode(&s1); err != nil {
+			t.Errorf("Decode() returned error %v", err)
+		}
+		if !reflect.DeepEqual(s1, wantS) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", s1, s1, wantS, wantS)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantS = s{B: 4}
+	wantErrorMsg := "cbor: found duplicate map key \"1\" at map element index 2"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s2 s
+		if err := dec.Decode(&s2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(s2, wantS) {
+			t.Errorf("Decode() = %v (%T), want %v (%T)", s2, s2, wantS, wantS)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructWrongType(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a861616141fa47c35000026162614261636143fa47c3500003616161466164614461656145") // {"a": "A", 100000.0:2, "b": "B", "c": "C", 100000.0:3, "a": "F", "d": "D", "e": "E"}
+
+	var s1 s
+	wantS := s{A: "A", B: "B", C: "C", D: "D", E: "E"}
+	wantErrorMsg := "cbor: cannot unmarshal"
+	if err := Unmarshal(cborData, &s1); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	wantS = s{A: "A", B: "B", C: "C"}
+	wantErrorMsg = "cbor: found duplicate map key \"100000\" at map element index 4"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*DupMapKeyError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*DupMapKeyError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestStreamDupMapKeyToStructWrongType(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a861616141fa47c35000026162614261636143fa47c3500003616161466164614461656145") // {"a": "A", 100000.0:2, "b": "B", "c": "C", 100000.0:3, "a": "F", "d": "D", "e": "E"}
+
+	var b []byte
+	for i := 0; i < 3; i++ {
+		b = append(b, cborData...)
+	}
+
+	wantS := s{A: "A", B: "B", C: "C", D: "D", E: "E"}
+	wantErrorMsg := "cbor: cannot unmarshal"
+	dec := NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s1 s
+		if err := dec.Decode(&s1); err == nil {
+			t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+		} else if _, ok := err.(*UnmarshalTypeError); !ok {
+			t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+		} else if !strings.Contains(err.Error(), wantErrorMsg) {
+			t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(s1, wantS) {
+			t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+		}
+	}
+	var v interface{}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+
+	// Duplicate key triggers error.
+	wantS = s{A: "A", B: "B", C: "C"}
+	wantErrorMsg = "cbor: found duplicate map key \"100000\" at map element index 4"
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	dec = dm.NewDecoder(bytes.NewReader(b))
+	for i := 0; i < 3; i++ {
+		var s2 s
+		if err := dec.Decode(&s2); err == nil {
+			t.Errorf("Decode() didn't return an error")
+		} else if _, ok := err.(*DupMapKeyError); !ok {
+			t.Errorf("Decode() returned wrong error type %T, want (*DupMapKeyError)", err)
+		} else if err.Error() != wantErrorMsg {
+			t.Errorf("Decode() returned error %q, want error containing %q", err.Error(), wantErrorMsg)
+		}
+		if !reflect.DeepEqual(s2, wantS) {
+			t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+		}
+	}
+	if err := dec.Decode(&v); err != io.EOF {
+		t.Errorf("Decode() returned error %v, want %v", err, io.EOF)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructStringParseError(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a661fe6141616261426163614361fe61466164614461656145") // {"\xFE": "A", "b": "B", "c": "C", "\xFE": "F", "d": "D", "e": "E"}
+	wantS := s{A: "", B: "B", C: "C", D: "D", E: "E"}
+	wantErrorMsg := "cbor: invalid UTF-8 string"
+
+	// Duplicate key doesn't overwrite previous value (default).
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*SemanticError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*SemanticError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error.
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*SemanticError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*SemanticError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructIntParseError(t *testing.T) {
+	type s struct {
+		A int `cbor:"1,keyasint"`
+		B int `cbor:"3,keyasint"`
+		C int `cbor:"5,keyasint"`
+	}
+	cborData := hexDecode("a43bffffffffffffffff0203043bffffffffffffffff030506") // {-18446744073709551616:2, 3:4, -18446744073709551616:3, 5:6}
+
+	// Duplicate key doesn't overwrite previous value (default).
+	wantS := s{B: 4, C: 6}
+	wantErrorMsg := "cbor: cannot unmarshal"
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error.
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructWrongTypeParseError(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a68161fe614161626142616361438161fe61466164614461656145") // {["\xFE"]: "A", "b": "B", "c": "C", ["\xFE"]: "F", "d": "D", "e": "E"}
+
+	// Duplicate key doesn't overwrite previous value (default).
+	wantS := s{A: "", B: "B", C: "C", D: "D", E: "E"}
+	wantErrorMsg := "cbor: cannot unmarshal"
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error.
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructWrongTypeUnhashableError(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a6810061416162614261636143810061466164614461656145") // {[0]: "A", "b": "B", "c": "C", [0]: "F", "d": "D", "e": "E"}
+	wantS := s{A: "", B: "B", C: "C", D: "D", E: "E"}
+
+	// Duplicate key doesn't overwrite previous value (default).
+	wantErrorMsg := "cbor: cannot unmarshal"
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error.
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
+
+func TestUnmarshalDupMapKeyToStructTagTypeError(t *testing.T) {
+	type s struct {
+		A string `cbor:"a"`
+		B string `cbor:"b"`
+		C string `cbor:"c"`
+		D string `cbor:"d"`
+		E string `cbor:"e"`
+	}
+	cborData := hexDecode("a6c24901000000000000000061416162614261636143c24901000000000000000061466164614461656145") // {bignum(18446744073709551616): "A", "b": "B", "c": "C", bignum(18446744073709551616): "F", "d": "D", "e": "E"}
+	wantS := s{A: "", B: "B", C: "C", D: "D", E: "E"}
+
+	// Duplicate key doesn't overwrite previous value (default).
+	wantErrorMsg := "cbor: cannot unmarshal"
+	var s1 s
+	if err := Unmarshal(cborData, &s1); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s1, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s1, s1, wantS, wantS)
+	}
+
+	// Duplicate key triggers error.
+	dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
+	var s2 s
+	if err := dm.Unmarshal(cborData, &s2); err == nil {
+		t.Errorf("Unmarshal(0x%x, %s) didn't return an error", cborData, reflect.TypeOf(s2))
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), wantErrorMsg) {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), wantErrorMsg)
+	}
+	if !reflect.DeepEqual(s2, wantS) {
+		t.Errorf("Unmarshal(0x%x) = %+v (%T), want %+v (%T)", cborData, s2, s2, wantS, wantS)
+	}
+}
diff --git a/encode_test.go b/encode_test.go
index df36f2c..fd6e2c6 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -1339,23 +1339,23 @@ func TestEncodeTimeWithTag(t *testing.T) {
 			convert: []timeConvert{
 				{
 					opt:          timeUnixOpt,
-					wantCborData: hexDecode("f7"), // encode as CBOR undefined
+					wantCborData: hexDecode("f6"), // encode as CBOR null
 				},
 				{
 					opt:          timeUnixMicroOpt,
-					wantCborData: hexDecode("f7"), // encode as CBOR undefined
+					wantCborData: hexDecode("f6"), // encode as CBOR null
 				},
 				{
 					opt:          timeUnixDynamicOpt,
-					wantCborData: hexDecode("f7"), // encode as CBOR undefined
+					wantCborData: hexDecode("f6"), // encode as CBOR null
 				},
 				{
 					opt:          timeRFC3339Opt,
-					wantCborData: hexDecode("f7"), // encode as CBOR undefined
+					wantCborData: hexDecode("f6"), // encode as CBOR null
 				},
 				{
 					opt:          timeRFC3339NanoOpt,
-					wantCborData: hexDecode("f7"), // encode as CBOR undefined
+					wantCborData: hexDecode("f6"), // encode as CBOR null
 				},
 			},
 		},
diff --git a/tag_test.go b/tag_test.go
index 059745c..2184473 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -284,11 +284,11 @@ func TestAddTagError(t *testing.T) {
 			wantErrorMsg: "cbor: cannot add nil content type to TagSet",
 		},
 		{
-			name:         "DecTag is DecTagIgnored && EncTag is EncTagIgnored",
+			name:         "DecTag is DecTagIgnored && EncTag is EncTagNone",
 			typ:          reflect.TypeOf(myInt(0)),
 			num:          100,
-			opts:         TagOptions{DecTag: DecTagIgnored, EncTag: EncTagIgnored},
-			wantErrorMsg: "cbor: cannot add tag with DecTagIgnored and EncTagIgnored options to TagSet",
+			opts:         TagOptions{DecTag: DecTagIgnored, EncTag: EncTagNone},
+			wantErrorMsg: "cbor: cannot add tag with DecTagIgnored and EncTagNone options to TagSet",
 		},
 		{
 			name:         "time.Time",
@@ -336,7 +336,7 @@ func TestAddTagError(t *testing.T) {
 			name:         "cbor.Unmarshaler",
 			typ:          reflect.TypeOf(number2(0)),
 			num:          107,
-			opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagIgnored},
+			opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagNone},
 			wantErrorMsg: "cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored",
 		},
 		{
@@ -344,7 +344,7 @@ func TestAddTagError(t *testing.T) {
 			typ:          reflect.TypeOf(number2(0)),
 			num:          108,
 			opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
-			wantErrorMsg: "cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagIgnored",
+			wantErrorMsg: "cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone",
 		},
 		{
 			name:         "tag number 0",
@@ -563,7 +563,7 @@ func TestAddTagTypeAliasError(t *testing.T) {
 	}
 }
 
-// TestDecodeTag decodes tag data with DecTagRequired/EncTagOptional/EncTagIgnored options.
+// TestDecodeTag decodes tag data with DecTagRequired/EncTagOptional/EncTagNone options.
 func TestDecodeTagData(t *testing.T) {
 	type myInt int
 	type s struct {
@@ -639,7 +639,7 @@ func TestDecodeTagData(t *testing.T) {
 	}
 }
 
-// TestDecodeNoTag decodes no-tag data with DecTagRequired/EncTagOptional/EncTagIgnored options
+// TestDecodeNoTag decodes no-tag data with DecTagRequired/EncTagOptional/EncTagNone options
 func TestDecodeNoTagData(t *testing.T) {
 	type myInt int
 	type s struct {
@@ -662,10 +662,10 @@ func TestDecodeNoTagData(t *testing.T) {
 	tagsDecRequired := NewTagSet()
 	tagsDecOptional := NewTagSet()
 	for _, tag := range tagInfos {
-		if err := tagsDecRequired.Add(TagOptions{EncTag: EncTagIgnored, DecTag: DecTagRequired}, tag.t, tag.n[0], tag.n[1:]...); err != nil {
+		if err := tagsDecRequired.Add(TagOptions{EncTag: EncTagNone, DecTag: DecTagRequired}, tag.t, tag.n[0], tag.n[1:]...); err != nil {
 			t.Fatalf("TagSet.Add(%s, %v) returned error %v", tag.t, tag.n, err)
 		}
-		if err := tagsDecOptional.Add(TagOptions{EncTag: EncTagIgnored, DecTag: DecTagOptional}, tag.t, tag.n[0], tag.n[1:]...); err != nil {
+		if err := tagsDecOptional.Add(TagOptions{EncTag: EncTagNone, DecTag: DecTagOptional}, tag.t, tag.n[0], tag.n[1:]...); err != nil {
 			t.Fatalf("TagSet.Add(%s, %v) returned error %v", tag.t, tag.n, err)
 		}
 	}
@@ -728,7 +728,7 @@ func TestDecodeNoTagData(t *testing.T) {
 	}
 }
 
-// TestDecodeWrongTag decodes wrong tag data with DecTagRequired/EncTagOptional/EncTagIgnored options
+// TestDecodeWrongTag decodes wrong tag data with DecTagRequired/EncTagOptional/EncTagNone options
 func TestDecodeWrongTag(t *testing.T) {
 	type myInt int
 	type s struct {