diff options
Diffstat (limited to 'internal/cpe')
| -rw-r--r-- | internal/cpe/cpe.go | 89 | ||||
| -rw-r--r-- | internal/cpe/cpe_test.go | 144 | ||||
| -rw-r--r-- | internal/cpe/part.go | 41 | ||||
| -rw-r--r-- | internal/cpe/part_string.go | 41 | ||||
| -rw-r--r-- | internal/cpe/part_test.go | 108 | ||||
| -rw-r--r-- | internal/cpe/token.go | 99 | ||||
| -rw-r--r-- | internal/cpe/token_test.go | 149 | 
7 files changed, 438 insertions, 233 deletions
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) +      } +    }) +  } +}  | 
