Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use siphash to replace hash_string #471

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/xquic/xquic.h
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,8 @@ typedef struct xqc_config_s {

/** bucket size of connection hash table in engine */
size_t conns_hash_bucket_size;
/* for warning when the number of elements in one bucket exceeds the value of hash_conflict_threshold*/
uint32_t hash_conflict_threshold;

/** capacity of connection priority queue in engine */
size_t conns_active_pq_capacity;
Expand Down
190 changes: 190 additions & 0 deletions src/common/xqc_siphash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* @copyright Copyright (c) 2025, Alibaba Group Holding Limited
*/

#ifndef _XQC_SIPHASH_H_INCLUDED_
#define _XQC_SIPHASH_H_INCLUDED_

#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include "src/common/xqc_common.h"

#define XQC_SIPHASH_KEY_SIZE 16
#define XQC_SIPHASH_C_ROUNDS 2
#define XQC_SIPHASH_D_ROUNDS 4
#define XQC_DEFAULT_HASH_SIZE 8

/* save siphash context */
typedef struct xqc_siphash_ctx {
/* v0 v1 v2 v3 */
uint64_t v0;
uint64_t v1;
uint64_t v2;
uint64_t v3;
int hash_size; /* save sizeof(hash), only 8 or 16 */
/* SipHash-2-4 */
int crounds;
int drounds;
} xqc_siphash_ctx_t;

#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))

#define U32TO8_LE(p, v) \
(p)[0] = (uint8_t)((v)); \
(p)[1] = (uint8_t)((v) >> 8); \
(p)[2] = (uint8_t)((v) >> 16); \
(p)[3] = (uint8_t)((v) >> 24);

#define U64TO8_LE(p, v) \
U32TO8_LE((p), (uint32_t)((v))); \
U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));

