LCOV - code coverage report
Current view: top level - core - pt_init.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 258 354 72.9 %
Date: 2026-02-22 12:14:12 Functions: 22 26 84.6 %

          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             : }

Generated by: LCOV version 1.14