Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Comb UUID #116

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions comb_uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package uuid

import (
"bytes"
"encoding/binary"
"time"
)

type CombUUID UUID

// Equal returns true if this uuid and other uuid equals, otherwise false
func (u CombUUID) Equal(other CombUUID) bool {
return bytes.Equal(u[:], other[:])
}

// Bytes returns bytes slice representation of UUID.
func (u CombUUID) Bytes() []byte {
return u[:]
}

// Version returns algorithm version used to generate UUID.
func (u CombUUID) Version() byte {
return u[0] >> 4
}

// Returns canonical string representation of UUID:
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
func (u CombUUID) String() string {
return UUID(u).String()
}

// SetVersion sets version bits.
func (u *CombUUID) SetVersion(v byte) {
u[0] = (u[0] & 0x0f) | (v << 4)
}

// Returns created time in this CombUUID
func (u *CombUUID) Time() time.Time {
t := int64(binary.BigEndian.Uint32(u[4:8]))
t |= int64(binary.BigEndian.Uint16(u[2:4])) << 32
t |= int64(binary.BigEndian.Uint16(u[0:4])&0xfff) << 48

return time.Unix(0, (t-epochStart)*100)
}
73 changes: 73 additions & 0 deletions comb_uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package uuid

import (
"bytes"
"crypto/rand"
"testing"
"time"

. "gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func TestCombUUID(t *testing.T) { TestingT(t) }

type testSuiteComb struct{}

var _ = Suite(&testSuiteComb{})

func (s *testSuiteComb) TestBytes(c *C) {
u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}

bytes1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}

c.Assert(bytes.Equal(u.Bytes(), bytes1), Equals, true)
}