#define U8TO64_LE(p) \
(((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \
((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \
((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))

#define SIPROUND \
do { \
v0 += v1; \
v1 = ROTL(v1, 13); \
v1 ^= v0; \
v0 = ROTL(v0, 32); \
v2 += v3; \
v3 = ROTL(v3, 16); \
v3 ^= v2; \
v0 += v3; \
v3 = ROTL(v3, 21); \
v3 ^= v0; \
v2 += v1; \
v1 = ROTL(v1, 17); \
v1 ^= v2; \
v2 = ROTL(v2, 32); \
} while (0)

static inline int
xqc_siphash_init(xqc_siphash_ctx_t *ctx, const unsigned char *k, size_t key_len,
size_t hash_size, int crounds, int drounds)
{
uint64_t k0, k1;
if (key_len != XQC_SIPHASH_KEY_SIZE) {
return XQC_ERROR;
}
/* hash_size must be 8 or 16 */
if (hash_size != 8 && hash_size != 16) {
return XQC_ERROR;
}
k0 = U8TO64_LE(k);
k1 = U8TO64_LE(k + 8);

ctx->v0 = 0x736f6d6570736575ULL ^ k0;
ctx->v1 = 0x646f72616e646f6dULL ^ k1;
ctx->v2 = 0x6c7967656e657261ULL ^ k0;
ctx->v3 = 0x7465646279746573ULL ^ k1;

ctx->hash_size = hash_size;
if (hash_size == 16) {
ctx->v1 ^= 0xee;
}
/* default: SipHash-2-4 */
if (crounds == 0) {
ctx->crounds = XQC_SIPHASH_C_ROUNDS;
} else {
ctx->crounds = crounds;
}
if (drounds == 0) {
ctx->drounds = XQC_SIPHASH_D_ROUNDS;
} else {
ctx->drounds = drounds;
}
return XQC_OK;
}


/*
Computes a SipHash value
*ctx: point to siphash context
*in: pointer to input data (read-only)
inlen: input data length in bytes (any size_t value)
*out: pointer to output data (write-only), outlen bytes must be allocated
outlen: length of the output in bytes, must be 8 or 16
*/

static inline int
xqc_siphash(xqc_siphash_ctx_t *ctx, const uint8_t *in, size_t inlen, uint8_t *out, size_t outlen)
{
uint64_t b = (uint64_t)inlen << 56, m;
const uint8_t *pi = in, *end = in + inlen - (inlen % sizeof(uint64_t));
int left = inlen & 7;
int i = 0;
uint64_t v0 = ctx->v0;
uint64_t v1 = ctx->v1;
uint64_t v2 = ctx->v2;
uint64_t v3 = ctx->v3;

if (outlen != ctx->hash_size) {
return XQC_ERROR;
}

for(; pi != end; pi += 8) {
m = U8TO64_LE(pi);
v3 ^= m;
for (i = 0; i < ctx->crounds; i++) {
SIPROUND;
}
v0 ^= m;
}

switch (left) {
case 7:
b |= ((uint64_t)pi[6]) << 48;
case 6:
b |= ((uint64_t)pi[5]) << 40;
case 5:
b |= ((uint64_t)pi[4]) << 32;
case 4:
b |= ((uint64_t)pi[3]) << 24;
case 3:
b |= ((uint64_t)pi[2]) << 16;
case 2:
b |= ((uint64_t)pi[1]) << 8;
case 1:
b |= ((uint64_t)pi[0]);
break;
case 0:
break;
}

v3 ^= b;
for (i = 0; i < ctx->crounds; ++i)
SIPROUND;
v0 ^= b;

if (outlen == 16) {
v2 ^= 0xee;
} else {
v2 ^= 0xff;
}

for (i = 0; i < ctx->drounds; ++i)
SIPROUND;

b = v0 ^ v1 ^ v2 ^ v3;
U64TO8_LE(out, b);
if (outlen == 8) {
return XQC_OK;
}
v1 ^= 0xdd;
for (i = 0; i < ctx->drounds; ++i)
SIPROUND;
b = v0 ^ v1 ^ v2 ^ v3;
U64TO8_LE(out + 8, b);
return XQC_OK;
}




#endif /* _XQC_SIPHASH_H_INCLUDED_ */
88 changes: 87 additions & 1 deletion src/common/xqc_str_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,21 @@
#define _XQC_STR_HASH_INCLUDED_

#include <stdint.h>
#include <time.h>

#include "src/common/xqc_str.h"
#include "src/common/xqc_siphash.h"
#include "src/common/xqc_log.h"

/*
* default log threshold of number of element in on bucket
* test result of max conflict (default 1024*1024 hash buckets) in one bucket:
* 100000 connections, the max conflict is 5
* 1000000 connections, the max conflict is 24
*/
#define XQC_HASH_DEFAULT_CONFLICT_THRESHOLD 50
/*10 second, log interval must not less then 10 second*/
#define XQC_HASH_CONFLICT_LOG_INTERVAL 10

typedef struct xqc_str_hash_element_s {
uint64_t hash;
Expand All @@ -24,20 +37,79 @@ typedef struct xqc_str_hash_table_s {
xqc_str_hash_node_t **list;
size_t count;
xqc_allocator_t allocator; /* memory allocator */
uint8_t *conflict_stat; /* statistic the number of elements in every bucket */
xqc_siphash_ctx_t siphash_ctx; /* siphash context */
uint32_t conflict_thres; /* conflict threshold in one bucket, warning if exceeded */
time_t last_log_time; /* last timestamp(second) for logging the max conflict value*/
xqc_log_t *log; /* pointer to engine's log*/
} xqc_str_hash_table_t;

/* calculate the hash value using the siphash algorithm */
static inline uint64_t
xqc_siphash_get_hash(xqc_siphash_ctx_t *ctx, const uint8_t *data, size_t len)
{
uint64_t hash_value;
if (xqc_siphash(ctx, data, len, (uint8_t *)(&hash_value), sizeof(hash_value)) == XQC_OK) {
return hash_value;
}
/*
* impossible, we set hash_size value 8 when we call xqc_siphash_init, sizeof(hash_value) is always 8
* xqc_siphash return XQC_ERROR only when hash_size not equal sizeof(hash_value), it is impossible here
*/
return 0;
}

static inline int
xqc_str_hash_init(xqc_str_hash_table_t *hash_tab, xqc_allocator_t allocator, size_t bucket_num)
xqc_str_hash_init(xqc_str_hash_table_t *hash_tab,
xqc_allocator_t allocator, size_t bucket_num,
uint32_t conflict_thres, uint8_t *key,
size_t key_len, xqc_log_t *log)
{
if (bucket_num == 0) { /* impossible */
return XQC_ERROR;
}
if (key_len != XQC_SIPHASH_KEY_SIZE) { /* siphash key length must be 16 */
return XQC_ERROR;
}
if (log == NULL) {
return XQC_ERROR;
}
xqc_memzero(hash_tab, sizeof(xqc_str_hash_table_t));
hash_tab->allocator = allocator;
hash_tab->list = allocator.malloc(allocator.opaque, sizeof(xqc_str_hash_node_t *) * bucket_num);
if (hash_tab->list == NULL) {
return XQC_ERROR;
}
xqc_memzero(hash_tab->list, sizeof(xqc_str_hash_node_t *) * bucket_num);
hash_tab->count = bucket_num;
hash_tab->conflict_stat = allocator.malloc(allocator.opaque, sizeof(uint8_t) * bucket_num);
if (hash_tab->conflict_stat == NULL) {
goto fail;
}
xqc_memzero(hash_tab->conflict_stat, sizeof(uint8_t) * bucket_num);
if (conflict_thres > 0) {
hash_tab->conflict_thres = conflict_thres;
} else {
hash_tab->conflict_thres = XQC_HASH_DEFAULT_CONFLICT_THRESHOLD;
}
hash_tab->last_log_time = 0;
hash_tab->log = log;
if (xqc_siphash_init(&hash_tab->siphash_ctx, key, key_len,
XQC_DEFAULT_HASH_SIZE, XQC_SIPHASH_C_ROUNDS,
XQC_SIPHASH_D_ROUNDS) != XQC_OK)
{
goto fail;
}
return XQC_OK;

fail:
if (hash_tab->list) {
allocator.free(allocator.opaque, hash_tab->list);
}
if (hash_tab->conflict_stat) {
allocator.free(allocator.opaque, hash_tab->conflict_stat);
}
return XQC_ERROR;
}

static inline void
Expand All @@ -53,6 +125,7 @@ xqc_str_hash_release(xqc_str_hash_table_t *hash_tab)
}
}
a->free(a->opaque, hash_tab->list);
a->free(a->opaque, hash_tab->conflict_stat);
}

static inline void *
Expand Down Expand Up @@ -90,6 +163,16 @@ xqc_str_hash_add(xqc_str_hash_table_t *hash_tab, xqc_str_hash_element_t e)

node->next = hash_tab->list[index];
hash_tab->list[index] = node;
hash_tab->conflict_stat[index] += 1;
if (hash_tab->conflict_stat[index] > hash_tab->conflict_thres) {
time_t now_sec = time(NULL);
if (now_sec >= hash_tab->last_log_time + XQC_HASH_CONFLICT_LOG_INTERVAL) {
xqc_log(hash_tab->log, XQC_LOG_WARN,
"|xqc conn hash conflict exceed|index:%ui, number of elements:%d|",
index, hash_tab->conflict_stat[index]);
hash_tab->last_log_time = now_sec;
}
}

return XQC_OK;
}
Expand All @@ -104,6 +187,9 @@ xqc_str_hash_delete(xqc_str_hash_table_t *hash_tab, uint64_t hash, xqc_str_t str
while (node) {
if (node->element.hash == hash && xqc_str_equal(str, node->element.str)) {
*pp = node->next;
if (hash_tab->conflict_stat[index] > 0) {
hash_tab->conflict_stat[index] -= 1;
}
a->free(a->opaque, node->element.str.data);
a->free(a->opaque, node);
return XQC_OK;
Expand Down
Loading
Loading