diff --git a/eat.go b/eat.go index 350c1fb..05af81e 100644 --- a/eat.go +++ b/eat.go @@ -15,6 +15,7 @@ type Eat struct { SecureBoot *bool `cbor:"15,keyasint,omitempty" json:"secure-boot,omitempty"` Debug *Debug `cbor:"16,keyasint,omitempty" json:"debug-disable,omitempty"` Location *Location `cbor:"17,keyasint,omitempty" json:"location,omitempty"` + Profile *Profile `cbor:"18,keyasint,omitempty" json:"eat-profile,omitempty"` Uptime *uint `cbor:"19,keyasint,omitempty" json:"uptime,omitempty"` Submods *Submods `cbor:"20,keyasint,omitempty" json:"submods,omitempty"` diff --git a/go.sum b/go.sum index fc5a34c..3ad7275 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,7 @@ github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/ github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/profile.go b/profile.go new file mode 100644 index 0000000..06a076e --- /dev/null +++ b/profile.go @@ -0,0 +1,255 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package eat + +import ( + "encoding/asn1" + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" +) + +const ( + // MaxASN1OIDLen is the maximum OID length accepted by the implementation + MaxASN1OIDLen = 255 + // MinNumOIDArcs represents the minimum required arcs for a valid OID + MinNumOIDArcs = 3 +) + +const ( + // asn1AbsolueOIDType is the type of an ASN.1 Object Identifier + asn1AbsolueOIDType = 0x06 + // asn1LongLenMask is used to mask bit 8 of the length byte + asn1LongLenMask = 0x80 + // asn1LenBytesMask is used to extract the first 7 bits from the length byte + asn1LenBytesMask = 0x7F +) + +// Profile is either an absolute URI (RFC3986) or an ASN.1 Object Identifier +type Profile struct { + val interface{} +} + +// NewProfile instantiates a Profile object from the given input string +// The string can either be an absolute URI or an ASN.1 Object Identifier +// in dotted-decimal notation. Relative Object Identifiers (e.g., .1.1.29) are +// not accepted. +func NewProfile(urlOrOID string) (*Profile, error) { + p := Profile{} + if err := p.Set(urlOrOID); err != nil { + return nil, err + } + return &p, nil +} + +// Set sets the internal value of the Profile object to the given urlOrOID string +func (s *Profile) Set(urlOrOID string) error { + return s.decodeProfileFromString(urlOrOID) +} + +// Get returns the profile as string (URI or dotted-decimal OID) +func (s Profile) Get() (string, error) { + switch t := s.val.(type) { + case *url.URL: + return t.String(), nil + case asn1.ObjectIdentifier: + return t.String(), nil + default: + return "", fmt.Errorf("no valid EAT profile") + } +} + +// IsURI checks whether a stored profile is a URI +func (s Profile) IsURI() bool { + _, ok := s.val.(*url.URL) + return ok +} + +// IsOID checks whether a stored profile is an OID +func (s Profile) IsOID() bool { + _, ok := s.val.(asn1.ObjectIdentifier) + return ok +} + +// constructASN1fromVal constructs a TLV ASN.1 byte array from an ASN.1 value +// supplied as an input. The supplied OID byte array must be upto +// MaxASN1OIDLen bytes. +func constructASN1fromVal(val []byte) ([]byte, error) { + const maxTLOffset = 3 + var OID [MaxASN1OIDLen + maxTLOffset]byte + asn1OID := OID[:2] + asn1OID[0] = asn1AbsolueOIDType + if len(val) < 127 { + asn1OID[1] = byte(len(val)) + } else if len(val) <= MaxASN1OIDLen { + // extra one byte is sufficient + asn1OID[1] = 1 // Set to 1 to indicate one more byte carries the length + asn1OID[1] |= asn1LongLenMask + asn1OID = append(asn1OID, byte(len(val))) + } else { + return nil, fmt.Errorf("OIDs greater than %d bytes are not accepted", MaxASN1OIDLen) + } + asn1OID = append(asn1OID, val...) + return asn1OID, nil +} + +// decodeProfile decodes from a received CBOR data the profile +// as either a URL or a Object Identifier +func (s *Profile) decodeProfile(val interface{}) error { + switch t := val.(type) { + case string: + u, err := url.Parse(t) + if err != nil { + return fmt.Errorf("profile URL parsing failed: %w", err) + } + if !u.IsAbs() { + return fmt.Errorf("profile URL not in absolute form") + } + s.val = u + case []byte: + var profileOID asn1.ObjectIdentifier + val, err := constructASN1fromVal(t) + if err != nil { + return fmt.Errorf("could not construct valid ASN.1 buffer from ASN.1 value: %w", err) + } + rest, err := asn1.Unmarshal(val, &profileOID) + if err != nil { + return fmt.Errorf("malformed profile OID") + } + if len(rest) > 0 { + return fmt.Errorf("ASN.1 Unmarshal returned with %d leftover bytes", len(rest)) + } + if len(profileOID) < MinNumOIDArcs { + return fmt.Errorf("ASN.1 OID decoding failed: got %d arcs, expecting at least %d", len(profileOID), MinNumOIDArcs) + } + s.val = profileOID + default: + return fmt.Errorf("decoding failed: unexpected type for profile: %T", t) + } + return nil +} + +// extractASNValue extracts the value component from the supplied ASN.1 OID +func extractASNValue(asn1OID []byte) ([]byte, error) { + if asn1OID[0] != asn1AbsolueOIDType { + return nil, fmt.Errorf("the supplied value is not an ASN.1 OID") + } + // offset to default TL bytes + byteOffset := 2 + if asn1OID[1]&asn1LongLenMask != 0 { + byteOffset += int(asn1OID[1] & asn1LenBytesMask) + } + return asn1OID[byteOffset:], nil +} + +// MarshalCBOR encodes the Profile object as a CBOR text string (if it is a URL), +// or as CBOR byte string (if it is an ASN.1 OID) +func (s Profile) MarshalCBOR() ([]byte, error) { + switch t := s.val.(type) { + case *url.URL: + return em.Marshal(t.String()) + + case asn1.ObjectIdentifier: + var asn1OID []byte + asn1OID, err := asn1.Marshal(t) + if err != nil { + return nil, fmt.Errorf("ASN.1 encoding failed for OID: %w", err) + } + asn1OIDval, err := extractASNValue(asn1OID) + if err != nil { + return nil, fmt.Errorf("ASN.1 value extraction failed for OID: %w", err) + } + return em.Marshal(asn1OIDval) + default: + return nil, fmt.Errorf("invalid type for EAT profile") + } +} + +// UnmarshalCBOR attempts to initialize the Profile from the presented +// CBOR data. The data must be a CBOR text string, representing a URL +// or a CBOR byte string representing an Object Identifier +func (s *Profile) UnmarshalCBOR(data []byte) error { + var val interface{} + if len(data) == 0 { + return fmt.Errorf("decoding of CBOR data failed: zero length data buffer") + } + if err := dm.Unmarshal(data, &val); err != nil { + return fmt.Errorf("CBOR decoding of profile failed: %w", err) + } + if err := s.decodeProfile(val); err != nil { + return fmt.Errorf("invalid profile data: %w", err) + } + return nil +} + +func decodeOIDfromString(val string) (asn1.ObjectIdentifier, error) { + // Attempt to decode OID from received string + var oid asn1.ObjectIdentifier + if val == "" { + return nil, fmt.Errorf("no valid OID") + } + + for _, s := range strings.Split(val, ".") { + n, err := strconv.Atoi(s) + if err != nil { + return nil, fmt.Errorf("failed to extract OID from string: %w", err) + } + oid = append(oid, n) + } + if len(oid) < MinNumOIDArcs { + return nil, fmt.Errorf("invalid OID: got %d arcs, expecting at least %d", len(oid), MinNumOIDArcs) + } + return oid, nil +} + +// decodeProfileFromString attempts to decode the supplied string as a URL, +// if that fails, it then attempts to decode it as an OID. +func (s *Profile) decodeProfileFromString(val string) error { + // First attempt to decode profile as a URL + u, err := url.Parse(val) + if err != nil || !u.IsAbs() { + val, err := decodeOIDfromString(val) + if err != nil { + return fmt.Errorf("profile string must be an absolute URL or an ASN.1 OID: %w", err) + } + s.val = val + } else { + s.val = u + } + return nil +} + +// decodeProfileJSON attempts at extracting an absolute URI or ASN.1 OID +// from the supplied JSON string +func (s *Profile) decodeProfileJSON(val string) error { + return s.decodeProfileFromString(val) +} + +// MarshalJSON encodes the receiver Profile into a JSON string +func (s Profile) MarshalJSON() ([]byte, error) { + switch t := s.val.(type) { + case *url.URL: + return json.Marshal(t.String()) + case asn1.ObjectIdentifier: + return json.Marshal(t.String()) + default: + return nil, fmt.Errorf("invalid profile type: %T", t) + } +} + +// UnmarshalJSON attempts at decoding the supplied JSON data into the receiver +// Profile +func (s *Profile) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + if err := s.decodeProfileJSON(v); err != nil { + return fmt.Errorf("encoding of profile failed: %w", err) + } + return nil +} diff --git a/profile_test.go b/profile_test.go new file mode 100644 index 0000000..0d20966 --- /dev/null +++ b/profile_test.go @@ -0,0 +1,343 @@ +package eat + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + InvalidProfile = 100 + InvalidURL = "abcd" + EmptyURL = "" + InvalidOID = "xxxx" + EmptyOID = "" +) + +// TestProfile_GetSet_Basic_URL_OK tests the valid setting of Profile as URL string +func TestProfile_GetSet_Basic_URL_OK(t *testing.T) { + inputURL := "https://samplewebsite.com" + profile, err := NewProfile(inputURL) + assert.Nil(t, err) + + expectedURL := inputURL + actualURL, err := profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedURL, actualURL) + assert.True(t, profile.IsURI()) + + inputURL = "https://samplenewwebsite.co.uk" + err = profile.Set(inputURL) + assert.Nil(t, err) + expectedURL = inputURL + actualURL, err = profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedURL, actualURL) + assert.True(t, profile.IsURI()) +} + +// TestProfile_GetSet_Basic_URL_NOK tests the invalid setting of Profile as URL string +func TestProfile_GetSet_Basic_URL_NOK(t *testing.T) { + profile := &Profile{} + + // Negative test cases, below + inputURL := InvalidURL + expectedError := `profile string must be an absolute URL or an ASN.1 OID: ` + expectedError += `failed to extract OID from string: ` + expectedError += `strconv.Atoi: parsing "abcd": invalid syntax` + err := profile.Set(inputURL) + assert.EqualError(t, err, expectedError) + + inputURL = EmptyURL + expectedError = `profile string must be an absolute URL or an ASN.1 OID: ` + expectedError += `no valid OID` + err = profile.Set(inputURL) + assert.EqualError(t, err, expectedError) +} + +// TestProfile_GetSet_Basic_OID_OK tests the valid case of saving and retrieval of OID as profile +func TestProfile_GetSet_Basic_OID_OK(t *testing.T) { + inputOID := "1.2.3.4" + profile, err := NewProfile(inputOID) + assert.Nil(t, err) + + expectedOID := inputOID + actualOID, err := profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedOID, actualOID) + assert.True(t, profile.IsOID()) + + inputOID = "24.43.27.88" + err = profile.Set(inputOID) + assert.Nil(t, err) + expectedOID = inputOID + actualOID, err = profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedOID, actualOID) + assert.True(t, profile.IsOID()) +} + +// TestProfile_GetSet_Basic_OID_NOK tests the invalid case of saving of OID as profile +func TestProfile_GetSet_Basic_OID_NOK(t *testing.T) { + profile := &Profile{} + // Add a test for OID less than minimum arcs + inputOID := "56.78" + expectedError := `profile string must be an absolute URL or an ASN.1 OID: ` + expectedError += `invalid OID: ` + expectedError += `got 2 arcs, expecting at least 3` + err := profile.Set(inputOID) + assert.EqualError(t, err, expectedError) + + inputOID = InvalidOID + expectedError = `profile string must be an absolute URL or an ASN.1 OID: ` + expectedError += `failed to extract OID from string: ` + expectedError += `strconv.Atoi: parsing "xxxx": invalid syntax` + err = profile.Set(inputOID) + assert.EqualError(t, err, expectedError) + + inputOID = EmptyOID + expectedError = `profile string must be an absolute URL or an ASN.1 OID: ` + expectedError += `no valid OID` + err = profile.Set(inputOID) + assert.EqualError(t, err, expectedError) +} + +// TestProfile_MarshalCBOR_URL_OK tests the valid case of CBOR marshaling of profile been set as an URI +func TestProfile_MarshalCBOR_URL_OK(t *testing.T) { + urlText := "http://example.com" + profile, err := NewProfile(urlText) + assert.Nil(t, err) + + // URI is set as a non tagged string within CBOR payload + // 72 # text(18) + // 687474703a2f2f6578616d706c652e636f6d # "http://example.com" + expected := []byte{ + 0x72, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + } + + actual, err := profile.MarshalCBOR() + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +// TestProfile_MarshalCBOR_URL_NOK tests the invalid case of CBOR marshaling of profile been set as an URI +func TestProfile_MarshalCBOR_URL_NOK(t *testing.T) { + profile := &Profile{} + // (corrupted len byte) + data := []byte{ + 0x72, 0x88, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + } + expectedError := `CBOR decoding of profile failed: ` + expectedError += `cbor: ` + expectedError += `invalid UTF-8 string` + err := profile.UnmarshalCBOR(data) + assert.EqualError(t, err, expectedError) + + // (malformed profile as CBOR ) + data = []byte{ + 0x6B, 0x74, 0x65, 0x78, 0x74, 0x20, 0x73, 0x74, 0x72, + 0x69, 0x6E, 0x67, + } + expectedError = `invalid profile data: ` + expectedError += `profile URL not in absolute form` + err = profile.UnmarshalCBOR(data) + assert.EqualError(t, err, expectedError) +} + +// TestProfile_UnmarshalCBOR_URL_OK tests the valid case of CBOR UnMarshaling of profile claim set as URL +func TestProfile_UnmarshalCBOR_URL_OK(t *testing.T) { + inputURL := "http://example.com" + profile, err := NewProfile(inputURL) + assert.Nil(t, err) + + // 72 # text(18) + // 687474703a2f2f6578616d706c652e636f6d # "http://example.com" + data := []byte{ + 0x72, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + } + expectedURL := inputURL + err = profile.UnmarshalCBOR(data) + assert.Nil(t, err) + actualURL, err := profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedURL, actualURL) +} + +// TestProfile_MarshalCBOR_OID_OK tests the valid CBOR marshaling of profile set as OID +func TestProfile_MarshalCBOR_OID_OK(t *testing.T) { + inputOID := "2.5.2.8192" + profile, err := NewProfile(inputOID) + assert.Nil(t, err) + expected := []byte{ + 0x44, 0x55, 0x02, 0xC0, 0x00, + } + actual, err := profile.MarshalCBOR() + assert.Nil(t, err) + assert.Equal(t, expected, actual) + + // Sample test from section 3.1: https://tools.ietf.org/html/draft-ietf-cbor-tags-oid-05#section-3 + inputOID = "2.16.840.1.101.3.4.2.1" + expected = []byte{ + 0x49, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + } + err = profile.Set(inputOID) + assert.Nil(t, err) + actual, err = profile.MarshalCBOR() + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +// TestProfile_UnmarshalCBOR_OID_OK tests the valid CBOR unmarshaling of profile decoded as OID +func TestProfile_UnmarshalCBOR_OID_OK(t *testing.T) { + inputOID := "2.5.2.8192" + profile, err := NewProfile(inputOID) + assert.Nil(t, err) + + input := []byte{ + 0x44, 0x55, 0x02, 0xC0, 0x00, + } + expectedOID := inputOID + err = profile.UnmarshalCBOR(input) + assert.Nil(t, err) + actualOID, err := profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedOID, actualOID) + + // Section 3.1 of https://tools.ietf.org/html/draft-ietf-cbor-tags-oid-05#section-3 + inputOID = "2.16.840.1.101.3.4.2.1" + + // CBOR Input + input = []byte{ + 0x49, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + } + expectedOID = inputOID + err = profile.UnmarshalCBOR(input) + assert.Nil(t, err) + actualOID, err = profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedOID, actualOID) +} + +// TestProfile_MarshalJSON_URL_OK tests the JSON Marshaling for a known URL +func TestProfile_MarshalJSON_URL_OK(t *testing.T) { + inputURL := "http://example.com" + profile, err := NewProfile(inputURL) + assert.Nil(t, err) + + expected := []byte{ + 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x22, + } + + actual, err := profile.MarshalJSON() + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +// TestProfile_UnmarshalJSON_URL_OK tests the Unmarshaling of a JSON value as URL string +func TestProfile_UnmarshalJSON_URL_OK(t *testing.T) { + inputURL := "http://example.com" + profile, err := NewProfile(inputURL) + assert.Nil(t, err) + + data := []byte{ + 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x22, + } + expectedURL := inputURL + err = profile.UnmarshalJSON(data) + assert.Nil(t, err) + actualURL, err := profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedURL, actualURL) +} + +// TestProfile_UnmarshalJSON_URL_NOK tests the invalid case of Unmarshaling of a JSON value as URL string +func TestProfile_UnmarshalJSON_URL_NOK(t *testing.T) { + profile := &Profile{} + // Corrupted Header + data := []byte{ + 0x10, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x10, + } + + expectedError := `invalid character '\x10' looking for beginning of value` + err := profile.UnmarshalJSON(data) + assert.EqualError(t, err, expectedError) + + // Invalid Input data as string + data = []byte{ + 0x22, 0x88, 0x78, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x22, + } + expectedError = `encoding of profile failed: ` + expectedError += `profile string must be an absolute URL or an ASN.1 OID: ` + expectedError += `failed to extract OID from string: ` + expectedError += `strconv.Atoi: parsing "�xtp://example": invalid syntax` + err = profile.UnmarshalJSON(data) + assert.EqualError(t, err, expectedError) +} + +// TestProfile_MarshalJSON_OID_OK validates the JSON Marshaling of OID as profile +func TestProfile_MarshalJSON_OID_OK(t *testing.T) { + inputOID := "2.5.2.1" + profile, err := NewProfile(inputOID) + assert.Nil(t, err) + expected := []byte(`"2.5.2.1"`) + actual, err := profile.MarshalJSON() + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +// TestProfile_UnmarshalJSON_OID_OK tests the JSON unmarshaling for OID as profile +func TestProfile_UnmarshalJSON_OID_OK(t *testing.T) { + inputOID := "1.2.3.4" + profile, err := NewProfile(inputOID) + assert.Nil(t, err) + input := []byte(`"2.5.2.1"`) + err = profile.UnmarshalJSON(input) + assert.Nil(t, err) + expectedOID := "2.5.2.1" + actualOID, err := profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedOID, actualOID) + + // Partial OID test + inputOID = ".2.3.4" + _, err = NewProfile(inputOID) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "failed to extract OID from string") +} + +// TestProfile_RoundTrip_CBOR_Long_OID_OK, tests for a round trip encode/decode of +func TestProfile_RoundTrip_CBOR_Long_OID_OK(t *testing.T) { + inputOID := "1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1.1.3.6.1.4.1.2706.123.1.2.1" + profile, err := NewProfile(inputOID) + assert.Nil(t, err) + expected := []byte{ + 0x58, 0xa7, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, + 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, + 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, + 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, + 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, + 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, + 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, + 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, + 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, 0x1, 0x3, 0x6, + 0x1, 0x4, 0x1, 0x95, 0x12, 0x7b, 0x1, 0x2, 0x1, + } + actual, err := profile.MarshalCBOR() + assert.Nil(t, err) + assert.Equal(t, expected, actual) + + // Test Unmarshal CBOR + input := actual + expectedOID := inputOID + err = profile.UnmarshalCBOR(input) + assert.Nil(t, err) + actualOID, err := profile.Get() + assert.Nil(t, err) + assert.Equal(t, expectedOID, actualOID) +}