Skip to content

Commit ef3e13e

Browse files
authored
feat: generic custom CBOR tag handling (#549)
This moves the handling for some custom CBOR tag types from cbor.Value to the generic encoder/decoder to make them more widely usable. Fixes #548
1 parent 512417f commit ef3e13e

File tree

8 files changed

+345
-101
lines changed

8 files changed

+345
-101
lines changed

cbor/cbor.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2023 Blink Labs Software
1+
// Copyright 2024 Blink Labs Software
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -30,20 +30,6 @@ const (
3030

3131
// Max value able to be stored in a single byte without type prefix
3232
CborMaxUintSimple uint8 = 0x17
33-
34-
// Useful tag numbers
35-
CborTagCbor = 24
36-
CborTagRational = 30
37-
CborTagSet = 258
38-
CborTagMap = 259
39-
40-
// Tag ranges for "alternatives"
41-
// https://www.ietf.org/archive/id/draft-bormann-cbor-notable-tags-07.html#name-enumerated-alternative-data
42-
CborTagAlternative1Min = 121
43-
CborTagAlternative1Max = 127
44-
CborTagAlternative2Min = 1280
45-
CborTagAlternative2Max = 1400
46-
CborTagAlternative3 = 101
4733
)
4834

4935
// Create an alias for RawMessage for convenience

cbor/decode.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2023 Blink Labs Software
1+
// Copyright 2024 Blink Labs Software
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ func Decode(dataBytes []byte, dest interface{}) (int, error) {
3131
// This defaults to 32, but there are blocks in the wild using >64 nested levels
3232
MaxNestedLevels: 256,
3333
}
34-
decMode, err := decOptions.DecMode()
34+
decMode, err := decOptions.DecModeWithTags(customTagSet)
3535
if err != nil {
3636
return 0, err
3737
}

cbor/encode.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2023 Blink Labs Software
1+
// Copyright 2024 Blink Labs Software
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import (
2525

2626
func Encode(data interface{}) ([]byte, error) {
2727
buf := bytes.NewBuffer(nil)
28-
em, err := _cbor.CoreDetEncOptions().EncMode()
28+
em, err := _cbor.CoreDetEncOptions().EncModeWithTags(customTagSet)
2929
if err != nil {
3030
return nil, err
3131
}

cbor/tags.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2024 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cbor
16+
17+
import (
18+
"math/big"
19+
"reflect"
20+
21+
_cbor "github.com/fxamacker/cbor/v2"
22+
)
23+
24+
const (
25+
// Useful tag numbers
26+
CborTagCbor = 24
27+
CborTagRational = 30
28+
CborTagSet = 258
29+
CborTagMap = 259
30+
31+
// Tag ranges for "alternatives"
32+
// https://www.ietf.org/archive/id/draft-bormann-cbor-notable-tags-07.html#name-enumerated-alternative-data
33+
CborTagAlternative1Min = 121
34+
CborTagAlternative1Max = 127
35+
CborTagAlternative2Min = 1280
36+
CborTagAlternative2Max = 1400
37+
CborTagAlternative3 = 101
38+
)
39+
40+
var customTagSet _cbor.TagSet
41+
42+
func init() {
43+
// Build custom tagset
44+
customTagSet = _cbor.NewTagSet()
45+
tagOpts := _cbor.TagOptions{EncTag: _cbor.EncTagRequired, DecTag: _cbor.DecTagRequired}
46+
// Wrapped CBOR
47+
if err := customTagSet.Add(
48+
tagOpts,
49+
reflect.TypeOf(WrappedCbor{}),
50+
CborTagCbor,
51+
); err != nil {
52+
panic(err)
53+
}
54+
// Rational numbers
55+
if err := customTagSet.Add(
56+
tagOpts,
57+
reflect.TypeOf(Rat{}),
58+
CborTagRational,
59+
); err != nil {
60+
panic(err)
61+
}
62+
// Sets
63+
if err := customTagSet.Add(
64+
tagOpts,
65+
reflect.TypeOf(Set{}),
66+
CborTagSet,
67+
); err != nil {
68+
panic(err)
69+
}
70+
// Maps
71+
if err := customTagSet.Add(
72+
tagOpts,
73+
reflect.TypeOf(Map{}),
74+
CborTagMap,
75+
); err != nil {
76+
panic(err)
77+
}
78+
}
79+
80+
// WrappedCbor corresponds to CBOR tag 24 and is used to encode nested CBOR data
81+
type WrappedCbor []byte
82+
83+
func (w WrappedCbor) Bytes() []byte {
84+
return w[:]
85+
}
86+
87+
// Rat corresponds to CBOR tag 30 and is used to represent a rational number
88+
type Rat struct {
89+
*big.Rat
90+
}
91+
92+
func (r *Rat) UnmarshalCBOR(cborData []byte) error {
93+
tmpRat := []int64{}
94+
if _, err := Decode(cborData, &tmpRat); err != nil {
95+
return err
96+
}
97+
r.Rat = big.NewRat(tmpRat[0], tmpRat[1])
98+
return nil
99+
}
100+
101+
func (r *Rat) MarshalCBOR() ([]byte, error) {
102+
tmpData := _cbor.Tag{
103+
Number: CborTagRational,
104+
Content: []uint64{
105+
r.Num().Uint64(),
106+
r.Denom().Uint64(),
107+
},
108+
}
109+
return Encode(&tmpData)
110+
}
111+
112+
func (r *Rat) ToBigRat() *big.Rat {
113+
return r.Rat
114+
}
115+
116+
// Set corresponds to CBOR tag 258 and is used to represent a mathematical finite set
117+
type Set []any
118+
119+
// Map corresponds to CBOR tag 259 and is used to represent a map with key/value operations
120+
type Map map[any]any

cbor/tags_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2024 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cbor_test
16+
17+
import (
18+
"encoding/hex"
19+
"math/big"
20+
"reflect"
21+
"testing"
22+
23+
"github.com/blinklabs-io/gouroboros/cbor"
24+
)
25+
26+
var tagsTestDefs = []struct {
27+
cborHex string
28+
object any
29+
}{
30+
{
31+
cborHex: "d81843abcdef",
32+
object: cbor.WrappedCbor([]byte{0xab, 0xcd, 0xef}),
33+
},
34+
{
35+
cborHex: "d81e82031903e8",
36+
object: cbor.Rat{
37+
Rat: big.NewRat(3, 1000),
38+
},
39+
},
40+
{
41+
cborHex: "d9010283010203",
42+
object: cbor.Set(
43+
[]any{
44+
uint64(1), uint64(2), uint64(3),
45+
},
46+
),
47+
},
48+
{
49+
cborHex: "d90103a201020304",
50+
object: cbor.Map(
51+
map[any]any{
52+
uint64(1): uint64(2),
53+
uint64(3): uint64(4),
54+
},
55+
),
56+
},
57+
}
58+
59+
func TestTagsDecode(t *testing.T) {
60+
for _, testDef := range tagsTestDefs {
61+
cborData, err := hex.DecodeString(testDef.cborHex)
62+
if err != nil {
63+
t.Fatalf("failed to decode CBOR hex: %s", err)
64+
}
65+
var dest any
66+
if _, err := cbor.Decode(cborData, &dest); err != nil {
67+
t.Fatalf("failed to decode CBOR: %s", err)
68+
}
69+
if !reflect.DeepEqual(dest, testDef.object) {
70+
t.Fatalf(
71+
"CBOR did not decode to expected object\n got: %#v\n wanted: %#v",
72+
dest,
73+
testDef.object,
74+
)
75+
}
76+
}
77+
}
78+
79+
func TestTagsEncode(t *testing.T) {
80+
for _, testDef := range tagsTestDefs {
81+
cborData, err := cbor.Encode(testDef.object)
82+
if err != nil {
83+
t.Fatalf("failed to encode object to CBOR: %s", err)
84+
}
85+
cborHex := hex.EncodeToString(cborData)
86+
if cborHex != testDef.cborHex {
87+
t.Fatalf(
88+
"object did not encode to expected CBOR\n got: %s\n wanted: %s",
89+
cborHex,
90+
testDef.cborHex,
91+
)
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)