123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- package xorm
- 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
- }
|