LCOV - code coverage report
Current view: top level - src/infrastructure - transport.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 100.0 % 96 96
Test Date: 2026-04-20 19:54:24 Functions: 100.0 % 5 5

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file transport.c
       6              :  * @brief TCP transport with MTProto Abridged encoding.
       7              :  */
       8              : 
       9              : #include "transport.h"
      10              : #include "platform/socket.h"
      11              : #include "logger.h"
      12              : 
      13              : #include <errno.h>
      14              : #include <stdlib.h>
      15              : #include <string.h>
      16              : 
      17              : #define ABRIDGED_MARKER 0xEF
      18              : 
      19          260 : void transport_init(Transport *t) {
      20          260 :     if (!t) return;
      21          260 :     memset(t, 0, sizeof(*t));
      22          260 :     t->fd = -1;
      23              : }
      24              : 
      25          259 : int transport_connect(Transport *t, const char *host, int port) {
      26          259 :     if (!t || !host) return -1;
      27              : 
      28          259 :     t->fd = sys_socket_create();
      29          259 :     if (t->fd < 0) {
      30            1 :         logger_log(LOG_ERROR, "transport: failed to create socket");
      31            1 :         return -1;
      32              :     }
      33              : 
      34          258 :     if (sys_socket_connect(t->fd, host, port) < 0) {
      35            3 :         logger_log(LOG_ERROR, "transport: failed to connect to %s:%d", host, port);
      36            3 :         sys_socket_close(t->fd);
      37            3 :         t->fd = -1;
      38            3 :         return -1;
      39              :     }
      40              : 
      41              :     /* Send abridged transport marker, retrying on EINTR/EAGAIN */
      42          255 :     uint8_t marker = ABRIDGED_MARKER;
      43              :     ssize_t msent;
      44              :     do {
      45          255 :         msent = sys_socket_send(t->fd, &marker, 1);
      46          255 :     } while (msent < 0 && (errno == EINTR || errno == EAGAIN));
      47          255 :     if (msent != 1) {
      48            1 :         logger_log(LOG_ERROR, "transport: failed to send abridged marker");
      49            1 :         sys_socket_close(t->fd);
      50            1 :         t->fd = -1;
      51            1 :         return -1;
      52              :     }
      53              : 
      54          254 :     t->connected = 1;
      55          254 :     return 0;
      56              : }
      57              : 
      58          332 : int transport_send(Transport *t, const uint8_t *data, size_t len) {
      59          332 :     if (!t || !data || len == 0 || t->fd < 0) return -1;
      60              : 
      61              :     /* MTProto Abridged transport requires 4-byte aligned payloads.
      62              :      * If len is not a multiple of 4, the length prefix would truncate
      63              :      * and desynchronize the TCP stream. */
      64          332 :     if (len % 4 != 0) {
      65            1 :         logger_log(LOG_ERROR, "transport: send length %zu not 4-byte aligned", len);
      66            1 :         return -1;
      67              :     }
      68              : 
      69              :     /* Abridged: length is in 4-byte units */
      70          331 :     size_t wire_len = len / 4;
      71              : 
      72          331 :     if (wire_len < 0x7F) {
      73          298 :         uint8_t prefix = (uint8_t)wire_len;
      74              :         ssize_t n;
      75              :         do {
      76          300 :             n = sys_socket_send(t->fd, &prefix, 1);
      77          300 :         } while (n < 0 && (errno == EINTR || errno == EAGAIN));
      78          298 :         if (n != 1) {
      79            1 :             logger_log(LOG_ERROR, "transport: failed to send 1-byte length prefix");
      80            1 :             return -1;
      81              :         }
      82              :     } else {
      83              :         /* Abridged extended prefix: 0x7F + 3 LE bytes carrying wire_len
      84              :          * (in 4-byte units). Capacity = 0xFFFFFF * 4 = ~67 MB. */
      85              :         uint8_t prefix[4];
      86           33 :         prefix[0] = 0x7F;
      87           33 :         prefix[1] = (uint8_t)(wire_len & 0xFF);
      88           33 :         prefix[2] = (uint8_t)((wire_len >> 8) & 0xFF);
      89           33 :         prefix[3] = (uint8_t)((wire_len >> 16) & 0xFF);
      90              :         ssize_t n;
      91              :         do {
      92           33 :             n = sys_socket_send(t->fd, prefix, 4);
      93           33 :         } while (n < 0 && (errno == EINTR || errno == EAGAIN));
      94           33 :         if (n != 4) {
      95            1 :             logger_log(LOG_ERROR, "transport: failed to send 4-byte length prefix");
      96            1 :             return -1;
      97              :         }
      98              :     }
      99              : 
     100              :     /* Send payload in chunks, retrying on EINTR/EAGAIN */
     101          329 :     size_t total = 0;
     102          662 :     while (total < len) {
     103              :         ssize_t sent;
     104              :         do {
     105          334 :             sent = sys_socket_send(t->fd, data + total, len - total);
     106          334 :         } while (sent < 0 && (errno == EINTR || errno == EAGAIN));
     107          334 :         if (sent <= 0) {
     108            1 :             logger_log(LOG_ERROR, "transport: send failed after %zu/%zu bytes", total, len);
     109            1 :             return -1;
     110              :         }
     111          333 :         total += (size_t)sent;
     112              :     }
     113          328 :     return 0;
     114              : }
     115              : 
     116          341 : int transport_recv(Transport *t, uint8_t *out, size_t max_len, size_t *out_len) {
     117          341 :     if (!t || !out || !out_len || t->fd < 0) return -1;
     118              : 
     119              :     /* Read first byte of length prefix, retrying on EINTR/EAGAIN */
     120              :     uint8_t first;
     121              :     ssize_t r;
     122              :     do {
     123          343 :         r = sys_socket_recv(t->fd, &first, 1);
     124          343 :     } while (r < 0 && (errno == EINTR || errno == EAGAIN));
     125          341 :     if (r != 1) {
     126            2 :         logger_log(LOG_ERROR, "transport: failed to read length prefix byte");
     127            2 :         return -1;
     128              :     }
     129              : 
     130              :     size_t wire_len;
     131          339 :     if (first < 0x7F) {
     132          313 :         wire_len = first;
     133              :     } else {
     134              :         /* 0x7F marker → 3-byte LE length follows. */
     135              :         uint8_t extra[3];
     136              :         do {
     137           26 :             r = sys_socket_recv(t->fd, extra, 3);
     138           26 :         } while (r < 0 && (errno == EINTR || errno == EAGAIN));
     139           26 :         if (r != 3) {
     140            1 :             logger_log(LOG_ERROR, "transport: failed to read 3-byte length prefix");
     141            1 :             return -1;
     142              :         }
     143           25 :         wire_len = (size_t)extra[0]
     144           25 :                  | ((size_t)extra[1] << 8)
     145           25 :                  | ((size_t)extra[2] << 16);
     146              :     }
     147              : 
     148          338 :     size_t payload_len = wire_len * 4;
     149          338 :     if (payload_len == 0) {
     150            1 :         *out_len = 0;
     151            1 :         return 0;
     152              :     }
     153          337 :     if (payload_len > max_len) {
     154            1 :         logger_log(LOG_ERROR, "transport: frame too large: %zu > %zu", payload_len, max_len);
     155            1 :         return -1;
     156              :     }
     157              : 
     158              :     /* Read payload, retrying on EINTR/EAGAIN */
     159          336 :     size_t total = 0;
     160          677 :     while (total < payload_len) {
     161              :         do {
     162          342 :             r = sys_socket_recv(t->fd, out + total, payload_len - total);
     163          342 :         } while (r < 0 && (errno == EINTR || errno == EAGAIN));
     164          342 :         if (r <= 0) {
     165            1 :             logger_log(LOG_ERROR, "transport: recv failed after %zu/%zu bytes", total, payload_len);
     166            1 :             return -1;
     167              :         }
     168          341 :         total += (size_t)r;
     169              :     }
     170              : 
     171          335 :     *out_len = payload_len;
     172          335 :     return 0;
     173              : }
     174              : 
     175          257 : void transport_close(Transport *t) {
     176          257 :     if (t && t->fd >= 0) {
     177          254 :         sys_socket_close(t->fd);
     178          254 :         t->fd = -1;
     179          254 :         t->connected = 0;
     180              :     }
     181          257 : }
        

Generated by: LCOV version 2.0-1