#include "hashmap.h"
#include "lstring.h"
#include "types.h"
#include "helper.h"
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
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(struct uidmap *uidmap, 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(uidmap->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(uidmap->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(struct uidmap *uidmap, const struct string *name, const uint32_t number)
{
insertTime(uidmap, 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(struct uidmap *uidmap, 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(uidmap->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(uidmap->numToName, &asNumber);
if (u == NULL) {
// No collision, use name as number
insert(uidmap, &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(uidmap->numToName, &asNumber);
if (u == NULL)
break;
do {
asNumber = rand() + 2000;
} while (asNumber < 2000);
}
insert(uidmap, &lower, asNumber);
return asNumber;
}
const struct string *uidmap_getNameForNumber(struct uidmap *uidmap, const struct string *numberString)
{
uint32_t number = parseUInt32(numberString);
if (number < 2000)
return NULL;
struct user *u = number_hashmap_get(uidmap->numToName, &number);
if (u == NULL)
return NULL;
u->lastUse = time(NULL);
return u->name;
}
void uidmap_init(struct uidmap *uidmap)
{
if (uidmap->nameToNum != NULL)
return;
uidmap->numToName = calloc(2, sizeof(struct hashmap));
uidmap->nameToNum = uidmap->numToName + 1;
hashmap_init(uidmap->numToName, uint32_hash, uint32_compare, 0);
hashmap_init(uidmap->nameToNum, string_hash, string_compare, 0);
if (uidmap->fileName == NULL)
return;
// We have a filename, try to load DB
FILE *fh = fopen(uidmap->fileName, "rb");
if (fh == NULL)
return;
fseek(fh, 0, SEEK_END);
const int fsize = ftell(fh);
fseek(fh, 0, SEEK_SET);
if (fsize > 0) { // Guess required map size
const size_t entries = (size_t)fsize / 25;
hashmap_destroy(uidmap->numToName);
hashmap_destroy(uidmap->nameToNum);
hashmap_init(uidmap->numToName, uint32_hash, uint32_compare, entries);
hashmap_init(uidmap->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(uidmap, &name, uidNumber, lastUse);
loaded++;
}
fclose(fh);
plog(DEBUG_INFO, "Loaded %d entries from %s", loaded, uidmap->fileName);
}
void uidmap_cleanupSave(struct uidmap *uidmap)
{
if (uidmap->nameToNum == NULL)
return;
FILE *fh = NULL;
if (uidmap->fileName != NULL) {
fh = fopen(uidmap->fileName, "wb");
if (fh != NULL) {
if (fwrite(&MAGIC, sizeof(MAGIC), 1, fh) != 1) {
plog(DEBUG_WARNING, "Cannot write magic to %s", uidmap->fileName);
fclose(fh);
fh = NULL;
}
}
}
const int64_t deadline = time(NULL) - MAX_AGE;
struct hashmap_iter *it = hashmap_iter(uidmap->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(uidmap->nameToNum, it);
if (number_hashmap_remove(uidmap->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", uidmap->fileName);
fclose(fh);
fh = NULL;
} else {
saved++;
}
}
it = hashmap_iter_next(uidmap->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);
}
}