diff --git a/encode.go b/encode.go index b45147f5..ebef78f1 100644 --- a/encode.go +++ b/encode.go @@ -1648,8 +1648,21 @@ func encodeTag(e *bytes.Buffer, em *encMode, v reflect.Value) error { // Marshal tag number encodeHead(e, byte(cborTypeTag), t.Number) + vem := *em // shallow copy + + // For built-in tags, disable settings that may introduce tag validity errors when + // marshaling certain Content values. + switch t.Number { + case tagNumRFC3339Time: + vem.stringType = StringToTextString + vem.stringMajorType = cborTypeTextString + case tagNumUnsignedBignum, tagNumNegativeBignum: + vem.byteSlice = ByteSliceToByteString + vem.byteSliceEncodingTag = 0 + } + // Marshal tag content - return encode(e, em, reflect.ValueOf(t.Content)) + return encode(e, &vem, reflect.ValueOf(t.Content)) } // encodeHead writes CBOR head of specified type t and returns number of bytes written. diff --git a/tag.go b/tag.go index 4c9c99f8..5c4d2b7a 100644 --- a/tag.go +++ b/tag.go @@ -7,7 +7,9 @@ import ( "sync" ) -// Tag represents CBOR tag data, including tag number and unmarshaled tag content. +// Tag represents CBOR tag data, including tag number and unmarshaled tag content. Marshaling and +// unmarshaling of tag content is subject to any encode and decode options that would apply to +// enclosed data item if it were to appear outside of a tag. type Tag struct { Number uint64 Content interface{} diff --git a/tag_test.go b/tag_test.go index fd9246d1..f5917c4f 100644 --- a/tag_test.go +++ b/tag_test.go @@ -1490,3 +1490,49 @@ func TestMarshalRawTagContainingMalformedCBORData(t *testing.T) { }) } } + +// TestEncodeBuiltinTag tests that marshaling a value of type Tag "does the right thing" when +// marshaling the enclosed data item of a built-in tag number. +func TestEncodeBuiltinTag(t *testing.T) { + for _, tc := range []struct { + name string + tag Tag + opts EncOptions + want []byte + }{ + { + 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}, + 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}, + want: []byte{0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + }, + { + name: "rfc 3339 content is not encoded as byte string", + tag: Tag{Number: tagNumRFC3339Time, Content: "2013-03-21T20:04:00Z"}, + opts: EncOptions{String: StringToByteString}, + want: hexDecode("c074323031332d30332d32315432303a30343a30305a"), + }, + } { + t.Run(tc.name, func(t *testing.T) { + em, err := tc.opts.EncMode() + if err != nil { + t.Fatal(err) + } + + got, err := em.Marshal(tc.tag) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(got, tc.want) { + t.Errorf("unexpected difference\ngot: 0x%x\nwant: 0x%x", got, tc.want) + } + }) + } +}