From 33db97b72473124697170394efbc993870276c55 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 9 Nov 2018 15:36:21 +0100 Subject: Started work on proxy-side uid generation/tracking --- uidmap.c | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 uidmap.c (limited to 'uidmap.c') diff --git a/uidmap.c b/uidmap.c new file mode 100644 index 0000000..8315be2 --- /dev/null +++ b/uidmap.c @@ -0,0 +1,262 @@ +#include "hashmap.h" +#include "lstring.h" +#include "types.h" +#include "helper.h" +#include +#include +#include +#include +#include +#include + +static struct hashmap _numToName, _nameToNum; +static BOOL _initDone = FALSE; +static const char *_saveFileName = NULL; + +static const uint16_t MAGIC = 1234; +static const int64_t MAX_AGE = 86400 * 2; // 2 days +#define MAX_NAME_LEN 1024 + +HASHMAP_FUNCS_CREATE(name, struct string, struct user); +HASHMAP_FUNCS_CREATE(number, uint32_t, struct user); + +struct user +{ + uint32_t number; + const struct string *name; + int64_t lastUse; +}; + +static void insertTime(const struct string *name, const uint32_t number, const int64_t lastUse) +{ + uint8_t *buffer = malloc(sizeof(struct user) + sizeof(struct string) + name->l); + struct user *u = (struct user*)buffer; + struct string *s = (struct string*)(buffer + sizeof(struct user)); + char *str = (char*)(buffer + sizeof(struct user) + sizeof(struct string)); + memcpy(str, name->s, name->l); + s->s = str; + s->l = name->l; + u->name = s; + u->number = number; + u->lastUse = lastUse; + if (name_hashmap_put(&_nameToNum, u->name, u) != u) { + plog(DEBUG_WARNING, "[uidmap] Collision when inserting %.*s by name (%"PRIu32")", (int)s->l, s->s, number); + } + if (number_hashmap_put(&_numToName, &u->number, u) != u) { + plog(DEBUG_WARNING, "[uidmap] Collision when inserting %.*s by number (%"PRIu32")", (int)s->l, s->s, number); + } + // TODO: Leak +} + +static void insert(const struct string *name, const uint32_t number) +{ + insertTime(name, number, time(NULL)); +} + +static int uint32_compare(const void *a, const void *b) +{ + return (*(const uint32_t*)a) != (*(const uint32_t*)b); +} + +static int string_compare(const void *a, const void *b) +{ + return !equals((const struct string*)a, (const struct string*)b); +} + +static size_t uint32_hash(const void *key) +{ + return *(const uint32_t*)key; +} + +static size_t string_hash(const void *key) +{ + const struct string *key_str = (const struct string*)key; + size_t hash = 0; + + for (size_t pos = 0; pos < key_str->l; ++pos) { + hash += key_str->s[pos]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; +} + +uint32_t uidmap_getNumberForName(const struct string *name) +{ + // Try string matching + char buf[name->l]; + struct string lower = { .s = buf, .l = name->l }; + for (size_t i = 0; i < name->l; ++i) { + buf[i] = tolower(name->s[i]); + } + // Get + struct user *u = name_hashmap_get(&_nameToNum, &lower); + if (u != NULL) { + u->lastUse = time(NULL); + return u->number; + } + // Not known yet - try numeric + uint32_t asNumber = parseUInt32(name); + if (asNumber >= 2000) { + u = number_hashmap_get(&_numToName, &asNumber); + if (u == NULL) { + // No collision, use name as number + insert(&lower, asNumber); + return asNumber; + } + plog(DEBUG_WARNING, "%.*s is numeric (%u) but collides", (int)name->l, name->s, asNumber); + } + // Default case - generate number + asNumber = (uint32_t)string_hash(&lower); + while (asNumber < 2000) { + asNumber = rand() + 2000; + } + for (;;) { + //plog(DEBUG_WARNING, "Using number %u for %.*s", asNumber, (int)name->l, name->s); + u = number_hashmap_get(&_numToName, &asNumber); + if (u == NULL) + break; + do { + asNumber = rand() + 2000; + } while (asNumber < 2000); + } + insert(&lower, asNumber); + return asNumber; +} + +const struct string *uidmap_getNameForNumber(const uint32_t number) +{ + struct user *u = number_hashmap_get(&_numToName, &number); + if (u == NULL) + return NULL; + u->lastUse = time(NULL); + return u->name; +} + +void uidmap_init(const char *fileName) +{ + if (_initDone) + return; + _initDone = TRUE; + hashmap_init(&_numToName, uint32_hash, uint32_compare, 0); + hashmap_init(&_nameToNum, string_hash, string_compare, 0); + if (fileName == NULL) + return; + // We have a filename, remember it and try to load DB + _saveFileName = strdup(fileName); + FILE *fh = fopen(_saveFileName, "rb"); + if (fh == NULL) + return; + fseek(fh, 0, SEEK_END); + const int fsize = ftell(fh); + fseek(fh, 0, SEEK_SET); + if (fsize > 0) { + const size_t entries = (size_t)fsize / 25; + hashmap_destroy(&_numToName); + hashmap_destroy(&_nameToNum); + hashmap_init(&_numToName, uint32_hash, uint32_compare, entries); + hashmap_init(&_nameToNum, string_hash, string_compare, entries); + } + // Not endian safe, do not copy file around... + uint16_t len = 0; + int64_t lastUse; + uint32_t uidNumber; + char nbuffer[MAX_NAME_LEN]; + struct string name; + name.s = nbuffer; + if (fread(&len, sizeof(len), 1, fh) != 1 || len != MAGIC) { + plog(DEBUG_WARNING, "uid map file missing header magic (%"PRIu16")", len); + fclose(fh); + return; + } + int loaded = 0; + const int64_t now = time(NULL); + for (;;) { + if (fread(&len, sizeof(len), 1, fh) != 1) + break; + if (len > MAX_NAME_LEN) { + plog(DEBUG_WARNING, "DB has entry of size %"PRIu16", aborting import"); + break; + } + if (fread(&lastUse, sizeof(lastUse), 1, fh) != 1 || fread(&uidNumber, sizeof(uidNumber), 1, fh) != 1) { + plog(DEBUG_WARNING, "DB has truncated entry (no lastUse/uidNumber)"); + break; + } + if (fread(nbuffer, len, 1, fh) != 1) { + plog(DEBUG_WARNING, "DB has truncated entry (no name)"); + break; + } + // Check age + if (now - lastUse > MAX_AGE) + continue; + if (lastUse > now) + lastUse = now; + // Add + name.l = len; + insertTime(&name, uidNumber, lastUse); + loaded++; + } + fclose(fh); + plog(DEBUG_INFO, "Loaded %d entries from %s", loaded, _saveFileName); +} + +void uidmap_cleanupSave() +{ + if (!_initDone) + return; + FILE *fh = NULL; + if (_saveFileName != NULL) { + fh = fopen(_saveFileName, "wb"); + if (fh != NULL) { + if (fwrite(&MAGIC, sizeof(MAGIC), 1, fh) != 1) { + plog(DEBUG_WARNING, "Cannot write magic to %s", _saveFileName); + fclose(fh); + fh = NULL; + } + } + } + const int64_t deadline = time(NULL) - MAX_AGE; + struct hashmap_iter *it = hashmap_iter(&_nameToNum); + struct user *u = NULL; + int saved = 0, removed = 0; + uint16_t len; + while (it != NULL) { + u = name_hashmap_iter_get_data(it); + if (u->lastUse < deadline) { + it = hashmap_iter_remove(&_nameToNum, it); + if (number_hashmap_remove(&_numToName, &u->number) != u) { + plog(DEBUG_WARNING, "Could not remove user from numToName that was in nameToNum"); + } + free(u); + removed++; + continue; + } + // Save if file + if (fh != NULL && u->name->l < MAX_NAME_LEN) { + len = (uint16_t)u->name->l; + if (fwrite(&len, sizeof(len), 1, fh) != 1 + || fwrite(&u->lastUse, sizeof(u->lastUse), 1, fh) != 1 + || fwrite(&u->number, sizeof(u->number), 1, fh) != 1 + || fwrite(u->name->s, len, 1, fh) != 1 + ) { + plog(DEBUG_WARNING, "Could not write record to %s", _saveFileName); + fclose(fh); + fh = NULL; + } else { + saved++; + } + } + it = hashmap_iter_next(&_nameToNum, it); + } + if (fh != NULL) { + fsync(fileno(fh)); + fclose(fh); + } + if (saved > 0 || removed > 0) { + plog(DEBUG_VERBOSE, "Purged %d old entries, saved %d entries", removed, saved); + } +} + -- cgit v1.2.3-55-g7522