Files

147 lines
3.2 KiB
Go
Raw Permalink Normal View History

2026-08-26 06:41:07 +08:00
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
}