/* * hash.c - FNV-1a hash function and symbol table (sorted array with bsearch) * * The symbol table maps KuCoin trading pair names (e.g. "BTC-USDT") to * dense 16-bit indices. Entries are sorted alphabetically for O(log n) * lookup via bsearch(3). Used by ws_client to resolve symbol names in * book update messages. */ #include "hash.h" #include #include #include static const uint32_t FNV_OFFSET = 2166136261u; static const uint32_t FNV_PRIME = 16777619u; /* FNV-1a non-cryptographic hash for arbitrary-length strings. */ uint32_t fnv1a_hash(const char *str, uint32_t len) { uint32_t hash = FNV_OFFSET; for (uint32_t i = 0; i < len; i++) { hash ^= (uint8_t)str[i]; hash *= FNV_PRIME; } return hash; } /* Initialise an empty symbol table with initial capacity. */ void symbol_table_init(symbol_table_t *table) { table->capacity = SYMBOL_TABLE_INITIAL; table->count = 0; table->entries = calloc(table->capacity, sizeof(symbol_entry_t)); } /* Compare two symbol entries by name (for bsearch). */ static int entry_cmp(const void *a, const void *b) { const symbol_entry_t *ea = (const symbol_entry_t *)a; const symbol_entry_t *eb = (const symbol_entry_t *)b; return strcmp(ea->name, eb->name); } /* Wrapper matching qsort's comparison signature (same as entry_cmp). */ static int entry_cmp_qsort(const void *a, const void *b) { return entry_cmp(a, b); } /* Sort entries by name and re-assign dense indices. Must be called after all additions are complete, before any lookups via bsearch. */ void symbol_table_sort(symbol_table_t *table) { qsort(table->entries, table->count, sizeof(symbol_entry_t), entry_cmp_qsort); for (uint32_t i = 0; i < table->count; i++) { table->entries[i].index = (uint16_t)i; } } /* Append a new symbol entry, doubling capacity if full. Returns the assigned index, or -1 on allocation failure. */ int symbol_table_add(symbol_table_t *table, const char *name) { if (table->count >= table->capacity) { uint32_t new_cap = table->capacity * 2; symbol_entry_t *new_entries = realloc(table->entries, new_cap * sizeof(symbol_entry_t)); if (!new_entries) return -1; table->entries = new_entries; table->capacity = new_cap; } symbol_entry_t *entry = &table->entries[table->count]; strncpy(entry->name, name, SYMBOL_NAME_LEN - 1); entry->name[SYMBOL_NAME_LEN - 1] = '\0'; entry->index = (uint16_t)table->count; table->count++; return (int)(table->count - 1); } /* Binary-search for a symbol by name. Returns its 16-bit index or -1. */ int16_t symbol_table_lookup(const symbol_table_t *table, const char *name) { symbol_entry_t key; strncpy(key.name, name, SYMBOL_NAME_LEN - 1); key.name[SYMBOL_NAME_LEN - 1] = '\0'; symbol_entry_t *found = bsearch(&key, table->entries, table->count, sizeof(symbol_entry_t), entry_cmp); if (!found) return -1; return (int16_t)found->index; }