Skip to content

Commit

Permalink
Wrap TagID into its own type with custom (un)marshalers (#11)
Browse files Browse the repository at this point in the history
Fixes #10
  • Loading branch information
thomas-fossati authored Feb 18, 2021
1 parent 285540f commit b2c5a8e
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 21 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/veraison/swid
go 1.15

require (
github.com/fxamacker/cbor v1.5.1
github.com/fxamacker/cbor/v2 v2.2.0
github.com/stretchr/testify v1.6.1
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
9 changes: 5 additions & 4 deletions softwareidentity.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package swid
import (
"encoding/json"
"encoding/xml"
"errors"
)

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

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

t.TagID = tagID
t.TagID = *tagID

return nil
}
Expand Down
4 changes: 2 additions & 2 deletions softwareidentity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func makeACMEEntityWithRoles(t *testing.T, roles ...interface{}) Entity {

func TestTag_RoundtripPSABundle(t *testing.T) {
tv := SoftwareIdentity{
TagID: TagID("example.acme.roadrunner-sw-v1-0-0"),
TagID: *NewTagID("example.acme.roadrunner-sw-v1-0-0"),
SoftwareName: "Roadrunner software bundle",
SoftwareVersion: "1.0.0",
Entities: Entities{
Expand Down Expand Up @@ -139,7 +139,7 @@ func TestTag_RoundtripPSABundle(t *testing.T) {

func TestTag_RoundtripPSAComponent(t *testing.T) {
tv := SoftwareIdentity{
TagID: TagID("example.acme.roadrunner-sw-bl-v1-0-0"),
TagID: *NewTagID("example.acme.roadrunner-sw-bl-v1-0-0"),
SoftwareName: "Roadrunner boot loader",
SoftwareVersion: "1.0.0",
Entities: Entities{
Expand Down
111 changes: 106 additions & 5 deletions tagid.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,125 @@
package swid

import (
"encoding/hex"
"encoding/json"
"encoding/xml"
"errors"
"fmt"

"github.com/fxamacker/cbor"
)

// TagID is the type of a tag identifier. Allowed formats (enforced via
// checkTagID) are string or [16]byte
type TagID interface{}
type TagID struct {
val interface{}
}

// NewTagID returns a TagID initialized with the supplied value v
// v is either a string or a [16]byte
func NewTagID(v interface{}) *TagID {
if checkTagID(v) != nil {
return nil
}
return &TagID{v}
}

// String returns the value of the TagID as string. If the TagID has type
// [16]byte the Base 16 encoding is returned
func (t TagID) String() string {
switch v := t.val.(type) {
case string:
return v
case []byte:
return hex.EncodeToString(v)
default:
return "unknown type for tag-id"
}
}

func checkTagID(v interface{}) (TagID, error) {
func checkTagID(v interface{}) error {
switch t := v.(type) {
case string:
case []byte:
if len(t) != 16 {
return nil, errors.New("binary tag-id MUST be 16 bytes")
return errors.New("binary tag-id MUST be 16 bytes")
}
default:
return nil, fmt.Errorf("tag-id MUST be []byte or string; got %T", v)
return fmt.Errorf("tag-id MUST be []byte or string; got %T", v)
}

return nil
}

func (t TagID) isString() bool {
switch t.val.(type) {
case string:
return true
}
return false
}

// MarshalXMLAttr encodes the TagID receiver as XML attribute
func (t TagID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
if !t.isString() {
return xml.Attr{}, errors.New("only tag-id of type string can be serialized to XML")
}
return xml.Attr{Name: name, Value: t.String()}, nil
}

// UnmarshalXMLAttr decodes the supplied XML attribute into a TagID
// Note that this can only unmarshal to string.
func (t *TagID) UnmarshalXMLAttr(attr xml.Attr) error {
t.val = attr.Value
return nil
}

// MarshalJSON encodes the TagID receiver as JSON string
func (t TagID) MarshalJSON() ([]byte, error) {
if !t.isString() {
return nil, errors.New("only tag-id of type string can be serialized to JSON")
}

return json.Marshal(t.val)
}

// UnmarshalJSON decodes the supplied JSON data into a TagID
// Note that this can only unmarshal to string.
func (t *TagID) UnmarshalJSON(data []byte) error {
var v interface{}

if err := json.Unmarshal(data, &v); err != nil {
return err
}

switch s := v.(type) {
case string:
t.val = s
return nil
default:
return fmt.Errorf("expecting string, found %T instead", s)
}
}

// MarshalCBOR encodes the TagID receiver to CBOR
func (t TagID) MarshalCBOR() ([]byte, error) {
return em.Marshal(t.val)
}

// UnmarshalCBOR decodes the supplied data into a TagID
func (t *TagID) UnmarshalCBOR(data []byte) error {
var v interface{}

if err := cbor.Unmarshal(data, &v); err != nil {
return err
}

return TagID(v), nil
if err := checkTagID(v); err != nil {
return err
}

t.val = v

return nil
}
91 changes: 81 additions & 10 deletions tagid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package swid

import (
"encoding/xml"
"testing"

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

expected := tv
expected := "00010001000100010001000100010001"

actual, err := checkTagID(tv)
actual := NewTagID(tv)

assert.Nil(t, err)
assert.Equal(t, expected, actual)
assert.NotNil(t, actual)
assert.Equal(t, expected, actual.String())
}

func TestTagID_15Bytes(t *testing.T) {
Expand All @@ -29,7 +30,7 @@ func TestTagID_15Bytes(t *testing.T) {
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
}

_, err := checkTagID(tv)
err := checkTagID(tv)

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

_, err := checkTagID(tv)
err := checkTagID(tv)

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

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

actual, err := checkTagID(tv)
actual := NewTagID(tv)

assert.Nil(t, err)
assert.Equal(t, tv, actual)
assert.NotNil(t, actual)
assert.Equal(t, tv, actual.String())
}

func TestTagID_UnhandledType(t *testing.T) {
Expand All @@ -64,7 +65,77 @@ func TestTagID_UnhandledType(t *testing.T) {
b: "one",
}

_, err := checkTagID(tv)
err := checkTagID(tv)

assert.EqualError(t, err, "tag-id MUST be []byte or string; got struct { a int; b string }")
}

func TestTagID_UnmarshalXMLAttrString(t *testing.T) {
v := "example.acme.roadrunner-sw-v1-0-0"

tv := xml.Attr{
Name: xml.Name{Local: "tagId"},
Value: v,
}

expected := *NewTagID(v)

var actual TagID

err := actual.UnmarshalXMLAttr(tv)

assert.Nil(t, err)
assert.Equal(t, expected, actual)
}

func TestTagID_MarshalXMLAttrString(t *testing.T) {
v := "example.acme.roadrunner-sw-v1-0-0"

tv := *NewTagID(v)

expected := xml.Attr{
Name: xml.Name{Local: "tagId"},
Value: v,
}

actual, err := tv.MarshalXMLAttr(xml.Name{Local: "tagId"})

assert.Nil(t, err)
assert.Equal(t, expected, actual)
}

func TestTagID_MarshalXMLAttrBytes(t *testing.T) {
v := []byte{
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
}

tv := *NewTagID(v)

_, err := tv.MarshalXMLAttr(xml.Name{Local: "tagId"})

assert.EqualError(t, err, "only tag-id of type string can be serialized to XML")
}

func TestTagID_MarshalJSONBytes(t *testing.T) {
v := []byte{
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
}

tv := *NewTagID(v)

_, err := tv.MarshalJSON()

assert.EqualError(t, err, "only tag-id of type string can be serialized to JSON")
}

func TestTagID_UnMarshalJSONUnhandled(t *testing.T) {
tv := []byte(`{ "k": "0" }`)

var actual TagID

err := actual.UnmarshalJSON(tv)

assert.EqualError(t, err, "expecting string, found map[string]interface {} instead")
}

0 comments on commit b2c5a8e

Please sign in to comment.