Repository: fxamacker/cbor
Pull request #550 (2024-06-09 21:03:47)

Improve byte string format decoding options
This PR improves unreleased decoding options to be included in v2.7.0.

Changes include:
- Renamed `ByteSliceExpectedEncodingMode` to `ByteStringExpectedFormatMode`, also renamed related options
- Renamed `DecOptions.ByteSliceExpectedEncoding` to `DecOptions.ByteStringExpectedFormat`
- Added `ByteStringExpectedFormatError`, which is returned when decoding expected format fails
- Updated comments to improve docs. For example,

  ```Go
  // ByteStringExpectedFormatMode specifies how to decode CBOR byte string into Go byte slice
  // when the byte string is NOT enclosed in CBOR tag 21, 22, or 23.  An error is returned if
  // the CBOR byte string does not contain the expected format (e.g. base64) specified.
  // For tags 21-23, see "Expected Later Encoding for CBOR-to-JSON Converters"
  // in RFC 8949 Section 3.4.5.2.
  ```



Patch:
diff --git a/decode.go b/decode.go
index 06ee294..133b63f 100644
--- a/decode.go
+++ b/decode.go
@@ -222,6 +222,38 @@ func (e UnacceptableDataItemError) Error() string {
 	return fmt.Sprintf("cbor: data item of cbor type %s is not accepted by protocol: %s", e.CBORType, e.Message)
 }
 
+// ByteStringExpectedFormatError is returned when unmarshaling CBOR byte string fails when
+// using non-default ByteStringExpectedFormat decoding option that makes decoder expect
+// a specified format such as base64, hex, etc.
+type ByteStringExpectedFormatError struct {
+	expectedFormatOption ByteStringExpectedFormatMode
+	err                  error
+}
+
+func newByteStringExpectedFormatError(expectedFormatOption ByteStringExpectedFormatMode, err error) *ByteStringExpectedFormatError {
+	return &ByteStringExpectedFormatError{expectedFormatOption, err}
+}
+
+func (e *ByteStringExpectedFormatError) Error() string {
+	switch e.expectedFormatOption {
+	case ByteStringExpectedBase64URL:
+		return fmt.Sprintf("cbor: failed to decode base64url from byte string: %s", e.err)
+
+	case ByteStringExpectedBase64:
+		return fmt.Sprintf("cbor: failed to decode base64 from byte string: %s", e.err)
+
+	case ByteStringExpectedBase16:
+		return fmt.Sprintf("cbor: failed to decode hex from byte string: %s", e.err)
+
+	default:
+		return fmt.Sprintf("cbor: failed to decode byte string in expected format %d: %s", e.expectedFormatOption, e.err)
+	}
+}
+
+func (e *ByteStringExpectedFormatError) Unwrap() error {
+	return e.err
+}
+
 // DupMapKeyMode specifies how to enforce duplicate map key. Two map keys are considered duplicates if:
 //  1. When decoding into a struct, both keys match the same struct field. The keys are also
 //     considered duplicates if neither matches any field and decoding to interface{} would produce
@@ -602,32 +634,38 @@ func (bttm ByteStringToTimeMode) valid() bool {
 	return bttm >= 0 && bttm < maxByteStringToTimeMode
 }
 
