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