Am stupid. The compiler even warned me there weren’t enough elements when i wrote this, but i just thought i miscalculated the padding.
277 lines
8.2 KiB
C
277 lines
8.2 KiB
C
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
unsigned char base62_to_dec_table[256] = {
|
|
/* Illegal encoded values */
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
/* 0 - 10 */
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
|
/* Illegal again */
|
|
-1, -1, -1, -1, -1, -1, -1,
|
|
/* A-Z */
|
|
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,
|
|
/* Illegal once more */
|
|
-1, -1, -1, -1, -1, -1,
|
|
/* a-z */
|
|
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,
|
|
/* padding */
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
|
|
};
|
|
|
|
char dec_to_base62_table[62] = {
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
|
};
|
|
|
|
static bool convert_b62_to_dec(const char* b62, unsigned __int128* res)
|
|
{
|
|
const char* strend = b62 + strlen(b62);
|
|
|
|
// There can be legacy IDs which are just a regular 64-bit dec integer,
|
|
// check for them first
|
|
char* stop;
|
|
errno = 0;
|
|
unsigned long long legid = strtoull(b62, &stop, 10);
|
|
if (errno == 0 && stop == strend && legid <= UINT64_MAX) {
|
|
*res = legid;
|
|
return true;
|
|
}
|
|
|
|
// base62 decoding
|
|
const unsigned __int128 MAX_UINT128 = ~((unsigned __int128) 0);
|
|
*res = 0;
|
|
for (; b62 < strend; ++b62) {
|
|
unsigned char curr = base62_to_dec_table[(unsigned char) *b62];
|
|
if (curr > 61) {
|
|
fprintf(stderr, "Illegal character %c in base62 input!\n", (char) *b62);
|
|
return false;
|
|
}
|
|
|
|
if (*res > (MAX_UINT128 - curr) / 62) {
|
|
fprintf(stderr, "Input value too big; cannot be valid ID!\n");
|
|
return false;
|
|
}
|
|
|
|
*res = *res * 62 + curr;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool convert_uint_to_uuid(unsigned __int128* dec, char *res, size_t reslen)
|
|
{
|
|
int cnt = snprintf(res, reslen,
|
|
"%08lx-%04lx-%04lx-%04lx-%012llx",
|
|
(unsigned long) (*dec >> 96) & 0xFFFFFFFF,
|
|
(unsigned long) (*dec >> 80) & 0xFFFF,
|
|
(unsigned long) (*dec >> 64) & 0xFFFF,
|
|
(unsigned long) (*dec >> 48) & 0xFFFF,
|
|
(unsigned long long) (*dec >> 0) & 0xFFFFFFFFFFFF
|
|
);
|
|
|
|
bool fail = cnt < 0 || (size_t) cnt > reslen - 1;
|
|
if(fail)
|
|
fprintf(stderr, "Failed to write resulting hex string! (%d, %zu)\n", cnt, reslen);
|
|
return !fail;
|
|
}
|
|
|
|
static bool convert_b62_to_uuid(const char* b62, char* res, size_t reslen)
|
|
{
|
|
unsigned __int128 dec;
|
|
if (!convert_b62_to_dec(b62, &dec))
|
|
return false;
|
|
|
|
return convert_uint_to_uuid(&dec, res, reslen);
|
|
}
|
|
|
|
static bool convert_hex_to_uint(const char* hex, unsigned __int128* res)
|
|
{
|
|
*res = 0;
|
|
for (; *hex != 0; ++hex) {
|
|
if (*hex == '-' || *hex == ' ')
|
|
continue;
|
|
|
|
if (*res & (((unsigned __int128) 0xF) << 124)) {
|
|
fprintf(stderr, "Input value too big; cannot be valid ID!\n");
|
|
return false;
|
|
}
|
|
|
|
*res <<= 4;
|
|
if (*hex >= '0' && *hex <= '9')
|
|
*res += *hex - '0';
|
|
else if (*hex >= 'a' && *hex <= 'f')
|
|
*res += (*hex - 'a') + 10;
|
|
else if (*hex >= 'A' && *hex <= 'F')
|
|
*res += (*hex - 'A') + 10;
|
|
else {
|
|
fprintf(stderr, "Illegal character %c in hex input!\n", *hex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return *hex == 0;
|
|
}
|
|
|
|
static bool convert_uint_to_b62(unsigned __int128 uuid, char* res, size_t reslen)
|
|
{
|
|
assert(reslen > 22);
|
|
char* pos = res + 22;
|
|
memset(res, ' ', pos - res);
|
|
*(pos--) = 0;
|
|
*pos = '0';
|
|
|
|
while (uuid != 0) {
|
|
unsigned __int128 div = uuid / 62;
|
|
unsigned rem = uuid % 62;
|
|
|
|
if (pos == res && div != 0) {
|
|
fprintf(stderr, "Ran out of space in output buffer!\n");
|
|
return false;
|
|
}
|
|
|
|
*(pos--) = dec_to_base62_table[rem];
|
|
uuid = div;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool convert_hex_to_api(const char* hex, char* res, size_t reslen)
|
|
{
|
|
unsigned __int128 uuid;
|
|
if (!convert_hex_to_uint(hex, &uuid))
|
|
return false;
|
|
|
|
if (uuid > UINT64_MAX)
|
|
return convert_uint_to_b62(uuid, res, reslen);
|
|
else {
|
|
// Legacy IDs MUST be passed as decimals, else FlakeId simply rejects them
|
|
int cnt = snprintf(res, reslen, "%llu", (unsigned long long) uuid);
|
|
return cnt > 0 && (unsigned) cnt < reslen - 1;
|
|
}
|
|
}
|
|
|
|
static void usage(char* name)
|
|
{
|
|
name = name ? name : "shinyr";
|
|
printf("%s -h\n\tPrint this help text\n\n", name);
|
|
printf("%s api2db [api_id]\n\tConvert API ID to database ID format\n\n", name);
|
|
printf("%s db2api [db_id]\n\tConvert database ID to API ID format\n\n", name);
|
|
printf("%s time [-f api|db] [api_id|db_id]\n\tExtract millisecond-precision unix timestamp from FlakeID\n\n", name);
|
|
}
|
|
|
|
static const char* guess_format(const char* input)
|
|
{
|
|
while (*input) {
|
|
char c = *input;
|
|
if ((c > 'F' && c <= 'Z') || (c > 'f' && c <= 'z'))
|
|
return "api";
|
|
++input;
|
|
}
|
|
return "db";
|
|
}
|
|
|
|
static bool extract_time(int argc, char** argv, unsigned long long* timestamp)
|
|
{
|
|
// should have been pre-checked before calling this
|
|
assert(argc >= 3);
|
|
|
|
const char* mode;
|
|
const char* input;
|
|
|
|
if (argc == 3) {
|
|
input = argv[2];
|
|
mode = guess_format(input);
|
|
} else if (argc == 5 && !strcmp(argv[2], "-f")) {
|
|
mode = argv[3];
|
|
input = argv[4];
|
|
} else {
|
|
printf("Wrong argument count!\n");
|
|
usage(argv[0]);
|
|
return false;
|
|
}
|
|
|
|
unsigned __int128 uuid;
|
|
bool parsed_input = false;
|
|
|
|
if (!strcmp(mode, "db")) {
|
|
parsed_input = convert_hex_to_uint(input, &uuid);
|
|
} else if (!strcmp(mode, "api")) {
|
|
parsed_input = convert_b62_to_dec(input, &uuid);
|
|
} else {
|
|
printf("Unknown format: %s!\n", mode);
|
|
usage(argv[0]);
|
|
return false;
|
|
}
|
|
|
|
if (!parsed_input) {
|
|
printf("Invalid ID for attempted format (%s)!: %s\n", mode, input);
|
|
return false;
|
|
}
|
|
|
|
*timestamp = uuid >> 64;
|
|
|
|
if (*timestamp == 0) {
|
|
printf("Input ID has zero timestamp; most likely it wasn't a flake ID!\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (argc < 3) {
|
|
usage(argv[0]);
|
|
return !(argc == 2 && !strcmp(argv[1], "-h"));
|
|
}
|
|
|
|
const char* const mode = argv[1];
|
|
const char* const input = argv[2];
|
|
|
|
// Base62 values will take up less space than hex-encoded uuid
|
|
// For uuid: 32 characters for hex + 4 hypens + terminating zero
|
|
char res[37] = {0};
|
|
const size_t reslen = sizeof(res);
|
|
|
|
bool success;
|
|
|
|
if (!strcmp(mode, "api2db")) {
|
|
if ((success = convert_b62_to_uuid(input, res, reslen)))
|
|
printf("%s\n", res);
|
|
} else if (!strcmp(mode, "db2api")) {
|
|
if ((success = convert_hex_to_api(input, res, reslen))) {
|
|
char* ptr = res;
|
|
while(*ptr == ' ') ++ptr;
|
|
printf("%s\n", ptr);
|
|
}
|
|
} else if (!strcmp(mode, "time")) {
|
|
unsigned long long timestamp;
|
|
if ((success = extract_time(argc, argv, ×tamp)))
|
|
printf("%llu\n", timestamp);
|
|
} else {
|
|
fprintf(stderr, "Unknown mode '%s'!\n", mode);
|
|
success = false;
|
|
}
|
|
|
|
return !success;
|
|
}
|