├── .gitignore ├── .travis.yml ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── README.md.tpl ├── example_test.go ├── guid.go └── guid_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | install: 3 | - go get -u github.com/golang/dep/cmd/dep 4 | - dep ensure -v 5 | go: 6 | - 1.8.3 7 | - 1.9 8 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/onsi/ginkgo" 6 | packages = [".","config","internal/codelocation","internal/containernode","internal/failer","internal/leafnodes","internal/remote","internal/spec","internal/spec_iterator","internal/specrunner","internal/suite","internal/testingtproxy","internal/writer","reporters","reporters/stenographer","reporters/stenographer/support/go-colorable","reporters/stenographer/support/go-isatty","types"] 7 | revision = "9eda700730cba42af70d53180f9dcce9266bc2bc" 8 | version = "v1.4.0" 9 | 10 | [[projects]] 11 | name = "github.com/onsi/gomega" 12 | packages = [".","format","internal/assertion","internal/asyncassertion","internal/oraclematcher","internal/testingtsupport","matchers","matchers/support/goraph/bipartitegraph","matchers/support/goraph/edge","matchers/support/goraph/node","matchers/support/goraph/util","types"] 13 | revision = "c893efa28eb45626cdaa76c9f653b62488858837" 14 | version = "v1.2.0" 15 | 16 | [[projects]] 17 | branch = "master" 18 | name = "golang.org/x/net" 19 | packages = ["html","html/atom","html/charset"] 20 | revision = "66aacef3dd8a676686c7ae3716979581e8b03c47" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "golang.org/x/sys" 25 | packages = ["unix"] 26 | revision = "bb24a47a89eac6c1227fbcb2ae37a8b9ed323366" 27 | 28 | [[projects]] 29 | branch = "master" 30 | name = "golang.org/x/text" 31 | packages = ["encoding","encoding/charmap","encoding/htmlindex","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","encoding/unicode","internal/gen","internal/tag","internal/utf8internal","language","runes","transform","unicode/cldr"] 32 | revision = "0b2f934451366a0d631d133c9cda87655f30ebcb" 33 | 34 | [[projects]] 35 | branch = "v2" 36 | name = "gopkg.in/yaml.v2" 37 | packages = ["."] 38 | revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" 39 | 40 | [solve-meta] 41 | analyzer-name = "dep" 42 | analyzer-version = 1 43 | inputs-digest = "dca1bcd6639a944d8a343d0f9d51d7e96e82079e138c45cc607f268535967727" 44 | solver-name = "gps-cdcl" 45 | solver-version = 1 46 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | name = "github.com/onsi/ginkgo" 26 | version = "^1.4.0" 27 | 28 | [[constraint]] 29 | name = "github.com/onsi/gomega" 30 | version = "^1.2.0" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Black Square Media 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | test: 4 | go test . -v 1 5 | 6 | bench: 7 | go test -test.run=NONE -test.bench=. -test.benchmem 8 | 9 | README.md: README.md.tpl $(wildcard *.go) 10 | becca -package $(subst $(GOPATH)/src/,,$(PWD)) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GUID 2 | 3 | [![Build Status](https://travis-ci.org/bsm/go-guid.png?branch=master)](https://travis-ci.org/bsm/go-guid) 4 | [![GoDoc](https://godoc.org/github.com/bsm/go-guid?status.png)](http://godoc.org/github.com/bsm/go-guid) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/bsm/go-guid)](https://goreportcard.com/report/github.com/bsm/go-guid) 6 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 7 | 8 | Simple, thread-safe MongoDB style GUID generator. 9 | 10 | ### Examples 11 | 12 | ```go 13 | func main { 14 | // Create a new 12-byte globally-unique identifier 15 | id := guid.New96() 16 | fmt.Println(hex.EncodeToString(id.Bytes())) 17 | } 18 | ``` 19 | 20 | ```go 21 | func main { 22 | // Create a new 16-byte globally-unique identifier 23 | id := guid.New128() 24 | fmt.Println(hex.EncodeToString(id.Bytes())) 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /README.md.tpl: -------------------------------------------------------------------------------- 1 | # GUID 2 | 3 | [![Build Status](https://travis-ci.org/bsm/go-guid.png?branch=master)](https://travis-ci.org/bsm/go-guid) 4 | [![GoDoc](https://godoc.org/github.com/bsm/go-guid?status.png)](http://godoc.org/github.com/bsm/go-guid) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/bsm/go-guid)](https://goreportcard.com/report/github.com/bsm/go-guid) 6 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 7 | 8 | Simple, thread-safe MongoDB style GUID generator. 9 | 10 | ### Examples 11 | 12 | ```go 13 | func main {{ "ExampleNew96" | code }} 14 | ``` 15 | 16 | ```go 17 | func main {{ "ExampleNew128" | code }} 18 | ``` 19 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package guid_test 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | guid "github.com/bsm/go-guid" 8 | ) 9 | 10 | func ExampleNew96() { 11 | // Create a new 12-byte globally-unique identifier 12 | id := guid.New96() 13 | fmt.Println(hex.EncodeToString(id.Bytes())) 14 | } 15 | 16 | func ExampleNew128() { 17 | // Create a new 16-byte globally-unique identifier 18 | id := guid.New128() 19 | fmt.Println(hex.EncodeToString(id.Bytes())) 20 | } 21 | -------------------------------------------------------------------------------- /guid.go: -------------------------------------------------------------------------------- 1 | package guid 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/binary" 6 | "io" 7 | "os" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | // GUID is an interface unifying identifiers of 13 | // different lengths 14 | type GUID interface { 15 | CreatedAt() time.Time 16 | WriteTo(w io.Writer) (int64, error) 17 | Bytes() []byte 18 | } 19 | 20 | // -------------------------------------------------------------------- 21 | 22 | // GUID96 is a 12-byte globally unique identifier 23 | type GUID96 [12]byte 24 | 25 | var base96 = GUID96{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 26 | 27 | // New96 creates a 96bit/12byte global identifier 28 | func New96() GUID96 { return new96at(time.Now()) } 29 | 30 | func new96at(ts time.Time) GUID96 { 31 | bininc := [4]byte{0, 0, 0, 0} 32 | encoder.PutUint32(bininc[:], nextInc()) 33 | 34 | bytes := base96 35 | encoder.PutUint32(bytes[0:], uint32(ts.Unix())) 36 | copy(bytes[4:], hostpid[:]) 37 | copy(bytes[9:], bininc[1:]) 38 | 39 | return bytes 40 | } 41 | 42 | // Bytes returns a byte slice 43 | func (g GUID96) Bytes() []byte { return g[:] } 44 | 45 | // WriteTo implements io.WriterTo 46 | func (g GUID96) WriteTo(w io.Writer) (int64, error) { 47 | n, err := w.Write(g[:]) 48 | return int64(n), err 49 | } 50 | 51 | // CreatedAt extract the timestamp at which the GUID was created 52 | func (g GUID96) CreatedAt() time.Time { 53 | return time.Unix(int64(encoder.Uint32(g[0:])), 0) 54 | } 55 | 56 | // -------------------------------------------------------------------- 57 | 58 | // GUID128 is a 16-byte globally unique identifier 59 | type GUID128 [16]byte 60 | 61 | var base128 = GUID128{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 62 | 63 | // New128 creates a 128bit/16byte global identifier 64 | func New128() GUID128 { return new128at(time.Now()) } 65 | 66 | func new128at(ts time.Time) GUID128 { 67 | bininc := [4]byte{0, 0, 0, 0} 68 | encoder.PutUint32(bininc[:], nextInc()) 69 | 70 | bytes := base128 71 | encoder.PutUint64(bytes[0:], uint64(ts.Unix())) 72 | copy(bytes[8:], hostpid[:]) 73 | copy(bytes[13:], bininc[1:]) 74 | 75 | return bytes 76 | } 77 | 78 | // Bytes returns a byte slice 79 | func (g GUID128) Bytes() []byte { return g[:] } 80 | 81 | // WriteTo implements io.WriterTo 82 | func (g GUID128) WriteTo(w io.Writer) (int64, error) { 83 | n, err := w.Write(g[:]) 84 | return int64(n), err 85 | } 86 | 87 | // CreatedAt extract the timestamp at which the GUID was created 88 | func (g GUID128) CreatedAt() time.Time { 89 | return time.Unix(int64(encoder.Uint64(g[0:])), 0) 90 | } 91 | 92 | // -------------------------------------------------------------------- 93 | 94 | const maxUint24 = (1 << 24) - 1 95 | 96 | var inc uint32 97 | var hostpid [5]byte 98 | var encoder = binary.BigEndian 99 | 100 | func init() { 101 | hostname, _ := os.Hostname() 102 | if hostname == "" { 103 | hostname = "localhost" 104 | } 105 | 106 | hash := md5.Sum([]byte(hostname)) 107 | copy(hostpid[:], hash[:3]) 108 | encoder.PutUint16(hostpid[3:], uint16(os.Getpid())) 109 | } 110 | 111 | func nextInc() uint32 { 112 | num := atomic.AddUint32(&inc, 1) 113 | if num > maxUint24 { 114 | num = atomic.AddUint32(&inc, maxUint24+1) 115 | } 116 | return num 117 | } 118 | -------------------------------------------------------------------------------- /guid_test.go: -------------------------------------------------------------------------------- 1 | package guid 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "time" 7 | 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | var _ = Describe("GUID", func() { 13 | 14 | BeforeEach(func() { 15 | inc = 0 16 | hostpid = [5]byte{91, 93, 95, 97, 99} 17 | }) 18 | 19 | It("should increment correctly", func() { 20 | Expect(New96().Bytes()[9:]).To(Equal([]byte{0, 0, 1})) 21 | inc = 16777214 22 | Expect(New96().Bytes()[9:]).To(Equal([]byte{255, 255, 255})) 23 | Expect(New96().Bytes()[9:]).To(Equal([]byte{0, 0, 0})) 24 | }) 25 | 26 | Describe("96bit", func() { 27 | 28 | It("should generate", func() { 29 | g1, g2 := New96(), New96() 30 | Expect(g1).NotTo(Equal(g2)) 31 | }) 32 | 33 | It("should extract creation time", func() { 34 | g := New96() 35 | Expect(g.CreatedAt()).To(BeTemporally("~", time.Now(), time.Second)) 36 | }) 37 | 38 | It("should export bytes", func() { 39 | g := new96at(time.Unix(1414141414, 123456)) 40 | Expect(g.Bytes()).To(Equal([]byte{84, 74, 21, 230, 91, 93, 95, 97, 99, 0, 0, 1})) 41 | }) 42 | 43 | It("should implement io.WriterTo", func() { 44 | n, err := New96().WriteTo(&bytes.Buffer{}) 45 | Expect(err).NotTo(HaveOccurred()) 46 | Expect(n).To(Equal(int64(12))) 47 | }) 48 | 49 | It("should avoid collisions", func() { 50 | set := make(map[GUID96]int) 51 | for i := 0; i < 1000000; i++ { 52 | set[New96()]++ 53 | } 54 | Expect(len(set)).To(Equal(1000000)) 55 | }) 56 | 57 | }) 58 | 59 | Describe("128bit", func() { 60 | 61 | It("should generate", func() { 62 | g1, g2 := New128(), New128() 63 | Expect(g1).NotTo(Equal(g2)) 64 | }) 65 | 66 | It("should extract creation time", func() { 67 | g := New128() 68 | Expect(g.CreatedAt()).To(BeTemporally("~", time.Now(), time.Second)) 69 | }) 70 | 71 | It("should export bytes", func() { 72 | g := new128at(time.Unix(1414141414, 123456)) 73 | Expect(g.Bytes()).To(Equal([]byte{0, 0, 0, 0, 84, 74, 21, 230, 91, 93, 95, 97, 99, 0, 0, 1})) 74 | }) 75 | 76 | It("should implement io.WriterTo", func() { 77 | n, err := New128().WriteTo(&bytes.Buffer{}) 78 | Expect(err).NotTo(HaveOccurred()) 79 | Expect(n).To(Equal(int64(16))) 80 | }) 81 | 82 | It("should avoid collisions", func() { 83 | set := make(map[GUID128]int) 84 | for i := 0; i < 1000000; i++ { 85 | set[New128()]++ 86 | } 87 | Expect(len(set)).To(Equal(1000000)) 88 | }) 89 | 90 | }) 91 | }) 92 | 93 | func TestSuite(t *testing.T) { 94 | RegisterFailHandler(Fail) 95 | RunSpecs(t, "github.com/bsm/guid") 96 | } 97 | 98 | // -------------------------------------------------------------------- 99 | 100 | func BenchmarkNew96(b *testing.B) { 101 | for i := 0; i < b.N; i++ { 102 | New96() 103 | } 104 | } 105 | 106 | func BenchmarkNew128(b *testing.B) { 107 | for i := 0; i < b.N; i++ { 108 | New128() 109 | } 110 | } 111 | --------------------------------------------------------------------------------