initial
This commit is contained in:
		
							
								
								
									
										11
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
module code.icod.de/dalu/oid36
 | 
			
		||||
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require github.com/stretchr/testify v1.8.4
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										9
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 | 
			
		||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										167
									
								
								internal/base36/base36.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								internal/base36/base36.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
package base36
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	base36 = []byte{
 | 
			
		||||
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
 | 
			
		||||
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
 | 
			
		||||
		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
 | 
			
		||||
		'U', 'V', 'W', 'X', 'Y', 'Z'}
 | 
			
		||||
 | 
			
		||||
	//index = map[byte]int{
 | 
			
		||||
	//	'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
 | 
			
		||||
	//	'5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
 | 
			
		||||
	//	'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14,
 | 
			
		||||
	//	'F': 15, 'G': 16, 'H': 17, 'I': 18, 'J': 19,
 | 
			
		||||
	//	'K': 20, 'L': 21, 'M': 22, 'N': 23, 'O': 24,
 | 
			
		||||
	//	'P': 25, 'Q': 26, 'R': 27, 'S': 28, 'T': 29,
 | 
			
		||||
	//	'U': 30, 'V': 31, 'W': 32, 'X': 33, 'Y': 34,
 | 
			
		||||
	//	'Z': 35,
 | 
			
		||||
	//	'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14,
 | 
			
		||||
	//	'f': 15, 'g': 16, 'h': 17, 'i': 18, 'j': 19,
 | 
			
		||||
	//	'k': 20, 'l': 21, 'm': 22, 'n': 23, 'o': 24,
 | 
			
		||||
	//	'p': 25, 'q': 26, 'r': 27, 's': 28, 't': 29,
 | 
			
		||||
	//	'u': 30, 'v': 31, 'w': 32, 'x': 33, 'y': 34,
 | 
			
		||||
	//	'z': 35,
 | 
			
		||||
	//}
 | 
			
		||||
	uint8Index = []uint64{
 | 
			
		||||
		0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
 | 
			
		||||
		3, 4, 5, 6, 7, 8, 9, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 10, 11, 12, 13, 14,
 | 
			
		||||
		15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
 | 
			
		||||
		25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
 | 
			
		||||
		35, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13,
 | 
			
		||||
		14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
 | 
			
		||||
		24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
 | 
			
		||||
		34, 35, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
		0, 0, 0, 0, 0, // 256
 | 
			
		||||
	}
 | 
			
		||||
	pow36Index = []uint64{
 | 
			
		||||
		1, 36, 1296, 46656, 1679616, 60466176,
 | 
			
		||||
		2176782336, 78364164096, 2821109907456,
 | 
			
		||||
		101559956668416, 3656158440062976,
 | 
			
		||||
		131621703842267136, 4738381338321616896,
 | 
			
		||||
		9223372036854775808,
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Encode encodes a number to base36.
 | 
			
		||||
func Encode(value uint64) string {
 | 
			
		||||
	var res [16]byte
 | 
			
		||||
	var i int
 | 
			
		||||
	for i = len(res) - 1; ; i-- {
 | 
			
		||||
		res[i] = base36[value%36]
 | 
			
		||||
		value /= 36
 | 
			
		||||
		if value == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(res[i:])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Decode decodes a base36-encoded string.
 | 
			
		||||
func Decode(s string) uint64 {
 | 
			
		||||
	if len(s) > 13 {
 | 
			
		||||
		s = s[:12]
 | 
			
		||||
	}
 | 
			
		||||
	res := uint64(0)
 | 
			
		||||
	l := len(s) - 1
 | 
			
		||||
	for idx := 0; idx < len(s); idx++ {
 | 
			
		||||
		c := s[l-idx]
 | 
			
		||||
		res += uint8Index[c] * pow36Index[idx]
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var bigRadix = big.NewInt(36)
 | 
			
		||||
var bigZero = big.NewInt(0)
 | 
			
		||||
 | 
			
		||||
// EncodeBytesAsBytes encodes a byte slice to base36.
 | 
			
		||||
func EncodeBytesAsBytes(b []byte) []byte {
 | 
			
		||||
	x := new(big.Int)
 | 
			
		||||
	x.SetBytes(b)
 | 
			
		||||
 | 
			
		||||
	answer := make([]byte, 0, len(b)*136/100)
 | 
			
		||||
	for x.Cmp(bigZero) > 0 {
 | 
			
		||||
		mod := new(big.Int)
 | 
			
		||||
		x.DivMod(x, bigRadix, mod)
 | 
			
		||||
		answer = append(answer, base36[mod.Int64()])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// leading zero bytes
 | 
			
		||||
	for _, i := range b {
 | 
			
		||||
		if i != 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		answer = append(answer, base36[0])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// reverse
 | 
			
		||||
	alen := len(answer)
 | 
			
		||||
	for i := 0; i < alen/2; i++ {
 | 
			
		||||
		answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return answer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EncodeBytes encodes a byte slice to base36 string.
 | 
			
		||||
func EncodeBytes(b []byte) string {
 | 
			
		||||
	return string(EncodeBytesAsBytes(b))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DecodeToBytes decodes a base36 string to a byte slice, using alphabet.
 | 
			
		||||
func DecodeToBytes(b string) []byte {
 | 
			
		||||
	alphabet := string(base36)
 | 
			
		||||
	answer := big.NewInt(0)
 | 
			
		||||
	j := big.NewInt(1)
 | 
			
		||||
 | 
			
		||||
	for i := len(b) - 1; i >= 0; i-- {
 | 
			
		||||
		tmp := strings.IndexAny(alphabet, string(b[i]))
 | 
			
		||||
		if tmp == -1 {
 | 
			
		||||
			return []byte("")
 | 
			
		||||
		}
 | 
			
		||||
		idx := big.NewInt(int64(tmp))
 | 
			
		||||
		tmp1 := big.NewInt(0)
 | 
			
		||||
		tmp1.Mul(j, idx)
 | 
			
		||||
 | 
			
		||||
		answer.Add(answer, tmp1)
 | 
			
		||||
		j.Mul(j, bigRadix)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tmpval := answer.Bytes()
 | 
			
		||||
 | 
			
		||||
	var numZeros int
 | 
			
		||||
	for numZeros = 0; numZeros < len(b); numZeros++ {
 | 
			
		||||
		if b[numZeros] != alphabet[0] {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	flen := numZeros + len(tmpval)
 | 
			
		||||
	val := make([]byte, flen, flen)
 | 
			
		||||
	copy(val[numZeros:], tmpval)
 | 
			
		||||
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								internal/base36/base36_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								internal/base36/base36_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
package base36
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var raw = []uint64{0, 50, 100, 999, 1000, 1111, 5959, 99999,
 | 
			
		||||
	123456789, 5481594952936519619, math.MaxInt64 / 2048, math.MaxInt64 / 512,
 | 
			
		||||
	math.MaxInt64, math.MaxUint64}
 | 
			
		||||
 | 
			
		||||
var encoded = []string{"0", "1E", "2S", "RR", "RS", "UV", "4LJ", "255R",
 | 
			
		||||
	"21I3V9", "15N9Z8L3AU4EB", "18CE53UN18F", "4XDKKFEK4XR",
 | 
			
		||||
	"1Y2P0IJ32E8E7", "3W5E11264SGSF"}
 | 
			
		||||
 | 
			
		||||
func TestEncode(t *testing.T) {
 | 
			
		||||
	for i, v := range raw {
 | 
			
		||||
		assert.Equal(t, encoded[i], Encode(v))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDecode(t *testing.T) {
 | 
			
		||||
	for i, v := range encoded {
 | 
			
		||||
		assert.Equal(t, raw[i], Decode(v))
 | 
			
		||||
		assert.Equal(t, raw[i], Decode(strings.ToLower(v)))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkEncode(b *testing.B) {
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		Encode(5481594952936519619)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkEncodeBytesAsBytes(b *testing.B) {
 | 
			
		||||
	data := []byte{
 | 
			
		||||
		0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B, 0x48, 0x4F, 0x2A, 0x48, 0x4F, 0x2A,
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		EncodeBytesAsBytes(data)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkDecode(b *testing.B) {
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		Decode("1Y2P0IJ32E8E7")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								oid36.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								oid36.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package oid36
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
 | 
			
		||||
	"code.icod.de/dalu/oid36/internal/base36"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ObjectIDToBase36(oid string) (string, error) {
 | 
			
		||||
	b, e := hex.DecodeString(oid)
 | 
			
		||||
	if e != nil {
 | 
			
		||||
		return "", e
 | 
			
		||||
	}
 | 
			
		||||
	s := base36.EncodeBytes(b)
 | 
			
		||||
	return s, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Base36ToObjectID(bid string) string {
 | 
			
		||||
	b := base36.DecodeToBytes(bid)
 | 
			
		||||
	return hex.EncodeToString(b)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								oid36_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								oid36_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
package oid36_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.icod.de/dalu/oid36"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestObjectIDToBase36(t *testing.T) {
 | 
			
		||||
	oid := "617fc6a58149e807907dac6f"
 | 
			
		||||
	exp := "2XBEMITFMZGGAZ8UC1B"
 | 
			
		||||
	result, e := oid36.ObjectIDToBase36(oid)
 | 
			
		||||
	if e != nil {
 | 
			
		||||
		t.Error(e)
 | 
			
		||||
		t.Fail()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if result != exp {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBase36ToObjectID(t *testing.T) {
 | 
			
		||||
	bid := "2XBEMITFMZGGAZ8UC1B"
 | 
			
		||||
	exp := "617fc6a58149e807907dac6f"
 | 
			
		||||
	result := oid36.Base36ToObjectID(bid)
 | 
			
		||||
	if result != exp {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user