encoded_id 1.0.0.rc4 → 1.0.0.rc5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +9 -0
- data/.devcontainer/compose.yml +8 -0
- data/.devcontainer/devcontainer.json +8 -0
- data/.standard.yml +2 -0
- data/CHANGELOG.md +9 -2
- data/Gemfile +17 -5
- data/LICENSE.txt +1 -1
- data/README.md +50 -3
- data/Rakefile +8 -2
- data/ext/encoded_id/extconf.rb +3 -0
- data/ext/encoded_id/extension.c +123 -0
- data/ext/encoded_id/hashids.c +939 -0
- data/ext/encoded_id/hashids.h +139 -0
- data/lib/encoded_id/alphabet.rb +4 -0
- data/lib/encoded_id/hash_id.rb +227 -0
- data/lib/encoded_id/hash_id_consistent_shuffle.rb +27 -0
- data/lib/encoded_id/hash_id_salt.rb +15 -0
- data/lib/encoded_id/ordinal_alphabet_separator_guards.rb +90 -0
- data/lib/encoded_id/reversible_id.rb +3 -5
- data/lib/encoded_id/version.rb +1 -1
- data/lib/encoded_id.rb +8 -0
- data/sig/encoded_id.rbs +75 -3
- metadata +19 -24
- data/sig/hash_ids.rbs +0 -70
@@ -0,0 +1,939 @@
|
|
1
|
+
// https://github.com/tzvetkoff/hashids.c
|
2
|
+
/*
|
3
|
+
Copyright (C) 2014 Latchezar Tzvetkoff
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
10
|
+
*/
|
11
|
+
|
12
|
+
#include <stdio.h>
|
13
|
+
#include <string.h>
|
14
|
+
#include <stdlib.h>
|
15
|
+
#include <stdarg.h>
|
16
|
+
#include <math.h>
|
17
|
+
|
18
|
+
#include "hashids.h"
|
19
|
+
|
20
|
+
/* branch prediction hinting */
|
21
|
+
#ifndef __has_builtin
|
22
|
+
# define __has_builtin(x) (0)
|
23
|
+
#endif
|
24
|
+
#if defined(__builtin_expect) || __has_builtin(__builtin_expect)
|
25
|
+
# define HASHIDS_LIKELY(x) (__builtin_expect(!!(x), 1))
|
26
|
+
# define HASHIDS_UNLIKELY(x) (__builtin_expect(!!(x), 0))
|
27
|
+
#else
|
28
|
+
# define HASHIDS_LIKELY(x) (x)
|
29
|
+
# define HASHIDS_UNLIKELY(x) (x)
|
30
|
+
#endif
|
31
|
+
|
32
|
+
/* fallthrough warning suppression */
|
33
|
+
#ifndef __has_feature
|
34
|
+
# define __has_feature(x) (0)
|
35
|
+
#endif
|
36
|
+
#if __has_feature(fallthrough)
|
37
|
+
# define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough))
|
38
|
+
#else
|
39
|
+
# define ATTRIBUTE_FALLTHROUGH
|
40
|
+
#endif
|
41
|
+
|
42
|
+
/* thread-local storage */
|
43
|
+
#ifndef TLS
|
44
|
+
#define TLS
|
45
|
+
#endif
|
46
|
+
|
47
|
+
/* thread-safe hashids_errno indirection */
|
48
|
+
TLS int __hashids_errno_val;
|
49
|
+
int *
|
50
|
+
__hashids_errno_addr()
|
51
|
+
{
|
52
|
+
return &__hashids_errno_val;
|
53
|
+
}
|
54
|
+
|
55
|
+
/* default alloc() implementation */
|
56
|
+
static inline void *
|
57
|
+
hashids_alloc_f(size_t size)
|
58
|
+
{
|
59
|
+
return calloc(size, 1);
|
60
|
+
}
|
61
|
+
|
62
|
+
/* default free() implementation */
|
63
|
+
static inline void
|
64
|
+
hashids_free_f(void *ptr)
|
65
|
+
{
|
66
|
+
free(ptr);
|
67
|
+
}
|
68
|
+
|
69
|
+
void *(*_hashids_alloc)(size_t size) = hashids_alloc_f;
|
70
|
+
void (*_hashids_free)(void *ptr) = hashids_free_f;
|
71
|
+
|
72
|
+
/* fast ceil(x / y) for size_t arguments */
|
73
|
+
static inline size_t
|
74
|
+
hashids_div_ceil_size_t(size_t x, size_t y)
|
75
|
+
{
|
76
|
+
return x / y + !!(x % y);
|
77
|
+
}
|
78
|
+
|
79
|
+
/* fast ceil(x / y) for unsigned short arguments */
|
80
|
+
static inline unsigned short
|
81
|
+
hashids_div_ceil_unsigned_short(unsigned short x, unsigned short y) {
|
82
|
+
return x / y + !!(x % y);
|
83
|
+
}
|
84
|
+
|
85
|
+
/* fast log2(x) for unsigned long long */
|
86
|
+
const unsigned short hashids_log2_64_tab[64] = {
|
87
|
+
63, 0, 58, 1, 59, 47, 53, 2,
|
88
|
+
60, 39, 48, 27, 54, 33, 42, 3,
|
89
|
+
61, 51, 37, 40, 49, 18, 28, 20,
|
90
|
+
55, 30, 34, 11, 43, 14, 22, 4,
|
91
|
+
62, 57, 46, 52, 38, 26, 32, 41,
|
92
|
+
50, 36, 17, 19, 29, 10, 13, 21,
|
93
|
+
56, 45, 25, 31, 35, 16, 9, 12,
|
94
|
+
44, 24, 15, 8, 23, 7, 6, 5
|
95
|
+
};
|
96
|
+
|
97
|
+
static inline unsigned short
|
98
|
+
hashids_log2_64(unsigned long long x)
|
99
|
+
{
|
100
|
+
x |= x >> 1;
|
101
|
+
x |= x >> 2;
|
102
|
+
x |= x >> 4;
|
103
|
+
x |= x >> 8;
|
104
|
+
x |= x >> 16;
|
105
|
+
x |= x >> 32;
|
106
|
+
|
107
|
+
/* pure evil : ieee abuse */
|
108
|
+
return hashids_log2_64_tab[
|
109
|
+
((unsigned long long)((x - (x >> 1)) * 0x07EDD5E59A4E28C2)) >> 58];
|
110
|
+
}
|
111
|
+
|
112
|
+
/* shuffle loop step */
|
113
|
+
#define hashids_shuffle_step(iter) \
|
114
|
+
if (i == 0) { break; } \
|
115
|
+
if (v == salt_length) { v = 0; } \
|
116
|
+
p += salt[v]; j = (salt[v] + v + p) % (iter); \
|
117
|
+
temp = str[(iter)]; str[(iter)] = str[j]; str[j] = temp; \
|
118
|
+
--i; ++v;
|
119
|
+
|
120
|
+
/* consistent shuffle */
|
121
|
+
void
|
122
|
+
hashids_shuffle(char *str, size_t str_length, char *salt, size_t salt_length)
|
123
|
+
{
|
124
|
+
size_t i, j, v, p;
|
125
|
+
char temp;
|
126
|
+
|
127
|
+
/* meh, meh */
|
128
|
+
if (!salt_length) {
|
129
|
+
return;
|
130
|
+
}
|
131
|
+
|
132
|
+
/* pure evil : loop unroll */
|
133
|
+
for (i = str_length - 1, v = 0, p = 0; i > 0; /* empty */) {
|
134
|
+
switch (i % 32) {
|
135
|
+
case 31: hashids_shuffle_step(i);
|
136
|
+
/* fall through */
|
137
|
+
case 30: hashids_shuffle_step(i);
|
138
|
+
/* fall through */
|
139
|
+
case 29: hashids_shuffle_step(i);
|
140
|
+
/* fall through */
|
141
|
+
case 28: hashids_shuffle_step(i);
|
142
|
+
/* fall through */
|
143
|
+
case 27: hashids_shuffle_step(i);
|
144
|
+
/* fall through */
|
145
|
+
case 26: hashids_shuffle_step(i);
|
146
|
+
/* fall through */
|
147
|
+
case 25: hashids_shuffle_step(i);
|
148
|
+
/* fall through */
|
149
|
+
case 24: hashids_shuffle_step(i);
|
150
|
+
/* fall through */
|
151
|
+
case 23: hashids_shuffle_step(i);
|
152
|
+
/* fall through */
|
153
|
+
case 22: hashids_shuffle_step(i);
|
154
|
+
/* fall through */
|
155
|
+
case 21: hashids_shuffle_step(i);
|
156
|
+
/* fall through */
|
157
|
+
case 20: hashids_shuffle_step(i);
|
158
|
+
/* fall through */
|
159
|
+
case 19: hashids_shuffle_step(i);
|
160
|
+
/* fall through */
|
161
|
+
case 18: hashids_shuffle_step(i);
|
162
|
+
/* fall through */
|
163
|
+
case 17: hashids_shuffle_step(i);
|
164
|
+
/* fall through */
|
165
|
+
case 16: hashids_shuffle_step(i);
|
166
|
+
/* fall through */
|
167
|
+
case 15: hashids_shuffle_step(i);
|
168
|
+
/* fall through */
|
169
|
+
case 14: hashids_shuffle_step(i);
|
170
|
+
/* fall through */
|
171
|
+
case 13: hashids_shuffle_step(i);
|
172
|
+
/* fall through */
|
173
|
+
case 12: hashids_shuffle_step(i);
|
174
|
+
/* fall through */
|
175
|
+
case 11: hashids_shuffle_step(i);
|
176
|
+
/* fall through */
|
177
|
+
case 10: hashids_shuffle_step(i);
|
178
|
+
/* fall through */
|
179
|
+
case 9: hashids_shuffle_step(i);
|
180
|
+
/* fall through */
|
181
|
+
case 8: hashids_shuffle_step(i);
|
182
|
+
/* fall through */
|
183
|
+
case 7: hashids_shuffle_step(i);
|
184
|
+
/* fall through */
|
185
|
+
case 6: hashids_shuffle_step(i);
|
186
|
+
/* fall through */
|
187
|
+
case 5: hashids_shuffle_step(i);
|
188
|
+
/* fall through */
|
189
|
+
case 4: hashids_shuffle_step(i);
|
190
|
+
/* fall through */
|
191
|
+
case 3: hashids_shuffle_step(i);
|
192
|
+
/* fall through */
|
193
|
+
case 2: hashids_shuffle_step(i);
|
194
|
+
/* fall through */
|
195
|
+
case 1: hashids_shuffle_step(i);
|
196
|
+
/* fall through */
|
197
|
+
case 0: hashids_shuffle_step(i);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
/* "destructor" */
|
203
|
+
void
|
204
|
+
hashids_free(hashids_t *hashids)
|
205
|
+
{
|
206
|
+
if (hashids) {
|
207
|
+
if (hashids->alphabet) {
|
208
|
+
_hashids_free(hashids->alphabet);
|
209
|
+
}
|
210
|
+
if (hashids->alphabet_copy_1) {
|
211
|
+
_hashids_free(hashids->alphabet_copy_1);
|
212
|
+
}
|
213
|
+
if (hashids->alphabet_copy_2) {
|
214
|
+
_hashids_free(hashids->alphabet_copy_2);
|
215
|
+
}
|
216
|
+
if (hashids->salt) {
|
217
|
+
_hashids_free(hashids->salt);
|
218
|
+
}
|
219
|
+
if (hashids->separators) {
|
220
|
+
_hashids_free(hashids->separators);
|
221
|
+
}
|
222
|
+
if (hashids->guards) {
|
223
|
+
_hashids_free(hashids->guards);
|
224
|
+
}
|
225
|
+
|
226
|
+
_hashids_free(hashids);
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
/* common init */
|
231
|
+
hashids_t *
|
232
|
+
hashids_init3(const char *salt, size_t min_hash_length, const char *alphabet)
|
233
|
+
{
|
234
|
+
hashids_t *result;
|
235
|
+
size_t i, j, len;
|
236
|
+
char ch, *p;
|
237
|
+
|
238
|
+
hashids_errno = HASHIDS_ERROR_OK;
|
239
|
+
|
240
|
+
/* allocate the structure */
|
241
|
+
result = (hashids_t *)_hashids_alloc(sizeof(hashids_t));
|
242
|
+
if (HASHIDS_UNLIKELY(!result)) {
|
243
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
244
|
+
return NULL;
|
245
|
+
}
|
246
|
+
|
247
|
+
/* allocate enough space for the alphabet */
|
248
|
+
len = strlen(alphabet) + 1;
|
249
|
+
result->alphabet = (char *)_hashids_alloc(len);
|
250
|
+
|
251
|
+
/* extract only the unique characters */
|
252
|
+
result->alphabet[0] = '\0';
|
253
|
+
for (i = 0, j = 0; i < len; ++i) {
|
254
|
+
ch = alphabet[i];
|
255
|
+
if (!strchr(result->alphabet, ch)) {
|
256
|
+
result->alphabet[j++] = ch;
|
257
|
+
}
|
258
|
+
}
|
259
|
+
result->alphabet[j] = '\0';
|
260
|
+
|
261
|
+
/* store alphabet length */
|
262
|
+
result->alphabet_length = j;
|
263
|
+
|
264
|
+
/* check length and whitespace */
|
265
|
+
if (result->alphabet_length < HASHIDS_MIN_ALPHABET_LENGTH) {
|
266
|
+
hashids_free(result);
|
267
|
+
hashids_errno = HASHIDS_ERROR_ALPHABET_LENGTH;
|
268
|
+
return NULL;
|
269
|
+
}
|
270
|
+
if (strchr(result->alphabet, 0x20) || strchr(result->alphabet, 0x09)) {
|
271
|
+
hashids_free(result);
|
272
|
+
hashids_errno = HASHIDS_ERROR_ALPHABET_SPACE;
|
273
|
+
return NULL;
|
274
|
+
}
|
275
|
+
|
276
|
+
/* copy salt */
|
277
|
+
result->salt_length = salt ? strlen(salt) : 0;
|
278
|
+
result->salt = (char *)_hashids_alloc(result->salt_length + 1);
|
279
|
+
if (HASHIDS_UNLIKELY(!result->salt)) {
|
280
|
+
hashids_free(result);
|
281
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
282
|
+
return NULL;
|
283
|
+
}
|
284
|
+
strncpy(result->salt, salt, result->salt_length);
|
285
|
+
|
286
|
+
/* allocate enough space for separators */
|
287
|
+
len = strlen(HASHIDS_DEFAULT_SEPARATORS);
|
288
|
+
j = (size_t)
|
289
|
+
(ceil((float)result->alphabet_length / HASHIDS_SEPARATOR_DIVISOR) + 1);
|
290
|
+
if (j < len + 1) {
|
291
|
+
j = len + 1;
|
292
|
+
}
|
293
|
+
|
294
|
+
result->separators = (char *)_hashids_alloc(j);
|
295
|
+
if (HASHIDS_UNLIKELY(!result->separators)) {
|
296
|
+
hashids_free(result);
|
297
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
298
|
+
return NULL;
|
299
|
+
}
|
300
|
+
|
301
|
+
/* take default separators out of the alphabet */
|
302
|
+
for (i = 0, j = 0; i < strlen(HASHIDS_DEFAULT_SEPARATORS); ++i) {
|
303
|
+
ch = HASHIDS_DEFAULT_SEPARATORS[i];
|
304
|
+
|
305
|
+
/* check if separator is actually in the used alphabet */
|
306
|
+
if ((p = strchr(result->alphabet, ch))) {
|
307
|
+
result->separators[j++] = ch;
|
308
|
+
|
309
|
+
/* remove that separator */
|
310
|
+
memmove(p, p + 1,
|
311
|
+
strlen(result->alphabet) - (p - result->alphabet));
|
312
|
+
}
|
313
|
+
}
|
314
|
+
|
315
|
+
/* store separators length */
|
316
|
+
result->separators_count = j;
|
317
|
+
|
318
|
+
/* subtract separators count from alphabet length */
|
319
|
+
result->alphabet_length -= result->separators_count;
|
320
|
+
|
321
|
+
/* shuffle the separators */
|
322
|
+
if (result->separators_count) {
|
323
|
+
hashids_shuffle(result->separators, result->separators_count,
|
324
|
+
result->salt, result->salt_length);
|
325
|
+
}
|
326
|
+
|
327
|
+
/* check if we have any/enough separators */
|
328
|
+
if (!result->separators_count
|
329
|
+
|| (((float)result->alphabet_length / (float)result->separators_count)
|
330
|
+
> HASHIDS_SEPARATOR_DIVISOR)) {
|
331
|
+
size_t separators_count = (size_t)ceil(
|
332
|
+
(float)result->alphabet_length / HASHIDS_SEPARATOR_DIVISOR);
|
333
|
+
|
334
|
+
if (separators_count == 1) {
|
335
|
+
separators_count = 2;
|
336
|
+
}
|
337
|
+
|
338
|
+
if (separators_count > result->separators_count) {
|
339
|
+
/* we need more separators - get some from alphabet */
|
340
|
+
size_t diff = separators_count - result->separators_count;
|
341
|
+
strncat(result->separators, result->alphabet, diff);
|
342
|
+
memmove(result->alphabet, result->alphabet + diff,
|
343
|
+
result->alphabet_length - diff + 1);
|
344
|
+
|
345
|
+
result->separators_count += diff;
|
346
|
+
result->alphabet_length -= diff;
|
347
|
+
} else {
|
348
|
+
/* we have more than enough - truncate */
|
349
|
+
result->separators[separators_count] = '\0';
|
350
|
+
result->separators_count = separators_count;
|
351
|
+
}
|
352
|
+
}
|
353
|
+
|
354
|
+
/* shuffle alphabet */
|
355
|
+
hashids_shuffle(result->alphabet, result->alphabet_length,
|
356
|
+
result->salt, result->salt_length);
|
357
|
+
|
358
|
+
/* allocate guards */
|
359
|
+
result->guards_count = hashids_div_ceil_size_t(result->alphabet_length,
|
360
|
+
HASHIDS_GUARD_DIVISOR);
|
361
|
+
result->guards = (char *)_hashids_alloc(result->guards_count + 1);
|
362
|
+
if (HASHIDS_UNLIKELY(!result->guards)) {
|
363
|
+
hashids_free(result);
|
364
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
365
|
+
return NULL;
|
366
|
+
}
|
367
|
+
|
368
|
+
if (HASHIDS_UNLIKELY(result->alphabet_length < 3)) {
|
369
|
+
/* take some from separators */
|
370
|
+
strncpy(result->guards, result->separators, result->guards_count);
|
371
|
+
memmove(result->separators, result->separators + result->guards_count,
|
372
|
+
result->separators_count - result->guards_count + 1);
|
373
|
+
|
374
|
+
result->separators_count -= result->guards_count;
|
375
|
+
} else {
|
376
|
+
/* take them from alphabet */
|
377
|
+
strncpy(result->guards, result->alphabet, result->guards_count);
|
378
|
+
memmove(result->alphabet, result->alphabet + result->guards_count,
|
379
|
+
result->alphabet_length - result->guards_count + 1);
|
380
|
+
|
381
|
+
result->alphabet_length -= result->guards_count;
|
382
|
+
}
|
383
|
+
|
384
|
+
/* allocate enough space for the alphabet copies */
|
385
|
+
result->alphabet_copy_1 = (char *)_hashids_alloc(result->alphabet_length +
|
386
|
+
1);
|
387
|
+
result->alphabet_copy_2 = (char *)_hashids_alloc(result->alphabet_length +
|
388
|
+
1);
|
389
|
+
if (HASHIDS_UNLIKELY(!result->alphabet || !result->alphabet_copy_1
|
390
|
+
|| !result->alphabet_copy_2)) {
|
391
|
+
hashids_free(result);
|
392
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
393
|
+
return NULL;
|
394
|
+
}
|
395
|
+
|
396
|
+
/* set min hash length */
|
397
|
+
result->min_hash_length = min_hash_length;
|
398
|
+
|
399
|
+
/* return result happily */
|
400
|
+
return result;
|
401
|
+
}
|
402
|
+
|
403
|
+
/* init with salt and minimum hash length */
|
404
|
+
hashids_t *
|
405
|
+
hashids_init2(const char *salt, size_t min_hash_length)
|
406
|
+
{
|
407
|
+
return hashids_init3(salt, min_hash_length, HASHIDS_DEFAULT_ALPHABET);
|
408
|
+
}
|
409
|
+
|
410
|
+
/* init with hash only */
|
411
|
+
hashids_t *
|
412
|
+
hashids_init(const char *salt)
|
413
|
+
{
|
414
|
+
return hashids_init2(salt, HASHIDS_DEFAULT_MIN_HASH_LENGTH);
|
415
|
+
}
|
416
|
+
|
417
|
+
/* estimate buffer size (generic) */
|
418
|
+
size_t
|
419
|
+
hashids_estimate_encoded_size(hashids_t *hashids,
|
420
|
+
size_t numbers_count, unsigned long long *numbers)
|
421
|
+
{
|
422
|
+
size_t i, result_len;
|
423
|
+
|
424
|
+
for (i = 0, result_len = 1; i < numbers_count; ++i) {
|
425
|
+
if (numbers[i] == 0) {
|
426
|
+
result_len += 2;
|
427
|
+
} else if (numbers[i] == 0xFFFFFFFFFFFFFFFFull) {
|
428
|
+
result_len += hashids_div_ceil_unsigned_short(
|
429
|
+
hashids_log2_64(numbers[i]),
|
430
|
+
hashids_log2_64(hashids->alphabet_length)) - 1;
|
431
|
+
} else {
|
432
|
+
result_len += hashids_div_ceil_unsigned_short(
|
433
|
+
hashids_log2_64(numbers[i] + 1),
|
434
|
+
hashids_log2_64(hashids->alphabet_length));
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
if (numbers_count > 1) {
|
439
|
+
result_len += numbers_count - 1;
|
440
|
+
}
|
441
|
+
|
442
|
+
if (result_len < hashids->min_hash_length) {
|
443
|
+
result_len = hashids->min_hash_length;
|
444
|
+
}
|
445
|
+
|
446
|
+
return result_len + 2 /* fast log2 & ceil sometimes undershoot by 1 */;
|
447
|
+
}
|
448
|
+
|
449
|
+
/* estimate buffer size (variadic) */
|
450
|
+
size_t
|
451
|
+
hashids_estimate_encoded_size_v(hashids_t *hashids,
|
452
|
+
size_t numbers_count, ...)
|
453
|
+
{
|
454
|
+
size_t i, result;
|
455
|
+
unsigned long long *numbers;
|
456
|
+
va_list ap;
|
457
|
+
|
458
|
+
numbers = (unsigned long long *)_hashids_alloc(numbers_count *
|
459
|
+
sizeof(unsigned long long));
|
460
|
+
|
461
|
+
if (HASHIDS_UNLIKELY(!numbers)) {
|
462
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
463
|
+
return 0;
|
464
|
+
}
|
465
|
+
|
466
|
+
va_start(ap, numbers_count);
|
467
|
+
for (i = 0; i < numbers_count; ++i) {
|
468
|
+
numbers[i] = va_arg(ap, unsigned long long);
|
469
|
+
}
|
470
|
+
va_end(ap);
|
471
|
+
|
472
|
+
result = hashids_estimate_encoded_size(hashids, numbers_count, numbers);
|
473
|
+
_hashids_free(numbers);
|
474
|
+
|
475
|
+
return result;
|
476
|
+
}
|
477
|
+
|
478
|
+
/* encode many (generic) */
|
479
|
+
size_t
|
480
|
+
hashids_encode(hashids_t *hashids, char *buffer,
|
481
|
+
size_t numbers_count, unsigned long long *numbers)
|
482
|
+
{
|
483
|
+
/* bail out if no numbers */
|
484
|
+
if (HASHIDS_UNLIKELY(!numbers_count)) {
|
485
|
+
buffer[0] = '\0';
|
486
|
+
|
487
|
+
return 0;
|
488
|
+
}
|
489
|
+
|
490
|
+
size_t i, j, result_len, guard_index, half_length_ceil, half_length_floor;
|
491
|
+
unsigned long long number, number_copy, numbers_hash;
|
492
|
+
int p_max;
|
493
|
+
char lottery, ch, temp_ch, *p, *buffer_end, *buffer_temp;
|
494
|
+
|
495
|
+
/* return an estimation if no buffer */
|
496
|
+
if (HASHIDS_UNLIKELY(!buffer)) {
|
497
|
+
return hashids_estimate_encoded_size(hashids, numbers_count, numbers);
|
498
|
+
}
|
499
|
+
|
500
|
+
/* copy the alphabet into internal buffer 1 */
|
501
|
+
strncpy(hashids->alphabet_copy_1, hashids->alphabet,
|
502
|
+
hashids->alphabet_length);
|
503
|
+
|
504
|
+
/* walk arguments once and generate a hash */
|
505
|
+
for (i = 0, numbers_hash = 0; i < numbers_count; ++i) {
|
506
|
+
number = numbers[i];
|
507
|
+
numbers_hash += number % (i + 100);
|
508
|
+
}
|
509
|
+
|
510
|
+
/* lottery character */
|
511
|
+
lottery = hashids->alphabet[numbers_hash % hashids->alphabet_length];
|
512
|
+
|
513
|
+
/* start output buffer with it (or don't) */
|
514
|
+
buffer[0] = lottery;
|
515
|
+
buffer_end = buffer + 1;
|
516
|
+
|
517
|
+
/* alphabet-like buffer used for salt at each iteration */
|
518
|
+
hashids->alphabet_copy_2[0] = lottery;
|
519
|
+
hashids->alphabet_copy_2[1] = '\0';
|
520
|
+
strncat(hashids->alphabet_copy_2, hashids->salt,
|
521
|
+
hashids->alphabet_length - 1);
|
522
|
+
p = hashids->alphabet_copy_2 + hashids->salt_length + 1;
|
523
|
+
p_max = hashids->alphabet_length - 1 - hashids->salt_length;
|
524
|
+
if (p_max > 0) {
|
525
|
+
strncat(hashids->alphabet_copy_2, hashids->alphabet,
|
526
|
+
p_max);
|
527
|
+
} else {
|
528
|
+
hashids->alphabet_copy_2[hashids->alphabet_length] = '\0';
|
529
|
+
}
|
530
|
+
|
531
|
+
for (i = 0; i < numbers_count; ++i) {
|
532
|
+
/* take number */
|
533
|
+
number = number_copy = numbers[i];
|
534
|
+
|
535
|
+
/* create a salt for this iteration */
|
536
|
+
if (p_max > 0) {
|
537
|
+
strncpy(p, hashids->alphabet_copy_1, p_max);
|
538
|
+
}
|
539
|
+
|
540
|
+
/* shuffle the alphabet */
|
541
|
+
hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length,
|
542
|
+
hashids->alphabet_copy_2, hashids->alphabet_length);
|
543
|
+
|
544
|
+
/* hash the number */
|
545
|
+
buffer_temp = buffer_end;
|
546
|
+
do {
|
547
|
+
ch = hashids->alphabet_copy_1[number % hashids->alphabet_length];
|
548
|
+
*buffer_end++ = ch;
|
549
|
+
number /= hashids->alphabet_length;
|
550
|
+
} while (number);
|
551
|
+
|
552
|
+
/* reverse the hash we got */
|
553
|
+
for (j = 0; j < (size_t)((buffer_end - buffer_temp) / 2); ++j) {
|
554
|
+
temp_ch = *(buffer_temp + j);
|
555
|
+
*(buffer_temp + j) = *(buffer_end - 1 - j);
|
556
|
+
*(buffer_end - 1 - j) = temp_ch;
|
557
|
+
}
|
558
|
+
|
559
|
+
if (i + 1 < numbers_count) {
|
560
|
+
number_copy %= ch + i;
|
561
|
+
*buffer_end = hashids->separators[number_copy %
|
562
|
+
hashids->separators_count];
|
563
|
+
++buffer_end;
|
564
|
+
}
|
565
|
+
}
|
566
|
+
|
567
|
+
/* intermediate string length */
|
568
|
+
result_len = buffer_end - buffer;
|
569
|
+
|
570
|
+
if (result_len < hashids->min_hash_length) {
|
571
|
+
/* add a guard before the encoded numbers */
|
572
|
+
guard_index = (numbers_hash + buffer[0]) % hashids->guards_count;
|
573
|
+
memmove(buffer + 1, buffer, result_len);
|
574
|
+
buffer[0] = hashids->guards[guard_index];
|
575
|
+
++result_len;
|
576
|
+
|
577
|
+
if (result_len < hashids->min_hash_length) {
|
578
|
+
/* add a guard after the encoded numbers */
|
579
|
+
guard_index = (numbers_hash + buffer[2]) % hashids->guards_count;
|
580
|
+
buffer[result_len] = hashids->guards[guard_index];
|
581
|
+
++result_len;
|
582
|
+
|
583
|
+
/* pad with half alphabet before and after */
|
584
|
+
half_length_ceil = hashids_div_ceil_size_t(
|
585
|
+
hashids->alphabet_length, 2);
|
586
|
+
half_length_floor = floor((float)hashids->alphabet_length / 2);
|
587
|
+
|
588
|
+
/* pad, pad, pad */
|
589
|
+
while (result_len < hashids->min_hash_length) {
|
590
|
+
/* shuffle the alphabet */
|
591
|
+
strncpy(hashids->alphabet_copy_2, hashids->alphabet_copy_1,
|
592
|
+
hashids->alphabet_length);
|
593
|
+
hashids_shuffle(hashids->alphabet_copy_1,
|
594
|
+
hashids->alphabet_length, hashids->alphabet_copy_2,
|
595
|
+
hashids->alphabet_length);
|
596
|
+
|
597
|
+
/* left pad from the end of the alphabet */
|
598
|
+
i = hashids_div_ceil_size_t(
|
599
|
+
hashids->min_hash_length - result_len, 2);
|
600
|
+
/* right pad from the beginning */
|
601
|
+
j = floor((float)(hashids->min_hash_length - result_len) / 2);
|
602
|
+
|
603
|
+
/* check bounds */
|
604
|
+
if (i > half_length_ceil) {
|
605
|
+
i = half_length_ceil;
|
606
|
+
}
|
607
|
+
if (j > half_length_floor) {
|
608
|
+
j = half_length_floor;
|
609
|
+
}
|
610
|
+
|
611
|
+
/* handle excessively excessive excess */
|
612
|
+
if ((i + j) % 2 == 0 && hashids->alphabet_length % 2 == 1) {
|
613
|
+
++i; --j;
|
614
|
+
}
|
615
|
+
|
616
|
+
/* move the current result to "center" */
|
617
|
+
memmove(buffer + i, buffer, result_len);
|
618
|
+
/* pad left */
|
619
|
+
memmove(buffer,
|
620
|
+
hashids->alphabet_copy_1 + hashids->alphabet_length - i, i);
|
621
|
+
/* pad right */
|
622
|
+
memmove(buffer + i + result_len, hashids->alphabet_copy_1, j);
|
623
|
+
|
624
|
+
/* increment result_len */
|
625
|
+
result_len += i + j;
|
626
|
+
}
|
627
|
+
}
|
628
|
+
}
|
629
|
+
|
630
|
+
buffer[result_len] = '\0';
|
631
|
+
return result_len;
|
632
|
+
}
|
633
|
+
|
634
|
+
/* encode many (variadic) */
|
635
|
+
size_t
|
636
|
+
hashids_encode_v(hashids_t *hashids, char *buffer,
|
637
|
+
size_t numbers_count, ...)
|
638
|
+
{
|
639
|
+
size_t i, result;
|
640
|
+
unsigned long long *numbers;
|
641
|
+
va_list ap;
|
642
|
+
|
643
|
+
numbers = (unsigned long long *)_hashids_alloc(numbers_count *
|
644
|
+
sizeof(unsigned long long));
|
645
|
+
|
646
|
+
if (HASHIDS_UNLIKELY(!numbers)) {
|
647
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
648
|
+
return 0;
|
649
|
+
}
|
650
|
+
|
651
|
+
va_start(ap, numbers_count);
|
652
|
+
for (i = 0; i < numbers_count; ++i) {
|
653
|
+
numbers[i] = va_arg(ap, unsigned long long);
|
654
|
+
}
|
655
|
+
va_end(ap);
|
656
|
+
|
657
|
+
result = hashids_encode(hashids, buffer, numbers_count, numbers);
|
658
|
+
_hashids_free(numbers);
|
659
|
+
|
660
|
+
return result;
|
661
|
+
}
|
662
|
+
|
663
|
+
/* encode one */
|
664
|
+
size_t
|
665
|
+
hashids_encode_one(hashids_t *hashids, char *buffer,
|
666
|
+
unsigned long long number)
|
667
|
+
{
|
668
|
+
return hashids_encode(hashids, buffer, 1, &number);
|
669
|
+
}
|
670
|
+
|
671
|
+
/* numbers count */
|
672
|
+
size_t
|
673
|
+
hashids_numbers_count(hashids_t *hashids, const char *str)
|
674
|
+
{
|
675
|
+
size_t numbers_count;
|
676
|
+
char ch;
|
677
|
+
const char *p;
|
678
|
+
|
679
|
+
/* skip characters until we find a guard */
|
680
|
+
if (hashids->min_hash_length) {
|
681
|
+
p = str;
|
682
|
+
while ((ch = *p)) {
|
683
|
+
if (strchr(hashids->guards, ch)) {
|
684
|
+
str = p + 1;
|
685
|
+
break;
|
686
|
+
}
|
687
|
+
|
688
|
+
p++;
|
689
|
+
}
|
690
|
+
}
|
691
|
+
|
692
|
+
/* parse */
|
693
|
+
numbers_count = 0;
|
694
|
+
while ((ch = *str)) {
|
695
|
+
if (strchr(hashids->guards, ch)) {
|
696
|
+
break;
|
697
|
+
}
|
698
|
+
if (strchr(hashids->separators, ch)) {
|
699
|
+
numbers_count++;
|
700
|
+
str++;
|
701
|
+
continue;
|
702
|
+
}
|
703
|
+
if (!strchr(hashids->alphabet, ch)) {
|
704
|
+
hashids_errno = HASHIDS_ERROR_INVALID_HASH;
|
705
|
+
return 0;
|
706
|
+
}
|
707
|
+
|
708
|
+
str++;
|
709
|
+
}
|
710
|
+
|
711
|
+
/* account for the last number */
|
712
|
+
return numbers_count + 1;
|
713
|
+
}
|
714
|
+
|
715
|
+
/* decode */
|
716
|
+
size_t
|
717
|
+
hashids_decode(hashids_t *hashids, const char *str,
|
718
|
+
unsigned long long *numbers, size_t numbers_max)
|
719
|
+
{
|
720
|
+
size_t numbers_count;
|
721
|
+
unsigned long long number;
|
722
|
+
char lottery, ch, *p, *c;
|
723
|
+
int p_max;
|
724
|
+
|
725
|
+
if (!numbers || !numbers_max) {
|
726
|
+
return hashids_numbers_count(hashids, str);
|
727
|
+
}
|
728
|
+
|
729
|
+
/* skip characters until we find a guard */
|
730
|
+
if (hashids->min_hash_length) {
|
731
|
+
p = (char *)str;
|
732
|
+
while ((ch = *p)) {
|
733
|
+
if (strchr(hashids->guards, ch)) {
|
734
|
+
str = p + 1;
|
735
|
+
break;
|
736
|
+
}
|
737
|
+
|
738
|
+
p++;
|
739
|
+
}
|
740
|
+
}
|
741
|
+
|
742
|
+
/* get the lottery character */
|
743
|
+
lottery = *str++;
|
744
|
+
|
745
|
+
/* copy the alphabet into internal buffer 1 */
|
746
|
+
strncpy(hashids->alphabet_copy_1, hashids->alphabet,
|
747
|
+
hashids->alphabet_length);
|
748
|
+
|
749
|
+
/* alphabet-like buffer used for salt at each iteration */
|
750
|
+
hashids->alphabet_copy_2[0] = lottery;
|
751
|
+
hashids->alphabet_copy_2[1] = '\0';
|
752
|
+
strncat(hashids->alphabet_copy_2, hashids->salt,
|
753
|
+
hashids->alphabet_length - 1);
|
754
|
+
p = hashids->alphabet_copy_2 + hashids->salt_length + 1;
|
755
|
+
p_max = hashids->alphabet_length - 1 - hashids->salt_length;
|
756
|
+
if (p_max > 0) {
|
757
|
+
strncat(hashids->alphabet_copy_2, hashids->alphabet,
|
758
|
+
p_max);
|
759
|
+
} else {
|
760
|
+
hashids->alphabet_copy_2[hashids->alphabet_length] = '\0';
|
761
|
+
}
|
762
|
+
|
763
|
+
/* first shuffle */
|
764
|
+
hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length,
|
765
|
+
hashids->alphabet_copy_2, hashids->alphabet_length);
|
766
|
+
|
767
|
+
/* parse */
|
768
|
+
numbers_count = 0;
|
769
|
+
number = 0;
|
770
|
+
while ((ch = *str)) {
|
771
|
+
if (strchr(hashids->guards, ch)) {
|
772
|
+
break;
|
773
|
+
}
|
774
|
+
if (strchr(hashids->separators, ch)) {
|
775
|
+
/* store the number */
|
776
|
+
*numbers++ = number;
|
777
|
+
|
778
|
+
/* check limit */
|
779
|
+
if (++numbers_count >= numbers_max) {
|
780
|
+
return numbers_count;
|
781
|
+
}
|
782
|
+
|
783
|
+
number = 0;
|
784
|
+
|
785
|
+
/* resalt the alphabet */
|
786
|
+
if (p_max > 0) {
|
787
|
+
strncpy(p, hashids->alphabet_copy_1, p_max);
|
788
|
+
}
|
789
|
+
hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length,
|
790
|
+
hashids->alphabet_copy_2, hashids->alphabet_length);
|
791
|
+
|
792
|
+
str++;
|
793
|
+
continue;
|
794
|
+
}
|
795
|
+
if (!(c = strchr(hashids->alphabet_copy_1, ch))) {
|
796
|
+
hashids_errno = HASHIDS_ERROR_INVALID_HASH;
|
797
|
+
return 0;
|
798
|
+
}
|
799
|
+
|
800
|
+
number *= hashids->alphabet_length;
|
801
|
+
number += c - hashids->alphabet_copy_1;
|
802
|
+
|
803
|
+
str++;
|
804
|
+
}
|
805
|
+
|
806
|
+
/* store last number */
|
807
|
+
*numbers = number;
|
808
|
+
|
809
|
+
return numbers_count + 1;
|
810
|
+
}
|
811
|
+
|
812
|
+
/* unsafe decode */
|
813
|
+
size_t
|
814
|
+
hashids_decode_unsafe(hashids_t *hashids, const char *str,
|
815
|
+
unsigned long long *numbers)
|
816
|
+
{
|
817
|
+
return hashids_decode(hashids, str, numbers, (size_t)-1);
|
818
|
+
}
|
819
|
+
|
820
|
+
/* safe decode */
|
821
|
+
size_t
|
822
|
+
hashids_decode_safe(hashids_t *hashids, const char *str,
|
823
|
+
unsigned long long *numbers, size_t numbers_max)
|
824
|
+
{
|
825
|
+
size_t numbers_count;
|
826
|
+
size_t len;
|
827
|
+
char *p;
|
828
|
+
|
829
|
+
numbers_count = hashids_decode(hashids, str, numbers, numbers_max);
|
830
|
+
if (HASHIDS_UNLIKELY(!numbers_count)) {
|
831
|
+
hashids_errno = HASHIDS_ERROR_INVALID_HASH;
|
832
|
+
return 0;
|
833
|
+
}
|
834
|
+
|
835
|
+
len = hashids_estimate_encoded_size(hashids, numbers_count, numbers);
|
836
|
+
|
837
|
+
p = (char *)_hashids_alloc(len);
|
838
|
+
if (HASHIDS_UNLIKELY(!p)) {
|
839
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
840
|
+
return 0;
|
841
|
+
}
|
842
|
+
|
843
|
+
len = hashids_encode(hashids, p, numbers_count, numbers);
|
844
|
+
if (HASHIDS_UNLIKELY(!len)) {
|
845
|
+
_hashids_free(p);
|
846
|
+
return 0;
|
847
|
+
}
|
848
|
+
|
849
|
+
if (strcmp(str, p) != 0) {
|
850
|
+
_hashids_free(p);
|
851
|
+
hashids_errno = HASHIDS_ERROR_INVALID_HASH;
|
852
|
+
return 0;
|
853
|
+
}
|
854
|
+
|
855
|
+
_hashids_free(p);
|
856
|
+
return numbers_count;
|
857
|
+
}
|
858
|
+
|
859
|
+
/* encode hex */
|
860
|
+
size_t
|
861
|
+
hashids_encode_hex(hashids_t *hashids, char *buffer,
|
862
|
+
const char *hex_str)
|
863
|
+
{
|
864
|
+
int len;
|
865
|
+
char *temp, *p;
|
866
|
+
size_t result;
|
867
|
+
unsigned long long number;
|
868
|
+
|
869
|
+
len = strlen(hex_str);
|
870
|
+
temp = (char *)_hashids_alloc(len + 2);
|
871
|
+
|
872
|
+
if (!temp) {
|
873
|
+
hashids_errno = HASHIDS_ERROR_ALLOC;
|
874
|
+
return 0;
|
875
|
+
}
|
876
|
+
|
877
|
+
temp[0] = '1';
|
878
|
+
strncpy(temp + 1, hex_str, len);
|
879
|
+
|
880
|
+
number = strtoull(temp, &p, 16);
|
881
|
+
|
882
|
+
if (p == temp) {
|
883
|
+
_hashids_free(temp);
|
884
|
+
hashids_errno = HASHIDS_ERROR_INVALID_NUMBER;
|
885
|
+
return 0;
|
886
|
+
}
|
887
|
+
|
888
|
+
result = hashids_encode(hashids, buffer, 1, &number);
|
889
|
+
_hashids_free(temp);
|
890
|
+
|
891
|
+
return result;
|
892
|
+
}
|
893
|
+
|
894
|
+
/* decode hex */
|
895
|
+
size_t
|
896
|
+
hashids_decode_hex(hashids_t *hashids, char *str, char *output)
|
897
|
+
{
|
898
|
+
size_t result, i;
|
899
|
+
unsigned long long number;
|
900
|
+
char ch, *temp;
|
901
|
+
|
902
|
+
result = hashids_numbers_count(hashids, str);
|
903
|
+
|
904
|
+
if (result != 1) {
|
905
|
+
return 0;
|
906
|
+
}
|
907
|
+
|
908
|
+
result = hashids_decode_unsafe(hashids, str, &number);
|
909
|
+
|
910
|
+
if (result != 1) {
|
911
|
+
return 0;
|
912
|
+
}
|
913
|
+
|
914
|
+
temp = output;
|
915
|
+
|
916
|
+
do {
|
917
|
+
ch = number % 16;
|
918
|
+
if (ch > 9) {
|
919
|
+
ch += 'A' - 10;
|
920
|
+
} else {
|
921
|
+
ch += '0';
|
922
|
+
}
|
923
|
+
|
924
|
+
*temp++ = (char)ch;
|
925
|
+
|
926
|
+
number /= 16;
|
927
|
+
} while (number);
|
928
|
+
|
929
|
+
temp--;
|
930
|
+
*temp = 0;
|
931
|
+
|
932
|
+
for (i = 0; i < (size_t)((temp - output) / 2); ++i) {
|
933
|
+
ch = *(output + i);
|
934
|
+
*(output + i) = *(temp - 1 - i);
|
935
|
+
*(temp - 1 - i) = ch;
|
936
|
+
}
|
937
|
+
|
938
|
+
return 1;
|
939
|
+
}
|