func (s *testSuiteComb) TestString(c *C) {
c.Assert(NamespaceDNS.String(), Equals, "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
}

func (s *testSuiteComb) TestEqual(c *C) {
c.Assert(CombUUID(NamespaceDNS).Equal(CombUUID(NamespaceDNS)), Equals, true)
c.Assert(CombUUID(NamespaceDNS).Equal(CombUUID(NamespaceURL)), Equals, false)
}

func (s *testSuiteComb) TestVersion(c *C) {
u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
c.Assert(u.Version(), Equals, V1)
}

func (s *testSuiteComb) TestSetVersion(c *C) {
u := UUID{}
u.SetVersion(4)
c.Assert(u.Version(), Equals, V4)
}

func (s *testSuiteComb) TestTime(c *C) {
now := time.Unix(0, 0)

g := &rfc4122AndCombGenerator{
epochFunc: func() time.Time {
return now
},
hwAddrFunc: defaultHWAddrFunc,
rand: rand.Reader,
}

u1, err := g.NewCombV4()
c.Assert(err, IsNil)

if err == nil {
ut := u1.Time()
c.Assert(ut.Unix(), Equals, now.Unix())
}

u2, err := g.NewCombV1()
c.Assert(err, IsNil)

if err == nil {
ut := u2.Time()
c.Assert(ut.Unix(), Equals, now.Unix())
}
}
85 changes: 73 additions & 12 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type epochFunc func() time.Time
type hwAddrFunc func() (net.HardwareAddr, error)

var (
global = newRFC4122Generator()
global = newRfc4122AndCombGenerator()

posixUID = uint32(os.Getuid())
posixGID = uint32(os.Getgid())
Expand All @@ -69,22 +69,38 @@ func NewV4() (UUID, error) {
return global.NewV4()
}

// NewCombV1 returns ordered CombUUID based on current timestamp and MAC address.
func NewCombV1() (CombUUID, error) {
return global.NewCombV1()
}

// NewCombV4 returns CombUUID based on current timestamp and random generated UUID
// with ordered created time in 100ns precision.
func NewCombV4() (CombUUID, error) {
return global.NewCombV4()
}

// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
func NewV5(ns UUID, name string) UUID {
return global.NewV5(ns, name)
}

// Generator provides interface for generating UUIDs.
type Generator interface {
// RFC 4122
NewV1() (UUID, error)
NewV2(domain byte) (UUID, error)
NewV3(ns UUID, name string) UUID
NewV4() (UUID, error)
NewV5(ns UUID, name string) UUID

// COMB
NewCombV1() (CombUUID, error)
NewCombV4() (CombUUID, error)
}

// Default generator implementation.
type rfc4122Generator struct {
type rfc4122AndCombGenerator struct {
clockSequenceOnce sync.Once
hardwareAddrOnce sync.Once
storageMutex sync.Mutex
Expand All @@ -98,16 +114,16 @@ type rfc4122Generator struct {
hardwareAddr [6]byte
}

func newRFC4122Generator() Generator {
return &rfc4122Generator{
func newRfc4122AndCombGenerator() Generator {
return &rfc4122AndCombGenerator{
epochFunc: time.Now,
hwAddrFunc: defaultHWAddrFunc,
rand: rand.Reader,
}
}

// NewV1 returns UUID based on current timestamp and MAC address.
func (g *rfc4122Generator) NewV1() (UUID, error) {
func (g *rfc4122AndCombGenerator) NewV1() (UUID, error) {
u := UUID{}

timeNow, clockSeq, err := g.getClockSequence()
Expand All @@ -132,7 +148,7 @@ func (g *rfc4122Generator) NewV1() (UUID, error) {
}

// NewV2 returns DCE Security UUID based on POSIX UID/GID.
func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) {
func (g *rfc4122AndCombGenerator) NewV2(domain byte) (UUID, error) {
u, err := g.NewV1()
if err != nil {
return Nil, err
Expand All @@ -154,7 +170,7 @@ func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) {
}

// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID {
func (g *rfc4122AndCombGenerator) NewV3(ns UUID, name string) UUID {
u := newFromHash(md5.New(), ns, name)
u.SetVersion(V3)
u.SetVariant(VariantRFC4122)
Expand All @@ -163,7 +179,7 @@ func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID {
}

// NewV4 returns random generated UUID.
func (g *rfc4122Generator) NewV4() (UUID, error) {
func (g *rfc4122AndCombGenerator) NewV4() (UUID, error) {
u := UUID{}
if _, err := io.ReadFull(g.rand, u[:]); err != nil {
return Nil, err
Expand All @@ -175,16 +191,61 @@ func (g *rfc4122Generator) NewV4() (UUID, error) {
}

// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID {
func (g *rfc4122AndCombGenerator) NewV5(ns UUID, name string) UUID {
u := newFromHash(sha1.New(), ns, name)
u.SetVersion(V5)
u.SetVariant(VariantRFC4122)

return u
}

// NewCombV1 returns nonstandard UUID based on V1 RFC4122 with timestamp bytes in the front
func (g *rfc4122AndCombGenerator) NewCombV1() (CombUUID, error) {
u := CombUUID{}

timeNow, clockSeq, err := g.getClockSequence()
if err != nil {
return CombUUID(Nil), err
}

binary.BigEndian.PutUint16(u[0:], uint16(timeNow>>48))
binary.BigEndian.PutUint16(u[2:], uint16(timeNow>>32))
binary.BigEndian.PutUint32(u[4:], uint32(timeNow))
binary.BigEndian.PutUint16(u[8:], clockSeq)

hardwareAddr, err := g.getHardwareAddr()
if err != nil {
return CombUUID(Nil), err
}
copy(u[10:], hardwareAddr)

u.SetVersion(V1)

return u, nil
}

// NewCombV4 returns nonstandard UUID based on V4 RFC4122
// with timestamp bytes in the front (not fully random)
func (g *rfc4122AndCombGenerator) NewCombV4() (CombUUID, error) {
u := CombUUID{}

timeNow := g.getEpoch()

binary.BigEndian.PutUint16(u[0:], uint16(timeNow>>48))
binary.BigEndian.PutUint16(u[2:], uint16(timeNow>>32))
binary.BigEndian.PutUint32(u[4:], uint32(timeNow))

if _, err := io.ReadFull(g.rand, u[8:]); err != nil {
return CombUUID(Nil), err
}

u.SetVersion(V4)

return u, nil
}

// Returns epoch and clock sequence.
func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) {
func (g *rfc4122AndCombGenerator) getClockSequence() (uint64, uint16, error) {
var err error
g.clockSequenceOnce.Do(func() {
buf := make([]byte, 2)
Expand Down Expand Up @@ -212,7 +273,7 @@ func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) {
}

// Returns hardware address.
func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) {
func (g *rfc4122AndCombGenerator) getHardwareAddr() ([]byte, error) {
var err error
g.hardwareAddrOnce.Do(func() {
if hwAddr, err := g.hwAddrFunc(); err == nil {
Expand All @@ -236,7 +297,7 @@ func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) {

// Returns difference in 100-nanosecond intervals between
// UUID epoch (October 15, 1582) and current time.
func (g *rfc4122Generator) getEpoch() uint64 {
func (g *rfc4122AndCombGenerator) getEpoch() uint64 {
return epochStart + uint64(g.epochFunc().UnixNano()/100)
}

Expand Down
Loading