115 lines
2.7 KiB
Go
115 lines
2.7 KiB
Go
// Package base62 implements base62 encoding, fork from https://github.com/yihleego/base62
|
|
package base62
|
|
|
|
import (
|
|
"math"
|
|
"strconv"
|
|
)
|
|
|
|
// An Encoding is a radix 62 encoding/decoding scheme, defined by a 62-character alphabet.
|
|
type Encoding struct {
|
|
encode [62]byte
|
|
decodeMap [256]byte
|
|
}
|
|
|
|
const encodeStd = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
|
|
// newEncoding returns a new padded Encoding defined by the given alphabet,
|
|
// which must be a 62-byte string that does not contain the padding character
|
|
// or CR / LF ('\r', '\n').
|
|
func newEncoding(encoder string) *Encoding {
|
|
e := new(Encoding)
|
|
copy(e.encode[:], encoder)
|
|
|
|
for i := 0; i < len(e.decodeMap); i++ {
|
|
e.decodeMap[i] = 0xFF
|
|
}
|
|
for i := 0; i < len(encoder); i++ {
|
|
e.decodeMap[encoder[i]] = byte(i)
|
|
}
|
|
return e
|
|
}
|
|
|
|
// StdEncoding is the standard base62 encoding.
|
|
var StdEncoding = newEncoding(encodeStd)
|
|
|
|
// Encode encodes src using the encoding enc.
|
|
func (enc *Encoding) Encode(src []byte) []byte {
|
|
if len(src) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// enc is a pointer receiver, so the use of enc.encode within the hot
|
|
// loop below means a nil check at every operation. Lift that nil check
|
|
// outside the loop to speed up the encoder.
|
|
_ = enc.encode
|
|
|
|
rs := 0
|
|
cs := int(math.Ceil(math.Log(256) / math.Log(62) * float64(len(src))))
|
|
dst := make([]byte, cs)
|
|
for i := range src {
|
|
c := 0
|
|
v := int(src[i])
|
|
for j := cs - 1; j >= 0 && (v != 0 || c < rs); j-- {
|
|
v += 256 * int(dst[j])
|
|
dst[j] = byte(v % 62)
|
|
v /= 62
|
|
c++
|
|
}
|
|
rs = c
|
|
}
|
|
for i := range dst {
|
|
dst[i] = enc.encode[dst[i]]
|
|
}
|
|
if cs > rs {
|
|
return dst[cs-rs:]
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// Decode decodes src using the encoding enc.
|
|
// If src contains invalid base62 data, it will return the
|
|
// number of bytes successfully written and CorruptInputError.
|
|
// New line characters (\r and \n) are ignored.
|
|
func (enc *Encoding) Decode(src []byte) ([]byte, error) {
|
|
if len(src) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Lift the nil check outside the loop. enc.decodeMap is directly
|
|
// used later in this function, to let the compiler know that the
|
|
// receiver can't be nil.
|
|
_ = enc.decodeMap
|
|
|
|
rs := 0
|
|
cs := int(math.Ceil(math.Log(62) / math.Log(256) * float64(len(src))))
|
|
dst := make([]byte, cs)
|
|
for i := range src {
|
|
if src[i] == '\n' || src[i] == '\r' {
|
|
continue
|
|
}
|
|
c := 0
|
|
v := int(enc.decodeMap[src[i]])
|
|
if v == 255 {
|
|
return nil, CorruptInputError(src[i])
|
|
}
|
|
for j := cs - 1; j >= 0 && (v != 0 || c < rs); j-- {
|
|
v += 62 * int(dst[j])
|
|
dst[j] = byte(v % 256)
|
|
v /= 256
|
|
c++
|
|
}
|
|
rs = c
|
|
}
|
|
if cs > rs {
|
|
return dst[cs-rs:], nil
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
type CorruptInputError int64
|
|
|
|
func (e CorruptInputError) Error() string {
|
|
return "illegal base62 data at input byte " + strconv.FormatInt(int64(e), 10)
|
|
}
|