/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "zbxcommon.h" #include "string.h" #include <time.h> #include <stdlib.h> #ifdef HAVE_SYS_UTSNAME_H # include <sys/utsname.h> #endif #define CUID_PID_BLOCK_SIZE 2 #define CUID_HOSTNAME_BLOCK_SIZE 2 #define CUID_BLOCK_SIZE 4 #define CUID_BASE_36 36 #define DISCRETE_VALUES 1679616 #define CUID_TIMESTAMP_SIZE 8 #define PID_TMP_36_BASE_BUF_LEN 10 #define HOST_TMP_36_BASE_BUF_LEN 10 #define RAND_TMP_36_BASE_BUF_LEN 10 #define PAD_FILL_CHAR '0' static char host_block[HOST_TMP_36_BASE_BUF_LEN]; /****************************************************************************** * * * Purpose: modify string in-place to the specified length as required by * * CUID algorithm * * * * Parameters: * * input - [IN/OUT] input string and result * * input_alloc - [IN] buffer size where the input string is stored. It * * should be larger than 'pad_size'. * * pad_size - [IN] target length * * fill_char - [IN] character used to fill target string if there are * * not enough existing characters * * * ******************************************************************************/ static void pad(char *input, size_t input_alloc, size_t pad_size, char fill_char) { size_t input_len; if (pad_size == (input_len = strlen(input))) return; if (pad_size > input_len) /* make string longer by prepending fill_char s */ { if (pad_size + 1 > input_alloc || input_len + 1 > input_alloc) { THIS_SHOULD_NEVER_HAPPEN; return; } memmove(input + pad_size - input_len, input, input_len); memset(input, fill_char, pad_size - input_len); } else /* make string shorter by keeping only last characters */ memmove(input, input + input_len - pad_size, pad_size); input[pad_size] = '\0'; } static char base36_digit(size_t num) { if (num <= 9) return (char)(num + '0'); return (char)(num - 10 + 'a'); } static void str_rev(char *str) { size_t len, i; char temp; len = strlen(str); for (i = 0; i < len/2; i++) { temp = str[i]; str[i] = str[len - i - 1]; str[len - i - 1] = temp; } } static void from_decimal(char *res, size_t base, zbx_uint64_t input_num) { zbx_uint64_t index = 0; if (0 == input_num) { res[0] = '0'; res[1] = '\0'; } else { while (0 < input_num) { res[index++] = base36_digit(input_num % base); input_num /= base; } res[index] = '\0'; } str_rev(res); } /****************************************************************************** * * * Purpose: initializes context for the cuid generation * * * ******************************************************************************/ static void zbx_cuid_init(void) { char *hostname; zbx_uint64_t hostname_num, hostname_len, i; struct utsname name; srand((unsigned int)time(NULL) + (unsigned int)getpid()); if (-1 == uname(&name)) hostname = zbx_strdup(NULL, "dummy"); else hostname = zbx_strdup(NULL, name.nodename); hostname_len = strlen(hostname); hostname_num = hostname_len + CUID_BASE_36; for (i = 0; i < hostname_len; i++) hostname_num = hostname_num + (zbx_uint64_t)hostname[i]; from_decimal(host_block, CUID_BASE_36, hostname_num); pad(host_block, sizeof(host_block), CUID_HOSTNAME_BLOCK_SIZE, PAD_FILL_CHAR); zbx_free(hostname); } static size_t next(void) { size_t out; static int counter_value = -1; if (-1 == counter_value) zbx_cuid_init(); counter_value++; out = (size_t)counter_value; if (counter_value >= DISCRETE_VALUES) counter_value = 0; return out; } /****************************************************************************** * * * Purpose: generates cuid, is based on the go cuid implementation from * * https://github.com/lucsky/cuid/blob/master/cuid.go * * consider using mutexes around it if used inside threads * * * * Parameters: cuid - [OUT] resulting cuid * * * ******************************************************************************/ void zbx_new_cuid(char *cuid) { char rand_block_1[RAND_TMP_36_BASE_BUF_LEN + 1], rand_block_2[RAND_TMP_36_BASE_BUF_LEN + 1], fingerprint[CUID_BLOCK_SIZE + 1], timestamp[CUID_TIMESTAMP_SIZE + 1], counter[CUID_BLOCK_SIZE+1], pid_block[PID_TMP_36_BASE_BUF_LEN]; struct timeval current_time; from_decimal(counter, CUID_BASE_36, (zbx_uint64_t)next()); pad(counter, sizeof(counter), CUID_BLOCK_SIZE, PAD_FILL_CHAR); from_decimal(pid_block, CUID_BASE_36, (zbx_uint64_t)getpid()); pad(pid_block, sizeof(pid_block), CUID_PID_BLOCK_SIZE, PAD_FILL_CHAR); gettimeofday(¤t_time, NULL); from_decimal(timestamp, CUID_BASE_36, ((zbx_uint64_t)current_time.tv_sec * 1000 + current_time.tv_usec / 1000)); pad(timestamp, sizeof(timestamp), CUID_TIMESTAMP_SIZE, PAD_FILL_CHAR); from_decimal(rand_block_1, CUID_BASE_36, (zbx_uint64_t)rand()); pad(rand_block_1, sizeof(rand_block_1), CUID_BLOCK_SIZE, PAD_FILL_CHAR); from_decimal(rand_block_2, CUID_BASE_36, (zbx_uint64_t)rand()); pad(rand_block_2, sizeof(rand_block_2), CUID_BLOCK_SIZE, PAD_FILL_CHAR); zbx_snprintf(fingerprint, sizeof(fingerprint), "%s%s", pid_block, host_block); zbx_snprintf(cuid, CUID_LEN, "c%s%s%s%s%s", timestamp, counter, fingerprint, rand_block_1, rand_block_2); }