Repository: fxamacker/cbor
Pull request #434 (2023-11-05 00:59:31)

Refactor valid() to reject negative values for integer modes
For modes of integer type, make `valid()` reject negative values since 0 is default and non-negative values are used.

Also reformat comments with gofmt.

Special thanks to @benluddy for adding this check in PR #432 for a new setting.  Basically, this PR does the same check as PR #432 for all other settings of integer type.


Patch:
diff --git a/decode.go b/decode.go
index 65be785..bc41565 100644
--- a/decode.go
+++ b/decode.go
@@ -226,7 +226,7 @@ const (
 )
 
 func (dmkm DupMapKeyMode) valid() bool {
-	return dmkm < maxDupMapKeyMode
+	return dmkm >= 0 && dmkm < maxDupMapKeyMode
 }
 
 // IndefLengthMode specifies whether to allow indefinite length items.
@@ -243,7 +243,7 @@ const (
 )
 
 func (m IndefLengthMode) valid() bool {
-	return m < maxIndefLengthMode
+	return m >= 0 && m < maxIndefLengthMode
 }
 
 // TagsMode specifies whether to allow CBOR tags.
@@ -260,7 +260,7 @@ const (
 )
 
 func (tm TagsMode) valid() bool {
-	return tm < maxTagsMode
+	return tm >= 0 && tm < maxTagsMode
 }
 
 // IntDecMode specifies which Go int type (int64 or uint64) should
@@ -282,7 +282,7 @@ const (
 )
 
 func (idm IntDecMode) valid() bool {
-	return idm < maxIntDec
+	return idm >= 0 && idm < maxIntDec
 }
 
 // MapKeyByteStringMode specifies how to decode CBOR byte string (major type 2)
@@ -312,7 +312,7 @@ const (
 )
 
 func (mkbsm MapKeyByteStringMode) valid() bool {
-	return mkbsm < maxMapKeyByteStringMode
+	return mkbsm >= 0 && mkbsm < maxMapKeyByteStringMode
 }
 
 // ExtraDecErrorCond specifies extra conditions that should be treated as errors.
@@ -350,7 +350,7 @@ const (
 )
 
 func (um UTF8Mode) valid() bool {
-	return um < maxUTF8Mode
+	return um >= 0 && um < maxUTF8Mode
 }
 
 // FieldNameMatchingMode specifies how string keys in CBOR maps are matched to Go struct field names.
@@ -1985,7 +1985,7 @@ var (
 	typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
 )
 
