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 694 : void transport_init(Transport *t) {
20 694 : if (!t) return;
21 694 : memset(t, 0, sizeof(*t));
22 694 : t->fd = -1;
23 : }
24 :
25 571 : int transport_connect(Transport *t, const char *host, int port) {
26 571 : if (!t || !host) return -1;
27 :
28 569 : t->fd = sys_socket_create();
29 569 : if (t->fd < 0) {
30 3 : logger_log(LOG_ERROR, "transport: failed to create socket");
31 3 : return -1;
32 : }
33 :
34 566 : 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 558 : uint8_t marker = ABRIDGED_MARKER;
43 : ssize_t msent;
44 : do {
45 558 : msent = sys_socket_send(t->fd, &marker, 1);
46 558 : } while (msent < 0 && (errno == EINTR || errno == EAGAIN));
47 558 : 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 555 : t->connected = 1;
55 555 : return 0;
56 : }
57 :
58 827 : int transport_send(Transport *t, const uint8_t *data, size_t len) {
59 827 : 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 823 : 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 818 : size_t wire_len = len / 4;
71 :
72 818 : if (wire_len < 0x7F) {
73 725 : uint8_t prefix = (uint8_t)wire_len;
74 : ssize_t n;
75 : do {
76 730 : n = sys_socket_send(t->fd, &prefix, 1);
77 730 : } while (n < 0 && (errno == EINTR || errno == EAGAIN));
78 725 : 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 812 : size_t total = 0;
102 1631 : while (total < len) {
103 : ssize_t sent;
104 : do {
105 823 : sent = sys_socket_send(t->fd, data + total, len - total);
106 823 : } while (sent < 0 && (errno == EINTR || errno == EAGAIN));
107 822 : if (sent <= 0) {
108 3 : logger_log(LOG_ERROR, "transport: send failed after %zu/%zu bytes", total, len);
109 3 : return -1;
110 : }
111 819 : total += (size_t)sent;
112 : }
113 809 : return 0;
114 : }
115 :
116 966 : int transport_recv(Transport *t, uint8_t *out, size_t max_len, size_t *out_len) {
117 966 : 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 967 : r = sys_socket_recv(t->fd, &first, 1);
124 967 : } while (r < 0 && (errno == EINTR || errno == EAGAIN));
125 962 : 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 953 : if (first < 0x7F) {
132 895 : 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 950 : size_t payload_len = wire_len * 4;
149 950 : if (payload_len == 0) {
150 3 : *out_len = 0;
151 3 : return 0;
152 : }
153 947 : 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 943 : size_t total = 0;
160 1895 : while (total < payload_len) {
161 : do {
162 957 : r = sys_socket_recv(t->fd, out + total, payload_len - total);
163 957 : } while (r < 0 && (errno == EINTR || errno == EAGAIN));
164 956 : 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 952 : total += (size_t)r;
169 : }
170 :
171 939 : *out_len = payload_len;
172 :
173 : /* Telegram encodes transport-level errors as a 4-byte negative int32. */
174 939 : if (payload_len == 4) {
175 : int32_t code;
176 1 : memcpy(&code, out, 4);
177 1 : if (code < 0) {
178 0 : logger_log(LOG_ERROR,
179 : "transport: server returned error code %d "
180 : "(common codes: -404 unknown method, -429 flood, "
181 : "-444 invalid DC)",
182 : (int)code);
183 : }
184 : }
185 939 : return 0;
186 : }
187 :
188 562 : void transport_close(Transport *t) {
189 562 : if (t && t->fd >= 0) {
190 555 : sys_socket_close(t->fd);
191 555 : t->fd = -1;
192 555 : t->connected = 0;
193 : }
194 562 : }
|