Skip to content

Commit

Permalink
Merge pull request #29 from lithammer/port-upstream
Browse files Browse the repository at this point in the history
Encode/decode using MSB first
  • Loading branch information
lithammer committed Feb 1, 2022
2 parents c330047 + c09cf66 commit 47db091
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 192 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ package main
import (
"fmt"

"github.com/lithammer/shortuuid/v3"
"github.com/lithammer/shortuuid/v4"
)

func main() {
u := shortuuid.New() // Cekw67uyMpBGZLRP2HFVbe
u := shortuuid.New() // KwSysDpxcBU9FNhGkn2dCf
}
```

Expand All @@ -42,7 +42,7 @@ characters long.

```go
alphabet := "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxy="
shortuuid.NewWithAlphabet(alphabet) // u=BFWRLr5dXbeWf==iasZi
shortuuid.NewWithAlphabet(alphabet) // iZsai==fWebXd5rLRWFB=u
```

Bring your own encoder! For example, base58 is popular among bitcoin.
Expand All @@ -55,7 +55,7 @@ import (

"github.com/btcsuite/btcutil/base58"
"github.com/google/uuid"
"github.com/lithammer/shortuuid/v3"
"github.com/lithammer/shortuuid/v4"
)

type base58Encoder struct{}
Expand Down
12 changes: 8 additions & 4 deletions alphabet.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
const DefaultAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

type alphabet struct {
chars [57]string
chars [57]rune
len int64
}

Expand All @@ -26,7 +26,11 @@ func newAlphabet(s string) alphabet {
a := alphabet{
len: int64(len(abc)),
}
copy(a.chars[:], abc)

for i, char := range strings.Join(abc, "") {
a.chars[i] = char
}

return a
}

Expand All @@ -36,13 +40,13 @@ func (a *alphabet) Length() int64 {

// Index returns the index of the first instance of t in the alphabet, or an
// error if t is not present.
func (a *alphabet) Index(t string) (int64, error) {
func (a *alphabet) Index(t rune) (int64, error) {
for i, char := range a.chars {
if char == t {
return int64(i), nil
}
}
return 0, fmt.Errorf("Element '%v' is not part of the alphabet", t)
return 0, fmt.Errorf("element '%v' is not part of the alphabet", t)
}

// dudupe removes duplicate characters from s.
Expand Down
8 changes: 3 additions & 5 deletions alphabet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ func TestDedupe(t *testing.T) {

func TestAlphabetIndex(t *testing.T) {
abc := newAlphabet(DefaultAlphabet)
idx, err := abc.Index("z")

idx, err := abc.Index('z')
if err != nil {
t.Errorf("expected index 56, got an error trying to get it %v", err)
}
Expand All @@ -35,8 +34,7 @@ func TestAlphabetIndex(t *testing.T) {

func TestAlphabetIndexZero(t *testing.T) {
abc := newAlphabet(DefaultAlphabet)
idx, err := abc.Index("2")

idx, err := abc.Index('2')
if err != nil {
t.Errorf("expected index 0, got an error trying to get it %v", err)
}
Expand All @@ -47,7 +45,7 @@ func TestAlphabetIndexZero(t *testing.T) {

func TestAlphabetIndexError(t *testing.T) {
abc := newAlphabet(DefaultAlphabet)
idx, err := abc.Index("l")
idx, err := abc.Index('l')
if err == nil {
t.Errorf("expected an error, got a valid index %d", idx)
}
Expand Down
44 changes: 24 additions & 20 deletions base57.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ type base57 struct {
alphabet alphabet
}

// Encode encodes uuid.UUID into a string using the least significant bits
// (LSB) first according to the alphabet. If the most significant bits (MSB)
// are 0, the string might be shorter.
// Encode encodes uuid.UUID into a string using the most significant bits (MSB)
// first according to the alphabet.
func (b base57) Encode(u uuid.UUID) string {
var num big.Int
num.SetString(strings.Replace(u.String(), "-", "", 4), 16)

// Calculate encoded length.
factor := math.Log(float64(25)) / math.Log(float64(b.alphabet.Length()))
length := math.Ceil(factor * float64(len(u)))
length := math.Ceil(math.Log(math.Pow(2, 128)) / math.Log(float64(b.alphabet.Length())))

return b.numToString(&num, int(length))
}
Expand All @@ -41,47 +39,53 @@ func (b base57) Decode(u string) (uuid.UUID, error) {
// numToString converts a number a string using the given alphabet.
func (b *base57) numToString(number *big.Int, padToLen int) string {
var (
out string
out []rune
digit *big.Int
)

alphaLen := big.NewInt(b.alphabet.Length())

zero := new(big.Int)
for number.Cmp(zero) > 0 {
number, digit = new(big.Int).DivMod(number, big.NewInt(b.alphabet.Length()), new(big.Int))
out += b.alphabet.chars[digit.Int64()]
number, digit = new(big.Int).DivMod(number, alphaLen, new(big.Int))
out = append(out, b.alphabet.chars[digit.Int64()])
}

if padToLen > 0 {
remainder := math.Max(float64(padToLen-len(out)), 0)
out = out + strings.Repeat(b.alphabet.chars[0], int(remainder))
out = append(out, []rune(strings.Repeat(string(b.alphabet.chars[0]), int(remainder)))...)
}

return out
reverse(out)

return string(out)
}

// stringToNum converts a string a number using the given alphabet.
func (b *base57) stringToNum(s string) (string, error) {
n := big.NewInt(0)

for i := len(s) - 1; i >= 0; i-- {
for _, char := range s {
n.Mul(n, big.NewInt(b.alphabet.Length()))

index, err := b.alphabet.Index(string(s[i]))
index, err := b.alphabet.Index(char)
if err != nil {
return "", err
}

n.Add(n, big.NewInt(index))
}

x := fmt.Sprintf("%x", n)

if len(x) < 32 {
// Pad the most significant bit (MSG) with 0 (zero) if the string is too short.
x = strings.Repeat("0", 32-len(x)) + x
} else if len(x) > 32 {
return "", fmt.Errorf("UUID length overflow for %q", s)
if n.BitLen() > 128 {
return "", fmt.Errorf("number is out of range (need a 128-bit value)")
}

return fmt.Sprintf("%s-%s-%s-%s-%s", x[0:8], x[8:12], x[12:16], x[16:20], x[20:32]), nil
return fmt.Sprintf("%032x", n), nil
}

// reverse reverses a inline.
func reverse(a []rune) {
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
}
13 changes: 13 additions & 0 deletions base57_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package shortuuid

import (
"testing"
)

func TestReverse(t *testing.T) {
a := []rune("abc123")
reverse(a)
if string(a) != "321cba" {
t.Errorf("expected string to be %q, got %q", "321cba", string(a))
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/lithammer/shortuuid/v3
module github.com/lithammer/shortuuid/v4

require github.com/google/uuid v1.3.0

Expand Down
4 changes: 3 additions & 1 deletion shortuuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ func NewWithNamespace(name string) string {
switch {
case name == "":
u = uuid.New()
case strings.HasPrefix(name, "http"):
case strings.HasPrefix(strings.ToLower(name), "http://"):
u = uuid.NewSHA1(uuid.NameSpaceURL, []byte(name))
case strings.HasPrefix(strings.ToLower(name), "https://"):
u = uuid.NewSHA1(uuid.NameSpaceURL, []byte(name))
default:
u = uuid.NewSHA1(uuid.NameSpaceDNS, []byte(name))
Expand Down
Loading

0 comments on commit 47db091

Please sign in to comment.