diff --git a/decode.go b/decode.go index f7ec8822..bf35dedf 100644 --- a/decode.go +++ b/decode.go @@ -731,14 +731,7 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin return fillTextString(t, b, v) case cborTypePrimitives: _, ai, val := d.getHead() - if ai < 20 || ai == 24 { - return fillPositiveInt(t, val, v) - } switch ai { - case 20, 21: - return fillBool(t, ai == 21, v) - case 22, 23: - return fillNil(t, v) case 25: f := float64(float16.Frombits(uint16(val)).Float32()) return fillFloat(t, f, v) @@ -748,7 +741,22 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin case 27: f := math.Float64frombits(val) return fillFloat(t, f, v) + default: // ai <= 24 + // Decode simple values (including false, true, null, and undefined) + if tInfo.nonPtrType == typeSimpleValue { + v.SetUint(val) + return nil + } + switch ai { + case 20, 21: + return fillBool(t, ai == 21, v) + case 22, 23: + return fillNil(t, v) + default: + return fillPositiveInt(t, val, v) + } } + case cborTypeTag: _, _, tagNum := d.getHead() switch tagNum { @@ -1034,7 +1042,7 @@ func (d *decoder) parse(skipSelfDescribedTag bool) (interface{}, error) { //noli case cborTypePrimitives: _, ai, val := d.getHead() if ai < 20 || ai == 24 { - return val, nil + return SimpleValue(val), nil } switch ai { case 20, 21: diff --git a/decode_test.go b/decode_test.go index 3c1725e9..edc0ce89 100644 --- a/decode_test.go +++ b/decode_test.go @@ -431,31 +431,31 @@ var unmarshalTests = []unmarshalTest{ { hexDecode("f4"), false, - []interface{}{false}, + []interface{}{false, SimpleValue(20)}, []reflect.Type{typeUint8, typeUint16, typeUint32, typeUint64, typeInt8, typeInt16, typeInt32, typeInt64, typeFloat32, typeFloat64, typeByteArray, typeByteSlice, typeString, typeIntSlice, typeMapStringInt, typeTag, typeRawTag, typeBigInt}, }, { hexDecode("f5"), true, - []interface{}{true}, + []interface{}{true, SimpleValue(21)}, []reflect.Type{typeUint8, typeUint16, typeUint32, typeUint64, typeInt8, typeInt16, typeInt32, typeInt64, typeFloat32, typeFloat64, typeByteArray, typeByteSlice, typeString, typeIntSlice, typeMapStringInt, typeTag, typeRawTag, typeBigInt}, }, { hexDecode("f6"), nil, - []interface{}{false, uint(0), uint8(0), uint16(0), uint32(0), uint64(0), int(0), int8(0), int16(0), int32(0), int64(0), float32(0.0), float64(0.0), "", []byte(nil), []int(nil), []string(nil), map[string]int(nil), time.Time{}, bigIntOrPanic("0"), Tag{}, RawTag{}}, + []interface{}{SimpleValue(22), false, uint(0), uint8(0), uint16(0), uint32(0), uint64(0), int(0), int8(0), int16(0), int32(0), int64(0), float32(0.0), float64(0.0), "", []byte(nil), []int(nil), []string(nil), map[string]int(nil), time.Time{}, bigIntOrPanic("0"), Tag{}, RawTag{}}, nil, }, { hexDecode("f7"), nil, - []interface{}{false, uint(0), uint8(0), uint16(0), uint32(0), uint64(0), int(0), int8(0), int16(0), int32(0), int64(0), float32(0.0), float64(0.0), "", []byte(nil), []int(nil), []string(nil), map[string]int(nil), time.Time{}, bigIntOrPanic("0"), Tag{}, RawTag{}}, + []interface{}{SimpleValue(23), false, uint(0), uint8(0), uint16(0), uint32(0), uint64(0), int(0), int8(0), int16(0), int32(0), int64(0), float32(0.0), float64(0.0), "", []byte(nil), []int(nil), []string(nil), map[string]int(nil), time.Time{}, bigIntOrPanic("0"), Tag{}, RawTag{}}, nil, }, { hexDecode("f0"), - uint64(16), - []interface{}{uint8(16), uint16(16), uint32(16), uint64(16), uint(16), int8(16), int16(16), int32(16), int64(16), int(16), float32(16), float64(16), bigIntOrPanic("16")}, + SimpleValue(16), + []interface{}{SimpleValue(16), uint8(16), uint16(16), uint32(16), uint64(16), uint(16), int8(16), int16(16), int32(16), int64(16), int(16), float32(16), float64(16), bigIntOrPanic("16")}, []reflect.Type{typeByteSlice, typeString, typeBool, typeByteArray, typeIntSlice, typeMapStringInt, typeTag, typeRawTag}, }, // This example is not well-formed because Simple value (with 5-bit value 24) must be >= 32. @@ -471,14 +471,14 @@ var unmarshalTests = []unmarshalTest{ */ { hexDecode("f820"), - uint64(32), - []interface{}{uint8(32), uint16(32), uint32(32), uint64(32), uint(32), int8(32), int16(32), int32(32), int64(32), int(32), float32(32), float64(32), bigIntOrPanic("32")}, + SimpleValue(32), + []interface{}{SimpleValue(32), uint8(32), uint16(32), uint32(32), uint64(32), uint(32), int8(32), int16(32), int32(32), int64(32), int(32), float32(32), float64(32), bigIntOrPanic("32")}, []reflect.Type{typeByteSlice, typeString, typeBool, typeByteArray, typeIntSlice, typeMapStringInt, typeTag, typeRawTag}, }, { hexDecode("f8ff"), - uint64(255), - []interface{}{uint8(255), uint16(255), uint32(255), uint64(255), uint(255), int16(255), int32(255), int64(255), int(255), float32(255), float64(255), bigIntOrPanic("255")}, + SimpleValue(255), + []interface{}{SimpleValue(255), uint8(255), uint16(255), uint32(255), uint64(255), uint(255), int16(255), int32(255), int64(255), int(255), float32(255), float64(255), bigIntOrPanic("255")}, []reflect.Type{typeByteSlice, typeString, typeBool, typeByteArray, typeIntSlice, typeMapStringInt, typeTag, typeRawTag}, }, // More testcases not covered by https://tools.ietf.org/html/rfc7049#appendix-A. diff --git a/encode.go b/encode.go index 9081eba3..2662caa8 100644 --- a/encode.go +++ b/encode.go @@ -1250,6 +1250,14 @@ func encodeTag(e *encoderBuffer, em *encMode, v reflect.Value) error { return nil } +func encodeSimpleValue(e *encoderBuffer, em *encMode, v reflect.Value) error { + if b := em.encTagBytes(v.Type()); b != nil { + e.Write(b) + } + encodeHead(e, byte(cborTypePrimitives), v.Uint()) + return nil +} + func encodeHead(e *encoderBuffer, t byte, n uint64) { if n <= 23 { e.WriteByte(t | byte(n)) @@ -1290,6 +1298,8 @@ func getEncodeFuncInternal(t reflect.Type) (encodeFunc, isEmptyFunc) { return getEncodeIndirectValueFunc(t), isEmptyPtr } switch t { + case typeSimpleValue: + return encodeSimpleValue, isEmptyUint case typeTag: return encodeTag, alwaysNotEmpty case typeTime: diff --git a/encode_test.go b/encode_test.go index 229f7555..0c53fc09 100644 --- a/encode_test.go +++ b/encode_test.go @@ -260,6 +260,11 @@ var marshalTests = []marshalTest{ {hexDecode("f4"), []interface{}{false}}, {hexDecode("f5"), []interface{}{true}}, {hexDecode("f6"), []interface{}{nil, []byte(nil), []int(nil), map[uint]bool(nil), (*int)(nil), io.Reader(nil)}}, + // simple values + {hexDecode("e0"), []interface{}{SimpleValue(0)}}, + {hexDecode("f0"), []interface{}{SimpleValue(16)}}, + {hexDecode("f820"), []interface{}{SimpleValue(32)}}, + {hexDecode("f8ff"), []interface{}{SimpleValue(255)}}, // nan, positive and negative inf {hexDecode("f97c00"), []interface{}{math.Inf(1)}}, {hexDecode("f97e00"), []interface{}{math.NaN()}}, @@ -3590,3 +3595,61 @@ func TestMarshalNegBigInt(t *testing.T) { }) } } + +func TestStructWithSimpleValueFields(t *testing.T) { + type T struct { + SV1 SimpleValue `cbor:",omitempty"` // omit empty + SV2 SimpleValue + } + + v1 := T{} + want1 := []byte{0xa1, 0x63, 0x53, 0x56, 0x32, 0xe0} + + v2 := T{SV1: SimpleValue(1), SV2: SimpleValue(255)} + want2 := []byte{ + 0xa2, + 0x63, 0x53, 0x56, 0x31, 0xe1, + 0x63, 0x53, 0x56, 0x32, 0xf8, 0xff, + } + + em, _ := EncOptions{}.EncMode() + dm, _ := DecOptions{}.DecMode() + tests := []roundTripTest{ + {"default values", v1, want1}, + {"non-default values", v2, want2}} + testRoundTrip(t, tests, em, dm) +} + +func TestMapWithSimpleValueKey(t *testing.T) { + data := []byte{0xa2, 0x00, 0x00, 0xe0, 0x00} // {0: 0, simple(0): 0} + + // Decode CBOR map with positive integer 0 and simple value 0 as keys. + // No map key duplication is detected because keys are of different CBOR types. + // RFC 8949 Section 5.6.1 says "a simple value 2 is not equivalent to an integer 2". + decOpts := DecOptions{ + DupMapKey: DupMapKeyEnforcedAPF, // duplicated key not allowed + } + decMode, _ := decOpts.DecMode() + + var v map[interface{}]interface{} + err := decMode.Unmarshal(data, &v) + if err != nil { + t.Errorf("Unmarshal(0x%x) returned error %v", data, err) + } + + // Encode decoded Go map. + encOpts := EncOptions{ + Sort: SortBytewiseLexical, + } + encMode, _ := encOpts.EncMode() + + encodedData, err := encMode.Marshal(v) + if err != nil { + t.Errorf("Marshal(%v) returned error %v", v, err) + } + + // Test roundtrip produces identical CBOR data. + if !bytes.Equal(data, encodedData) { + t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, encodedData, data) + } +} diff --git a/simplevalue.go b/simplevalue.go new file mode 100644 index 00000000..ec77a946 --- /dev/null +++ b/simplevalue.go @@ -0,0 +1,17 @@ +package cbor + +import "reflect" + +// SimpleValue represents CBOR simple value. +// CBOR simple value is: +// * an extension point like CBOR tag. +// * a subset of CBOR major type 7 that isn't floating-point. +// * "identified by a number between 0 and 255, but distinct from that number itself". +// For example, "a simple value 2 is not equivalent to an integer 2" as a CBOR map key. +// CBOR simple values identified by 20..23 are: "false", "true" , "null", and "undefined". +// Other CBOR simple values are currently unassigned/reserved by IANA. +type SimpleValue uint8 + +var ( + typeSimpleValue = reflect.TypeOf(SimpleValue(0)) +)