/
uuid.dart
307 lines (257 loc) · 9.9 KB
/
uuid.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
library Uuid;
import 'uuid_util.dart';
import 'package:crypto/crypto.dart';
import 'package:convert/convert.dart' as convert;
/**
* uuid for Dart
*
* Copyright (c) 2015 Yulian Kuncheff
*
* Released under MIT License.
*
* Based on node-uuid by Robert Kieffer.
*/
class Uuid {
// RFC4122 provided namespaces for v3 and v5 namespace based UUIDs
static const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
static const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
static const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
static const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
static const NAMESPACE_NIL = '00000000-0000-0000-0000-000000000000';
var _seedBytes, _nodeId, _clockSeq, _lastMSecs = 0, _lastNSecs = 0;
List<String> _byteToHex;
Map<String, int> _hexToByte;
Uuid() {
_byteToHex = new List<String>(256);
_hexToByte = new Map<String, int>();
// Easy number <-> hex conversion
for (var i = 0; i < 256; i++) {
var hex = new List<int>();
hex.add(i);
_byteToHex[i] = convert.hex.encode(hex);
_hexToByte[_byteToHex[i]] = i;
}
// Sets initial seedBytes, node, and clock seq based on MathRNG.
_seedBytes = UuidUtil.mathRNG();
// Per 4.5, create a 48-bit node id (47 random bits + multicast bit = 1)
_nodeId = [
_seedBytes[0] | 0x01,
_seedBytes[1],
_seedBytes[2],
_seedBytes[3],
_seedBytes[4],
_seedBytes[5]
];
// Per 4.2.2, randomize (14 bit) clockseq
_clockSeq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3ffff;
}
/**
* Parses the provided [uuid] into a list of byte values.
* Can optionally be provided a [buffer] to write into and
* a positional [offset] for where to start inputting into the buffer.
*/
List<int> parse(String uuid, {List<int> buffer, int offset: 0}) {
var i = offset, ii = 0;
// Create a 16 item buffer if one hasn't been provided.
buffer = (buffer != null) ? buffer : new List<int>(16);
// Convert to lowercase and replace all hex with bytes then
// string.replaceAll() does a lot of work that I don't need, and a manual
// regex gives me more control.
final RegExp regex = new RegExp('[0-9a-f]{2}');
for (Match match in regex.allMatches(uuid.toLowerCase())) {
if (ii < 16) {
var hex = uuid.toLowerCase().substring(match.start, match.end);
buffer[i + ii++] = _hexToByte[hex];
}
}
// Zero out any left over bytes if the string was too short.
while (ii < 16) {
buffer[i + ii++] = 0;
}
return buffer;
}
/**
* Unparses a [buffer] of bytes and outputs a proper UUID string.
* An optional [offset] is allowed if you want to start at a different point
* in the buffer.
*/
String unparse(List buffer, {int offset: 0}) {
var i = offset;
return '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}'
'${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-'
'${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-'
'${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-'
'${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-'
'${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}'
'${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}'
'${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}';
}
/**
* v1() Generates a time-based version 1 UUID
*
* By default it will generate a string based off current time, and will
* return a string.
*
* If an optional [buffer] list is provided, it will put the byte data into
* that buffer and return a buffer.
*
* Optionally an [offset] can be provided with a start position in the buffer.
*
* The first argument is an options map that takes various configuration
* options detailed in the readme.
*
* http://tools.ietf.org/html/rfc4122.html#section-4.2.2
*/
v1({Map options: null, List buffer: null, int offset: 0}) {
var i = offset;
var buf = (buffer != null) ? buffer : new List(16);
options = (options != null) ? options : new Map();
var clockSeq =
(options['clockSeq'] != null) ? options['clockSeq'] : _clockSeq;
// UUID timestamps are 100 nano-second units since the Gregorian epoch,
// (1582-10-15 00:00). Time is handled internally as 'msecs' (integer
// milliseconds) and 'nsecs' (100-nanoseconds offset from msecs) since unix
// epoch, 1970-01-01 00:00.
var mSecs = (options['mSecs'] != null)
? options['mSecs']
: (new DateTime.now()).millisecondsSinceEpoch;
// Per 4.2.1.2, use count of uuid's generated during the current clock
// cycle to simulate higher resolution clock
var nSecs = (options['nSecs'] != null) ? options['nSecs'] : _lastNSecs + 1;
// Time since last uuid creation (in msecs)
var dt = (mSecs - _lastMSecs) + (nSecs - _lastNSecs) / 10000;
// Per 4.2.1.2, Bump clockseq on clock regression
if (dt < 0 && options['clockSeq'] == null) {
clockSeq = clockSeq + 1 & 0x3fff;
}
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
// time interval
if ((dt < 0 || mSecs > _lastMSecs) && options['nSecs'] == null) {
nSecs = 0;
}
// Per 4.2.1.2 Throw error if too many uuids are requested
if (nSecs >= 10000) {
throw new Exception('uuid.v1(): Can\'t create more than 10M uuids/sec');
}
_lastMSecs = mSecs;
_lastNSecs = nSecs;
_clockSeq = clockSeq;
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
mSecs += 12219292800000;
// time Low
var tl = ((mSecs & 0xfffffff) * 10000 + nSecs) % 0x100000000;
buf[i++] = tl >> 24 & 0xff;
buf[i++] = tl >> 16 & 0xff;
buf[i++] = tl >> 8 & 0xff;
buf[i++] = tl & 0xff;
// time mid
var tmh = (mSecs ~/ 0x100000000 * 10000) & 0xfffffff;
buf[i++] = tmh >> 8 & 0xff;
buf[i++] = tmh & 0xff;
// time high and version
buf[i++] = tmh >> 24 & 0xf | 0x10; // include version
buf[i++] = tmh >> 16 & 0xff;
// clockSeq high and reserved (Per 4.2.2 - include variant)
buf[i++] = clockSeq >> 8 | 0x80;
// clockSeq low
buf[i++] = clockSeq & 0xff;
// node
var node = (options['node'] != null) ? options['node'] : _nodeId;
for (var n = 0; n < 6; n++) {
buf[i + n] = node[n];
}
return (buffer != null) ? buffer : unparse(buf);
}
/**
* v4() Generates a time-based version 4 UUID
*
* By default it will generate a string based AES-based RNG, and will return
* a string.
*
* If an optional [buffer] list is provided, it will put the byte data into
* that buffer and return a buffer.
*
* Optionally an [offset] can be provided with a start position in the buffer.
*
* The first argument is an options map that takes various configuration
* options detailed in the readme.
*
* http://tools.ietf.org/html/rfc4122.html#section-4.4
*/
v4({Map<String, dynamic> options: null, List buffer: null, int offset: 0}) {
var i = offset;
options = (options != null) ? options : new Map<String, dynamic>();
// Use the built-in RNG or a custom provided RNG
var positionalArgs =
(options['positionalArgs'] != null) ? options['positionalArgs'] : [];
var namedArgs = (options['namedArgs'] != null)
? options['namedArgs'] as Map<Symbol, dynamic>
: const <Symbol, dynamic>{};
var rng = (options['rng'] != null)
? Function.apply(options['rng'], positionalArgs, namedArgs)
: UuidUtil.mathRNG();
// Use provided values over RNG
var rnds = (options['random'] != null) ? options['random'] : rng;
// per 4.4, set bits for version and clockSeq high and reserved
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
// Copy the bytes to the buffer if one is provided.
if (buffer != null) {
for (var j = 0; j < 16; j++) {
buffer[i + j] = rnds[j];
}
}
return (buffer != null) ? buffer : unparse(rnds);
}
/**
* v5() Generates a namspace & name-based version 5 UUID
*
* By default it will generate a string based on a provided uuid namespace and
* name, and will return a string.
*
* If an optional [buffer] list is provided, it will put the byte data into
* that buffer and return a buffer.
*
* Optionally an [offset] can be provided with a start position in the buffer.
*
* The first argument is an options map that takes various configuration
* options detailed in the readme.
*
* http://tools.ietf.org/html/rfc4122.html#section-4.4
*/
v5(String namespace, String name,
{Map options: null, List buffer: null, int offset: 0}) {
var i = offset;
options = (options != null) ? options : new Map();
// Check if user wants a random namespace generated by v4() or a NIL namespace.
var useRandom = (options['randomNamespace'] != null)
? options['randomNamespace']
: true;
// If useRandom is true, generate UUIDv4, else use NIL
var blankNS = useRandom ? v4() : NAMESPACE_NIL;
// Use provided namespace, or use whatever is decided by options.
namespace = (namespace != null) ? namespace : blankNS;
// Use provided name,
name = (name != null) ? name : '';
// Convert namespace UUID to Byte List
var bytes = parse(namespace);
// Convert name to a list of bytes
var nameBytes = new List<int>();
for (var singleChar in name.codeUnits) {
nameBytes.add(singleChar);
}
// Generate SHA1 using namespace concatenated with name
List hashBytes =
sha1.convert(new List.from(bytes)..addAll(nameBytes)).bytes;
// per 4.4, set bits for version and clockSeq high and reserved
hashBytes[6] = (hashBytes[6] & 0x0f) | 0x50;
hashBytes[8] = (hashBytes[8] & 0x3f) | 0x80;
// Copy the bytes to the buffer if one is provided.
if (buffer != null) {
for (var j = 0; j < 16; j++) {
buffer[i + j] = hashBytes[j];
}
}
return (buffer != null) ? buffer : unparse(hashBytes);
}
}