Repository: fxamacker/cbor
Pull request #171 (2020-02-23 06:42:38)

Add CBOR encoding and decoding options, update CTAP2 preset options
Add these 2 options to both EncOptions and DecOptions:

* IndefLength: IndefLengthAllowed, IndefLengthForbidden
* TagsMd: TagsAllowed, TagsForbidden

Add 3 more options to DecOptions:

* MaxMapArrayElements (default = 128x1024)
* MaxMapPairs (default = 128x1024)
* MaxNestingLevel (default = 32)

Update func CTAP2EncOptions() to return 2 additional presets:

* TagsMd = TagsForbidden
* IndefLength = IndefLengthForbidden



Patch:
diff --git a/decode.go b/decode.go
index 5624cb8..079e682 100644
--- a/decode.go
+++ b/decode.go
@@ -176,6 +176,40 @@ func (dmkm DupMapKeyMode) valid() bool {
 	return dmkm < maxDupMapKeyMode
 }
 
+// IndefLengthMode specifies whether to allow indefinite length items.
+type IndefLengthMode int
+
+const (
+	// IndefLengthAllowed allows indefinite length items.
+	IndefLengthAllowed IndefLengthMode = iota
+
+	// IndefLengthForbidden disallows indefinite length items.
+	IndefLengthForbidden
+
+	maxIndefLengthMode
+)
+
+func (m IndefLengthMode) valid() bool {
+	return m < maxIndefLengthMode
+}
+
+// TagsMode specifies whether to allow CBOR tags.
+type TagsMode int
+
+const (
+	// TagsAllowed allows CBOR tags.
+	TagsAllowed TagsMode = iota
+
+	// TagsForbidden disallows CBOR tags.
+	TagsForbidden
+
+	maxTagsMode
+)
+
+func (tm TagsMode) valid() bool {
+	return tm < maxTagsMode
+}
+
 // DecOptions specifies decoding options.
 type DecOptions struct {
 	// DupMapKey specifies whether to enforce duplicate map key.
@@ -184,6 +218,24 @@ type DecOptions struct {
 	// 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
+
+	// MaxNestedLevels specifies the max nested levels allowed for any combination of CBOR array, maps, and tags.
+	// Default is 32 levels and it can be set to [4, 256].
+	MaxNestedLevels int
+
+	// MaxArrayElements specifies the max number of elements for CBOR arrays.
+	// Default is 128*1024=131072 and it can be set to [16, 134217728]
+	MaxArrayElements int
+
+	// MaxMapPairs specifies the max number of key-value pairs for CBOR maps.
+	// Default is 128*1024=131072 and it can be set to [16, 134217728]
+	MaxMapPairs int
+
+	// IndefLength specifies whether to allow indefinite length CBOR items.
+	IndefLength IndefLengthMode
+
+	// TagsMd specifies whether to allow CBOR tags (major type 6).
+	TagsMd TagsMode
 }
 
 // DecMode returns DecMode with immutable options and no tags (safe for concurrency).
@@ -193,6 +245,9 @@ func (opts DecOptions) DecMode() (DecMode, error) {
 
 // DecModeWithTags returns DecMode with options and tags that are both immutable (safe for concurrency).
 func (opts DecOptions) DecModeWithTags(tags TagSet) (DecMode, error) {
+	if opts.TagsMd == TagsForbidden {
+		return nil, errors.New("cbor: cannot create DecMode with TagSet when TagsMd is TagsForbidden")
+	}
 	if tags == nil {
 		return nil, errors.New("cbor: cannot create DecMode with nil value as TagSet")
 	}
@@ -222,6 +277,9 @@ func (opts DecOptions) DecModeWithTags(tags TagSet) (DecMode, error) {
 
 // DecModeWithSharedTags returns DecMode with immutable options and mutable shared tags (safe for concurrency).
 func (opts DecOptions) DecModeWithSharedTags(tags TagSet) (DecMode, error) {
+	if opts.TagsMd == TagsForbidden {
+		return nil, errors.New("cbor: cannot create DecMode with TagSet when TagsMd is TagsForbidden")
+	}
 	if tags == nil {
 		return nil, errors.New("cbor: cannot create DecMode with nil value as TagSet")
 	}
@@ -233,6 +291,16 @@ func (opts DecOptions) DecModeWithSharedTags(tags TagSet) (DecMode, error) {
 	return dm, nil
 }
 
+const (
+	defaultMaxArrayElements = 131072
+	minMaxArrayElements     = 16
+	maxMaxArrayElements     = 134217728
+
+	defaultMaxMapPairs = 131072
+	minMaxMapPairs     = 16
+	maxMaxMapPairs     = 134217728
+)
+
 func (opts DecOptions) decMode() (*decMode, error) {
 	if !opts.DupMapKey.valid() {
 		return nil, errors.New("cbor: invalid DupMapKey " + strconv.Itoa(int(opts.DupMapKey)))
@@ -240,7 +308,36 @@ func (opts DecOptions) decMode() (*decMode, error) {
 	if !opts.TimeTag.valid() {
 		return nil, errors.New("cbor: invalid TimeTag " + strconv.Itoa(int(opts.TimeTag)))
 	}
-	dm := decMode{dupMapKey: opts.DupMapKey, timeTag: opts.TimeTag}
+	if !opts.IndefLength.valid() {
+		return nil, errors.New("cbor: invalid IndefLength " + strconv.Itoa(int(opts.IndefLength)))
+	}
+	if !opts.TagsMd.valid() {
+		return nil, errors.New("cbor: invalid TagsMd " + strconv.Itoa(int(opts.TagsMd)))
+	}
+	if opts.MaxNestedLevels == 0 {
+		opts.MaxNestedLevels = 32
+	} else if opts.MaxNestedLevels < 4 || opts.MaxNestedLevels > 256 {
+		return nil, errors.New("cbor: invalid MaxNestedLevels " + strconv.Itoa(opts.MaxNestedLevels) + " (range is [4, 256])")
+	}
+	if opts.MaxArrayElements == 0 {
+		opts.MaxArrayElements = defaultMaxArrayElements
+	} else if opts.MaxArrayElements < minMaxArrayElements || opts.MaxArrayElements > maxMaxArrayElements {
+		return nil, errors.New("cbor: invalid MaxArrayElements " + strconv.Itoa(opts.MaxArrayElements) + " (range is [" + strconv.Itoa(minMaxArrayElements) + ", " + strconv.Itoa(maxMaxArrayElements) + "])")
+	}
+	if opts.MaxMapPairs == 0 {
+		opts.MaxMapPairs = defaultMaxMapPairs
+	} else if opts.MaxMapPairs < minMaxMapPairs || opts.MaxMapPairs > maxMaxMapPairs {
+		return nil, errors.New("cbor: invalid MaxMapPairs " + strconv.Itoa(opts.MaxMapPairs) + " (range is [" + strconv.Itoa(minMaxMapPairs) + ", " + strconv.Itoa(maxMaxMapPairs) + "])")
+	}
+	dm := decMode{
+		dupMapKey:        opts.DupMapKey,
+		timeTag:          opts.TimeTag,
+		maxNestedLevels:  opts.MaxNestedLevels,
+		maxArrayElements: opts.MaxArrayElements,
+		maxMapPairs:      opts.MaxMapPairs,
+		indefLength:      opts.IndefLength,
+		tagsMd:           opts.TagsMd,
+	}
 	return &dm, nil
 }
 
@@ -252,16 +349,29 @@ type DecMode interface {
 }
 
 type decMode struct {
-	tags      tagProvider
-	dupMapKey DupMapKeyMode
-	timeTag   DecTagMode
+	tags             tagProvider
+	dupMapKey        DupMapKeyMode
+	timeTag          DecTagMode
+	maxNestedLevels  int
+	maxArrayElements int
+	maxMapPairs      int
+	indefLength      IndefLengthMode
+	tagsMd           TagsMode
 }
 
-var defaultDecMode = &decMode{}
+var defaultDecMode, _ = DecOptions{}.decMode()
 
 // DecOptions returns user specified options used to create this DecMode.
 func (dm *decMode) DecOptions() DecOptions {
-	return DecOptions{DupMapKey: dm.dupMapKey, TimeTag: dm.timeTag}
+	return DecOptions{
+		DupMapKey:        dm.dupMapKey,
+		TimeTag:          dm.timeTag,
+		MaxNestedLevels:  dm.maxNestedLevels,
+		MaxArrayElements: dm.maxArrayElements,
+		MaxMapPairs:      dm.maxMapPairs,
+		IndefLength:      dm.indefLength,
+		TagsMd:           dm.tagsMd,
+	}
 }
 
 // Unmarshal parses the CBOR-encoded data and stores the result in the value
@@ -291,7 +401,10 @@ func (d *decodeState) value(v interface{}) error {
 		return &InvalidUnmarshalError{reflect.TypeOf(v)}
 	}
 
-	if _, err := valid(d.data[d.off:]); err != nil {
+	off := d.off // Save offset before data validation
+	err := d.valid()
+	d.off = off // Restore offset
+	if err != nil {
 		return err
 	}
 
diff --git a/encode.go b/encode.go
index 50621d6..de52b54 100644
--- a/encode.go
+++ b/encode.go
@@ -257,7 +257,11 @@ type EncOptions struct {
 	// RFC3339 format gets tag number 0, and numeric epoch time tag number 1.
 	TimeTag EncTagMode
 
-	disableIndefiniteLength bool
+	// IndefLength specifies whether to allow indefinite length CBOR items.
+	IndefLength IndefLengthMode
+
+	// TagsMd specifies whether to allow CBOR tags (major type 6).
+	TagsMd TagsMode
 }
 
 // CanonicalEncOptions returns EncOptions for "Canonical CBOR" encoding,
@@ -278,11 +282,11 @@ type EncOptions struct {
 //
 func CanonicalEncOptions() EncOptions {
 	return EncOptions{
-		Sort:                    SortCanonical,
-		ShortestFloat:           ShortestFloat16,
-		NaNConvert:              NaNConvert7e00,
-		InfConvert:              InfConvertFloat16,
-		disableIndefiniteLength: true,
+		Sort:          SortCanonical,
+		ShortestFloat: ShortestFloat16,
+		NaNConvert:    NaNConvert7e00,
+		InfConvert:    InfConvertFloat16,
+		IndefLength:   IndefLengthForbidden,
 	}
 }
 
@@ -299,11 +303,12 @@ func CanonicalEncOptions() EncOptions {
 //
 func CTAP2EncOptions() EncOptions {
 	return EncOptions{
-		Sort:                    SortCTAP2,
-		ShortestFloat:           ShortestFloatNone,
-		NaNConvert:              NaNConvertNone,
-		InfConvert:              InfConvertNone,
-		disableIndefiniteLength: true,
+		Sort:          SortCTAP2,
+		ShortestFloat: ShortestFloatNone,
+		NaNConvert:    NaNConvertNone,
+		InfConvert:    InfConvertNone,
+		IndefLength:   IndefLengthForbidden,
+		TagsMd:        TagsForbidden,
 	}
 }
 
@@ -320,11 +325,11 @@ func CTAP2EncOptions() EncOptions {
 //
 func CoreDetEncOptions() EncOptions {
 	return EncOptions{
-		Sort:                    SortCoreDeterministic,
-		ShortestFloat:           ShortestFloat16,
-		NaNConvert:              NaNConvert7e00,
-		InfConvert:              InfConvertFloat16,
-		disableIndefiniteLength: true,
+		Sort:          SortCoreDeterministic,
+		ShortestFloat: ShortestFloat16,
+		NaNConvert:    NaNConvert7e00,
+		InfConvert:    InfConvertFloat16,
+		IndefLength:   IndefLengthForbidden,
 	}
 }
 
@@ -360,6 +365,9 @@ func (opts EncOptions) EncMode() (EncMode, error) {
 
 // EncModeWithTags returns EncMode with options and tags that are both immutable (safe for concurrency).
 func (opts EncOptions) EncModeWithTags(tags TagSet) (EncMode, error) {
+	if opts.TagsMd == TagsForbidden {
+		return nil, errors.New("cbor: cannot create EncMode with TagSet when TagsMd is TagsForbidden")
+	}
 	if tags == nil {
 		return nil, errors.New("cbor: cannot create EncMode with nil value as TagSet")
 	}
@@ -385,6 +393,9 @@ func (opts EncOptions) EncModeWithTags(tags TagSet) (EncMode, error) {
 
 // EncModeWithSharedTags returns EncMode with immutable options and mutable shared tags (safe for concurrency).
 func (opts EncOptions) EncModeWithSharedTags(tags TagSet) (EncMode, error) {
+	if opts.TagsMd == TagsForbidden {
+		return nil, errors.New("cbor: cannot create EncMode with TagSet when TagsMd is TagsForbidden")
+	}
 	if tags == nil {
 		return nil, errors.New("cbor: cannot create EncMode with nil value as TagSet")
 	}
@@ -415,14 +426,24 @@ func (opts EncOptions) encMode() (*encMode, error) {
 	if !opts.TimeTag.valid() {
 		return nil, errors.New("cbor: invalid TimeTag " + strconv.Itoa(int(opts.TimeTag)))
 	}
+	if !opts.IndefLength.valid() {
+		return nil, errors.New("cbor: invalid IndefLength " + strconv.Itoa(int(opts.IndefLength)))
+	}
+	if !opts.TagsMd.valid() {
+		return nil, errors.New("cbor: invalid TagsMd " + strconv.Itoa(int(opts.TagsMd)))
+	}
+	if opts.TagsMd == TagsForbidden && opts.TimeTag == EncTagRequired {
+		return nil, errors.New("cbor: cannot set TagsMd to TagsForbidden when TimeTag is EncTagRequired")
+	}
 	em := encMode{
-		sort:                    opts.Sort,
-		shortestFloat:           opts.ShortestFloat,
-		nanConvert:              opts.NaNConvert,
-		infConvert:              opts.InfConvert,
-		time:                    opts.Time,
-		timeTag:                 opts.TimeTag,
-		disableIndefiniteLength: opts.disableIndefiniteLength,
+		sort:          opts.Sort,
+		shortestFloat: opts.ShortestFloat,
+		nanConvert:    opts.NaNConvert,
+		infConvert:    opts.InfConvert,
+		time:          opts.Time,
+		timeTag:       opts.TimeTag,
+		indefLength:   opts.IndefLength,
+		tagsMd:        opts.TagsMd,
 	}
 	return &em, nil
 }
@@ -435,14 +456,15 @@ type EncMode interface {
 }
 
 type encMode struct {
-	tags                    tagProvider
-	sort                    SortMode
-	shortestFloat           ShortestFloatMode
-	nanConvert              NaNConvertMode
-	infConvert              InfConvertMode
-	time                    TimeMode
-	timeTag                 EncTagMode
-	disableIndefiniteLength bool
+	tags          tagProvider
+	sort          SortMode
+	shortestFloat ShortestFloatMode
+	nanConvert    NaNConvertMode
+	infConvert    InfConvertMode
+	time          TimeMode
+	timeTag       EncTagMode
+	indefLength   IndefLengthMode
+	tagsMd        TagsMode
 }
 
 var defaultEncMode = &encMode{}
@@ -450,13 +472,14 @@ var defaultEncMode = &encMode{}
 // EncOptions returns user specified options used to create this EncMode.
 func (em *encMode) EncOptions() EncOptions {
 	return EncOptions{
-		Sort:                    em.sort,
-		ShortestFloat:           em.shortestFloat,
-		NaNConvert:              em.nanConvert,
-		InfConvert:              em.infConvert,
-		Time:                    em.time,
-		TimeTag:                 em.timeTag,
-		disableIndefiniteLength: em.disableIndefiniteLength,
+		Sort:          em.sort,
+		ShortestFloat: em.shortestFloat,
+		NaNConvert:    em.nanConvert,
+		InfConvert:    em.infConvert,
+		Time:          em.time,
+		TimeTag:       em.timeTag,
+		IndefLength:   em.indefLength,
+		TagsMd:        em.tagsMd,
 	}
 }
 
@@ -1097,6 +1120,9 @@ func encodeBinaryMarshalerType(e *encodeState, em *encMode, v reflect.Value) err
 }
 
 func encodeMarshalerType(e *encodeState, em *encMode, v reflect.Value) error {
+	if em.tagsMd == TagsForbidden && v.Type() == typeRawTag {
+		return errors.New("cbor: cannot encode cbor.RawTag when TagsMd is TagsForbidden")
+	}
 	m, ok := v.Interface().(Marshaler)
 	if !ok {
 		pv := reflect.New(v.Type())
@@ -1112,6 +1138,10 @@ func encodeMarshalerType(e *encodeState, em *encMode, v reflect.Value) error {
 }
 
 func encodeTag(e *encodeState, em *encMode, v reflect.Value) error {
+	if em.tagsMd == TagsForbidden {
+		return errors.New("cbor: cannot encode cbor.Tag when TagsMd is TagsForbidden")
+	}
+
 	t := v.Interface().(Tag)
 
 	// Marshal tag number
diff --git a/stream.go b/stream.go
index aa0816a..6231933 100644
--- a/stream.go
+++ b/stream.go
@@ -163,8 +163,8 @@ var cborIndefHeader = map[cborType][]byte{
 }
 
 func (enc *Encoder) startIndefinite(typ cborType) error {
-	if enc.em.disableIndefiniteLength {
-		return errors.New("cbor: indefinite-length items are not allowed")
+	if enc.em.indefLength == IndefLengthForbidden {
+		return &IndefiniteLengthError{typ}
 	}
 	_, err := enc.w.Write(cborIndefHeader[typ])
 	if err == nil {
diff --git a/valid.go b/valid.go
index a0b5864..0d243bb 100644
--- a/valid.go
+++ b/valid.go
@@ -24,59 +24,112 @@ type SemanticError struct {
 
 func (e *SemanticError) Error() string { return e.msg }
 
-// valid checks whether CBOR data is complete and well-formed.
-func valid(data []byte) (rest []byte, err error) {
-	if len(data) == 0 {
-		return nil, io.EOF
-	}
-	offset, _, err := validInternal(data, 0, 1)
-	if err != nil {
-		return nil, err
-	}
-	return data[offset:], nil
+// MaxNestedLevelError indicates exceeded max nested level of any combination of CBOR arrays/maps/tags.
+type MaxNestedLevelError struct {
+	maxNestedLevels int
 }
 
-const (
-	maxNestingLevel = 32
-)
+func (e *MaxNestedLevelError) Error() string {
+	return "cbor: exceeded max nested level " + strconv.Itoa(e.maxNestedLevels)
+}
 
-// validInternal checks data's well-formedness and returns data's next offset, max depth, and error.
-func validInternal(data []byte, off int, depth int) (int, int, error) {
-	if depth > maxNestingLevel {
-		return 0, 0, errors.New("cbor: reached max depth " + strconv.Itoa(maxNestingLevel))
-	}
+// MaxArrayElementsError indicates exceeded max number of elements for CBOR arrays.
+type MaxArrayElementsError struct {
+	maxArrayElements int
+}
 
-	off, t, ai, val, err := validHead(data, off)
-	if err != nil {
-		return 0, 0, err
-	}
+func (e *MaxArrayElementsError) Error() string {
+	return "cbor: exceeded max number of elements " + strconv.Itoa(e.maxArrayElements) + " for CBOR array"
+}
 
-	if ai == 31 {
-		if t == cborTypeByteString || t == cborTypeTextString {
-			return validIndefiniteString(data, off, t, depth)
-		}
-		return validIndefiniteArrOrMap(data, off, t, depth)
+// MaxMapPairsError indicates exceeded max number of key-value pairs for CBOR maps.
+type MaxMapPairsError struct {
+	maxMapPairs int
+}
+
+func (e *MaxMapPairsError) Error() string {
+	return "cbor: exceeded max number of key-value pairs " + strconv.Itoa(e.maxMapPairs) + " for CBOR map"
+}
+
+// IndefiniteLengthError indicates found disallowed indefinite length items.
+type IndefiniteLengthError struct {
+	t cborType
+}
+
+func (e *IndefiniteLengthError) Error() string {
+	return "cbor: indefinite-length " + e.t.String() + " isn't allowed"
+}
+
+// TagsMdError indicates found disallowed CBOR tags.
+type TagsMdError struct {
+}
+
+func (e *TagsMdError) Error() string {
+	return "cbor: CBOR tag isn't allowed"
+}
+
+// valid checks whether CBOR data is complete and well-formed.
+func (d *decodeState) valid() error {
+	if len(d.data) == d.off {
+		return io.EOF
 	}
+	_, err := d.validInternal(0)
+	return err
+}
 
-	dataLen := len(data)
+// validInternal checks data's well-formedness and returns max depth and error.
+func (d *decodeState) validInternal(depth int) (int, error) {
+	t, ai, val, err := d.validHead()
+	if err != nil {
+		return 0, err
+	}
 
 	switch t {
 	case cborTypeByteString, cborTypeTextString:
+		if ai == 31 {
+			if d.dm.indefLength == IndefLengthForbidden {
+				return 0, &IndefiniteLengthError{t}
+			}
+			return d.validIndefiniteString(t, depth)
+		}
 		valInt := int(val)
 		if valInt < 0 {
 			// Detect integer overflow
-			return 0, 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
+			return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
 		}
-		if dataLen-off < valInt { // valInt+off may overflow integer
-			return 0, 0, io.ErrUnexpectedEOF
+		if len(d.data)-d.off < valInt { // valInt+off may overflow integer
+			return 0, io.ErrUnexpectedEOF
 		}
-		off += valInt
+		d.off += valInt
 	case cborTypeArray, cborTypeMap:
+		depth++
+		if depth > d.dm.maxNestedLevels {
+			return 0, &MaxNestedLevelError{d.dm.maxNestedLevels}
+		}
+
+		if ai == 31 {
+			if d.dm.indefLength == IndefLengthForbidden {
+				return 0, &IndefiniteLengthError{t}
+			}
+			return d.validIndefiniteArrayOrMap(t, depth)
+		}
+
 		valInt := int(val)
 		if valInt < 0 {
 			// Detect integer overflow
-			return 0, 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
+			return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, it would cause integer overflow")
 		}
+
+		if t == cborTypeArray {
+			if valInt > d.dm.maxArrayElements {
+				return 0, &MaxArrayElementsError{d.dm.maxArrayElements}
+			}
+		} else {
+			if valInt > d.dm.maxMapPairs {
+				return 0, &MaxMapPairsError{d.dm.maxMapPairs}
+			}
+		}
+
 		count := 1
 		if t == cborTypeMap {
 			count = 2
@@ -84,150 +137,164 @@ func validInternal(data []byte, off int, depth int) (int, int, error) {
 		maxDepth := depth
 		for j := 0; j < count; j++ {
 			for i := 0; i < valInt; i++ {
-				var d int
-				if off, d, err = validInternal(data, off, depth+1); err != nil {
-					return 0, 0, err
+				var dpt int
+				if dpt, err = d.validInternal(depth); err != nil {
+					return 0, err
 				}
-				if d > maxDepth {
-					maxDepth = d // Save max depth
+				if dpt > maxDepth {
+					maxDepth = dpt // Save max depth
 				}
 			}
 		}
 		depth = maxDepth
 	case cborTypeTag:
+		if d.dm.tagsMd == TagsForbidden {
+			return 0, &TagsMdError{}
+		}
+
 		// Scan nested tag numbers to avoid recursion.
 		for {
-			if dataLen == off { // Tag number must be followed by tag content.
-				return 0, 0, io.ErrUnexpectedEOF
+			if len(d.data) == d.off { // Tag number must be followed by tag content.
+				return 0, io.ErrUnexpectedEOF
 			}
-			if cborType(data[off]&0xe0) != cborTypeTag {
+			if cborType(d.data[d.off]&0xe0) != cborTypeTag {
 				break
 			}
-			if off, _, _, _, err = validHead(data, off); err != nil {
-				return 0, 0, err
+			if _, _, _, err = d.validHead(); err != nil {
+				return 0, err
 			}
 			depth++
+			if depth > d.dm.maxNestedLevels {
+				return 0, &MaxNestedLevelError{d.dm.maxNestedLevels}
+			}
 		}
 		// Check tag content.
-		return validInternal(data, off, depth)
+		return d.validInternal(depth)
 	}
-	return off, depth, nil
+	return depth, nil
 }
 
-// validIndefiniteString checks indefinite length byte/text string's well-formedness and returns data's next offset, max depth, and error.
-func validIndefiniteString(data []byte, off int, t cborType, depth int) (int, int, error) {
+// validIndefiniteString checks indefinite length byte/text string's well-formedness and returns max depth and error.
+func (d *decodeState) validIndefiniteString(t cborType, depth int) (int, error) {
 	var err error
-	dataLen := len(data)
 	for {
-		if dataLen == off {
-			return 0, 0, io.ErrUnexpectedEOF
+		if len(d.data) == d.off {
+			return 0, io.ErrUnexpectedEOF
 		}
-		if data[off] == 0xff {
-			off++
+		if d.data[d.off] == 0xff {
+			d.off++
 			break
 		}
 		// Peek ahead to get next type and indefinite length status.
-		nt := cborType(data[off] & 0xe0)
+		nt := cborType(d.data[d.off] & 0xe0)
 		if t != nt {
-			return 0, 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
+			return 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
 		}
-		if (data[off] & 0x1f) == 31 {
-			return 0, 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
+		if (d.data[d.off] & 0x1f) == 31 {
+			return 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
 		}
-		if off, depth, err = validInternal(data, off, depth); err != nil {
-			return 0, 0, err
+		if depth, err = d.validInternal(depth); err != nil {
+			return 0, err
 		}
 	}
-	return off, depth, nil
+	return depth, nil
 }
 
-// validIndefiniteArrOrMap checks indefinite length array/map's well-formedness and returns data's next offset, max depth, and error.
-func validIndefiniteArrOrMap(data []byte, off int, t cborType, depth int) (int, int, error) {
+// validIndefiniteArrayOrMap checks indefinite length array/map's well-formedness and returns max depth and error.
+func (d *decodeState) validIndefiniteArrayOrMap(t cborType, depth int) (int, error) {
 	var err error
 	maxDepth := depth
-	dataLen := len(data)
 	i := 0
 	for {
-		if dataLen == off {
-			return 0, 0, io.ErrUnexpectedEOF
+		if len(d.data) == d.off {
+			return 0, io.ErrUnexpectedEOF
 		}
-		if data[off] == 0xff {
-			off++
+		if d.data[d.off] == 0xff {
+			d.off++
 			break
 		}
-		var d int
-		if off, d, err = validInternal(data, off, depth+1); err != nil {
-			return 0, 0, err
+		var dpt int
+		if dpt, err = d.validInternal(depth); err != nil {
+			return 0, err
 		}
-		if d > maxDepth {
-			maxDepth = d
+		if dpt > maxDepth {
+			maxDepth = dpt
 		}
 		i++
+		if t == cborTypeArray {
+			if i > d.dm.maxArrayElements {
+				return 0, &MaxArrayElementsError{d.dm.maxArrayElements}
+			}
+		} else {
+			if i%2 == 0 && i/2 > d.dm.maxMapPairs {
+				return 0, &MaxMapPairsError{d.dm.maxMapPairs}
+			}
+		}
 	}
 	if t == cborTypeMap && i%2 == 1 {
-		return 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
+		return 0, &SyntaxError{"cbor: unexpected \"break\" code"}
 	}
-	return off, maxDepth, nil
+	return maxDepth, nil
 }
 
-func validHead(data []byte, off int) (_ int, t cborType, ai byte, val uint64, err error) {
-	dataLen := len(data) - off
+func (d *decodeState) validHead() (t cborType, ai byte, val uint64, err error) {
+	dataLen := len(d.data) - d.off
 	if dataLen == 0 {
-		return 0, 0, 0, 0, io.ErrUnexpectedEOF
+		return 0, 0, 0, io.ErrUnexpectedEOF
 	}
 
-	t = cborType(data[off] & 0xe0)
-	ai = data[off] & 0x1f
+	t = cborType(d.data[d.off] & 0xe0)
+	ai = d.data[d.off] & 0x1f
 	val = uint64(ai)
-	off++
+	d.off++
 
 	if ai < 24 {
-		return off, t, ai, val, nil
+		return t, ai, val, nil
 	}
 	if ai == 24 {
 		if dataLen < 2 {
-			return 0, 0, 0, 0, io.ErrUnexpectedEOF
+			return 0, 0, 0, io.ErrUnexpectedEOF
 		}
-		val = uint64(data[off])
-		off++
+		val = uint64(d.data[d.off])
+		d.off++
 		if t == cborTypePrimitives && val < 32 {
-			return 0, 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()}
+			return 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()}
 		}
-		return off, t, ai, val, nil
+		return t, ai, val, nil
 	}
 	if ai == 25 {
 		if dataLen < 3 {
-			return 0, 0, 0, 0, io.ErrUnexpectedEOF
+			return 0, 0, 0, io.ErrUnexpectedEOF
 		}
-		val = uint64(binary.BigEndian.Uint16(data[off : off+2]))
-		off += 2
-		return off, t, ai, val, nil
+		val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+2]))
+		d.off += 2
+		return t, ai, val, nil
 	}
 	if ai == 26 {
 		if dataLen < 5 {
-			return 0, 0, 0, 0, io.ErrUnexpectedEOF
+			return 0, 0, 0, io.ErrUnexpectedEOF
 		}
-		val = uint64(binary.BigEndian.Uint32(data[off : off+4]))
-		off += 4
-		return off, t, ai, val, nil
+		val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+4]))
+		d.off += 4
+		return t, ai, val, nil
 	}
 	if ai == 27 {
 		if dataLen < 9 {
-			return 0, 0, 0, 0, io.ErrUnexpectedEOF
+			return 0, 0, 0, io.ErrUnexpectedEOF
 		}
-		val = binary.BigEndian.Uint64(data[off : off+8])
-		off += 8
-		return off, t, ai, val, nil
+		val = binary.BigEndian.Uint64(d.data[d.off : d.off+8])
+		d.off += 8
+		return t, ai, val, nil
 	}
 	if ai == 31 {
 		switch t {
 		case cborTypePositiveInt, cborTypeNegativeInt, cborTypeTag:
-			return 0, 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
+			return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
 		case cborTypePrimitives: // 0xff (break code) should not be outside validIndefinite().
-			return 0, 0, 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
+			return 0, 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
 		}
-		return off, t, ai, val, nil
+		return t, ai, val, nil
 	}
 	// ai == 28, 29, 30
-	return 0, 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
+	return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
 }


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 6a8e7e3..0855d69 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -1171,6 +1171,7 @@ var invalidCBORUnmarshalTests = []struct {
 	{"Tag number not followed by tag content", []byte{0xc0}, "unexpected EOF", false},
 	{"Definite length strings with tagged chunk", hexDecode("5fc64401020304ff"), "cbor: wrong element type tag for indefinite-length byte string", false},
 	{"Definite length strings with tagged chunk", hexDecode("7fc06161ff"), "cbor: wrong element type tag for indefinite-length UTF-8 text string", false},
+	{"Indefinite length strings with invalid head", hexDecode("7f61"), "unexpected EOF", false},
 	{"Invalid nested tag number", hexDecode("d864dc1a514b67b0"), "cbor: invalid additional information", true},
 	// Data from 7049bis G.1
 	// Premature end of the input
@@ -1550,23 +1551,6 @@ func TestUnmarshalPrefilledStruct(t *testing.T) {
 	}
 }
 
-func TestValid(t *testing.T) {
-	var buf bytes.Buffer
-	for _, t := range marshalTests {
-		buf.Write(t.cborData)
-	}
-	cborData := buf.Bytes()
-	var err error
-	for i := 0; i < len(marshalTests); i++ {
-		if cborData, err = valid(cborData); err != nil {
-			t.Errorf("Valid() returned error %v", err)
-		}
-	}
-	if len(cborData) != 0 {
-		t.Errorf("Valid() returned leftover data 0x%x, want none", cborData)
-	}
-}
-
 func TestStructFieldNil(t *testing.T) {
 	type TestStruct struct {
 		I   int
@@ -2861,7 +2845,14 @@ func TestUnmarshalToNotNilInterface(t *testing.T) {
 }
 
 func TestDecOptions(t *testing.T) {
-	opts1 := DecOptions{TimeTag: DecTagRequired, DupMapKey: DupMapKeyEnforcedAPF}
+	opts1 := DecOptions{
+		TimeTag:          DecTagRequired,
+		DupMapKey:        DupMapKeyEnforcedAPF,
+		IndefLength:      IndefLengthForbidden,
+		MaxNestedLevels:  100,
+		MaxMapPairs:      101,
+		MaxArrayElements: 102,
+	}
 	dm, err := opts1.DecMode()
 	if err != nil {
 		t.Errorf("DecMode() returned an error %v", err)
@@ -2920,6 +2911,149 @@ func TestDecModeInvalidDuplicateMapKey(t *testing.T) {
 	}
 }
 
+func TestDecModeDefaultMaxNestedLevel(t *testing.T) {
+	dm, err := DecOptions{}.DecMode()
+	if err != nil {
+		t.Errorf("DecMode() returned error %v", err)
+	} else {
+		maxNestedLevels := dm.DecOptions().MaxNestedLevels
+		if maxNestedLevels != 32 {
+			t.Errorf("DecOptions().MaxNestedLevels = %d, want %v", maxNestedLevels, 32)
+		}
+	}
+}
+
+func TestDecModeInvalidMaxNestedLevel(t *testing.T) {
+	testCases := []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "MaxNestedLevels < 4",
+			opts:         DecOptions{MaxNestedLevels: 1},
+			wantErrorMsg: "cbor: invalid MaxNestedLevels 1 (range is [4, 256])",
+		},
+		{
+			name:         "MaxNestedLevels > 256",
+			opts:         DecOptions{MaxNestedLevels: 257},
+			wantErrorMsg: "cbor: invalid MaxNestedLevels 257 (range is [4, 256])",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.DecMode()
+			if err == nil {
+				t.Errorf("DecMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("DecMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
+	}
+}
+
+func TestDecModeDefaultMaxMapPairs(t *testing.T) {
+	dm, err := DecOptions{}.DecMode()
+	if err != nil {
+		t.Errorf("DecMode() returned error %v", err)
+	} else {
+		maxMapPairs := dm.DecOptions().MaxMapPairs
+		if maxMapPairs != defaultMaxMapPairs {
+			t.Errorf("DecOptions().MaxMapPairs = %d, want %v", maxMapPairs, defaultMaxMapPairs)
+		}
+	}
+}
+
+func TestDecModeInvalidMaxMapPairs(t *testing.T) {
+	testCases := []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "MaxMapPairs < 16",
+			opts:         DecOptions{MaxMapPairs: 1},
+			wantErrorMsg: "cbor: invalid MaxMapPairs 1 (range is [16, 134217728])",
+		},
+		{
+			name:         "MaxMapPairs > 134217728",
+			opts:         DecOptions{MaxMapPairs: 134217729},
+			wantErrorMsg: "cbor: invalid MaxMapPairs 134217729 (range is [16, 134217728])",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.DecMode()
+			if err == nil {
+				t.Errorf("DecMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("DecMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
+	}
+}
+
+func TestDecModeDefaultMaxArrayElements(t *testing.T) {
+	dm, err := DecOptions{}.DecMode()
+	if err != nil {
+		t.Errorf("DecMode() returned error %v", err)
+	} else {
+		maxArrayElements := dm.DecOptions().MaxArrayElements
+		if maxArrayElements != defaultMaxArrayElements {
+			t.Errorf("DecOptions().MaxArrayElementsr = %d, want %v", maxArrayElements, defaultMaxArrayElements)
+		}
+	}
+}
+
+func TestDecModeInvalidMaxArrayElements(t *testing.T) {
+	testCases := []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "MaxArrayElements < 16",
+			opts:         DecOptions{MaxArrayElements: 1},
+			wantErrorMsg: "cbor: invalid MaxArrayElements 1 (range is [16, 134217728])",
+		},
+		{
+			name:         "MaxArrayElements > 134217728",
+			opts:         DecOptions{MaxArrayElements: 134217729},
+			wantErrorMsg: "cbor: invalid MaxArrayElements 134217729 (range is [16, 134217728])",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.DecMode()
+			if err == nil {
+				t.Errorf("DecMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("DecMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
+	}
+}
+
+func TestDecModeInvalidIndefiniteLengthMode(t *testing.T) {
+	wantErrorMsg := "cbor: invalid IndefLength 101"
+	_, err := DecOptions{IndefLength: 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 TestDecModeInvalidTagsMode(t *testing.T) {
+	wantErrorMsg := "cbor: invalid TagsMd 101"
+	_, err := DecOptions{TagsMd: 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"`
@@ -3916,3 +4050,154 @@ func TestIndefiniteLengthArrayToArray(t *testing.T) {
 		})
 	}
 }
+
+func TestExceedMaxArrayElements(t *testing.T) {
+	testCases := []struct {
+		name         string
+		opts         DecOptions
+		cborData     []byte
+		wantErrorMsg string
+	}{
+		{
+			name:         "array",
+			opts:         DecOptions{MaxArrayElements: 16},
+			cborData:     hexDecode("910101010101010101010101010101010101"),
+			wantErrorMsg: "cbor: exceeded max number of elements 16 for CBOR array",
+		},
+		{
+			name:         "indefinite length array",
+			opts:         DecOptions{MaxArrayElements: 16},
+			cborData:     hexDecode("9f0101010101010101010101010101010101ff"),
+			wantErrorMsg: "cbor: exceeded max number of elements 16 for CBOR array",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			dm, _ := tc.opts.DecMode()
+			var v interface{}
+			if err := dm.Unmarshal(tc.cborData, &v); err == nil {
+				t.Errorf("Unmarshal(0x%x) didn't return an error", tc.cborData)
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("Unmarshal(0x%x) returned error %q, want %q", tc.cborData, err.Error(), tc.wantErrorMsg)
+			}
+		})
+	}
+}
+
+func TestExceedMaxMapPairs(t *testing.T) {
+	testCases := []struct {
+		name         string
+		opts         DecOptions
+		cborData     []byte
+		wantErrorMsg string
+	}{
+		{
+			name:         "array",
+			opts:         DecOptions{MaxMapPairs: 16},
+			cborData:     hexDecode("b101010101010101010101010101010101010101010101010101010101010101010101"),
+			wantErrorMsg: "cbor: exceeded max number of key-value pairs 16 for CBOR map",
+		},
+		{
+			name:         "indefinite length array",
+			opts:         DecOptions{MaxMapPairs: 16},
+			cborData:     hexDecode("bf01010101010101010101010101010101010101010101010101010101010101010101ff"),
+			wantErrorMsg: "cbor: exceeded max number of key-value pairs 16 for CBOR map",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			dm, _ := tc.opts.DecMode()
+			var v interface{}
+			if err := dm.Unmarshal(tc.cborData, &v); err == nil {
+				t.Errorf("Unmarshal(0x%x) didn't return an error", tc.cborData)
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("Unmarshal(0x%x) returned error %q, want %q", tc.cborData, err.Error(), tc.wantErrorMsg)
+			}
+		})
+	}
+}
+
+func TestDecIndefiniteLengthOption(t *testing.T) {
+	testCases := []struct {
+		name         string
+		opts         DecOptions
+		cborData     []byte
+		wantErrorMsg string
+	}{
+		{
+			name:         "byte string",
+			opts:         DecOptions{IndefLength: IndefLengthForbidden},
+			cborData:     hexDecode("5fff"),
+			wantErrorMsg: "cbor: indefinite-length byte string isn't allowed",
+		},
+		{
+			name:         "text string",
+			opts:         DecOptions{IndefLength: IndefLengthForbidden},
+			cborData:     hexDecode("7fff"),
+			wantErrorMsg: "cbor: indefinite-length UTF-8 text string isn't allowed",
+		},
+		{
+			name:         "array",
+			opts:         DecOptions{IndefLength: IndefLengthForbidden},
+			cborData:     hexDecode("9fff"),
+			wantErrorMsg: "cbor: indefinite-length array isn't allowed",
+		},
+		{
+			name:         "indefinite length array",
+			opts:         DecOptions{IndefLength: IndefLengthForbidden},
+			cborData:     hexDecode("bfff"),
+			wantErrorMsg: "cbor: indefinite-length map isn't allowed",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			// Default option allows indefinite length items
+			var v interface{}
+			if err := Unmarshal(tc.cborData, &v); err != nil {
+				t.Errorf("Unmarshal(0x%x) returned an error %v", tc.cborData, err)
+			}
+
+			dm, _ := tc.opts.DecMode()
+			if err := dm.Unmarshal(tc.cborData, &v); err == nil {
+				t.Errorf("Unmarshal(0x%x) didn't return an error", tc.cborData)
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("Unmarshal(0x%x) returned error %q, want %q", tc.cborData, err.Error(), tc.wantErrorMsg)
+			}
+		})
+	}
+}
+
+func TestDecTagsMdOption(t *testing.T) {
+	cborData := hexDecode("c074323031332d30332d32315432303a30343a30305a")
+	wantErrorMsg := "cbor: CBOR tag isn't allowed"
+
+	// Default option allows CBOR tags
+	var v interface{}
+	if err := Unmarshal(cborData, &v); err != nil {
+		t.Errorf("Unmarshal(0x%x) returned an error %v", cborData, err)
+	}
+
+	// Decoding CBOR tags with TagsForbidden option returns error
+	dm, _ := DecOptions{TagsMd: TagsForbidden}.DecMode()
+	if err := dm.Unmarshal(cborData, &v); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want %q", cborData, err.Error(), wantErrorMsg)
+	}
+
+	// Create DecMode with TagSet and TagsForbidden option returns error
+	wantErrorMsg = "cbor: cannot create DecMode with TagSet when TagsMd is TagsForbidden"
+	tags := NewTagSet()
+	_, err := DecOptions{TagsMd: TagsForbidden}.DecModeWithTags(tags)
+	if err == nil {
+		t.Errorf("DecModeWithTags() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("DecModeWithTags() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+	_, err = DecOptions{TagsMd: TagsForbidden}.DecModeWithSharedTags(tags)
+	if err == nil {
+		t.Errorf("DecModeWithSharedTags() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("DecModeWithSharedTags() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+}
diff --git a/encode_test.go b/encode_test.go
index fd6e2c6..8638c8d 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -43,10 +43,6 @@ type outer struct {
 	unexportedField   int64
 }
 
-const (
-	wantIndefiniteLengthErrorMsg = "cbor: indefinite-length items are not allowed"
-)
-
 // CBOR test data are from https://tools.ietf.org/html/rfc7049#appendix-A.
 var marshalTests = []marshalTest{
 	// positive integer
@@ -373,7 +369,7 @@ func TestMarshalLargeTextString(t *testing.T) {
 
 func TestMarshalLargeArray(t *testing.T) {
 	// []string{"水", "水", "水", ...}
-	lengths := []int{0, 1, 2, 22, 23, 24, 254, 255, 256, 65534, 65535, 65536, 10000000}
+	lengths := []int{0, 1, 2, 22, 23, 24, 254, 255, 256, 65534, 65535, 65536, 131072}
 	tests := make([]marshalTest, len(lengths))
 	for i, length := range lengths {
 		cborData := bytes.NewBuffer(encodeCborHeader(cborTypeArray, uint64(length)))
@@ -390,7 +386,7 @@ func TestMarshalLargeArray(t *testing.T) {
 
 func TestMarshalLargeMapCanonical(t *testing.T) {
 	// map[int]int {0:0, 1:1, 2:2, ...}
-	lengths := []int{0, 1, 2, 22, 23, 24, 254, 255, 256, 65534, 65535, 65536, 1000000}
+	lengths := []int{0, 1, 2, 22, 23, 24, 254, 255, 256, 65534, 65535, 65536, 131072}
 	tests := make([]marshalTest, len(lengths))
 	for i, length := range lengths {
 		cborData := bytes.NewBuffer(encodeCborHeader(cborTypeMap, uint64(length)))
@@ -409,7 +405,7 @@ func TestMarshalLargeMapCanonical(t *testing.T) {
 
 func TestMarshalLargeMap(t *testing.T) {
 	// map[int]int {0:0, 1:1, 2:2, ...}
-	lengths := []int{0, 1, 2, 22, 23, 24, 254, 255, 256, 65534, 65535, 65536, 1000000}
+	lengths := []int{0, 1, 2, 22, 23, 24, 254, 255, 256, 65534, 65535, 65536, 131072}
 	for _, length := range lengths {
 		m1 := make(map[int]int, length)
 		for i := 0; i < length; i++ {
@@ -2770,6 +2766,7 @@ func TestCanonicalEncOptions(t *testing.T) { //nolint:dupl
 	wantShortestFloat := ShortestFloat16
 	wantNaNConvert := NaNConvert7e00
 	wantInfConvert := InfConvertFloat16
+	wantErrorMsg := "cbor: indefinite-length array isn't allowed"
 	em, err := CanonicalEncOptions().EncMode()
 	if err != nil {
 		t.Errorf("EncMode() returned an error %v", err)
@@ -2790,8 +2787,8 @@ func TestCanonicalEncOptions(t *testing.T) { //nolint:dupl
 	enc := em.NewEncoder(ioutil.Discard)
 	if err := enc.StartIndefiniteArray(); err == nil {
 		t.Errorf("StartIndefiniteArray() didn't return an error")
-	} else if err.Error() != wantIndefiniteLengthErrorMsg {
-		t.Errorf("StartIndefiniteArray() returned error %q, want %q", err.Error(), wantIndefiniteLengthErrorMsg)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("StartIndefiniteArray() returned error %q, want %q", err.Error(), wantErrorMsg)
 	}
 }
 
@@ -2800,6 +2797,7 @@ func TestCTAP2EncOptions(t *testing.T) { //nolint:dupl
 	wantShortestFloat := ShortestFloatNone
 	wantNaNConvert := NaNConvertNone
 	wantInfConvert := InfConvertNone
+	wantErrorMsg := "cbor: indefinite-length array isn't allowed"
 	em, err := CTAP2EncOptions().EncMode()
 	if err != nil {
 		t.Errorf("EncMode() returned an error %v", err)
@@ -2820,8 +2818,8 @@ func TestCTAP2EncOptions(t *testing.T) { //nolint:dupl
 	enc := em.NewEncoder(ioutil.Discard)
 	if err := enc.StartIndefiniteArray(); err == nil {
 		t.Errorf("StartIndefiniteArray() didn't return an error")
-	} else if err.Error() != wantIndefiniteLengthErrorMsg {
-		t.Errorf("StartIndefiniteArray() returned error %q, want %q", err.Error(), wantIndefiniteLengthErrorMsg)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("StartIndefiniteArray() returned error %q, want %q", err.Error(), wantErrorMsg)
 	}
 }
 
@@ -2830,6 +2828,7 @@ func TestCoreDetEncOptions(t *testing.T) { //nolint:dupl
 	wantShortestFloat := ShortestFloat16
 	wantNaNConvert := NaNConvert7e00
 	wantInfConvert := InfConvertFloat16
+	wantErrorMsg := "cbor: indefinite-length array isn't allowed"
 	em, err := CoreDetEncOptions().EncMode()
 	if err != nil {
 		t.Errorf("EncMode() returned an error %v", err)
@@ -2850,8 +2849,8 @@ func TestCoreDetEncOptions(t *testing.T) { //nolint:dupl
 	enc := em.NewEncoder(ioutil.Discard)
 	if err := enc.StartIndefiniteArray(); err == nil {
 		t.Errorf("StartIndefiniteArray() didn't return an error")
-	} else if err.Error() != wantIndefiniteLengthErrorMsg {
-		t.Errorf("StartIndefiniteArray() returned error %q, want %q", err.Error(), wantIndefiniteLengthErrorMsg)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("StartIndefiniteArray() returned error %q, want %q", err.Error(), wantErrorMsg)
 	}
 }
 
@@ -2883,6 +2882,26 @@ func TestPreferredUnsortedEncOptions(t *testing.T) {
 	}
 }
 
+func TestEncModeInvalidIndefiniteLengthMode(t *testing.T) {
+	wantErrorMsg := "cbor: invalid IndefLength 101"
+	_, err := EncOptions{IndefLength: 101}.EncMode()
+	if err == nil {
+		t.Errorf("EncMode() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("EncMode() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+}
+
+func TestEncModeInvalidTagsMode(t *testing.T) {
+	wantErrorMsg := "cbor: invalid TagsMd 101"
+	_, err := EncOptions{TagsMd: 101}.EncMode()
+	if err == nil {
+		t.Errorf("EncMode() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("EncMode() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+}
+
 func TestEncOptions(t *testing.T) {
 	opts1 := EncOptions{
 		Sort:          SortBytewiseLexical,
@@ -2891,6 +2910,8 @@ func TestEncOptions(t *testing.T) {
 		InfConvert:    InfConvertNone,
 		Time:          TimeRFC3339Nano,
 		TimeTag:       EncTagRequired,
+		IndefLength:   IndefLengthForbidden,
+		TagsMd:        TagsAllowed,
 	}
 	em, err := opts1.EncMode()
 	if err != nil {
@@ -2912,3 +2933,111 @@ func TestEncModeInvalidTimeTag(t *testing.T) {
 		t.Errorf("EncMode() returned error %q, want %q", err.Error(), wantErrorMsg)
 	}
 }
+
+func TestEncIndefiniteLengthOption(t *testing.T) {
+	// Default option allows indefinite length items
+	var buf bytes.Buffer
+	enc := NewEncoder(&buf)
+	if err := enc.StartIndefiniteByteString(); err != nil {
+		t.Errorf("StartIndefiniteByteString() returned an error %v", err)
+	}
+	if err := enc.StartIndefiniteTextString(); err != nil {
+		t.Errorf("StartIndefiniteTextString() returned an error %v", err)
+	}
+	if err := enc.StartIndefiniteArray(); err != nil {
+		t.Errorf("StartIndefiniteArray() returned an error %v", err)
+	}
+	if err := enc.StartIndefiniteMap(); err != nil {
+		t.Errorf("StartIndefiniteMap() returned an error %v", err)
+	}
+
+	// StartIndefiniteXXX returns error when IndefLength = IndefLengthForbidden
+	em, _ := EncOptions{IndefLength: IndefLengthForbidden}.EncMode()
+	enc = em.NewEncoder(&buf)
+	wantErrorMsg := "cbor: indefinite-length byte string isn't allowed"
+	if err := enc.StartIndefiniteByteString(); err == nil {
+		t.Errorf("StartIndefiniteByteString() didn't return an error")
+	} else if _, ok := err.(*IndefiniteLengthError); !ok {
+		t.Errorf("StartIndefiniteByteString() error type %T, want *IndefiniteLengthError", err)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("StartIndefiniteByteString() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+
+	wantErrorMsg = "cbor: indefinite-length UTF-8 text string isn't allowed"
+	if err := enc.StartIndefiniteTextString(); err == nil {
+		t.Errorf("StartIndefiniteTextString() didn't return an error")
+	} else if _, ok := err.(*IndefiniteLengthError); !ok {
+		t.Errorf("StartIndefiniteTextString() error type %T, want *IndefiniteLengthError", err)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("StartIndefiniteTextString() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+
+	wantErrorMsg = "cbor: indefinite-length array isn't allowed"
+	if err := enc.StartIndefiniteArray(); err == nil {
+		t.Errorf("StartIndefiniteArray() didn't return an error")
+	} else if _, ok := err.(*IndefiniteLengthError); !ok {
+		t.Errorf("StartIndefiniteArray() error type %T, want *IndefiniteLengthError", err)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("StartIndefiniteArray() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+
+	wantErrorMsg = "cbor: indefinite-length map isn't allowed"
+	if err := enc.StartIndefiniteMap(); err == nil {
+		t.Errorf("StartIndefiniteMap() didn't return an error")
+	} else if _, ok := err.(*IndefiniteLengthError); !ok {
+		t.Errorf("StartIndefiniteMap() error type %T, want *IndefiniteLengthError", err)
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("StartIndefiniteMap() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+}
+
+func TestEncTagsMdOption(t *testing.T) {
+	// Default option allows encoding CBOR tags
+	tag := Tag{123, "hello"}
+	if _, err := Marshal(tag); err != nil {
+		t.Errorf("Marshal() returned an error %v", err)
+	}
+
+	// Create EncMode with TimeTag = EncTagRequired and TagsForbidden option returns error
+	wantErrorMsg := "cbor: cannot set TagsMd to TagsForbidden when TimeTag is EncTagRequired"
+	_, err := EncOptions{TimeTag: EncTagRequired, TagsMd: TagsForbidden}.EncMode()
+	if err == nil {
+		t.Errorf("EncModeWithTags() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("EncModeWithTags() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+
+	// Create EncMode with TagSet and TagsForbidden option returns error
+	wantErrorMsg = "cbor: cannot create EncMode with TagSet when TagsMd is TagsForbidden"
+	tags := NewTagSet()
+	_, err = EncOptions{TagsMd: TagsForbidden}.EncModeWithTags(tags)
+	if err == nil {
+		t.Errorf("EncModeWithTags() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("EncModeWithTags() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+	_, err = EncOptions{TagsMd: TagsForbidden}.EncModeWithSharedTags(tags)
+	if err == nil {
+		t.Errorf("EncModeWithSharedTags() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("EncModeWithSharedTags() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+
+	// Encoding Tag and TagsForbidden option returns error
+	wantErrorMsg = "cbor: cannot encode cbor.Tag when TagsMd is TagsForbidden"
+	em, _ := EncOptions{TagsMd: TagsForbidden}.EncMode()
+	if _, err := em.Marshal(&tag); err == nil {
+		t.Errorf("Marshal() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("Marshal() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+
+	// Encoding RawTag and TagsForbidden option returns error
+	wantErrorMsg = "cbor: cannot encode cbor.RawTag when TagsMd is TagsForbidden"
+	rawTag := RawTag{123, []byte{01}}
+	if _, err := em.Marshal(&rawTag); err == nil {
+		t.Errorf("Marshal() didn't return an error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("Marshal() returned error %q, want %q", err.Error(), wantErrorMsg)
+	}
+}
diff --git a/maxdepth_test.go b/maxdepth_test.go
deleted file mode 100644
index 9c7d7f3..0000000
--- a/maxdepth_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) Faye Amacker. All rights reserved.
-// Licensed under the MIT License. See LICENSE in the project root for license information.
-
-package cbor
-
-import (
-	"testing"
-)
-
-func TestDepth(t *testing.T) {
-	testCases := []struct {
-		name      string
-		cborData  []byte
-		wantDepth int
-	}{
-		{"uint", hexDecode("00"), 1},                                                          // 0
-		{"int", hexDecode("20"), 1},                                                           // -1
-		{"bool", hexDecode("f4"), 1},                                                          // false
-		{"nil", hexDecode("f6"), 1},                                                           // nil
-		{"float", hexDecode("fa47c35000"), 1},                                                 // 100000.0
-		{"byte string", hexDecode("40"), 1},                                                   // []byte{}
-		{"indefinite length byte string", hexDecode("5f42010243030405ff"), 1},                 // []byte{1, 2, 3, 4, 5}
-		{"text string", hexDecode("60"), 1},                                                   // ""
-		{"indefinite length text string", hexDecode("7f657374726561646d696e67ff"), 1},         // "streaming"
-		{"empty array", hexDecode("80"), 1},                                                   // []
-		{"indefinite length empty array", hexDecode("9fff"), 1},                               // []
-		{"array", hexDecode("98190102030405060708090a0b0c0d0e0f101112131415161718181819"), 2}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
-		{"indefinite length array", hexDecode("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"), 2}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
-		{"nested array", hexDecode("8301820203820405"), 3},                                                      // [1,[2,3],[4,5]]
-		{"indefinite length nested array", hexDecode("83018202039f0405ff"), 3},                                  // [1,[2,3],[4,5]]
-		{"array and map", hexDecode("826161a161626163"), 3},                                                     // [a", {"b": "c"}]
-		{"indefinite length array and map", hexDecode("826161bf61626163ff"), 3},                                 // [a", {"b": "c"}]
-		{"empty map", hexDecode("a0"), 1},                                                                       // {}
-		{"indefinite length empty map", hexDecode("bfff"), 1},                                                   // {}
-		{"map", hexDecode("a201020304"), 2},                                                                     // {1:2, 3:4}
-		{"nested map", hexDecode("a26161016162820203"), 3},                                                      // {"a": 1, "b": [2, 3]}
-		{"indefinite length nested map", hexDecode("bf61610161629f0203ffff"), 3},                                // {"a": 1, "b": [2, 3]}
-		{"tag", hexDecode("c074323031332d30332d32315432303a30343a30305a"), 1},                                   // 0("2013-03-21T20:04:00Z")
-		{"tagged map", hexDecode("d864a26161016162820203"), 3},                                                  // 100({"a": 1, "b": [2, 3]})
-		{"tagged map and array", hexDecode("d864a26161016162d865d866820203"), 4},                                // 100({"a": 1, "b": 101(102([2, 3]))})
-		{"nested tag", hexDecode("d864d865d86674323031332d30332d32315432303a30343a30305a"), 3},                  // 100(101(102("2013-03-21T20:04:00Z")))
-		{"32-level array", hexDecode("820181818181818181818181818181818181818181818181818181818181818101"), 32},
-		{"32-level indefinite length array", hexDecode("9f0181818181818181818181818181818181818181818181818181818181818101ff"), 32},
-		{"32-level map", hexDecode("a10181818181818181818181818181818181818181818181818181818181818101"), 32},
-		{"32-level indefinite length map", hexDecode("bf0181818181818181818181818181818181818181818181818181818181818101ff"), 32},
-		{"32-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), 32}, // 100(100(...("2013-03-21T20:04:00Z")))
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			_, depth, err := validInternal(tc.cborData, 0, 1)
-			if err != nil {
-				t.Errorf("valid(0x%x) returned error %v", tc.cborData, err)
-			}
-			if depth != tc.wantDepth {
-				t.Errorf("valid(0x%x) returned depth %d, want %d", tc.cborData, depth, tc.wantDepth)
-			}
-		})
-	}
-}
-
-func TestDepthError(t *testing.T) {
-	testCases := []struct {
-		name         string
-		cborData     []byte
-		wantErrorMsg string
-	}{
-		{"33-level array", hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"), "cbor: reached max depth 32"},
-		{"33-level indefinite length array", hexDecode("9f018181818181818181818181818181818181818181818181818181818181818101ff"), "cbor: reached max depth 32"},
-		{"33-level map", hexDecode("a1018181818181818181818181818181818181818181818181818181818181818101"), "cbor: reached max depth 32"},
-		{"33-level indefinite length map", hexDecode("bf018181818181818181818181818181818181818181818181818181818181818101ff"), "cbor: reached max depth 32"},
-		{"33-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), "cbor: reached max depth 32"},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			_, _, err := validInternal(tc.cborData, 0, 1)
-			if err == nil {
-				t.Errorf("valid(0x%x) didn't return an error, want %q", tc.cborData, tc.wantErrorMsg)
-			} else if err.Error() != tc.wantErrorMsg {
-				t.Errorf("valid(0x%x) returned error %q, want %q", tc.cborData, err.Error(), tc.wantErrorMsg)
-			}
-		})
-	}
-}
diff --git a/valid_test.go b/valid_test.go
new file mode 100644
index 0000000..0ee4aaf
--- /dev/null
+++ b/valid_test.go
@@ -0,0 +1,143 @@
+// Copyright (c) Faye Amacker. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+package cbor
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestValid(t *testing.T) {
+	var buf bytes.Buffer
+	for _, t := range marshalTests {
+		buf.Write(t.cborData)
+	}
+	d := decodeState{data: buf.Bytes(), dm: defaultDecMode}
+	for i := 0; i < len(marshalTests); i++ {
+		if err := d.valid(); err != nil {
+			t.Errorf("Valid() returned error %v", err)
+		}
+	}
+	if len(d.data) != d.off {
+		t.Errorf("Valid() didn't read all data, leftover data 0x%x", d.data[d.off:])
+	}
+}
+
+func TestDepth(t *testing.T) {
+	testCases := []struct {
+		name      string
+		cborData  []byte
+		wantDepth int
+	}{
+		{"uint", hexDecode("00"), 0},                                                          // 0
+		{"int", hexDecode("20"), 0},                                                           // -1
+		{"bool", hexDecode("f4"), 0},                                                          // false
+		{"nil", hexDecode("f6"), 0},                                                           // nil
+		{"float", hexDecode("fa47c35000"), 0},                                                 // 100000.0
+		{"byte string", hexDecode("40"), 0},                                                   // []byte{}
+		{"indefinite length byte string", hexDecode("5f42010243030405ff"), 0},                 // []byte{1, 2, 3, 4, 5}
+		{"text string", hexDecode("60"), 0},                                                   // ""
+		{"indefinite length text string", hexDecode("7f657374726561646d696e67ff"), 0},         // "streaming"
+		{"empty array", hexDecode("80"), 1},                                                   // []
+		{"indefinite length empty array", hexDecode("9fff"), 1},                               // []
+		{"array", hexDecode("98190102030405060708090a0b0c0d0e0f101112131415161718181819"), 1}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
+		{"indefinite length array", hexDecode("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"), 1}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
+		{"nested array", hexDecode("8301820203820405"), 2},                                                      // [1,[2,3],[4,5]]
+		{"indefinite length nested array", hexDecode("83018202039f0405ff"), 2},                                  // [1,[2,3],[4,5]]
+		{"array and map", hexDecode("826161a161626163"), 2},                                                     // [a", {"b": "c"}]
+		{"indefinite length array and map", hexDecode("826161bf61626163ff"), 2},                                 // [a", {"b": "c"}]
+		{"empty map", hexDecode("a0"), 1},                                                                       // {}
+		{"indefinite length empty map", hexDecode("bfff"), 1},                                                   // {}
+		{"map", hexDecode("a201020304"), 1},                                                                     // {1:2, 3:4}
+		{"nested map", hexDecode("a26161016162820203"), 2},                                                      // {"a": 1, "b": [2, 3]}
+		{"indefinite length nested map", hexDecode("bf61610161629f0203ffff"), 2},                                // {"a": 1, "b": [2, 3]}
+		{"tag", hexDecode("c074323031332d30332d32315432303a30343a30305a"), 0},                                   // 0("2013-03-21T20:04:00Z")
+		{"tagged map", hexDecode("d864a26161016162820203"), 2},                                                  // 100({"a": 1, "b": [2, 3]})
+		{"tagged map and array", hexDecode("d864a26161016162d865820203"), 2},                                    // 100({"a": 1, "b": 101([2, 3])})
+		{"tagged map and array", hexDecode("d864a26161016162d865d866820203"), 3},                                // 100({"a": 1, "b": 101(102([2, 3]))})
+		{"nested tag", hexDecode("d864d865d86674323031332d30332d32315432303a30343a30305a"), 2},                  // 100(101(102("2013-03-21T20:04:00Z")))
+		{"32-level array", hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"), 32},
+		{"32-level indefinite length array", hexDecode("9f018181818181818181818181818181818181818181818181818181818181818101ff"), 32},
+		{"32-level map", hexDecode("a1018181818181818181818181818181818181818181818181818181818181818101"), 32},
+		{"32-level indefinite length map", hexDecode("bf018181818181818181818181818181818181818181818181818181818181818101ff"), 32},
+		{"32-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), 32}, // 100(100(...("2013-03-21T20:04:00Z")))
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			d := decodeState{data: tc.cborData, dm: defaultDecMode}
+			depth, err := d.validInternal(0)
+			if err != nil {
+				t.Errorf("valid(0x%x) returned error %v", tc.cborData, err)
+			}
+			if depth != tc.wantDepth {
+				t.Errorf("valid(0x%x) returned depth %d, want %d", tc.cborData, depth, tc.wantDepth)
+			}
+		})
+	}
+}
+
+func TestDepthError(t *testing.T) {
+	testCases := []struct {
+		name         string
+		cborData     []byte
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "33-level array",
+			cborData:     hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
+			opts:         DecOptions{MaxNestedLevels: 4},
+			wantErrorMsg: "cbor: exceeded max nested level 4",
+		},
+		{
+			name:         "33-level array",
+			cborData:     hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
+			opts:         DecOptions{MaxNestedLevels: 10},
+			wantErrorMsg: "cbor: exceeded max nested level 10",
+		},
+		{
+			name:         "33-level array",
+			cborData:     hexDecode("8201818181818181818181818181818181818181818181818181818181818181818101"),
+			opts:         DecOptions{},
+			wantErrorMsg: "cbor: exceeded max nested level 32",
+		},
+		{
+			name:         "33-level indefinite length array",
+			cborData:     hexDecode("9f01818181818181818181818181818181818181818181818181818181818181818101ff"),
+			opts:         DecOptions{},
+			wantErrorMsg: "cbor: exceeded max nested level 32",
+		},
+		{
+			name:         "33-level map",
+			cborData:     hexDecode("a101818181818181818181818181818181818181818181818181818181818181818101"),
+			opts:         DecOptions{},
+			wantErrorMsg: "cbor: exceeded max nested level 32",
+		},
+		{
+			name:         "33-level indefinite length map",
+			cborData:     hexDecode("bf01818181818181818181818181818181818181818181818181818181818181818101ff"),
+			opts:         DecOptions{},
+			wantErrorMsg: "cbor: exceeded max nested level 32",
+		},
+		{
+			name:         "33-level tag",
+			cborData:     hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"),
+			opts:         DecOptions{},
+			wantErrorMsg: "cbor: exceeded max nested level 32",
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			dm, _ := tc.opts.decMode()
+			d := decodeState{data: tc.cborData, dm: dm}
+			if _, err := d.validInternal(0); err == nil {
+				t.Errorf("valid(0x%x) didn't return an error", tc.cborData)
+			} else if _, ok := err.(*MaxNestedLevelError); !ok {
+				t.Errorf("valid(0x%x) returned wrong error type %T, want (*MaxNestedLevelError)", tc.cborData, err)
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("valid(0x%x) returned error %q, want error %q", tc.cborData, err.Error(), tc.wantErrorMsg)
+			}
+		})
+	}
+}