-func fillNil(t cborType, v reflect.Value) error {
+func fillNil(_ cborType, v reflect.Value) error {
 	switch v.Kind() {
 	case reflect.Slice, reflect.Map, reflect.Interface, reflect.Ptr:
 		v.Set(reflect.Zero(v.Type()))
diff --git a/doc.go b/doc.go
index b7e377f..23f68b9 100644
--- a/doc.go
+++ b/doc.go
@@ -17,17 +17,17 @@ For example, "toarray" tag makes struct fields encode to CBOR array elements.  A
 
 Latest docs can be viewed at https://github.com/fxamacker/cbor#cbor-library-in-go
 
-Basics
+# Basics
 
 The Quick Start guide is at https://github.com/fxamacker/cbor#quick-start
 
 Function signatures identical to encoding/json include:
 
-    Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode.
+	Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode.
 
 Standard interfaces include:
 
-    BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler.
+	BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler.
 
 Custom encoding and decoding is possible by implementing standard interfaces for
 user-defined Go types.
@@ -39,9 +39,9 @@ creating modes from options at runtime.
 
 EncMode and DecMode interfaces are created from EncOptions or DecOptions structs.
 
-    em, err := cbor.EncOptions{...}.EncMode()
-    em, err := cbor.CanonicalEncOptions().EncMode()
-    em, err := cbor.CTAP2EncOptions().EncMode()
+	em, err := cbor.EncOptions{...}.EncMode()
+	em, err := cbor.CanonicalEncOptions().EncMode()
+	em, err := cbor.CTAP2EncOptions().EncMode()
 
 Modes use immutable options to avoid side-effects and simplify concurrency. Behavior of
 modes won't accidentally change at runtime after they're created.
@@ -50,55 +50,55 @@ Modes are intended to be reused and are safe for concurrent use.
 
 EncMode and DecMode Interfaces
 
-    // EncMode interface uses immutable options and is safe for concurrent use.
-    type EncMode interface {
-	Marshal(v interface{}) ([]byte, error)
-	NewEncoder(w io.Writer) *Encoder
-	EncOptions() EncOptions  // returns copy of options
-    }
+	    // EncMode interface uses immutable options and is safe for concurrent use.
+	    type EncMode interface {
+		Marshal(v interface{}) ([]byte, error)
+		NewEncoder(w io.Writer) *Encoder
+		EncOptions() EncOptions  // returns copy of options
+	    }
 
-    // DecMode interface uses immutable options and is safe for concurrent use.
-    type DecMode interface {
-	Unmarshal(data []byte, v interface{}) error
-	NewDecoder(r io.Reader) *Decoder
-	DecOptions() DecOptions  // returns copy of options
-    }
+	    // DecMode interface uses immutable options and is safe for concurrent use.
+	    type DecMode interface {
+		Unmarshal(data []byte, v interface{}) error
+		NewDecoder(r io.Reader) *Decoder
+		DecOptions() DecOptions  // returns copy of options
+	    }
 
 Using Default Encoding Mode
 
-    b, err := cbor.Marshal(v)
+	b, err := cbor.Marshal(v)
 
-    encoder := cbor.NewEncoder(w)
-    err = encoder.Encode(v)
+	encoder := cbor.NewEncoder(w)
+	err = encoder.Encode(v)
 
 Using Default Decoding Mode
 
-    err := cbor.Unmarshal(b, &v)
+	err := cbor.Unmarshal(b, &v)
 
-    decoder := cbor.NewDecoder(r)
-    err = decoder.Decode(&v)
+	decoder := cbor.NewDecoder(r)
+	err = decoder.Decode(&v)
 
 Creating and Using Encoding Modes
 
-    // Create EncOptions using either struct literal or a function.
-    opts := cbor.CanonicalEncOptions()
+	// Create EncOptions using either struct literal or a function.
+	opts := cbor.CanonicalEncOptions()
 
-    // If needed, modify encoding options
-    opts.Time = cbor.TimeUnix
+	// If needed, modify encoding options
+	opts.Time = cbor.TimeUnix
 
-    // Create reusable EncMode interface with immutable options, safe for concurrent use.
-    em, err := opts.EncMode()
+	// Create reusable EncMode interface with immutable options, safe for concurrent use.
+	em, err := opts.EncMode()
 
-    // Use EncMode like encoding/json, with same function signatures.
-    b, err := em.Marshal(v)
-    // or
-    encoder := em.NewEncoder(w)
-    err := encoder.Encode(v)
+	// Use EncMode like encoding/json, with same function signatures.
+	b, err := em.Marshal(v)
+	// or
+	encoder := em.NewEncoder(w)
+	err := encoder.Encode(v)
 
-    // NOTE: Both em.Marshal(v) and encoder.Encode(v) use encoding options
-    // specified during creation of em (encoding mode).
+	// NOTE: Both em.Marshal(v) and encoder.Encode(v) use encoding options
+	// specified during creation of em (encoding mode).
 
-CBOR Options
+# CBOR Options
 
 Predefined Encoding Options: https://github.com/fxamacker/cbor#predefined-encoding-options
 
@@ -106,7 +106,7 @@ Encoding Options: https://github.com/fxamacker/cbor#encoding-options
 
 Decoding Options: https://github.com/fxamacker/cbor#decoding-options
 
-Struct Tags
+# Struct Tags
 
 Struct tags like `cbor:"name,omitempty"` and `json:"name,omitempty"` work as expected.
 If both struct tags are specified then `cbor` is used.
@@ -121,9 +121,9 @@ https://raw.githubusercontent.com/fxamacker/images/master/cbor/v2.0.0/cbor_easy_
 
 Struct tags are listed at https://github.com/fxamacker/cbor#struct-tags-1
 
-Tests and Fuzzing
+# Tests and Fuzzing
 
 Over 375 tests are included in this package. Cover-guided fuzzing is handled by
-fxamacker/cbor-fuzz.
+a private fuzzer that replaced fxamacker/cbor-fuzz years ago.
 */
 package cbor
diff --git a/encode.go b/encode.go
index 10255a6..afb6c8c 100644
--- a/encode.go
+++ b/encode.go
@@ -143,7 +143,7 @@ const (
 )
 
 func (sm SortMode) valid() bool {
-	return sm < maxSortMode
+	return sm >= 0 && sm < maxSortMode
 }
 
 // ShortestFloatMode specifies which floating-point format should
@@ -168,7 +168,7 @@ const (
 )
 
 func (sfm ShortestFloatMode) valid() bool {
-	return sfm < maxShortestFloat
+	return sfm >= 0 && sfm < maxShortestFloat
 }
 
 // NaNConvertMode specifies how to encode NaN and overrides ShortestFloatMode.
@@ -196,7 +196,7 @@ const (
 )
 
 func (ncm NaNConvertMode) valid() bool {
-	return ncm < maxNaNConvert
+	return ncm >= 0 && ncm < maxNaNConvert
 }
 
 // InfConvertMode specifies how to encode Infinity and overrides ShortestFloatMode.
@@ -214,7 +214,7 @@ const (
 )
 
 func (icm InfConvertMode) valid() bool {
-	return icm < maxInfConvert
+	return icm >= 0 && icm < maxInfConvert
 }
 
 // TimeMode specifies how to encode time.Time values.
@@ -241,7 +241,7 @@ const (
 )
 
 func (tm TimeMode) valid() bool {
-	return tm < maxTimeMode
+	return tm >= 0 && tm < maxTimeMode
 }
 
 // BigIntConvertMode specifies how to encode big.Int values.
@@ -261,7 +261,7 @@ const (
 )
 
 func (bim BigIntConvertMode) valid() bool {
-	return bim < maxBigIntConvert
+	return bim >= 0 && bim < maxBigIntConvert
 }
 
 // NilContainersMode specifies how to encode nil slices and maps.
@@ -280,7 +280,7 @@ const (
 )
 
 func (m NilContainersMode) valid() bool {
-	return m < maxNilContainersMode
+	return m >= 0 && m < maxNilContainersMode
 }
 
 // EncOptions specifies encoding options.
@@ -321,19 +321,18 @@ type EncOptions struct {
 // CanonicalEncOptions returns EncOptions for "Canonical CBOR" encoding,
 // defined in RFC 7049 Section 3.9 with the following rules:
 //
-//     1. "Integers must be as small as possible."
-//     2. "The expression of lengths in major types 2 through 5 must be as short as possible."
-//     3. The keys in every map must be sorted in length-first sorting order.
-//        See SortLengthFirst for details.
-//     4. "Indefinite-length items must be made into definite-length items."
-//     5. "If a protocol allows for IEEE floats, then additional canonicalization rules might
-//        need to be added.  One example rule might be to have all floats start as a 64-bit
-//        float, then do a test conversion to a 32-bit float; if the result is the same numeric
-//        value, use the shorter value and repeat the process with a test conversion to a
-//        16-bit float.  (This rule selects 16-bit float for positive and negative Infinity
-//        as well.)  Also, there are many representations for NaN.  If NaN is an allowed value,
-//        it must always be represented as 0xf97e00."
-//
+//  1. "Integers must be as small as possible."
+//  2. "The expression of lengths in major types 2 through 5 must be as short as possible."
+//  3. The keys in every map must be sorted in length-first sorting order.
+//     See SortLengthFirst for details.
+//  4. "Indefinite-length items must be made into definite-length items."
+//  5. "If a protocol allows for IEEE floats, then additional canonicalization rules might
+//     need to be added.  One example rule might be to have all floats start as a 64-bit
+//     float, then do a test conversion to a 32-bit float; if the result is the same numeric
+//     value, use the shorter value and repeat the process with a test conversion to a
+//     16-bit float.  (This rule selects 16-bit float for positive and negative Infinity
+//     as well.)  Also, there are many representations for NaN.  If NaN is an allowed value,
+//     it must always be represented as 0xf97e00."
 func CanonicalEncOptions() EncOptions {
 	return EncOptions{
 		Sort:          SortCanonical,
@@ -347,14 +346,13 @@ func CanonicalEncOptions() EncOptions {
 // CTAP2EncOptions returns EncOptions for "CTAP2 Canonical CBOR" encoding,
 // defined in CTAP specification, with the following rules:
 //
-//     1. "Integers must be encoded as small as possible."
-//     2. "The representations of any floating-point values are not changed."
-//     3. "The expression of lengths in major types 2 through 5 must be as short as possible."
-//     4. "Indefinite-length items must be made into definite-length items.""
-//     5. The keys in every map must be sorted in bytewise lexicographic order.
-//        See SortBytewiseLexical for details.
-//     6. "Tags as defined in Section 2.4 in [RFC7049] MUST NOT be present."
-//
+//  1. "Integers must be encoded as small as possible."
+//  2. "The representations of any floating-point values are not changed."
+//  3. "The expression of lengths in major types 2 through 5 must be as short as possible."
+//  4. "Indefinite-length items must be made into definite-length items.""
+//  5. The keys in every map must be sorted in bytewise lexicographic order.
+//     See SortBytewiseLexical for details.
+//  6. "Tags as defined in Section 2.4 in [RFC7049] MUST NOT be present."
 func CTAP2EncOptions() EncOptions {
 	return EncOptions{
 		Sort:          SortCTAP2,
@@ -369,14 +367,13 @@ func CTAP2EncOptions() EncOptions {
 // CoreDetEncOptions returns EncOptions for "Core Deterministic" encoding,
 // defined in RFC 7049bis with the following rules:
 //
-//     1. "Preferred serialization MUST be used. In particular, this means that arguments
-//        (see Section 3) for integers, lengths in major types 2 through 5, and tags MUST
-//        be as short as possible"
-//        "Floating point values also MUST use the shortest form that preserves the value"
-//     2. "Indefinite-length items MUST NOT appear."
-//     3. "The keys in every map MUST be sorted in the bytewise lexicographic order of
-//        their deterministic encodings."
-//
+//  1. "Preferred serialization MUST be used. In particular, this means that arguments
+//     (see Section 3) for integers, lengths in major types 2 through 5, and tags MUST
+//     be as short as possible"
+//     "Floating point values also MUST use the shortest form that preserves the value"
+//  2. "Indefinite-length items MUST NOT appear."
+//  3. "The keys in every map MUST be sorted in the bytewise lexicographic order of
+//     their deterministic encodings."
 func CoreDetEncOptions() EncOptions {
 	return EncOptions{
 		Sort:          SortCoreDeterministic,
@@ -390,19 +387,18 @@ func CoreDetEncOptions() EncOptions {
 // PreferredUnsortedEncOptions returns EncOptions for "Preferred Serialization" encoding,
 // defined in RFC 7049bis with the following rules:
 //
-//     1. "The preferred serialization always uses the shortest form of representing the argument
-//        (Section 3);"
-//     2. "it also uses the shortest floating-point encoding that preserves the value being
-//        encoded (see Section 5.5)."
-//        "The preferred encoding for a floating-point value is the shortest floating-point encoding
-//        that preserves its value, e.g., 0xf94580 for the number 5.5, and 0xfa45ad9c00 for the
-//        number 5555.5, unless the CBOR-based protocol specifically excludes the use of the shorter
-//        floating-point encodings. For NaN values, a shorter encoding is preferred if zero-padding
-//        the shorter significand towards the right reconstitutes the original NaN value (for many
-//        applications, the single NaN encoding 0xf97e00 will suffice)."
-//     3. "Definite length encoding is preferred whenever the length is known at the time the
-//        serialization of the item starts."
-//
+//  1. "The preferred serialization always uses the shortest form of representing the argument
+//     (Section 3);"
+//  2. "it also uses the shortest floating-point encoding that preserves the value being
+//     encoded (see Section 5.5)."
+//     "The preferred encoding for a floating-point value is the shortest floating-point encoding
+//     that preserves its value, e.g., 0xf94580 for the number 5.5, and 0xfa45ad9c00 for the
+//     number 5555.5, unless the CBOR-based protocol specifically excludes the use of the shorter
+//     floating-point encodings. For NaN values, a shorter encoding is preferred if zero-padding
+//     the shorter significand towards the right reconstitutes the original NaN value (for many
+//     applications, the single NaN encoding 0xf97e00 will suffice)."
+//  3. "Definite length encoding is preferred whenever the length is known at the time the
+//     serialization of the item starts."
 func PreferredUnsortedEncOptions() EncOptions {
 	return EncOptions{
 		Sort:          SortNone,
@@ -1409,7 +1405,7 @@ func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc {
 	}
 }
 
-func alwaysNotEmpty(v reflect.Value) (empty bool, err error) {
+func alwaysNotEmpty(_ reflect.Value) (empty bool, err error) {
 	return false, nil
 }
 
diff --git a/tag.go b/tag.go
index 5205aa6..aefb4d3 100644
--- a/tag.go
+++ b/tag.go
@@ -90,7 +90,7 @@ const (
 )
 
 func (dtm DecTagMode) valid() bool {
-	return dtm < maxDecTagMode
+	return dtm >= 0 && dtm < maxDecTagMode
 }
 
 // EncTagMode specifies how encoder handles tag number.
@@ -107,7 +107,7 @@ const (
 )
 
 func (etm EncTagMode) valid() bool {
-	return etm < maxEncTagMode
+	return etm >= 0 && etm < maxEncTagMode
 }
 
 // TagOptions specifies how encoder and decoder handle tag number.


Test patch:
diff --git a/decode_test.go b/decode_test.go
index e9dc84d..aa409a3 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -3294,22 +3294,58 @@ func testRoundTrip(t *testing.T, testCases []roundTripTest, em EncMode, dm DecMo
 }
 
 func TestDecModeInvalidTimeTag(t *testing.T) {
-	wantErrorMsg := "cbor: invalid TimeTag 101"
-	_, err := DecOptions{TimeTag: 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)
+	for _, tc := range []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         DecOptions{TimeTag: -1},
+			wantErrorMsg: "cbor: invalid TimeTag -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         DecOptions{TimeTag: 101},
+			wantErrorMsg: "cbor: invalid TimeTag 101",
+		},
+	} {
+		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 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)
+	for _, tc := range []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         DecOptions{DupMapKey: -1},
+			wantErrorMsg: "cbor: invalid DupMapKey -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         DecOptions{DupMapKey: 101},
+			wantErrorMsg: "cbor: invalid DupMapKey 101",
+		},
+	} {
+		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)
+			}
+		})
 	}
 }
 
@@ -3437,22 +3473,58 @@ func TestDecModeInvalidMaxArrayElements(t *testing.T) {
 }
 
 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)
+	for _, tc := range []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         DecOptions{IndefLength: -1},
+			wantErrorMsg: "cbor: invalid IndefLength -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         DecOptions{IndefLength: 101},
+			wantErrorMsg: "cbor: invalid IndefLength 101",
+		},
+	} {
+		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 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)
+	for _, tc := range []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         DecOptions{TagsMd: -1},
+			wantErrorMsg: "cbor: invalid TagsMd -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         DecOptions{TagsMd: 101},
+			wantErrorMsg: "cbor: invalid TagsMd 101",
+		},
+	} {
+		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)
+			}
+		})
 	}
 }
 
