1 /++ 2 see also: 3 https://github.com/alizain/ulid (original ulid) 4 https://github.com/suyash/ulid (c++ implementation of ulid, ulid-d is based on this) 5 +/ 6 module ulid.ulid; 7 8 @safe: 9 10 /++ 11 ULID data type. 12 13 see: 14 https://github.com/alizain/ulid 15 +/ 16 struct ULID 17 { 18 /// data stores ulid elements 19 /// 0..6 bytes make up the time stamp 20 /// 6..16 bytes make up entropy (random) part 21 ubyte[16] data; 22 23 private void setTimePart() 24 { 25 import core.time : MonoTime; 26 27 setTimePart(MonoTime.currTime.ticks); 28 } 29 30 private void setTimePart(long time) @nogc 31 { 32 data[0] = cast(ubyte)(time >> 40); 33 data[1] = cast(ubyte)(time >> 32); 34 data[2] = cast(ubyte)(time >> 24); 35 data[3] = cast(ubyte)(time >> 16); 36 data[4] = cast(ubyte)(time >> 8); 37 data[5] = cast(ubyte)(time); 38 } 39 40 private void setEntropy() 41 { 42 import std.random : uniform; 43 44 foreach (idx; 6 .. 16) 45 data[idx] = uniform!ubyte; 46 } 47 48 private void setEntropy(ubyte function() @safe gen) 49 { 50 foreach (idx; 6 .. 16) 51 data[idx] = gen(); 52 } 53 54 private void setEntropy(ubyte delegate() @safe gen) 55 { 56 foreach (idx; 6 .. 16) 57 data[idx] = gen(); 58 } 59 60 /// allocation free string encoding 61 void encode(ref char[26] dst) const @nogc 62 { 63 static string Encoding = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; 64 65 // 10 byte timestamp 66 dst[0] = Encoding[(data[0] & 224) >> 5]; 67 dst[1] = Encoding[data[0] & 31]; 68 dst[2] = Encoding[(data[1] & 248) >> 3]; 69 dst[3] = Encoding[((data[1] & 7) << 2) | ((data[2] & 192) >> 6)]; 70 dst[4] = Encoding[(data[2] & 62) >> 1]; 71 dst[5] = Encoding[((data[2] & 1) << 4) | ((data[3] & 240) >> 4)]; 72 dst[6] = Encoding[((data[3] & 15) << 1) | ((data[4] & 128) >> 7)]; 73 dst[7] = Encoding[(data[4] & 124) >> 2]; 74 dst[8] = Encoding[((data[4] & 3) << 3) | ((data[5] & 224) >> 5)]; 75 dst[9] = Encoding[data[5] & 31]; 76 77 // 16 bytes of entropy 78 dst[10] = Encoding[(data[6] & 248) >> 3]; 79 dst[11] = Encoding[((data[6] & 7) << 2) | ((data[7] & 192) >> 6)]; 80 dst[12] = Encoding[(data[7] & 62) >> 1]; 81 dst[13] = Encoding[((data[7] & 1) << 4) | ((data[8] & 240) >> 4)]; 82 dst[14] = Encoding[((data[8] & 15) << 1) | ((data[9] & 128) >> 7)]; 83 dst[15] = Encoding[(data[9] & 124) >> 2]; 84 dst[16] = Encoding[((data[9] & 3) << 3) | ((data[10] & 224) >> 5)]; 85 dst[17] = Encoding[data[10] & 31]; 86 dst[18] = Encoding[(data[11] & 248) >> 3]; 87 dst[19] = Encoding[((data[11] & 7) << 2) | ((data[12] & 192) >> 6)]; 88 dst[20] = Encoding[(data[12] & 62) >> 1]; 89 dst[21] = Encoding[((data[12] & 1) << 4) | ((data[13] & 240) >> 4)]; 90 dst[22] = Encoding[((data[13] & 15) << 1) | ((data[14] & 128) >> 7)]; 91 dst[23] = Encoding[(data[14] & 124) >> 2]; 92 dst[24] = Encoding[((data[14] & 3) << 3) | ((data[15] & 224) >> 5)]; 93 dst[25] = Encoding[data[15] & 31]; 94 } 95 96 /// ditto, but allocates 97 string toString() const 98 { 99 char[26] chars; 100 encode(chars); 101 return cast(string) chars.idup; 102 } 103 104 /// creates ULID from a previously string encoded ULID 105 static ULID fromString(string str) @nogc 106 { 107 static const ubyte[256] dec = [ 108 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 109 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 110 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 111 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 112 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 113 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 114 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 115 0x14, 0x15, 0xFF, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, 116 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 117 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 118 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 119 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 120 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 121 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 122 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 123 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 124 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 125 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 126 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 127 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 128 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 129 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 130 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF 131 ]; 132 133 ULID ulid; 134 // timestamp 135 ulid.data[0] = cast(ubyte)((dec[cast(int) str[0]] << 5) | dec[cast(int) str[1]]); 136 ulid.data[1] = cast(ubyte)((dec[cast(int) str[2]] << 3) | (dec[cast(int) str[3]] >> 2)); 137 ulid.data[2] = cast(ubyte)((dec[cast(int) str[3]] << 6) | ( 138 dec[cast(int) str[4]] << 1) | (dec[cast(int) str[5]] >> 4)); 139 ulid.data[3] = cast(ubyte)((dec[cast(int) str[5]] << 4) | (dec[cast(int) str[6]] >> 1)); 140 ulid.data[4] = cast(ubyte)((dec[cast(int) str[6]] << 7) | ( 141 dec[cast(int) str[7]] << 2) | (dec[cast(int) str[8]] >> 3)); 142 ulid.data[5] = cast(ubyte)((dec[cast(int) str[8]] << 5) | dec[cast(int) str[9]]); 143 144 // entropy 145 ulid.data[6] = cast(ubyte)((dec[cast(int) str[10]] << 3) | (dec[cast(int) str[11]] >> 2)); 146 ulid.data[7] = cast(ubyte)((dec[cast(int) str[11]] << 6) | ( 147 dec[cast(int) str[12]] << 1) | (dec[cast(int) str[13]] >> 4)); 148 ulid.data[8] = cast(ubyte)((dec[cast(int) str[13]] << 4) | (dec[cast(int) str[14]] >> 1)); 149 ulid.data[9] = cast(ubyte)((dec[cast(int) str[14]] << 7) | ( 150 dec[cast(int) str[15]] << 2) | (dec[cast(int) str[16]] >> 3)); 151 ulid.data[10] = cast(ubyte)((dec[cast(int) str[16]] << 5) | dec[cast(int) str[17]]); 152 ulid.data[11] = cast(ubyte)((dec[cast(int) str[18]] << 3) | (dec[cast(int) str[19]] >> 2)); 153 ulid.data[12] = cast(ubyte)((dec[cast(int) str[19]] << 6) | ( 154 dec[cast(int) str[20]] << 1) | (dec[cast(int) str[21]] >> 4)); 155 ulid.data[13] = cast(ubyte)((dec[cast(int) str[21]] << 4) | (dec[cast(int) str[22]] >> 1)); 156 ulid.data[14] = cast(ubyte)((dec[cast(int) str[22]] << 7) | ( 157 dec[cast(int) str[23]] << 2) | (dec[cast(int) str[24]] >> 3)); 158 ulid.data[15] = cast(ubyte)((dec[cast(int) str[24]] << 5) | dec[cast(int) str[25]]); 159 return ulid; 160 } 161 162 /// 163 unittest 164 { 165 import fluent.asserts; 166 167 static ubyte gen() 168 { 169 return 4; 170 } 171 172 ULID decoded = ULID.fromString("01ARYZ6S410G2081040G208104"); 173 ULID.generate(1469918176385, &gen).should.equal(decoded); 174 } 175 176 /// uses std.random.uniform and core.time.MonoTime.currTime 177 static ULID generate() 178 { 179 ULID ulid; 180 ulid.setTimePart(); 181 ulid.setEntropy(); 182 return ulid; 183 } 184 185 /// 186 unittest 187 { 188 import fluent.asserts; 189 190 auto ulid1 = ULID.generate(); 191 auto ulid2 = ULID.generate(); 192 ulid1.should.not.equal(ulid2); 193 194 ulid1.toString[0 .. 10].should.not.equal(ulid2.toString[0 .. 10]); 195 ulid1.data[0 .. 6].should.not.equal(ulid2.data[0 .. 6]); 196 } 197 198 /// uses std.random.uniform 199 static ULID generate(ulong seed) 200 { 201 ULID ulid; 202 ulid.setTimePart(seed); 203 ulid.setEntropy(); 204 return ulid; 205 } 206 207 /// 208 unittest 209 { 210 import fluent.asserts; 211 212 auto ulid1 = ULID.generate(1469918176385); 213 auto ulid2 = ULID.generate(1469918176385); 214 ulid1.toString[0 .. 10].should.equal("01ARYZ6S41"); 215 ulid2.toString[0 .. 10].should.equal(ulid1.toString[0 .. 10]); 216 ulid1.should.not.equal(ulid2); 217 } 218 219 /// uses callback to set entropy 220 static ULID generate(ulong seed, ubyte function() @safe gen) 221 { 222 ULID ulid; 223 ulid.setTimePart(seed); 224 ulid.setEntropy(gen); 225 return ulid; 226 } 227 228 /// 229 unittest 230 { 231 import fluent.asserts; 232 233 static ubyte gen() 234 { 235 return 4; 236 } 237 238 //should return expected encoded time component result 239 ULID.generate(1469918176385, &gen).toString.should.equal("01ARYZ6S410G2081040G208104"); 240 } 241 242 /// ditto 243 static ULID generate(ulong seed, ubyte delegate() @safe gen) 244 { 245 ULID ulid; 246 ulid.setTimePart(seed); 247 ulid.setEntropy(gen); 248 return ulid; 249 } 250 251 /// 252 unittest 253 { 254 import fluent.asserts; 255 256 ubyte gen() 257 { 258 return 4; 259 } 260 261 //should return expected encoded time component result 262 ULID.generate(1469918176385, &gen).toString.should.equal("01ARYZ6S410G2081040G208104"); 263 } 264 } 265 266 /// 267 unittest 268 { 269 import fluent.asserts; 270 271 ULID.generate().toString.length.should.equal(26); 272 273 //should return expected encoded time component result 274 ULID.generate(1469918176385).toString[0 .. 10].should.equal("01ARYZ6S41"); 275 }