Created
March 21, 2022 17:37
-
-
Save nsa-yoda/3676ed82f28fef41fb5fbbcdcd79c0fa to your computer and use it in GitHub Desktop.
Sphire Mantis UUID <1.2.4 based on Jet/UUID
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package uuid generates RFC4122-compliant UUIDs, and provides functions to marshal the UUIDs into their canonical form of 16 bytes and unmarshal them from a variety of formats. See https://tools.ietf.org/html/rfc4122. | |
package uuid | |
import ( | |
"bytes" | |
"crypto/md5" | |
"crypto/rand" | |
"crypto/sha1" | |
"encoding/binary" | |
"encoding/hex" | |
"encoding/json" | |
"errors" | |
"fmt" | |
"net" | |
"strings" | |
"sync" | |
"time" | |
) | |
// Original credit::::: "github.com/jet/go-mantis/uuid" - | |
// ErrInvalidFormat is returned if the textual representation being unmarshaled is not a valid UUID | |
var ErrInvalidFormat = errors.New("uuid: invalid format") | |
// UUID is a Universally unique identifier as described in RFC4122/DCE1.1 | |
type UUID [16]byte | |
// Nil UUID is an uuid with all zeros | |
var Nil = UUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | |
// RFC4122 Predefined Namespace UUIDs https://tools.ietf.org/html/rfc4122#appendix-C | |
var ( | |
// NamespaceDNS {6ba7b810-9dad-11d1-80b4-00c04fd430c8} | |
NamespaceDNS = UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} | |
// NamespaceURL {6ba7b811-9dad-11d1-80b4-00c04fd430c8} | |
NamespaceURL = UUID{0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} | |
// NamespaceOID {6ba7b812-9dad-11d1-80b4-00c04fd430c8} | |
NamespaceOID = UUID{0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} | |
// NamespaceX500 {6ba7b814-9dad-11d1-80b4-00c04fd430c8} | |
NamespaceX500 = UUID{0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} | |
) | |
// Version gets the version number of the UUID | |
func (u UUID) Version() byte { | |
M := u[6] | |
M &= 0xF0 | |
M >>= 4 | |
return M | |
} | |
// SetVersion sets the version number of the UUID | |
func (u *UUID) SetVersion(ver byte) { | |
// xxxx xxxx : 0xXX | |
// 0000 1111 : && 0x0F | |
// vvvv 0000 : || 0xV0 | |
// ----------: | |
// vvvv xxxx 0xVX | |
u[6] = (u[6] & 0x0F) | ((ver & 0x0F) << 4) | |
} | |
// SetDCESecurity sets the security information in a Time-Based UUID as defined by | |
// DCE 1.1 Authentication and Security Services: http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 | |
func (u *UUID) SetDCESecurity(domain byte, id uint32) { | |
if ver := u.Version(); ver < 0 || ver > 2 { | |
return | |
} | |
u.SetVersion(2) | |
u.SetVariant(VariantRFC4122) // Sets the variant bits to `10xx` | |
binary.BigEndian.PutUint32(u[0:4], id) | |
u[9] = domain | |
} | |
// DCESecurity gets the DCE security information on a Time-based UUID | |
// See: http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 | |
func (u *UUID) DCESecurity() (domain byte, id uint32) { | |
if u.Version() != 2 { | |
return | |
} | |
id = binary.BigEndian.Uint32(u[0:4]) | |
domain = u[9] | |
return | |
} | |
// 100ns ticks since 1582-10-15 to 1970-1-1 | |
var uuidEpocStart = uint64(time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix() * int64(-1e7)) | |
func timestamp64() uint64 { | |
return uuidEpocStart + uint64(time.Now().UnixNano()/100) | |
} | |
func timestamp32() uint64 { | |
ts := timestamp64() | |
return ts & 0xFFFFFFFF00000000 // Drop time_low | |
} | |
func tick16(clk uint16) uint16 { | |
return clk + 1 | |
} | |
func tickHigh8(clk uint16) uint16 { | |
c := (clk & 0xFF00) >> 8 | |
return (c + 1) << 8 | |
} | |
type rfc4122Generator struct { | |
initOnce sync.Once | |
mu sync.Mutex | |
hwAddr [6]byte // MAC | |
lastTime uint64 | |
clockSeq uint16 | |
clockTickFunc func(uint16) uint16 | |
timestampFunc func() uint64 | |
} | |
func (r *rfc4122Generator) String() string { | |
marshaledStruct, err := json.Marshal(r) | |
if err != nil { | |
return err.Error() | |
} | |
return string(marshaledStruct) | |
} | |
var rfc4122 = rfc4122Generator{ | |
clockTickFunc: tick16, | |
timestampFunc: timestamp64, | |
} | |
var dceSec = rfc4122Generator{ | |
clockTickFunc: tickHigh8, | |
timestampFunc: timestamp32, | |
} | |
func (r *rfc4122Generator) newV1() (UUID, error) { | |
hw, ts, clk, err := r.tick() | |
if err != nil { | |
return Nil, err | |
} | |
var u UUID | |
binary.BigEndian.PutUint32(u[0:], uint32(ts)) | |
binary.BigEndian.PutUint16(u[4:], uint16(ts>>32)) | |
binary.BigEndian.PutUint16(u[6:], uint16(ts>>48)) | |
binary.BigEndian.PutUint16(u[8:], clk) | |
copy(u[10:], hw[:]) | |
u.SetVersion(1) | |
u.SetVariant(VariantRFC4122) | |
return u, nil | |
} | |
func (r *rfc4122Generator) tick() ([6]byte, uint64, uint16, error) { | |
var err error | |
r.initOnce.Do(func() { | |
hwAddr, e := r.getHwAddr() | |
if e != nil { | |
err = e | |
return | |
} | |
r.hwAddr = hwAddr | |
cbs := make([]byte, 2) | |
if _, f := rand.Read(cbs); f != nil { | |
err = fmt.Errorf("uuid: init clock seq failed: %v", f) | |
} | |
r.clockSeq = binary.BigEndian.Uint16(cbs) | |
}) | |
if err != nil { | |
return [6]byte{}, 0, 0, err | |
} | |
r.mu.Lock() | |
defer r.mu.Unlock() | |
ts := r.timestampFunc() | |
if r.lastTime >= ts { | |
r.clockSeq = r.clockTickFunc(r.clockSeq) | |
} | |
r.lastTime = ts | |
return r.hwAddr, r.lastTime, r.clockSeq, nil | |
} | |
func (r *rfc4122Generator) getRandHwAddr() ([6]byte, error) { | |
var hwAddr [6]byte | |
if _, err := rand.Read(hwAddr[:]); err != nil { | |
return [6]byte{}, err | |
} | |
// Set multicast bit | |
hwAddr[0] |= 0x01 | |
return hwAddr, nil | |
} | |
func (r *rfc4122Generator) getHwAddr() ([6]byte, error) { | |
ifaces, err := net.Interfaces() | |
if err != nil { | |
return r.getRandHwAddr() | |
} | |
for _, iface := range ifaces { | |
if len(iface.HardwareAddr) >= 6 { | |
var hwAddr [6]byte | |
copy(hwAddr[:], iface.HardwareAddr[0:6]) | |
// Set multicast bit | |
hwAddr[0] |= 0x01 | |
return hwAddr, nil | |
} | |
} | |
return r.getRandHwAddr() | |
} | |
// Variant of the UUID version | |
// See RFC4122 Section 4.1.1 | |
// - https://tools.ietf.org/html/rfc4122#section-4.1.1 | |
type Variant byte | |
// UnknownVariant is an unknown uuid variant | |
const UnknownVariant Variant = 0xFF | |
const ( | |
// VariantNCS is reserved, NCS backward compatibility. | |
VariantNCS Variant = iota | |
// VariantRFC4122 is the standard variant specified in RFC4122 | |
VariantRFC4122 | |
// VariantMicrosoft is reserved for Microsoft Corporation backward compatibility | |
VariantMicrosoft | |
) | |
// Variant returns the variant version and the data bits of | |
// M in the UUID format | |
// xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx | |
// | |
// The variant is designated by up to 4 bits in N | |
// - v0. 0xxx : N = 0...7 ; returns VariantNCS | |
// - v1. 10xx : N = 8...b ; returns VariantRFC4122 | |
// - v2. 110x : N = c...d ; returns VariantMicrosoft | |
// - any other pattern is unknown and returns UnknownVariant | |
func (u UUID) Variant() Variant { | |
switch { | |
case (u[8] >> 7) == 0x00: | |
return VariantNCS | |
case (u[8] >> 6) == 0x02: | |
return VariantRFC4122 | |
case (u[8] >> 5) == 0x06: | |
return VariantMicrosoft | |
case (u[8] >> 5) == 0x07: | |
fallthrough | |
default: | |
return UnknownVariant | |
} | |
} | |
// SetVariant sets the variant version and the data bits | |
// of N in the UUID format: | |
// xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx | |
func (u *UUID) SetVariant(v Variant) { | |
switch v { | |
case VariantNCS: | |
u[8] = u[8]&(0xff>>1) | (0x00 << 7) | |
case VariantRFC4122: | |
u[8] = u[8]&(0xff>>2) | (0x02 << 6) | |
case VariantMicrosoft: | |
u[8] = u[8]&(0xff>>3) | (0x06 << 5) | |
case UnknownVariant: | |
fallthrough | |
default: | |
u[8] = u[8]&(0xff>>3) | (0x07 << 5) | |
} | |
} | |
// Time extracts the timestamp from a Version1 or Version2 UUID | |
// | |
// For versions that are not RFC4122 complaint, or not Version1 or Version2, this will a zero time: `time.Time{}` | |
func (u UUID) Time() time.Time { | |
if u.Variant() != VariantRFC4122 { // Not RFC4122 Compliant | |
return time.Time{} | |
} | |
ver := u.Version() | |
if ver < 1 || ver > 2 { | |
return time.Time{} // Not a Time-based UUID | |
} | |
// Timestamp | |
var ts [8]byte | |
if ver == 1 { | |
copy(ts[4:8], u[0:4]) // low | |
} | |
copy(ts[2:4], u[4:6]) // mid | |
copy(ts[0:2], u[6:]) // high | |
ts[0] &= 0x0F // clear version | |
ticks := binary.BigEndian.Uint64(ts[:]) | |
fmt.Printf("%x\n", ticks) | |
// convert to unix nanos | |
return time.Unix(0, int64((ticks-uuidEpocStart)*100)) | |
} | |
// In its canonical textual representation, the sixteen octets of a UUID are represented as 32 hexadecimal (base 16) digits, displayed in five groups separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 alphanumeric characters and four hyphens). For example: | |
// 123e4567-e89b-12d3-a456-426655440000 | |
// xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx | |
// ... | |
// The four bits of digit M indicate the UUID version, | |
// and the one to three most significant bits of digit N indicate the UUID | |
// variant. In the example, M is 1 and N is a (10xx), | |
// meaning that the UUID is a variant 1, version 1 UUID; | |
// that is, a time-based DCE/RFC 4122 UUID. | |
// ... | |
// The canonical 8-4-4-4-12 format string is based on the "record layout" | |
// for the 16 bytes of the UUID | |
func (u UUID) String() string { | |
return fmt.Sprintf("%s-%s-%s-%s-%s", | |
hex.EncodeToString(u[0:4]), | |
hex.EncodeToString(u[4:6]), | |
hex.EncodeToString(u[6:8]), | |
hex.EncodeToString(u[8:10]), | |
hex.EncodeToString(u[10:]), | |
) | |
} | |
// Format formats the UUID according to the fmt.Formatter interface. | |
// | |
// %s canonical form lowercase (123e4567-e89b-12d3-a456-426655440000) | |
// %+s canonical form UPPERCASE (123E4567-E89B-12D3-A456-426655440000) | |
// %x encryption-like lowercase (123e4567e89b12d3a456426655440000) | |
// %X encryption-like UPPERCASE (123E4567E89B12D3A456426655440000) | |
// %v equivalent to %s | |
// %+v equivalent to %+s | |
// %q equivalent to %s enclosed in double-quotes | |
// %+q equivalent to %+s enclosed in double-quotes | |
func (u UUID) Format(s fmt.State, verb rune) { | |
switch verb { | |
case 's': | |
switch { | |
case s.Flag('+'): | |
_, _ = fmt.Fprintf(s, strings.ToUpper(u.String())) | |
default: | |
_, _ = fmt.Fprintf(s, u.String()) | |
} | |
case 'x': | |
_, _ = fmt.Fprintf(s, hex.EncodeToString(u[:])) | |
case 'X': | |
_, _ = fmt.Fprintf(s, strings.ToUpper(hex.EncodeToString(u[:]))) | |
case 'v': | |
u.Format(s, 's') | |
case 'q': | |
_, _ = fmt.Fprintf(s, `"`) | |
u.Format(s, 's') | |
_, _ = fmt.Fprintf(s, `"`) | |
} | |
} | |
// GenerateV1 creates a Time-based UUID. | |
// | |
// A Version 1 UUID is arranged like: | |
// | |
// 0 1 2 3 | |
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | time_low | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | time_mid | time_hi_and_version | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// |clk_seq_hi_res | clk_seq_low | node (0-1) | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | node (2-5) | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | |
func GenerateV1() (UUID, error) { | |
return rfc4122.newV1() | |
} | |
// GenerateV2 generates a version 2 time-based UUID with DCE Security Information | |
// | |
// *Warning*: You should not exceed a call-rate of about 1 per 7 seconds. Why? | |
// | |
// The clock value truncated to the 28 most significant bits, compared to 60 bits in version 1 | |
// Therefore, it will "tick" only once every 429.49 seconds: a little more than 7 minutes | |
// Additionally, the clock sequence number that prevents duplicate ids for the same timestamp is only 6 bits | |
// compared to 14 bits in version 1; so you can only call this 64 times in a 7 minute period (6.7 seconds per UUID) | |
// | |
// A Version 2 UUID is arranged like: | |
// 0 1 2 3 | |
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | id | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | time_mid | time_hi_and_version | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | clk_seq_res | domain | node (0-1) | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | node (2-5) | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | |
// See: https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_2_(date-time_and_MAC_address,_DCE_security_version) | |
// RFC4122 does not formally specify Version 2, but references it from DCE 1.1 Authentication and Security Services (http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01), | |
// and mentions that *"Nothing in this document should be construed to override the DCE standards that defined UUIDs."* | |
func GenerateV2(domain byte, id uint32) (UUID, error) { | |
u, err := dceSec.newV1() | |
if err != nil { | |
return u, err | |
} | |
u.SetDCESecurity(domain, id) | |
return u, nil | |
} | |
// GenerateV3 generates a UUID by hashing the `ns` UUID and the input byte slice using MD5. | |
func GenerateV3(ns UUID, n []byte) UUID { | |
h := md5.New() | |
h.Write(ns[:]) | |
h.Write(n) | |
hs := h.Sum(nil) | |
var u UUID | |
copy(u[:], hs[:16]) | |
u.SetVersion(3) | |
u.SetVariant(VariantRFC4122) | |
return u | |
} | |
// GenerateV4 generates a random (Version 4) UUID | |
// A version 4 UUID is randomly generated by grabbing a random 16-byte sequence from `crypto/rand`. | |
// An error may be returned if it fails to get the random bytes. | |
func GenerateV4() (UUID, error) { | |
var u UUID | |
_, err := rand.Read(u[:]) | |
if err != nil { | |
return Nil, err | |
} | |
u.SetVersion(4) | |
u.SetVariant(VariantRFC4122) | |
return u, nil | |
} | |
// GenerateV5 generates a UUID by hashing the namespace UUID and the input byte slice using SHA1 (truncated to 16 bytes) | |
func GenerateV5(ns UUID, n []byte) UUID { | |
h := sha1.New() | |
h.Write(ns[:]) | |
h.Write(n) | |
hs := h.Sum(nil) | |
var u UUID | |
copy(u[:], hs[:16]) | |
u.SetVersion(5) | |
u.SetVariant(VariantRFC4122) | |
return u | |
} | |
// GenerateV4String generates a UUID and serializes it to a string | |
// in the standard format: | |
// xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx | |
func GenerateV4String() (string, error) { | |
u, err := GenerateV4() | |
if err != nil { | |
return "", err | |
} | |
return u.String(), nil | |
} | |
// UnmarshalText implements encoding.UnmarshalText | |
// The text can be in a few formats | |
// - hex only : e8c8cec324e9445aa086f021ecbac4dd | |
// - canonical : e8c8cec3-24e9-445a-a086-f021ecbac4dd | |
// - braced : {e8c8cec3-24e9-445a-a086-f021ecbac4dd} | |
// - urn:uuid : | |
// - urn:uuid:e8c8cec324e9445aa086f021ecbac4dd | |
// - urn:uuid:e8c8cec3-24e9-445a-a086-f021ecbac4dd | |
func (u *UUID) UnmarshalText(text []byte) error { | |
switch len(text) { | |
case 32: // e8c8cec324e9445aa086f021ecbac4dd | |
return u.unmarshalHex(text) | |
case 36: // e8c8cec3-24e9-445a-a086-f021ecbac4dd | |
return u.unmarshalCanonical(text) | |
case 38: // {e8c8cec3-24e9-445a-a086-f021ecbac4dd} | |
return u.unmarshalBraced(text) | |
case 41: // urn:uuid:e8c8cec324e9445aa086f021ecbac4dd | |
fallthrough | |
case 45: // urn:uuid:e8c8cec3-24e9-445a-a086-f021ecbac4dd | |
return u.unmarshalURN(text) | |
} | |
return ErrInvalidFormat | |
} | |
// MarshalText marshalls this UUID's value as text | |
func (u *UUID) MarshalText() ([]byte, error) { | |
if u == nil { | |
return nil, nil | |
} | |
return []byte(u.String()), nil | |
} | |
// Equals compares two pointers to a UUID, with the additional logic for equating `nil` to `uuid.Nil` | |
// For non-pointer comparison, a UUID can be directly compared using `==` since it is of type `[16]byte` | |
func (u *UUID) Equals(o *UUID) bool { | |
if u == nil { | |
return Nil.Equals(o) | |
} | |
if o == nil { | |
return u.Equals(&Nil) | |
} | |
return *o == *u | |
} | |
var ( | |
urnPrefix = []byte("urn:uuid:") | |
byteGroups = []int{8, 4, 4, 4, 12} | |
) | |
func (u *UUID) unmarshalPlain(text []byte) error { | |
switch len(text) { | |
case 32: | |
return u.unmarshalHex(text) | |
case 36: | |
return u.unmarshalCanonical(text) | |
} | |
return ErrInvalidFormat | |
} | |
func (u *UUID) unmarshalHex(text []byte) error { | |
_, err := hex.Decode(u[:], text) | |
return err | |
} | |
func (u *UUID) unmarshalCanonical(text []byte) error { | |
i := 0 | |
j := 0 | |
for _, bgl := range byteGroups { | |
if i > 0 { | |
if text[i] != '-' { | |
return ErrInvalidFormat | |
} | |
i++ | |
} | |
ii := i + bgl | |
jj := j + bgl/2 | |
_, err := hex.Decode(u[j:jj], text[i:ii]) | |
if err != nil { | |
return ErrInvalidFormat | |
} | |
i += bgl | |
j += bgl / 2 | |
} | |
return nil | |
} | |
func (u *UUID) unmarshalBraced(text []byte) error { | |
n := len(text) - 1 | |
if text[0] != '{' || text[n] != '}' { | |
return ErrInvalidFormat | |
} | |
return u.unmarshalCanonical(text[1:n]) | |
} | |
func (u *UUID) unmarshalURN(text []byte) error { | |
if !bytes.HasPrefix(text, urnPrefix) { | |
return ErrInvalidFormat | |
} | |
n := len(urnPrefix) | |
return u.unmarshalPlain(text[n:]) | |
} | |
// MustParseUUIDString parses an uuid string and returns the UUID | |
// If the parse fails, it panics | |
func MustParseUUIDString(s string) UUID { | |
u, err := ParseUUIDString(s) | |
if err != nil { | |
panic(err) | |
} | |
return u | |
} | |
// ParseUUIDString parses a string into a UUID | |
func ParseUUIDString(s string) (UUID, error) { | |
var u UUID | |
err := u.UnmarshalText([]byte(s)) | |
return u, err | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package uuid | |
import ( | |
"bytes" | |
"encoding/json" | |
"fmt" | |
"github.com/stretchr/testify/assert" | |
"testing" | |
) | |
type marshalTestJSON struct { | |
Pointer1 *UUID `json:"ptr1"` | |
Pointer2 *UUID `json:"ptr2,omitempty"` | |
Value UUID `json:"value"` | |
} | |
func TestMarshalUnMarshalJSON(t *testing.T) { | |
u1 := MustParseUUIDString("5ebd21f5-73bd-4574-9598-68f11584e266") | |
u2 := MustParseUUIDString("{5161487f-c712-4689-a12a-b391ab7eb423}") | |
u3 := MustParseUUIDString("urn:uuid:02ed992a-4082-4981-af49-e4423d3e13b8") | |
tests := []struct { | |
v *marshalTestJSON | |
e []byte | |
}{ | |
{v: &marshalTestJSON{Pointer1: &u1, Pointer2: &u2, Value: u3}, e: []byte(`{"ptr1":"5ebd21f5-73bd-4574-9598-68f11584e266","ptr2":"5161487f-c712-4689-a12a-b391ab7eb423","value":"02ed992a-4082-4981-af49-e4423d3e13b8"}`)}, | |
{v: &marshalTestJSON{Pointer1: &u1, Pointer2: nil, Value: u3}, e: []byte(`{"ptr1":"5ebd21f5-73bd-4574-9598-68f11584e266","value":"02ed992a-4082-4981-af49-e4423d3e13b8"}`)}, | |
{v: &marshalTestJSON{Pointer1: nil, Pointer2: &u2, Value: u3}, e: []byte(`{"ptr1":null,"ptr2":"5161487f-c712-4689-a12a-b391ab7eb423","value":"02ed992a-4082-4981-af49-e4423d3e13b8"}`)}, | |
{v: &marshalTestJSON{Pointer1: &u1, Pointer2: &u2}, e: []byte(`{"ptr1":"5ebd21f5-73bd-4574-9598-68f11584e266","ptr2":"5161487f-c712-4689-a12a-b391ab7eb423","value":"00000000-0000-0000-0000-000000000000"}`)}, | |
{v: &marshalTestJSON{}, e: []byte(`{"ptr1":null,"value":"00000000-0000-0000-0000-000000000000"}`)}, | |
} | |
for i, test := range tests { | |
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { | |
bs, err := json.Marshal(test.v) | |
if err != nil { | |
t.Fatal("unexpected marshal error") | |
} | |
if !bytes.Equal(test.e, bs) { | |
t.Fatalf("unexpected json body. Expected\n%s\nActual\n%s", string(test.e), string(bs)) | |
} | |
var v marshalTestJSON | |
if err = json.Unmarshal(bs, &v); err != nil { | |
t.Fatal("unexpected unmarshal error") | |
} | |
if v.Value != test.v.Value { | |
t.Fatalf("unexpected unmarshal Value. Expected\n%v\nActual\n%v", test.v.Value, v.Value) | |
} | |
if !v.Pointer1.Equals(test.v.Pointer1) { | |
t.Fatalf("unexpected unmarshal Pointer1. Expected\n%v\nActual\n%v", test.v.Pointer1, v.Pointer1) | |
} | |
if !v.Pointer2.Equals(test.v.Pointer2) { | |
t.Fatalf("unexpected unmarshal Pointer2. Expected\n%v\nActual\n%v", test.v.Pointer2, v.Pointer2) | |
} | |
//t.Logf("%s", string(bs)) | |
}) | |
} | |
} | |
func TestUnmarshalUUIDError(t *testing.T) { | |
testStrings := []string{ | |
"", // empty | |
"e8c8cec324e9445aa0", // too short | |
"e8c8cec324e9445aa086f021ecbac4ddaaaaaaaa", // too long | |
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // not hex | |
"e8c8cec3-24e944-5a-a086f021ecbac-4dd", // dashes misplaced | |
"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", // not hex (braced) | |
"{e8c8cec3-24e9-445a-a086-f021ecbac4dd]", // wrong braces | |
"urn:uuid:", // urn empty | |
"urn:uuid:e8c8cec324e9445aa0", // urn too short | |
"urn:uuid:e8c8cec324e9445aa086f021ecbac4ddaaaaaaaa", // urn too long | |
"urn:uuid:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // urn not hex | |
"urn:uuid:e8c8cec3-24e944-5a-a086f021ecbac-4dd", // urn dashes misplaced | |
} | |
for _, str := range testStrings { | |
t.Run(str, func(t *testing.T) { | |
var u UUID | |
if err := u.UnmarshalText([]byte(str)); err == nil { | |
t.Fatalf("expected fail parsing: %v", u) | |
} | |
}) | |
} | |
} | |
func TestUnmarshalUUID(t *testing.T) { | |
expected := UUID{0xe8, 0xc8, 0xce, 0xc3, 0x24, 0xe9, 0x44, 0x5a, 0xa0, 0x86, 0xf0, 0x21, 0xec, 0xba, 0xc4, 0xdd} | |
testStrings := []string{ | |
"e8c8cec324e9445aa086f021ecbac4dd", | |
"e8c8cec3-24e9-445a-a086-f021ecbac4dd", | |
"{e8c8cec3-24e9-445a-a086-f021ecbac4dd}", | |
"urn:uuid:e8c8cec324e9445aa086f021ecbac4dd", | |
"urn:uuid:e8c8cec3-24e9-445a-a086-f021ecbac4dd", | |
} | |
for _, str := range testStrings { | |
t.Run(str, func(t *testing.T) { | |
var u UUID | |
if err := u.UnmarshalText([]byte(str)); err != nil { | |
t.Fatalf("parsing: %v", err) | |
} | |
if u != expected { | |
t.Fatalf("expected uuid '%s' got '%s'", expected, u) | |
} | |
}) | |
} | |
} | |
func TestGenerateV1(t *testing.T) { | |
uuids := make(map[UUID]bool) | |
for i := 0; i < 1000; i++ { | |
u0, err := GenerateV1() | |
if err != nil { | |
t.Fatal(err) | |
} | |
u1, err := GenerateV1() | |
if err != nil { | |
t.Fatal(err) | |
} | |
if _, ok := uuids[u0]; ok { | |
t.Fatalf("u0: Conflict: %s", u0) | |
} | |
if _, ok := uuids[u1]; ok { | |
t.Fatalf("u1: Conflict: %s", u0) | |
} | |
uuids[u0] = true | |
uuids[u1] = true | |
u2, err := ParseUUIDString(u1.String()) | |
if err != nil { | |
t.Fatal(err) | |
} | |
if u1 != u2 { | |
t.Fatalf("%s != parsed %s", u1, u2) | |
} | |
if u1.Version() != 1 { | |
t.Errorf("invalid version '%d'. expected 1", u1.Version()) | |
} | |
if v := u1.Variant(); v != VariantRFC4122 { | |
t.Errorf("incorrect variant '%d'. expected '%d'", v, VariantRFC4122) | |
} | |
//t0 := u0.Time() | |
//t1 := u1.Time() | |
//if t1.Before(t0) { | |
// t.Errorf("time went backwards '%s' < '%s", t1, t0) | |
//} | |
} | |
} | |
func TestGenerateV2(t *testing.T) { | |
var domain byte = 1 | |
var id uint32 = 5000 | |
uuids := make(map[UUID]bool) | |
for i := 0; i < 64; i++ { | |
u0, err := GenerateV2(domain, id) | |
if err != nil { | |
t.Fatal(err) | |
} | |
if _, ok := uuids[u0]; ok { | |
t.Fatalf("Conflict: %s", u0) | |
} | |
uuids[u0] = true | |
u1, err := ParseUUIDString(u0.String()) | |
if err != nil { | |
t.Fatal(err) | |
} | |
if u0 != u1 { | |
t.Fatalf("%s != parsed %s", u0, u1) | |
} | |
if u1.Version() != 2 { | |
t.Errorf("invalid version '%d'. expected 2", u1.Version()) | |
} | |
if v := u1.Variant(); v != VariantRFC4122 { | |
t.Errorf("incorrect variant '%d'. expected '%d'", v, VariantRFC4122) | |
} | |
d0, id0 := u0.DCESecurity() | |
//t0 := u0.Time() | |
//t.Logf("u0: %s - %v (%x/%d)", u0, t0, d0, id0) | |
if d0 != domain { | |
t.Errorf("incorrect domain '%x'. expected '%x'", d0, domain) | |
} | |
if id0 != id { | |
t.Errorf("incorrect id '%x'. expected '%x'", id0, id) | |
} | |
} | |
} | |
func TestGenerateV4(t *testing.T) { | |
for i := 0; i < 1000; i++ { | |
u, err := GenerateV4() | |
if err != nil { | |
t.Fatal(err) | |
} | |
u2, err := ParseUUIDString(u.String()) | |
if err != nil { | |
t.Fatal(err) | |
} | |
if u != u2 { | |
t.Fatalf("%s != parsed %s", u, u2) | |
} | |
if u.Version() != 4 { | |
t.Errorf("invalid version '%d'. expected 4", u.Version()) | |
} | |
if v := u.Variant(); v != VariantRFC4122 { | |
t.Errorf("incorrect variant '%d'. expected '%d'", v, VariantRFC4122) | |
} | |
//t.Log(u) | |
} | |
} | |
func TestGenerateV4String(t *testing.T) { | |
for i := 0; i < 1000; i++ { | |
u, err := GenerateV4String() | |
if err != nil { | |
t.Fatal(err) | |
} | |
u2, err := ParseUUIDString(u) | |
if err != nil { | |
t.Fatal(err) | |
} | |
if u != u2.String() { | |
t.Fatalf("%s != parsed %s", u, u2) | |
} | |
} | |
} | |
func TestFormatUUID(t *testing.T) { | |
u := MustParseUUIDString("9073926b-929f-31c2-abc9-fad77ae3e8eb") | |
tests := []struct { | |
Format string | |
Expected string | |
}{ | |
{"%s", "9073926b-929f-31c2-abc9-fad77ae3e8eb"}, | |
{"%+s", "9073926B-929F-31C2-ABC9-FAD77AE3E8EB"}, | |
{"%v", "9073926b-929f-31c2-abc9-fad77ae3e8eb"}, | |
{"%+v", "9073926B-929F-31C2-ABC9-FAD77AE3E8EB"}, | |
{"%x", "9073926b929f31c2abc9fad77ae3e8eb"}, | |
{"%X", "9073926B929F31C2ABC9FAD77AE3E8EB"}, | |
{"%q", `"9073926b-929f-31c2-abc9-fad77ae3e8eb"`}, | |
{"%+q", `"9073926B-929F-31C2-ABC9-FAD77AE3E8EB"`}, | |
} | |
for _, test := range tests { | |
t.Run(test.Format, func(t *testing.T) { | |
f := fmt.Sprintf(test.Format, u) | |
if f != test.Expected { | |
t.Errorf("got '%s', expected '%s", f, test.Expected) | |
} | |
//t.Logf(f) | |
}) | |
} | |
} | |
func TestHashBasedUUID(t *testing.T) { | |
tests := []struct { | |
namespace UUID | |
name string | |
expected3 UUID | |
expected5 UUID | |
}{ | |
{NamespaceDNS, "example.com", MustParseUUIDString("9073926b-929f-31c2-abc9-fad77ae3e8eb"), MustParseUUIDString("cfbff0d1-9375-5685-968c-48ce8b15ae17")}, | |
{NamespaceX500, "example.com", MustParseUUIDString("11c2f001-e3a4-3ad0-90f7-88ac418c36b8"), MustParseUUIDString("f014ed3c-177a-541e-a840-fc330670f8e8")}, | |
{NamespaceOID, "example.com", MustParseUUIDString("109f8204-164d-33ef-871d-d92c373e8c66"), MustParseUUIDString("eb6106fd-8a37-5395-b3f7-7cb93195fdba")}, | |
{NamespaceDNS, "www.example.com", MustParseUUIDString("5df41881-3aed-3515-88a7-2f4a814cf09e"), MustParseUUIDString("2ed6657d-e927-568b-95e1-2665a8aea6a2")}, | |
{NamespaceURL, "https://www.example.com/uuid5", MustParseUUIDString("73a6ec42-6919-32f6-95e5-ae233b1dbfb9"), MustParseUUIDString("268f0b2f-1cb0-5e48-a699-a61590854f48")}, | |
{NamespaceURL, "https://www.example.com/uuid5?help", MustParseUUIDString("d74cf8b7-d8ca-360c-896d-e3b1a295d1df"), MustParseUUIDString("37c80565-384a-5dde-aead-8a190c9cbf8e")}, | |
} | |
for _, ex := range tests { | |
t.Run(fmt.Sprintf("v3-%s-%s", ex.namespace, ex.name), func(t *testing.T) { | |
u := GenerateV3(ex.namespace, []byte(ex.name)) | |
if u != ex.expected3 { | |
t.Errorf("%s != %s", u, ex.expected3) | |
} | |
if u.Version() != 3 { | |
t.Errorf("invalid version '%d'. expected 3", u.Version()) | |
} | |
if v := u.Variant(); v != VariantRFC4122 { | |
t.Errorf("incorrect variant '%d'. expected '%d'", v, VariantRFC4122) | |
} | |
}) | |
t.Run(fmt.Sprintf("v5-%s-%s", ex.namespace, ex.name), func(t *testing.T) { | |
u := GenerateV5(ex.namespace, []byte(ex.name)) | |
if u != ex.expected5 { | |
t.Errorf("%s != %s", u, ex.expected5) | |
} | |
if u.Version() != 5 { | |
t.Errorf("invalid version '%d'. expected 5", u.Version()) | |
} | |
if v := u.Variant(); v != VariantRFC4122 { | |
t.Errorf("incorrect variant '%d'. expected '%d'", v, VariantRFC4122) | |
} | |
}) | |
} | |
} | |
// TestParseUUIDString parses a uuid in braced form and prints the canonical form | |
func TestParseUUIDString(t *testing.T) { | |
uuid, _ := ParseUUIDString("{e3b4fa08-0365-403d-bc0c-5a3589a1401d}") | |
assert.Equal(t, uuid.String(), "e3b4fa08-0365-403d-bc0c-5a3589a1401d") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment