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 }