1package s3mem
2
3import (
4	"encoding/base32"
5	"fmt"
6	"math/big"
7	"sync"
8
9	"github.com/johannesboyne/gofakes3"
10)
11
12var add1 = new(big.Int).SetInt64(1)
13
14type versionGenerator struct {
15	state uint64
16	size  int
17	next  *big.Int
18	mu    sync.Mutex
19}
20
21func newVersionGenerator(seed uint64, size int) *versionGenerator {
22	if size <= 0 {
23		size = 64
24	}
25	return &versionGenerator{next: new(big.Int), state: seed}
26}
27
28func (v *versionGenerator) Next(scratch []byte) (gofakes3.VersionID, []byte) {
29	v.mu.Lock()
30
31	v.next.Add(v.next, add1)
32	idb := []byte(fmt.Sprintf("%030d", v.next))
33
34	neat := v.size/8*8 + 8 // cheap and nasty way to ensure a multiple of 8 definitely greater than size
35
36	scratchLen := len(idb) + neat + 1
37	if len(scratch) < scratchLen {
38		scratch = make([]byte, scratchLen)
39	}
40	copy(scratch, idb)
41
42	b := scratch[len(idb)+1:]
43
44	// This is a simple inline implementation of http://xoshiro.di.unimi.it/splitmix64.c.
45	// It may not ultimately be the right tool for this job but with a large
46	// enough size the collision risk should still be minuscule.
47	for i := 0; i < neat; i += 8 {
48		v.state += 0x9E3779B97F4A7C15
49		z := v.state
50		z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9
51		z = (z ^ (z >> 27)) * 0x94D049BB133111EB
52		b[i], b[i+1], b[i+2], b[i+3], b[i+4], b[i+5], b[i+6], b[i+7] =
53			byte(z), byte(z>>8), byte(z>>16), byte(z>>24), byte(z>>32), byte(z>>40), byte(z>>48), byte(z>>56)
54	}
55
56	v.mu.Unlock()
57
58	// The version IDs that come out of S3 appear to start with '3/' and follow
59	// with a base64-URL encoded blast of god knows what. There didn't appear
60	// to be any explanation of the format beyond that, but let's copy it anyway.
61	//
62	// Base64 is not sortable though, and we need our versions to be lexicographically
63	// sortable for the SkipList key, so we have to encode it as base32hex, which _is_
64	// sortable, and just pretend that it's "Base64". Phew!
65
66	return gofakes3.VersionID(fmt.Sprintf("3/%s", base32.HexEncoding.EncodeToString(scratch))), scratch
67}
68