Repository: fxamacker/cbor (go)

Issue:
bug: Decoding registered tag to empty interface should return object of registered type
### What version of fxamacker/cbor are you using?
commit 8c2d6eb841d71a9c7df4c11438f4cb78f7f628f9

### Does this issue reproduce with the latest release?
Yes

### What OS and CPU architecture are you using (`go env`)?

<details><summary><code>go env</code> Output</summary><br><pre>
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/user/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/user/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build282002194=/tmp/go-build -gno-record-gcc-switches"
</pre></details>

### What did you do?

* Register tag number with tag content type 
* Decode CBOR data with registered tag number to empty interface.

```

func main() {
	type myBool bool

	tags := cbor.NewTagSet()
	// Register tag number 100 with tag content type myBool
	tags.Add(
		cbor.TagOptions{EncTag: cbor.EncTagRequired, DecTag: cbor.DecTagRequired},
		reflect.TypeOf(myBool(false)),
		100)

	dm, _ := cbor.DecOptions{}.DecModeWithTags(tags)

	cborData, _ := hex.DecodeString("d864f5") // 100(true)

	var v interface{}
	dm.Unmarshal(cborData, &v)
	if _, ok := v.(myBool); !ok {
		fmt.Printf("Unmarshal() returned wrong type %T, expected myBool", v)
	}
}
```


### What did you expect to see?
Unmarshal() should return an object of registered content type based on CBOR tag number.

### What did you see instead?
Unmarshal() returns an object of cbor.Tag type.

Pull request:
Return registered type when decoding tag to interface{}
Unmarshaling to empty interface returns object of registered content type when tag number matches.

Also allow Go types to register with tag number if they implement Unmarshaler interface.

Closes #223



Patch:
diff --git a/decode.go b/decode.go
index 36549d9..f418d07 100644
--- a/decode.go
+++ b/decode.go
@@ -766,6 +766,24 @@ func (d *decodeState) parse() (interface{}, error) {
 			return d.parseToTime()
 		}
 
+		if d.dm.tags != nil {
+			// Parse to specified type if tag number is registered.
+			tagNums := []uint64{tagNum}
+			for d.nextCBORType() == cborTypeTag {
+				_, _, num := d.getHead()
+				tagNums = append(tagNums, num)
+			}
+			registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
+			if registeredType != nil {
+				d.off = tagOff
+				rv := reflect.New(registeredType)
+				if err := d.parseToValue(rv.Elem(), getTypeInfo(registeredType)); err != nil {
+					return nil, err
+				}
+				return rv.Elem().Interface(), nil
+			}
+		}
+
 		// Parse tag content
 		d.off = contentOff
 		content, err := d.parse()
