423 lines
9.8 KiB
Go
423 lines
9.8 KiB
Go
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/md5"
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"hash"
|
|
"math"
|
|
"math/big"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// The UUID represents Universally Unique IDentifier (which is 128 bit long).
|
|
type UUID [16]byte
|
|
|
|
var (
|
|
// NIL is defined in RFC 4122 section 4.1.7.
|
|
// The nil UUID is special form of UUID that is specified to have all 128 bits set to zero.
|
|
NIL = &UUID{
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
// NameSpaceDNS assume name to be a fully-qualified domain name.
|
|
// Declared in RFC 4122 Appendix C.
|
|
NameSpaceDNS = &UUID{
|
|
0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
|
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
|
}
|
|
// NameSpaceURL assume name to be a URL.
|
|
// Declared in RFC 4122 Appendix C.
|
|
NameSpaceURL = &UUID{
|
|
0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
|
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
|
}
|
|
// NameSpaceOID assume name to be an ISO OID.
|
|
// Declared in RFC 4122 Appendix C.
|
|
NameSpaceOID = &UUID{
|
|
0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1,
|
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
|
}
|
|
// NameSpaceX500 assume name to be a X.500 DN (in DER or a text output format).
|
|
// Declared in RFC 4122 Appendix C.
|
|
NameSpaceX500 = &UUID{
|
|
0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1,
|
|
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
|
}
|
|
)
|
|
|
|
// Version of the UUID represents a kind of subtype specifier.
|
|
func (u *UUID) Version() int {
|
|
return int(binary.BigEndian.Uint16(u[6:8]) >> 12)
|
|
}
|
|
|
|
// String returns the human readable form of the UUID.
|
|
func (u *UUID) String() string {
|
|
return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
|
|
}
|
|
|
|
func (u *UUID) WithoutDashString() string {
|
|
return fmt.Sprintf("%x", u[:])
|
|
}
|
|
|
|
func (u *UUID) variantRFC4122() {
|
|
u[8] = (u[8] & 0x3f) | 0x80
|
|
}
|
|
|
|
// NewV3 creates a new UUID with variant 3 as described in RFC 4122.
|
|
// Variant 3 based namespace-uuid and name and MD-5 hash calculation.
|
|
func NewV3(namespace *UUID, name []byte) *UUID {
|
|
uuid := newByHash(md5.New(), namespace, name)
|
|
uuid[6] = (uuid[6] & 0x0f) | 0x30
|
|
return uuid
|
|
}
|
|
|
|
func newByHash(hash hash.Hash, namespace *UUID, name []byte) *UUID {
|
|
hash.Write(namespace[:])
|
|
hash.Write(name[:])
|
|
|
|
var uuid UUID
|
|
copy(uuid[:], hash.Sum(nil)[:16])
|
|
uuid.variantRFC4122()
|
|
return &uuid
|
|
}
|
|
|
|
type stamp [10]byte
|
|
|
|
var (
|
|
mac []byte
|
|
requests chan bool
|
|
answers chan stamp
|
|
)
|
|
|
|
const gregorianUnix = 122192928000000000 // nanoseconds between gregorion zero and unix zero
|
|
|
|
func init() {
|
|
mac = make([]byte, 6)
|
|
rand.Read(mac)
|
|
requests = make(chan bool)
|
|
answers = make(chan stamp)
|
|
go unique()
|
|
i, err := net.Interfaces()
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, d := range i {
|
|
if len(d.HardwareAddr) == 6 {
|
|
mac = d.HardwareAddr[:6]
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewV1 creates a new UUID with variant 1 as described in RFC 4122.
|
|
// Variant 1 is based on hosts MAC address and actual timestamp (as count of 100-nanosecond intervals since
|
|
// 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the Christian calendar).
|
|
func NewV1() *UUID {
|
|
var uuid UUID
|
|
requests <- true
|
|
s := <-answers
|
|
copy(uuid[:4], s[4:])
|
|
copy(uuid[4:6], s[2:4])
|
|
copy(uuid[6:8], s[:2])
|
|
uuid[6] = (uuid[6] & 0x0f) | 0x10
|
|
copy(uuid[8:10], s[8:])
|
|
copy(uuid[10:], mac)
|
|
uuid.variantRFC4122()
|
|
return &uuid
|
|
}
|
|
|
|
func unique() {
|
|
var (
|
|
lastNanoTicks uint64
|
|
clockSequence [2]byte
|
|
)
|
|
rand.Read(clockSequence[:])
|
|
|
|
for range requests {
|
|
var s stamp
|
|
nanoTicks := uint64((time.Now().UTC().UnixNano() / 100) + gregorianUnix)
|
|
if nanoTicks < lastNanoTicks {
|
|
lastNanoTicks = nanoTicks
|
|
rand.Read(clockSequence[:])
|
|
} else if nanoTicks == lastNanoTicks {
|
|
lastNanoTicks = nanoTicks + 1
|
|
} else {
|
|
lastNanoTicks = nanoTicks
|
|
}
|
|
binary.BigEndian.PutUint64(s[:], lastNanoTicks)
|
|
copy(s[8:], clockSequence[:])
|
|
answers <- s
|
|
}
|
|
}
|
|
|
|
// NewV4 creates a new UUID with variant 4 as described in RFC 4122. Variant 4 based on pure random bytes.
|
|
func NewV4() *UUID {
|
|
buf := make([]byte, 16)
|
|
rand.Read(buf)
|
|
buf[6] = (buf[6] & 0x0f) | 0x40
|
|
var uuid UUID
|
|
copy(uuid[:], buf[:])
|
|
uuid.variantRFC4122()
|
|
return &uuid
|
|
}
|
|
|
|
// NewV5 creates a new UUID with variant 5 as described in RFC 4122.
|
|
// Variant 5 based namespace-uuid and name and SHA-1 hash calculation.
|
|
func NewV5(namespaceUUID *UUID, name []byte) *UUID {
|
|
uuid := newByHash(sha1.New(), namespaceUUID, name)
|
|
uuid[6] = (uuid[6] & 0x0f) | 0x50
|
|
return uuid
|
|
}
|
|
|
|
// NewNamespaceUUID creates a namespace UUID by using the namespace name in the NIL name space.
|
|
// This is a different approach as the 4 "standard" namespace UUIDs which are timebased UUIDs (V1).
|
|
func NewNamespaceUUID(namespace string) *UUID {
|
|
return NewV5(NIL, []byte(namespace))
|
|
}
|
|
|
|
// String parse helpers.
|
|
var (
|
|
urnPrefix = []byte("urn:uuid:")
|
|
byteGroups = []int{8, 4, 4, 4, 12}
|
|
)
|
|
|
|
func (u *UUID) UnmarshalText(text []byte) (err error) {
|
|
if len(text) < 32 {
|
|
err = fmt.Errorf("uuid: UUID string too short: %s", text)
|
|
return
|
|
}
|
|
|
|
t := text[:]
|
|
braced := false
|
|
|
|
if bytes.Equal(t[:9], urnPrefix) {
|
|
t = t[9:]
|
|
} else if t[0] == '{' {
|
|
braced = true
|
|
t = t[1:]
|
|
}
|
|
|
|
b := u[:]
|
|
|
|
for i, byteGroup := range byteGroups {
|
|
if i > 0 {
|
|
if t[0] != '-' {
|
|
err = fmt.Errorf("uuid: invalid string format")
|
|
return
|
|
}
|
|
t = t[1:]
|
|
}
|
|
|
|
if len(t) < byteGroup {
|
|
err = fmt.Errorf("uuid: UUID string too short: %s", text)
|
|
return
|
|
}
|
|
|
|
if i == 4 && len(t) > byteGroup &&
|
|
((braced && t[byteGroup] != '}') || len(t[byteGroup:]) > 1 || !braced) {
|
|
err = fmt.Errorf("uuid: UUID string too long: %s", text)
|
|
return
|
|
}
|
|
|
|
_, err = hex.Decode(b[:byteGroup/2], t[:byteGroup])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
t = t[byteGroup:]
|
|
b = b[byteGroup/2:]
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// FromString returns UUID parsed from string input.
|
|
// Input is expected in a form accepted by UnmarshalText.
|
|
func FromString(input string) (u UUID, err error) {
|
|
err = u.UnmarshalText([]byte(input))
|
|
return
|
|
}
|
|
|
|
type StringSet struct {
|
|
set map[string]bool
|
|
list []string
|
|
sorted bool
|
|
}
|
|
|
|
func NewStringSet() *StringSet {
|
|
return &StringSet{make(map[string]bool), make([]string, 0), false}
|
|
}
|
|
|
|
func (set *StringSet) Add(i string) bool {
|
|
_, found := set.set[i]
|
|
set.set[i] = true
|
|
if !found {
|
|
set.sorted = false
|
|
}
|
|
return !found //False if it existed already
|
|
}
|
|
|
|
func (set *StringSet) Contains(i string) bool {
|
|
_, found := set.set[i]
|
|
return found //true if it existed already
|
|
}
|
|
|
|
func (set *StringSet) Remove(i string) {
|
|
set.sorted = false
|
|
delete(set.set, i)
|
|
}
|
|
|
|
func (set *StringSet) Len() int {
|
|
return len(set.set)
|
|
}
|
|
|
|
func (set *StringSet) ItemByIndex(idx int) string {
|
|
set.Sort()
|
|
return set.list[idx]
|
|
}
|
|
|
|
func (set *StringSet) Index(c string) int {
|
|
for i, s := range set.list {
|
|
if c == s {
|
|
return i
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (set *StringSet) Sort() {
|
|
if set.sorted {
|
|
return
|
|
}
|
|
set.list = make([]string, 0)
|
|
for s, _ := range set.set {
|
|
set.list = append(set.list, s)
|
|
}
|
|
sort.Strings(set.list)
|
|
set.sorted = true
|
|
}
|
|
|
|
func (set *StringSet) String() string {
|
|
set.Sort()
|
|
return strings.Join(set.list, "")
|
|
}
|
|
|
|
const (
|
|
DEFAULT_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
)
|
|
|
|
type ShortUUID struct {
|
|
alphabet *StringSet
|
|
}
|
|
|
|
func NewShortUUID() *ShortUUID {
|
|
suid := &ShortUUID{}
|
|
suid.SetAlphabet(DEFAULT_ALPHABET)
|
|
return suid
|
|
}
|
|
|
|
func NewShortUUIDWithAlphabet(alphabet string) *ShortUUID {
|
|
|
|
suuid := &ShortUUID{}
|
|
if alphabet == "" {
|
|
alphabet = DEFAULT_ALPHABET
|
|
}
|
|
suuid.SetAlphabet(alphabet)
|
|
return suuid
|
|
}
|
|
|
|
func (s *ShortUUID) SetAlphabet(alphabet string) {
|
|
set := NewStringSet()
|
|
for _, a := range alphabet {
|
|
set.Add(string(a))
|
|
}
|
|
set.Sort()
|
|
s.alphabet = set
|
|
}
|
|
|
|
func (s ShortUUID) String() string {
|
|
return s.UUID("")
|
|
}
|
|
|
|
var (
|
|
NamespaceDNS, _ = FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
NamespaceURL, _ = FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
|
NamespaceOID, _ = FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
|
NamespaceX500, _ = FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
|
)
|
|
|
|
func (s *ShortUUID) UUID(name string) string {
|
|
var _uuid *UUID
|
|
if name == "" {
|
|
_uuid = NewV4()
|
|
} else if strings.HasPrefix(name, "http") {
|
|
_uuid = NewV5(&NamespaceDNS, []byte(name))
|
|
} else {
|
|
_uuid = NewV5(&NamespaceURL, []byte(name))
|
|
}
|
|
|
|
return s.Encode(_uuid)
|
|
}
|
|
|
|
// Encodes a UUID into a string (LSB first) according to the alphabet
|
|
// If leftmost (MSB) bits 0, string might be shorter
|
|
func (s *ShortUUID) Encode(uuid *UUID) string {
|
|
padLen := s.encodeLen(len(uuid))
|
|
number := uuidToInt(uuid)
|
|
return s.numToString(number, padLen)
|
|
}
|
|
|
|
func (s *ShortUUID) Decode(input string) (UUID, error) {
|
|
_uuid, err := FromString(s.stringToNum(input))
|
|
return _uuid, err
|
|
}
|
|
|
|
func (s *ShortUUID) encodeLen(numBytes int) int {
|
|
factor := math.Log(float64(25)) / math.Log(float64(s.alphabet.Len()))
|
|
length := math.Ceil(factor * float64(numBytes))
|
|
return int(length)
|
|
}
|
|
|
|
//Covert a number to a string, using the given alphabet.
|
|
func (s *ShortUUID) numToString(number *big.Int, padToLen int) string {
|
|
output := ""
|
|
var digit *big.Int
|
|
for number.Uint64() > 0 {
|
|
number, digit = new(big.Int).DivMod(number, big.NewInt(int64(s.alphabet.Len())), new(big.Int))
|
|
output += s.alphabet.ItemByIndex(int(digit.Int64()))
|
|
}
|
|
if padToLen > 0 {
|
|
remainer := math.Max(float64(padToLen)-float64(len(output)), 0)
|
|
output = output + strings.Repeat(s.alphabet.ItemByIndex(0), int(remainer))
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
// Convert a string to a number(based uuid string),using the given alphabet.
|
|
func (s *ShortUUID) stringToNum(input string) string {
|
|
n := big.NewInt(0)
|
|
for i := len(input) - 1; i >= 0; i-- {
|
|
n.Mul(n, big.NewInt(int64(s.alphabet.Len())))
|
|
n.Add(n, big.NewInt(int64(s.alphabet.Index(string(input[i])))))
|
|
}
|
|
|
|
x := fmt.Sprintf("%x", n)
|
|
x = x[0:8] + "-" + x[8:12] + "-" + x[12:16] + "-" + x[16:20] + "-" + x[20:32]
|
|
return x
|
|
}
|
|
|
|
func uuidToInt(_uuid *UUID) *big.Int {
|
|
var i big.Int
|
|
i.SetString(strings.Replace(_uuid.String(), "-", "", 4), 16)
|
|
return &i
|
|
}
|