diff --git a/id.go b/id.go index 6152a4a..ff54c1f 100644 --- a/id.go +++ b/id.go @@ -1,7 +1,51 @@ package gorand -// ID is a wrapper for GetHex(64). -// 64 bytes should be enough to use as unique IDs to avoid collisions between generated values. -func ID() (string, error) { - return GetHex(64) +import ( + "bytes" + "io" + "time" +) + +var localID [9]byte + +// Initializes the value for the local process run identifier +func init() { + buf, err := GetBytes(9) + if err != nil { + localID = [9]byte{'D', 'e', 'f', 'a', 'u', 'l', 't', 'I', 'D'} + } else { + _, err = io.ReadFull(bytes.NewBuffer(buf), localID[:]) + if err != nil { + localID = [9]byte{'D', 'e', 'f', 'a', 'u', 'l', 't', 'I', 'D'} + } + } +} + +// ID generates a [64]byte random value, using time and local identifier into it. +// +// First (most-significative) 15 bytes: time value +// Next 9 bytes: Local process randomly-generated identifier +// Next 40 bytes: Random value +func ID() (id [64]byte, err error) { + var buf []byte + + // Time part + now, err := time.Now().MarshalBinary() + if err != nil { + return + } + buf = append(buf, now[:15]...) + + // Local ID part + buf = append(buf, localID[:]...) + + // Random value + r, err := GetBytes(40) + if err != nil { + return + } + buf = append(buf, r...) + + _, err = io.ReadFull(bytes.NewBuffer(buf), id[:]) + return } diff --git a/id_test.go b/id_test.go index 9a8be39..454dc60 100644 --- a/id_test.go +++ b/id_test.go @@ -1,6 +1,7 @@ package gorand import ( + "encoding/hex" "testing" ) @@ -10,8 +11,28 @@ func TestID(t *testing.T) { t.Error(err.Error()) } - if len(id) != 128 { - t.Error("Length of UUID isn't 128") + if len(id) != 64 { + t.Error("Length of ID isn't 64 bytes") + } +} + +func TestIDOrder(t *testing.T) { + ids := make([][64]byte, 100000) + for i := 0; i < 100000; i++ { + id, err := ID() + if err != nil { + t.Fatal(err) + } + ids[i] = id + } + + for i, v := range ids { + if i == 0 { + continue + } + if hex.EncodeToString(v[:]) <= hex.EncodeToString(ids[i-1][:]) { + t.Fatalf("%v is lesser or equal than %v", hex.EncodeToString(v[:]), hex.EncodeToString(ids[i-1][:])) + } } } diff --git a/uuid-v4.go b/uuid-v4.go index 133870a..0645479 100644 --- a/uuid-v4.go +++ b/uuid-v4.go @@ -11,42 +11,43 @@ import ( type UUID [16]byte // UUIDv4 generates a version 4 (randomly generated) UUID -func UUIDv4() (UUID, error) { - var uuid UUID - +func UUIDv4() (uuid UUID, err error) { // Get 16 random bytes b, err := GetBytes(16) if err != nil { - return uuid, err + return } _, err = io.ReadFull(bytes.NewBuffer(b), uuid[:]) if err != nil { - return uuid, err + return } uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 - return uuid, nil + + return } // UnmarshalUUID parses a string representation of a UUID and returns its []bytes value. // It doesn't check for version or varian bits, so it can be used with invalid (non RFC 4122 compilant) values. func UnmarshalUUID(s string) (uuid UUID, err error) { if len(s) != 36 { - return uuid, errors.New("Invalid UUID length") + err = errors.New("Invalid UUID length") + return } if s[8:9] != "-" || s[13:14] != "-" || s[18:19] != "-" || s[23:24] != "-" { - return uuid, errors.New("Invalid UUID format") + err = errors.New("Invalid UUID format") + return } b, err := hex.DecodeString(s[0:8] + s[9:13] + s[14:18] + s[19:23] + s[24:]) if err != nil { - return uuid, err + return } _, err = io.ReadFull(bytes.NewBuffer(b), uuid[:]) - return uuid, err + return } // MarshalUUID converts UUID into its canonical string representation. @@ -56,9 +57,11 @@ func MarshalUUID(uuid UUID) (s string, err error) { _, err = io.ReadFull(bytes.NewBuffer(uuid[:]), b[:]) if err != nil { - return s, err + return } - s = hex.EncodeToString(b[:]) - return s[0:8] + "-" + s[8:12] + "-" + s[12:16] + "-" + s[16:20] + "-" + s[20:], nil + bin := hex.EncodeToString(b[:]) + s = bin[0:8] + "-" + bin[8:12] + "-" + bin[12:16] + "-" + bin[16:20] + "-" + bin[20:] + + return }