github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/id.go (about) 1 package tlog 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "math/rand" 8 "sync" 9 "time" 10 11 "github.com/nikandfor/errors" 12 13 "github.com/nikandfor/tlog/low" 14 ) 15 16 type ( 17 ID [16]byte 18 19 // ShortIDError is an ID parsing error. 20 ShortIDError struct { 21 Bytes int // Bytes successfully parsed 22 } 23 24 concurrentRand struct { 25 mu sync.Mutex 26 r *rand.Rand 27 } 28 ) 29 30 var rnd = &concurrentRand{r: rand.New(rand.NewSource(time.Now().UnixNano()))} //nolint:gosec 31 32 // String returns short string representation. 33 // 34 // It's not supposed to be able to recover it back to the same value as it was. 35 func (id ID) String() string { 36 var b [8]byte 37 id.FormatTo(b[:], 'v') 38 return string(b[:]) 39 } 40 41 // StringFull returns full id represented as string. 42 func (id ID) StringFull() string { 43 var b [32]byte 44 id.FormatTo(b[:], 'v') 45 return string(b[:]) 46 } 47 48 // IDFromBytes decodes ID from bytes slice. 49 // 50 // If byte slice is shorter than type length result is returned as is and ShortIDError as error value. 51 // You may use result if you expected short ID prefix. 52 func IDFromBytes(b []byte) (id ID, err error) { 53 n := copy(id[:], b) 54 55 if n < len(id) { 56 err = ShortIDError{Bytes: n} 57 } 58 59 return 60 } 61 62 // IDFromString parses ID from string. 63 // 64 // If parsed string is shorter than type length result is returned as is and ShortIDError as error value. 65 // You may use result if you expected short ID prefix (profuced by ID.String, for example). 66 func IDFromString(s string) (id ID, err error) { 67 if "________________________________"[:len(s)] == s { 68 return 69 } 70 71 var i int 72 var c byte 73 for ; i < len(s); i++ { 74 switch { 75 case '0' <= s[i] && s[i] <= '9': 76 c = s[i] - '0' 77 case 'a' <= s[i] && s[i] <= 'f': 78 c = s[i] - 'a' + 10 79 default: 80 err = hex.InvalidByteError(s[i]) 81 return 82 } 83 84 if i&1 == 0 { 85 c <<= 4 86 } 87 88 id[i>>1] |= c 89 } 90 91 if i < 2*len(id) { 92 err = ShortIDError{Bytes: i / 2} 93 } 94 95 return 96 } 97 98 // IDFromStringAsBytes is the same as IDFromString. It avoids alloc in IDFromString(string(b)). 99 func IDFromStringAsBytes(s []byte) (id ID, err error) { 100 if bytes.Equal([]byte("________________________________")[:len(s)], s) { 101 return 102 } 103 104 n, err := hex.Decode(id[:], s) 105 if err != nil { 106 return 107 } 108 109 if n < len(id) { 110 return id, ShortIDError{Bytes: n} 111 } 112 113 return id, nil 114 } 115 116 // ShouldID wraps IDFrom* call and skips error if any. 117 func ShouldID(id ID, err error) ID { 118 return id 119 } 120 121 // MustID wraps IDFrom* call and panics if error occurred. 122 func MustID(id ID, err error) ID { 123 if err != nil { 124 panic(err) 125 } 126 127 return id 128 } 129 130 // Error is an error interface implementation. 131 func (e ShortIDError) Error() string { 132 return fmt.Sprintf("too short id: %d bytes, wanted %d", e.Bytes, len(ID{})) 133 } 134 135 // Format is fmt.Formatter interface implementation. 136 // It supports width. '+' flag sets width to full ID length. 137 func (id ID) Format(s fmt.State, c rune) { 138 var buf0 [32]byte 139 buf := low.NoEscapeBuffer(buf0[:]) 140 141 w := 8 142 if W, ok := s.Width(); ok { 143 w = W 144 } 145 if s.Flag('+') { 146 w = 2 * len(id) 147 } 148 id.FormatTo(buf[:w], c) 149 _, _ = s.Write(buf[:w]) 150 } 151 152 // FormatTo is alloc free Format alternative. 153 func (id ID) FormatTo(b []byte, f rune) { 154 if id == (ID{}) { 155 if f == 'v' || f == 'V' { 156 copy(b, "________________________________") 157 } else { 158 copy(b, "00000000000000000000000000000000") 159 } 160 return 161 } 162 163 const digitsx = "0123456789abcdef" 164 const digitsX = "0123456789ABCDEF" 165 166 dg := digitsx 167 if f == 'X' || f == 'V' { 168 dg = digitsX 169 } 170 171 m := len(b) 172 if 2*len(id) < m { 173 m = 2 * len(id) 174 } 175 176 ji := 0 177 for j := 0; j+1 < m; j += 2 { 178 b[j] = dg[id[ji]>>4] 179 b[j+1] = dg[id[ji]&0xf] 180 ji++ 181 } 182 183 if m&1 == 1 { 184 b[m-1] = dg[id[m>>1]>>4] 185 } 186 } 187 188 func (id ID) MarshalJSON() ([]byte, error) { 189 b := make([]byte, len(id)*2+2) 190 b[0] = '"' 191 b[len(b)-1] = '"' 192 193 id.FormatTo(b[1:], 'x') 194 195 return b, nil 196 } 197 198 func (id *ID) UnmarshalJSON(b []byte) error { 199 if len(b) < 4 { 200 return errors.New("bad id") 201 } 202 203 if b[0] != '"' || b[len(b)-1] != '"' { 204 return errors.New("bad id encoding") 205 } 206 207 x, err := IDFromStringAsBytes(b[1 : len(b)-1]) 208 if err != nil { 209 return err 210 } 211 212 *id = x 213 214 return nil 215 } 216 217 func MathRandID() (id ID) { 218 rnd.mu.Lock() 219 220 for id == (ID{}) { 221 _, _ = rnd.r.Read(id[:]) 222 } 223 224 rnd.mu.Unlock() 225 226 return 227 } 228 229 func RandIDFromReader(read func(p []byte) (int, error)) func() ID { 230 return func() (id ID) { 231 n, err := read(id[:]) 232 if err != nil { 233 panic(err) 234 } 235 if n != len(id) { 236 panic(n) 237 } 238 239 return id 240 } 241 } 242 243 /* will repeat at most after 2 ** (32 - 2) ids 244 func FastRandID() (id ID) { 245 *(*uint32)(unsafe.Pointer(&id[0])) = fastrand() 246 *(*uint32)(unsafe.Pointer(&id[4])) = fastrand() 247 *(*uint32)(unsafe.Pointer(&id[8])) = fastrand() 248 *(*uint32)(unsafe.Pointer(&id[12])) = fastrand() 249 return 250 } 251 */ 252 253 // UUID creates ID generation function. 254 // read is a random Read method. Function panics on Read error. 255 // read must be safe for concurrent use. 256 // 257 // It's got from github.com/google/uuid. 258 func UUID(read func(p []byte) (int, error)) func() ID { 259 return func() (uuid ID) { 260 n, err := read(uuid[:]) 261 if err != nil { 262 panic(err) 263 } 264 if n != len(uuid) { 265 panic(n) 266 } 267 268 uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 269 uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 270 271 return uuid 272 } 273 }