-// ByteSliceExpectedEncodingMode specifies how to decode a byte string NOT enclosed in an "expected
-// later encoding" tag (RFC 8949 Section 3.4.5.2) into a Go byte slice.
-type ByteSliceExpectedEncodingMode int
+// ByteStringExpectedFormatMode specifies how to decode CBOR byte string into Go byte slice
+// when the byte string is NOT enclosed in CBOR tag 21, 22, or 23.  An error is returned if
+// the CBOR byte string does not contain the expected format (e.g. base64) specified.
+// For tags 21-23, see "Expected Later Encoding for CBOR-to-JSON Converters"
+// in RFC 8949 Section 3.4.5.2.
+type ByteStringExpectedFormatMode int
 
 const (
-	// ByteSliceExpectedEncodingIgnored copies the contents of the byte string, unmodified, into
-	// a destination Go byte slice.
-	ByteSliceExpectedEncodingIgnored = iota
-
-	// ByteSliceExpectedEncodingBase64URL assumes that byte strings with no text encoding hint
-	// contain base64url-encoded bytes.
-	ByteSliceExpectedEncodingBase64URL
-
-	// ByteSliceExpectedEncodingBase64 assumes that byte strings with no text encoding hint
-	// contain base64-encoded bytes.
-	ByteSliceExpectedEncodingBase64
-
-	// ByteSliceExpectedEncodingBase16 assumes that byte strings with no text encoding hint
-	// contain base16-encoded bytes.
-	ByteSliceExpectedEncodingBase16
-
-	maxByteSliceExpectedEncodingMode
+	// ByteStringExpectedFormatNone copies the unmodified CBOR byte string into Go byte slice
+	// if the byte string is not tagged by CBOR tag 21-23.
+	ByteStringExpectedFormatNone ByteStringExpectedFormatMode = iota
+
+	// ByteStringExpectedBase64URL expects CBOR byte strings to contain base64url-encoded bytes
+	// if the byte string is not tagged by CBOR tag 21-23.  The decoder will attempt to decode
+	// the base64url-encoded bytes into Go slice.
+	ByteStringExpectedBase64URL
+
+	// ByteStringExpectedBase64 expects CBOR byte strings to contain base64-encoded bytes
+	// if the byte string is not tagged by CBOR tag 21-23.  The decoder will attempt to decode
+	// the base64-encoded bytes into Go slice.
+	ByteStringExpectedBase64
+
+	// ByteStringExpectedBase16 expects CBOR byte strings to contain base16-encoded bytes
+	// if the byte string is not tagged by CBOR tag 21-23.  The decoder will attempt to decode
+	// the base16-encoded bytes into Go slice.
+	ByteStringExpectedBase16
+
+	maxByteStringExpectedFormatMode
 )
 
