Line data Source code
1 : /**
2 : * @file pt_init.c
3 : * @brief PeerTalk Initialization and Shutdown
4 : *
5 : * Lifecycle management for PeerTalk context, including PT_Log integration.
6 : */
7 :
8 : #include "pt_internal.h"
9 : #include "pt_compat.h"
10 : #include "peer.h"
11 : #include "queue.h"
12 : #include "direct_buffer.h"
13 : #include "protocol.h"
14 : #include <string.h>
15 :
16 : /* ========================================================================== */
17 : /* PeerTalk_Init - Initialize PeerTalk Context */
18 : /* ========================================================================== */
19 :
20 233 : PeerTalk_Context *PeerTalk_Init(const PeerTalk_Config *config) {
21 : struct pt_context *ctx;
22 : size_t ctx_size;
23 : size_t extra_size;
24 :
25 : /* Validate configuration */
26 233 : if (!config) {
27 2 : return NULL;
28 : }
29 :
30 : /* Validate local name is not empty */
31 231 : if (!config->local_name[0]) {
32 1 : return NULL;
33 : }
34 :
35 : /* Calculate allocation size: context + platform-specific data */
36 230 : extra_size = pt_plat_extra_size();
37 230 : ctx_size = sizeof(struct pt_context) + extra_size;
38 :
39 : /* Allocate context using platform-specific allocator */
40 230 : ctx = (struct pt_context *)pt_plat_alloc(ctx_size);
41 230 : if (!ctx) {
42 0 : return NULL;
43 : }
44 :
45 : /* Zero the entire context */
46 230 : pt_memset(ctx, 0, ctx_size);
47 :
48 : /* Set magic number for validation */
49 230 : ctx->magic = PT_CONTEXT_MAGIC;
50 :
51 : /* Copy configuration */
52 230 : pt_memcpy(&ctx->config, config, sizeof(PeerTalk_Config));
53 :
54 : /* Apply defaults for zero values */
55 230 : if (ctx->config.transports == 0) {
56 229 : ctx->config.transports = PT_TRANSPORT_ALL;
57 : }
58 230 : if (ctx->config.discovery_port == 0) {
59 209 : ctx->config.discovery_port = PT_DEFAULT_DISCOVERY_PORT;
60 : }
61 230 : if (ctx->config.tcp_port == 0) {
62 171 : ctx->config.tcp_port = PT_DEFAULT_TCP_PORT;
63 : }
64 230 : if (ctx->config.udp_port == 0) {
65 224 : ctx->config.udp_port = PT_DEFAULT_UDP_PORT;
66 : }
67 230 : if (ctx->config.max_peers == 0) {
68 161 : ctx->config.max_peers = PT_MAX_PEERS;
69 : }
70 230 : if (ctx->config.discovery_interval == 0) {
71 230 : ctx->config.discovery_interval = 5000; /* 5 seconds */
72 : }
73 230 : if (ctx->config.peer_timeout == 0) {
74 230 : ctx->config.peer_timeout = 15000; /* 15 seconds */
75 : }
76 230 : if (ctx->config.direct_buffer_size == 0) {
77 229 : ctx->config.direct_buffer_size = PT_DIRECT_DEFAULT_SIZE;
78 : }
79 :
80 : /* Auto-allocate buffer pool if requested and not already provided */
81 : #if defined(PT_PLATFORM_MACTCP) || defined(PT_PLATFORM_OT)
82 : if (ctx->config.auto_buffers && ctx->config.buffer_pool == NULL) {
83 : uint16_t peer_count = ctx->config.max_peers > 0 ? ctx->config.max_peers : PT_MAX_PEERS;
84 : ctx->config.buffer_pool = PeerTalk_AllocateBuffersAuto(peer_count);
85 : /* Mark that we own this pool and should free it on shutdown */
86 : ctx->owns_buffer_pool = (ctx->config.buffer_pool != NULL) ? 1 : 0;
87 : }
88 : #endif
89 :
90 : /* Apply two-tier queue configuration */
91 230 : ctx->direct_threshold = PT_DIRECT_THRESHOLD;
92 230 : ctx->direct_buffer_size = ctx->config.direct_buffer_size;
93 :
94 : /* Apply capability negotiation defaults */
95 230 : if (ctx->config.max_message_size == 0) {
96 228 : ctx->config.max_message_size = PT_CAP_MAX_MAX_MSG; /* 8192 */
97 : }
98 230 : if (ctx->config.preferred_chunk == 0) {
99 230 : ctx->config.preferred_chunk = 1024;
100 : }
101 : /* enable_fragmentation: 0 or 0xFF = unset (default enabled), 1 = enabled, 2+ = disabled
102 : * This allows memset(&config, 0, ...) to get sensible defaults */
103 230 : if (ctx->config.enable_fragmentation == 0 || ctx->config.enable_fragmentation == 0xFF) {
104 226 : ctx->config.enable_fragmentation = 1; /* Default to enabled */
105 : }
106 230 : ctx->local_max_message = ctx->config.max_message_size;
107 230 : ctx->local_preferred_chunk = ctx->config.preferred_chunk;
108 : /* We support fragmentation and compact headers */
109 230 : ctx->local_capability_flags = PT_CAPFLAG_FRAGMENTATION | PT_CAPFLAG_COMPACT_HEADER;
110 230 : ctx->enable_fragmentation = (ctx->config.enable_fragmentation == 1) ? 1 : 0;
111 :
112 : /* Pressure thresholds - use defaults if zero */
113 230 : ctx->pressure_medium = ctx->config.pressure_medium ?
114 : ctx->config.pressure_medium : PT_PRESSURE_MEDIUM;
115 230 : ctx->pressure_high = ctx->config.pressure_high ?
116 : ctx->config.pressure_high : PT_PRESSURE_HIGH;
117 230 : ctx->pressure_critical = ctx->config.pressure_critical ?
118 : ctx->config.pressure_critical : PT_PRESSURE_CRITICAL;
119 230 : ctx->pressure_frag = ctx->config.pressure_frag ?
120 : ctx->config.pressure_frag : PT_PRESSURE_FRAG_THRESHOLD;
121 :
122 : /* Connection timeout - default 30 seconds */
123 230 : ctx->connect_timeout = ctx->config.connect_timeout ?
124 : ctx->config.connect_timeout : 30000;
125 :
126 : /* Initialize PT_Log from Phase 0 */
127 230 : ctx->log = PT_LogCreate();
128 230 : if (ctx->log) {
129 : /* Configure logging based on user settings */
130 230 : if (config->log_level > 0) {
131 0 : PT_LogSetLevel(ctx->log, (PT_LogLevel)config->log_level);
132 : }
133 230 : PT_LogSetCategories(ctx->log, PT_LOG_CAT_ALL);
134 : #if defined(PT_PLATFORM_MACTCP) || defined(PT_PLATFORM_OT)
135 : /* Mac has no console - use file output */
136 : PT_LogSetOutput(ctx->log, PT_LOG_OUT_FILE);
137 : PT_LogSetFile(ctx->log, "PT_Log");
138 : #else
139 230 : PT_LogSetOutput(ctx->log, PT_LOG_OUT_CONSOLE);
140 : #endif
141 : }
142 :
143 : /* Initialize local peer info */
144 230 : ctx->local_info.id = 0; /* Self is always ID 0 */
145 230 : ctx->local_info.address = 0; /* Filled in by platform layer */
146 230 : ctx->local_info.port = ctx->config.tcp_port;
147 230 : ctx->local_info.transports_available = ctx->config.transports;
148 230 : ctx->local_info.transport_connected = 0;
149 230 : ctx->local_info.name_idx = 0xFF; /* No name index yet */
150 :
151 : /* Initialize peer management */
152 230 : ctx->max_peers = ctx->config.max_peers;
153 230 : ctx->peer_count = 0;
154 230 : ctx->next_peer_id = 1; /* Start from 1 (0 is reserved for self) */
155 :
156 : /* Initialize peer ID lookup table (0xFF = invalid) */
157 230 : pt_memset(ctx->peer_id_to_index, 0xFF,
158 : sizeof(ctx->peer_id_to_index));
159 :
160 : /* Allocate peer list */
161 230 : if (pt_peer_list_init(ctx, ctx->max_peers) != 0) {
162 0 : PT_CTX_ERR(ctx, PT_LOG_CAT_INIT,
163 : "Failed to initialize peer list");
164 0 : if (ctx->log) {
165 0 : PT_LogDestroy(ctx->log);
166 : }
167 0 : pt_plat_free(ctx);
168 0 : return NULL;
169 : }
170 :
171 : /* Select platform operations based on compile-time platform */
172 : #if defined(PT_PLATFORM_POSIX)
173 230 : ctx->plat = &pt_posix_ops;
174 : #elif defined(PT_PLATFORM_MACTCP)
175 : ctx->plat = &pt_mactcp_ops;
176 : #elif defined(PT_PLATFORM_OT)
177 : ctx->plat = &pt_ot_ops;
178 : #else
179 : #error "Unknown platform"
180 : #endif
181 :
182 : /* Initialize platform-specific layer */
183 230 : if (ctx->plat->init) {
184 230 : if (ctx->plat->init(ctx) != 0) {
185 : /* Platform init failed */
186 0 : if (ctx->log) {
187 0 : PT_LogDestroy(ctx->log);
188 : }
189 0 : pt_plat_free(ctx);
190 0 : return NULL;
191 : }
192 : }
193 :
194 : /* Log successful initialization */
195 230 : PT_CTX_INFO(ctx, PT_LOG_CAT_INIT,
196 : "PeerTalk v%s initialized: name='%s' transports=0x%04X",
197 : PeerTalk_Version(), ctx->config.local_name, ctx->config.transports);
198 :
199 230 : ctx->initialized = 1;
200 :
201 : /* Return opaque handle */
202 230 : return (PeerTalk_Context *)ctx;
203 : }
204 :
205 : /* ========================================================================== */
206 : /* PeerTalk_QuickStart - Zero-Config Initialization */
207 : /* ========================================================================== */
208 :
209 0 : PeerTalk_Context *PeerTalk_QuickStart(
210 : const char *name,
211 : uint16_t max_peers,
212 : const PeerTalk_Callbacks *callbacks)
213 : {
214 0 : return PeerTalk_QuickStartWithPool(name, max_peers, NULL, callbacks);
215 : }
216 :
217 0 : PeerTalk_Context *PeerTalk_QuickStartWithPool(
218 : const char *name,
219 : uint16_t max_peers,
220 : PeerTalk_BufferPool *pool,
221 : const PeerTalk_Callbacks *callbacks)
222 : {
223 : PeerTalk_Config config;
224 : PeerTalk_Context *ctx;
225 : size_t name_len;
226 :
227 : /* Validate parameters */
228 0 : if (!name || !name[0]) {
229 0 : return NULL;
230 : }
231 :
232 : /* Initialize config with zeros */
233 0 : pt_memset(&config, 0, sizeof(config));
234 :
235 : /* Copy name safely */
236 0 : name_len = 0;
237 0 : while (name[name_len] && name_len < PT_MAX_PEER_NAME) {
238 0 : config.local_name[name_len] = name[name_len];
239 0 : name_len++;
240 : }
241 0 : config.local_name[name_len] = '\0';
242 :
243 : /* Set sensible defaults */
244 0 : config.max_peers = (max_peers > 0 && max_peers <= PT_MAX_PEERS)
245 : ? max_peers : 4;
246 0 : config.transports = PT_TRANSPORT_ALL;
247 0 : config.discovery_port = PT_DEFAULT_DISCOVERY_PORT;
248 0 : config.tcp_port = PT_DEFAULT_TCP_PORT;
249 0 : config.udp_port = PT_DEFAULT_UDP_PORT;
250 0 : config.max_message_size = PT_MAX_MESSAGE_SIZE;
251 0 : config.preferred_chunk = 1024;
252 0 : config.enable_fragmentation = 1;
253 :
254 : /* Buffer handling: use provided pool or enable auto-allocation */
255 0 : if (pool != NULL) {
256 0 : config.buffer_pool = pool;
257 0 : config.auto_buffers = 0;
258 : } else {
259 0 : config.buffer_pool = NULL;
260 0 : config.auto_buffers = 1; /* SDK allocates optimal buffers */
261 : }
262 :
263 : /* Initialize PeerTalk */
264 0 : ctx = PeerTalk_Init(&config);
265 0 : if (ctx == NULL) {
266 0 : return NULL;
267 : }
268 :
269 : /* Set callbacks if provided */
270 0 : if (callbacks != NULL) {
271 0 : PeerTalk_SetCallbacks(ctx, callbacks);
272 : }
273 :
274 0 : return ctx;
275 : }
276 :
277 : /* ========================================================================== */
278 : /* PeerTalk_Shutdown - Clean Up and Free Resources */
279 : /* ========================================================================== */
280 :
281 233 : void PeerTalk_Shutdown(PeerTalk_Context *ctx_handle) {
282 233 : struct pt_context *ctx = (struct pt_context *)ctx_handle;
283 :
284 : /* Validate context */
285 233 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
286 3 : return;
287 : }
288 :
289 230 : PT_CTX_INFO(ctx, PT_LOG_CAT_INIT, "PeerTalk shutting down");
290 :
291 : /* Mark as not initialized */
292 230 : ctx->initialized = 0;
293 :
294 : /* Shutdown platform-specific layer */
295 230 : if (ctx->plat && ctx->plat->shutdown) {
296 230 : ctx->plat->shutdown(ctx);
297 : }
298 :
299 : /* Free peer list */
300 230 : pt_peer_list_free(ctx);
301 :
302 : /* Free auto-allocated buffer pool */
303 : #if defined(PT_PLATFORM_MACTCP) || defined(PT_PLATFORM_OT)
304 : if (ctx->owns_buffer_pool && ctx->config.buffer_pool != NULL) {
305 : PeerTalk_FreeBuffers(ctx->config.buffer_pool);
306 : ctx->config.buffer_pool = NULL;
307 : }
308 : #endif
309 :
310 : /* Destroy PT_Log context last (need it for shutdown logging) */
311 230 : if (ctx->log) {
312 230 : PT_LogFlush(ctx->log);
313 230 : PT_LogDestroy(ctx->log);
314 230 : ctx->log = NULL;
315 : }
316 :
317 : /* Clear magic number */
318 230 : ctx->magic = 0;
319 :
320 : /* Free context memory */
321 230 : pt_plat_free(ctx);
322 : }
323 :
324 : /* ========================================================================== */
325 : /* PeerTalk_Poll - Main Event Loop (Stub for Phase 4+) */
326 : /* ========================================================================== */
327 :
328 742 : PeerTalk_Error PeerTalk_Poll(PeerTalk_Context *ctx_handle) {
329 742 : struct pt_context *ctx = (struct pt_context *)ctx_handle;
330 :
331 : /* Validate context */
332 742 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
333 1 : return PT_ERR_INVALID_PARAM;
334 : }
335 :
336 : /* Call platform-specific poll */
337 741 : if (ctx->plat && ctx->plat->poll) {
338 741 : if (ctx->plat->poll(ctx) != 0) {
339 0 : return PT_ERR_NETWORK;
340 : }
341 : }
342 :
343 741 : return PT_OK;
344 : }
345 :
346 : /* ========================================================================== */
347 : /* PeerTalk_PollFast - Fast Poll for Tight Game Loops */
348 : /* ========================================================================== */
349 :
350 102 : PeerTalk_Error PeerTalk_PollFast(PeerTalk_Context *ctx_handle) {
351 102 : struct pt_context *ctx = (struct pt_context *)ctx_handle;
352 :
353 : /* Validate context */
354 102 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
355 1 : return PT_ERR_INVALID_PARAM;
356 : }
357 :
358 : /* Call platform-specific fast poll */
359 101 : if (ctx->plat && ctx->plat->poll_fast) {
360 101 : if (ctx->plat->poll_fast(ctx) != 0) {
361 0 : return PT_ERR_NETWORK;
362 : }
363 : }
364 :
365 101 : return PT_OK;
366 : }
367 :
368 : /* ========================================================================== */
369 : /* PeerTalk_SetCallbacks - Register Callbacks (Stub for Phase 4+) */
370 : /* ========================================================================== */
371 :
372 37 : PeerTalk_Error PeerTalk_SetCallbacks(PeerTalk_Context *ctx_handle,
373 : const PeerTalk_Callbacks *callbacks) {
374 37 : struct pt_context *ctx = (struct pt_context *)ctx_handle;
375 :
376 : /* Validate context */
377 37 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
378 1 : return PT_ERR_INVALID_PARAM;
379 : }
380 :
381 36 : if (!callbacks) {
382 1 : return PT_ERR_INVALID_PARAM;
383 : }
384 :
385 : /* Copy callbacks */
386 35 : pt_memcpy(&ctx->callbacks, callbacks, sizeof(PeerTalk_Callbacks));
387 :
388 35 : PT_CTX_DEBUG(ctx, PT_LOG_CAT_INIT, "Callbacks registered");
389 :
390 35 : return PT_OK;
391 : }
392 :
393 :
394 : /* ========================================================================== */
395 : /* Peer Name Table Access (Stub for Phase 2+) */
396 : /* ========================================================================== */
397 :
398 2 : const char *pt_get_peer_name(struct pt_context *ctx, uint8_t name_idx) {
399 : /* Stub - full implementation in Phase 2+ */
400 2 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
401 0 : return "";
402 : }
403 :
404 2 : if (name_idx >= PT_MAX_PEERS) {
405 2 : return "";
406 : }
407 :
408 0 : return ctx->peer_names[name_idx];
409 : }
410 :
411 : /* ========================================================================== */
412 : /* Discovery Control (Phase 4) */
413 : /* ========================================================================== */
414 :
415 : #if defined(PT_PLATFORM_POSIX)
416 : /* Forward declarations from net_posix.h */
417 : extern int pt_posix_discovery_start(struct pt_context *ctx);
418 : extern void pt_posix_discovery_stop(struct pt_context *ctx);
419 : #elif defined(PT_PLATFORM_MACTCP)
420 : /* Forward declarations from discovery_mactcp.c */
421 : extern int pt_mactcp_discovery_start(struct pt_context *ctx);
422 : extern void pt_mactcp_discovery_stop(struct pt_context *ctx);
423 : #endif
424 :
425 20 : PeerTalk_Error PeerTalk_StartDiscovery(PeerTalk_Context *ctx_public) {
426 20 : struct pt_context *ctx = (struct pt_context *)ctx_public;
427 :
428 20 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
429 1 : return PT_ERR_INVALID_PARAM;
430 : }
431 :
432 : #if defined(PT_PLATFORM_POSIX)
433 19 : if (pt_posix_discovery_start(ctx) < 0) {
434 0 : return PT_ERR_NETWORK;
435 : }
436 : #elif defined(PT_PLATFORM_MACTCP)
437 : if (pt_mactcp_discovery_start(ctx) < 0) {
438 : return PT_ERR_NETWORK;
439 : }
440 : #else
441 : (void)ctx;
442 : return PT_ERR_NOT_SUPPORTED;
443 : #endif
444 :
445 19 : ctx->discovery_active = 1;
446 19 : PT_CTX_INFO(ctx, PT_LOG_CAT_DISCOVERY, "Discovery started");
447 19 : return PT_OK;
448 : }
449 :
450 17 : PeerTalk_Error PeerTalk_StopDiscovery(PeerTalk_Context *ctx_public) {
451 17 : struct pt_context *ctx = (struct pt_context *)ctx_public;
452 :
453 17 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
454 1 : return PT_ERR_INVALID_PARAM;
455 : }
456 :
457 : #if defined(PT_PLATFORM_POSIX)
458 16 : pt_posix_discovery_stop(ctx);
459 : #elif defined(PT_PLATFORM_MACTCP)
460 : pt_mactcp_discovery_stop(ctx);
461 : #else
462 : (void)ctx;
463 : #endif
464 :
465 16 : ctx->discovery_active = 0;
466 16 : PT_CTX_INFO(ctx, PT_LOG_CAT_DISCOVERY, "Discovery stopped");
467 16 : return PT_OK;
468 : }
469 :
470 : /* ========================================================================== */
471 : /* Connection Control (Phase 4 Session 4.2) */
472 : /* ========================================================================== */
473 :
474 : #if defined(PT_PLATFORM_POSIX)
475 : /* Forward declarations from net_posix.h */
476 : extern int pt_posix_listen_start(struct pt_context *ctx);
477 : extern void pt_posix_listen_stop(struct pt_context *ctx);
478 : extern int pt_posix_connect(struct pt_context *ctx, struct pt_peer *peer);
479 : extern int pt_posix_disconnect(struct pt_context *ctx, struct pt_peer *peer);
480 : #elif defined(PT_PLATFORM_MACTCP)
481 : /* Forward declarations from tcp_listen.c, tcp_connect.c */
482 : extern int pt_mactcp_listen_start(struct pt_context *ctx);
483 : extern void pt_mactcp_listen_stop(struct pt_context *ctx);
484 : extern int pt_mactcp_connect(struct pt_context *ctx, struct pt_peer *peer);
485 : extern int pt_mactcp_disconnect(struct pt_context *ctx, struct pt_peer *peer);
486 : #endif
487 :
488 28 : PeerTalk_Error PeerTalk_StartListening(PeerTalk_Context *ctx_public) {
489 28 : struct pt_context *ctx = (struct pt_context *)ctx_public;
490 :
491 28 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
492 1 : return PT_ERR_INVALID_PARAM;
493 : }
494 :
495 : #if defined(PT_PLATFORM_POSIX)
496 27 : if (pt_posix_listen_start(ctx) < 0) {
497 0 : return PT_ERR_NETWORK;
498 : }
499 : #elif defined(PT_PLATFORM_MACTCP)
500 : if (pt_mactcp_listen_start(ctx) < 0) {
501 : return PT_ERR_NETWORK;
502 : }
503 : #else
504 : (void)ctx;
505 : return PT_ERR_NOT_SUPPORTED;
506 : #endif
507 :
508 27 : PT_CTX_INFO(ctx, PT_LOG_CAT_CONNECT, "TCP listening started");
509 27 : return PT_OK;
510 : }
511 :
512 26 : PeerTalk_Error PeerTalk_StopListening(PeerTalk_Context *ctx_public) {
513 26 : struct pt_context *ctx = (struct pt_context *)ctx_public;
514 :
515 26 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
516 1 : return PT_ERR_INVALID_PARAM;
517 : }
518 :
519 : #if defined(PT_PLATFORM_POSIX)
520 25 : pt_posix_listen_stop(ctx);
521 : #elif defined(PT_PLATFORM_MACTCP)
522 : pt_mactcp_listen_stop(ctx);
523 : #else
524 : (void)ctx;
525 : #endif
526 :
527 25 : PT_CTX_INFO(ctx, PT_LOG_CAT_CONNECT, "TCP listening stopped");
528 25 : return PT_OK;
529 : }
530 :
531 15 : PeerTalk_Error PeerTalk_Connect(PeerTalk_Context *ctx_public,
532 : PeerTalk_PeerID peer_id) {
533 15 : struct pt_context *ctx = (struct pt_context *)ctx_public;
534 : struct pt_peer *peer;
535 :
536 15 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
537 1 : return PT_ERR_INVALID_PARAM;
538 : }
539 :
540 : /* Find peer by ID */
541 14 : peer = pt_peer_find_by_id(ctx, peer_id);
542 14 : if (!peer) {
543 2 : PT_CTX_WARN(ctx, PT_LOG_CAT_CONNECT,
544 : "Connect failed: Peer %u not found", peer_id);
545 2 : return PT_ERR_PEER_NOT_FOUND;
546 : }
547 :
548 : #if defined(PT_PLATFORM_POSIX)
549 12 : return pt_posix_connect(ctx, peer);
550 : #elif defined(PT_PLATFORM_MACTCP)
551 : return pt_mactcp_connect(ctx, peer);
552 : #else
553 : (void)peer;
554 : return PT_ERR_NOT_SUPPORTED;
555 : #endif
556 : }
557 :
558 6 : PeerTalk_Error PeerTalk_Disconnect(PeerTalk_Context *ctx_public,
559 : PeerTalk_PeerID peer_id) {
560 6 : struct pt_context *ctx = (struct pt_context *)ctx_public;
561 : struct pt_peer *peer;
562 :
563 6 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
564 1 : return PT_ERR_INVALID_PARAM;
565 : }
566 :
567 : /* Find peer by ID */
568 5 : peer = pt_peer_find_by_id(ctx, peer_id);
569 5 : if (!peer) {
570 2 : PT_CTX_WARN(ctx, PT_LOG_CAT_CONNECT,
571 : "Disconnect failed: Peer %u not found", peer_id);
572 2 : return PT_ERR_PEER_NOT_FOUND;
573 : }
574 :
575 : #if defined(PT_PLATFORM_POSIX)
576 3 : return pt_posix_disconnect(ctx, peer);
577 : #elif defined(PT_PLATFORM_MACTCP)
578 : return pt_mactcp_disconnect(ctx, peer);
579 : #else
580 : (void)peer;
581 : return PT_ERR_NOT_SUPPORTED;
582 : #endif
583 : }
584 :
585 : /* ========================================================================== */
586 : /* Peer List Functions (Phase 1) */
587 : /* ========================================================================== */
588 :
589 : /**
590 : * Get list of discovered peers
591 : *
592 : * Copies peer information for all discovered peers into the provided buffer.
593 : * Returns the actual count of peers copied.
594 : *
595 : * @param ctx Valid PeerTalk context
596 : * @param peers Buffer to receive peer info (caller-allocated)
597 : * @param max_peers Size of the peers buffer
598 : * @param out_count Receives actual number of peers copied
599 : *
600 : * @return PT_OK on success, PT_ERR_* on failure
601 : */
602 9 : PeerTalk_Error PeerTalk_GetPeers(PeerTalk_Context *ctx_pub,
603 : PeerTalk_PeerInfo *peers,
604 : uint16_t max_peers,
605 : uint16_t *out_count) {
606 9 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
607 9 : uint16_t count = 0;
608 : uint16_t i;
609 :
610 : /* Validate parameters */
611 9 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
612 2 : return PT_ERR_INVALID_STATE;
613 : }
614 7 : if (!peers || !out_count) {
615 4 : return PT_ERR_INVALID_PARAM;
616 : }
617 :
618 : /* Iterate through all peer slots */
619 43 : for (i = 0; i < ctx->max_peers && count < max_peers; i++) {
620 40 : struct pt_peer *peer = &ctx->peers[i];
621 :
622 : /* Skip unused slots */
623 40 : if (peer->hot.state == PT_PEER_UNUSED) {
624 39 : continue;
625 : }
626 :
627 : /* Validate peer magic */
628 1 : if (peer->hot.magic != PT_PEER_MAGIC) {
629 0 : continue;
630 : }
631 :
632 : /* Copy peer info using existing helper */
633 1 : pt_peer_get_info(peer, &peers[count]);
634 1 : count++;
635 : }
636 :
637 3 : *out_count = count;
638 3 : return PT_OK;
639 : }
640 :
641 : /**
642 : * Get peer list version (increments when peers added/removed)
643 : *
644 : * Allows detecting changes without copying entire peer list.
645 : *
646 : * @param ctx Valid PeerTalk context
647 : * @return Current peers version counter, 0 on error
648 : */
649 2 : uint32_t PeerTalk_GetPeersVersion(PeerTalk_Context *ctx_pub) {
650 2 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
651 :
652 2 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
653 1 : return 0;
654 : }
655 :
656 1 : return ctx->peers_version;
657 : }
658 :
659 : /**
660 : * Get peer info by ID (returns pointer to internal structure)
661 : *
662 : * Returns pointer valid until next Poll call. Does not copy data.
663 : *
664 : * @param ctx Valid PeerTalk context
665 : * @param peer_id Peer ID to look up
666 : * @return Pointer to peer info, or NULL if not found
667 : */
668 3 : const PeerTalk_PeerInfo *PeerTalk_GetPeerByID(PeerTalk_Context *ctx_pub,
669 : PeerTalk_PeerID peer_id) {
670 3 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
671 : struct pt_peer *peer;
672 :
673 3 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
674 1 : return NULL;
675 : }
676 :
677 2 : peer = pt_peer_find_by_id(ctx, peer_id);
678 2 : if (!peer) {
679 1 : return NULL;
680 : }
681 :
682 : /* Return pointer to cold info - valid until next Poll */
683 1 : return &peer->cold.info;
684 : }
685 :
686 : /**
687 : * Get peer info by ID (copies to caller-provided structure)
688 : *
689 : * Safer than GetPeerByID - copies data so it remains valid.
690 : *
691 : * @param ctx Valid PeerTalk context
692 : * @param peer_id Peer ID to look up
693 : * @param info Buffer to receive peer info (caller-allocated)
694 : * @return PT_OK on success, PT_ERR_* on failure
695 : */
696 4 : PeerTalk_Error PeerTalk_GetPeer(PeerTalk_Context *ctx_pub,
697 : PeerTalk_PeerID peer_id,
698 : PeerTalk_PeerInfo *info) {
699 4 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
700 : struct pt_peer *peer;
701 :
702 4 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
703 1 : return PT_ERR_INVALID_STATE;
704 : }
705 3 : if (!info) {
706 1 : return PT_ERR_INVALID_PARAM;
707 : }
708 :
709 2 : peer = pt_peer_find_by_id(ctx, peer_id);
710 2 : if (!peer) {
711 1 : return PT_ERR_PEER_NOT_FOUND;
712 : }
713 :
714 : /* Copy peer info using existing helper */
715 1 : pt_peer_get_info(peer, info);
716 1 : return PT_OK;
717 : }
718 :
719 : /**
720 : * Find peer by name
721 : *
722 : * Searches for peer with matching name string.
723 : *
724 : * @param ctx Valid PeerTalk context
725 : * @param name Name to search for (not null)
726 : * @param info Optional buffer to receive peer info (can be NULL)
727 : * @return Peer ID if found, 0 if not found
728 : */
729 5 : PeerTalk_PeerID PeerTalk_FindPeerByName(PeerTalk_Context *ctx_pub,
730 : const char *name,
731 : PeerTalk_PeerInfo *info) {
732 5 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
733 : struct pt_peer *peer;
734 :
735 5 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC || !name) {
736 2 : return 0;
737 : }
738 :
739 3 : peer = pt_peer_find_by_name(ctx, name);
740 3 : if (!peer) {
741 2 : return 0;
742 : }
743 :
744 : /* Optionally copy peer info */
745 1 : if (info) {
746 0 : pt_peer_get_info(peer, info);
747 : }
748 :
749 1 : return peer->hot.id;
750 : }
751 :
752 : /**
753 : * Find peer by address
754 : *
755 : * Searches for peer with matching IP address and port.
756 : *
757 : * @param ctx Valid PeerTalk context
758 : * @param address IP address (host byte order)
759 : * @param port Port number (host byte order)
760 : * @param info Optional buffer to receive peer info (can be NULL)
761 : * @return Peer ID if found, 0 if not found
762 : */
763 4 : PeerTalk_PeerID PeerTalk_FindPeerByAddress(PeerTalk_Context *ctx_pub,
764 : uint32_t address,
765 : uint16_t port,
766 : PeerTalk_PeerInfo *info) {
767 4 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
768 : struct pt_peer *peer;
769 :
770 4 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
771 1 : return 0;
772 : }
773 :
774 3 : peer = pt_peer_find_by_addr(ctx, address, port);
775 3 : if (!peer) {
776 2 : return 0;
777 : }
778 :
779 : /* Optionally copy peer info */
780 1 : if (info) {
781 0 : pt_peer_get_info(peer, info);
782 : }
783 :
784 1 : return peer->hot.id;
785 : }
786 :
787 : /* ========================================================================== */
788 : /* Broadcast Message (Phase 1) */
789 : /* ========================================================================== */
790 :
791 : /**
792 : * Broadcast message to all connected peers
793 : *
794 : * Sends a message to all peers in PT_PEER_CONNECTED state.
795 : * Returns error if no peers are connected.
796 : *
797 : * @param ctx Valid PeerTalk context
798 : * @param data Message data (not null)
799 : * @param length Message length (1-PT_MAX_MESSAGE bytes)
800 : *
801 : * @return PT_OK if sent to at least one peer, PT_ERR_* on failure
802 : */
803 9 : PeerTalk_Error PeerTalk_Broadcast(PeerTalk_Context *ctx_pub,
804 : const void *data, uint16_t length) {
805 9 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
806 : uint16_t i;
807 9 : uint16_t sent_count = 0;
808 9 : PeerTalk_Error last_err = PT_OK;
809 :
810 : /* Validate parameters */
811 9 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
812 2 : return PT_ERR_INVALID_STATE;
813 : }
814 7 : if (!data || length == 0 || length > PT_MAX_MESSAGE_SIZE) {
815 5 : return PT_ERR_INVALID_PARAM;
816 : }
817 :
818 : /* Iterate through all peer slots */
819 26 : for (i = 0; i < ctx->max_peers; i++) {
820 24 : struct pt_peer *peer = &ctx->peers[i];
821 :
822 : /* Skip non-connected peers */
823 24 : if (peer->hot.state != PT_PEER_CONNECTED) {
824 24 : continue;
825 : }
826 :
827 : /* Validate peer magic */
828 0 : if (peer->hot.magic != PT_PEER_MAGIC) {
829 0 : continue;
830 : }
831 :
832 : /* Send to this peer */
833 0 : PeerTalk_Error err = PeerTalk_Send(ctx_pub, peer->hot.id, data, length);
834 0 : if (err == PT_OK) {
835 0 : sent_count++;
836 : } else {
837 : /* Track last error but continue sending to other peers */
838 0 : last_err = err;
839 0 : PT_CTX_WARN(ctx, PT_LOG_CAT_SEND,
840 : "Broadcast failed to peer %u: error %d",
841 : peer->hot.id, err);
842 : }
843 : }
844 :
845 : /* If no peers were connected, return error */
846 2 : if (sent_count == 0) {
847 2 : if (last_err != PT_OK) {
848 : /* Had peers but all sends failed */
849 0 : return last_err;
850 : }
851 : /* No connected peers at all */
852 2 : return PT_ERR_PEER_NOT_FOUND;
853 : }
854 :
855 0 : PT_CTX_DEBUG(ctx, PT_LOG_CAT_SEND,
856 : "Broadcast sent to %u peer(s)", sent_count);
857 :
858 0 : return PT_OK;
859 : }
860 :
861 : /* ========================================================================== */
862 : /* Queue Status (Phase 4) */
863 : /* ========================================================================== */
864 :
865 : /**
866 : * Get send queue status for peer
867 : *
868 : * Returns the number of pending messages in the send queue and available
869 : * slots. Useful for monitoring backpressure and flow control.
870 : *
871 : * @param ctx Valid PeerTalk context
872 : * @param peer_id Peer ID to query
873 : * @param out_pending Receives count of pending messages (can be NULL)
874 : * @param out_available Receives count of available slots (can be NULL)
875 : * @return PT_OK on success, PT_ERR_* on failure
876 : */
877 9 : PeerTalk_Error PeerTalk_GetQueueStatus(PeerTalk_Context *ctx_pub,
878 : PeerTalk_PeerID peer_id,
879 : uint16_t *out_pending,
880 : uint16_t *out_available) {
881 9 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
882 : struct pt_peer *peer;
883 : struct pt_queue *queue;
884 :
885 : /* Validate parameters */
886 9 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
887 1 : return PT_ERR_INVALID_STATE;
888 : }
889 :
890 : /* Find peer by ID */
891 8 : peer = pt_peer_find_by_id(ctx, peer_id);
892 8 : if (!peer) {
893 1 : return PT_ERR_PEER_NOT_FOUND;
894 : }
895 :
896 : /* Get send queue */
897 7 : queue = peer->send_queue;
898 7 : if (!queue || queue->magic != PT_QUEUE_MAGIC) {
899 1 : return PT_ERR_INVALID_STATE;
900 : }
901 :
902 : /* Return queue status */
903 6 : if (out_pending) {
904 6 : *out_pending = queue->count;
905 : }
906 6 : if (out_available) {
907 6 : *out_available = queue->capacity - queue->count;
908 : }
909 :
910 6 : return PT_OK;
911 : }
912 :
913 : /* ========================================================================== */
914 : /* PeerTalk_GetLog - Get Library Logger for Configuration */
915 : /* ========================================================================== */
916 :
917 0 : PT_Log *PeerTalk_GetLog(PeerTalk_Context *ctx_handle) {
918 0 : struct pt_context *ctx = (struct pt_context *)ctx_handle;
919 0 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
920 0 : return NULL;
921 : }
922 0 : return ctx->log;
923 : }
924 :
925 : /* ========================================================================== */
926 : /* Capability Negotiation (Phase 6+) */
927 : /* ========================================================================== */
928 :
929 : /**
930 : * Get negotiated capabilities for a peer
931 : *
932 : * Returns information about peer's constraints and negotiated parameters.
933 : * Useful for adapting message sizes to peer capabilities.
934 : */
935 1 : PeerTalk_Error PeerTalk_GetPeerCapabilities(PeerTalk_Context *ctx_pub,
936 : PeerTalk_PeerID peer_id,
937 : PeerTalk_Capabilities *caps) {
938 1 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
939 : struct pt_peer *peer;
940 :
941 : /* Validate parameters */
942 1 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
943 0 : return PT_ERR_INVALID_STATE;
944 : }
945 1 : if (!caps) {
946 0 : return PT_ERR_INVALID_PARAM;
947 : }
948 :
949 : /* Find peer */
950 1 : peer = pt_peer_find_by_id(ctx, peer_id);
951 1 : if (!peer) {
952 1 : return PT_ERR_PEER_NOT_FOUND;
953 : }
954 :
955 : /* Fill in capabilities.
956 : * If capability exchange hasn't completed yet (caps_exchanged==0), return
957 : * sensible defaults rather than uninitialized zeros. This commonly happens
958 : * when called from on_peer_connected callback before first message exchange. */
959 0 : caps->max_message_size = peer->hot.effective_max_msg;
960 0 : caps->fragmentation_active = (ctx->enable_fragmentation &&
961 0 : peer->hot.effective_max_msg < ctx->local_max_message) ? 1 : 0;
962 :
963 0 : if (peer->cold.caps.caps_exchanged) {
964 : /* Peer sent capabilities - use negotiated values */
965 0 : caps->preferred_chunk = peer->cold.caps.preferred_chunk;
966 0 : caps->capability_flags = peer->cold.caps.capability_flags;
967 0 : caps->recv_buffer_size = peer->cold.caps.recv_buffer_size;
968 0 : caps->optimal_chunk = peer->cold.caps.optimal_chunk;
969 0 : caps->buffer_pressure = peer->cold.caps.buffer_pressure;
970 : } else {
971 : /* Not yet exchanged - return conservative defaults */
972 0 : caps->preferred_chunk = ctx->local_preferred_chunk;
973 0 : caps->capability_flags = 0;
974 0 : caps->recv_buffer_size = 8192; /* Typical TCP buffer */
975 0 : caps->optimal_chunk = 2048; /* 25% of 8KB buffer */
976 0 : caps->buffer_pressure = 0;
977 : }
978 :
979 0 : return PT_OK;
980 : }
981 :
982 : /**
983 : * Get effective max message size for a peer
984 : *
985 : * Quick accessor for the negotiated maximum message size.
986 : * Returns min(our_max, peer_max) for connected peers.
987 : */
988 1 : uint16_t PeerTalk_GetPeerMaxMessage(PeerTalk_Context *ctx_pub,
989 : PeerTalk_PeerID peer_id) {
990 1 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
991 : struct pt_peer *peer;
992 :
993 : /* Validate parameters */
994 1 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
995 0 : return 0;
996 : }
997 :
998 : /* Find peer */
999 1 : peer = pt_peer_find_by_id(ctx, peer_id);
1000 1 : if (!peer) {
1001 1 : return 0;
1002 : }
1003 :
1004 0 : return peer->hot.effective_max_msg;
1005 : }
1006 :
1007 : /**
1008 : * Get optimal send chunk size for a peer
1009 : *
1010 : * Returns the ideal message size for sending to this peer. For Classic Mac
1011 : * peers, this is 25% of their receive buffer (the MacTCP completion threshold).
1012 : */
1013 0 : uint16_t PeerTalk_GetPeerOptimalChunk(PeerTalk_Context *ctx_pub,
1014 : PeerTalk_PeerID peer_id) {
1015 0 : struct pt_context *ctx = (struct pt_context *)ctx_pub;
1016 : struct pt_peer *peer;
1017 :
1018 : /* Validate parameters */
1019 0 : if (!ctx || ctx->magic != PT_CONTEXT_MAGIC) {
1020 0 : return 1024; /* Default chunk */
1021 : }
1022 :
1023 : /* Find peer */
1024 0 : peer = pt_peer_find_by_id(ctx, peer_id);
1025 0 : if (!peer) {
1026 0 : return 1024; /* Default chunk */
1027 : }
1028 :
1029 : /* Return peer's optimal chunk if known, otherwise default */
1030 0 : if (peer->cold.caps.caps_exchanged && peer->cold.caps.optimal_chunk > 0) {
1031 0 : return peer->cold.caps.optimal_chunk;
1032 : }
1033 :
1034 : /* Fall back to effective_chunk (adaptive based on RTT) or default */
1035 0 : if (peer->hot.effective_chunk > 0) {
1036 0 : return peer->hot.effective_chunk;
1037 : }
1038 :
1039 0 : return 1024; /* Default chunk */
1040 : }
|