Added Base32 and ParseBase32 methods.

These methods are nearly identical to the base58 methods, except that
these use the z-base-32 character set.
This commit is contained in:
Bruce Marriner 2018-03-12 20:56:32 +00:00
parent 7d434bc4d8
commit 0516e7e5cf
2 changed files with 99 additions and 0 deletions

View File

@ -20,6 +20,10 @@ const (
nodeShift uint8 = stepBits nodeShift uint8 = stepBits
) )
const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769"
var decodeBase32Map [256]byte
const encodeBase58Map = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ" const encodeBase58Map = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
var decodeBase58Map [256]byte var decodeBase58Map [256]byte
@ -41,11 +45,22 @@ func init() {
for i := 0; i < len(encodeBase58Map); i++ { for i := 0; i < len(encodeBase58Map); i++ {
decodeBase58Map[encodeBase58Map[i]] = byte(i) decodeBase58Map[encodeBase58Map[i]] = byte(i)
} }
for i := 0; i < len(encodeBase32Map); i++ {
decodeBase32Map[i] = 0xFF
}
for i := 0; i < len(encodeBase32Map); i++ {
decodeBase32Map[encodeBase32Map[i]] = byte(i)
}
} }
// ErrInvalidBase58 is returned by ParseBase58 when given an invalid []byte // ErrInvalidBase58 is returned by ParseBase58 when given an invalid []byte
var ErrInvalidBase58 = errors.New("invalid base58") var ErrInvalidBase58 = errors.New("invalid base58")
// ErrInvalidBase32 is returned by ParseBase32 when given an invalid []byte
var ErrInvalidBase32 = errors.New("invalid base32")
// Epoch is set to the twitter snowflake epoch of 2006-03-21:20:50:14 GMT // Epoch is set to the twitter snowflake epoch of 2006-03-21:20:50:14 GMT
// You may customize this to set a different epoch for your application. // You may customize this to set a different epoch for your application.
var Epoch int64 = 1288834974657 var Epoch int64 = 1288834974657
@ -128,6 +143,47 @@ func (f ID) Base36() string {
return strconv.FormatInt(int64(f), 36) return strconv.FormatInt(int64(f), 36)
} }
// Base32 uses the z-base-32 character set but encodes and decodes similar
// to base58, allowing it to create an even smaller result string.
// NOTE: There are many different base32 implementations so becareful when
// doing any interoperation interop with other packages.
func (f ID) Base32() string {
if f < 32 {
return string(encodeBase32Map[f])
}
b := make([]byte, 0, 12)
for f >= 32 {
b = append(b, encodeBase32Map[f%32])
f /= 32
}
b = append(b, encodeBase32Map[f])
for x, y := 0, len(b)-1; x < y; x, y = x+1, y-1 {
b[x], b[y] = b[y], b[x]
}
return string(b)
}
// ParseBase32 parses a base32 []byte into a snowflake ID
// NOTE: There are many different base32 implementations so becareful when
// doing any interoperation interop with other packages.
func ParseBase32(b []byte) (ID, error) {
var id int64
for i := range b {
if decodeBase32Map[b[i]] == 0xFF {
return -1, ErrInvalidBase32
}
id = id*32 + int64(decodeBase32Map[b[i]])
}
return ID(id), nil
}
// Base58 returns a base58 string of the snowflake ID // Base58 returns a base58 string of the snowflake ID
func (f ID) Base58() string { func (f ID) Base58() string {

View File

@ -52,6 +52,49 @@ func TestUnmarshalJSON(t *testing.T) {
} }
} }
func TestBase32(t *testing.T) {
node, _ := NewNode(1)
for i := 0; i < 100; i++ {
sf := node.Generate()
b32i := sf.Base32()
psf, err := ParseBase32([]byte(b32i))
if err != nil {
t.Fatal(err)
}
if sf != psf {
t.Fatal("Parsed does not match String.")
}
}
}
func BenchmarkParseBase32(b *testing.B) {
node, _ := NewNode(1)
sf := node.Generate()
b32i := sf.Base32()
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
ParseBase32([]byte(b32i))
}
}
func BenchmarkBase32(b *testing.B) {
node, _ := NewNode(1)
sf := node.Generate()
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
sf.Base32()
}
}
func TestBase58(t *testing.T) { func TestBase58(t *testing.T) {
node, _ := NewNode(1) node, _ := NewNode(1)