1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package server
16
17 import (
18 "context"
19 "crypto/rand"
20 "encoding/hex"
21 "fmt"
22 "net/http"
23 "strconv"
24 "sync/atomic"
25 "time"
26
27 "gitlab.com/flimzy/httpe"
28
29 internal "github.com/go-kivik/kivik/v4/int/errors"
30 )
31
32 type uuidAlgorithm string
33
34 const (
35
36 uuidAlgorithmRandom uuidAlgorithm = "random"
37 uuidAlgorithmSequential uuidAlgorithm = "sequential"
38 uuidAlgorithmUTCRandom uuidAlgorithm = "utc_random"
39 uuidAlgorithmUTCID uuidAlgorithm = "utc_id"
40 uuidAlgorithmDefault = uuidAlgorithmSequential
41
42 uuidDefaultMaxCount = 1000
43 )
44
45 var supportedUUIDAlgorithms = []uuidAlgorithm{uuidAlgorithmRandom, uuidAlgorithmSequential, uuidAlgorithmUTCRandom, uuidAlgorithmUTCID}
46
47
48 func (s *Server) confUUIDAlgorithm(ctx context.Context) uuidAlgorithm {
49 value, _ := s.config.Key(ctx, "uuids", "algorithm")
50 algo := uuidAlgorithm(value)
51 for _, a := range supportedUUIDAlgorithms {
52 if algo == a {
53 return algo
54 }
55 }
56
57 return uuidAlgorithmDefault
58 }
59
60 func (s *Server) confUUIDMaxCount(ctx context.Context) int {
61 var count int
62 _ = s.conf(ctx, "uuids", "max_count", &count)
63 if count < 1 {
64 return uuidDefaultMaxCount
65 }
66 return count
67 }
68
69 func (s *Server) uuids() httpe.HandlerWithError {
70 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
71 var count int
72 if param := r.URL.Query().Get("count"); param != "" {
73 var err error
74 count, err = strconv.Atoi(param)
75 if err != nil {
76 return &internal.Error{Status: http.StatusBadRequest, Message: "count must be a positive integer"}
77 }
78 }
79 if count == 0 {
80 count = 1
81 }
82 maxCount := s.confUUIDMaxCount(r.Context())
83 if count > maxCount {
84 return &internal.Error{Status: http.StatusBadRequest, Message: fmt.Sprintf("count must not exceed %d", maxCount)}
85 }
86 var uuids []string
87 var err error
88 switch algo := s.confUUIDAlgorithm(r.Context()); algo {
89 case uuidAlgorithmRandom:
90 uuids, err = s.uuidsRandom(count)
91 case uuidAlgorithmSequential:
92 uuids, err = s.uuidsSequential(count)
93 case uuidAlgorithmUTCRandom:
94 uuids, err = s.uuidsUTCRandom(count)
95 case uuidAlgorithmUTCID:
96 uuids = s.uuidsUTCID(r.Context(), count)
97 }
98 if err != nil {
99 return err
100 }
101 return serveJSON(w, http.StatusOK, map[string][]string{"uuids": uuids})
102 })
103 }
104
105 func (s *Server) uuidsRandom(count int) ([]string, error) {
106 const randomUUIDLength = 32
107 uuids := make([]string, count)
108 for i := range uuids {
109 uuid, err := randomHexString(randomUUIDLength)
110 if err != nil {
111 return nil, err
112 }
113 uuids[i] = uuid
114 }
115 return uuids, nil
116 }
117
118 func randomHexString(length int) (string, error) {
119 const charsPerByte = 2
120 var hexByteLength int
121 if length%4 == 0 {
122 hexByteLength = length / charsPerByte
123 } else {
124 hexByteLength = length/charsPerByte + 1
125 }
126 randomBytes := make([]byte, hexByteLength)
127 if _, err := rand.Read(randomBytes); err != nil {
128 return "", err
129 }
130 return hex.EncodeToString(randomBytes)[:length], nil
131 }
132
133
134 func randomIncrement() (int32, error) {
135 buf := make([]byte, 1)
136 if _, err := rand.Read(buf); err != nil {
137 return 0, err
138 }
139 return int32(buf[0]) + 1, nil
140 }
141
142 func (s *Server) uuidsSequential(count int) ([]string, error) {
143 prefix, err := s.uuidSequentialPrefix()
144 if err != nil {
145 return nil, err
146 }
147
148 uuids := make([]string, count)
149 for i := range uuids {
150 incr, err := randomIncrement()
151 if err != nil {
152 return nil, err
153 }
154 monotonic := atomic.AddInt32(&s.sequentialUUIDMonotonicID, incr)
155 uuids[i] = prefix + fmt.Sprintf("%06x", monotonic)
156 }
157 return uuids, nil
158 }
159
160 func (s *Server) uuidSequentialPrefix() (string, error) {
161 s.sequentialMU.Lock()
162 defer s.sequentialMU.Unlock()
163 if s.sequentialUUIDPrefix != "" {
164 return s.sequentialUUIDPrefix, nil
165 }
166
167 const sequentialUUIDPrefixLength = 26
168 var err error
169 s.sequentialUUIDPrefix, err = randomHexString(sequentialUUIDPrefixLength)
170 return s.sequentialUUIDPrefix, err
171 }
172
173 func (s *Server) uuidsUTCRandom(count int) ([]string, error) {
174 const randomChars = 18
175 uuids := make([]string, count)
176 for i := range uuids {
177 r, err := randomHexString(randomChars)
178 if err != nil {
179 return nil, err
180 }
181 uuids[i] = fmt.Sprintf("%014x%s", time.Now().UnixMicro(), r)
182 }
183 return uuids, nil
184 }
185
186 func (s *Server) uuidsUTCID(ctx context.Context, count int) []string {
187 suffix, _ := s.config.Key(ctx, "uuids", "utc_id_suffix")
188 uuids := make([]string, count)
189 for i := range uuids {
190 uuids[i] = fmt.Sprintf("%014x%s", time.Now().UnixMicro(), suffix)
191 }
192 return uuids
193 }
194
View as plain text