LCOV - code coverage report
Current view: top level - core - protocol.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 261 342 76.3 %
Date: 2026-02-22 12:14:12 Functions: 16 18 88.9 %

          Line data    Source code
       1             : /* protocol.c - Wire protocol implementation for PeerTalk */
       2             : 
       3             : #include "protocol.h"
       4             : #include "pt_internal.h"
       5             : #include "pt_compat.h"
       6             : #include "../../include/peertalk.h"
       7             : 
       8             : /* ========================================================================
       9             :  * CRC-16 Lookup Table
      10             :  * ======================================================================== */
      11             : 
      12             : /* CRC-16 table for polynomial 0x8408 (reflected 0x1021)
      13             :  * Generated with init=0x0000
      14             :  * Check value: pt_crc16("123456789", 9) == 0x2189
      15             :  */
      16             : static const uint16_t crc16_table[256] = {
      17             :     0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF,
      18             :     0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7,
      19             :     0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E,
      20             :     0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876,
      21             :     0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD,
      22             :     0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5,
      23             :     0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C,
      24             :     0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974,
      25             :     0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
      26             :     0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3,
      27             :     0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A,
      28             :     0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72,
      29             :     0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9,
      30             :     0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1,
      31             :     0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738,
      32             :     0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70,
      33             :     0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7,
      34             :     0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
      35             :     0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036,
      36             :     0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E,
      37             :     0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5,
      38             :     0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD,
      39             :     0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134,
      40             :     0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C,
      41             :     0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3,
      42             :     0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB,
      43             :     0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
      44             :     0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A,
      45             :     0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1,
      46             :     0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9,
      47             :     0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330,
      48             :     0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78
      49             : };
      50             : 
      51             : /* ========================================================================
      52             :  * CRC-16 Functions
      53             :  * ======================================================================== */
      54             : 
      55       20296 : uint16_t pt_crc16(const uint8_t *data, size_t len)
      56             : {
      57       20296 :     uint16_t crc = 0x0000;
      58      572223 :     while (len--) {
      59      551927 :         crc = (crc >> 8) ^ crc16_table[(crc ^ *data++) & 0xFF];
      60             :     }
      61       20296 :     return crc;
      62             : }
      63             : 
      64          65 : uint16_t pt_crc16_update(uint16_t crc, const uint8_t *data, size_t len)
      65             : {
      66        1368 :     while (len--) {
      67        1303 :         crc = (crc >> 8) ^ crc16_table[(crc ^ *data++) & 0xFF];
      68             :     }
      69          65 :     return crc;
      70             : }
      71             : 
      72           3 : int pt_crc16_check(const uint8_t *data, size_t len, uint16_t expected)
      73             : {
      74           3 :     return pt_crc16(data, len) == expected ? 1 : 0;
      75             : }
      76             : 
      77             : /* ========================================================================
      78             :  * Discovery Packet Functions
      79             :  * ======================================================================== */
      80             : 
      81       10060 : int pt_discovery_encode(const pt_discovery_packet *pkt, uint8_t *buf,
      82             :                         size_t buf_len)
      83             : {
      84             :     uint16_t crc;
      85             :     size_t packet_size;
      86             : 
      87             :     /* Validate name length */
      88       10060 :     if (pkt->name_len > PT_PEER_NAME_MAX) {
      89           0 :         return PT_ERR_INVALID;
      90             :     }
      91             : 
      92             :     /* Validate type */
      93       10060 :     if (pkt->type < PT_DISC_TYPE_ANNOUNCE || pkt->type > PT_DISC_TYPE_GOODBYE) {
      94           0 :         return PT_ERR_INVALID;
      95             :     }
      96             : 
      97             :     /* Calculate packet size */
      98       10060 :     packet_size = 12 + pkt->name_len + 2;  /* header + name + CRC */
      99             : 
     100             :     /* Check buffer size */
     101       10060 :     if (buf_len < packet_size) {
     102           0 :         return PT_ERR_BUFFER_FULL;
     103             :     }
     104             : 
     105             :     /* Magic: "PTLK" */
     106       10060 :     buf[0] = 'P';
     107       10060 :     buf[1] = 'T';
     108       10060 :     buf[2] = 'L';
     109       10060 :     buf[3] = 'K';
     110             : 
     111             :     /* Version, Type */
     112       10060 :     buf[4] = pkt->version;
     113       10060 :     buf[5] = pkt->type;
     114             : 
     115             :     /* Flags (big-endian) */
     116       10060 :     buf[6] = (pkt->flags >> 8) & 0xFF;
     117       10060 :     buf[7] = pkt->flags & 0xFF;
     118             : 
     119             :     /* Sender Port (big-endian) */
     120       10060 :     buf[8] = (pkt->sender_port >> 8) & 0xFF;
     121       10060 :     buf[9] = pkt->sender_port & 0xFF;
     122             : 
     123             :     /* Transports, Name Length */
     124       10060 :     buf[10] = pkt->transports;
     125       10060 :     buf[11] = pkt->name_len;
     126             : 
     127             :     /* Peer Name */
     128       10060 :     pt_memcpy(buf + 12, pkt->name, pkt->name_len);
     129             : 
     130             :     /* CRC-16 over header + name */
     131       10060 :     crc = pt_crc16(buf, 12 + pkt->name_len);
     132       10060 :     buf[12 + pkt->name_len] = (crc >> 8) & 0xFF;
     133       10060 :     buf[12 + pkt->name_len + 1] = crc & 0xFF;
     134             : 
     135       10060 :     return (int)packet_size;
     136             : }
     137             : 
     138       11230 : int pt_discovery_decode(struct pt_context *ctx, const uint8_t *buf, size_t len,
     139             :                         pt_discovery_packet *pkt)
     140             : {
     141             :     uint16_t crc_computed, crc_received;
     142             :     size_t expected_len;
     143             : 
     144             :     /* Minimum size check */
     145       11230 :     if (len < 14) {
     146         224 :         if (ctx) {
     147           1 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     148             :                         "Discovery packet too short: %zu bytes (min 14)", len);
     149             :         }
     150         224 :         return PT_ERR_TRUNCATED;
     151             :     }
     152             : 
     153             :     /* Magic validation */
     154       11006 :     if (buf[0] != 'P' || buf[1] != 'T' || buf[2] != 'L' || buf[3] != 'K') {
     155         830 :         if (ctx) {
     156           1 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     157             :                         "Invalid discovery magic: 0x%02X%02X%02X%02X",
     158             :                         buf[0], buf[1], buf[2], buf[3]);
     159             :         }
     160         830 :         return PT_ERR_MAGIC;
     161             :     }
     162             : 
     163             :     /* Extract version */
     164       10176 :     pkt->version = buf[4];
     165       10176 :     if (pkt->version != PT_PROTOCOL_VERSION) {
     166          10 :         if (ctx) {
     167           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     168             :                     "Protocol version mismatch: got %u, expected %u",
     169             :                     pkt->version, PT_PROTOCOL_VERSION);
     170             :         }
     171          10 :         return PT_ERR_VERSION;
     172             :     }
     173             : 
     174             :     /* Extract type */
     175       10166 :     pkt->type = buf[5];
     176       10166 :     if (pkt->type < PT_DISC_TYPE_ANNOUNCE || pkt->type > PT_DISC_TYPE_GOODBYE) {
     177          19 :         if (ctx) {
     178           6 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     179             :                     "Invalid discovery type: 0x%02X", pkt->type);
     180             :         }
     181          19 :         return PT_ERR_INVALID;
     182             :     }
     183             : 
     184             :     /* Extract flags (big-endian) */
     185       10147 :     pkt->flags = ((uint16_t)buf[6] << 8) | buf[7];
     186             : 
     187             :     /* Extract sender port (big-endian) */
     188       10147 :     pkt->sender_port = ((uint16_t)buf[8] << 8) | buf[9];
     189             : 
     190             :     /* Extract transports and name length */
     191       10147 :     pkt->transports = buf[10];
     192       10147 :     pkt->name_len = buf[11];
     193             : 
     194             :     /* Validate name length */
     195       10147 :     if (pkt->name_len > PT_PEER_NAME_MAX) {
     196           5 :         if (ctx) {
     197           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     198             :                     "Name length too long: %u (max %u)",
     199             :                     pkt->name_len, PT_PEER_NAME_MAX);
     200             :         }
     201           5 :         return PT_ERR_INVALID;
     202             :     }
     203             : 
     204             :     /* Check complete packet length */
     205       10142 :     expected_len = 12 + pkt->name_len + 2;
     206       10142 :     if (len < expected_len) {
     207           7 :         if (ctx) {
     208           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     209             :                     "Discovery packet truncated: %zu bytes (expected %zu)",
     210             :                     len, expected_len);
     211             :         }
     212           7 :         return PT_ERR_TRUNCATED;
     213             :     }
     214             : 
     215             :     /* Extract name */
     216       10135 :     pt_memcpy(pkt->name, buf + 12, pkt->name_len);
     217       10135 :     pkt->name[pkt->name_len] = '\0';  /* Null-terminate */
     218             : 
     219             :     /* Extract CRC (big-endian) */
     220       10135 :     crc_received = ((uint16_t)buf[12 + pkt->name_len] << 8) |
     221       10135 :                    buf[12 + pkt->name_len + 1];
     222             : 
     223             :     /* Verify CRC */
     224       10135 :     crc_computed = pt_crc16(buf, 12 + pkt->name_len);
     225       10135 :     if (crc_computed != crc_received) {
     226         124 :         if (ctx) {
     227           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     228             :                     "Discovery CRC mismatch: got 0x%04X, expected 0x%04X",
     229             :                     crc_received, crc_computed);
     230             :         }
     231         124 :         return PT_ERR_CRC;
     232             :     }
     233             : 
     234       10011 :     if (ctx) {
     235           1 :         PT_CTX_DEBUG(ctx, PT_LOG_CAT_PROTOCOL,
     236             :                      "Discovery packet decoded: type=%u, name='%s', port=%u",
     237             :                      pkt->type, pkt->name, pkt->sender_port);
     238             :     }
     239             : 
     240       10011 :     return 0;
     241             : }
     242             : 
     243             : /* ========================================================================
     244             :  * Message Frame Functions
     245             :  * ======================================================================== */
     246             : 
     247          50 : int pt_message_encode_header(const pt_message_header *hdr, uint8_t *buf)
     248             : {
     249             :     /* Magic: "PTMG" */
     250          50 :     buf[0] = 'P';
     251          50 :     buf[1] = 'T';
     252          50 :     buf[2] = 'M';
     253          50 :     buf[3] = 'G';
     254             : 
     255             :     /* Version, Type, Flags, Sequence */
     256          50 :     buf[4] = hdr->version;
     257          50 :     buf[5] = hdr->type;
     258          50 :     buf[6] = hdr->flags;
     259          50 :     buf[7] = hdr->sequence;
     260             : 
     261             :     /* Payload Length (big-endian) */
     262          50 :     buf[8] = (hdr->payload_len >> 8) & 0xFF;
     263          50 :     buf[9] = hdr->payload_len & 0xFF;
     264             : 
     265          50 :     return PT_MESSAGE_HEADER_SIZE;
     266             : }
     267             : 
     268        1055 : int pt_message_decode_header(struct pt_context *ctx, const uint8_t *buf,
     269             :                              size_t len, pt_message_header *hdr)
     270             : {
     271             :     /* Minimum size check */
     272        1055 :     if (len < PT_MESSAGE_HEADER_SIZE) {
     273         283 :         if (ctx) {
     274           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     275             :                     "Message header too short: %zu bytes (min %d)",
     276             :                     len, PT_MESSAGE_HEADER_SIZE);
     277             :         }
     278         283 :         return PT_ERR_TRUNCATED;
     279             :     }
     280             : 
     281             :     /* Magic validation */
     282         772 :     if (buf[0] != 'P' || buf[1] != 'T' || buf[2] != 'M' || buf[3] != 'G') {
     283         734 :         if (ctx) {
     284           1 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     285             :                     "Invalid message magic: 0x%02X%02X%02X%02X",
     286             :                     buf[0], buf[1], buf[2], buf[3]);
     287             :         }
     288         734 :         return PT_ERR_MAGIC;
     289             :     }
     290             : 
     291             :     /* Extract version */
     292          38 :     hdr->version = buf[4];
     293          38 :     if (hdr->version != PT_PROTOCOL_VERSION) {
     294           1 :         if (ctx) {
     295           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     296             :                     "Protocol version mismatch: got %u, expected %u",
     297             :                     hdr->version, PT_PROTOCOL_VERSION);
     298             :         }
     299           1 :         return PT_ERR_VERSION;
     300             :     }
     301             : 
     302             :     /* Extract type */
     303          37 :     hdr->type = buf[5];
     304          37 :     if (hdr->type < PT_MSG_TYPE_DATA || hdr->type > PT_MSG_TYPE_CAPABILITY) {
     305           6 :         if (ctx) {
     306           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     307             :                     "Invalid message type: 0x%02X", hdr->type);
     308             :         }
     309           6 :         return PT_ERR_INVALID;
     310             :     }
     311             : 
     312             :     /* Extract flags, sequence */
     313          31 :     hdr->flags = buf[6];
     314          31 :     hdr->sequence = buf[7];
     315             : 
     316             :     /* Extract payload length (big-endian) */
     317          31 :     hdr->payload_len = ((uint16_t)buf[8] << 8) | buf[9];
     318             : 
     319          31 :     if (ctx) {
     320          27 :         PT_CTX_DEBUG(ctx, PT_LOG_CAT_PROTOCOL,
     321             :                      "Message header decoded: type=%u, seq=%u, len=%u",
     322             :                      hdr->type, hdr->sequence, hdr->payload_len);
     323             :     }
     324             : 
     325          31 :     return 0;
     326             : }
     327             : 
     328             : /* ========================================================================
     329             :  * Compact Header Functions
     330             :  * ======================================================================== */
     331             : 
     332          46 : int pt_message_encode_compact(const pt_compact_header *hdr, uint8_t *buf)
     333             : {
     334             :     /* Marker byte: 'P' (0x50) */
     335          46 :     buf[0] = PT_COMPACT_MARKER;
     336             : 
     337             :     /* TypeFlags: high nibble = type (0-7), low nibble = flags (0-15) */
     338          46 :     buf[1] = ((hdr->type & 0x0F) << 4) | (hdr->flags & 0x0F);
     339             : 
     340             :     /* Payload Length (big-endian) */
     341          46 :     buf[2] = (hdr->payload_len >> 8) & 0xFF;
     342          46 :     buf[3] = hdr->payload_len & 0xFF;
     343             : 
     344          46 :     return PT_COMPACT_HEADER_SIZE;
     345             : }
     346             : 
     347          38 : int pt_message_decode_compact(const uint8_t *buf, size_t len, pt_compact_header *hdr)
     348             : {
     349             :     /* Minimum size check */
     350          38 :     if (len < PT_COMPACT_HEADER_SIZE) {
     351           1 :         return PT_ERR_TRUNCATED;
     352             :     }
     353             : 
     354             :     /* Marker validation */
     355          37 :     if (buf[0] != PT_COMPACT_MARKER) {
     356           1 :         return PT_ERR_MAGIC;
     357             :     }
     358             : 
     359             :     /* Extract type and flags from TypeFlags byte */
     360          36 :     hdr->type = (buf[1] >> 4) & 0x0F;
     361          36 :     hdr->flags = buf[1] & 0x0F;
     362             : 
     363             :     /* Validate type */
     364          36 :     if (hdr->type < PT_MSG_TYPE_DATA || hdr->type > PT_MSG_TYPE_CAPABILITY) {
     365           0 :         return PT_ERR_INVALID;
     366             :     }
     367             : 
     368             :     /* Extract payload length (big-endian) */
     369          36 :     hdr->payload_len = ((uint16_t)buf[2] << 8) | buf[3];
     370             : 
     371          36 :     return 0;
     372             : }
     373             : 
     374          49 : int pt_message_is_compact(const uint8_t *buf, size_t len)
     375             : {
     376          49 :     if (len < 2) {
     377           1 :         return 0;
     378             :     }
     379             : 
     380             :     /* Compact header starts with 'P' (0x50) followed by TypeFlags byte.
     381             :      * Full header starts with 'P' 'T' 'M' 'G'.
     382             :      * If second byte is NOT 'T', it's a compact header. */
     383          48 :     if (buf[0] == PT_COMPACT_MARKER && buf[1] != 'T') {
     384          15 :         return 1;
     385             :     }
     386             : 
     387          33 :     return 0;
     388             : }
     389             : 
     390             : /* ========================================================================
     391             :  * UDP Message Functions
     392             :  * ======================================================================== */
     393             : 
     394           5 : int pt_udp_encode(const void *payload, uint16_t payload_len,
     395             :                   uint16_t sender_port, uint8_t *buf, size_t buf_len)
     396             : {
     397           5 :     size_t packet_size = PT_UDP_HEADER_SIZE + payload_len;
     398             : 
     399             :     /* Check buffer size */
     400           5 :     if (buf_len < packet_size) {
     401           0 :         return PT_ERR_BUFFER_FULL;
     402             :     }
     403             : 
     404             :     /* Magic: "PTUD" */
     405           5 :     buf[0] = 'P';
     406           5 :     buf[1] = 'T';
     407           5 :     buf[2] = 'U';
     408           5 :     buf[3] = 'D';
     409             : 
     410             :     /* Sender Port (big-endian) */
     411           5 :     buf[4] = (sender_port >> 8) & 0xFF;
     412           5 :     buf[5] = sender_port & 0xFF;
     413             : 
     414             :     /* Payload Length (big-endian) */
     415           5 :     buf[6] = (payload_len >> 8) & 0xFF;
     416           5 :     buf[7] = payload_len & 0xFF;
     417             : 
     418             :     /* Payload */
     419           5 :     if (payload_len > 0) {
     420           5 :         pt_memcpy(buf + PT_UDP_HEADER_SIZE, payload, payload_len);
     421             :     }
     422             : 
     423           5 :     return (int)packet_size;
     424             : }
     425             : 
     426        1017 : int pt_udp_decode(struct pt_context *ctx, const uint8_t *buf, size_t len,
     427             :                   uint16_t *sender_port_out, const void **payload_out,
     428             :                   uint16_t *payload_len_out)
     429             : {
     430             :     uint16_t payload_len;
     431             : 
     432             :     /* Minimum size check */
     433        1017 :     if (len < PT_UDP_HEADER_SIZE) {
     434         121 :         if (ctx) {
     435           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     436             :                     "UDP message too short: %zu bytes (min %d)",
     437             :                     len, PT_UDP_HEADER_SIZE);
     438             :         }
     439         121 :         return PT_ERR_TRUNCATED;
     440             :     }
     441             : 
     442             :     /* Magic validation */
     443         896 :     if (buf[0] != 'P' || buf[1] != 'T' || buf[2] != 'U' || buf[3] != 'D') {
     444         893 :         if (ctx) {
     445           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     446             :                     "Invalid UDP magic: 0x%02X%02X%02X%02X",
     447             :                     buf[0], buf[1], buf[2], buf[3]);
     448             :         }
     449         893 :         return PT_ERR_MAGIC;
     450             :     }
     451             : 
     452             :     /* Extract sender port (big-endian) */
     453           3 :     *sender_port_out = ((uint16_t)buf[4] << 8) | buf[5];
     454             : 
     455             :     /* Extract payload length (big-endian) */
     456           3 :     payload_len = ((uint16_t)buf[6] << 8) | buf[7];
     457           3 :     *payload_len_out = payload_len;
     458             : 
     459             :     /* Verify complete packet length */
     460           3 :     if (len < PT_UDP_HEADER_SIZE + payload_len) {
     461           0 :         if (ctx) {
     462           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     463             :                     "UDP packet truncated: %zu bytes (expected %u)",
     464             :                     len, PT_UDP_HEADER_SIZE + payload_len);
     465             :         }
     466           0 :         return PT_ERR_TRUNCATED;
     467             :     }
     468             : 
     469             :     /* Set payload pointer */
     470           3 :     *payload_out = buf + PT_UDP_HEADER_SIZE;
     471             : 
     472           3 :     if (ctx) {
     473           2 :         PT_CTX_DEBUG(ctx, PT_LOG_CAT_PROTOCOL,
     474             :                      "UDP message decoded: port=%u, len=%u",
     475             :                      *sender_port_out, payload_len);
     476             :     }
     477             : 
     478           3 :     return 0;
     479             : }
     480             : 
     481             : /* ========================================================================
     482             :  * Capability Message Functions
     483             :  * ======================================================================== */
     484             : 
     485          37 : int pt_capability_encode(const pt_capability_msg *caps, uint8_t *buf, size_t buf_len)
     486             : {
     487          37 :     size_t offset = 0;
     488             : 
     489             :     /* Calculate required size: 6 TLVs
     490             :      * - MAX_MESSAGE: 1 + 1 + 2 = 4
     491             :      * - PREFERRED_CHUNK: 1 + 1 + 2 = 4
     492             :      * - BUFFER_PRESSURE: 1 + 1 + 1 = 3
     493             :      * - FLAGS: 1 + 1 + 2 = 4
     494             :      * - RECV_BUFFER_SIZE: 1 + 1 + 2 = 4
     495             :      * - OPTIMAL_CHUNK: 1 + 1 + 2 = 4
     496             :      * Total: 23 bytes
     497             :      */
     498          37 :     if (buf_len < 23) {
     499           1 :         return PT_ERR_BUFFER_FULL;
     500             :     }
     501             : 
     502             :     /* TLV 1: Max message size (2 bytes) */
     503          36 :     buf[offset++] = PT_CAP_MAX_MESSAGE;
     504          36 :     buf[offset++] = 2;
     505          36 :     buf[offset++] = (caps->max_message_size >> 8) & 0xFF;
     506          36 :     buf[offset++] = caps->max_message_size & 0xFF;
     507             : 
     508             :     /* TLV 2: Preferred chunk (2 bytes) */
     509          36 :     buf[offset++] = PT_CAP_PREFERRED_CHUNK;
     510          36 :     buf[offset++] = 2;
     511          36 :     buf[offset++] = (caps->preferred_chunk >> 8) & 0xFF;
     512          36 :     buf[offset++] = caps->preferred_chunk & 0xFF;
     513             : 
     514             :     /* TLV 3: Buffer pressure (1 byte) */
     515          36 :     buf[offset++] = PT_CAP_BUFFER_PRESSURE;
     516          36 :     buf[offset++] = 1;
     517          36 :     buf[offset++] = caps->buffer_pressure;
     518             : 
     519             :     /* TLV 4: Capability flags (2 bytes) */
     520          36 :     buf[offset++] = PT_CAP_FLAGS;
     521          36 :     buf[offset++] = 2;
     522          36 :     buf[offset++] = (caps->capability_flags >> 8) & 0xFF;
     523          36 :     buf[offset++] = caps->capability_flags & 0xFF;
     524             : 
     525             :     /* TLV 5: Receive buffer size (2 bytes) */
     526          36 :     buf[offset++] = PT_CAP_RECV_BUFFER_SIZE;
     527          36 :     buf[offset++] = 2;
     528          36 :     buf[offset++] = (caps->recv_buffer_size >> 8) & 0xFF;
     529          36 :     buf[offset++] = caps->recv_buffer_size & 0xFF;
     530             : 
     531             :     /* TLV 6: Optimal chunk size (2 bytes)
     532             :      * This is 25% of recv_buffer - the MacTCP completion threshold.
     533             :      * Tells sender the ideal per-send size for this receiver. */
     534          36 :     buf[offset++] = PT_CAP_OPTIMAL_CHUNK;
     535          36 :     buf[offset++] = 2;
     536          36 :     buf[offset++] = (caps->optimal_chunk >> 8) & 0xFF;
     537          36 :     buf[offset++] = caps->optimal_chunk & 0xFF;
     538             : 
     539          36 :     return (int)offset;
     540             : }
     541             : 
     542          21 : int pt_capability_decode(struct pt_context *ctx, const uint8_t *buf, size_t len,
     543             :                          pt_capability_msg *caps)
     544             : {
     545          21 :     size_t offset = 0;
     546          21 :     int tlvs_parsed = 0;
     547             : 
     548             :     /* Initialize with defaults (for missing TLVs) */
     549          21 :     caps->max_message_size = PT_CAP_DEFAULT_MAX_MSG;
     550          21 :     caps->preferred_chunk = PT_CAP_DEFAULT_CHUNK;
     551          21 :     caps->buffer_pressure = PT_CAP_DEFAULT_PRESSURE;
     552          21 :     caps->capability_flags = 0;
     553          21 :     caps->recv_buffer_size = 8192;  /* Default to typical TCP buffer */
     554          21 :     caps->optimal_chunk = 2048;     /* Default: 25% of 8KB buffer */
     555          21 :     caps->reserved = 0;
     556             : 
     557             :     /* Log payload info for debugging capability exchange issues */
     558          21 :     if (ctx && len < 23) {
     559             :         /* Expected payload is 23 bytes (6 TLVs). Log if shorter. */
     560           0 :         PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     561             :             "Capability payload short: len=%zu (expected 23), bytes: %02X %02X %02X %02X",
     562             :             len,
     563             :             (len > 0) ? buf[0] : 0, (len > 1) ? buf[1] : 0,
     564             :             (len > 2) ? buf[2] : 0, (len > 3) ? buf[3] : 0);
     565             :     }
     566             : 
     567             :     /* Parse TLVs */
     568         147 :     while (offset + 2 <= len) {
     569         126 :         uint8_t type = buf[offset++];
     570         126 :         uint8_t tlv_len = buf[offset++];
     571             : 
     572             :         /* Check TLV fits in buffer */
     573         126 :         if (offset + tlv_len > len) {
     574           0 :             if (ctx) {
     575           0 :                 PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     576             :                     "Capability TLV truncated: type=%u, len=%u, remaining=%zu",
     577             :                     type, tlv_len, len - offset);
     578             :             }
     579           0 :             return PT_ERR_TRUNCATED;
     580             :         }
     581             : 
     582         126 :         switch (type) {
     583          21 :         case PT_CAP_MAX_MESSAGE:
     584          21 :             if (tlv_len >= 2) {
     585          21 :                 caps->max_message_size = ((uint16_t)buf[offset] << 8) | buf[offset + 1];
     586             :                 /* Clamp to valid range */
     587          21 :                 if (caps->max_message_size < PT_CAP_MIN_MAX_MSG) {
     588           0 :                     caps->max_message_size = PT_CAP_MIN_MAX_MSG;
     589             :                 }
     590          21 :                 if (caps->max_message_size > PT_CAP_MAX_MAX_MSG) {
     591           1 :                     caps->max_message_size = PT_CAP_MAX_MAX_MSG;
     592             :                 }
     593          21 :                 tlvs_parsed++;
     594             :             }
     595          21 :             break;
     596             : 
     597          21 :         case PT_CAP_PREFERRED_CHUNK:
     598          21 :             if (tlv_len >= 2) {
     599          21 :                 caps->preferred_chunk = ((uint16_t)buf[offset] << 8) | buf[offset + 1];
     600             :             }
     601          21 :             break;
     602             : 
     603          21 :         case PT_CAP_BUFFER_PRESSURE:
     604          21 :             if (tlv_len >= 1) {
     605          21 :                 caps->buffer_pressure = buf[offset];
     606          21 :                 if (caps->buffer_pressure > 100) {
     607           1 :                     caps->buffer_pressure = 100;
     608             :                 }
     609             :             }
     610          21 :             break;
     611             : 
     612          21 :         case PT_CAP_FLAGS:
     613          21 :             if (tlv_len >= 2) {
     614          21 :                 caps->capability_flags = ((uint16_t)buf[offset] << 8) | buf[offset + 1];
     615             :             }
     616          21 :             break;
     617             : 
     618          21 :         case PT_CAP_RECV_BUFFER_SIZE:
     619          21 :             if (tlv_len >= 2) {
     620          21 :                 caps->recv_buffer_size = ((uint16_t)buf[offset] << 8) | buf[offset + 1];
     621             :                 /* Minimum 4KB, maximum 64KB */
     622          21 :                 if (caps->recv_buffer_size < 4096) {
     623           4 :                     caps->recv_buffer_size = 4096;
     624             :                 }
     625             :             }
     626          21 :             break;
     627             : 
     628          21 :         case PT_CAP_OPTIMAL_CHUNK:
     629          21 :             if (tlv_len >= 2) {
     630          21 :                 caps->optimal_chunk = ((uint16_t)buf[offset] << 8) | buf[offset + 1];
     631             :                 /* Minimum 512 bytes, maximum 16KB */
     632          21 :                 if (caps->optimal_chunk < 512) {
     633           4 :                     caps->optimal_chunk = 512;
     634             :                 }
     635          21 :                 if (caps->optimal_chunk > 16384) {
     636           0 :                     caps->optimal_chunk = 16384;
     637             :                 }
     638             :             }
     639          21 :             break;
     640             : 
     641           0 :         default:
     642             :             /* Unknown TLV - skip it (forward compatibility) */
     643           0 :             if (ctx) {
     644           0 :                 PT_CTX_DEBUG(ctx, PT_LOG_CAT_PROTOCOL,
     645             :                     "Unknown capability TLV: type=%u, len=%u", type, tlv_len);
     646             :             }
     647           0 :             break;
     648             :         }
     649             : 
     650         126 :         offset += tlv_len;
     651             :     }
     652             : 
     653             :     /* Warn if no TLVs were parsed - indicates empty or malformed payload */
     654          21 :     if (ctx && tlvs_parsed == 0 && len > 0) {
     655           0 :         PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     656             :             "Capability decode: no MAX_MESSAGE TLV found in %zu bytes", len);
     657             :     }
     658             : 
     659          21 :     if (ctx) {
     660          17 :         PT_CTX_DEBUG(ctx, PT_LOG_CAT_PROTOCOL,
     661             :             "Capability decoded: max=%u, chunk=%u, pressure=%u, flags=0x%04X, recv_buf=%u, optimal=%u",
     662             :             caps->max_message_size, caps->preferred_chunk,
     663             :             caps->buffer_pressure, caps->capability_flags,
     664             :             caps->recv_buffer_size, caps->optimal_chunk);
     665             :     }
     666             : 
     667          21 :     return 0;
     668             : }
     669             : 
     670             : /* ========================================================================
     671             :  * Fragment Header Functions
     672             :  * ======================================================================== */
     673             : 
     674           4 : int pt_fragment_encode(const pt_fragment_header *hdr, uint8_t *buf)
     675             : {
     676             :     /* Message ID (big-endian) */
     677           4 :     buf[0] = (hdr->message_id >> 8) & 0xFF;
     678           4 :     buf[1] = hdr->message_id & 0xFF;
     679             : 
     680             :     /* Total length (big-endian) */
     681           4 :     buf[2] = (hdr->total_length >> 8) & 0xFF;
     682           4 :     buf[3] = hdr->total_length & 0xFF;
     683             : 
     684             :     /* Fragment offset (big-endian) */
     685           4 :     buf[4] = (hdr->fragment_offset >> 8) & 0xFF;
     686           4 :     buf[5] = hdr->fragment_offset & 0xFF;
     687             : 
     688             :     /* Flags and reserved */
     689           4 :     buf[6] = hdr->fragment_flags;
     690           4 :     buf[7] = 0;  /* Reserved */
     691             : 
     692           4 :     return PT_FRAGMENT_HEADER_SIZE;
     693             : }
     694             : 
     695           5 : int pt_fragment_decode(const uint8_t *buf, size_t len, pt_fragment_header *hdr)
     696             : {
     697           5 :     if (len < PT_FRAGMENT_HEADER_SIZE) {
     698           1 :         return PT_ERR_TRUNCATED;
     699             :     }
     700             : 
     701             :     /* Message ID (big-endian) */
     702           4 :     hdr->message_id = ((uint16_t)buf[0] << 8) | buf[1];
     703             : 
     704             :     /* Total length (big-endian) */
     705           4 :     hdr->total_length = ((uint16_t)buf[2] << 8) | buf[3];
     706             : 
     707             :     /* Fragment offset (big-endian) */
     708           4 :     hdr->fragment_offset = ((uint16_t)buf[4] << 8) | buf[5];
     709             : 
     710             :     /* Flags and reserved */
     711           4 :     hdr->fragment_flags = buf[6];
     712           4 :     hdr->reserved = buf[7];
     713             : 
     714           4 :     return 0;
     715             : }
     716             : 
     717             : /* ========================================================================
     718             :  * Fragment Reassembly
     719             :  *
     720             :  * These functions handle transparent fragment reassembly. When fragments
     721             :  * arrive, they're accumulated in the peer's recv_direct buffer. When the
     722             :  * last fragment arrives, the complete message is delivered to the app
     723             :  * callback. Applications never see individual fragments.
     724             :  *
     725             :  * DOD: Reassembly state is stored in pt_peer_cold (rarely accessed after
     726             :  * negotiation). The actual data goes in recv_direct buffer.
     727             :  * ======================================================================== */
     728             : 
     729           0 : int pt_reassembly_process(struct pt_context *ctx, struct pt_peer *peer,
     730             :                           const uint8_t *fragment_data, uint16_t fragment_len,
     731             :                           const pt_fragment_header *frag_hdr,
     732             :                           const uint8_t **complete_data, uint16_t *complete_len)
     733             : {
     734           0 :     pt_reassembly_state *rs = &peer->cold.reassembly;
     735           0 :     pt_direct_buffer *buf = &peer->recv_direct;
     736             :     const uint8_t *payload;
     737             :     uint16_t payload_len;
     738             : 
     739             :     /* Extract fragment payload (skip fragment header in data) */
     740           0 :     if (fragment_len < PT_FRAGMENT_HEADER_SIZE) {
     741           0 :         PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     742             :             "Fragment too short: %u bytes", fragment_len);
     743           0 :         return PT_ERR_TRUNCATED;
     744             :     }
     745           0 :     payload = fragment_data + PT_FRAGMENT_HEADER_SIZE;
     746           0 :     payload_len = fragment_len - PT_FRAGMENT_HEADER_SIZE;
     747             : 
     748             :     /* Validate fragment offset + length doesn't exceed total */
     749           0 :     if (frag_hdr->fragment_offset + payload_len > frag_hdr->total_length) {
     750           0 :         PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     751             :             "Fragment exceeds total: offset=%u len=%u total=%u",
     752             :             frag_hdr->fragment_offset, payload_len, frag_hdr->total_length);
     753           0 :         rs->active = 0;
     754           0 :         return PT_ERR_INVALID_PARAM;
     755             :     }
     756             : 
     757             :     /* Check if this is the first fragment */
     758           0 :     if (frag_hdr->fragment_flags & PT_FRAGMENT_FLAG_FIRST) {
     759             :         /* Start new reassembly */
     760           0 :         if (rs->active && rs->message_id != frag_hdr->message_id) {
     761           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     762             :                 "Dropping incomplete reassembly for msg %u (new msg %u)",
     763             :                 rs->message_id, frag_hdr->message_id);
     764             :         }
     765             : 
     766             :         /* Validate total length fits in buffer */
     767           0 :         if (frag_hdr->total_length > buf->capacity) {
     768           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     769             :                 "Reassembly too large: %u > %u",
     770             :                 frag_hdr->total_length, buf->capacity);
     771           0 :             return PT_ERR_MESSAGE_TOO_LARGE;
     772             :         }
     773             : 
     774             :         /* Initialize reassembly state */
     775           0 :         rs->message_id = frag_hdr->message_id;
     776           0 :         rs->total_length = frag_hdr->total_length;
     777           0 :         rs->received_length = 0;
     778           0 :         rs->active = 1;
     779             : 
     780           0 :         PT_CTX_DEBUG(ctx, PT_LOG_CAT_PROTOCOL,
     781             :             "Starting reassembly: msg_id=%u total=%u",
     782             :             frag_hdr->message_id, frag_hdr->total_length);
     783             :     }
     784             : 
     785             :     /* Validate we're reassembling the right message */
     786           0 :     if (!rs->active) {
     787           0 :         PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     788             :             "Fragment received without FIRST: msg_id=%u offset=%u",
     789             :             frag_hdr->message_id, frag_hdr->fragment_offset);
     790           0 :         return PT_ERR_INVALID_STATE;
     791             :     }
     792             : 
     793           0 :     if (rs->message_id != frag_hdr->message_id) {
     794           0 :         PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     795             :             "Fragment msg_id mismatch: expected %u got %u",
     796             :             rs->message_id, frag_hdr->message_id);
     797           0 :         return PT_ERR_INVALID_PARAM;
     798             :     }
     799             : 
     800             :     /* Validate offset matches what we've received */
     801           0 :     if (frag_hdr->fragment_offset != rs->received_length) {
     802           0 :         PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     803             :             "Fragment offset mismatch: expected %u got %u",
     804             :             rs->received_length, frag_hdr->fragment_offset);
     805             :         /* Could be out-of-order - for now, abort reassembly */
     806           0 :         rs->active = 0;
     807           0 :         return PT_ERR_INVALID_PARAM;
     808             :     }
     809             : 
     810             :     /* Copy fragment data to buffer */
     811           0 :     pt_memcpy(buf->data + rs->received_length, payload, payload_len);
     812           0 :     rs->received_length += payload_len;
     813             : 
     814           0 :     PT_CTX_DEBUG(ctx, PT_LOG_CAT_PROTOCOL,
     815             :         "Fragment received: msg_id=%u offset=%u len=%u (%u/%u)",
     816             :         frag_hdr->message_id, frag_hdr->fragment_offset, payload_len,
     817             :         rs->received_length, rs->total_length);
     818             : 
     819             :     /* Check if this is the last fragment */
     820           0 :     if (frag_hdr->fragment_flags & PT_FRAGMENT_FLAG_LAST) {
     821           0 :         if (rs->received_length != rs->total_length) {
     822           0 :             PT_CTX_WARN(ctx, PT_LOG_CAT_PROTOCOL,
     823             :                 "LAST fragment but length mismatch: received=%u total=%u",
     824             :                 rs->received_length, rs->total_length);
     825           0 :             rs->active = 0;
     826           0 :             return PT_ERR_TRUNCATED;
     827             :         }
     828             : 
     829             :         /* Reassembly complete! */
     830           0 :         *complete_data = buf->data;
     831           0 :         *complete_len = rs->total_length;
     832             : 
     833           0 :         PT_CTX_INFO(ctx, PT_LOG_CAT_PROTOCOL,
     834             :             "Reassembly complete: msg_id=%u total=%u bytes",
     835             :             rs->message_id, rs->total_length);
     836             : 
     837           0 :         rs->active = 0;
     838           0 :         return 1;  /* Complete message ready */
     839             :     }
     840             : 
     841             :     /* More fragments expected */
     842           0 :     *complete_data = NULL;
     843           0 :     *complete_len = 0;
     844           0 :     return 0;  /* Not complete yet */
     845             : }
     846             : 
     847           0 : void pt_reassembly_reset(struct pt_peer *peer)
     848             : {
     849           0 :     peer->cold.reassembly.active = 0;
     850           0 :     peer->cold.reassembly.received_length = 0;
     851           0 : }

Generated by: LCOV version 1.14