aboutsummaryrefslogtreecommitdiff
path: root/cpe
diff options
context:
space:
mode:
Diffstat (limited to 'cpe')
-rw-r--r--cpe/avstring.go54
-rw-r--r--cpe/avstring_test.go95
-rw-r--r--cpe/avstringtype_string.go25
-rw-r--r--cpe/cpe.go5
-rw-r--r--cpe/part.go41
-rw-r--r--cpe/part_string.go41
-rw-r--r--cpe/part_test.go108
-rw-r--r--cpe/token.go99
-rw-r--r--cpe/token_test.go149
-rw-r--r--cpe/tokentype_string.go25
-rw-r--r--cpe/v23binding.go119
-rw-r--r--cpe/v23binding_test.go132
12 files changed, 893 insertions, 0 deletions
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
+ }
+ })
+ }
+}