mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-06 21:49:08 +02:00
[management] Improve Base62 encoding/decoding performance and robustness (#3391)
This commit is contained in:
+52
-33
@@ -3,56 +3,75 @@ package base62
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
base = uint32(len(alphabet))
|
base = uint32(len(alphabet))
|
||||||
|
maxBase62Digits = 6 // max number of digits required to encode MaxUint32
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrEmptyString = fmt.Errorf("empty string")
|
||||||
|
ErrInvalidChar = fmt.Errorf("invalid character")
|
||||||
|
ErrOverflow = fmt.Errorf("integer overflow")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fixed-size arrays have better performance and lower memory overhead compared to maps for small static sets of data
|
||||||
|
var charToIndex [123]int8 // Assuming ASCII from '\0' - 'z'
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for i := range charToIndex {
|
||||||
|
charToIndex[i] = -1
|
||||||
|
}
|
||||||
|
for i, c := range alphabet {
|
||||||
|
charToIndex[c] = int8(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Encode encodes a uint32 value to a base62 string.
|
// Encode encodes a uint32 value to a base62 string.
|
||||||
func Encode(num uint32) string {
|
// The returned string will be between 1-6 characters long.
|
||||||
if num == 0 {
|
func Encode(n uint32) string {
|
||||||
return string(alphabet[0])
|
if n < base {
|
||||||
|
return string(alphabet[n])
|
||||||
|
}
|
||||||
|
// avoid dynamic memory usage for small, fixed size data
|
||||||
|
buf := [maxBase62Digits]byte{}
|
||||||
|
idx := len(buf)
|
||||||
|
|
||||||
|
for n > 0 {
|
||||||
|
idx--
|
||||||
|
buf[idx] = alphabet[n%base]
|
||||||
|
n /= base
|
||||||
}
|
}
|
||||||
|
|
||||||
var encoded strings.Builder
|
return string(buf[idx:])
|
||||||
|
|
||||||
for num > 0 {
|
|
||||||
remainder := num % base
|
|
||||||
encoded.WriteByte(alphabet[remainder])
|
|
||||||
num /= base
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse the encoded string
|
|
||||||
encodedString := encoded.String()
|
|
||||||
reversed := reverse(encodedString)
|
|
||||||
return reversed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode decodes a base62 string to a uint32 value.
|
// Decode decodes a base62 string to a uint32 value.
|
||||||
|
// Returns an error if the input string is empty, contains invalid characters,
|
||||||
|
// or would result in integer overflow.
|
||||||
func Decode(encoded string) (uint32, error) {
|
func Decode(encoded string) (uint32, error) {
|
||||||
|
if len(encoded) == 0 {
|
||||||
|
return 0, ErrEmptyString
|
||||||
|
}
|
||||||
var decoded uint32
|
var decoded uint32
|
||||||
strLen := len(encoded)
|
for _, char := range encoded {
|
||||||
|
index := int8(-1)
|
||||||
for i, char := range encoded {
|
if int(char) < len(charToIndex) {
|
||||||
index := strings.IndexRune(alphabet, char)
|
index = charToIndex[char]
|
||||||
|
}
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
return 0, fmt.Errorf("invalid character: %c", char)
|
return 0, fmt.Errorf("%w: %c", ErrInvalidChar, char)
|
||||||
|
}
|
||||||
|
// Add overflow check when calculating the decoded value to prevent silent overflow of uint32
|
||||||
|
if decoded > (math.MaxUint32-uint32(index))/base {
|
||||||
|
return 0, fmt.Errorf("%w: %s", ErrOverflow, encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
decoded += uint32(index) * uint32(math.Pow(float64(base), float64(strLen-i-1)))
|
decoded = decoded*base + uint32(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse a string.
|
|
||||||
func reverse(s string) string {
|
|
||||||
runes := []rune(s)
|
|
||||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
runes[i], runes[j] = runes[j], runes[i]
|
|
||||||
}
|
|
||||||
return string(runes)
|
|
||||||
}
|
|
||||||
|
|||||||
+50
-14
@@ -1,31 +1,67 @@
|
|||||||
package base62
|
package base62
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodeDecode(t *testing.T) {
|
func TestEncodeDecode(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
num uint32
|
input uint32
|
||||||
|
expected string
|
||||||
}{
|
}{
|
||||||
{0},
|
{0, "0"},
|
||||||
{1},
|
{1, "1"},
|
||||||
{42},
|
{5, "5"},
|
||||||
{12345},
|
{9, "9"},
|
||||||
{99999},
|
{10, "A"},
|
||||||
{123456789},
|
{42, "g"},
|
||||||
|
{61, "z"},
|
||||||
|
{62, "10"},
|
||||||
|
{'0', "m"},
|
||||||
|
{'9', "v"},
|
||||||
|
{'A', "13"},
|
||||||
|
{'Z', "1S"},
|
||||||
|
{'a', "1Z"},
|
||||||
|
{'z', "1y"},
|
||||||
|
{99999, "Q0t"},
|
||||||
|
{12345, "3D7"},
|
||||||
|
{123456789, "8M0kX"},
|
||||||
|
{math.MaxUint32, "4gfFC3"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tc := range testCases {
|
||||||
encoded := Encode(tt.num)
|
encoded := Encode(tc.input)
|
||||||
|
if encoded != tc.expected {
|
||||||
|
t.Errorf("Encode(%d) = %s; want %s", tc.input, encoded, tc.expected)
|
||||||
|
}
|
||||||
decoded, err := Decode(encoded)
|
decoded, err := Decode(encoded)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Decode error: %v", err)
|
t.Errorf("Expected error nil, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if decoded != tt.num {
|
if decoded != tc.input {
|
||||||
t.Errorf("Decode(%v) = %v, want %v", encoded, decoded, tt.num)
|
t.Errorf("Decode(%v) = %v, want %v", encoded, decoded, tc.input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode handles empty string input with appropriate error
|
||||||
|
func TestDecodeEmptyString(t *testing.T) {
|
||||||
|
if _, err := Decode(""); !errors.Is(err, ErrEmptyString) {
|
||||||
|
t.Errorf("Expected error %v, got %v", ErrEmptyString, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeOverflow(t *testing.T) {
|
||||||
|
if _, err := Decode("4gfFC4"); !errors.Is(err, ErrOverflow) {
|
||||||
|
t.Errorf("Expected error %v, got %v", ErrOverflow, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeInvalid(t *testing.T) {
|
||||||
|
if _, err := Decode("/"); !errors.Is(err, ErrInvalidChar) {
|
||||||
|
t.Errorf("Expected error %v, got %v", ErrInvalidChar, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user