diff --git a/snowflake.go b/snowflake.go index 7f10e5e..859a648 100644 --- a/snowflake.go +++ b/snowflake.go @@ -19,6 +19,25 @@ const ( nodeShift uint8 = stepBits ) +const encodeBase58Map = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ" + +var decodeBase58Map [256]byte + +// Create a map for decoding Base58. This speeds up the process tremendously. +func init() { + + for i := 0; i < len(encodeBase58Map); i++ { + decodeBase58Map[i] = 0xFF + } + + for i := 0; i < len(encodeBase58Map); i++ { + decodeBase58Map[encodeBase58Map[i]] = byte(i) + } +} + +// ErrInvalidBase58 is returned by ParseBase58 when given an invalid []byte +var ErrInvalidBase58 = errors.New("invalid base58") + // 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. var Epoch int64 = 1288834974657 @@ -101,6 +120,42 @@ func (f ID) Base36() string { return strconv.FormatInt(int64(f), 36) } +// Base58 returns a base58 string of the snowflake ID +func (f ID) Base58() string { + + if f < 58 { + return string(encodeBase58Map[f]) + } + + b := make([]byte, 0, 11) + for f >= 58 { + b = append(b, encodeBase58Map[f%58]) + f /= 58 + } + b = append(b, encodeBase58Map[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) +} + +// ParseBase58 parses a base58 []byte into a snowflake ID +func ParseBase58(b []byte) (ID, error) { + + var id int64 + + for i := range b { + if decodeBase58Map[b[i]] == 0xFF { + return -1, ErrInvalidBase58 + } + id = id*58 + int64(decodeBase58Map[b[i]]) + } + + return ID(id), nil +} + // Base64 returns a base64 string of the snowflake ID func (f ID) Base64() string { return base64.StdEncoding.EncodeToString(f.Bytes()) diff --git a/snowflake_test.go b/snowflake_test.go index a9c989c..f2ba51d 100644 --- a/snowflake_test.go +++ b/snowflake_test.go @@ -42,6 +42,48 @@ func TestUnmarshalJSON(t *testing.T) { } } +func TestBase58(t *testing.T) { + + node, _ := NewNode(1) + + for i := 0; i < 10; i++ { + + sf := node.Generate() + b58 := sf.Base58() + psf, err := ParseBase58([]byte(b58)) + if err != nil { + t.Fatal(err) + } + if sf != psf { + t.Fatal("Parsed does not match String.") + } + } +} +func BenchmarkParseBase58(b *testing.B) { + + node, _ := NewNode(1) + sf := node.Generate() + b58 := sf.Base58() + + b.ReportAllocs() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + ParseBase58([]byte(b58)) + } +} +func BenchmarkBase58(b *testing.B) { + + node, _ := NewNode(1) + sf := node.Generate() + + b.ReportAllocs() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + sf.Base58() + } +} func BenchmarkGenerate(b *testing.B) { node, _ := NewNode(1)