chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package firmwareversion
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type identifier struct {
|
||||
numeric bool
|
||||
number uint64
|
||||
text string
|
||||
}
|
||||
|
||||
type semanticVersion struct {
|
||||
core [3]uint64
|
||||
pre []identifier
|
||||
}
|
||||
|
||||
// AtLeast compares SemVer-compatible versions. Vendor-specific opaque versions
|
||||
// are accepted only when they exactly match the configured minimum, so an
|
||||
// unorderable version can never silently bypass a minimum policy.
|
||||
func AtLeast(current, minimum string) bool {
|
||||
current = strings.TrimSpace(current)
|
||||
minimum = strings.TrimSpace(minimum)
|
||||
if minimum == "" {
|
||||
return true
|
||||
}
|
||||
left, leftOK := parse(current)
|
||||
right, rightOK := parse(minimum)
|
||||
if !leftOK || !rightOK {
|
||||
return current == minimum
|
||||
}
|
||||
return compare(left, right) >= 0
|
||||
}
|
||||
|
||||
func parse(value string) (semanticVersion, bool) {
|
||||
value = strings.TrimSpace(value)
|
||||
if strings.HasPrefix(value, "v") || strings.HasPrefix(value, "V") {
|
||||
value = value[1:]
|
||||
}
|
||||
if build := strings.IndexByte(value, '+'); build >= 0 {
|
||||
value = value[:build]
|
||||
}
|
||||
coreText, preText, _ := strings.Cut(value, "-")
|
||||
parts := strings.Split(coreText, ".")
|
||||
if len(parts) != 3 {
|
||||
return semanticVersion{}, false
|
||||
}
|
||||
var result semanticVersion
|
||||
for index, part := range parts {
|
||||
number, ok := numericIdentifier(part)
|
||||
if !ok {
|
||||
return semanticVersion{}, false
|
||||
}
|
||||
result.core[index] = number
|
||||
}
|
||||
if preText == "" {
|
||||
return result, true
|
||||
}
|
||||
for _, part := range strings.Split(preText, ".") {
|
||||
if part == "" || !validIdentifier(part) {
|
||||
return semanticVersion{}, false
|
||||
}
|
||||
if number, ok := numericIdentifier(part); ok {
|
||||
result.pre = append(result.pre, identifier{numeric: true, number: number, text: part})
|
||||
} else {
|
||||
result.pre = append(result.pre, identifier{text: part})
|
||||
}
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func compare(left, right semanticVersion) int {
|
||||
for index := range left.core {
|
||||
if left.core[index] < right.core[index] {
|
||||
return -1
|
||||
}
|
||||
if left.core[index] > right.core[index] {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if len(left.pre) == 0 && len(right.pre) == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(left.pre) == 0 {
|
||||
return 1
|
||||
}
|
||||
if len(right.pre) == 0 {
|
||||
return -1
|
||||
}
|
||||
limit := min(len(left.pre), len(right.pre))
|
||||
for index := 0; index < limit; index++ {
|
||||
leftID, rightID := left.pre[index], right.pre[index]
|
||||
if leftID.numeric && rightID.numeric {
|
||||
if leftID.number < rightID.number {
|
||||
return -1
|
||||
}
|
||||
if leftID.number > rightID.number {
|
||||
return 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if leftID.numeric != rightID.numeric {
|
||||
if leftID.numeric {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if leftID.text < rightID.text {
|
||||
return -1
|
||||
}
|
||||
if leftID.text > rightID.text {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if len(left.pre) < len(right.pre) {
|
||||
return -1
|
||||
}
|
||||
if len(left.pre) > len(right.pre) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func numericIdentifier(value string) (uint64, bool) {
|
||||
if value == "" || (len(value) > 1 && value[0] == '0') {
|
||||
return 0, false
|
||||
}
|
||||
for _, char := range value {
|
||||
if char < '0' || char > '9' {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
number, err := strconv.ParseUint(value, 10, 64)
|
||||
return number, err == nil
|
||||
}
|
||||
|
||||
func validIdentifier(value string) bool {
|
||||
for _, char := range value {
|
||||
if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user