Skip to content

Commit b2c5a8e

Browse files
Wrap TagID into its own type with custom (un)marshalers (#11)
Fixes #10
1 parent 285540f commit b2c5a8e

File tree

6 files changed

+196
-21
lines changed

6 files changed

+196
-21
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/veraison/swid
33
go 1.15
44

55
require (
6+
github.com/fxamacker/cbor v1.5.1
67
github.com/fxamacker/cbor/v2 v2.2.0
78
github.com/stretchr/testify v1.6.1
89
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
22
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
33
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
4+
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
45
github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
56
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
67
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

softwareidentity.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package swid
66
import (
77
"encoding/json"
88
"encoding/xml"
9+
"errors"
910
)
1011

1112
// SoftwareIdentity represents the top-level SWID
@@ -189,12 +190,12 @@ func (t *SoftwareIdentity) FromCBOR(data []byte) error {
189190
}
190191

191192
func (t *SoftwareIdentity) setTagID(v interface{}) error {
192-
tagID, err := checkTagID(v)
193-
if err != nil {
194-
return err
193+
tagID := NewTagID(v)
194+
if tagID == nil {
195+
return errors.New("bad type for TagID: expecting string or [16]byte")
195196
}
196197

197-
t.TagID = tagID
198+
t.TagID = *tagID
198199

199200
return nil
200201
}

softwareidentity_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func makeACMEEntityWithRoles(t *testing.T, roles ...interface{}) Entity {
2222

2323
func TestTag_RoundtripPSABundle(t *testing.T) {
2424
tv := SoftwareIdentity{
25-
TagID: TagID("example.acme.roadrunner-sw-v1-0-0"),
25+
TagID: *NewTagID("example.acme.roadrunner-sw-v1-0-0"),
2626
SoftwareName: "Roadrunner software bundle",
2727
SoftwareVersion: "1.0.0",
2828
Entities: Entities{
@@ -139,7 +139,7 @@ func TestTag_RoundtripPSABundle(t *testing.T) {
139139

140140
func TestTag_RoundtripPSAComponent(t *testing.T) {
141141
tv := SoftwareIdentity{
142-
TagID: TagID("example.acme.roadrunner-sw-bl-v1-0-0"),
142+
TagID: *NewTagID("example.acme.roadrunner-sw-bl-v1-0-0"),
143143
SoftwareName: "Roadrunner boot loader",
144144
SoftwareVersion: "1.0.0",
145145
Entities: Entities{

tagid.go

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,125 @@
44
package swid
55

66
import (
7+
"encoding/hex"
8+
"encoding/json"
9+
"encoding/xml"
710
"errors"
811
"fmt"
12+
13+
"github.com/fxamacker/cbor"
914
)
1015

1116
// TagID is the type of a tag identifier. Allowed formats (enforced via
1217
// checkTagID) are string or [16]byte
13-
type TagID interface{}
18+
type TagID struct {
19+
val interface{}
20+
}
21+
22+
// NewTagID returns a TagID initialized with the supplied value v
23+
// v is either a string or a [16]byte
24+
func NewTagID(v interface{}) *TagID {
25+
if checkTagID(v) != nil {
26+
return nil
27+
}
28+
return &TagID{v}
29+
}
30+
31+
// String returns the value of the TagID as string. If the TagID has type
32+
// [16]byte the Base 16 encoding is returned
33+
func (t TagID) String() string {
34+
switch v := t.val.(type) {
35+
case string:
36+
return v
37+
case []byte:
38+
return hex.EncodeToString(v)
39+
default:
40+
return "unknown type for tag-id"
41+
}
42+
}
1443

15-
func checkTagID(v interface{}) (TagID, error) {
44+
func checkTagID(v interface{}) error {
1645
switch t := v.(type) {
1746
case string:
1847
case []byte:
1948
if len(t) != 16 {
20-
return nil, errors.New("binary tag-id MUST be 16 bytes")
49+
return errors.New("binary tag-id MUST be 16 bytes")
2150
}
2251
default:
23-
return nil, fmt.Errorf("tag-id MUST be []byte or string; got %T", v)
52+
return fmt.Errorf("tag-id MUST be []byte or string; got %T", v)
53+
}
54+
55+
return nil
56+
}
57+
58+
func (t TagID) isString() bool {
59+
switch t.val.(type) {
60+
case string:
61+
return true
62+
}
63+
return false
64+
}
65+
66+
// MarshalXMLAttr encodes the TagID receiver as XML attribute
67+
func (t TagID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
68+
if !t.isString() {
69+
return xml.Attr{}, errors.New("only tag-id of type string can be serialized to XML")
70+
}
71+
return xml.Attr{Name: name, Value: t.String()}, nil
72+
}
73+
74+
// UnmarshalXMLAttr decodes the supplied XML attribute into a TagID
75+
// Note that this can only unmarshal to string.
76+
func (t *TagID) UnmarshalXMLAttr(attr xml.Attr) error {
77+
t.val = attr.Value
78+
return nil
79+
}
80+
81+
// MarshalJSON encodes the TagID receiver as JSON string
82+
func (t TagID) MarshalJSON() ([]byte, error) {
83+
if !t.isString() {
84+
return nil, errors.New("only tag-id of type string can be serialized to JSON")
85+
}
86+
87+
return json.Marshal(t.val)
88+
}
89+
90+
// UnmarshalJSON decodes the supplied JSON data into a TagID
91+
// Note that this can only unmarshal to string.
92+
func (t *TagID) UnmarshalJSON(data []byte) error {
93+
var v interface{}
94+
95+
if err := json.Unmarshal(data, &v); err != nil {
96+
return err
97+
}
98+
99+
switch s := v.(type) {
100+
case string:
101+
t.val = s
102+
return nil
103+
default:
104+
return fmt.Errorf("expecting string, found %T instead", s)
105+
}
106+
}
107+
108+
// MarshalCBOR encodes the TagID receiver to CBOR
109+
func (t TagID) MarshalCBOR() ([]byte, error) {
110+
return em.Marshal(t.val)
111+
}
112+
113+
// UnmarshalCBOR decodes the supplied data into a TagID
114+
func (t *TagID) UnmarshalCBOR(data []byte) error {
115+
var v interface{}
116+
117+
if err := cbor.Unmarshal(data, &v); err != nil {
118+
return err
24119
}
25120

26-
return TagID(v), nil
121+
if err := checkTagID(v); err != nil {
122+
return err
123+
}
124+
125+
t.val = v
126+
127+
return nil
27128
}

tagid_test.go

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package swid
55

66
import (
7+
"encoding/xml"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -15,12 +16,12 @@ func TestTagID_16Bytes(t *testing.T) {
1516
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
1617
}
1718

18-
expected := tv
19+
expected := "00010001000100010001000100010001"
1920

20-
actual, err := checkTagID(tv)
21+
actual := NewTagID(tv)
2122

22-
assert.Nil(t, err)
23-
assert.Equal(t, expected, actual)
23+
assert.NotNil(t, actual)
24+
assert.Equal(t, expected, actual.String())
2425
}
2526

2627
func TestTagID_15Bytes(t *testing.T) {
@@ -29,7 +30,7 @@ func TestTagID_15Bytes(t *testing.T) {
2930
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
3031
}
3132

32-
_, err := checkTagID(tv)
33+
err := checkTagID(tv)
3334

3435
assert.EqualError(t, err, "binary tag-id MUST be 16 bytes")
3536
}
@@ -41,18 +42,18 @@ func TestTagID_17Bytes(t *testing.T) {
4142
0x00,
4243
}
4344

44-
_, err := checkTagID(tv)
45+
err := checkTagID(tv)
4546

4647
assert.EqualError(t, err, "binary tag-id MUST be 16 bytes")
4748
}
4849

4950
func TestTagID_String(t *testing.T) {
5051
tv := "example.acme.roadrunner-sw-v1-0-0"
5152

52-
actual, err := checkTagID(tv)
53+
actual := NewTagID(tv)
5354

54-
assert.Nil(t, err)
55-
assert.Equal(t, tv, actual)
55+
assert.NotNil(t, actual)
56+
assert.Equal(t, tv, actual.String())
5657
}
5758

5859
func TestTagID_UnhandledType(t *testing.T) {
@@ -64,7 +65,77 @@ func TestTagID_UnhandledType(t *testing.T) {
6465
b: "one",
6566
}
6667

67-
_, err := checkTagID(tv)
68+
err := checkTagID(tv)
6869

6970
assert.EqualError(t, err, "tag-id MUST be []byte or string; got struct { a int; b string }")
7071
}
72+
73+
func TestTagID_UnmarshalXMLAttrString(t *testing.T) {
74+
v := "example.acme.roadrunner-sw-v1-0-0"
75+
76+
tv := xml.Attr{
77+
Name: xml.Name{Local: "tagId"},
78+
Value: v,
79+
}
80+
81+
expected := *NewTagID(v)
82+
83+
var actual TagID
84+
85+
err := actual.UnmarshalXMLAttr(tv)
86+
87+
assert.Nil(t, err)
88+
assert.Equal(t, expected, actual)
89+
}
90+
91+
func TestTagID_MarshalXMLAttrString(t *testing.T) {
92+
v := "example.acme.roadrunner-sw-v1-0-0"
93+
94+
tv := *NewTagID(v)
95+
96+
expected := xml.Attr{
97+
Name: xml.Name{Local: "tagId"},
98+
Value: v,
99+
}
100+
101+
actual, err := tv.MarshalXMLAttr(xml.Name{Local: "tagId"})
102+
103+
assert.Nil(t, err)
104+
assert.Equal(t, expected, actual)
105+
}
106+
107+
func TestTagID_MarshalXMLAttrBytes(t *testing.T) {
108+
v := []byte{
109+
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
110+
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
111+
}
112+
113+
tv := *NewTagID(v)
114+
115+
_, err := tv.MarshalXMLAttr(xml.Name{Local: "tagId"})
116+
117+
assert.EqualError(t, err, "only tag-id of type string can be serialized to XML")
118+
}
119+
120+
func TestTagID_MarshalJSONBytes(t *testing.T) {
121+
v := []byte{
122+
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
123+
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
124+
}
125+
126+
tv := *NewTagID(v)
127+
128+
_, err := tv.MarshalJSON()
129+
130+
assert.EqualError(t, err, "only tag-id of type string can be serialized to JSON")
131+
}
132+
133+
func TestTagID_UnMarshalJSONUnhandled(t *testing.T) {
134+
tv := []byte(`{ "k": "0" }`)
135+
136+
var actual TagID
137+
138+
err := actual.UnmarshalJSON(tv)
139+
140+
assert.EqualError(t, err, "expecting string, found map[string]interface {} instead")
141+
}

0 commit comments

Comments
 (0)