Skip to content

Commit

Permalink
feat: custom CBOR tag handling
Browse files Browse the repository at this point in the history
Fixes #548
  • Loading branch information
agaffney committed Mar 17, 2024
1 parent 512417f commit 5c11a64
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 59 deletions.
16 changes: 1 addition & 15 deletions cbor/cbor.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 Blink Labs Software
// Copyright 2024 Blink Labs Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,20 +30,6 @@ const (

// Max value able to be stored in a single byte without type prefix
CborMaxUintSimple uint8 = 0x17

// Useful tag numbers
CborTagCbor = 24
CborTagRational = 30
CborTagSet = 258
CborTagMap = 259

// Tag ranges for "alternatives"
// https://www.ietf.org/archive/id/draft-bormann-cbor-notable-tags-07.html#name-enumerated-alternative-data
CborTagAlternative1Min = 121
CborTagAlternative1Max = 127
CborTagAlternative2Min = 1280
CborTagAlternative2Max = 1400
CborTagAlternative3 = 101
)

// Create an alias for RawMessage for convenience
Expand Down
4 changes: 2 additions & 2 deletions cbor/decode.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 Blink Labs Software
// Copyright 2024 Blink Labs Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,7 +31,7 @@ func Decode(dataBytes []byte, dest interface{}) (int, error) {
// This defaults to 32, but there are blocks in the wild using >64 nested levels
MaxNestedLevels: 256,
}
decMode, err := decOptions.DecMode()
decMode, err := decOptions.DecModeWithTags(customTagSet)
if err != nil {
return 0, err
}
Expand Down
4 changes: 2 additions & 2 deletions cbor/encode.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 Blink Labs Software
// Copyright 2024 Blink Labs Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -25,7 +25,7 @@ import (

