diff --git a/Makefile.am b/Makefile.am index 840d114c65..7caf7d51e8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -300,6 +300,7 @@ library_include_HEADERS = \ libs/libteletone/src/libteletone_detect.h \ libs/libteletone/src/libteletone_generate.h \ libs/libteletone/src/libteletone.h \ + src/include/switch_uuidv7.h \ src/include/switch_limit.h \ src/include/switch_odbc.h \ src/include/switch_hashtable.h \ @@ -394,7 +395,8 @@ libfreeswitch_la_SOURCES = \ libs/miniupnpc/minissdpc.c \ libs/miniupnpc/upnperrors.c \ libs/libnatpmp/natpmp.c \ - libs/libnatpmp/getgateway.c + libs/libnatpmp/getgateway.c \ + src/switch_uuidv7.c if ENABLE_CPP libfreeswitch_la_SOURCES += src/switch_cpp.cpp @@ -814,4 +816,3 @@ support: @cp support-d/.screenrc ~ @cp support-d/.bashrc ~ @test -f ~/.cc-mode-installed || sh support-d/install-cc-mode.sh && touch ~/.cc-mode-installed - diff --git a/conf/vanilla/autoload_configs/switch.conf.xml b/conf/vanilla/autoload_configs/switch.conf.xml index 8117d8ed9c..29ac39976f 100644 --- a/conf/vanilla/autoload_configs/switch.conf.xml +++ b/conf/vanilla/autoload_configs/switch.conf.xml @@ -64,6 +64,9 @@ + + + @@ -206,4 +209,3 @@ - diff --git a/src/include/private/switch_core_pvt.h b/src/include/private/switch_core_pvt.h index fafaae3cba..7507f5dc33 100644 --- a/src/include/private/switch_core_pvt.h +++ b/src/include/private/switch_core_pvt.h @@ -287,6 +287,7 @@ struct switch_runtime { char *event_channel_key_separator; uint32_t max_audio_channels; switch_call_cause_t shutdown_cause; + uint32_t uuid_version; }; extern struct switch_runtime runtime; diff --git a/src/include/switch_types.h b/src/include/switch_types.h index e71570c21d..294c64ee3f 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -2310,7 +2310,8 @@ typedef enum { SCSC_SESSIONS_PEAK, SCSC_SESSIONS_PEAK_FIVEMIN, SCSC_MDNS_RESOLVE, - SCSC_SHUTDOWN_CAUSE + SCSC_SHUTDOWN_CAUSE, + SCSC_UUID_VERSION } switch_session_ctl_t; typedef enum { diff --git a/src/include/switch_uuidv7.h b/src/include/switch_uuidv7.h new file mode 100644 index 0000000000..432e9635f9 --- /dev/null +++ b/src/include/switch_uuidv7.h @@ -0,0 +1,196 @@ +/* + * switch_uuidv7.h uuidv7 +*/ +#include + +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 199309L + +#include +#include + + +/** + * Indicates that the `unix_ts_ms` passed was used because no preceding UUID was + * specified. + */ +#define UUIDV7_STATUS_UNPRECEDENTED (0) + +/** + * Indicates that the `unix_ts_ms` passed was used because it was greater than + * the previous one. + */ +#define UUIDV7_STATUS_NEW_TIMESTAMP (1) + +/** + * Indicates that the counter was incremented because the `unix_ts_ms` passed + * was no greater than the previous one. + */ +#define UUIDV7_STATUS_COUNTER_INC (2) + +/** + * Indicates that the previous `unix_ts_ms` was incremented because the counter + * reached its maximum value. + */ +#define UUIDV7_STATUS_TIMESTAMP_INC (3) + +/** + * Indicates that the monotonic order of generated UUIDs was broken because the + * `unix_ts_ms` passed was less than the previous one by more than ten seconds. + */ +#define UUIDV7_STATUS_CLOCK_ROLLBACK (4) + +/** Indicates that an invalid `unix_ts_ms` is passed. */ +#define UUIDV7_STATUS_ERR_TIMESTAMP (-1) + +/** + * Indicates that the attempt to increment the previous `unix_ts_ms` failed + * because it had reached its maximum value. + */ +#define UUIDV7_STATUS_ERR_TIMESTAMP_OVERFLOW (-2) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Generates a new UUIDv7 from the given Unix time, random bytes, and previous + * UUID. + * + * @param uuid_out 16-byte byte array where the generated UUID is stored. + * @param unix_ts_ms Current Unix time in milliseconds. + * @param rand_bytes At least 10-byte byte array filled with random bytes. This + * function consumes the leading 4 bytes or the whole 10 + * bytes per call depending on the conditions. + * `uuidv7_status_n_rand_consumed()` maps the return value of + * this function to the number of random bytes consumed. + * @param uuid_prev 16-byte byte array representing the immediately preceding + * UUID, from which the previous timestamp and counter are + * extracted. This may be NULL if the caller does not care + * the ascending order of UUIDs within the same timestamp. + * This may point to the same location as `uuid_out`; this + * function reads the value before writing. + * @return One of the `UUIDV7_STATUS_*` codes that describe the + * characteristics of generated UUIDs. Callers can usually + * ignore the status unless they need to guarantee the + * monotonic order of UUIDs or fine-tune the generation + * process. + */ + +static inline int8_t uuidv7_generate(uint8_t *uuid_out, uint64_t unix_ts_ms,const uint8_t *rand_bytes,const uint8_t *uuid_prev) { + int8_t status; + uint64_t timestamp = 0; + static const uint64_t MAX_TIMESTAMP = ((uint64_t)1 << 48) - 1; + static const uint64_t MAX_COUNTER = ((uint64_t)1 << 42) - 1; + + if (unix_ts_ms > MAX_TIMESTAMP) { + return UUIDV7_STATUS_ERR_TIMESTAMP; + } + + + if (uuid_prev == NULL) { + status = UUIDV7_STATUS_UNPRECEDENTED; + timestamp = unix_ts_ms; + } else { + for (int i = 0; i < 6; i++) { + timestamp = (timestamp << 8) | uuid_prev[i]; + } + + if (unix_ts_ms > timestamp) { + status = UUIDV7_STATUS_NEW_TIMESTAMP; + timestamp = unix_ts_ms; + } else if (unix_ts_ms + 10000 < timestamp) { + // ignore prev if clock moves back by more than ten seconds + status = UUIDV7_STATUS_CLOCK_ROLLBACK; + timestamp = unix_ts_ms; + } else { + // increment prev counter + uint64_t counter = uuid_prev[6] & 0x0f; // skip ver + counter = (counter << 8) | uuid_prev[7]; + counter = (counter << 6) | (uuid_prev[8] & 0x3f); // skip var + counter = (counter << 8) | uuid_prev[9]; + counter = (counter << 8) | uuid_prev[10]; + counter = (counter << 8) | uuid_prev[11]; + + if (counter++ < MAX_COUNTER) { + status = UUIDV7_STATUS_COUNTER_INC; + uuid_out[6] = counter >> 38; // ver + bits 0-3 + uuid_out[7] = counter >> 30; // bits 4-11 + uuid_out[8] = counter >> 24; // var + bits 12-17 + uuid_out[9] = counter >> 16; // bits 18-25 + uuid_out[10] = counter >> 8; // bits 26-33 + uuid_out[11] = counter; // bits 34-41 + } else { + // increment prev timestamp at counter overflow + status = UUIDV7_STATUS_TIMESTAMP_INC; + timestamp++; + if (timestamp > MAX_TIMESTAMP) { + return UUIDV7_STATUS_ERR_TIMESTAMP_OVERFLOW; + } + } + } + } + + uuid_out[0] = timestamp >> 40; + uuid_out[1] = timestamp >> 32; + uuid_out[2] = timestamp >> 24; + uuid_out[3] = timestamp >> 16; + uuid_out[4] = timestamp >> 8; + uuid_out[5] = timestamp; + + for (int i = (status == UUIDV7_STATUS_COUNTER_INC) ? 12 : 6; i < 16; i++) { + uuid_out[i] = *rand_bytes++; + } + + uuid_out[6] = 0x70 | (uuid_out[6] & 0x0f); // set ver + uuid_out[8] = 0x80 | (uuid_out[8] & 0x3f); // set var + + return status; +} + +/** + * Determines the number of random bytes consumsed by `uuidv7_generate()` from + * the `UUIDV7_STATUS_*` code returned. + * + * @param status `UUIDV7_STATUS_*` code returned by `uuidv7_generate()`. + * @return `4` if `status` is `UUIDV7_STATUS_COUNTER_INC` or `10` + * otherwise. + */ +static inline int uuidv7_status_n_rand_consumed(int8_t status) { + return status == UUIDV7_STATUS_COUNTER_INC ? 4 : 10; +} + + +/** + * @name High-level APIs that require platform integration + */ + +/** + * Generates a new UUIDv7 with the current Unix time. + * + * This declaration defines the interface to generate a new UUIDv7 with the + * current time, default random number generator, and global shared state + * holding the previously generated UUID. Since this single-file library does + * not provide platform-specific implementations, users need to prepare a + * concrete implementation (if necessary) by integrating a real-time clock, + * cryptographically strong random number generator, and shared state storage + * available in the target platform. + * + * @param uuid_out 16-byte byte array where the generated UUID is stored. + * @return One of the `UUIDV7_STATUS_*` codes that describe the + * characteristics of generated UUIDs or an + * implementation-dependent code. Callers can usually ignore + * the `UUIDV7_STATUS_*` code unless they need to guarantee the + * monotonic order of UUIDs or fine-tune the generation + * process. The implementation-dependent code must be out of + * the range of `int8_t` and negative if it reports an error. + */ + +SWITCH_DECLARE(int) uuidv7_new(uint8_t *uuid_out); + + +#ifdef __cplusplus +} /* extern "C" { */ +#endif diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c index 5b9620a781..075f29aa11 100644 --- a/src/mod/applications/mod_commands/mod_commands.c +++ b/src/mod/applications/mod_commands/mod_commands.c @@ -2417,7 +2417,7 @@ SWITCH_STANDARD_API(uptime_function) return SWITCH_STATUS_SUCCESS; } -#define CTL_SYNTAX "[api_expansion [on|off]|recover|send_sighup|hupall|pause [inbound|outbound]|resume [inbound|outbound]|shutdown [cancel|elegant|asap|now|restart]|sps|sps_peak_reset|sync_clock|sync_clock_when_idle|reclaim_mem|max_sessions|min_dtmf_duration [num]|max_dtmf_duration [num]|default_dtmf_duration [num]|min_idle_cpu|loglevel [level]|debug_level [level]|mdns_resolve [enable|disable]]" +#define CTL_SYNTAX "[api_expansion [on|off]|recover|send_sighup|hupall|pause [inbound|outbound]|resume [inbound|outbound]|shutdown [cancel|elegant|asap|now|restart]|uuid_version [4|7]|sps|sps_peak_reset|sync_clock|sync_clock_when_idle|reclaim_mem|max_sessions|min_dtmf_duration [num]|max_dtmf_duration [num]|default_dtmf_duration [num]|min_idle_cpu|loglevel [level]|debug_level [level]|mdns_resolve [enable|disable]]" SWITCH_STANDARD_API(ctl_function) { int argc; @@ -2661,6 +2661,14 @@ SWITCH_STANDARD_API(ctl_function) } switch_core_session_ctl(SCSC_SPS, &arg); stream->write_function(stream, "+OK sessions per second: %d\n", arg); + } else if (!strcasecmp(argv[0], "uuid_version")) { + if (argc > 1) { + arg = atoi(argv[1]); + } else { + arg = 0; + } + switch_core_session_ctl(SCSC_UUID_VERSION, &arg); + stream->write_function(stream, "+OK set uuid version: %d\n", arg); } else if (!strcasecmp(argv[0], "sync_clock")) { arg = 0; switch_core_session_ctl(SCSC_SYNC_CLOCK, &arg); @@ -7808,6 +7816,9 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load) switch_console_set_complete("add fsctl send_sighup"); switch_console_set_complete("add fsctl mdns_resolve disable"); switch_console_set_complete("add fsctl mdns_resolve enable"); + switch_console_set_complete("add fsctl uuid_version"); + switch_console_set_complete("add fsctl uuid_version 4"); + switch_console_set_complete("add fsctl uuid_version 7"); switch_console_set_complete("add interface_ip auto ::console::list_interfaces"); switch_console_set_complete("add interface_ip ipv4 ::console::list_interfaces"); switch_console_set_complete("add interface_ip ipv6 ::console::list_interfaces"); diff --git a/src/switch_apr.c b/src/switch_apr.c index bd6cfdec56..f20bcb4bc5 100644 --- a/src/switch_apr.c +++ b/src/switch_apr.c @@ -89,6 +89,7 @@ #ifndef WIN32 #include #endif +#include /* apr stubs */ @@ -1153,7 +1154,11 @@ SWITCH_DECLARE(void) switch_uuid_get(switch_uuid_t *uuid) { switch_mutex_lock(runtime.uuid_mutex); #ifndef WIN32 - uuid_generate(uuid->data); + if (runtime.uuid_version == 7) { + uuidv7_new(uuid->data); + } else { + uuid_generate(uuid->data); + } #else UuidCreate((UUID *) uuid); #endif diff --git a/src/switch_core.c b/src/switch_core.c index 7ec10d8885..cf4abf731c 100644 --- a/src/switch_core.c +++ b/src/switch_core.c @@ -1904,6 +1904,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_init(switch_core_flag_t flags, switc load_mime_types(); runtime.flags |= flags; runtime.sps_total = 30; + runtime.uuid_version = 4; *err = NULL; @@ -2212,6 +2213,8 @@ static void switch_load_core_config(const char *file) switch_time_set_use_system_time(switch_true(val)); } else if (!strcasecmp(var, "enable-monotonic-timing")) { switch_time_set_monotonic(switch_true(val)); + } else if (!strcasecmp(var, "uuid-version") && !zstr(val)) { + runtime.uuid_version = atoi(val); } else if (!strcasecmp(var, "enable-softtimer-timerfd")) { int ival = 0; if (val) { @@ -2963,7 +2966,12 @@ SWITCH_DECLARE(int32_t) switch_core_session_ctl(switch_session_ctl_t cmd, void * newintval = runtime.sps_total; switch_mutex_unlock(runtime.throttle_mutex); break; - + case SCSC_UUID_VERSION: + if(oldintval > 0){ + runtime.uuid_version = oldintval; + } + newintval = runtime.uuid_version; + break; case SCSC_RECLAIM: switch_core_memory_reclaim_all(); newintval = 0; diff --git a/src/switch_uuidv7.c b/src/switch_uuidv7.c new file mode 100644 index 0000000000..cf855dc099 --- /dev/null +++ b/src/switch_uuidv7.c @@ -0,0 +1,40 @@ + + +#include "switch_uuidv7.h" + +// #include +// #include +// #include +// #include +// #include +#ifdef __APPLE__ +#include // for macOS getentropy() +#endif + +SWITCH_DECLARE(int) uuidv7_new(uint8_t *uuid_out) +{ + int8_t status; + // struct timespec tp; + static uint8_t uuid_prev[16] = {0}; + static uint8_t rand_bytes[256] = {0}; + static size_t n_rand_consumed = sizeof(rand_bytes); + + uint64_t unix_ts_ms ; + // clock_gettime(CLOCK_REALTIME, &tp); + // unix_ts_ms = (uint64_t)tp.tv_sec * 1000 + tp.tv_nsec / 1000000; + unix_ts_ms = switch_time_now() / 1000; + + if (n_rand_consumed > sizeof(rand_bytes) - 10) + { + getentropy(rand_bytes, n_rand_consumed); + n_rand_consumed = 0; + } + + status = uuidv7_generate(uuid_prev, unix_ts_ms, + &rand_bytes[n_rand_consumed], uuid_prev); + n_rand_consumed += uuidv7_status_n_rand_consumed(status); + + memcpy(uuid_out, uuid_prev, 16); + return status; + +} diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore index 470c5ded12..de3e635bf2 100644 --- a/tests/unit/.gitignore +++ b/tests/unit/.gitignore @@ -43,6 +43,10 @@ switch_xml switch_estimators switch_jitter_buffer test_sofia +switch_core_asr +switch_core_media +switch_sip +switch_rtp_pcap .deps/ Makefile conf/*/ diff --git a/tests/unit/switch_core.c b/tests/unit/switch_core.c index 295e4e0ff1..0f7dbb1f78 100644 --- a/tests/unit/switch_core.c +++ b/tests/unit/switch_core.c @@ -32,6 +32,11 @@ #include #include +#include +// #include +#include +#include + #if defined(HAVE_OPENSSL) #include #endif @@ -568,6 +573,52 @@ FST_CORE_BEGIN("./conf") fst_check(switch_channel_get_variable_buf(channel, "test_var_does_not_exist", buf, sizeof(buf)) == SWITCH_STATUS_FALSE); } FST_SESSION_END() + + FST_TEST_BEGIN(test_create_uuid) + { + switch_uuid_t uuid; + switch_uuid_t uuid2; + char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1] = { 0 }; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "test_create_uuid:\n"); + uuidv7_new(uuid.data); + switch_uuid_format(uuid_str, &uuid); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "uuidv7: %s\n", uuid_str); + uuidv7_new(uuid.data); + switch_uuid_format(uuid_str, &uuid); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "uuidv7: %s\n", uuid_str); + fst_check(0 != memcmp(uuid.data, uuid2.data, sizeof(uuid.data))); + uuid_generate(uuid.data); + switch_uuid_format(uuid_str, &uuid); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "uuidv4: %s\n", uuid_str); + } + FST_TEST_END() + + FST_TEST_BEGIN(test_create_uuid_speed) + { + int n; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "test_create_uuid_speed:\n"); + for (n = 1; n < 4; n++) { + clock_t start = 0, end = 0; + switch_uuid_t uuid; + double cpu_time_used = 0; + start = clock(); + for (int i = 0; i < 1000 * pow(10, n); i++) { + uuidv7_new(uuid.data); + } + end = clock(); + cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%d uuidv7_new running time: %f seconds\n", 1000 * (int)pow(10, n), cpu_time_used); + + start = clock(); + for (long long int i = 0; i < 1000 * pow(10, n); i++) { + uuid_generate(uuid.data); + } + end = clock(); + cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%d uuid_generate running time: %f seconds\n", 1000 * (int)pow(10, n), cpu_time_used); + } + } + FST_TEST_END() } FST_SUITE_END() }