Skip to content

Commit

Permalink
Disable conflicting encode options when marshaling cbor.Tag.
Browse files Browse the repository at this point in the history
Encode options, especially those that control the mapping from Go type to CBOR type, can result in
output containing tag validity errors. For tag numbers that are built in, it's possible to "do the
right thing" and override those options on a case-by-case basis. This can't and does not prevent tag
validity errors for unrecognized tag numbers.

Signed-off-by: Ben Luddy <[email protected]>
  • Loading branch information
benluddy committed Jun 3, 2024
1 parent 69326f1 commit 0cabb4a
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 2 deletions.
15 changes: 14 additions & 1 deletion encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
46 changes: 46 additions & 0 deletions tag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}

0 comments on commit 0cabb4a

Please sign in to comment.