154 lines
3.5 KiB
Go
154 lines
3.5 KiB
Go
/*
|
|
|
|
Package base36 provides a reasonably fast implementation of a binary base36 codec.
|
|
|
|
*/
|
|
package base36
|
|
|
|
// Simplified code based on https://godoc.org/github.com/mr-tron/base58
|
|
// which in turn is based on https://github.com/trezor/trezor-crypto/commit/89a7d7797b806fac
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
const UcAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
const LcAlphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
const maxDigitOrdinal = byte('z')
|
|
const maxDigitValueB36 = 35
|
|
|
|
var revAlphabet [maxDigitOrdinal + 1]byte
|
|
|
|
func init() {
|
|
for i := range revAlphabet {
|
|
revAlphabet[i] = maxDigitValueB36 + 1
|
|
}
|
|
for i, c := range UcAlphabet {
|
|
revAlphabet[byte(c)] = byte(i)
|
|
if c > '9' {
|
|
revAlphabet[byte(c)+32] = byte(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// EncodeToStringUc encodes the given byte-buffer as base36 using [0-9A-Z] as
|
|
// the digit-alphabet
|
|
func EncodeToStringUc(b []byte) string { return encode(b, UcAlphabet) }
|
|
|
|
// EncodeToStringLc encodes the given byte-buffer as base36 using [0-9a-z] as
|
|
// the digit-alphabet
|
|
func EncodeToStringLc(b []byte) string { return encode(b, LcAlphabet) }
|
|
|
|
func encode(inBuf []byte, al string) string {
|
|
|
|
// As a polar opposite to the base58 implementation, using a uint32 here is
|
|
// significantly slower
|
|
var carry uint64
|
|
|
|
var encIdx, valIdx, zcnt, high int
|
|
|
|
inSize := len(inBuf)
|
|
for zcnt < inSize && inBuf[zcnt] == 0 {
|
|
zcnt++
|
|
}
|
|
|
|
// Really this is log(256)/log(36) or 1.55, but integer math is easier
|
|
// Use 2 as a constant and just overallocate
|
|
encSize := (inSize - zcnt) * 2
|
|
|
|
// Allocate one big buffer up front
|
|
// Note: pools *DO NOT* help, the overhead of zeroing the val-half (see below)
|
|
// kills any performance gain to be had
|
|
outBuf := make([]byte, (zcnt + encSize*2))
|
|
|
|
// use the second half for the temporary numeric buffer
|
|
val := outBuf[encSize+zcnt:]
|
|
|
|
high = encSize - 1
|
|
for _, b := range inBuf[zcnt:] {
|
|
valIdx = encSize - 1
|
|
for carry = uint64(b); valIdx > high || carry != 0; valIdx-- {
|
|
carry += uint64((val[valIdx])) * 256
|
|
val[valIdx] = byte(carry % 36)
|
|
carry /= 36
|
|
}
|
|
high = valIdx
|
|
}
|
|
|
|
// Reset the value index to the first significant value position
|
|
for valIdx = 0; valIdx < encSize && val[valIdx] == 0; valIdx++ {
|
|
}
|
|
|
|
// Now write the known-length result to first half of buffer
|
|
encSize += zcnt - valIdx
|
|
|
|
for encIdx = 0; encIdx < zcnt; encIdx++ {
|
|
outBuf[encIdx] = '0'
|
|
}
|
|
|
|
for encIdx < encSize {
|
|
outBuf[encIdx] = al[val[valIdx]]
|
|
encIdx++
|
|
valIdx++
|
|
}
|
|
|
|
return string(outBuf[:encSize])
|
|
}
|
|
|
|
// DecodeString takes a base36 encoded string and returns a slice of the decoded
|
|
// bytes.
|
|
func DecodeString(s string) ([]byte, error) {
|
|
|
|
if len(s) == 0 {
|
|
return nil, fmt.Errorf("can not decode zero-length string")
|
|
}
|
|
|
|
var zcnt int
|
|
|
|
for i := 0; i < len(s) && s[i] == '0'; i++ {
|
|
zcnt++
|
|
}
|
|
|
|
var t, c uint64
|
|
|
|
outi := make([]uint32, (len(s)+3)/4)
|
|
binu := make([]byte, (len(s)+3)*3)
|
|
|
|
for _, r := range s {
|
|
if r > rune(maxDigitOrdinal) || revAlphabet[r] > maxDigitValueB36 {
|
|
return nil, fmt.Errorf("invalid base36 character (%q)", r)
|
|
}
|
|
|
|
c = uint64(revAlphabet[r])
|
|
|
|
for j := len(outi) - 1; j >= 0; j-- {
|
|
t = uint64(outi[j])*36 + c
|
|
c = (t >> 32)
|
|
outi[j] = uint32(t & 0xFFFFFFFF)
|
|
}
|
|
|
|
}
|
|
|
|
mask := (uint(len(s)%4) * 8)
|
|
if mask == 0 {
|
|
mask = 32
|
|
}
|
|
mask -= 8
|
|
var j, cnt int
|
|
for j, cnt = 0, 0; j < len(outi); j++ {
|
|
for mask < 32 { // loop relies on uint overflow
|
|
binu[cnt] = byte(outi[j] >> mask)
|
|
mask -= 8
|
|
cnt++
|
|
}
|
|
mask = 24
|
|
}
|
|
|
|
for n := zcnt; n < len(binu); n++ {
|
|
if binu[n] > 0 {
|
|
return binu[n-zcnt : cnt], nil
|
|
}
|
|
}
|
|
return binu[:cnt], nil
|
|
}
|