ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
discovery/strings.c
Go to the documentation of this file.
1
15#include <ascii-chat/uthash.h>
17#include <string.h>
18#include <stdlib.h>
19#include <stdio.h>
20#include <sodium.h>
21#define PCRE2_CODE_UNIT_WIDTH 8
22#include <pcre2.h>
24#include <ascii-chat/log/log.h>
25
27
28// ============================================================================
29// Word Cache Implementation (Hashtable for O(1) validation)
30// ============================================================================
31
35typedef struct {
36 char *word;
37 UT_hash_handle hh;
39
43static word_cache_entry_t *g_adjectives_cache = NULL;
44static word_cache_entry_t *g_nouns_cache = NULL;
45// Lifecycle for thread-safe one-time cache initialization (replaces C11 call_once)
46static lifecycle_t g_cache_lc = LIFECYCLE_INIT;
47
54 // Shutdown if initialized
55 if (!lifecycle_shutdown(&g_cache_lc)) {
56 return; // Not initialized
57 }
58
59 // Cleanup adjectives cache
60 word_cache_entry_t *adj_entry, *adj_tmp;
61 HASH_ITER(hh, g_adjectives_cache, adj_entry, adj_tmp) {
62 HASH_DEL(g_adjectives_cache, adj_entry);
63 SAFE_FREE(adj_entry->word);
64 SAFE_FREE(adj_entry);
65 }
66 g_adjectives_cache = NULL;
67
68 // Cleanup nouns cache
69 word_cache_entry_t *noun_entry, *noun_tmp;
70 HASH_ITER(hh, g_nouns_cache, noun_entry, noun_tmp) {
71 HASH_DEL(g_nouns_cache, noun_entry);
72 SAFE_FREE(noun_entry->word);
73 SAFE_FREE(noun_entry);
74 }
75 g_nouns_cache = NULL;
76
77 log_dev("Session string word cache cleaned up");
78}
79
90static void do_build_validation_caches(void) {
91 // Build adjectives cache
92 for (size_t i = 0; i < adjectives_count; i++) {
94 if (!entry) {
95 log_warn("Failed to allocate adjectives cache entry - validation will be slower");
96 return;
97 }
98
99 size_t word_len = strlen(adjectives[i]) + 1;
100 entry->word = SAFE_MALLOC(word_len, char *);
101 if (!entry->word) {
102 SAFE_FREE(entry);
103 log_warn("Failed to allocate memory for adjective word - validation will be slower");
104 return;
105 }
106 memcpy(entry->word, adjectives[i], word_len);
107
108 HASH_ADD_KEYPTR(hh, g_adjectives_cache, entry->word, strlen(entry->word), entry);
109 }
110
111 // Build nouns cache
112 for (size_t i = 0; i < nouns_count; i++) {
114 if (!entry) {
115 log_warn("Failed to allocate nouns cache entry - validation will be slower");
116 return;
117 }
118
119 size_t word_len = strlen(nouns[i]) + 1;
120 entry->word = SAFE_MALLOC(word_len, char *);
121 if (!entry->word) {
122 SAFE_FREE(entry);
123 log_warn("Failed to allocate memory for noun word - validation will be slower");
124 return;
125 }
126 memcpy(entry->word, nouns[i], word_len);
127
128 HASH_ADD_KEYPTR(hh, g_nouns_cache, entry->word, strlen(entry->word), entry);
129 }
130
131 log_dev("Session string word cache initialized (%zu adjectives, %zu nouns)", adjectives_count, nouns_count);
132}
133
138static void build_validation_caches(void) {
139 if (!lifecycle_init_once(&g_cache_lc)) {
140 // Already initialized or in progress - spin-wait for completion
141 while (!lifecycle_is_initialized(&g_cache_lc)) {
142 // Spin until initialization completes
143 }
144 return;
145 }
146
147 // Winner: do the initialization
148 do_build_validation_caches();
149 lifecycle_init_commit(&g_cache_lc);
150}
151
152// ============================================================================
153// PCRE2 Session String Format Validator
154// ============================================================================
155
169static const char *SESSION_STRING_FORMAT_PATTERN = "^[a-z]{2,12}-[a-z]{2,12}-[a-z]{2,12}$";
170
171static pcre2_code *g_session_format_regex = NULL;
172static lifecycle_t g_regex_lc = LIFECYCLE_INIT;
173
177static void session_regex_init_once(void) {
178 int error_code;
179 PCRE2_SIZE error_offset;
180 g_session_format_regex = pcre2_compile((PCRE2_SPTR8)SESSION_STRING_FORMAT_PATTERN, PCRE2_ZERO_TERMINATED, 0,
181 &error_code, &error_offset, NULL);
182
183 if (!g_session_format_regex) {
184 PCRE2_UCHAR error_buf[256];
185 pcre2_get_error_message(error_code, error_buf, sizeof(error_buf));
186 log_error("Failed to compile session string regex at offset %zu: %s", error_offset, (const char *)error_buf);
187 return;
188 }
189
190 // Compile to JIT for 5-10x performance boost
191 int jit_ret = pcre2_jit_compile(g_session_format_regex, PCRE2_JIT_COMPLETE);
192 if (jit_ret < 0) {
193 log_warn("Session string regex JIT compilation failed (code %d), falling back to interpreter", jit_ret);
194 }
195
196 log_dev("Session string format validator initialized");
197}
198
203static pcre2_code *session_format_regex_get(void) {
204 if (!lifecycle_init_once(&g_regex_lc)) {
205 // Already initialized or in progress - spin-wait for completion
206 while (!lifecycle_is_initialized(&g_regex_lc)) {
207 // Spin until initialization completes
208 }
209 return g_session_format_regex;
210 }
211
212 // Winner: do the initialization
213 session_regex_init_once();
214 lifecycle_init_commit(&g_regex_lc);
215 return g_session_format_regex;
216}
217
221
222asciichat_error_t acds_string_generate(char *output, size_t output_size) {
223 if (!output || output_size < SESSION_STRING_BUFFER_SIZE) {
224 return SET_ERRNO(ERROR_INVALID_PARAM, "output buffer must be at least %zu bytes",
226 }
227
228 // libsodium is guaranteed to be initialized by acds_string_init() before this is called
229 // No need to call sodium_init() again (redundant initialization removed)
230
231 // Pick random adjective
232 uint32_t adj_idx = randombytes_uniform((uint32_t)adjectives_count);
233 const char *adj = adjectives[adj_idx];
234
235 // Pick two random nouns
236 uint32_t noun1_idx = randombytes_uniform((uint32_t)nouns_count);
237 uint32_t noun2_idx = randombytes_uniform((uint32_t)nouns_count);
238 const char *noun1 = nouns[noun1_idx];
239 const char *noun2 = nouns[noun2_idx];
240
241 // Format: adjective-noun-noun
242 int written = safe_snprintf(output, output_size, "%s-%s-%s", adj, noun1, noun2);
243 if (written < 0 || (size_t)written >= output_size) {
244 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Session string too long for buffer");
245 }
246
247 log_dev("Generated session string: %s", output);
248 return ASCIICHAT_OK;
249}
250
251bool is_session_string(const char *str) {
252 if (!str || str[0] == '\0') {
253 return false;
254 }
255
256 size_t len = strlen(str);
257
258 // Check length bounds (min 4, max defined by SESSION_STRING_MAX_LEN)
259 if (len < 4 || len > SESSION_STRING_MAX_LEN) {
260 return false;
261 }
262
263 // Session strings must be ASCII-only (homograph attack prevention)
264 if (!utf8_is_ascii_only(str)) {
265 return false;
266 }
267
268 // Get compiled regex (lazy initialization)
269 pcre2_code *regex = session_format_regex_get();
270 if (!regex) {
271 SET_ERRNO(ERROR_INTERNAL, "Session string PCRE2 regex failed to initialize");
272 return false;
273 }
274
275 // Validate format using PCRE2 regex
276 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
277 if (!match_data) {
278 log_error("Failed to allocate match data for session string regex");
279 SET_ERRNO(ERROR_MEMORY, "Failed to allocate match data");
280 return false;
281 }
282
283 int match_result = pcre2_jit_match(regex, (PCRE2_SPTR8)str, len, 0, 0, match_data, NULL);
284
285 pcre2_match_data_free(match_data);
286
287 if (match_result < 0) {
288 if (match_result != PCRE2_ERROR_NOMATCH) {
289 SET_ERRNO(ERROR_INTERNAL, "Session string PCRE2 matching failed: %d", match_result);
290 }
291 return false;
292 }
293
294 // Extract the three words by finding hyphens
295 // Format is guaranteed to be: word-word-word where each word is 2-12 lowercase letters
296 const char *hyphen1 = strchr(str, '-');
297 if (!hyphen1) {
298 return false;
299 }
300
301 const char *hyphen2 = strchr(hyphen1 + 1, '-');
302 if (!hyphen2) {
303 return false;
304 }
305
306 // Extract three words
307 size_t adj_len = hyphen1 - str;
308 size_t noun1_len = hyphen2 - hyphen1 - 1;
309 size_t noun2_len = len - (hyphen2 - str) - 1;
310
311 if (adj_len >= 32 || noun1_len >= 32 || noun2_len >= 32) {
312 return false;
313 }
314
315 char adj[32], noun1[32], noun2[32];
316 memcpy(adj, str, adj_len);
317 adj[adj_len] = '\0';
318 memcpy(noun1, hyphen1 + 1, noun1_len);
319 noun1[noun1_len] = '\0';
320 memcpy(noun2, hyphen2 + 1, noun2_len);
321 noun2[noun2_len] = '\0';
322
323 // Lazy initialization: build validation caches on first use
324 // call_once ensures thread-safe exactly-once execution
325 build_validation_caches();
326 // If initialization fails silently (logs warning), hashtables will be empty
327 // and validation will fail, which is safe behavior
328
329 // Validate first word is an adjective
330 word_cache_entry_t *adj_entry = NULL;
331 HASH_FIND_STR(g_adjectives_cache, adj, adj_entry);
332 if (!adj_entry) {
333 return false;
334 }
335
336 // Validate second and third words are nouns
337 word_cache_entry_t *noun_entry1 = NULL;
338 HASH_FIND_STR(g_nouns_cache, noun1, noun_entry1);
339 if (!noun_entry1) {
340 return false;
341 }
342
343 word_cache_entry_t *noun_entry2 = NULL;
344 HASH_FIND_STR(g_nouns_cache, noun2, noun_entry2);
345 if (!noun_entry2) {
346 return false;
347 }
348
349 log_dev("Valid session string: %s", str);
350 return true;
351}
const char * adjectives[]
Array of English adjectives for ACDS session naming.
Definition adjectives.c:51
const size_t adjectives_count
Count of adjectives in the adjectives array.
Definition adjectives.c:411
Wordlist for generating friendly session identifiers in ACDS.
asciichat_error_t error_code
asciichat_error_t acds_string_init(void)
Initialize session string system with cached word validation.
bool is_session_string(const char *str)
Check if a string is a valid session string.
void acds_strings_destroy(void)
Cleanup function for session string cache Called by asciichat_shared_destroy() during library cleanup...
asciichat_error_t acds_string_generate(char *output, size_t output_size)
Generate random session string.
#define SESSION_STRING_MAX_LEN
Maximum length of a session string (without null terminator)
Error and exit codes - unified status values (0-255)
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
@ ERROR_INTERNAL
Definition error_codes.h:92
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
bool utf8_is_ascii_only(const char *str)
Check if a string contains only ASCII characters.
Definition utf8.c:165
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
void lifecycle_init_commit(lifecycle_t *lc)
Definition lifecycle.c:90
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
bool lifecycle_init_once(lifecycle_t *lc)
Definition lifecycle.c:52
Lock-free module lifecycle state machine using pure stdatomic.
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
📝 Logging API with multiple log levels and terminal output control
const char * nouns[]
Array of English nouns for session and client naming.
Definition nouns.c:55
const size_t nouns_count
Count of nouns in the nouns array.
Definition nouns.c:772
Wordlist and utilities for generating friendly session and client names.
🔍 Centralized PCRE2 singleton pattern for efficient regex compilation
#define SESSION_STRING_BUFFER_SIZE
Hashtable entry for cached word lookups.
UT_hash_handle hh
uthash handle
char * word
The word string (key)
🔤 UTF-8 Encoding and Decoding Utilities