-func (bseem ByteSliceExpectedEncodingMode) valid() bool {
-	return bseem >= 0 && bseem < maxByteSliceExpectedEncodingMode
+func (bsefm ByteStringExpectedFormatMode) valid() bool {
+	return bsefm >= 0 && bsefm < maxByteStringExpectedFormatMode
 }
 
 // BignumTagMode specifies whether or not the "bignum" tags 2 and 3 (RFC 8949 Section 3.4.3) can be
@@ -761,7 +799,7 @@ type DecOptions struct {
 	// Currently, recognized CBOR tag numbers are 0, 1, 2, 3, or registered by TagSet.
 	UnrecognizedTagToAny UnrecognizedTagToAnyMode
 
-	// TimeTagToAnyMode specifies how to decode CBOR tag 0 and 1 into an empty interface (any).
+	// TimeTagToAny specifies how to decode CBOR tag 0 and 1 into an empty interface (any).
 	// Based on the specified mode, Unmarshal can return a time.Time value or a time string in a specific format.
 	TimeTagToAny TimeTagToAnyMode
 
@@ -783,12 +821,15 @@ type DecOptions struct {
 	// 25 through 27) representing positive or negative infinity.
 	Inf InfMode
 
-	// ByteStringToTimeMode specifies the behavior when decoding a CBOR byte string into a Go time.Time.
+	// ByteStringToTime specifies how to decode CBOR byte string into Go time.Time.
 	ByteStringToTime ByteStringToTimeMode
 
-	// ByteSliceExpectedEncodingMode specifies how to decode a byte string NOT enclosed in an
-	// "expected later encoding" tag (RFC 8949 Section 3.4.5.2) into a Go byte slice.
-	ByteSliceExpectedEncoding ByteSliceExpectedEncodingMode
+	// ByteStringExpectedFormat specifies how to decode CBOR byte string into Go byte slice
+	// when the byte string is NOT enclosed in CBOR tag 21, 22, or 23.  An error is returned if
+	// the CBOR byte string does not contain the expected format (e.g. base64) specified.
+	// For tags 21-23, see "Expected Later Encoding for CBOR-to-JSON Converters"
+	// in RFC 8949 Section 3.4.5.2.
+	ByteStringExpectedFormat ByteStringExpectedFormatMode
 
 	// BignumTag specifies whether or not the "bignum" tags 2 and 3 (RFC 8949 Section 3.4.3) can
 	// be decoded. Unlike BigIntDec, this option applies to all bignum tags encountered in a
@@ -815,7 +856,8 @@ func (opts DecOptions) validForTags(tags TagSet) error { //nolint:gocritic // ig
 	if tags == nil {
 		return errors.New("cbor: cannot create DecMode with nil value as TagSet")
 	}
-	if opts.ByteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding || opts.ByteSliceExpectedEncoding != ByteSliceExpectedEncodingIgnored {
+	if opts.ByteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding ||
+		opts.ByteStringExpectedFormat != ByteStringExpectedFormatNone {
 		for _, tagNum := range []uint64{
 			tagNumExpectedLaterEncodingBase64URL,
 			tagNumExpectedLaterEncodingBase64,
@@ -998,8 +1040,8 @@ func (opts DecOptions) decMode() (*decMode, error) { //nolint:gocritic // ignore
 		return nil, errors.New("cbor: invalid ByteStringToTime " + strconv.Itoa(int(opts.ByteStringToTime)))
 	}
 
-	if !opts.ByteSliceExpectedEncoding.valid() {
-		return nil, errors.New("cbor: invalid ByteSliceExpectedEncoding " + strconv.Itoa(int(opts.ByteSliceExpectedEncoding)))
+	if !opts.ByteStringExpectedFormat.valid() {
+		return nil, errors.New("cbor: invalid ByteStringExpectedFormat " + strconv.Itoa(int(opts.ByteStringExpectedFormat)))
 	}
 
 	if !opts.BignumTag.valid() {
@@ -1011,32 +1053,32 @@ func (opts DecOptions) decMode() (*decMode, error) { //nolint:gocritic // ignore
 	}
 
 	dm := decMode{
-		dupMapKey:                 opts.DupMapKey,
-		timeTag:                   opts.TimeTag,
-		maxNestedLevels:           opts.MaxNestedLevels,
-		maxArrayElements:          opts.MaxArrayElements,
-		maxMapPairs:               opts.MaxMapPairs,
-		indefLength:               opts.IndefLength,
-		tagsMd:                    opts.TagsMd,
-		intDec:                    opts.IntDec,
-		mapKeyByteString:          opts.MapKeyByteString,
-		extraReturnErrors:         opts.ExtraReturnErrors,
-		defaultMapType:            opts.DefaultMapType,
-		utf8:                      opts.UTF8,
-		fieldNameMatching:         opts.FieldNameMatching,
-		bigIntDec:                 opts.BigIntDec,
-		defaultByteStringType:     opts.DefaultByteStringType,
-		byteStringToString:        opts.ByteStringToString,
-		fieldNameByteString:       opts.FieldNameByteString,
-		unrecognizedTagToAny:      opts.UnrecognizedTagToAny,
-		timeTagToAny:              opts.TimeTagToAny,
-		simpleValues:              simpleValues,
-		nanDec:                    opts.NaN,
-		infDec:                    opts.Inf,
-		byteStringToTime:          opts.ByteStringToTime,
-		byteSliceExpectedEncoding: opts.ByteSliceExpectedEncoding,
-		bignumTag:                 opts.BignumTag,
-		binaryUnmarshaler:         opts.BinaryUnmarshaler,
+		dupMapKey:                opts.DupMapKey,
+		timeTag:                  opts.TimeTag,
+		maxNestedLevels:          opts.MaxNestedLevels,
+		maxArrayElements:         opts.MaxArrayElements,
+		maxMapPairs:              opts.MaxMapPairs,
+		indefLength:              opts.IndefLength,
+		tagsMd:                   opts.TagsMd,
+		intDec:                   opts.IntDec,
+		mapKeyByteString:         opts.MapKeyByteString,
+		extraReturnErrors:        opts.ExtraReturnErrors,
+		defaultMapType:           opts.DefaultMapType,
+		utf8:                     opts.UTF8,
+		fieldNameMatching:        opts.FieldNameMatching,
+		bigIntDec:                opts.BigIntDec,
+		defaultByteStringType:    opts.DefaultByteStringType,
+		byteStringToString:       opts.ByteStringToString,
+		fieldNameByteString:      opts.FieldNameByteString,
+		unrecognizedTagToAny:     opts.UnrecognizedTagToAny,
+		timeTagToAny:             opts.TimeTagToAny,
+		simpleValues:             simpleValues,
+		nanDec:                   opts.NaN,
+		infDec:                   opts.Inf,
+		byteStringToTime:         opts.ByteStringToTime,
+		byteStringExpectedFormat: opts.ByteStringExpectedFormat,
+		bignumTag:                opts.BignumTag,
+		binaryUnmarshaler:        opts.BinaryUnmarshaler,
 	}
 
 	return &dm, nil
@@ -1089,33 +1131,33 @@ type DecMode interface {
 }
 
 type decMode struct {
-	tags                      tagProvider
-	dupMapKey                 DupMapKeyMode
-	timeTag                   DecTagMode
-	maxNestedLevels           int
-	maxArrayElements          int
-	maxMapPairs               int
-	indefLength               IndefLengthMode
-	tagsMd                    TagsMode
-	intDec                    IntDecMode
-	mapKeyByteString          MapKeyByteStringMode
-	extraReturnErrors         ExtraDecErrorCond
-	defaultMapType            reflect.Type
-	utf8                      UTF8Mode
-	fieldNameMatching         FieldNameMatchingMode
-	bigIntDec                 BigIntDecMode
-	defaultByteStringType     reflect.Type
-	byteStringToString        ByteStringToStringMode
-	fieldNameByteString       FieldNameByteStringMode
-	unrecognizedTagToAny      UnrecognizedTagToAnyMode
-	timeTagToAny              TimeTagToAnyMode
-	simpleValues              *SimpleValueRegistry
-	nanDec                    NaNMode
-	infDec                    InfMode
-	byteStringToTime          ByteStringToTimeMode
-	byteSliceExpectedEncoding ByteSliceExpectedEncodingMode
-	bignumTag                 BignumTagMode
-	binaryUnmarshaler         BinaryUnmarshalerMode
+	tags                     tagProvider
+	dupMapKey                DupMapKeyMode
+	timeTag                  DecTagMode
+	maxNestedLevels          int
+	maxArrayElements         int
+	maxMapPairs              int
+	indefLength              IndefLengthMode
+	tagsMd                   TagsMode
+	intDec                   IntDecMode
+	mapKeyByteString         MapKeyByteStringMode
+	extraReturnErrors        ExtraDecErrorCond
+	defaultMapType           reflect.Type
+	utf8                     UTF8Mode
+	fieldNameMatching        FieldNameMatchingMode
+	bigIntDec                BigIntDecMode
+	defaultByteStringType    reflect.Type
+	byteStringToString       ByteStringToStringMode
+	fieldNameByteString      FieldNameByteStringMode
+	unrecognizedTagToAny     UnrecognizedTagToAnyMode
+	timeTagToAny             TimeTagToAnyMode
+	simpleValues             *SimpleValueRegistry
+	nanDec                   NaNMode
+	infDec                   InfMode
+	byteStringToTime         ByteStringToTimeMode
+	byteStringExpectedFormat ByteStringExpectedFormatMode
+	bignumTag                BignumTagMode
+	binaryUnmarshaler        BinaryUnmarshalerMode
 }
 
 var defaultDecMode, _ = DecOptions{}.decMode()
@@ -1130,32 +1172,32 @@ func (dm *decMode) DecOptions() DecOptions {
 	}
 
 	return DecOptions{
-		DupMapKey:                 dm.dupMapKey,
-		TimeTag:                   dm.timeTag,
-		MaxNestedLevels:           dm.maxNestedLevels,
-		MaxArrayElements:          dm.maxArrayElements,
-		MaxMapPairs:               dm.maxMapPairs,
-		IndefLength:               dm.indefLength,
-		TagsMd:                    dm.tagsMd,
-		IntDec:                    dm.intDec,
-		MapKeyByteString:          dm.mapKeyByteString,
-		ExtraReturnErrors:         dm.extraReturnErrors,
-		DefaultMapType:            dm.defaultMapType,
-		UTF8:                      dm.utf8,
-		FieldNameMatching:         dm.fieldNameMatching,
-		BigIntDec:                 dm.bigIntDec,
-		DefaultByteStringType:     dm.defaultByteStringType,
-		ByteStringToString:        dm.byteStringToString,
-		FieldNameByteString:       dm.fieldNameByteString,
-		UnrecognizedTagToAny:      dm.unrecognizedTagToAny,
-		TimeTagToAny:              dm.timeTagToAny,
-		SimpleValues:              simpleValues,
-		NaN:                       dm.nanDec,
-		Inf:                       dm.infDec,
-		ByteStringToTime:          dm.byteStringToTime,
-		ByteSliceExpectedEncoding: dm.byteSliceExpectedEncoding,
-		BignumTag:                 dm.bignumTag,
-		BinaryUnmarshaler:         dm.binaryUnmarshaler,
+		DupMapKey:                dm.dupMapKey,
+		TimeTag:                  dm.timeTag,
+		MaxNestedLevels:          dm.maxNestedLevels,
+		MaxArrayElements:         dm.maxArrayElements,
+		MaxMapPairs:              dm.maxMapPairs,
+		IndefLength:              dm.indefLength,
+		TagsMd:                   dm.tagsMd,
+		IntDec:                   dm.intDec,
+		MapKeyByteString:         dm.mapKeyByteString,
+		ExtraReturnErrors:        dm.extraReturnErrors,
+		DefaultMapType:           dm.defaultMapType,
+		UTF8:                     dm.utf8,
+		FieldNameMatching:        dm.fieldNameMatching,
+		BigIntDec:                dm.bigIntDec,
+		DefaultByteStringType:    dm.defaultByteStringType,
+		ByteStringToString:       dm.byteStringToString,
+		FieldNameByteString:      dm.fieldNameByteString,
+		UnrecognizedTagToAny:     dm.unrecognizedTagToAny,
+		TimeTagToAny:             dm.timeTagToAny,
+		SimpleValues:             simpleValues,
+		NaN:                      dm.nanDec,
+		Inf:                      dm.infDec,
+		ByteStringToTime:         dm.byteStringToTime,
+		ByteStringExpectedFormat: dm.byteStringExpectedFormat,
+		BignumTag:                dm.bignumTag,
+		BinaryUnmarshaler:        dm.binaryUnmarshaler,
 	}
 }
 
@@ -1531,7 +1573,7 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
 		case tagNumExpectedLaterEncodingBase64URL, tagNumExpectedLaterEncodingBase64, tagNumExpectedLaterEncodingBase16:
 			// If conversion for interoperability with text encodings is not configured,
 			// treat tags 21-23 as unregistered tags.
-			if d.dm.byteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding || d.dm.byteSliceExpectedEncoding != ByteSliceExpectedEncodingIgnored {
+			if d.dm.byteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding || d.dm.byteStringExpectedFormat != ByteStringExpectedFormatNone {
 				d.expectedLaterEncodingTags = append(d.expectedLaterEncodingTags, tagNum)
 				defer func() {
 					d.expectedLaterEncodingTags = d.expectedLaterEncodingTags[:len(d.expectedLaterEncodingTags)-1]
@@ -1923,7 +1965,8 @@ func (d *decoder) parse(skipSelfDescribedTag bool) (interface{}, error) { //noli
 		case tagNumExpectedLaterEncodingBase64URL, tagNumExpectedLaterEncodingBase64, tagNumExpectedLaterEncodingBase16:
 			// If conversion for interoperability with text encodings is not configured,
 			// treat tags 21-23 as unregistered tags.
-			if d.dm.byteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding || d.dm.byteSliceExpectedEncoding != ByteSliceExpectedEncodingIgnored {
+			if d.dm.byteStringToString == ByteStringToStringAllowedWithExpectedLaterEncoding ||
+				d.dm.byteStringExpectedFormat != ByteStringExpectedFormatNone {
 				d.expectedLaterEncodingTags = append(d.expectedLaterEncodingTags, tagNum)
 				defer func() {
 					d.expectedLaterEncodingTags = d.expectedLaterEncodingTags[:len(d.expectedLaterEncodingTags)-1]
@@ -2082,28 +2125,28 @@ func (d *decoder) applyByteStringTextConversion(
 			return src, false, nil
 		}
 
-		switch d.dm.byteSliceExpectedEncoding {
-		case ByteSliceExpectedEncodingBase64URL:
+		switch d.dm.byteStringExpectedFormat {
+		case ByteStringExpectedBase64URL:
 			decoded := make([]byte, base64.RawURLEncoding.DecodedLen(len(src)))
 			n, err := base64.RawURLEncoding.Decode(decoded, src)
 			if err != nil {
-				return nil, false, fmt.Errorf("cbor: failed to decode base64url string: %v", err)
+				return nil, false, newByteStringExpectedFormatError(ByteStringExpectedBase64URL, err)
 			}
 			return decoded[:n], true, nil
 
-		case ByteSliceExpectedEncodingBase64:
+		case ByteStringExpectedBase64:
 			decoded := make([]byte, base64.StdEncoding.DecodedLen(len(src)))
 			n, err := base64.StdEncoding.Decode(decoded, src)
 			if err != nil {
-				return nil, false, fmt.Errorf("cbor: failed to decode base64 string: %v", err)
+				return nil, false, newByteStringExpectedFormatError(ByteStringExpectedBase64, err)
 			}
 			return decoded[:n], true, nil
 
-		case ByteSliceExpectedEncodingBase16:
+		case ByteStringExpectedBase16:
 			decoded := make([]byte, hex.DecodedLen(len(src)))
 			n, err := hex.Decode(decoded, src)
 			if err != nil {
-				return nil, false, fmt.Errorf("cbor: failed to decode hex string: %v", err)
+				return nil, false, newByteStringExpectedFormatError(ByteStringExpectedBase16, err)
 			}
 			return decoded[:n], true, nil
 		}


Test patch:
diff --git a/decode_test.go b/decode_test.go
index b5060fc..dafdd41 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -4899,32 +4899,32 @@ func TestDecOptions(t *testing.T) {
 	}
 
 	opts1 := DecOptions{
-		DupMapKey:                 DupMapKeyEnforcedAPF,
-		TimeTag:                   DecTagRequired,
-		MaxNestedLevels:           100,
-		MaxArrayElements:          102,
-		MaxMapPairs:               101,
-		IndefLength:               IndefLengthForbidden,
-		TagsMd:                    TagsForbidden,
-		IntDec:                    IntDecConvertSigned,
-		MapKeyByteString:          MapKeyByteStringForbidden,
-		ExtraReturnErrors:         ExtraDecErrorUnknownField,
-		DefaultMapType:            reflect.TypeOf(map[string]interface{}(nil)),
-		UTF8:                      UTF8DecodeInvalid,
-		FieldNameMatching:         FieldNameMatchingCaseSensitive,
-		BigIntDec:                 BigIntDecodePointer,
-		DefaultByteStringType:     reflect.TypeOf(""),
-		ByteStringToString:        ByteStringToStringAllowed,
-		FieldNameByteString:       FieldNameByteStringAllowed,
-		UnrecognizedTagToAny:      UnrecognizedTagContentToAny,
-		TimeTagToAny:              TimeTagToRFC3339,
-		SimpleValues:              simpleValues,
-		NaN:                       NaNDecodeForbidden,
-		Inf:                       InfDecodeForbidden,
-		ByteStringToTime:          ByteStringToTimeAllowed,
-		ByteSliceExpectedEncoding: ByteSliceToByteStringWithExpectedConversionToBase64,
-		BignumTag:                 BignumTagForbidden,
-		BinaryUnmarshaler:         BinaryUnmarshalerNone,
+		DupMapKey:                DupMapKeyEnforcedAPF,
+		TimeTag:                  DecTagRequired,
+		MaxNestedLevels:          100,
+		MaxArrayElements:         102,
+		MaxMapPairs:              101,
+		IndefLength:              IndefLengthForbidden,
+		TagsMd:                   TagsForbidden,
+		IntDec:                   IntDecConvertSigned,
+		MapKeyByteString:         MapKeyByteStringForbidden,
+		ExtraReturnErrors:        ExtraDecErrorUnknownField,
+		DefaultMapType:           reflect.TypeOf(map[string]interface{}(nil)),
+		UTF8:                     UTF8DecodeInvalid,
+		FieldNameMatching:        FieldNameMatchingCaseSensitive,
+		BigIntDec:                BigIntDecodePointer,
+		DefaultByteStringType:    reflect.TypeOf(""),
+		ByteStringToString:       ByteStringToStringAllowed,
+		FieldNameByteString:      FieldNameByteStringAllowed,
+		UnrecognizedTagToAny:     UnrecognizedTagContentToAny,
+		TimeTagToAny:             TimeTagToRFC3339,
+		SimpleValues:             simpleValues,
+		NaN:                      NaNDecodeForbidden,
+		Inf:                      InfDecodeForbidden,
+		ByteStringToTime:         ByteStringToTimeAllowed,
+		ByteStringExpectedFormat: ByteSliceToByteStringWithExpectedConversionToBase64,
+		BignumTag:                BignumTagForbidden,
+		BinaryUnmarshaler:        BinaryUnmarshalerNone,
 	}
 	ov := reflect.ValueOf(opts1)
 	for i := 0; i < ov.NumField(); i++ {
@@ -9556,13 +9556,13 @@ func TestInvalidByteSliceExpectedEncodingMode(t *testing.T) {
 	}{
 		{
 			name:         "below range of valid modes",
-			opts:         DecOptions{ByteSliceExpectedEncoding: -1},
-			wantErrorMsg: "cbor: invalid ByteSliceExpectedEncoding -1",
+			opts:         DecOptions{ByteStringExpectedFormat: -1},
+			wantErrorMsg: "cbor: invalid ByteStringExpectedFormat -1",
 		},
 		{
 			name:         "above range of valid modes",
-			opts:         DecOptions{ByteSliceExpectedEncoding: 101},
-			wantErrorMsg: "cbor: invalid ByteSliceExpectedEncoding 101",
+			opts:         DecOptions{ByteStringExpectedFormat: 101},
+			wantErrorMsg: "cbor: invalid ByteStringExpectedFormat 101",
 		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
@@ -9603,7 +9603,7 @@ func TestDecOptionsConflictWithRegisteredTags(t *testing.T) {
 		},
 		{
 			name: "base64url encoding tag conflicts with non-default ByteSliceExpectedEncoding option",
-			opts: DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16},
+			opts: DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase16},
 			tags: func(tags TagSet) error {
 				return tags.Add(TagOptions{DecTag: DecTagOptional}, reflect.TypeOf(empty{}), 21)
 			},
@@ -9627,7 +9627,7 @@ func TestDecOptionsConflictWithRegisteredTags(t *testing.T) {
 		},
 		{
 			name: "base64 encoding tag conflicts with non-default ByteSliceExpectedEncoding option",
-			opts: DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16},
+			opts: DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase16},
 			tags: func(tags TagSet) error {
 				return tags.Add(TagOptions{DecTag: DecTagOptional}, reflect.TypeOf(empty{}), 22)
 			},
@@ -9651,7 +9651,7 @@ func TestDecOptionsConflictWithRegisteredTags(t *testing.T) {
 		},
 		{
 			name: "base16 encoding tag conflicts with non-default ByteSliceExpectedEncoding option",
-			opts: DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16},
+			opts: DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase16},
 			tags: func(tags TagSet) error {
 				return tags.Add(TagOptions{DecTag: DecTagOptional}, reflect.TypeOf(empty{}), 23)
 			},
@@ -9701,42 +9701,42 @@ func TestUnmarshalByteStringTextConversionError(t *testing.T) {
 	}{
 		{
 			name:    "reject untagged byte string containing invalid base64url",
-			opts:    DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64URL},
+			opts:    DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase64URL},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0x41, 0x00},
-			wantErr: "cbor: failed to decode base64url string: illegal base64 data at input byte 0",
+			wantErr: "cbor: failed to decode base64url from byte string: illegal base64 data at input byte 0",
 		},
 		{
 			name:    "reject untagged byte string containing invalid base64url",
-			opts:    DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64},
+			opts:    DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase64},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0x41, 0x00},
-			wantErr: "cbor: failed to decode base64 string: illegal base64 data at input byte 0",
+			wantErr: "cbor: failed to decode base64 from byte string: illegal base64 data at input byte 0",
 		},
 		{
 			name:    "reject untagged byte string containing invalid base16",
-			opts:    DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16},
+			opts:    DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase16},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0x41, 0x00},
-			wantErr: "cbor: failed to decode hex string: encoding/hex: invalid byte: U+0000",
+			wantErr: "cbor: failed to decode hex from byte string: encoding/hex: invalid byte: U+0000",
 		},
 		{
 			name:    "accept tagged byte string containing invalid base64url",
-			opts:    DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64URL},
+			opts:    DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase64URL},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd5, 0x41, 0x00},
 			wantErr: "",
 		},
 		{
 			name:    "accept tagged byte string containing invalid base64url",
-			opts:    DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64},
+			opts:    DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase64},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd5, 0x41, 0x00},
 			wantErr: "",
 		},
 		{
 			name:    "accept tagged byte string containing invalid base16",
-			opts:    DecOptions{ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16},
+			opts:    DecOptions{ByteStringExpectedFormat: ByteStringExpectedBase16},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd5, 0x41, 0x00},
 			wantErr: "",
@@ -9810,7 +9810,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "tagged into []byte with default encoding base64url",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64URL,
+				ByteStringExpectedFormat: ByteStringExpectedBase64URL,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd5, 0x41, 0xff}, // 21(h'ff')
@@ -9819,7 +9819,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "indirectly tagged into []byte with default encoding base64url",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64URL,
+				ByteStringExpectedFormat: ByteStringExpectedBase64URL,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd5, 0xd9, 0xd9, 0xf7, 0x41, 0xff}, // 21(55799(h'ff'))
@@ -9828,7 +9828,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "untagged base64url into []byte with default encoding base64url",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64URL,
+				ByteStringExpectedFormat: ByteStringExpectedBase64URL,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0x42, '_', 'w'}, // '_w'
@@ -9864,7 +9864,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "tagged into []byte with default encoding base64",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64,
+				ByteStringExpectedFormat: ByteStringExpectedBase64,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd6, 0x41, 0xff}, // 22(h'ff')
@@ -9873,7 +9873,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "indirectly tagged into []byte with default encoding base64",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64,
+				ByteStringExpectedFormat: ByteStringExpectedBase64,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd6, 0xd9, 0xd9, 0xf7, 0x41, 0xff}, // 22(55799(h'ff'))
@@ -9882,7 +9882,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "untagged base64 into []byte with default encoding base64",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase64,
+				ByteStringExpectedFormat: ByteStringExpectedBase64,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0x44, '/', 'w', '=', '='}, // '/w=='
@@ -9918,7 +9918,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "tagged into []byte with default encoding base16",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16,
+				ByteStringExpectedFormat: ByteStringExpectedBase16,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd7, 0x41, 0xff}, // 23(h'ff')
@@ -9927,7 +9927,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "indirectly tagged into []byte with default encoding base16",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16,
+				ByteStringExpectedFormat: ByteStringExpectedBase16,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0xd7, 0xd9, 0xd9, 0xf7, 0x41, 0xff}, // 23(55799(h'ff'))
@@ -9936,7 +9936,7 @@ func TestUnmarshalByteStringTextConversion(t *testing.T) {
 		{
 			name: "untagged base16 into []byte with default encoding base16",
 			opts: DecOptions{
-				ByteSliceExpectedEncoding: ByteSliceExpectedEncodingBase16,
+				ByteStringExpectedFormat: ByteStringExpectedBase16,
 			},
 			dstType: reflect.TypeOf([]byte{}),
 			in:      []byte{0x42, 'f', 'f'},
diff --git a/json_test.go b/json_test.go
index e1cc873..15cdebd 100644
--- a/json_test.go
+++ b/json_test.go
@@ -24,9 +24,9 @@ func TestStdlibJSONCompatibility(t *testing.T) {
 	}
 
 	dec, err := cbor.DecOptions{
-		DefaultByteStringType:     reflect.TypeOf(""),
-		ByteStringToString:        cbor.ByteStringToStringAllowedWithExpectedLaterEncoding,
-		ByteSliceExpectedEncoding: cbor.ByteSliceExpectedEncodingBase64,
+		DefaultByteStringType:    reflect.TypeOf(""),
+		ByteStringToString:       cbor.ByteStringToStringAllowedWithExpectedLaterEncoding,
+		ByteStringExpectedFormat: cbor.ByteStringExpectedBase64,
 	}.DecMode()
 	if err != nil {
 		t.Fatal(err)
diff --git a/tag_test.go b/tag_test.go
index f5917c4..7248eed 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -1503,13 +1503,13 @@ func TestEncodeBuiltinTag(t *testing.T) {
 		{
 			name: "unsigned bignum content not enclosed in expected encoding tag",
 			tag:  Tag{Number: tagNumUnsignedBignum, Content: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
-			opts: EncOptions{ByteSlice: ByteSliceExpectedEncodingBase16},
+			opts: EncOptions{ByteSlice: ByteSliceToByteStringWithExpectedConversionToBase16},
 			want: []byte{0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
 		},
 		{
 			name: "negative bignum content not enclosed in expected encoding tag",
 			tag:  Tag{Number: tagNumNegativeBignum, Content: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
-			opts: EncOptions{ByteSlice: ByteSliceExpectedEncodingBase16},
+			opts: EncOptions{ByteSlice: ByteSliceToByteStringWithExpectedConversionToBase16},
 			want: []byte{0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
 		},
 		{