LCOV - code coverage report
Current view: top level - src/infrastructure - transport.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 96 96
Test Date: 2026-04-20 19:54:22 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          699 : void transport_init(Transport *t) {
      20          699 :     if (!t) return;
      21          699 :     memset(t, 0, sizeof(*t));
      22          699 :     t->fd = -1;
      23              : }
      24              : 
      25          576 : int transport_connect(Transport *t, const char *host, int port) {
      26          576 :     if (!t || !host) return -1;
      27              : 
      28          574 :     t->fd = sys_socket_create();
      29          574 :     if (t->fd < 0) {
      30            3 :         logger_log(LOG_ERROR, "transport: failed to create socket");
      31            3 :         return -1;
      32              :     }
      33              : 
      34          571 :     if (sys_socket_connect(t->fd, host, port) < 0) {
      35            8 :         logger_log(LOG_ERROR, "transport: failed to connect to %s:%d", host, port);
      36            8 :         sys_socket_close(t->fd);
      37            8 :         t->fd = -1;
      38            8 :         return -1;
      39              :     }
      40              : 
      41              :     /* Send abridged transport marker, retrying on EINTR/EAGAIN */
      42          563 :     uint8_t marker = ABRIDGED_MARKER;
      43              :     ssize_t msent;
      44              :     do {
      45          563 :         msent = sys_socket_send(t->fd, &marker, 1);
      46          563 :     } while (msent < 0 && (errno == EINTR || errno == EAGAIN));
      47          563 :     if (msent != 1) {
      48            3 :         logger_log(LOG_ERROR, "transport: failed to send abridged marker");
      49            3 :         sys_socket_close(t->fd);
      50            3 :         t->fd = -1;
      51            3 :         return -1;
      52              :     }
      53              : 
      54          560 :     t->connected = 1;
      55          560 :     return 0;
      56              : }
      57              : 
      58          835 : int transport_send(Transport *t, const uint8_t *data, size_t len) {
      59          835 :     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          831 :     if (len % 4 != 0) {
      65            5 :         logger_log(LOG_ERROR, "transport: send length %zu not 4-byte aligned", len);
      66            5 :         return -1;
      67              :     }
      68              : 
      69              :     /* Abridged: length is in 4-byte units */
      70          826 :     size_t wire_len = len / 4;
      71              : 
      72          826 :     if (wire_len < 0x7F) {
      73          733 :         uint8_t prefix = (uint8_t)wire_len;
      74              :         ssize_t n;
      75              :         do {
      76          738 :             n = sys_socket_send(t->fd, &prefix, 1);
      77          738 :         } while (n < 0 && (errno == EINTR || errno == EAGAIN));
      78          733 :         if (n != 1) {
      79            3 :             logger_log(LOG_ERROR, "transport: failed to send 1-byte length prefix");
      80            3 :             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           93 :         prefix[0] = 0x7F;
      87           93 :         prefix[1] = (uint8_t)(wire_len & 0xFF);
      88           93 :         prefix[2] = (uint8_t)((wire_len >> 8) & 0xFF);
      89           93 :         prefix[3] = (uint8_t)((wire_len >> 16) & 0xFF);
      90              :         ssize_t n;
      91              :         do {
      92           93 :             n = sys_socket_send(t->fd, prefix, 4);
      93           93 :         } while (n < 0 && (errno == EINTR || errno == EAGAIN));
      94           93 :         if (n != 4) {
      95            3 :             logger_log(LOG_ERROR, "transport: failed to send 4-byte length prefix");
      96            3 :             return -1;
      97              :         }
      98              :     }
      99              : 
     100              :     /* Send payload in chunks, retrying on EINTR/EAGAIN */
     101          820 :     size_t total = 0;
     102         1647 :     while (total < len) {
     103              :         ssize_t sent;
     104              :         do {
     105          831 :             sent = sys_socket_send(t->fd, data + total, len - total);
     106          831 :         } while (sent < 0 && (errno == EINTR || errno == EAGAIN));
     107          830 :         if (sent <= 0) {
     108            3 :             logger_log(LOG_ERROR, "transport: send failed after %zu/%zu bytes", total, len);
     109            3 :             return -1;
     110              :         }
     111          827 :         total += (size_t)sent;
     112              :     }
     113          817 :     return 0;
     114              : }
     115              : 
     116          862 : int transport_recv(Transport *t, uint8_t *out, size_t max_len, size_t *out_len) {
     117          862 :     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          863 :         r = sys_socket_recv(t->fd, &first, 1);
     124          863 :     } while (r < 0 && (errno == EINTR || errno == EAGAIN));
     125          858 :     if (r != 1) {
     126            9 :         logger_log(LOG_ERROR, "transport: failed to read length prefix byte");
     127            9 :         return -1;
     128              :     }
     129              : 
     130              :     size_t wire_len;
     131          849 :     if (first < 0x7F) {
     132          791 :         wire_len = first;
     133              :     } else {
     134              :         /* 0x7F marker → 3-byte LE length follows. */
     135              :         uint8_t extra[3];
     136              :         do {
     137           58 :             r = sys_socket_recv(t->fd, extra, 3);
     138           58 :         } while (r < 0 && (errno == EINTR || errno == EAGAIN));
     139           58 :         if (r != 3) {
     140            3 :             logger_log(LOG_ERROR, "transport: failed to read 3-byte length prefix");
     141            3 :             return -1;
     142              :         }
     143           55 :         wire_len = (size_t)extra[0]
     144           55 :                  | ((size_t)extra[1] << 8)
     145           55 :                  | ((size_t)extra[2] << 16);
     146              :     }
     147              : 
     148          846 :     size_t payload_len = wire_len * 4;
     149          846 :     if (payload_len == 0) {
     150            3 :         *out_len = 0;
     151            3 :         return 0;
     152              :     }
     153          843 :     if (payload_len > max_len) {
     154            4 :         logger_log(LOG_ERROR, "transport: frame too large: %zu > %zu", payload_len, max_len);
     155            4 :         return -1;
     156              :     }
     157              : 
     158              :     /* Read payload, retrying on EINTR/EAGAIN */
     159          839 :     size_t total = 0;
     160         1687 :     while (total < payload_len) {
     161              :         do {
     162          853 :             r = sys_socket_recv(t->fd, out + total, payload_len - total);
     163          853 :         } while (r < 0 && (errno == EINTR || errno == EAGAIN));
     164          852 :         if (r <= 0) {
     165            4 :             logger_log(LOG_ERROR, "transport: recv failed after %zu/%zu bytes", total, payload_len);
     166            4 :             return -1;
     167              :         }
     168          848 :         total += (size_t)r;
     169              :     }
     170              : 
     171          835 :     *out_len = payload_len;
     172          835 :     return 0;
     173              : }
     174              : 
     175          567 : void transport_close(Transport *t) {
     176          567 :     if (t && t->fd >= 0) {
     177          560 :         sys_socket_close(t->fd);
     178          560 :         t->fd = -1;
     179          560 :         t->connected = 0;
     180              :     }
     181          567 : }
        

Generated by: LCOV version 2.0-1