From: Vsevolod Stakhov Date: Sat, 14 Feb 2026 15:35:51 +0000 (+0000) Subject: [Feature] Add native UUID v7 per task with Lua binding X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=700a21ef7b68066f6d38f36f3ea4226563c3ca1d;p=thirdparty%2Frspamd.git [Feature] Add native UUID v7 per task with Lua binding Generate a UUID v7 (time-ordered, RFC 9562) natively in C at task creation time. The task's log UID is derived from the UUID's random portion so that UID and UUID are always correlated. - rspamd_uuid_v7() in util.c: 48-bit ms timestamp + crypto-random bits - task_uuid[37] field in rspamd_task, populated in rspamd_task_new() - task:get_uuid() Lua method for plugin access --- diff --git a/src/libserver/task.c b/src/libserver/task.c index bfe3d5e3b6..768d306c30 100644 --- a/src/libserver/task.c +++ b/src/libserver/task.c @@ -180,6 +180,8 @@ rspamd_task_new(struct rspamd_worker *worker, new_task->event_loop = event_loop; new_task->task_timestamp = ev_time(); new_task->time_real_finish = NAN; + rspamd_uuid_v7(new_task->task_uuid, new_task->task_pool->tag.uid, + sizeof(new_task->task_pool->tag.uid), new_task->task_timestamp); new_task->request_headers = kh_init(rspamd_req_headers_hash); new_task->sock = -1; diff --git a/src/libserver/task.h b/src/libserver/task.h index 27fe463076..9b14eb52e4 100644 --- a/src/libserver/task.h +++ b/src/libserver/task.h @@ -206,6 +206,7 @@ struct rspamd_task { rspamd_mempool_t *task_pool; /**< memory pool for task */ double time_real_finish; ev_tstamp task_timestamp; + char task_uuid[37]; /**< UUID v7 for cross-system correlation */ gboolean (*fin_callback)(struct rspamd_task *task, void *arg); /**< callback for filters finalizing */ diff --git a/src/libutil/util.c b/src/libutil/util.c index b585a26901..9c1ea18fe9 100644 --- a/src/libutil/util.c +++ b/src/libutil/util.c @@ -1604,6 +1604,69 @@ void rspamd_random_hex(char *buf, uint64_t len) } } +int rspamd_uuid_v7(char uuid_out[37], char *opt_uid_buf, gsize uid_buflen, double timestamp) +{ + uint8_t uuid[16]; + uint8_t rand_bytes[10]; + char hex[32]; + uint64_t ms = (uint64_t) (timestamp * 1000.0); + + ottery_rand_bytes(rand_bytes, sizeof(rand_bytes)); + + /* Bytes 0-5: 48-bit millisecond timestamp, big-endian */ + uuid[0] = (ms >> 40) & 0xff; + uuid[1] = (ms >> 32) & 0xff; + uuid[2] = (ms >> 24) & 0xff; + uuid[3] = (ms >> 16) & 0xff; + uuid[4] = (ms >> 8) & 0xff; + uuid[5] = ms & 0xff; + + /* Bytes 6-7: version 7 (0111) + 12 random bits */ + uuid[6] = 0x70 | (rand_bytes[0] & 0x0f); + uuid[7] = rand_bytes[1]; + + /* Bytes 8-9: variant 10 + 14 random bits */ + uuid[8] = 0x80 | (rand_bytes[2] & 0x3f); + uuid[9] = rand_bytes[3]; + + /* Bytes 10-15: 48 random bits */ + uuid[10] = rand_bytes[4]; + uuid[11] = rand_bytes[5]; + uuid[12] = rand_bytes[6]; + uuid[13] = rand_bytes[7]; + uuid[14] = rand_bytes[8]; + uuid[15] = rand_bytes[9]; + + /* Encode all 16 bytes as 32 hex chars, then insert dashes */ + rspamd_encode_hex_buf(uuid, 16, hex, sizeof(hex)); + + /* Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx */ + memcpy(uuid_out, hex, 8); + uuid_out[8] = '-'; + memcpy(uuid_out + 9, hex + 8, 4); + uuid_out[13] = '-'; + memcpy(uuid_out + 14, hex + 12, 4); + uuid_out[18] = '-'; + memcpy(uuid_out + 19, hex + 16, 4); + uuid_out[23] = '-'; + memcpy(uuid_out + 24, hex + 20, 12); + uuid_out[36] = '\0'; + + /* Derive log UID from bytes 8-15 (variant + random portion) */ + if (opt_uid_buf) { + /* 8 binary bytes -> 16 hex chars + NUL = 17 bytes minimum */ + if (uid_buflen < 17) { + return -1; + } + int enc_len = rspamd_encode_hex_buf(&uuid[8], 8, + opt_uid_buf, uid_buflen - 1); + opt_uid_buf[enc_len] = '\0'; + return enc_len; + } + + return 0; +} + int rspamd_shmem_mkstemp(char *pattern) { int fd = -1; diff --git a/src/libutil/util.h b/src/libutil/util.h index 8531ce4509..34b7a685ab 100644 --- a/src/libutil/util.h +++ b/src/libutil/util.h @@ -379,6 +379,17 @@ uint64_t rspamd_hash_seed(void); */ void rspamd_random_hex(char *buf, uint64_t len); +/** + * Generate a UUID v7 (time-ordered) string. + * @param uuid_out output buffer for the 36-char UUID string + NUL + * @param opt_uid_buf if not NULL, filled with hex log UID derived from UUID's random part + * @param uid_buflen size of opt_uid_buf in bytes (ignored when opt_uid_buf is NULL) + * @param timestamp wall-clock time as double (e.g. from ev_time()) + * @return number of UID chars written to opt_uid_buf, 0 if opt_uid_buf is NULL, + * -1 if uid_buflen is too small + */ +int rspamd_uuid_v7(char uuid_out[37], char *opt_uid_buf, gsize uid_buflen, double timestamp); + /** * Returns * @param pattern pattern to create (should end with some number of X symbols), modified by this function diff --git a/src/lua/lua_task.c b/src/lua/lua_task.c index 0043ba3f29..239bd896e7 100644 --- a/src/lua/lua_task.c +++ b/src/lua/lua_task.c @@ -497,6 +497,11 @@ LUA_FUNCTION_DEF(task, get_queue_id); * Returns ID of the task being processed. */ LUA_FUNCTION_DEF(task, get_uid); +/*** + * @method task:get_uuid() + * Returns UUID v7 of the task (time-ordered, suitable for cross-system correlation). + */ +LUA_FUNCTION_DEF(task, get_uuid); /*** * @method task:get_resolver() * Returns ready to use rspamd_resolver object suitable for making asynchronous DNS requests. @@ -1364,6 +1369,7 @@ static const struct luaL_reg tasklib_m[] = { LUA_INTERFACE_DEF(task, get_received_headers), LUA_INTERFACE_DEF(task, get_queue_id), LUA_INTERFACE_DEF(task, get_uid), + LUA_INTERFACE_DEF(task, get_uuid), LUA_INTERFACE_DEF(task, get_resolver), LUA_INTERFACE_DEF(task, set_resolver), LUA_INTERFACE_DEF(task, inc_dns_req), @@ -3646,6 +3652,22 @@ lua_task_get_uid(lua_State *L) return 1; } +static int +lua_task_get_uuid(lua_State *L) +{ + LUA_TRACE_POINT; + struct rspamd_task *task = lua_check_task(L, 1); + + if (task) { + lua_pushstring(L, task->task_uuid); + } + else { + return luaL_error(L, "invalid arguments"); + } + + return 1; +} + static int lua_task_get_resolver(lua_State *L) {