ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
stun.c
Go to the documentation of this file.
1
7#include <ascii-chat/network/webrtc/stun.h>
8#include <ascii-chat/common.h>
9#include <ascii-chat/log/logging.h>
10#include <ascii-chat/util/pcre2.h>
11#include <string.h>
12#include <ctype.h>
13#include <pcre2.h>
14
22// Pattern: Match comma-separated entries (trimmed)
23// Matches: optional whitespace, capture non-comma chars (trimmed), optional whitespace, then comma or end
24static const char *STUN_ENTRY_PATTERN = "\\s*([^,]+?)\\s*(?=,|$)";
25
26static pcre2_singleton_t *g_stun_entry_regex = NULL;
27
32static pcre2_code *stun_entry_regex_get(void) {
33 if (g_stun_entry_regex == NULL) {
34 g_stun_entry_regex = asciichat_pcre2_singleton_compile(STUN_ENTRY_PATTERN, PCRE2_MULTILINE);
35 }
36 return asciichat_pcre2_singleton_get_code(g_stun_entry_regex);
37}
38
51int stun_servers_parse(const char *csv_servers, const char *default_csv, stun_server_t *out_servers, int max_count) {
52 if (!out_servers || max_count <= 0) {
53 log_warn("stun_servers_parse: Invalid output array or max_count");
54 return -1;
55 }
56
57 // Use defaults if input is empty
58 const char *servers_to_parse = csv_servers;
59 if (!servers_to_parse || servers_to_parse[0] == '\0') {
60 servers_to_parse = default_csv;
61 }
62
63 if (!servers_to_parse || servers_to_parse[0] == '\0') {
64 log_warn("stun_servers_parse: No servers to parse and no defaults provided");
65 return 0;
66 }
67
68 // Get compiled regex (lazy initialization)
69 pcre2_code *regex = stun_entry_regex_get();
70
71 // If regex not available, fall back to manual parsing
72 if (!regex) {
73 log_warn("stun_servers_parse: PCRE2 regex not available, falling back to manual parsing");
74 // Fallback: manual character-by-character parsing (original implementation)
75 int count = 0;
76 const char *current = servers_to_parse;
77 const char *end;
78
79 while (count < max_count && *current != '\0') {
80 while (isspace(*current)) {
81 current++;
82 }
83 if (*current == '\0')
84 break;
85
86 end = strchr(current, ',');
87 if (!end) {
88 end = current + strlen(current);
89 }
90
91 while (end > current && isspace(*(end - 1))) {
92 end--;
93 }
94
95 size_t len = end - current;
96 if (len == 0) {
97 current = *end ? end + 1 : end;
98 continue;
99 }
100
101 if (len >= STUN_MAX_URL_LEN) {
102 log_warn("stun_servers_parse: STUN server URL too long (max %d): %.*s", STUN_MAX_URL_LEN, (int)len, current);
103 return -1;
104 }
105
106 out_servers[count].host_len = (uint8_t)len;
107 memcpy(out_servers[count].host, current, len);
108 out_servers[count].host[len] = '\0';
109 count++;
110
111 current = *end ? end + 1 : end;
112 }
113 return count;
114 }
115
116 // Parse using PCRE2 regex: match each trimmed entry
117 int count = 0;
118 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
119 if (!match_data) {
120 log_warn("stun_servers_parse: Failed to allocate PCRE2 match data");
121 return -1;
122 }
123
124 size_t offset = 0;
125 int rc;
126
127 while (count < max_count) {
128 rc = pcre2_jit_match(regex, (PCRE2_SPTR8)servers_to_parse, strlen(servers_to_parse), offset, 0, match_data, NULL);
129
130 if (rc < 0) {
131 // No more matches (PCRE2_ERROR_NOMATCH or other error)
132 break;
133 }
134
135 // Extract the captured group (group 1)
136 size_t len;
137 const char *server_url = asciichat_pcre2_extract_group_ptr(match_data, 1, servers_to_parse, &len);
138
139 if (!server_url) {
140 break; // Group not matched
141 }
142
143 if (len >= STUN_MAX_URL_LEN) {
144 log_warn("stun_servers_parse: STUN server URL too long (max %d): %.*s", STUN_MAX_URL_LEN, (int)len, server_url);
145 pcre2_match_data_free(match_data);
146 return -1;
147 }
148
149 // Copy trimmed entry to output
150 out_servers[count].host_len = (uint8_t)len;
151 memcpy(out_servers[count].host, server_url, len);
152 out_servers[count].host[len] = '\0';
153
154 count++;
155
156 // Move offset to end of this match (need ovector for overall match end)
157 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
158 offset = ovector[1];
159
160 // Skip comma if present
161 if (offset < strlen(servers_to_parse) && servers_to_parse[offset] == ',') {
162 offset++;
163 }
164 }
165
166 pcre2_match_data_free(match_data);
167 return count;
168}
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:95
const char * asciichat_pcre2_extract_group_ptr(pcre2_match_data *match_data, int group_num, const char *subject, size_t *out_len)
Extract numbered capture group as pointer into subject (non-allocating)
Definition pcre2.c:322
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:21
int stun_servers_parse(const char *csv_servers, const char *default_csv, stun_server_t *out_servers, int max_count)
Parse comma-separated STUN server URLs into stun_server_t array.
Definition stun.c:51