github.com/go-kivik/kivik/v4@v4.3.2/x/server/uuid.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 //go:build !js 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 // Supported UUID algorithms 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 // confUUIDAlgorithm returns the UUID algorithm used by the server. 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 // randomIncrement returns a random number between 1 and 256. 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 }