From e3a6bc8502a0366aee9040e938789a6016e2fa4a Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Wed, 2 Feb 2022 21:45:23 -0500 Subject: internal/cpe: add token.go, part.go, and tests --- internal/cpe/cpe.go | 89 -------------------------- internal/cpe/cpe_test.go | 144 ------------------------------------------ internal/cpe/part.go | 41 ++++++++++++ internal/cpe/part_string.go | 41 ++++++++++++ internal/cpe/part_test.go | 108 ++++++++++++++++++++++++++++++++ internal/cpe/token.go | 99 +++++++++++++++++++++++++++++ internal/cpe/token_test.go | 149 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 438 insertions(+), 233 deletions(-) create mode 100644 internal/cpe/part.go create mode 100644 internal/cpe/part_string.go create mode 100644 internal/cpe/part_test.go create mode 100644 internal/cpe/token.go create mode 100644 internal/cpe/token_test.go (limited to 'internal') diff --git a/internal/cpe/cpe.go b/internal/cpe/cpe.go index 73930e8..331f315 100644 --- a/internal/cpe/cpe.go +++ b/internal/cpe/cpe.go @@ -3,92 +3,3 @@ // Source: NISTIR 7605, figure 6-3: // https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf package cpe - -import ( - "fmt" -) - -//go:generate stringer -linecomment -type=tokenType - -// 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, Val: "" } - } -} - -// Parse buffer into slice 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) - } - } - } - - if len(curr) > 0 { - // push token, clear buffer - r = append(r, newToken(curr)) - curr = nil - } - - // return success - return r, nil -} diff --git a/internal/cpe/cpe_test.go b/internal/cpe/cpe_test.go index e842c75..0f121b6 100644 --- a/internal/cpe/cpe_test.go +++ b/internal/cpe/cpe_test.go @@ -1,145 +1 @@ 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", - }} - - 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/internal/cpe/part.go b/internal/cpe/part.go new file mode 100644 index 0000000..ef91f7c --- /dev/null +++ b/internal/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/internal/cpe/part_string.go b/internal/cpe/part_string.go new file mode 100644 index 0000000..98b9fd3 --- /dev/null +++ b/internal/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/internal/cpe/part_test.go b/internal/cpe/part_test.go new file mode 100644 index 0000000..269d16e --- /dev/null +++ b/internal/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/internal/cpe/token.go b/internal/cpe/token.go new file mode 100644 index 0000000..d88e773 --- /dev/null +++ b/internal/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/internal/cpe/token_test.go b/internal/cpe/token_test.go new file mode 100644 index 0000000..595df2c --- /dev/null +++ b/internal/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) + } + }) + } +} -- cgit v1.2.3