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