From 9c17b97cd0f83be3fff9fa4e87fd1d29052ea616 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Fri, 4 Feb 2022 00:35:31 -0500 Subject: rename to github.com/pablotron/cvez, remove internal libs --- cpe/avstring.go | 54 ++++++++++++++++ cpe/avstring_test.go | 95 +++++++++++++++++++++++++++++ cpe/avstringtype_string.go | 25 ++++++++ cpe/cpe.go | 5 ++ cpe/part.go | 41 +++++++++++++ cpe/part_string.go | 41 +++++++++++++ cpe/part_test.go | 108 ++++++++++++++++++++++++++++++++ cpe/token.go | 99 ++++++++++++++++++++++++++++++ cpe/token_test.go | 149 +++++++++++++++++++++++++++++++++++++++++++++ cpe/tokentype_string.go | 25 ++++++++ cpe/v23binding.go | 119 ++++++++++++++++++++++++++++++++++++ cpe/v23binding_test.go | 132 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 893 insertions(+) create mode 100644 cpe/avstring.go create mode 100644 cpe/avstring_test.go create mode 100644 cpe/avstringtype_string.go create mode 100644 cpe/cpe.go create mode 100644 cpe/part.go create mode 100644 cpe/part_string.go create mode 100644 cpe/part_test.go create mode 100644 cpe/token.go create mode 100644 cpe/token_test.go create mode 100644 cpe/tokentype_string.go create mode 100644 cpe/v23binding.go create mode 100644 cpe/v23binding_test.go (limited to 'cpe') diff --git a/cpe/avstring.go b/cpe/avstring.go new file mode 100644 index 0000000..46e16cf --- /dev/null +++ b/cpe/avstring.go @@ -0,0 +1,54 @@ +package cpe + +//go:generate stringer -linecomment -type=AvStringType + +import ( + "fmt" +) + +// type of avstring. +type AvStringType byte + +const ( + AnyString AvStringType = iota // any + NaString // na + ValString // val +) + +// String value (NISTIR 7695, CPE 2.3 spec, Figure 6-3) +type AvString struct { + Type AvStringType // value type + Val string // value +} + +// token type to avstring type map +var avTypes = map[tokenType]AvStringType { + anyToken: AnyString, + naToken: NaString, + valToken: ValString, +} + +// create new AvString from token. +func newAvString(t token) (AvString, error) { + if at, ok := avTypes[t.Type]; ok { + return AvString { at, t.Val }, nil + } else { + err := fmt.Errorf("invalid token type: 0x%02x", byte(t.Type)) + return AvString { 0, "" }, err + } +} + +// Serialize as string. +func (s AvString) String() string { + switch s.Type { + case AnyString: + return "*" + case NaString: + return "-" + case ValString: + return s.Val + default: + // not sure what to return here + return "" + } +} diff --git a/cpe/avstring_test.go b/cpe/avstring_test.go new file mode 100644 index 0000000..8cbb14b --- /dev/null +++ b/cpe/avstring_test.go @@ -0,0 +1,95 @@ +package cpe + +import ( + "testing" +) + +func TestNewAvString(t *testing.T) { + passTests := []struct { + name string + val token + exp AvString + } { + { "any", token { Type: anyToken }, AvString { Type: AnyString } }, + { "na", token { Type: naToken }, AvString { Type: NaString } }, + { "empty", token { Type: valToken }, AvString { ValString, "" } }, + { "foo", token { valToken, "foo" }, AvString { ValString, "foo" } }, + } + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + got, err := newAvString(test.val) + if err != nil { + t.Error(err) + } else if got.Type != test.exp.Type { + t.Errorf("token: got %s, exp %s", got.Type, test.exp.Type) + } else if got.Type == ValString && got.Val != test.exp.Val { + t.Errorf("value: got \"%s\", exp \"%s\"", got.Val, test.exp.Val) + } + }) + } + + failTests := []struct { + name string + val token + exp string + } {{ + name: "invalid token", + val: token { Type: tokenType(127), Val: "foo" }, + exp: "invalid token type: 0x7f", + }} + + for _, test := range(failTests) { + t.Run(test.name, func(t *testing.T) { + got, err := newAvString(test.val) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestAvStringString(t *testing.T) { + tests := []struct { + name string + val AvString + exp string + } { + { "any", AvString { AnyString, "" }, "*" }, + { "na", AvString { NaString, "" }, "-" }, + { "foo", AvString { ValString, "foo" }, "foo" }, + { "junk", AvString { AvStringType(255), "foo" }, "" }, + } + + for _, test := range(tests) { + t.Run(test.name, func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("value: got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestAvStringTypeString(t *testing.T) { + tests := []struct { + val AvStringType + exp string + } { + { AnyString, "any" }, + { NaString, "na" }, + { ValString, "val" }, + { AvStringType(255), "AvStringType(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/cpe/avstringtype_string.go b/cpe/avstringtype_string.go new file mode 100644 index 0000000..6fb88e1 --- /dev/null +++ b/cpe/avstringtype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=AvStringType"; DO NOT EDIT. + +package cpe + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AnyString-0] + _ = x[NaString-1] + _ = x[ValString-2] +} + +const _AvStringType_name = "anynaval" + +var _AvStringType_index = [...]uint8{0, 3, 5, 8} + +func (i AvStringType) String() string { + if i >= AvStringType(len(_AvStringType_index)-1) { + return "AvStringType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _AvStringType_name[_AvStringType_index[i]:_AvStringType_index[i+1]] +} diff --git a/cpe/cpe.go b/cpe/cpe.go new file mode 100644 index 0000000..dc38419 --- /dev/null +++ b/cpe/cpe.go @@ -0,0 +1,5 @@ +// CPE 2.3 formatted string parser. +// +// Source: NISTIR 7695, figure 6-3: +// https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf +package cpe diff --git a/cpe/part.go b/cpe/part.go new file mode 100644 index 0000000..ef91f7c --- /dev/null +++ b/cpe/part.go @@ -0,0 +1,41 @@ +package cpe + +//go:generate stringer -linecomment -type=Part + +import ( + "fmt" +) + +// CPE part +type Part byte + +const ( + ApplicationPart Part = 'a' // a + OperatingSystemPart Part = 'o' // o + HardwarePart Part = 'h' // h + AnyPart Part = '*' // * + NAPart Part = '-' // - +) + +// create new part from token +func newPart(t token) (Part, error) { + switch t.Type { + case anyToken: + return AnyPart, nil + case naToken: + return NAPart, nil + case valToken: + switch t.Val { + case "a": + return ApplicationPart, nil + case "o": + return OperatingSystemPart, nil + case "h": + return HardwarePart, nil + default: + return 0, fmt.Errorf("unknown part: \"%s\"", t.Val) + } + default: + return 0, fmt.Errorf("unknown token type: 0x%02x", byte(t.Type)) + } +} diff --git a/cpe/part_string.go b/cpe/part_string.go new file mode 100644 index 0000000..98b9fd3 --- /dev/null +++ b/cpe/part_string.go @@ -0,0 +1,41 @@ +// Code generated by "stringer -linecomment -type=Part"; DO NOT EDIT. + +package cpe + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ApplicationPart-97] + _ = x[OperatingSystemPart-111] + _ = x[HardwarePart-104] + _ = x[AnyPart-42] + _ = x[NAPart-45] +} + +const ( + _Part_name_0 = "*" + _Part_name_1 = "-" + _Part_name_2 = "a" + _Part_name_3 = "h" + _Part_name_4 = "o" +) + +func (i Part) String() string { + switch { + case i == 42: + return _Part_name_0 + case i == 45: + return _Part_name_1 + case i == 97: + return _Part_name_2 + case i == 104: + return _Part_name_3 + case i == 111: + return _Part_name_4 + default: + return "Part(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/cpe/part_test.go b/cpe/part_test.go new file mode 100644 index 0000000..269d16e --- /dev/null +++ b/cpe/part_test.go @@ -0,0 +1,108 @@ +package cpe + +import ( + "testing" +) + +func TestNewPart(t *testing.T) { + passTests := []struct { + name string + val token + exp Part + } {{ + name: "any", + val: token { Type: anyToken }, + exp: AnyPart, + }, { + name: "na", + val: token { Type: naToken }, + exp: NAPart, + }, { + name: "a", + val: token { Type: valToken, Val: "a" }, + exp: ApplicationPart, + }, { + name: "h", + val: token { Type: valToken, Val: "h" }, + exp: HardwarePart, + }, { + name: "o", + val: token { Type: valToken, Val: "o" }, + exp: OperatingSystemPart, + }} + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + if got, err := newPart(test.val); err != nil { + t.Error(err) + } else if got != test.exp { + t.Errorf("got %s, exp %s", got, test.exp) + } + }) + } + + failTests := []struct { + name string + val token + exp string + } {{ + name: "invalid token", + val: token { Type: valToken, Val: "foo" }, + exp: "unknown part: \"foo\"", + }, { + name: "empty token", + val: token { Type: valToken }, + exp: "unknown part: \"\"", + }, { + name: "unknown token type", + val: token { Type: tokenType(255) }, + exp: "unknown token type: 0xff", + }} + + for _, test := range(failTests) { + t.Run(test.name, func(t *testing.T) { + // tokenize, check for error + got, err := newPart(test.val) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestPartString(t *testing.T) { + tests := []struct { + val Part + exp string + } {{ + val: AnyPart, + exp: "*", + }, { + val: NAPart, + exp: "-", + }, { + val: ApplicationPart, + exp: "a", + }, { + val: HardwarePart, + exp: "h", + }, { + val: OperatingSystemPart, + exp: "o", + }, { + val: Part(255), + exp: "Part(255)", + }} + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/cpe/token.go b/cpe/token.go new file mode 100644 index 0000000..d88e773 --- /dev/null +++ b/cpe/token.go @@ -0,0 +1,99 @@ +package cpe + +//go:generate stringer -linecomment -type=tokenType + +import ( + "errors" + "fmt" +) + +// token type +type tokenType byte + +const ( + anyToken tokenType = iota // any + naToken // na + valToken // val +) + +// token +type token struct { + Type tokenType // token type + Val string // token value +} + +// parse buffer into token. +func newToken(val []byte) token { + if len(val) > 0 { + switch val[0] { + case '*': + return token { Type: anyToken } + case '-': + return token { Type: naToken } + default: + return token { Type: valToken, Val: string(val) } + } + } else { + // empty value + return token { Type: valToken } + } +} + +// unterminated escape error +var unterminatedEsc = errors.New("unterminated escape at end of buffer") + +// Parse buffer into list of tokens. +func tokenize(buf []byte) ([]token, error) { + // build result + var r []token + + // current token and escape state + var curr []byte + esc := false + + // build result + for _, b := range(buf) { + if esc { + switch b { + // valid escaped characters + case '\\', '*', '-', '!', '"', '#', '$', '%', '&', '\'', '(', ')', + '+', ',', '/', ':', ';', '<', '=', '>', '@', '[', ']', '^', '`', + '{', '|', '}', '~': + curr = append(curr, b) + esc = false + default: + return r, fmt.Errorf("invalid escape byte: 0x%02x", b) + } + } else { + switch b { + case '\\': + esc = true + case ':': + // push token, clear buffer + r = append(r, newToken(curr)) + curr = nil + case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '*', '?': + curr = append(curr, b) + default: + return r, fmt.Errorf("invalid byte: 0x%02x", b) + } + } + } + + // check for unterminated escape + if esc { + return r, unterminatedEsc + } + + if len(curr) > 0 { + // push token, clear buffer + r = append(r, newToken(curr)) + curr = nil + } + + // return success + return r, nil +} diff --git a/cpe/token_test.go b/cpe/token_test.go new file mode 100644 index 0000000..595df2c --- /dev/null +++ b/cpe/token_test.go @@ -0,0 +1,149 @@ +package cpe + +import ( + "reflect" + "testing" +) + +func TestTokenTypeString(t *testing.T) { + tests := []struct { + val tokenType + exp string + } { + { anyToken, "any" }, + { naToken, "na" }, + { valToken, "val" }, + { tokenType(255), "tokenType(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestNewToken(t *testing.T) { + passTests := []struct { + name string + val string + exp token + } { + { "any", "*", token { Type: anyToken } }, + { "na", "-", token { Type: naToken } }, + { "empty", "", token { Type: valToken } }, + { "foo", "foo", token { Type: valToken, Val: "foo" } }, + } + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + got := newToken([]byte(test.val)) + if got.Type != test.exp.Type { + t.Errorf("token: got %s, exp %s", got.Type, test.exp.Type) + } else if got.Type == valToken && got.Val != test.exp.Val { + t.Errorf("value: got \"%s\", exp \"%s\"", got.Val, test.exp.Val) + } + }) + } +} + +func TestTokenize(t *testing.T) { + passTests := []struct { + val string + exp []token + } {{ + val: "foo", + exp: []token { token { Type: valToken, Val: "foo" } }, + }, { + val: "foo:bar", + exp: []token { + token { Type: valToken, Val: "foo" }, + token { Type: valToken, Val: "bar" }, + }, + }, { + val: "*", + exp: []token { token { Type: anyToken } }, + }, { + val: "-", + exp: []token { token { Type: naToken } }, + }, { + val: "*:bar", + exp: []token { + token { Type: anyToken }, + token { Type: valToken, Val: "bar" }, + }, + }, { + val: "foo:*", + exp: []token { + token { Type: valToken, Val: "foo" }, + token { Type: anyToken }, + }, + }, { + val: "-:bar", + exp: []token { + token { Type: naToken }, + token { Type: valToken, Val: "bar" }, + }, + }, { + val: "foo:-", + exp: []token { + token { Type: valToken, Val: "foo" }, + token { Type: naToken }, + }, + }, { + val: "foo\\*:-", + exp: []token { + token { Type: valToken, Val: "foo*" }, + token { Type: naToken }, + }, + }} + + for _, test := range(passTests) { + t.Run(test.val, func(t *testing.T) { + // tokenize, check for error + got, err := tokenize([]byte(test.val)) + if err != nil { + t.Error(err) + return + } + + if !reflect.DeepEqual(got, test.exp) { + t.Errorf("token: got %v, exp %v", got, test.exp) + return + } + }) + } + + failTests := []struct { + id string + val string + exp string + } {{ + id: "invalid escape", + val: "foo\\.", + exp: "invalid escape byte: 0x2e", + }, { + id: "invalid byte", + val: "\n", + exp: "invalid byte: 0x0a", + }, { + id: "unterminated escape", + val: "\\", + exp: "unterminated escape at end of buffer", + }} + + for _, test := range(failTests) { + t.Run(test.id, func(t *testing.T) { + // tokenize, check for error + got, err := tokenize([]byte(test.val)) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} diff --git a/cpe/tokentype_string.go b/cpe/tokentype_string.go new file mode 100644 index 0000000..7b53758 --- /dev/null +++ b/cpe/tokentype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=tokenType"; DO NOT EDIT. + +package cpe + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[anyToken-0] + _ = x[naToken-1] + _ = x[valToken-2] +} + +const _tokenType_name = "anynaval" + +var _tokenType_index = [...]uint8{0, 3, 5, 8} + +func (i tokenType) String() string { + if i >= tokenType(len(_tokenType_index)-1) { + return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _tokenType_name[_tokenType_index[i]:_tokenType_index[i+1]] +} diff --git a/cpe/v23binding.go b/cpe/v23binding.go new file mode 100644 index 0000000..24fb7b2 --- /dev/null +++ b/cpe/v23binding.go @@ -0,0 +1,119 @@ +package cpe + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// CPE 2.3 binding. +type V23Binding struct { + Part Part // Part attribute (NISTIR7695 5.3.3.1). + Vendor AvString // Vendor attribute (NISTIR7695 5.3.3.2). + Product AvString // Product attribute (NISTIR7695 5.3.3.3). + Version AvString // Version attribute (NISTIR7695 5.3.3.4). + Update AvString // Update attribute (NISTIR7695 5.3.3.5). + Edition AvString // Edition attribute (NISTIR7695 5.3.3.6). + SwEdition AvString // Software edition attribute (NISTIR7695 5.3.3.7). + TargetSw AvString // Target software attribute (NISTIR7695 5.3.3.8). + TargetHw AvString // Target hardware attribute (NISTIR7695 5.3.3.9). + Lang AvString // Language attribute (NISTIR7695 5.3.3.10). + Other AvString // Other attribute (NISTIR7695 5.3.3.11). +} + +// formatted string prefix +var cpe23Prefix = "cpe:2.3:" + +// missing prefix error +var missingPrefix = errors.New("missing CPE 2.3 prefix") + +// Create binding from CPE 2.3 formatted string. +func NewV23Binding(s string) (V23Binding, error) { + // check prefix + if s[0:len(cpe23Prefix)] != cpe23Prefix { + return V23Binding{}, missingPrefix + } + + // tokenize string, check for error + toks, err := tokenize([]byte(s[len(cpe23Prefix):])) + if err != nil { + return V23Binding{}, err + } + + // check token count + if len(toks) != 11 { + err = fmt.Errorf("invalid attribute count: %d != 11", len(toks)) + return V23Binding{}, err + } + + // create part + part, err := newPart(toks[0]) + if err != nil { + return V23Binding{}, err + } + + // parse tokens into strings + strs := make([]AvString, len(toks) - 1) + for i, t := range(toks[1:]) { + if strs[i], err = newAvString(t); err != nil { + return V23Binding{}, err + } + } + + // build and return result + return V23Binding { + Part: part, + Vendor: strs[0], + Product: strs[1], + Version: strs[2], + Update: strs[3], + Edition: strs[4], + Lang: strs[5], + SwEdition: strs[6], + TargetSw: strs[7], + TargetHw: strs[8], + Other: strs[9], + }, nil +} + +// Serialize CPE 2.3 binding as formatted string. +func (v V23Binding) String() string { + return cpe23Prefix + strings.Join([]string { + v.Part.String(), + v.Vendor.String(), + v.Product.String(), + v.Version.String(), + v.Update.String(), + v.Edition.String(), + v.Lang.String(), + v.SwEdition.String(), + v.TargetSw.String(), + v.TargetHw.String(), + v.Other.String(), + }, ":") +} + +// Unmarshal CPE 2.3 binding from JSON string. +func (me *V23Binding) UnmarshalJSON(b []byte) error { + // decode json string + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // create binding + binding, err := NewV23Binding(s) + if err != nil { + return err + } + + // save result, return success + *me = binding + return nil +} + +// Marshal CPE binding as JSON string. +func (v V23Binding) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} diff --git a/cpe/v23binding_test.go b/cpe/v23binding_test.go new file mode 100644 index 0000000..4fa46bb --- /dev/null +++ b/cpe/v23binding_test.go @@ -0,0 +1,132 @@ +package cpe + +import ( + "encoding/json" + "testing" +) + +func TestNewV23Binding(t *testing.T) { + passTests := []string { + "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + } + + for _, val := range(passTests) { + t.Run(val, func(t *testing.T) { + if _, err := NewV23Binding(val); err != nil { + t.Error(err) + } + }) + } + + failTests := []struct { + val string + exp string + } {{ + val: "o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + exp: "missing CPE 2.3 prefix", + }, { + val: "cpe:2.3:\n", + exp: "invalid byte: 0x0a", + }, { + val: "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*", + exp: "invalid attribute count: 10 != 11", + }, { + val: "cpe:2.3:foo:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + exp: "unknown part: \"foo\"", + }} + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + got, err := NewV23Binding(test.val) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestV23BindingString(t *testing.T) { + tests := []string { + "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + } + + for _, val := range(tests) { + t.Run(val, func(t *testing.T) { + if got, err := NewV23Binding(val); err != nil { + t.Error(err) + } else if got.String() != val { + t.Errorf("got \"%s\", exp \"%s\"", got.String(), val) + } + }) + } +} + +func TestV23BindingUnmarshalJSON(t *testing.T) { + passTests := []string { + `"cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*"`, + } + + for _, val := range(passTests) { + t.Run(val, func(t *testing.T) { + var b V23Binding + if err := json.Unmarshal([]byte(val), &b); err != nil { + t.Error(err) + } + }) + } + + failTests := []struct { + val string + exp string + } {{ + val: `"o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*`, + exp: "unexpected end of JSON input", + }, { + val: `"o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*"`, + exp: "missing CPE 2.3 prefix", + }} + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + var got V23Binding + if err := json.Unmarshal([]byte(test.val), &got); err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestV23BindingMarshalJSON(t *testing.T) { + tests := []string { + "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + } + + for _, val := range(tests) { + t.Run(val, func(t *testing.T) { + // create binding, check for error + b, err := NewV23Binding(val) + if err != nil { + t.Error(err) + return + } + + // marshal json, check for error + got, err := json.Marshal(b) + if err != nil { + t.Error(err) + return + } + + // build/check expected value + exp := "\"" + val + "\"" + if string(got) != exp { + t.Errorf("got \"%s\", exp \"%s\"", string(got), exp) + return + } + }) + } +} -- cgit v1.2.3