diff options
Diffstat (limited to 'internal/cpe')
-rw-r--r-- | internal/cpe/binding.go | 118 | ||||
-rw-r--r-- | internal/cpe/binding_test.go | 132 | ||||
-rw-r--r-- | internal/cpe/cpe.go | 2 |
3 files changed, 251 insertions, 1 deletions
diff --git a/internal/cpe/binding.go b/internal/cpe/binding.go new file mode 100644 index 0000000..8c51d7d --- /dev/null +++ b/internal/cpe/binding.go @@ -0,0 +1,118 @@ +package cpe + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// Common Platform Enumeration binding. +type Binding 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). +} + +// cpe 2.3 formatted string prefix +var cpe23Prefix = "cpe:2.3:" + +// missing prefix error +var missingPrefix = errors.New("missing CPE 2.3 prefix") + +// Create CPE 2.3 binding from formatted string. +func NewBinding(s string) (Binding, error) { + // check prefix + if s[0:len(cpe23Prefix)] != cpe23Prefix { + return Binding{}, missingPrefix + } + + // tokenize string, check for error + toks, err := tokenize([]byte(s[len(cpe23Prefix):])) + if err != nil { + return Binding{}, err + } + + // check token count + if len(toks) != 11 { + err = fmt.Errorf("invalid attribute count: %d != 11", len(toks)) + return Binding{}, err + } + + // create part + part, err := newPart(toks[0]) + if err != nil { + return Binding{}, 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 Binding{}, err + } + } + + // build and return result + return Binding { + 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 +} + +func (v Binding) 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 formatted string from JSON string. +func (me *Binding) UnmarshalJSON(b []byte) error { + // decode json string + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // create binding + binding, err := NewBinding(s) + if err != nil { + return err + } + + // save result, return success + *me = binding + return nil +} + +// Marshal CPE formatted string as JSON string. +func (v Binding) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} diff --git a/internal/cpe/binding_test.go b/internal/cpe/binding_test.go new file mode 100644 index 0000000..7c5154a --- /dev/null +++ b/internal/cpe/binding_test.go @@ -0,0 +1,132 @@ +package cpe + +import ( + "encoding/json" + "testing" +) + +func TestNewBinding(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 := NewBinding(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 := NewBinding(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 TestBindingString(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 := NewBinding(val); err != nil { + t.Error(err) + } else if got.String() != val { + t.Errorf("got \"%s\", exp \"%s\"", got.String(), val) + } + }) + } +} + +func TestBindingUnmarshalJSON(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 Binding + 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 Binding + 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 TestBindingMarshalJSON(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 := NewBinding(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 + } + }) + } +} diff --git a/internal/cpe/cpe.go b/internal/cpe/cpe.go index 331f315..dc38419 100644 --- a/internal/cpe/cpe.go +++ b/internal/cpe/cpe.go @@ -1,5 +1,5 @@ // CPE 2.3 formatted string parser. // -// Source: NISTIR 7605, figure 6-3: +// Source: NISTIR 7695, figure 6-3: // https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf package cpe |