@@ -4605,12 +4677,30 @@ func TestDecTagsMdOption(t *testing.T) {
 }
 
 func TestDecModeInvalidIntDec(t *testing.T) {
-	wantErrorMsg := "cbor: invalid IntDec 101"
-	_, err := DecOptions{IntDec: 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)
+	for _, tc := range []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         DecOptions{IntDec: -1},
+			wantErrorMsg: "cbor: invalid IntDec -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         DecOptions{IntDec: 101},
+			wantErrorMsg: "cbor: invalid IntDec 101",
+		},
+	} {
+		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)
+			}
+		})
 	}
 }
 
@@ -4664,12 +4754,30 @@ func TestIntDec(t *testing.T) {
 }
 
 func TestDecModeInvalidMapKeyByteString(t *testing.T) {
-	wantErrorMsg := "cbor: invalid MapKeyByteString 101"
-	_, err := DecOptions{MapKeyByteString: 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)
+	for _, tc := range []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         DecOptions{MapKeyByteString: -1},
+			wantErrorMsg: "cbor: invalid MapKeyByteString -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         DecOptions{MapKeyByteString: 101},
+			wantErrorMsg: "cbor: invalid MapKeyByteString 101",
+		},
+	} {
+		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)
+			}
+		})
 	}
 }
 
