Skip to content

Commit 1902366

Browse files
committed
Improve error handling for timestamps that are out of bound, and update spec accordingly.
1 parent 11d43e9 commit 1902366

3 files changed

Lines changed: 43 additions & 14 deletions

File tree

README.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ From the community!
9292

9393
Below is the current specification of ULID as implemented in this repository.
9494

95-
*Note: the binary format has not been implemented.*
95+
*Note: the binary format has not been implemented in JavaScript as of yet.*
9696

9797
```
9898
01AN4Z07BY 79KA1307SR9X4MV3
@@ -117,14 +117,30 @@ Below is the current specification of ULID as implemented in this repository.
117117

118118
The left-most character must be sorted first, and the right-most character sorted last (lexical order). The default ASCII character set must be used. Within the same millisecond, sort order is not guaranteed
119119

120-
### Encoding
120+
### Canonical String Representation
121+
122+
```
123+
ttttttttttrrrrrrrrrrrrrrrr
124+
125+
where
126+
t is Timestamp (10 characters)
127+
r is Randomness (16 characters)
128+
```
129+
130+
#### Encoding
121131

122132
Crockford's Base32 is used as shown. This alphabet excludes the letters I, L, O, and U to avoid confusion and abuse.
123133

124134
```
125135
0123456789ABCDEFGHJKMNPQRSTVWXYZ
126136
```
127137

138+
#### Overflow Errors when Parsing Base32 Strings
139+
140+
Technically, a 26 character Base32 encoded string can contain 130 bits of information, whereas a ULID must only contain 128 bits. Therefore, the largest valid ULID encoded in Base32 is `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, which corresponds to an epoch time of `281474976710655`.
141+
142+
Any attempt to decode or encode a ULID larger than this should be rejected by all implementations, to prevent overflow bugs.
143+
128144
### Binary Layout and Byte Order
129145

130146
The components are encoded as 16 octets. Each component is encoded with the Most Significant Byte first (network byte order).
@@ -143,16 +159,6 @@ The components are encoded as 16 octets. Each component is encoded with the Most
143159
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
144160
```
145161

146-
### String Representation
147-
148-
```
149-
ttttttttttrrrrrrrrrrrrrrrr
150-
151-
where
152-
t is Timestamp
153-
r is Randomness
154-
```
155-
156162
## Prior Art
157163

158164
Partly inspired by:

index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ function factory(prng) {
4242
}
4343

4444
function decodeTime(id) {
45+
if (id.length !== TIME_LEN + RANDOM_LEN) {
46+
throw new Error("malformed ulid")
47+
}
4548
var binStr = ''
4649
for (var i = 0; i < 10; i++) {
4750
var dec = ENCODING.indexOf(id[i])
@@ -52,7 +55,11 @@ function factory(prng) {
5255
}
5356
binStr += bin
5457
}
55-
return parseInt(binStr, 2)
58+
var time = parseInt(binStr, 2)
59+
if (time > TIME_MAX) {
60+
throw new Error("malformed ulid, timestamp too large")
61+
}
62+
return time
5663
}
5764

5865
function ulid(seedTime) {

test.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('ulid', function() {
5959
})
6060

6161
})
62-
62+
6363
describe('decodeTime', function() {
6464

6565
it('should return correct timestamp', function() {
@@ -68,6 +68,22 @@ describe('ulid', function() {
6868
assert.strictEqual(timestamp, ulid.decodeTime(id))
6969
})
7070

71+
it('should accept the maximum allowed timestamp', function() {
72+
assert.strictEqual(281474976710655, ulid.decodeTime('7ZZZZZZZZZZZZZZZZZZZZZZZZZ'))
73+
})
74+
75+
describe('should reject', function() {
76+
77+
it('malformed strings of incorrect length', function() {
78+
assert.throws(() => ulid.decodeTime('FFFF'), Error)
79+
})
80+
81+
it('strings with timestamps that are too high', function() {
82+
assert.throws(() => ulid.decodeTime('80000000000000000000000000'), Error)
83+
})
84+
85+
})
86+
7187
})
7288

7389
describe('ulid', function() {

0 commit comments

Comments
 (0)