| package guid |
| |
| import ( |
| "bytes" |
| "crypto/rand" |
| "errors" |
| "fmt" |
| "net" |
| "strings" |
| "sync" |
| "time" |
| ) |
| |
| // GUID is a unique identifier designed to virtually guarantee non-conflict between values generated |
| // across a distributed system. |
| type GUID struct { |
| timeHighAndVersion uint16 |
| timeMid uint16 |
| timeLow uint32 |
| clockSeqHighAndReserved uint8 |
| clockSeqLow uint8 |
| node [6]byte |
| } |
| |
| // Format enumerates the values that are supported by Parse and Format |
| type Format string |
| |
| // These constants define the possible string formats available via this implementation of Guid. |
| const ( |
| FormatB Format = "B" // {00000000-0000-0000-0000-000000000000} |
| FormatD Format = "D" // 00000000-0000-0000-0000-000000000000 |
| FormatN Format = "N" // 00000000000000000000000000000000 |
| FormatP Format = "P" // (00000000-0000-0000-0000-000000000000) |
| FormatX Format = "X" // {0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}} |
| FormatDefault Format = FormatD |
| ) |
| |
| // CreationStrategy enumerates the values that are supported for populating the bits of a new Guid. |
| type CreationStrategy string |
| |
| // These constants define the possible creation strategies available via this implementation of Guid. |
| const ( |
| CreationStrategyVersion1 CreationStrategy = "version1" |
| CreationStrategyVersion2 CreationStrategy = "version2" |
| CreationStrategyVersion3 CreationStrategy = "version3" |
| CreationStrategyVersion4 CreationStrategy = "version4" |
| CreationStrategyVersion5 CreationStrategy = "version5" |
| ) |
| |
| var emptyGUID GUID |
| |
| // NewGUID generates and returns a new globally unique identifier |
| func NewGUID() GUID { |
| result, err := version4() |
| if err != nil { |
| panic(err) //Version 4 (pseudo-random GUID) doesn't use anything that could fail. |
| } |
| return result |
| } |
| |
| var knownStrategies = map[CreationStrategy]func() (GUID, error){ |
| CreationStrategyVersion1: version1, |
| CreationStrategyVersion4: version4, |
| } |
| |
| // NewGUIDs generates and returns a new globally unique identifier that conforms to the given strategy. |
| func NewGUIDs(strategy CreationStrategy) (GUID, error) { |
| if creator, present := knownStrategies[strategy]; present { |
| result, err := creator() |
| return result, err |
| } |
| return emptyGUID, errors.New("Unsupported CreationStrategy") |
| } |
| |
| // Empty returns a copy of the default and empty GUID. |
| func Empty() GUID { |
| return emptyGUID |
| } |
| |
| var knownFormats = map[Format]string{ |
| FormatN: "%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", |
| FormatD: "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", |
| FormatB: "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", |
| FormatP: "(%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x)", |
| FormatX: "{0x%08x,0x%04x,0x%04x,{0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x}}", |
| } |
| |
| // MarshalJSON writes a GUID as a JSON string. |
| func (guid GUID) MarshalJSON() (marshaled []byte, err error) { |
| buf := bytes.Buffer{} |
| |
| _, err = buf.WriteRune('"') |
| buf.WriteString(guid.String()) |
| buf.WriteRune('"') |
| |
| marshaled = buf.Bytes() |
| return |
| } |
| |
| // Parse instantiates a GUID from a text representation of the same GUID. |
| // This is the inverse of function family String() |
| func Parse(value string) (GUID, error) { |
| var guid GUID |
| for _, fullFormat := range knownFormats { |
| parity, err := fmt.Sscanf( |
| value, |
| fullFormat, |
| &guid.timeLow, |
| &guid.timeMid, |
| &guid.timeHighAndVersion, |
| &guid.clockSeqHighAndReserved, |
| &guid.clockSeqLow, |
| &guid.node[0], |
| &guid.node[1], |
| &guid.node[2], |
| &guid.node[3], |
| &guid.node[4], |
| &guid.node[5]) |
| if parity == 11 && err == nil { |
| return guid, err |
| } |
| } |
| return emptyGUID, fmt.Errorf("\"%s\" is not in a recognized format", value) |
| } |
| |
| // String returns a text representation of a GUID in the default format. |
| func (guid GUID) String() string { |
| return guid.Stringf(FormatDefault) |
| } |
| |
| // Stringf returns a text representation of a GUID that conforms to the specified format. |
| // If an unrecognized format is provided, the empty string is returned. |
| func (guid GUID) Stringf(format Format) string { |
| if format == "" { |
| format = FormatDefault |
| } |
| fullFormat, present := knownFormats[format] |
| if !present { |
| return "" |
| } |
| return fmt.Sprintf( |
| fullFormat, |
| guid.timeLow, |
| guid.timeMid, |
| guid.timeHighAndVersion, |
| guid.clockSeqHighAndReserved, |
| guid.clockSeqLow, |
| guid.node[0], |
| guid.node[1], |
| guid.node[2], |
| guid.node[3], |
| guid.node[4], |
| guid.node[5]) |
| } |
| |
| // UnmarshalJSON parses a GUID from a JSON string token. |
| func (guid *GUID) UnmarshalJSON(marshaled []byte) (err error) { |
| if len(marshaled) < 2 { |
| err = errors.New("JSON GUID must be surrounded by quotes") |
| return |
| } |
| stripped := marshaled[1 : len(marshaled)-1] |
| *guid, err = Parse(string(stripped)) |
| return |
| } |
| |
| // Version reads a GUID to parse which mechanism of generating GUIDS was employed. |
| // Values returned here are documented in rfc4122.txt. |
| func (guid GUID) Version() uint { |
| return uint(guid.timeHighAndVersion >> 12) |
| } |
| |
| var unixToGregorianOffset = time.Date(1970, 01, 01, 0, 0, 00, 0, time.UTC).Sub(time.Date(1582, 10, 15, 0, 0, 0, 0, time.UTC)) |
| |
| // getRFC4122Time returns a 60-bit count of 100-nanosecond intervals since 00:00:00.00 October 15th, 1582 |
| func getRFC4122Time() int64 { |
| currentTime := time.Now().UTC().Add(unixToGregorianOffset).UnixNano() |
| currentTime /= 100 |
| return currentTime & 0x0FFFFFFFFFFFFFFF |
| } |
| |
| var clockSeqVal uint16 |
| var clockSeqKey sync.Mutex |
| |
| func getClockSequence() (uint16, error) { |
| clockSeqKey.Lock() |
| defer clockSeqKey.Unlock() |
| |
| if 0 == clockSeqVal { |
| var temp [2]byte |
| if parity, err := rand.Read(temp[:]); !(2 == parity && nil == err) { |
| return 0, err |
| } |
| clockSeqVal = uint16(temp[0])<<8 | uint16(temp[1]) |
| } |
| clockSeqVal++ |
| return clockSeqVal, nil |
| } |
| |
| func getMACAddress() (mac [6]byte, err error) { |
| var hostNICs []net.Interface |
| |
| hostNICs, err = net.Interfaces() |
| if err != nil { |
| return |
| } |
| |
| for _, nic := range hostNICs { |
| var parity int |
| |
| parity, err = fmt.Sscanf( |
| strings.ToLower(nic.HardwareAddr.String()), |
| "%02x:%02x:%02x:%02x:%02x:%02x", |
| &mac[0], |
| &mac[1], |
| &mac[2], |
| &mac[3], |
| &mac[4], |
| &mac[5]) |
| |
| if parity == len(mac) { |
| return |
| } |
| } |
| |
| err = fmt.Errorf("No suitable address found") |
| |
| return |
| } |
| |
| func version1() (result GUID, err error) { |
| var localMAC [6]byte |
| var clockSeq uint16 |
| |
| currentTime := getRFC4122Time() |
| |
| result.timeLow = uint32(currentTime) |
| result.timeMid = uint16(currentTime >> 32) |
| result.timeHighAndVersion = uint16(currentTime >> 48) |
| if err = result.setVersion(1); err != nil { |
| return emptyGUID, err |
| } |
| |
| if localMAC, err = getMACAddress(); nil != err { |
| if parity, err := rand.Read(localMAC[:]); !(len(localMAC) != parity && err == nil) { |
| return emptyGUID, err |
| } |
| localMAC[0] |= 0x1 |
| } |
| copy(result.node[:], localMAC[:]) |
| |
| if clockSeq, err = getClockSequence(); nil != err { |
| return emptyGUID, err |
| } |
| |
| result.clockSeqLow = uint8(clockSeq) |
| result.clockSeqHighAndReserved = uint8(clockSeq >> 8) |
| |
| result.setReservedBits() |
| |
| return |
| } |
| |
| func version4() (GUID, error) { |
| var retval GUID |
| var bits [10]byte |
| |
| if parity, err := rand.Read(bits[:]); !(len(bits) == parity && err == nil) { |
| return emptyGUID, err |
| } |
| retval.timeHighAndVersion |= uint16(bits[0]) | uint16(bits[1])<<8 |
| retval.timeMid |= uint16(bits[2]) | uint16(bits[3])<<8 |
| retval.timeLow |= uint32(bits[4]) | uint32(bits[5])<<8 | uint32(bits[6])<<16 | uint32(bits[7])<<24 |
| retval.clockSeqHighAndReserved = uint8(bits[8]) |
| retval.clockSeqLow = uint8(bits[9]) |
| |
| //Randomly set clock-sequence, reserved, and node |
| if written, err := rand.Read(retval.node[:]); !(nil == err && written == len(retval.node)) { |
| retval = emptyGUID |
| return retval, err |
| } |
| |
| if err := retval.setVersion(4); nil != err { |
| return emptyGUID, err |
| } |
| retval.setReservedBits() |
| |
| return retval, nil |
| } |
| |
| func (guid *GUID) setVersion(version uint16) error { |
| if version > 5 || version == 0 { |
| return fmt.Errorf("While setting GUID version, unsupported version: %d", version) |
| } |
| guid.timeHighAndVersion = (guid.timeHighAndVersion & 0x0fff) | version<<12 |
| return nil |
| } |
| |
| func (guid *GUID) setReservedBits() { |
| guid.clockSeqHighAndReserved = (guid.clockSeqHighAndReserved & 0x3f) | 0x80 |
| } |