]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Feature] Add native UUID v7 per task with Lua binding
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 14 Feb 2026 15:35:51 +0000 (15:35 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 14 Feb 2026 15:35:51 +0000 (15:35 +0000)
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

src/libserver/task.c
src/libserver/task.h
src/libutil/util.c
src/libutil/util.h
src/lua/lua_task.c

index bfe3d5e3b6da4cad3deb41761cc636ebf03b5709..768d306c303c1bef743564e058c70962e8b65421 100644 (file)
@@ -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;
index 27fe463076d9494bd971814cbcbfcc43db5f4c9c..9b14eb52e446dd247a2c9e691ce9726a2cd6270e 100644 (file)
@@ -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                                    */
index b585a26901f88b5eb5f8d44d73c394f03506b617..9c1ea18fe9cd2cc7355493919e96cdb81dc716f9 100644 (file)
@@ -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;
index 8531ce4509757bfc9b612cf4dd09aea449c2fda1..34b7a685abbba4206588e29bf2f5a76ce1a30d03 100644 (file)
@@ -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
index 0043ba3f29caa18c2952a8510bba15d45460c7f5..239bd896e79db85c6f69a40e9ab0239e74b0d298 100644 (file)
@@ -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)
 {