ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
src/discovery/webrtc.c
Go to the documentation of this file.
1
12#include "webrtc.h"
13
14#include <ascii-chat/common.h>
15#include <ascii-chat/log/logging.h>
16#include <ascii-chat/network/acip/protocol.h>
17#include <ascii-chat/network/acip/acds.h>
18#include <ascii-chat/network/acip/send.h>
19#include <ascii-chat/network/packet.h>
20#include <ascii-chat/network/network.h>
21#include <ascii-chat/platform/abstraction.h>
22#include <ascii-chat/platform/init.h>
23#include <ascii-chat/util/endian.h>
24
25#include <string.h>
26
27// =============================================================================
28// Global State
29// =============================================================================
30
38static acip_transport_t *g_tcp_transport = NULL;
39
46static struct {
47 uint8_t session_id[16];
48 uint8_t participant_id[16];
49 bool is_set;
50} g_session_context = {0};
51
55static mutex_t g_webrtc_mutex;
56static bool g_webrtc_mutex_initialized = false;
57static static_mutex_t g_webrtc_init_mutex = STATIC_MUTEX_INIT;
58
59// =============================================================================
60// Internal Helpers
61// =============================================================================
62
69static void ensure_mutex_initialized(void) {
70 static_mutex_lock(&g_webrtc_init_mutex);
71
72 // Check again under lock to prevent race condition
73 if (!g_webrtc_mutex_initialized) {
74 mutex_init(&g_webrtc_mutex);
75 g_webrtc_mutex_initialized = true;
76 }
77
78 static_mutex_unlock(&g_webrtc_init_mutex);
79}
80
81// =============================================================================
82// Signaling Callback Implementations (Direct TCP)
83// =============================================================================
84
95static asciichat_error_t discovery_send_sdp(const uint8_t session_id[16], const uint8_t recipient_id[16],
96 const char *sdp_type, const char *sdp, void *user_data) {
97 (void)recipient_id; // Unused - direct connection, no need to route
98 (void)user_data; // Unused
99
100 ensure_mutex_initialized();
101 mutex_lock(&g_webrtc_mutex);
102
103 // Verify state
104 if (!g_tcp_transport) {
105 mutex_unlock(&g_webrtc_mutex);
106 SET_ERRNO(ERROR_INVALID_STATE, "TCP transport not set for direct signaling");
107 return ERROR_INVALID_STATE;
108 }
109
110 if (!g_session_context.is_set) {
111 mutex_unlock(&g_webrtc_mutex);
112 SET_ERRNO(ERROR_INVALID_STATE, "Session context not set for direct signaling");
113 return ERROR_INVALID_STATE;
114 }
115
116 // Verify inputs
117 if (!sdp) {
118 mutex_unlock(&g_webrtc_mutex);
119 SET_ERRNO(ERROR_INVALID_PARAM, "SDP is NULL");
120 return ERROR_INVALID_PARAM;
121 }
122
123 size_t sdp_len = strlen(sdp);
124 if (sdp_len > 4096) {
125 mutex_unlock(&g_webrtc_mutex);
126 SET_ERRNO(ERROR_INVALID_PARAM, "SDP too large (>4096 bytes)");
127 return ERROR_INVALID_PARAM;
128 }
129
130 // Construct packet
131 // Header: acip_webrtc_sdp_t (defined in network/acip/acds.h)
132 // Followed by: SDP string
133 uint8_t sdp_msg_buf[sizeof(acip_webrtc_sdp_t) + 4096];
134 acip_webrtc_sdp_t *sdp_msg = (acip_webrtc_sdp_t *)sdp_msg_buf;
135
136 memcpy(sdp_msg->session_id, session_id, 16);
137 memcpy(sdp_msg->sender_id, g_session_context.participant_id, 16);
138 memset(sdp_msg->recipient_id, 0, 16); // Broadcast to all
139 sdp_msg->sdp_type = (strcmp(sdp_type, "answer") == 0) ? 1 : 0; // 0=offer, 1=answer
140 sdp_msg->sdp_len = (uint16_t)sdp_len;
141 memcpy(sdp_msg_buf + sizeof(acip_webrtc_sdp_t), sdp, sdp_len);
142
143 // Send via direct TCP transport (not ACDS)
144 size_t msg_size = sizeof(acip_webrtc_sdp_t) + sdp_len;
145 asciichat_error_t result =
146 packet_send_via_transport(g_tcp_transport, PACKET_TYPE_ACIP_WEBRTC_SDP, sdp_msg_buf, msg_size, 0);
147
148 mutex_unlock(&g_webrtc_mutex);
149
150 if (result != ASCIICHAT_OK) {
151 log_error("Failed to send SDP via direct TCP: %d", result);
152 return result;
153 }
154
155 log_debug("Sent SDP %s directly to peer (TCP transport)", sdp_type);
156 return ASCIICHAT_OK;
157}
158
164static asciichat_error_t discovery_send_ice(const uint8_t session_id[16], const uint8_t recipient_id[16],
165 const char *candidate, const char *mid, void *user_data) {
166 (void)recipient_id; // Unused - direct connection, no need to route
167 (void)user_data; // Unused
168
169 ensure_mutex_initialized();
170 mutex_lock(&g_webrtc_mutex);
171
172 // Verify state
173 if (!g_tcp_transport) {
174 mutex_unlock(&g_webrtc_mutex);
175 SET_ERRNO(ERROR_INVALID_STATE, "TCP transport not set for direct signaling");
176 return ERROR_INVALID_STATE;
177 }
178
179 if (!g_session_context.is_set) {
180 mutex_unlock(&g_webrtc_mutex);
181 SET_ERRNO(ERROR_INVALID_STATE, "Session context not set for direct signaling");
182 return ERROR_INVALID_STATE;
183 }
184
185 // Verify inputs
186 if (!candidate || !mid) {
187 mutex_unlock(&g_webrtc_mutex);
188 SET_ERRNO(ERROR_INVALID_PARAM, "Candidate or mid is NULL");
189 return ERROR_INVALID_PARAM;
190 }
191
192 // Calculate payload length (candidate + null + mid + null)
193 size_t candidate_len = strlen(candidate);
194 size_t mid_len = strlen(mid);
195 size_t payload_len = candidate_len + 1 + mid_len + 1;
196
197 if (payload_len > 4096) {
198 mutex_unlock(&g_webrtc_mutex);
199 SET_ERRNO(ERROR_INVALID_PARAM, "ICE candidate + mid too large (>4096 bytes)");
200 return ERROR_INVALID_PARAM;
201 }
202
203 // Construct packet
204 // Header: acip_webrtc_ice_t (defined in network/acip/acds.h)
205 // Followed by: candidate string + null + mid string + null
206 uint8_t ice_msg_buf[sizeof(acip_webrtc_ice_t) + 4096];
207 acip_webrtc_ice_t *ice_msg = (acip_webrtc_ice_t *)ice_msg_buf;
208
209 // Fill header
210 memcpy(ice_msg->session_id, session_id, 16);
211 memcpy(ice_msg->sender_id, g_session_context.participant_id, 16);
212 memset(ice_msg->recipient_id, 0, 16); // Broadcast to all
213 ice_msg->candidate_len = HOST_TO_NET_U16((uint16_t)candidate_len);
214
215 // Copy candidate and mid after header
216 uint8_t *payload = ice_msg_buf + sizeof(acip_webrtc_ice_t);
217 memcpy(payload, candidate, candidate_len);
218 payload[candidate_len] = '\0';
219 memcpy(payload + candidate_len + 1, mid, mid_len);
220 payload[candidate_len + 1 + mid_len] = '\0';
221
222 log_debug("Sending ICE candidate directly to peer (candidate_len=%zu, mid=%s)", candidate_len, mid);
223
224 // Send via direct TCP transport (not ACDS)
225 size_t msg_size = sizeof(acip_webrtc_ice_t) + payload_len;
226 asciichat_error_t result =
227 packet_send_via_transport(g_tcp_transport, PACKET_TYPE_ACIP_WEBRTC_ICE, ice_msg_buf, msg_size, 0);
228
229 mutex_unlock(&g_webrtc_mutex);
230
231 if (result != ASCIICHAT_OK) {
232 log_error("Failed to send ICE candidate via direct TCP: %d", result);
233 return result;
234 }
235
236 log_debug("Sent ICE candidate directly to peer (TCP transport)");
237 return ASCIICHAT_OK;
238}
239
240// =============================================================================
241// Public API
242// =============================================================================
243
244webrtc_signaling_callbacks_t discovery_webrtc_get_direct_signaling_callbacks(acip_transport_t *tcp_transport,
245 const uint8_t session_id[16],
246 const uint8_t participant_id[16]) {
247 ensure_mutex_initialized();
248 mutex_lock(&g_webrtc_mutex);
249
250 g_tcp_transport = tcp_transport;
251
252 if (session_id && participant_id) {
253 memcpy(g_session_context.session_id, session_id, 16);
254 memcpy(g_session_context.participant_id, participant_id, 16);
255 g_session_context.is_set = true;
256 }
257
258 mutex_unlock(&g_webrtc_mutex);
259
260 webrtc_signaling_callbacks_t callbacks = {
261 .send_sdp = discovery_send_sdp,
262 .send_ice = discovery_send_ice,
263 .user_data = NULL, // Unused - we use global state
264 };
265
266 return callbacks;
267}
268
269void discovery_webrtc_set_tcp_transport(acip_transport_t *transport) {
270 ensure_mutex_initialized();
271 mutex_lock(&g_webrtc_mutex);
272 g_tcp_transport = transport;
273 mutex_unlock(&g_webrtc_mutex);
274}
275
276void discovery_webrtc_set_session_context(const uint8_t session_id[16], const uint8_t participant_id[16]) {
277 ensure_mutex_initialized();
278 mutex_lock(&g_webrtc_mutex);
279
280 if (session_id && participant_id) {
281 memcpy(g_session_context.session_id, session_id, 16);
282 memcpy(g_session_context.participant_id, participant_id, 16);
283 g_session_context.is_set = true;
284 } else {
285 g_session_context.is_set = false;
286 }
287
288 mutex_unlock(&g_webrtc_mutex);
289}
290
292 ensure_mutex_initialized();
293 mutex_lock(&g_webrtc_mutex);
294
295 g_tcp_transport = NULL;
296 g_session_context.is_set = false;
297
298 mutex_unlock(&g_webrtc_mutex);
299}
WebRTC P2P signaling for discovery mode failover.
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Send packet via transport with proper header (exported for generic wrappers)
Definition send.c:41
void discovery_webrtc_cleanup_transport(void)
Cleanup and release the direct peer-to-peer transport.
bool is_set
void discovery_webrtc_set_tcp_transport(acip_transport_t *transport)
Set the TCP transport for direct peer-to-peer signaling.
uint8_t session_id[16]
uint8_t participant_id[16]
webrtc_signaling_callbacks_t discovery_webrtc_get_direct_signaling_callbacks(acip_transport_t *tcp_transport, const uint8_t session_id[16], const uint8_t participant_id[16])
Create signaling callbacks for direct peer-to-peer connection during failover.
void discovery_webrtc_set_session_context(const uint8_t session_id[16], const uint8_t participant_id[16])
Set session and participant IDs for direct signaling.
int mutex_init(mutex_t *mutex)
Definition threading.c:16