Add examples

main
Wisellama 2024-02-20 23:04:26 -08:00
parent 91b8b60e16
commit db84ce0456
4 changed files with 153 additions and 6 deletions

View File

@ -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)
}

23
examples/secure/main.go Normal file
View File

@ -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)
}

34
examples/simple/main.go Normal file
View File

@ -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)
}

View File

@ -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,
},