generated from earthboundkid/go-cli
-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
crockford.go
210 lines (185 loc) · 5.66 KB
/
crockford.go
1
2
3
4
5
6
7
8
9
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Package crockford implements the Crockford base 32 encoding
//
// See https://www.crockford.com/base32.html
package crockford
import (
"crypto/md5"
"crypto/rand"
"encoding/base32"
"time"
)
// Base32 alphabets
const (
LowercaseAlphabet = "0123456789abcdefghjkmnpqrstvwxyz"
UppercaseAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
UppercaseChecksum = UppercaseAlphabet + "*~$=U"
LowercaseChecksum = LowercaseAlphabet + "*~$=u"
)
// Base32 encodings
var (
Lower = base32.NewEncoding(LowercaseAlphabet).WithPadding(base32.NoPadding)
Upper = base32.NewEncoding(UppercaseAlphabet).WithPadding(base32.NoPadding)
)
// Buffer lengths
const (
LenTime = 8 // length returned by AppendTime
LenRandom = 8 // length returned by AppendRandom
LenMD5 = 26 // length returned by AppendMD5
)
// Time encodes the Unix time as a 40-bit number. The resulting string is big endian
// and suitable for lexicographic sorting.
func Time(e *base32.Encoding, t time.Time) string {
return string(AppendTime(e, t, nil))
}
// AppendTime appends onto dst LenTime bytes with the Unix time encoded as a 40-bit number.
// The resulting slice is big endian and suitable for lexicographic sorting.
func AppendTime(e *base32.Encoding, t time.Time, dst []byte) []byte {
ut := t.Unix()
var src [5]byte
src[0] = byte(ut >> 32)
src[1] = byte(ut >> 24)
src[2] = byte(ut >> 16)
src[3] = byte(ut >> 8)
src[4] = byte(ut)
return appendN(e, LenTime, dst, src[:])
}
// mod calculates the big endian modulus of the byte string
func mod(b []byte, m int) (rem int) {
for _, c := range b {
rem = (rem*1<<8 + int(c)) % m
}
return
}
// Checksum returns the checksum byte for an unencoded body.
func Checksum(body []byte, uppercase bool) byte {
alphabet := LowercaseChecksum
if uppercase {
alphabet = UppercaseChecksum
}
return alphabet[mod(body, 37)]
}
func normUpper(c byte) byte {
switch c {
case '0', 'O', 'o':
return '0'
case '1', 'I', 'i', 'L', 'l':
return '1'
case '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', '*', '~', '$', '=', 'U':
return c
case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z', 'u':
return c + 'A' - 'a'
}
return 0
}
// Normalized returns a normalized version of Crockford encoded bytes of src
// onto dst and returns the resulting slice. It replaces I and L with 1, o with 0,
// and removes invalid characters such as hyphens. The resulting slice is uppercase.
func Normalized(s string) string {
return string(AppendNormalized(nil, []byte(s)))
}
// AppendNormalized appends a normalized version of Crockford encoded bytes of src
// onto dst and returns the resulting slice. It replaces I and L with 1, o with 0,
// and removes invalid characters such as hyphens. The resulting slice is uppercase.
func AppendNormalized(dst, src []byte) []byte {
dst = grow(dst, len(src))
for _, c := range src {
if r := normUpper(c); r != 0 {
dst = append(dst, r)
}
}
return dst
}
// Random returns LenRandom (8) encoded bytes generated by crypto/rand.
func Random(e *base32.Encoding) string {
return string(AppendRandom(e, nil))
}
// AppendRandom appends LenRandom (8) encoded bytes generated by crypto/rand onto dst.
func AppendRandom(e *base32.Encoding, dst []byte) []byte {
// 5 bytes -> 8 base32 characters
dst = grow(dst, LenRandom)
// Use the tail of dst as scratch
src := dst[len(dst) : len(dst)+5]
if _, err := rand.Read(src); err != nil {
panic(err)
}
return appendN(e, LenRandom, dst, src)
}
// MD5 returns encoded bytes generated by MD5 hashing src.
func MD5(e *base32.Encoding, src []byte) string {
return string(AppendMD5(e, nil, src))
}
// AppendMD5 appends LenMD (26) encoded bytes generated by MD5 hashing src onto dst.
func AppendMD5(e *base32.Encoding, dst, src []byte) []byte {
//16 bytes -> 26 base32 characters
var buf [md5.Size]byte
h := md5.New()
h.Write(src)
h.Sum(buf[:0])
return appendN(e, LenMD5, dst, buf[:])
}
// Append returns a slice with the encoded version of src appended onto dst.
//
// See https://github.com/golang/go/issues/53693.
func Append(e *base32.Encoding, dst, src []byte) []byte {
n := e.EncodedLen(len(src))
return appendN(e, n, dst, src)
}
func appendN(e *base32.Encoding, n int, dst, src []byte) []byte {
dst = grow(dst, n)
tar := dst[len(dst) : len(dst)+n]
e.Encode(tar, src)
return dst[:len(dst)+n]
}
func grow(b []byte, n int) []byte {
if cap(b)-len(b) >= n {
return b
}
return append(b, make([]byte, n)...)[:len(b)]
}
// Partition s with hyphens ("-") to every gap bytes to increase readability.
// Partition is not Unicode aware because it is made to work with encoded strings.
func Partition(s string, gap int) string {
return string(AppendPartition(nil, []byte(s), gap))
}
// AppendPartition appends onto dst the result of
// partitioning src with hyphens ("-") every gap bytes.
func AppendPartition(dst, src []byte, gap int) []byte {
if gap < 1 {
panic("invalid gap")
}
if len(src) < 1 {
return dst
}
// figure out how many hyphens to insert
gaps := len(src) / gap
rem := len(src) % gap
if rem == 0 && gaps > 0 {
gaps--
}
// reserve space
n := gaps + len(src)
dst = grow(dst, n)[:len(dst)+n]
r := dst
// copy chunks from tail to beginning of dst
// inserting hyphens along the way
for {
var tailDst, tailSrc []byte
tailLen := gap
if rem != 0 {
tailLen = rem
rem = 0
}
src, tailSrc = splitLast(src, tailLen)
dst, tailDst = splitLast(dst, tailLen)
copy(tailDst, tailSrc)
if gaps < 1 {
return r
}
dst, tailDst = splitLast(dst, 1)
tailDst[0] = '-'
gaps--
}
}
func splitLast(b []byte, n int) ([]byte, []byte) {
return b[:len(b)-n], b[len(b)-n:]
}