@@ -4844,12 +4952,30 @@ func TestExtraErrorCondUnknownField(t *testing.T) {
 }
 
 func TestInvalidUTF8Mode(t *testing.T) {
-	wantErrorMsg := "cbor: invalid UTF8 2"
-	_, err := DecOptions{UTF8: 2}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         DecOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         DecOptions{UTF8: -1},
+			wantErrorMsg: "cbor: invalid UTF8 -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         DecOptions{UTF8: 101},
+			wantErrorMsg: "cbor: invalid UTF8 101",
+		},
+	} {
+		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)
+			}
+		})
 	}
 }
 
diff --git a/encode_test.go b/encode_test.go
index 15b4c6f..e5d22e0 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -1463,7 +1463,7 @@ type TReader struct {
 	X int
 }
 
-func (s TReader) Read(p []byte) (n int, err error) {
+func (s TReader) Read(_ []byte) (n int, err error) {
 	return 0, nil
 }
 
@@ -1864,12 +1864,30 @@ func parseTime(layout string, value string) time.Time {
 }
 
 func TestInvalidTimeMode(t *testing.T) {
-	wantErrorMsg := "cbor: invalid TimeMode 100"
-	_, err := EncOptions{Time: TimeMode(100)}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{Time: -1},
+			wantErrorMsg: "cbor: invalid TimeMode -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{Time: 101},
+			wantErrorMsg: "cbor: invalid TimeMode 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
@@ -2330,12 +2348,30 @@ func TestStructSort(t *testing.T) {
 }
 
 func TestInvalidSort(t *testing.T) {
-	wantErrorMsg := "cbor: invalid SortMode 100"
-	_, err := EncOptions{Sort: SortMode(100)}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{Sort: -1},
+			wantErrorMsg: "cbor: invalid SortMode -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{Sort: 101},
+			wantErrorMsg: "cbor: invalid SortMode 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
@@ -2635,103 +2671,103 @@ func TestShortestFloat16(t *testing.T) {
 }
 
 /*
-func TestShortestFloat32(t *testing.T) {
-	testCases := []struct {
-		name         string
-		f64          float64
-		wantCborData []byte
-	}{
-		// Data from RFC 7049 appendix A
-		{"Shrink to float32", 0.0, hexDecode("fa00000000")},
-		{"Shrink to float32", 1.0, hexDecode("fa3f800000")},
-		{"Shrink to float32", 1.5, hexDecode("fa3fc00000")},
-		{"Shrink to float32", 65504.0, hexDecode("fa477fe000")},
-		{"Shrink to float32", 5.960464477539063e-08, hexDecode("fa33800000")},
-		{"Shrink to float32", 6.103515625e-05, hexDecode("fa38800000")},
-		{"Shrink to float32", -4.0, hexDecode("fac0800000")},
-		// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
-		{"Shrink to float32", 0.333251953125, hexDecode("fa3eaaa000")},
-		// Data from 7049bis 4.2.1 and 5.5
-		{"Shrink to float32", 5.5, hexDecode("fa40b00000")},
-		// Data from RFC 7049 appendix A
-		{"Shrink to float32", 100000.0, hexDecode("fa47c35000")},
-		{"Shrink to float32", 3.4028234663852886e+38, hexDecode("fa7f7fffff")},
-		// Data from 7049bis 4.2.1 and 5.5
-		{"Shrink to float32", 5555.5, hexDecode("fa45ad9c00")},
-		{"Shrink to float32", 1000000.5, hexDecode("fa49742408")},
-		// Data from RFC 7049 appendix A
-		{"Shrink to float64", 1.0e+300, hexDecode("fb7e37e43c8800759c")},
-	}
-	em, err := EncOptions{ShortestFloat: ShortestFloat32}.EncMode()
-	if err != nil {
-		t.Errorf("EncMode() returned an error %v", err)
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			b, err := em.Marshal(tc.f64)
-			if err != nil {
-				t.Errorf("Marshal(%v) returned error %v", tc.f64, err)
-			} else if !bytes.Equal(b, tc.wantCborData) {
-				t.Errorf("Marshal(%v) = 0x%x, want 0x%x", tc.f64, b, tc.wantCborData)
-			}
-			var f64 float64
-			if err = Unmarshal(b, &f64); err != nil {
-				t.Errorf("Unmarshal(0x%x) returned error %v", b, err)
-			} else if f64 != tc.f64 {
-				t.Errorf("Unmarshal(0x%x) = %f, want %f", b, f64, tc.f64)
-			}
-		})
+	func TestShortestFloat32(t *testing.T) {
+		testCases := []struct {
+			name         string
+			f64          float64
+			wantCborData []byte
+		}{
+			// Data from RFC 7049 appendix A
+			{"Shrink to float32", 0.0, hexDecode("fa00000000")},
+			{"Shrink to float32", 1.0, hexDecode("fa3f800000")},
+			{"Shrink to float32", 1.5, hexDecode("fa3fc00000")},
+			{"Shrink to float32", 65504.0, hexDecode("fa477fe000")},
+			{"Shrink to float32", 5.960464477539063e-08, hexDecode("fa33800000")},
+			{"Shrink to float32", 6.103515625e-05, hexDecode("fa38800000")},
+			{"Shrink to float32", -4.0, hexDecode("fac0800000")},
+			// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
+			{"Shrink to float32", 0.333251953125, hexDecode("fa3eaaa000")},
+			// Data from 7049bis 4.2.1 and 5.5
+			{"Shrink to float32", 5.5, hexDecode("fa40b00000")},
+			// Data from RFC 7049 appendix A
+			{"Shrink to float32", 100000.0, hexDecode("fa47c35000")},
+			{"Shrink to float32", 3.4028234663852886e+38, hexDecode("fa7f7fffff")},
+			// Data from 7049bis 4.2.1 and 5.5
+			{"Shrink to float32", 5555.5, hexDecode("fa45ad9c00")},
+			{"Shrink to float32", 1000000.5, hexDecode("fa49742408")},
+			// Data from RFC 7049 appendix A
+			{"Shrink to float64", 1.0e+300, hexDecode("fb7e37e43c8800759c")},
+		}
+		em, err := EncOptions{ShortestFloat: ShortestFloat32}.EncMode()
+		if err != nil {
+			t.Errorf("EncMode() returned an error %v", err)
+		}
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				b, err := em.Marshal(tc.f64)
+				if err != nil {
+					t.Errorf("Marshal(%v) returned error %v", tc.f64, err)
+				} else if !bytes.Equal(b, tc.wantCborData) {
+					t.Errorf("Marshal(%v) = 0x%x, want 0x%x", tc.f64, b, tc.wantCborData)
+				}
+				var f64 float64
+				if err = Unmarshal(b, &f64); err != nil {
+					t.Errorf("Unmarshal(0x%x) returned error %v", b, err)
+				} else if f64 != tc.f64 {
+					t.Errorf("Unmarshal(0x%x) = %f, want %f", b, f64, tc.f64)
+				}
+			})
+		}
 	}
-}
 
-func TestShortestFloat64(t *testing.T) {
-	testCases := []struct {
-		name         string
-		f64          float64
-		wantCborData []byte
-	}{
-		// Data from RFC 7049 appendix A
-		{"Shrink to float64", 0.0, hexDecode("fb0000000000000000")},
-		{"Shrink to float64", 1.0, hexDecode("fb3ff0000000000000")},
-		{"Shrink to float64", 1.5, hexDecode("fb3ff8000000000000")},
-		{"Shrink to float64", 65504.0, hexDecode("fb40effc0000000000")},
-		{"Shrink to float64", 5.960464477539063e-08, hexDecode("fb3e70000000000000")},
-		{"Shrink to float64", 6.103515625e-05, hexDecode("fb3f10000000000000")},
-		{"Shrink to float64", -4.0, hexDecode("fbc010000000000000")},
-		// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
-		{"Shrink to float64", 0.333251953125, hexDecode("fb3fd5540000000000")},
-		// Data from 7049bis 4.2.1 and 5.5
-		{"Shrink to float64", 5.5, hexDecode("fb4016000000000000")},
-		// Data from RFC 7049 appendix A
-		{"Shrink to float64", 100000.0, hexDecode("fb40f86a0000000000")},
-		{"Shrink to float64", 3.4028234663852886e+38, hexDecode("fb47efffffe0000000")},
-		// Data from 7049bis 4.2.1 and 5.5
-		{"Shrink to float64", 5555.5, hexDecode("fb40b5b38000000000")},
-		{"Shrink to float64", 1000000.5, hexDecode("fb412e848100000000")},
-		// Data from RFC 7049 appendix A
-		{"Shrink to float64", 1.0e+300, hexDecode("fb7e37e43c8800759c")},
-	}
-	em, err := EncOptions{ShortestFloat: ShortestFloat64}.EncMode()
-	if err != nil {
-		t.Errorf("EncMode() returned an error %v", err)
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			b, err := em.Marshal(tc.f64)
-			if err != nil {
-				t.Errorf("Marshal(%v) returned error %v", tc.f64, err)
-			} else if !bytes.Equal(b, tc.wantCborData) {
-				t.Errorf("Marshal(%v) = 0x%x, want 0x%x", tc.f64, b, tc.wantCborData)
-			}
-			var f64 float64
-			if err = Unmarshal(b, &f64); err != nil {
-				t.Errorf("Unmarshal(0x%x) returned error %v", b, err)
-			} else if f64 != tc.f64 {
-				t.Errorf("Unmarshal(0x%x) = %f, want %f", b, f64, tc.f64)
-			}
-		})
+	func TestShortestFloat64(t *testing.T) {
+		testCases := []struct {
+			name         string
+			f64          float64
+			wantCborData []byte
+		}{
+			// Data from RFC 7049 appendix A
+			{"Shrink to float64", 0.0, hexDecode("fb0000000000000000")},
+			{"Shrink to float64", 1.0, hexDecode("fb3ff0000000000000")},
+			{"Shrink to float64", 1.5, hexDecode("fb3ff8000000000000")},
+			{"Shrink to float64", 65504.0, hexDecode("fb40effc0000000000")},
+			{"Shrink to float64", 5.960464477539063e-08, hexDecode("fb3e70000000000000")},
+			{"Shrink to float64", 6.103515625e-05, hexDecode("fb3f10000000000000")},
+			{"Shrink to float64", -4.0, hexDecode("fbc010000000000000")},
+			// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
+			{"Shrink to float64", 0.333251953125, hexDecode("fb3fd5540000000000")},
+			// Data from 7049bis 4.2.1 and 5.5
+			{"Shrink to float64", 5.5, hexDecode("fb4016000000000000")},
+			// Data from RFC 7049 appendix A
+			{"Shrink to float64", 100000.0, hexDecode("fb40f86a0000000000")},
+			{"Shrink to float64", 3.4028234663852886e+38, hexDecode("fb47efffffe0000000")},
+			// Data from 7049bis 4.2.1 and 5.5
+			{"Shrink to float64", 5555.5, hexDecode("fb40b5b38000000000")},
+			{"Shrink to float64", 1000000.5, hexDecode("fb412e848100000000")},
+			// Data from RFC 7049 appendix A
+			{"Shrink to float64", 1.0e+300, hexDecode("fb7e37e43c8800759c")},
+		}
+		em, err := EncOptions{ShortestFloat: ShortestFloat64}.EncMode()
+		if err != nil {
+			t.Errorf("EncMode() returned an error %v", err)
+		}
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				b, err := em.Marshal(tc.f64)
+				if err != nil {
+					t.Errorf("Marshal(%v) returned error %v", tc.f64, err)
+				} else if !bytes.Equal(b, tc.wantCborData) {
+					t.Errorf("Marshal(%v) = 0x%x, want 0x%x", tc.f64, b, tc.wantCborData)
+				}
+				var f64 float64
+				if err = Unmarshal(b, &f64); err != nil {
+					t.Errorf("Unmarshal(0x%x) returned error %v", b, err)
+				} else if f64 != tc.f64 {
+					t.Errorf("Unmarshal(0x%x) = %f, want %f", b, f64, tc.f64)
+				}
+			})
+		}
 	}
-}
 */
 func TestShortestFloatNone(t *testing.T) {
 	testCases := []struct {
@@ -2804,12 +2840,30 @@ func TestShortestFloatNone(t *testing.T) {
 }
 
 func TestInvalidShortestFloat(t *testing.T) {
-	wantErrorMsg := "cbor: invalid ShortestFloatMode 100"
-	_, err := EncOptions{ShortestFloat: ShortestFloatMode(100)}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{ShortestFloat: -1},
+			wantErrorMsg: "cbor: invalid ShortestFloatMode -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{ShortestFloat: 101},
+			wantErrorMsg: "cbor: invalid ShortestFloatMode 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
@@ -2848,12 +2902,30 @@ func TestInfConvert(t *testing.T) {
 }
 
 func TestInvalidInfConvert(t *testing.T) {
-	wantErrorMsg := "cbor: invalid InfConvertMode 100"
-	_, err := EncOptions{InfConvert: InfConvertMode(100)}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{InfConvert: -1},
+			wantErrorMsg: "cbor: invalid InfConvertMode -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{InfConvert: 101},
+			wantErrorMsg: "cbor: invalid InfConvertMode 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
@@ -2893,12 +2965,30 @@ func TestNilContainers(t *testing.T) {
 }
 
 func TestInvalidNilContainers(t *testing.T) {
-	wantErrorMsg := "cbor: invalid NilContainers 100"
-	_, err := EncOptions{NilContainers: NilContainersMode(100)}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{NilContainers: -1},
+			wantErrorMsg: "cbor: invalid NilContainers -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{NilContainers: 101},
+			wantErrorMsg: "cbor: invalid NilContainers 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
@@ -3150,12 +3240,30 @@ func TestNaNConvert(t *testing.T) {
 }
 
 func TestInvalidNaNConvert(t *testing.T) {
-	wantErrorMsg := "cbor: invalid NaNConvertMode 100"
-	_, err := EncOptions{NaNConvert: NaNConvertMode(100)}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{NaNConvert: -1},
+			wantErrorMsg: "cbor: invalid NaNConvertMode -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{NaNConvert: 101},
+			wantErrorMsg: "cbor: invalid NaNConvertMode 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
@@ -3317,32 +3425,86 @@ 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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{IndefLength: -1},
+			wantErrorMsg: "cbor: invalid IndefLength -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{IndefLength: 101},
+			wantErrorMsg: "cbor: invalid IndefLength 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{TagsMd: -1},
+			wantErrorMsg: "cbor: invalid TagsMd -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{TagsMd: 101},
+			wantErrorMsg: "cbor: invalid TagsMd 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
 func TestEncModeInvalidBigIntConvertMode(t *testing.T) {
-	wantErrorMsg := "cbor: invalid BigIntConvertMode 101"
-	_, err := EncOptions{BigIntConvert: 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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{BigIntConvert: -1},
+			wantErrorMsg: "cbor: invalid BigIntConvertMode -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{BigIntConvert: 101},
+			wantErrorMsg: "cbor: invalid BigIntConvertMode 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }
 
@@ -3371,12 +3533,30 @@ func TestEncOptions(t *testing.T) {
 }
 
 func TestEncModeInvalidTimeTag(t *testing.T) {
-	wantErrorMsg := "cbor: invalid TimeTag 100"
-	_, err := EncOptions{TimeTag: 100}.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)
+	for _, tc := range []struct {
+		name         string
+		opts         EncOptions
+		wantErrorMsg string
+	}{
+		{
+			name:         "below range of valid modes",
+			opts:         EncOptions{TimeTag: -1},
+			wantErrorMsg: "cbor: invalid TimeTag -1",
+		},
+		{
+			name:         "above range of valid modes",
+			opts:         EncOptions{TimeTag: 101},
+			wantErrorMsg: "cbor: invalid TimeTag 101",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := tc.opts.EncMode()
+			if err == nil {
+				t.Errorf("EncMode() didn't return an error")
+			} else if err.Error() != tc.wantErrorMsg {
+				t.Errorf("EncMode() returned error %q, want %q", err.Error(), tc.wantErrorMsg)
+			}
+		})
 	}
 }