18 changed files with 378 additions and 50 deletions
@ -0,0 +1,33 @@ |
|||||
|
package uuid_test |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"github.com/eyebluecn/tank/code/tool/uuid" |
||||
|
) |
||||
|
|
||||
|
func ExampleNewV4() { |
||||
|
u4, err := uuid.NewV4() |
||||
|
if err != nil { |
||||
|
fmt.Println("error:", err) |
||||
|
return |
||||
|
} |
||||
|
fmt.Println(u4) |
||||
|
} |
||||
|
|
||||
|
func ExampleNewV5() { |
||||
|
u5, err := uuid.NewV5(uuid.NamespaceURL, []byte("nu7hat.ch")) |
||||
|
if err != nil { |
||||
|
fmt.Println("error:", err) |
||||
|
return |
||||
|
} |
||||
|
fmt.Println(u5) |
||||
|
} |
||||
|
|
||||
|
func ExampleParseHex() { |
||||
|
u, err := uuid.ParseHex("6ba7b810-9dad-11d1-80b4-00c04fd430c8") |
||||
|
if err != nil { |
||||
|
fmt.Println("error:", err) |
||||
|
return |
||||
|
} |
||||
|
fmt.Println(u) |
||||
|
} |
||||
@ -0,0 +1,173 @@ |
|||||
|
// This package provides immutable UUID structs and the functions
|
||||
|
// NewV3, NewV4, NewV5 and Parse() for generating versions 3, 4
|
||||
|
// and 5 UUIDs as specified in RFC 4122.
|
||||
|
//
|
||||
|
// Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
|
||||
|
package uuid |
||||
|
|
||||
|
import ( |
||||
|
"crypto/md5" |
||||
|
"crypto/rand" |
||||
|
"crypto/sha1" |
||||
|
"encoding/hex" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"hash" |
||||
|
"regexp" |
||||
|
) |
||||
|
|
||||
|
// The UUID reserved variants.
|
||||
|
const ( |
||||
|
ReservedNCS byte = 0x80 |
||||
|
ReservedRFC4122 byte = 0x40 |
||||
|
ReservedMicrosoft byte = 0x20 |
||||
|
ReservedFuture byte = 0x00 |
||||
|
) |
||||
|
|
||||
|
// The following standard UUIDs are for use with NewV3() or NewV5().
|
||||
|
var ( |
||||
|
NamespaceDNS, _ = ParseHex("6ba7b810-9dad-11d1-80b4-00c04fd430c8") |
||||
|
NamespaceURL, _ = ParseHex("6ba7b811-9dad-11d1-80b4-00c04fd430c8") |
||||
|
NamespaceOID, _ = ParseHex("6ba7b812-9dad-11d1-80b4-00c04fd430c8") |
||||
|
NamespaceX500, _ = ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8") |
||||
|
) |
||||
|
|
||||
|
// Pattern used to parse hex string representation of the UUID.
|
||||
|
// FIXME: do something to consider both brackets at one time,
|
||||
|
// current one allows to parse string with only one opening
|
||||
|
// or closing bracket.
|
||||
|
const hexPattern = "^(urn\\:uuid\\:)?\\{?([a-z0-9]{8})-([a-z0-9]{4})-" + |
||||
|
"([1-5][a-z0-9]{3})-([a-z0-9]{4})-([a-z0-9]{12})\\}?$" |
||||
|
|
||||
|
var re = regexp.MustCompile(hexPattern) |
||||
|
|
||||
|
// A UUID representation compliant with specification in
|
||||
|
// RFC 4122 document.
|
||||
|
type UUID [16]byte |
||||
|
|
||||
|
// ParseHex creates a UUID object from given hex string
|
||||
|
// representation. Function accepts UUID string in following
|
||||
|
// formats:
|
||||
|
//
|
||||
|
// uuid.ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||
|
// uuid.ParseHex("{6ba7b814-9dad-11d1-80b4-00c04fd430c8}")
|
||||
|
// uuid.ParseHex("urn:uuid:6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||
|
//
|
||||
|
func ParseHex(s string) (u *UUID, err error) { |
||||
|
md := re.FindStringSubmatch(s) |
||||
|
if md == nil { |
||||
|
err = errors.New("Invalid UUID string") |
||||
|
return |
||||
|
} |
||||
|
hash := md[2] + md[3] + md[4] + md[5] + md[6] |
||||
|
b, err := hex.DecodeString(hash) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
u = new(UUID) |
||||
|
copy(u[:], b) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Parse creates a UUID object from given bytes slice.
|
||||
|
func Parse(b []byte) (u *UUID, err error) { |
||||
|
if len(b) != 16 { |
||||
|
err = errors.New("Given slice is not valid UUID sequence") |
||||
|
return |
||||
|
} |
||||
|
u = new(UUID) |
||||
|
copy(u[:], b) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Generate a UUID based on the MD5 hash of a namespace identifier
|
||||
|
// and a name.
|
||||
|
func NewV3(ns *UUID, name []byte) (u *UUID, err error) { |
||||
|
if ns == nil { |
||||
|
err = errors.New("Invalid namespace UUID") |
||||
|
return |
||||
|
} |
||||
|
u = new(UUID) |
||||
|
// Set all bits to MD5 hash generated from namespace and name.
|
||||
|
u.setBytesFromHash(md5.New(), ns[:], name) |
||||
|
u.setVariant(ReservedRFC4122) |
||||
|
u.setVersion(3) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Generate a random UUID.
|
||||
|
func NewV4() (u *UUID, err error) { |
||||
|
u = new(UUID) |
||||
|
// Set all bits to randomly (or pseudo-randomly) chosen values.
|
||||
|
_, err = rand.Read(u[:]) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
u.setVariant(ReservedRFC4122) |
||||
|
u.setVersion(4) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Generate a UUID based on the SHA-1 hash of a namespace identifier
|
||||
|
// and a name.
|
||||
|
func NewV5(ns *UUID, name []byte) (u *UUID, err error) { |
||||
|
u = new(UUID) |
||||
|
// Set all bits to truncated SHA1 hash generated from namespace
|
||||
|
// and name.
|
||||
|
u.setBytesFromHash(sha1.New(), ns[:], name) |
||||
|
u.setVariant(ReservedRFC4122) |
||||
|
u.setVersion(5) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Generate a MD5 hash of a namespace and a name, and copy it to the
|
||||
|
// UUID slice.
|
||||
|
func (u *UUID) setBytesFromHash(hash hash.Hash, ns, name []byte) { |
||||
|
hash.Write(ns[:]) |
||||
|
hash.Write(name) |
||||
|
copy(u[:], hash.Sum([]byte{})[:16]) |
||||
|
} |
||||
|
|
||||
|
// Set the two most significant bits (bits 6 and 7) of the
|
||||
|
// clock_seq_hi_and_reserved to zero and one, respectively.
|
||||
|
func (u *UUID) setVariant(v byte) { |
||||
|
switch v { |
||||
|
case ReservedNCS: |
||||
|
u[8] = (u[8] | ReservedNCS) & 0xBF |
||||
|
case ReservedRFC4122: |
||||
|
u[8] = (u[8] | ReservedRFC4122) & 0x7F |
||||
|
case ReservedMicrosoft: |
||||
|
u[8] = (u[8] | ReservedMicrosoft) & 0x3F |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Variant returns the UUID Variant, which determines the internal
|
||||
|
// layout of the UUID. This will be one of the constants: RESERVED_NCS,
|
||||
|
// RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE.
|
||||
|
func (u *UUID) Variant() byte { |
||||
|
if u[8]&ReservedNCS == ReservedNCS { |
||||
|
return ReservedNCS |
||||
|
} else if u[8]&ReservedRFC4122 == ReservedRFC4122 { |
||||
|
return ReservedRFC4122 |
||||
|
} else if u[8]&ReservedMicrosoft == ReservedMicrosoft { |
||||
|
return ReservedMicrosoft |
||||
|
} |
||||
|
return ReservedFuture |
||||
|
} |
||||
|
|
||||
|
// Set the four most significant bits (bits 12 through 15) of the
|
||||
|
// time_hi_and_version field to the 4-bit version number.
|
||||
|
func (u *UUID) setVersion(v byte) { |
||||
|
u[6] = (u[6] & 0xF) | (v << 4) |
||||
|
} |
||||
|
|
||||
|
// Version returns a version number of the algorithm used to
|
||||
|
// generate the UUID sequence.
|
||||
|
func (u *UUID) Version() uint { |
||||
|
return uint(u[6] >> 4) |
||||
|
} |
||||
|
|
||||
|
// Returns unparsed version of the generated UUID sequence.
|
||||
|
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:]) |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
// This package provides immutable UUID structs and the functions
|
||||
|
// NewV3, NewV4, NewV5 and Parse() for generating versions 3, 4
|
||||
|
// and 5 UUIDs as specified in RFC 4122.
|
||||
|
//
|
||||
|
// Copyright (C) 2011 by Krzysztof Kowalik <chris@nu7hat.ch>
|
||||
|
package uuid |
||||
|
|
||||
|
import ( |
||||
|
"regexp" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
const format = "^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$" |
||||
|
|
||||
|
func TestParse(t *testing.T) { |
||||
|
_, err := Parse([]byte{1, 2, 3, 4, 5}) |
||||
|
if err == nil { |
||||
|
t.Errorf("Expected error due to invalid UUID sequence") |
||||
|
} |
||||
|
base, _ := NewV4() |
||||
|
u, err := Parse(base[:]) |
||||
|
if err != nil { |
||||
|
t.Errorf("Expected to parse UUID sequence without problems") |
||||
|
return |
||||
|
} |
||||
|
if u.String() != base.String() { |
||||
|
t.Errorf("Expected parsed UUID to be the same as base, %s != %s", u.String(), base.String()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestParseString(t *testing.T) { |
||||
|
_, err := ParseHex("foo") |
||||
|
if err == nil { |
||||
|
t.Errorf("Expected error due to invalid UUID string") |
||||
|
} |
||||
|
base, _ := NewV4() |
||||
|
u, err := ParseHex(base.String()) |
||||
|
if err != nil { |
||||
|
t.Errorf("Expected to parse UUID sequence without problems") |
||||
|
return |
||||
|
} |
||||
|
if u.String() != base.String() { |
||||
|
t.Errorf("Expected parsed UUID to be the same as base, %s != %s", u.String(), base.String()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestNewV3(t *testing.T) { |
||||
|
u, err := NewV3(NamespaceURL, []byte("golang.org")) |
||||
|
if err != nil { |
||||
|
t.Errorf("Expected to generate UUID without problems, error thrown: %d", err.Error()) |
||||
|
return |
||||
|
} |
||||
|
if u.Version() != 3 { |
||||
|
t.Errorf("Expected to generate UUIDv3, given %d", u.Version()) |
||||
|
} |
||||
|
if u.Variant() != ReservedRFC4122 { |
||||
|
t.Errorf("Expected to generate UUIDv3 RFC4122 variant, given %x", u.Variant()) |
||||
|
} |
||||
|
re := regexp.MustCompile(format) |
||||
|
if !re.MatchString(u.String()) { |
||||
|
t.Errorf("Expected string representation to be valid, given %s", u.String()) |
||||
|
} |
||||
|
u2, _ := NewV3(NamespaceURL, []byte("golang.org")) |
||||
|
if u2.String() != u.String() { |
||||
|
t.Errorf("Expected UUIDs generated of the same namespace and name to be the same") |
||||
|
} |
||||
|
u3, _ := NewV3(NamespaceDNS, []byte("golang.org")) |
||||
|
if u3.String() == u.String() { |
||||
|
t.Errorf("Expected UUIDs generated of different namespace and the same name to be different") |
||||
|
} |
||||
|
u4, _ := NewV3(NamespaceURL, []byte("code.google.com")) |
||||
|
if u4.String() == u.String() { |
||||
|
t.Errorf("Expected UUIDs generated of the same namespace and different names to be different") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestNewV4(t *testing.T) { |
||||
|
u, err := NewV4() |
||||
|
if err != nil { |
||||
|
t.Errorf("Expected to generate UUID without problems, error thrown: %s", err.Error()) |
||||
|
return |
||||
|
} |
||||
|
if u.Version() != 4 { |
||||
|
t.Errorf("Expected to generate UUIDv4, given %d", u.Version()) |
||||
|
} |
||||
|
if u.Variant() != ReservedRFC4122 { |
||||
|
t.Errorf("Expected to generate UUIDv4 RFC4122 variant, given %x", u.Variant()) |
||||
|
} |
||||
|
re := regexp.MustCompile(format) |
||||
|
if !re.MatchString(u.String()) { |
||||
|
t.Errorf("Expected string representation to be valid, given %s", u.String()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestNewV5(t *testing.T) { |
||||
|
u, err := NewV5(NamespaceURL, []byte("golang.org")) |
||||
|
if err != nil { |
||||
|
t.Errorf("Expected to generate UUID without problems, error thrown: %d", err.Error()) |
||||
|
return |
||||
|
} |
||||
|
if u.Version() != 5 { |
||||
|
t.Errorf("Expected to generate UUIDv5, given %d", u.Version()) |
||||
|
} |
||||
|
if u.Variant() != ReservedRFC4122 { |
||||
|
t.Errorf("Expected to generate UUIDv5 RFC4122 variant, given %x", u.Variant()) |
||||
|
} |
||||
|
re := regexp.MustCompile(format) |
||||
|
if !re.MatchString(u.String()) { |
||||
|
t.Errorf("Expected string representation to be valid, given %s", u.String()) |
||||
|
} |
||||
|
u2, _ := NewV5(NamespaceURL, []byte("golang.org")) |
||||
|
if u2.String() != u.String() { |
||||
|
t.Errorf("Expected UUIDs generated of the same namespace and name to be the same") |
||||
|
} |
||||
|
u3, _ := NewV5(NamespaceDNS, []byte("golang.org")) |
||||
|
if u3.String() == u.String() { |
||||
|
t.Errorf("Expected UUIDs generated of different namespace and the same name to be different") |
||||
|
} |
||||
|
u4, _ := NewV5(NamespaceURL, []byte("code.google.com")) |
||||
|
if u4.String() == u.String() { |
||||
|
t.Errorf("Expected UUIDs generated of the same namespace and different names to be different") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func BenchmarkParseHex(b *testing.B) { |
||||
|
s := "f3593cff-ee92-40df-4086-87825b523f13" |
||||
|
for i := 0; i < b.N; i++ { |
||||
|
_, err := ParseHex(s) |
||||
|
if err != nil { |
||||
|
b.Fatal(err) |
||||
|
} |
||||
|
} |
||||
|
b.StopTimer() |
||||
|
b.ReportAllocs() |
||||
|
} |
||||
Loading…
Reference in new issue