diff --git a/tag.go b/tag.go
index defc337..ddfacb0 100644
--- a/tag.go
+++ b/tag.go
@@ -124,6 +124,7 @@ type TagSet interface {
 
 type tagProvider interface {
 	getTagItemFromType(t reflect.Type) *tagItem
+	getTypeFromTagNum(num []uint64) reflect.Type
 }
 
 type tagItem struct {
@@ -165,6 +166,15 @@ func (t tagSet) getTagItemFromType(typ reflect.Type) *tagItem {
 	return t[typ]
 }
 
+func (t tagSet) getTypeFromTagNum(num []uint64) reflect.Type {
+	for typ, tag := range t {
+		if tag.equalTagNum(num) {
+			return typ
+		}
+	}
+	return nil
+}
+
 // NewTagSet returns TagSet (safe for concurrency).
 func NewTagSet() TagSet {
 	return &syncTagSet{t: make(map[reflect.Type]*tagItem)}
@@ -213,6 +223,13 @@ func (t *syncTagSet) getTagItemFromType(typ reflect.Type) *tagItem {
 	return ti
 }
 
+func (t *syncTagSet) getTypeFromTagNum(num []uint64) reflect.Type {
+	t.RLock()
+	rt := t.t.getTypeFromTagNum(num)
+	t.RUnlock()
+	return rt
+}
+
 func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) (*tagItem, error) {
 	if opts.DecTag == DecTagIgnored && opts.EncTag == EncTagNone {
 		return nil, errors.New("cbor: cannot add tag with DecTagIgnored and EncTagNone options to TagSet")
@@ -232,12 +249,12 @@ func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum
 	if num == 0 || num == 1 {
 		return nil, errors.New("cbor: cannot add tag number 0 or 1 to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead")
 	}
-	if reflect.PtrTo(contentType).Implements(typeMarshaler) && opts.EncTag != EncTagNone {
-		return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone")
-	}
-	if reflect.PtrTo(contentType).Implements(typeUnmarshaler) && opts.DecTag != DecTagIgnored {
-		return nil, errors.New("cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored")
-	}
+	//if reflect.PtrTo(contentType).Implements(typeMarshaler) && opts.EncTag != EncTagNone {
+	//return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone")
+	//}
+	//if reflect.PtrTo(contentType).Implements(typeUnmarshaler) && opts.DecTag != DecTagIgnored {
+	//return nil, errors.New("cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored")
+	//}
 
 	te := tagItem{num: []uint64{num}, opts: opts, contentType: contentType}
 	te.num = append(te.num, nestedNum...)


Test patch:
diff --git a/tag_test.go b/tag_test.go
index cba9a87..c90f444 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -2,6 +2,7 @@ package cbor
 
 import (
 	"bytes"
+	"fmt"
 	"io"
 	"reflect"
 	"strings"
@@ -332,20 +333,22 @@ func TestAddTagError(t *testing.T) {
 			opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
 			wantErrorMsg: "cbor: cannot add cbor.RawTag to TagSet",
 		},
-		{
-			name:         "cbor.Unmarshaler",
-			typ:          reflect.TypeOf(number2(0)),
-			num:          107,
-			opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagNone},
-			wantErrorMsg: "cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored",
-		},
-		{
-			name:         "cbor.Marshaler",
-			typ:          reflect.TypeOf(number2(0)),
-			num:          108,
-			opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
-			wantErrorMsg: "cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone",
-		},
+		/*
+			{
+				name:         "cbor.Unmarshaler",
+				typ:          reflect.TypeOf(number2(0)),
+				num:          107,
+				opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagNone},
+				wantErrorMsg: "cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored",
+			},
+			{
+				name:         "cbor.Marshaler",
+				typ:          reflect.TypeOf(number2(0)),
+				num:          108,
+				opts:         TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
+				wantErrorMsg: "cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone",
+			},
+		*/
 		{
 			name:         "tag number 0",
 			typ:          reflect.TypeOf(myInt(0)),
@@ -1103,7 +1106,7 @@ func TestTagMarshalError(t *testing.T) {
 	}
 }
 
-func TestTagMarshal(t *testing.T) {
+func TestEncodeTag(t *testing.T) {
 	m := make(map[interface{}]bool)
 	m[10] = true
 	m[100] = true
@@ -1137,3 +1140,170 @@ func TestTagMarshal(t *testing.T) {
 		t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, bytewiseSortedCborData)
 	}
 }
+
+func TestDecodeTagToEmptyIface(t *testing.T) {
+	type myBool bool
+	type myUint uint
+
+	typeMyBool := reflect.TypeOf(myBool(false))
+	typeMyUint := reflect.TypeOf(myUint(0))
+
+	tags := NewTagSet()
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typeMyBool, 100); err != nil {
+		t.Fatalf("TagSet.Add(%s, %d) returned error %v", typeMyBool, 100, err)
+	}
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typeMyUint, 101, 102); err != nil {
+		t.Fatalf("TagSet.Add(%s, %d, %d) returned error %v", typeMyUint, 101, 102, err)
+	}
+
+	dm, _ := DecOptions{}.DecModeWithTags(tags)
+	dmSharedTags, _ := DecOptions{}.DecModeWithSharedTags(tags)
+
+	testCases := []struct {
+		name     string
+		cborData []byte
+		wantObj  interface{}
+	}{
+		{
+			name:     "registered myBool",
+			cborData: hexDecode("d864f5"), // 100(true)
+			wantObj:  myBool(true),
+		},
+		{
+			name:     "registered myUint",
+			cborData: hexDecode("d865d86600"), // 101(102(0))
+			wantObj:  myUint(0),
+		},
+		{
+			name:     "not registered bool",
+			cborData: hexDecode("d865f5"), // 101(true)
+			wantObj:  Tag{101, true},
+		},
+		{
+			name:     "not registered uint",
+			cborData: hexDecode("d865d86700"), // 101(103(0))
+			wantObj:  Tag{101, Tag{103, uint64(0)}},
+		},
+		{
+			name:     "not registered uint",
+			cborData: hexDecode("d865d866d86700"), // 101(102(103(0)))
+			wantObj:  Tag{101, Tag{102, Tag{103, uint64(0)}}},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			var v1 interface{}
+			if err := dm.Unmarshal(tc.cborData, &v1); err != nil {
+				t.Errorf("Unmarshal() returned error %v", err)
+			}
+			if !reflect.DeepEqual(tc.wantObj, v1) {
+				t.Errorf("Unmarshal to interface{} returned different values: %v, %v", tc.wantObj, v1)
+			}
+
+			var v2 interface{}
+			if err := dmSharedTags.Unmarshal(tc.cborData, &v2); err != nil {
+				t.Errorf("Unmarshal() returned error %v", err)
+			}
+			if !reflect.DeepEqual(tc.wantObj, v2) {
+				t.Errorf("Unmarshal to interface{} returned different values: %v, %v", tc.wantObj, v2)
+			}
+		})
+	}
+}
+
+func TestDecodeRegisteredTagToEmptyIfaceError(t *testing.T) {
+	type myInt int
+
+	typeMyInt := reflect.TypeOf(myInt(0))
+
+	tags := NewTagSet()
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typeMyInt, 101, 102); err != nil {
+		t.Fatalf("TagSet.Add(%s, %d, %d) returned error %v", typeMyInt, 101, 102, err)
+	}
+
+	dm, _ := DecOptions{}.DecModeWithTags(tags)
+
+	cborData := hexDecode("d865d8663bffffffffffffffff") // 101(102(-18446744073709551616))
+
+	var v interface{}
+	if err := dm.Unmarshal(cborData, &v); err == nil {
+		t.Errorf("Unmarshal(0x%x) didn't return an error", cborData)
+	} else if _, ok := err.(*UnmarshalTypeError); !ok {
+		t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", cborData, err)
+	} else if !strings.Contains(err.Error(), "cannot unmarshal") {
+		t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", cborData, err.Error(), "cannot unmarshal")
+	}
+}
+
+type number3 uint64
+
+// MarshalCBOR marshals number3 to CBOR tagged map (tag number 100)
+func (n number3) MarshalCBOR() (data []byte, err error) {
+	m := map[string]uint64{"num": uint64(n)}
+	return Marshal(Tag{100, m})
+}
+
+// UnmarshalCBOR unmarshals CBOR tagged map to number3
+func (n *number3) UnmarshalCBOR(data []byte) (err error) {
+	var rawTag RawTag
+	if err := Unmarshal(data, &rawTag); err != nil {
+		return err
+	}
+
+	if rawTag.Number != 100 {
+		return fmt.Errorf("wrong tag number %d, want %d", rawTag.Number, 101)
+	}
+
+	var v map[string]uint64
+	if err := Unmarshal(rawTag.Content, &v); err != nil {
+		return err
+	}
+	*n = number3(v["num"])
+	return nil
+}
+
+func TestDecodeRegisterTagForUnmarshaler(t *testing.T) {
+	typ := reflect.TypeOf(number3(0))
+
+	tags := NewTagSet()
+	if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typ, 100); err != nil {
+		t.Fatalf("TagSet.Add(%s, %d) returned error %v", typ, 100, err)
+	}
+
+	cborData := hexDecode("d864a1636e756d01") // 100({"num": 1})
+	wantObj := number3(1)
+
+	dm, _ := DecOptions{}.DecModeWithTags(tags)
+	em, _ := EncOptions{}.EncModeWithTags(tags)
+
+	// Decode to empty interface.  Unmarshal() should return object of registered type.
+	var v1 interface{}
+	if err := dm.Unmarshal(cborData, &v1); err != nil {
+		t.Errorf("Unmarshal() returned error %v", err)
+	}
+	if !reflect.DeepEqual(wantObj, v1) {
+		t.Errorf("Unmarshal() returned different values: %v (%T), %v (%T)", wantObj, wantObj, v1, v1)
+	}
+	b, err := em.Marshal(v1)
+	if err != nil {
+		t.Errorf("Marshal(%v) returned error %v", v1, err)
+	} else if !bytes.Equal(b, cborData) {
+		t.Errorf("Marshal(%v) returned %v, want %v", v1, b, cborData)
+	}
+
+	// Decode to registered type.
+	var v2 number3
+	if err = dm.Unmarshal(cborData, &v2); err != nil {
+		t.Errorf("Unmarshal() returned error %v", err)
+	}
+	if !reflect.DeepEqual(wantObj, v2) {
+		t.Errorf("Unmarshal() returned different values: %v, %v", wantObj, v2)
+	}
+	b, err = em.Marshal(v2)
+	if err != nil {
+		t.Errorf("Marshal(%v) returned error %v", v2, err)
+	} else if !bytes.Equal(b, cborData) {
+		t.Errorf("Marshal(%v) returned %v, want %v", v2, b, cborData)
+	}
+}