diff --git a/README.md b/README.md index ace12ea..f5db14f 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ This package supports the following UUID versions: * Version 5, based on SHA-1 hashing of a named value (RFC-4122) This package also supports experimental Universally Unique Identifier implementations based on a -[draft RFC](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format) that updates RFC-4122 -* Version 6, a k-sortable id based on timestamp (draft-peabody-dispatch-new-uuid-format, RFC-4122) -* Version 7, a k-sortable id based on timestamp with variable precision (draft-peabody-dispatch-new-uuid-format, RFC-4122) +[draft RFC](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03) that updates RFC-4122 +* Version 6, a k-sortable id based on timestamp, and field-compatible with v1 (draft-peabody-dispatch-new-uuid-format, RFC-4122) +* Version 7, a k-sortable id based on timestamp (draft-peabody-dispatch-new-uuid-format, RFC-4122) The v6 and v7 IDs are **not** considered a part of the stable API, and may be subject to behavior or API changes as part of minor releases to this package. They will be updated as the draft RFC changes, and will become stable if and when the draft RFC is accepted. @@ -114,4 +114,4 @@ func main() { * [RFC-4122](https://tools.ietf.org/html/rfc4122) * [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) -* [New UUID Formats RFC Draft (Peabody) Rev 02](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02) +* [New UUID Formats RFC Draft (Peabody) Rev 03](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03) diff --git a/generator.go b/generator.go index 38bf685..e80ece5 100644 --- a/generator.go +++ b/generator.go @@ -26,7 +26,6 @@ import ( "crypto/rand" "crypto/sha1" "encoding/binary" - "errors" "fmt" "hash" "io" @@ -71,7 +70,7 @@ func NewV5(ns UUID, name string) UUID { // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit // order being adjusted to allow the UUID to be k-sortable. // -// This is implemented based on revision 02 of the Peabody UUID draft, and may +// This is implemented based on revision 03 of the Peabody UUID draft, and may // be subject to change pending further revisions. Until the final specification // revision is finished, changes required to implement updates to the spec will // not be considered a breaking change. They will happen as a minor version @@ -80,22 +79,16 @@ func NewV6() (UUID, error) { return DefaultGenerator.NewV6() } -// NewV7 returns a k-sortable UUID based on the current UNIX epoch, with the -// ability to configure the timestamp's precision from millisecond all the way -// to nanosecond. The additional precision is supported by reducing the amount -// of pseudorandom data that makes up the rest of the UUID. +// NewV7 returns a k-sortable UUID based on the current millisecond precision +// UNIX epoch and 74 bits of pseudorandom data. // -// If an unknown Precision argument is passed to this method it will panic. As -// such it's strongly encouraged to use the package-provided constants for this -// value. -// -// This is implemented based on revision 02 of the Peabody UUID draft, and may +// This is implemented based on revision 03 of the Peabody UUID draft, and may // be subject to change pending further revisions. Until the final specification // revision is finished, changes required to implement updates to the spec will // not be considered a breaking change. They will happen as a minor version // releases until the spec is final. -func NewV7(p Precision) (UUID, error) { - return DefaultGenerator.NewV7(p) +func NewV7() (UUID, error) { + return DefaultGenerator.NewV7() } // Generator provides an interface for generating UUIDs. @@ -105,7 +98,7 @@ type Generator interface { NewV4() (UUID, error) NewV5(ns UUID, name string) UUID NewV6() (UUID, error) - NewV7(Precision) (UUID, error) + NewV7() (UUID, error) } // Gen is a reference UUID generator based on the specifications laid out in @@ -131,10 +124,6 @@ type Gen struct { lastTime uint64 clockSequence uint16 hardwareAddr [6]byte - - v7LastTime uint64 - v7LastSubsec uint64 - v7ClockSequence uint16 } // interface check -- build will fail if *Gen doesn't satisfy Generator @@ -224,7 +213,7 @@ func (g *Gen) NewV5(ns UUID, name string) UUID { // pseudorandom data. The timestamp in a V6 UUID is the same as V1, with the bit // order being adjusted to allow the UUID to be k-sortable. // -// This is implemented based on revision 02 of the Peabody UUID draft, and may +// This is implemented based on revision 03 of the Peabody UUID draft, and may // be subject to change pending further revisions. Until the final specification // revision is finished, changes required to implement updates to the spec will // not be considered a breaking change. They will happen as a minor version @@ -280,244 +269,36 @@ func (g *Gen) getClockSequence() (uint64, uint16, error) { return timeNow, g.clockSequence, nil } -// Precision is used to configure the V7 generator, to specify how precise the -// timestamp within the UUID should be. -type Precision byte - -const ( - NanosecondPrecision Precision = iota - MicrosecondPrecision - MillisecondPrecision -) - -func (p Precision) String() string { - switch p { - case NanosecondPrecision: - return "nanosecond" - - case MicrosecondPrecision: - return "microsecond" - - case MillisecondPrecision: - return "millisecond" - - default: - return "unknown" - } -} - -// Duration returns the time.Duration for a specific precision. If the Precision -// value is not known, this returns 0. -func (p Precision) Duration() time.Duration { - switch p { - case NanosecondPrecision: - return time.Nanosecond - - case MicrosecondPrecision: - return time.Microsecond - - case MillisecondPrecision: - return time.Millisecond - - default: - return 0 - } -} - -// NewV7 returns a k-sortable UUID based on the current UNIX epoch, with the -// ability to configure the timestamp's precision from millisecond all the way -// to nanosecond. The additional precision is supported by reducing the amount -// of pseudorandom data that makes up the rest of the UUID. +// NewV7 returns a k-sortable UUID based on the current millisecond precision +// UNIX epoch and 74 bits of pseudorandom data. // -// If an unknown Precision argument is passed to this method it will panic. As -// such it's strongly encouraged to use the package-provided constants for this -// value. -// -// This is implemented based on revision 02 of the Peabody UUID draft, and may +// This is implemented based on revision 03 of the Peabody UUID draft, and may // be subject to change pending further revisions. Until the final specification // revision is finished, changes required to implement updates to the spec will // not be considered a breaking change. They will happen as a minor version // releases until the spec is final. -func (g *Gen) NewV7(p Precision) (UUID, error) { +func (g *Gen) NewV7() (UUID, error) { var u UUID - var err error - - switch p { - case NanosecondPrecision: - u, err = g.newV7Nano() - - case MicrosecondPrecision: - u, err = g.newV7Micro() - - case MillisecondPrecision: - u, err = g.newV7Milli() - default: - panic(fmt.Sprintf("unknown precision value %d", p)) - } - - if err != nil { + if _, err := io.ReadFull(g.rand, u[6:]); err != nil { return Nil, err } + tn := g.epochFunc() + ms := uint64(tn.UnixMilli()) + u[0] = byte(ms >> 40) + u[1] = byte(ms >> 32) + u[2] = byte(ms >> 24) + u[3] = byte(ms >> 16) + u[4] = byte(ms >> 8) + u[5] = byte(ms) + u.SetVersion(V7) u.SetVariant(VariantRFC4122) return u, nil } -func (g *Gen) newV7Milli() (UUID, error) { - var u UUID - - if _, err := io.ReadFull(g.rand, u[8:]); err != nil { - return Nil, err - } - - sec, nano, seq, err := g.getV7ClockSequence(MillisecondPrecision) - if err != nil { - return Nil, err - } - - msec := (nano / 1000000) & 0xfff - - d := (sec << 28) // set unixts field - d |= (msec << 16) // set msec field - d |= (uint64(seq) & 0xfff) // set seq field - - binary.BigEndian.PutUint64(u[:], d) - - return u, nil -} - -func (g *Gen) newV7Micro() (UUID, error) { - var u UUID - - if _, err := io.ReadFull(g.rand, u[10:]); err != nil { - return Nil, err - } - - sec, nano, seq, err := g.getV7ClockSequence(MicrosecondPrecision) - if err != nil { - return Nil, err - } - - usec := nano / 1000 - usech := (usec << 4) & 0xfff0000 - usecl := usec & 0xfff - - d := (sec << 28) // set unixts field - d |= usech | usecl // set usec fields - - binary.BigEndian.PutUint64(u[:], d) - binary.BigEndian.PutUint16(u[8:], seq) - - return u, nil -} - -func (g *Gen) newV7Nano() (UUID, error) { - var u UUID - - if _, err := io.ReadFull(g.rand, u[11:]); err != nil { - return Nil, err - } - - sec, nano, seq, err := g.getV7ClockSequence(NanosecondPrecision) - if err != nil { - return Nil, err - } - - nano &= 0x3fffffffff - nanoh := nano >> 26 - nanom := (nano >> 14) & 0xfff - nanol := uint16(nano & 0x3fff) - - d := (sec << 28) // set unixts field - d |= (nanoh << 16) | nanom // set nsec high and med fields - - binary.BigEndian.PutUint64(u[:], d) - binary.BigEndian.PutUint16(u[8:], nanol) // set nsec low field - - u[10] = byte(seq) // set seq field - - return u, nil -} - -const ( - maxSeq14 = (1 << 14) - 1 - maxSeq12 = (1 << 12) - 1 - maxSeq8 = (1 << 8) - 1 -) - -// getV7ClockSequence returns the unix epoch, nanoseconds of current second, and -// the sequence for V7 UUIDs. -func (g *Gen) getV7ClockSequence(p Precision) (epoch uint64, nano uint64, seq uint16, err error) { - g.storageMutex.Lock() - defer g.storageMutex.Unlock() - - tn := g.epochFunc() - unix := uint64(tn.Unix()) - nsec := uint64(tn.Nanosecond()) - - // V7 UUIDs have more precise requirements around how the clock sequence - // value is generated and used. Specifically they require that the sequence - // be zero, unless we've already generated a UUID within this unit of time - // (millisecond, microsecond, or nanosecond) at which point you should - // increment the sequence. Likewise if time has warped backwards for some reason (NTP - // adjustment?), we also increment the clock sequence to reduce the risk of a - // collision. - switch { - case unix < g.v7LastTime: - g.v7ClockSequence++ - - case unix > g.v7LastTime: - g.v7ClockSequence = 0 - - case unix == g.v7LastTime: - switch p { - case NanosecondPrecision: - if nsec <= g.v7LastSubsec { - if g.v7ClockSequence >= maxSeq8 { - return 0, 0, 0, errors.New("generating nanosecond precision UUIDv7s too fast: internal clock sequence would roll over") - } - - g.v7ClockSequence++ - } else { - g.v7ClockSequence = 0 - } - - case MicrosecondPrecision: - if nsec/1000 <= g.v7LastSubsec/1000 { - if g.v7ClockSequence >= maxSeq14 { - return 0, 0, 0, errors.New("generating microsecond precision UUIDv7s too fast: internal clock sequence would roll over") - } - - g.v7ClockSequence++ - } else { - g.v7ClockSequence = 0 - } - - case MillisecondPrecision: - if nsec/1000000 <= g.v7LastSubsec/1000000 { - if g.v7ClockSequence >= maxSeq12 { - return 0, 0, 0, errors.New("generating millisecond precision UUIDv7s too fast: internal clock sequence would roll over") - } - - g.v7ClockSequence++ - } else { - g.v7ClockSequence = 0 - } - - default: - panic(fmt.Sprintf("unknown precision value %d", p)) - } - } - - g.v7LastTime = unix - g.v7LastSubsec = nsec - - return unix, nsec, g.v7ClockSequence, nil -} - // Returns the hardware address. func (g *Gen) getHardwareAddr() ([]byte, error) { var err error diff --git a/generator_test.go b/generator_test.go index eaf8203..c3909e4 100644 --- a/generator_test.go +++ b/generator_test.go @@ -24,7 +24,6 @@ package uuid import ( "bytes" "crypto/rand" - "encoding/binary" "fmt" "net" "strings" @@ -447,54 +446,18 @@ func testNewV6KSortable(t *testing.T) { } func testNewV7(t *testing.T) { - t.Run("InvalidPrecision", testNewV7InvalidPrecision) - - for _, p := range []Precision{NanosecondPrecision, MicrosecondPrecision, MillisecondPrecision} { - t.Run(p.String(), func(t *testing.T) { - t.Run("Basic", makeTestNewV7Basic(p)) - t.Run("Basic10000000", makeTestNewV7Basic10000000(p)) - t.Run("DifferentAcrossCalls", makeTestNewV7DifferentAcrossCalls(p)) - t.Run("StaleEpoch", makeTestNewV7StaleEpoch(p)) - t.Run("FaultyRand", makeTestNewV7FaultyRand(p)) - t.Run("ShortRandomRead", makeTestNewV7ShortRandomRead(p)) - t.Run("ClockSequenceBehaviors", makeTestNewV7ClockSequenceBehaviors(p)) - t.Run("KSortable", makeTestNewV7KSortable(p)) - }) - } - - t.Run("ClockSequence", testNewV7ClockSequence) + t.Run("Basic", makeTestNewV7Basic()) + t.Run("Basic10000000", makeTestNewV7Basic10000000()) + t.Run("DifferentAcrossCalls", makeTestNewV7DifferentAcrossCalls()) + t.Run("StaleEpoch", makeTestNewV7StaleEpoch()) + t.Run("FaultyRand", makeTestNewV7FaultyRand()) + t.Run("ShortRandomRead", makeTestNewV7ShortRandomRead()) + t.Run("KSortable", makeTestNewV7KSortable()) } -func testNewV7InvalidPrecision(t *testing.T) { - t.Run("NewV7", func(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Fatal("call did not panic") - } - }() - - NewV7(255) - }) - - t.Run("getV7ClockSequence", func(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Fatal("expected panic did not occur") - } - }() - - g := NewGen() - g.epochFunc = func() time.Time { - return time.Unix(0, 0) - } - - g.getV7ClockSequence(255) - }) -} - -func makeTestNewV7Basic(p Precision) func(t *testing.T) { +func makeTestNewV7Basic() func(t *testing.T) { return func(t *testing.T) { - u, err := NewV7(p) + u, err := NewV7() if err != nil { t.Fatal(err) } @@ -507,20 +470,16 @@ func makeTestNewV7Basic(p Precision) func(t *testing.T) { } } -func makeTestNewV7Basic10000000(p Precision) func(t *testing.T) { +func makeTestNewV7Basic10000000() func(t *testing.T) { return func(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - if p == MillisecondPrecision { - t.Skip("skipping test, see: https://github.com/uuid6/uuid6-ietf-draft/issues/40") - } - g := NewGen() for i := 0; i < 10000000; i++ { - u, err := g.NewV7(p) + u, err := g.NewV7() if err != nil { t.Fatal(err) } @@ -534,15 +493,15 @@ func makeTestNewV7Basic10000000(p Precision) func(t *testing.T) { } } -func makeTestNewV7DifferentAcrossCalls(p Precision) func(t *testing.T) { +func makeTestNewV7DifferentAcrossCalls() func(t *testing.T) { return func(t *testing.T) { g := NewGen() - u1, err := g.NewV7(p) + u1, err := g.NewV7() if err != nil { t.Fatal(err) } - u2, err := g.NewV7(p) + u2, err := g.NewV7() if err != nil { t.Fatal(err) } @@ -552,7 +511,7 @@ func makeTestNewV7DifferentAcrossCalls(p Precision) func(t *testing.T) { } } -func makeTestNewV7StaleEpoch(p Precision) func(t *testing.T) { +func makeTestNewV7StaleEpoch() func(t *testing.T) { return func(t *testing.T) { g := &Gen{ epochFunc: func() time.Time { @@ -560,11 +519,11 @@ func makeTestNewV7StaleEpoch(p Precision) func(t *testing.T) { }, rand: rand.Reader, } - u1, err := g.NewV7(p) + u1, err := g.NewV7() if err != nil { t.Fatal(err) } - u2, err := g.NewV7(p) + u2, err := g.NewV7() if err != nil { t.Fatal(err) } @@ -574,7 +533,7 @@ func makeTestNewV7StaleEpoch(p Precision) func(t *testing.T) { } } -func makeTestNewV7FaultyRand(p Precision) func(t *testing.T) { +func makeTestNewV7FaultyRand() func(t *testing.T) { return func(t *testing.T) { g := &Gen{ epochFunc: time.Now, @@ -582,36 +541,35 @@ func makeTestNewV7FaultyRand(p Precision) func(t *testing.T) { readToFail: 0, // fail immediately }, } - u, err := g.NewV7(p) + u, err := g.NewV7() if err == nil { t.Errorf("got %v, nil error", u) } } } -func makeTestNewV7ShortRandomRead(p Precision) func(t *testing.T) { +func makeTestNewV7ShortRandomRead() func(t *testing.T) { return func(t *testing.T) { g := &Gen{ epochFunc: time.Now, rand: bytes.NewReader([]byte{42}), } - u, err := g.NewV7(p) + u, err := g.NewV7() if err == nil { t.Errorf("got %v, nil error", u) } } } -func makeTestNewV7KSortable(p Precision) func(t *testing.T) { +func makeTestNewV7KSortable() func(t *testing.T) { return func(t *testing.T) { uuids := make([]UUID, 10) for i := range uuids { - u, err := NewV7(p) - testErrCheck(t, "NewV6()", "", err) + u, err := NewV7() + testErrCheck(t, "NewV7()", "", err) uuids[i] = u - - time.Sleep(p.Duration()) + time.Sleep(time.Millisecond) } for i := 1; i < len(uuids); i++ { @@ -624,190 +582,6 @@ func makeTestNewV7KSortable(p Precision) func(t *testing.T) { } } -// to get 100% code coverage we need to do some glass box testing -func makeTestNewV7ClockSequenceBehaviors(p Precision) func(t *testing.T) { - return func(t *testing.T) { - t.Run("TimeWarp", func(t *testing.T) { - g := NewGen() - tn := time.Now() - unix := uint64(tn.Unix()) + 100 - nsec := uint64(tn.Nanosecond()) - - g.v7LastTime = unix - g.v7LastSubsec = nsec - - _, err := g.NewV7(p) - testErrCheck(t, "g.NewV7()", "", err) - - if g.v7ClockSequence != 1 { - t.Fatalf("g.v7ClockSequence = %d, want 1", g.v7ClockSequence) - } - }) - - t.Run("NominalTime", func(t *testing.T) { - g := NewGen() - g.v7ClockSequence = 100 - - tn := time.Now() - unix := uint64(tn.Unix()) - 100 - nsec := uint64(tn.Nanosecond()) - - g.v7LastTime = unix - g.v7LastSubsec = nsec - - _, err := g.NewV7(p) - testErrCheck(t, "g.NewV7()", "", err) - - if g.v7ClockSequence != 0 { - t.Fatalf("g.v7ClockSequence = %d, want 0", g.v7ClockSequence) - } - }) - - t.Run("Overflow", func(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - wantErrStr := fmt.Sprintf("generating %s precision UUIDv7s too fast: internal clock sequence would roll over", p.String()) - - g := NewGen() - - g.epochFunc = func() time.Time { - return time.Unix(0, 0) - } - - g.v7ClockSequence = maxSeq14 + 1 - g.v7LastTime = uint64(g.epochFunc().Unix()) - g.v7LastSubsec = uint64(g.epochFunc().Nanosecond()) - - _, err := g.NewV7(p) - testErrCheck(t, "g.NewV7()", wantErrStr, err) - }) - } -} - -func testNewV7ClockSequence(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - g := NewGen() - - // hack to try and reduce race conditions based on when the test starts - nsec := time.Now().Nanosecond() - sleepDur := int(time.Second) - nsec - time.Sleep(time.Duration(sleepDur)) - - u1, err := g.NewV7(MillisecondPrecision) - if err != nil { - t.Fatalf("failed to generate V7 UUID #1: %v", err) - } - - u2, err := g.NewV7(MillisecondPrecision) - if err != nil { - t.Fatalf("failed to generate V7 UUID #2: %v", err) - } - - time.Sleep(time.Millisecond) - - u3, err := g.NewV7(MillisecondPrecision) - if err != nil { - t.Fatalf("failed to generate V7 UUID #3: %v", err) - } - - time.Sleep(time.Second) - - u4, err := g.NewV7(MillisecondPrecision) - if err != nil { - t.Fatalf("failed to generate V7 UUID #3: %v", err) - } - - s1 := binary.BigEndian.Uint16(u1[6:8]) & 0xfff - s2 := binary.BigEndian.Uint16(u2[6:8]) & 0xfff - s3 := binary.BigEndian.Uint16(u3[6:8]) & 0xfff - s4 := binary.BigEndian.Uint16(u4[6:8]) & 0xfff - - if s1 != 0 { - t.Errorf("sequence 1 should be zero, was %d", s1) - } - - if s2 != s1+1 { - t.Errorf("sequence 2 expected to be one above sequence 1; seq 1: %d, seq 2: %d", s1, s2) - } - - if s3 != 0 { - t.Errorf("sequence 3 should be zero, was %d", s3) - } - - if s4 != 0 { - t.Errorf("sequence 4 should be zero, was %d", s4) - } -} - -func TestPrecision_String(t *testing.T) { - tests := []struct { - p Precision - want string - }{ - { - p: NanosecondPrecision, - want: "nanosecond", - }, - { - p: MillisecondPrecision, - want: "millisecond", - }, - { - p: MicrosecondPrecision, - want: "microsecond", - }, - { - p: 0xff, - want: "unknown", - }, - } - - for _, tt := range tests { - t.Run(tt.want, func(t *testing.T) { - if got := tt.p.String(); got != tt.want { - t.Errorf("got = %s, want %s", got, tt.want) - } - }) - } -} - -func TestPrecision_Duration(t *testing.T) { - tests := []struct { - p Precision - want time.Duration - }{ - { - p: NanosecondPrecision, - want: time.Nanosecond, - }, - { - p: MillisecondPrecision, - want: time.Millisecond, - }, - { - p: MicrosecondPrecision, - want: time.Microsecond, - }, - { - p: 0xff, - want: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.p.String(), func(t *testing.T) { - if got := tt.p.Duration(); got != tt.want { - t.Errorf("got = %s, want %s", got, tt.want) - } - }) - } -} - func BenchmarkGenerator(b *testing.B) { b.Run("NewV1", func(b *testing.B) { for i := 0; i < b.N; i++ { diff --git a/uuid.go b/uuid.go index f314b84..e747e54 100644 --- a/uuid.go +++ b/uuid.go @@ -20,7 +20,7 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // Package uuid provides implementations of the Universally Unique Identifier -// (UUID), as specified in RFC-4122 and the Peabody RFC Draft (revision 02). +// (UUID), as specified in RFC-4122 and the Peabody RFC Draft (revision 03). // // RFC-4122[1] provides the specification for versions 1, 3, 4, and 5. The // Peabody UUID RFC Draft[2] provides the specification for the new k-sortable @@ -36,7 +36,7 @@ // ensure we were understanding the specification correctly. // // [1] https://tools.ietf.org/html/rfc4122 -// [2] https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02 +// [2] https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03 // [3] http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 package uuid @@ -63,8 +63,8 @@ const ( V3 // Version 3 (namespace name-based) V4 // Version 4 (random) V5 // Version 5 (namespace name-based) - V6 // Version 6 (k-sortable timestamp and random data) [peabody draft] - V7 // Version 7 (k-sortable timestamp, with configurable precision, and random data) [peabody draft] + V6 // Version 6 (k-sortable timestamp and random data, field-compatible with v1) [peabody draft] + V7 // Version 7 (k-sortable timestamp and random data) [peabody draft] _ // Version 8 (k-sortable timestamp, meant for custom implementations) [peabody draft] [not implemented] ) @@ -116,7 +116,7 @@ func TimestampFromV1(u UUID) (Timestamp, error) { // TimestampFromV6 returns the Timestamp embedded within a V6 UUID. This // function returns an error if the UUID is any version other than 6. // -// This is implemented based on revision 01 of the Peabody UUID draft, and may +// This is implemented based on revision 03 of the Peabody UUID draft, and may // be subject to change pending further revisions. Until the final specification // revision is finished, changes required to implement updates to the spec will // not be considered a breaking change. They will happen as a minor version