From db84ce04563642ae6905fa18f7eea7f3200c2939 Mon Sep 17 00:00:00 2001 From: Wisellama Date: Tue, 20 Feb 2024 23:04:26 -0800 Subject: [PATCH] Add examples --- examples/interesting/main.go | 85 ++++++++++++++++++++++++++++++++++++ examples/secure/main.go | 23 ++++++++++ examples/simple/main.go | 34 +++++++++++++++ ulid_test.go | 17 +++++--- 4 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 examples/interesting/main.go create mode 100644 examples/secure/main.go create mode 100644 examples/simple/main.go diff --git a/examples/interesting/main.go b/examples/interesting/main.go new file mode 100644 index 0000000..2f59df2 --- /dev/null +++ b/examples/interesting/main.go @@ -0,0 +1,85 @@ +package main + +import ( + crypto "crypto/rand" + "encoding/binary" + "fmt" + "math/rand" + "sync" + "time" + + "git.wisellama.rocks/Wisellama/ulid" +) + +func main() { + t := time.Now() + + entropy, err := interestingEntropy() + if err != nil { + fmt.Printf("error creating entropy source: %v\n", err) + } + + wg := sync.WaitGroup{} + for i := 0; i < 30; i++ { + wg.Add(1) + go func() { + defer wg.Done() + u, err := ulid.NewULIDString(t, entropy) + if err != nil { + fmt.Printf("error: %v\n", err) + } + + fmt.Printf("ULID: %s\n", u) + }() + } + + wg.Wait() + fmt.Printf("Done\n") +} + +// interestingEntropy returns a psuedo random source. This is an +// alternative to always using crypto/rand and potentially running out +// of entropy. Don't rely on this for security-sensitive applications. +// +// It uses crypto/rand to get the seed for a math/rand psuedorandom +// number generator. This way we don't collide with other instances +// that might start at the same time. +// +// It also uses a simple locked source for concurrency. If you want a +// more official implementation, look at exp/rand LockedSource: +// +// https://pkg.go.dev/golang.org/x/exp/rand#LockedSource +func interestingEntropy() (*rand.Rand, error) { + seedBytes := make([]byte, 8) + _, err := crypto.Read(seedBytes) + if err != nil { + return nil, err + } + + seedInt, _ := binary.Varint(seedBytes) + + source := &LockedSource{ + Source: rand.NewSource(seedInt), + } + + // nolint + return rand.New(source), nil + +} + +type LockedSource struct { + mx sync.Mutex + Source rand.Source +} + +func (s *LockedSource) Int63() int64 { + s.mx.Lock() + defer s.mx.Unlock() + return s.Source.Int63() +} + +func (s *LockedSource) Seed(seed int64) { + s.mx.Lock() + defer s.mx.Unlock() + s.Source.Seed(seed) +} diff --git a/examples/secure/main.go b/examples/secure/main.go new file mode 100644 index 0000000..9cf2053 --- /dev/null +++ b/examples/secure/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "crypto/rand" + "fmt" + "time" + + "git.wisellama.rocks/Wisellama/ulid" +) + +func main() { + t := time.Now() + // This example uses the crypto/rand entropy. This is thread safe + // and intended to be used for secure features. If you are using + // ULIDs for anything related to security/authentication, then use + // this entropy source. + u, err := ulid.NewULIDString(t, rand.Reader) + if err != nil { + fmt.Printf("error: %v\n", err) + } + + fmt.Printf("ULID: %s\n", u) +} diff --git a/examples/simple/main.go b/examples/simple/main.go new file mode 100644 index 0000000..eb0fb69 --- /dev/null +++ b/examples/simple/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "math/rand" + "time" + + "git.wisellama.rocks/Wisellama/ulid" +) + +func main() { + t := time.Now() + + // Warning: Using time.Now() for the entropy seed is bad if you + // have multiple instances all starting up at the same time who + // might write to the same data store. + // + // The default rand.NewSource() is also bad for multiple + // threads. You either need to implement your own locking + // mechanism or use a LockedSource: + // https://pkg.go.dev/golang.org/x/exp/rand#LockedSource + // + // See the other examples for better alternatives. + // + // nolint + entropy := rand.New(rand.NewSource(t.UnixMilli())) + + u, err := ulid.NewULIDString(t, entropy) + if err != nil { + fmt.Printf("error: %v\n", err) + } + + fmt.Printf("ULID: %s\n", u) +} diff --git a/ulid_test.go b/ulid_test.go index e4b81f2..35eca5a 100644 --- a/ulid_test.go +++ b/ulid_test.go @@ -9,6 +9,11 @@ import ( "time" ) +func getTestRandomSource() *rand.Rand { + // nolint + return rand.New(rand.NewSource(0)) +} + func TestGetMSBytes(t *testing.T) { runAll := true @@ -102,7 +107,7 @@ func TestNewULID(t *testing.T) { TestName: "Unix zero time", RunIt: false || runAll, Time: time.Unix(0, 0), - Entropy: rand.New(rand.NewSource(0)), + Entropy: getTestRandomSource(), Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x94, 0xFD, 0xC2, 0xFA, 0x2F, 0xFC, 0xC0, 0x41, 0xD3}, Err: false, }, @@ -110,7 +115,7 @@ func TestNewULID(t *testing.T) { TestName: "time overflow", RunIt: false || runAll, Time: time.Time{}, // zero time overflows when using Unix epoch time - Entropy: rand.New(rand.NewSource(0)), + Entropy: getTestRandomSource(), Expected: nil, Err: true, }, @@ -118,7 +123,7 @@ func TestNewULID(t *testing.T) { TestName: "seed 0, real time", RunIt: false || runAll, Time: time.Date(2024, 02, 16, 14, 02, 15, 17, time.UTC), - Entropy: rand.New(rand.NewSource(0)), + Entropy: getTestRandomSource(), Expected: []byte{0x01, 0x8D, 0xB2, 0x39, 0x96, 0x58, 0x01, 0x94, 0xFD, 0xC2, 0xFA, 0x2F, 0xFC, 0xC0, 0x41, 0xD3}, Err: false, }, @@ -175,7 +180,7 @@ func TestULIDString(t *testing.T) { TestName: "Unix zero time", RunIt: false || runAll, Time: time.Unix(0, 0), - Entropy: rand.New(rand.NewSource(0)), + Entropy: getTestRandomSource(), Expected: "000000000006AFVGQT5ZYC0GEK", Err: false, }, @@ -183,7 +188,7 @@ func TestULIDString(t *testing.T) { TestName: "time overflow", RunIt: false || runAll, Time: time.Time{}, // zero time overflows when using Unix epoch time - Entropy: rand.New(rand.NewSource(0)), + Entropy: getTestRandomSource(), Expected: "", Err: true, }, @@ -191,7 +196,7 @@ func TestULIDString(t *testing.T) { TestName: "seed 0, real time", RunIt: false || runAll, Time: time.Date(2024, 02, 16, 14, 02, 15, 17, time.UTC), - Entropy: rand.New(rand.NewSource(0)), + Entropy: getTestRandomSource(), Expected: "01HPS3K5JR06AFVGQT5ZYC0GEK", Err: false, },