Skip to content

Instantly share code, notes, and snippets.

@nsa-yoda
Created March 21, 2022 17:37
Show Gist options
  • Save nsa-yoda/3676ed82f28fef41fb5fbbcdcd79c0fa to your computer and use it in GitHub Desktop.
Save nsa-yoda/3676ed82f28fef41fb5fbbcdcd79c0fa to your computer and use it in GitHub Desktop.
Sphire Mantis UUID <1.2.4 based on Jet/UUID
// 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
}
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