diff --git a/tstime/jitter.go b/tstime/jitter.go new file mode 100644 index 000000000..7407b141d --- /dev/null +++ b/tstime/jitter.go @@ -0,0 +1,44 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tstime + +import ( + crand "crypto/rand" + "encoding/binary" + "math/rand" + "sync" + "time" +) + +// crandSource is a rand.Source64 that gets its numbers from +// crypto/rand.Reader. +type crandSource struct{ sync.Mutex } + +var _ rand.Source64 = (*crandSource)(nil) + +func (s *crandSource) Int63() int64 { return int64(s.Uint64() >> 1) } + +func (s *crandSource) Uint64() uint64 { + s.Lock() + defer s.Unlock() + var buf [8]byte + crand.Read(buf[:]) + return binary.BigEndian.Uint64(buf[:]) +} + +func (*crandSource) Seed(seed int64) {} // nope + +var durRand = rand.New(new(crandSource)) + +// RandomDurationBetween returns a random duration in range [min,max). +// If panics if max < min. +func RandomDurationBetween(min, max time.Duration) time.Duration { + diff := max - min + if diff == 0 { + return min + } + ns := durRand.Int63n(int64(diff)) + return min + time.Duration(ns) +} diff --git a/tstime/jitter_test.go b/tstime/jitter_test.go new file mode 100644 index 000000000..b82de2d5c --- /dev/null +++ b/tstime/jitter_test.go @@ -0,0 +1,23 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tstime + +import ( + "testing" + "time" +) + +func TestRandomDurationBetween(t *testing.T) { + if got := RandomDurationBetween(1, 1); got != 1 { + t.Errorf("between 1 and 1 = %v; want 1", int64(got)) + } + const min = 1 * time.Second + const max = 10 * time.Second + for i := 0; i < 500; i++ { + if got := RandomDurationBetween(min, max); got < min || got >= max { + t.Fatalf("%v (%d) out of range", got, got) + } + } +}