/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */ /* * lsquic_stock_shi.c */ #include #include #include #include #include #include "lsquic.h" #include "lsquic_stock_shi.h" #include "lsquic_malo.h" #include "lsquic_hash.h" struct stock_shared_hash { TAILQ_HEAD(, hash_elem) lru_elems; struct lsquic_hash *lhash; struct malo *malo; }; struct key { void *buf; unsigned sz; }; struct hash_elem { TAILQ_ENTRY(hash_elem) next_lru_he; struct lsquic_hash_elem lhash_elem; void *data; time_t expiry; /* If not 0, the element is on LRU list */ struct key key; unsigned data_sz; }; static void free_key_data (struct hash_elem *he) { if (he->data_sz) free(he->data); free(he->key.buf); } static void delete_expired_elements (struct stock_shared_hash *hash) { struct hash_elem *he; time_t now = time(NULL); while ((he = TAILQ_FIRST(&hash->lru_elems))) { if (he->expiry < now) { lsquic_hash_erase(hash->lhash, &he->lhash_elem); if (he->expiry) TAILQ_REMOVE(&hash->lru_elems, he, next_lru_he); free_key_data(he); lsquic_malo_put(he); } else break; } } static int stock_shi_insert (void *hash_ctx, void *key, unsigned key_sz, void *data, unsigned data_sz, time_t expiry) { struct stock_shared_hash *const hash = hash_ctx; struct hash_elem *he; /* Potential optimization: do not exire on every insert. Use case: * if many insert occur in a row, it is not efficient to perform * this check every time. Can add a counter in hash. */ if (!TAILQ_EMPTY(&hash->lru_elems)) delete_expired_elements(hash); he = lsquic_malo_get(hash->malo); if (!he) return -1; he->key.buf = key; he->key.sz = key_sz; he->data = data; he->data_sz = data_sz; he->expiry = expiry; memset(&he->lhash_elem, 0, sizeof(he->lhash_elem)); if (lsquic_hash_insert(hash->lhash, he->key.buf, he->key.sz, he, &he->lhash_elem)) { if (expiry) TAILQ_INSERT_TAIL(&hash->lru_elems, he, next_lru_he); return 0; } else { lsquic_malo_put(he); return -1; } } static int stock_shi_lookup (void *hash_ctx, const void *key, unsigned key_sz, void **data, unsigned *data_sz) { struct stock_shared_hash *const hash = hash_ctx; struct hash_elem *he; struct lsquic_hash_elem *el; if (!TAILQ_EMPTY(&hash->lru_elems)) delete_expired_elements(hash); el = lsquic_hash_find(hash->lhash, key, key_sz); if (!el) return 0; /* 0: not found */ he = lsquic_hashelem_getdata(el); *data = he->data; *data_sz = he->data_sz; return 1; /* 1: found */ } static int stock_shi_delete (void *hash_ctx, const void *key, unsigned key_sz) { struct stock_shared_hash *const hash = hash_ctx; struct lsquic_hash_elem *el; struct hash_elem *he; if (!TAILQ_EMPTY(&hash->lru_elems)) delete_expired_elements(hash); el = lsquic_hash_find(hash->lhash, key, key_sz); if (!el) return -1; he = lsquic_hashelem_getdata(el); lsquic_hash_erase(hash->lhash, el); if (he->expiry) TAILQ_REMOVE(&hash->lru_elems, he, next_lru_he); free_key_data(he); lsquic_malo_put(he); return 0; } struct stock_shared_hash * lsquic_stock_shared_hash_new (void) { struct malo *malo; struct stock_shared_hash *hash; malo = lsquic_malo_create(sizeof(struct hash_elem)); if (!malo) return NULL; hash = lsquic_malo_get(malo); if (!hash) { /* This would be really odd, but let's check this for completeness. */ lsquic_malo_destroy(malo); return NULL; } hash->malo = malo; hash->lhash = lsquic_hash_create(); TAILQ_INIT(&hash->lru_elems); return hash; } void lsquic_stock_shared_hash_destroy (struct stock_shared_hash *hash) { struct hash_elem *he; struct lsquic_hash_elem *el; for (el = lsquic_hash_first(hash->lhash); el; el = lsquic_hash_next(hash->lhash)) { he = lsquic_hashelem_getdata(el); free_key_data(he); /* No need to lsquic_malo_put(he) here */ } lsquic_hash_destroy(hash->lhash); lsquic_malo_destroy(hash->malo); } const struct lsquic_shared_hash_if stock_shi = { .shi_insert = stock_shi_insert, .shi_delete = stock_shi_delete, .shi_lookup = stock_shi_lookup, }; /* Need this to save one malloc using malo: */ typedef char hash_not_larger_than_hash_elem [ (sizeof(struct stock_shared_hash) <= sizeof(struct hash_elem)) ? 1 : -1];