func Encode(data interface{}) ([]byte, error) {
buf := bytes.NewBuffer(nil)
em, err := _cbor.CoreDetEncOptions().EncMode()
em, err := _cbor.CoreDetEncOptions().EncModeWithTags(customTagSet)
if err != nil {
return nil, err
}
Expand Down
211 changes: 211 additions & 0 deletions cbor/tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright 2024 Blink Labs Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cbor

import (
"math/big"
"reflect"

"github.com/fxamacker/cbor/v2"
_cbor "github.com/fxamacker/cbor/v2"
)

const (
// Useful tag numbers
CborTagCbor = 24
CborTagRational = 30
CborTagSet = 258
CborTagMap = 259

// Tag ranges for "alternatives"
// https://www.ietf.org/archive/id/draft-bormann-cbor-notable-tags-07.html#name-enumerated-alternative-data
CborTagAlternative1Min = 121
CborTagAlternative1Max = 127
CborTagAlternative2Min = 1280
CborTagAlternative2Max = 1400
CborTagAlternative3 = 101
)

var customTagSet _cbor.TagSet

func init() {
// Build custom tagset
customTagSet = _cbor.NewTagSet()
tagOpts := _cbor.TagOptions{EncTag: _cbor.EncTagRequired, DecTag: _cbor.DecTagRequired}
// Wrapped CBOR
if err := customTagSet.Add(
tagOpts,
reflect.TypeOf(WrappedCbor{}),
CborTagCbor,
); err != nil {
panic(err)
}
// Rational numbers
if err := customTagSet.Add(
tagOpts,
reflect.TypeOf(Rat{}),
CborTagRational,
); err != nil {
panic(err)
}
// Sets
if err := customTagSet.Add(
tagOpts,
reflect.TypeOf(Set{}),
CborTagSet,
); err != nil {
panic(err)
}
// Maps
if err := customTagSet.Add(
tagOpts,
reflect.TypeOf(Map{}),
CborTagMap,
); err != nil {
panic(err)
}
}

type WrappedCbor []byte

func (w *WrappedCbor) UnmarshalCBOR(cborData []byte) error {
var tmpData []byte
if _, err := Decode(cborData, &tmpData); err != nil {
return err
}
*w = WrappedCbor(tmpData[:])
return nil
}

/*
func (w WrappedCbor) MarshalCBOR() ([]byte, error) {
tmpData := w.Bytes()
return Encode(&tmpData)
}
*/

func (w WrappedCbor) Bytes() []byte {
return w[:]
}

type Rat struct {
*big.Rat
}

func (r *Rat) UnmarshalCBOR(cborData []byte) error {
tmpRat := []int64{}
if _, err := Decode(cborData, &tmpRat); err != nil {
return err
}
r.Rat = big.NewRat(tmpRat[0], tmpRat[1])
return nil
}

func (r *Rat) MarshalCBOR() ([]byte, error) {
tmpData := cbor.Tag{
Number: CborTagRational,
Content: []uint64{
r.Num().Uint64(),
r.Denom().Uint64(),
},
}
return Encode(&tmpData)
}

func (r *Rat) ToBigRat() *big.Rat {
return r.Rat
}

type Set []any

func (s *Set) UnmarshalCBOR(cborData []byte) error {
var tmpData []any
if _, err := Decode(cborData, &tmpData); err != nil {
return err
}
*s = tmpData[:]
return nil
}

/*
func (s Set) MarshalCBOR() ([]byte, error) {
tmpData := make([]any, len(s))
for idx, item := range s {
tmpData[idx] = item
}
return Encode(&tmpData)
}
*/

type Map map[any]any

func (m *Map) UnmarshalCBOR(cborData []byte) error {
tmpData := make(map[any]any)
if _, err := Decode(cborData, &tmpData); err != nil {
return err
}
*m = tmpData
return nil
}

/*
func (m Map) MarshalCBOR() ([]byte, error) {
tmpData := make(map[any]any)
for k, v := range m {
tmpData[k] = v
}
return Encode(&tmpData)
}
*/

/*
case CborTagCbor:
v.value = tmpTag.Content
case CborTagRational:
tmpRat := []int64{}
if _, err := Decode(tmpTag.Content, &tmpRat); err != nil {
return err
}
v.value = big.NewRat(tmpRat[0], tmpRat[1])
case CborTagSet:
return v.processArray(tmpTag.Content)
case CborTagMap:
return v.processMap(tmpTag.Content)
default:
if (tmpTag.Number >= CborTagAlternative1Min && tmpTag.Number <= CborTagAlternative1Max) ||
(tmpTag.Number >= CborTagAlternative2Min && tmpTag.Number <= CborTagAlternative2Max) ||
tmpTag.Number == CborTagAlternative3 {
// Constructors/alternatives
var tmpConstr Constructor
if _, err := Decode(data, &tmpConstr); err != nil {
return err
}
v.value = tmpConstr
} else {
// Fall back to standard CBOR tag parsing for our supported types
var tmpTagDecode interface{}
if _, err := Decode(data, &tmpTagDecode); err != nil {
return err
}
switch tmpTagDecode.(type) {
case int, uint, int64, uint64, bool, big.Int:
v.value = tmpTagDecode
default:
return fmt.Errorf("unsupported CBOR tag number: %d", tmpTag.Number)
}
}
*/

// TODO
90 changes: 90 additions & 0 deletions cbor/tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2023 Blink Labs Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cbor_test

import (
"encoding/hex"
"math/big"
"reflect"
"testing"

"github.com/blinklabs-io/gouroboros/cbor"
)

var tagsTestDefs = []struct {
cborHex string
object any
}{
{
cborHex: "d81e82031903e8",
object: cbor.Rat{
Rat: big.NewRat(3, 1000),
},
},
{
cborHex: "d9010283010203",
object: cbor.Set(
[]any{
uint64(1), uint64(2), uint64(3),
},
),
},
{
cborHex: "d90103a201020304",
object: cbor.Map(
map[any]any{
uint64(1): uint64(2),
uint64(3): uint64(4),
},
),
},
}

func TestTagsDecode(t *testing.T) {
for _, testDef := range tagsTestDefs {
cborData, err := hex.DecodeString(testDef.cborHex)
if err != nil {
t.Fatalf("failed to decode CBOR hex: %s", err)
}
var dest any
if _, err := cbor.Decode(cborData, &dest); err != nil {
t.Fatalf("failed to decode CBOR: %s", err)
}
if !reflect.DeepEqual(dest, testDef.object) {
t.Fatalf(
"CBOR did not decode to expected object\n got: %#v\n wanted: %#v",
dest,
testDef.object,
)
}
}
}

func TestTagsEncode(t *testing.T) {
for _, testDef := range tagsTestDefs {
cborData, err := cbor.Encode(testDef.object)
if err != nil {
t.Fatalf("failed to encode object to CBOR: %s", err)
}
cborHex := hex.EncodeToString(cborData)
if cborHex != testDef.cborHex {
t.Fatalf(
"object did not encode to expected CBOR\n got: %s\n wanted: %s",
cborHex,
testDef.cborHex,
)
}
}
}
Loading

0 comments on commit 5c11a64

Please sign in to comment.