Line data Source code
1 : /**
2 : * @file test_domain_updates.c
3 : * @brief Unit tests for domain_updates_state / domain_updates_difference.
4 : */
5 :
6 : #include "test_helpers.h"
7 : #include "domain/read/updates.h"
8 : #include "tl_serial.h"
9 : #include "tl_registry.h"
10 : #include "mock_socket.h"
11 : #include "mock_crypto.h"
12 : #include "mtproto_session.h"
13 : #include "transport.h"
14 : #include "api_call.h"
15 :
16 : #include <stdlib.h>
17 : #include <string.h>
18 :
19 7 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
20 : uint8_t *out, size_t *out_len) {
21 7 : TlWriter w; tl_writer_init(&w);
22 7 : uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
23 7 : uint8_t header[32] = {0};
24 7 : uint32_t plen32 = (uint32_t)plen;
25 7 : memcpy(header + 28, &plen32, 4);
26 7 : tl_write_raw(&w, header, 32);
27 7 : tl_write_raw(&w, payload, plen);
28 7 : size_t enc = w.len - 24;
29 7 : if (enc % 16 != 0) {
30 6 : uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
31 : }
32 7 : out[0] = (uint8_t)(w.len / 4);
33 7 : memcpy(out + 1, w.data, w.len);
34 7 : *out_len = 1 + w.len;
35 7 : tl_writer_free(&w);
36 7 : }
37 :
38 7 : static void fix_session(MtProtoSession *s) {
39 7 : mtproto_session_init(s);
40 7 : s->session_id = 0; /* match the zero session_id in fake encrypted frames */
41 7 : uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
42 7 : mtproto_session_set_salt(s, 0xAABBCCDDEEFF1122ULL);
43 7 : }
44 7 : static void fix_transport(Transport *t) {
45 7 : transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
46 7 : }
47 7 : static void fix_cfg(ApiConfig *cfg) {
48 7 : api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
49 7 : }
50 :
51 1 : static void test_updates_state_parse(void) {
52 1 : mock_socket_reset(); mock_crypto_reset();
53 :
54 1 : TlWriter w; tl_writer_init(&w);
55 1 : tl_write_uint32(&w, TL_updates_state);
56 1 : tl_write_int32(&w, 100); /* pts */
57 1 : tl_write_int32(&w, 0); /* qts */
58 1 : tl_write_int32(&w, 1700000000); /* date */
59 1 : tl_write_int32(&w, 7); /* seq */
60 1 : tl_write_int32(&w, 3); /* unread_count */
61 1 : uint8_t payload[64]; memcpy(payload, w.data, w.len);
62 1 : size_t plen = w.len; tl_writer_free(&w);
63 :
64 1 : uint8_t resp[512]; size_t rlen = 0;
65 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
66 1 : mock_socket_set_response(resp, rlen);
67 :
68 : MtProtoSession s; Transport t; ApiConfig cfg;
69 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
70 :
71 1 : UpdatesState st = {0};
72 1 : int rc = domain_updates_state(&cfg, &s, &t, &st);
73 1 : ASSERT(rc == 0, "updates.state parsed");
74 1 : ASSERT(st.pts == 100, "pts");
75 1 : ASSERT(st.seq == 7, "seq");
76 1 : ASSERT(st.unread_count == 3, "unread_count");
77 : }
78 :
79 1 : static void test_updates_difference_empty(void) {
80 1 : mock_socket_reset(); mock_crypto_reset();
81 :
82 1 : TlWriter w; tl_writer_init(&w);
83 1 : tl_write_uint32(&w, TL_updates_differenceEmpty);
84 1 : tl_write_int32(&w, 1700000100); /* date */
85 1 : tl_write_int32(&w, 8); /* seq */
86 1 : uint8_t payload[32]; memcpy(payload, w.data, w.len);
87 1 : size_t plen = w.len; tl_writer_free(&w);
88 :
89 1 : uint8_t resp[128]; size_t rlen = 0;
90 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
91 1 : mock_socket_set_response(resp, rlen);
92 :
93 : MtProtoSession s; Transport t; ApiConfig cfg;
94 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
95 :
96 1 : UpdatesState in = { .pts=100, .qts=0, .date=1700000000, .seq=7 };
97 1 : UpdatesDifference diff = {0};
98 1 : int rc = domain_updates_difference(&cfg, &s, &t, &in, &diff);
99 1 : ASSERT(rc == 0, "differenceEmpty parsed");
100 1 : ASSERT(diff.is_empty == 1, "is_empty flag");
101 1 : ASSERT(diff.next_state.date == 1700000100, "new date propagated");
102 : }
103 :
104 1 : static void test_updates_rpc_error(void) {
105 1 : mock_socket_reset(); mock_crypto_reset();
106 :
107 1 : TlWriter w; tl_writer_init(&w);
108 1 : tl_write_uint32(&w, TL_rpc_error);
109 1 : tl_write_int32(&w, 401);
110 1 : tl_write_string(&w, "AUTH_KEY_UNREGISTERED");
111 1 : uint8_t payload[64]; memcpy(payload, w.data, w.len);
112 1 : size_t plen = w.len; tl_writer_free(&w);
113 :
114 1 : uint8_t resp[256]; size_t rlen = 0;
115 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
116 1 : mock_socket_set_response(resp, rlen);
117 :
118 : MtProtoSession s; Transport t; ApiConfig cfg;
119 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
120 :
121 1 : UpdatesState st = {0};
122 1 : int rc = domain_updates_state(&cfg, &s, &t, &st);
123 1 : ASSERT(rc != 0, "RPC error propagates");
124 : }
125 :
126 1 : static void test_updates_difference_messages(void) {
127 1 : mock_socket_reset(); mock_crypto_reset();
128 :
129 : /* updates.difference with 2 simple new messages, then empty
130 : * Vectors for encrypted/other/chats/users, then a state tail. */
131 1 : TlWriter w; tl_writer_init(&w);
132 1 : tl_write_uint32(&w, TL_updates_difference);
133 :
134 : /* new_messages */
135 1 : tl_write_uint32(&w, TL_vector);
136 1 : tl_write_uint32(&w, 2);
137 : /* msg 1 */
138 1 : tl_write_uint32(&w, TL_message);
139 1 : tl_write_uint32(&w, 0); tl_write_uint32(&w, 0);
140 1 : tl_write_int32 (&w, 501);
141 1 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 10LL);
142 1 : tl_write_int32 (&w, 1700000000);
143 1 : tl_write_string(&w, "alpha");
144 : /* msg 2 */
145 1 : tl_write_uint32(&w, TL_message);
146 1 : tl_write_uint32(&w, 0); tl_write_uint32(&w, 0);
147 1 : tl_write_int32 (&w, 502);
148 1 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 10LL);
149 1 : tl_write_int32 (&w, 1700000001);
150 1 : tl_write_string(&w, "beta");
151 :
152 1 : uint8_t payload[1024]; memcpy(payload, w.data, w.len);
153 1 : size_t plen = w.len; tl_writer_free(&w);
154 :
155 1 : uint8_t resp[2048]; size_t rlen = 0;
156 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
157 1 : mock_socket_set_response(resp, rlen);
158 :
159 : MtProtoSession s; Transport t; ApiConfig cfg;
160 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
161 :
162 1 : UpdatesState in = { .pts=10, .qts=0, .date=1700000000, .seq=1 };
163 1 : UpdatesDifference diff = {0};
164 1 : int rc = domain_updates_difference(&cfg, &s, &t, &in, &diff);
165 1 : ASSERT(rc == 0, "difference with messages parsed");
166 1 : ASSERT(diff.new_messages_count == 2, "both messages captured");
167 1 : ASSERT(diff.new_messages[0].id == 501, "msg0 id");
168 1 : ASSERT(strcmp(diff.new_messages[0].text, "alpha") == 0, "msg0 text");
169 1 : ASSERT(diff.new_messages[1].id == 502, "msg1 id");
170 1 : ASSERT(strcmp(diff.new_messages[1].text, "beta") == 0, "msg1 text");
171 : }
172 :
173 1 : static void test_updates_null_args(void) {
174 1 : ASSERT(domain_updates_state(NULL, NULL, NULL, NULL) == -1, "null rejected");
175 1 : ASSERT(domain_updates_difference(NULL, NULL, NULL, NULL, NULL) == -1,
176 : "null diff rejected");
177 : }
178 :
179 : /* ---- FEAT-14: peer_id is extracted from Message.peer_id ---- */
180 1 : static void test_updates_difference_peer_id(void) {
181 1 : mock_socket_reset(); mock_crypto_reset();
182 :
183 : /* Two messages: one from peer 111, one from peer 222. */
184 1 : TlWriter w; tl_writer_init(&w);
185 1 : tl_write_uint32(&w, TL_updates_difference);
186 :
187 : /* new_messages */
188 1 : tl_write_uint32(&w, TL_vector);
189 1 : tl_write_uint32(&w, 2);
190 : /* msg 1: peer_id = peerUser 111 */
191 1 : tl_write_uint32(&w, TL_message);
192 1 : tl_write_uint32(&w, 0); tl_write_uint32(&w, 0); /* flags, flags2 */
193 1 : tl_write_int32 (&w, 601); /* id */
194 1 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 111LL); /* peer_id */
195 1 : tl_write_int32 (&w, 1700000010); /* date */
196 1 : tl_write_string(&w, "from 111");
197 :
198 : /* msg 2: peer_id = peerUser 222 */
199 1 : tl_write_uint32(&w, TL_message);
200 1 : tl_write_uint32(&w, 0); tl_write_uint32(&w, 0);
201 1 : tl_write_int32 (&w, 602);
202 1 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 222LL);
203 1 : tl_write_int32 (&w, 1700000011);
204 1 : tl_write_string(&w, "from 222");
205 :
206 1 : uint8_t payload[1024]; memcpy(payload, w.data, w.len);
207 1 : size_t plen = w.len; tl_writer_free(&w);
208 :
209 1 : uint8_t resp[2048]; size_t rlen = 0;
210 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
211 1 : mock_socket_set_response(resp, rlen);
212 :
213 : MtProtoSession s; Transport t; ApiConfig cfg;
214 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
215 :
216 1 : UpdatesState in = { .pts=10, .qts=0, .date=1700000000, .seq=1 };
217 1 : UpdatesDifference diff = {0};
218 1 : int rc = domain_updates_difference(&cfg, &s, &t, &in, &diff);
219 1 : ASSERT(rc == 0, "peer_id: difference parsed ok");
220 1 : ASSERT(diff.new_messages_count == 2, "peer_id: two messages");
221 1 : ASSERT(diff.new_messages[0].peer_id == 111LL, "peer_id: msg0 peer_id==111");
222 1 : ASSERT(diff.new_messages[1].peer_id == 222LL, "peer_id: msg1 peer_id==222");
223 :
224 : /* Simulate the filter: only allow peer 111. */
225 1 : int matched = 0;
226 3 : for (int i = 0; i < diff.new_messages_count; i++) {
227 2 : if (diff.new_messages[i].peer_id == 111LL) {
228 1 : ASSERT(strcmp(diff.new_messages[i].text, "from 111") == 0,
229 : "peer_id: msg from 111 has correct text");
230 1 : matched++;
231 : }
232 : }
233 1 : ASSERT(matched == 1, "peer_id: exactly one message from peer 111");
234 : }
235 :
236 : /* ---- FEAT-24: int64_t date — 2038-safety tests ---- */
237 :
238 : /* A date value of 2^30 (1073741824) fits in int32 but is close to the 2038
239 : * boundary; verify it round-trips through HistoryEntry.date without truncation. */
240 1 : static void test_date_large_value_roundtrip(void) {
241 1 : mock_socket_reset(); mock_crypto_reset();
242 :
243 1 : int32_t wire_date = (int32_t)(1 << 30); /* 1073741824 — fits in int32 */
244 :
245 1 : TlWriter w; tl_writer_init(&w);
246 1 : tl_write_uint32(&w, TL_updates_state);
247 1 : tl_write_int32(&w, 1); /* pts */
248 1 : tl_write_int32(&w, 0); /* qts */
249 1 : tl_write_int32(&w, wire_date); /* date = 2^30 */
250 1 : tl_write_int32(&w, 1); /* seq */
251 1 : tl_write_int32(&w, 0); /* unread_count */
252 1 : uint8_t payload[64]; memcpy(payload, w.data, w.len);
253 1 : size_t plen = w.len; tl_writer_free(&w);
254 :
255 1 : uint8_t resp[256]; size_t rlen = 0;
256 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
257 1 : mock_socket_set_response(resp, rlen);
258 :
259 : MtProtoSession s; Transport t; ApiConfig cfg;
260 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
261 :
262 1 : UpdatesState st = {0};
263 1 : int rc = domain_updates_state(&cfg, &s, &t, &st);
264 1 : ASSERT(rc == 0, "FEAT-24: large date parse ok");
265 1 : ASSERT(st.date == (int64_t)(1 << 30),
266 : "FEAT-24: large date (2^30) preserved as int64");
267 : }
268 :
269 : /* A negative int32 date sentinel (e.g. -1) must sign-extend correctly to
270 : * int64_t -1, not become a large positive value. */
271 1 : static void test_date_negative_sentinel_preserved(void) {
272 1 : mock_socket_reset(); mock_crypto_reset();
273 :
274 1 : TlWriter w; tl_writer_init(&w);
275 1 : tl_write_uint32(&w, TL_updates_state);
276 1 : tl_write_int32(&w, 0); /* pts */
277 1 : tl_write_int32(&w, 0); /* qts */
278 1 : tl_write_int32(&w, -1); /* date = -1 sentinel */
279 1 : tl_write_int32(&w, 0); /* seq */
280 1 : tl_write_int32(&w, 0); /* unread_count */
281 1 : uint8_t payload[64]; memcpy(payload, w.data, w.len);
282 1 : size_t plen = w.len; tl_writer_free(&w);
283 :
284 1 : uint8_t resp[256]; size_t rlen = 0;
285 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
286 1 : mock_socket_set_response(resp, rlen);
287 :
288 : MtProtoSession s; Transport t; ApiConfig cfg;
289 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
290 :
291 1 : UpdatesState st = {0};
292 1 : int rc = domain_updates_state(&cfg, &s, &t, &st);
293 1 : ASSERT(rc == 0, "FEAT-24: negative date parse ok");
294 1 : ASSERT(st.date == (int64_t)-1LL,
295 : "FEAT-24: negative sentinel (-1) sign-extended correctly to int64");
296 : }
297 :
298 1 : void run_domain_updates_tests(void) {
299 1 : RUN_TEST(test_updates_state_parse);
300 1 : RUN_TEST(test_updates_difference_empty);
301 1 : RUN_TEST(test_updates_rpc_error);
302 1 : RUN_TEST(test_updates_difference_messages);
303 1 : RUN_TEST(test_updates_null_args);
304 1 : RUN_TEST(test_updates_difference_peer_id);
305 1 : RUN_TEST(test_date_large_value_roundtrip);
306 1 : RUN_TEST(test_date_negative_sentinel_preserved);
307 1 : }
|