Line data Source code
1 : /**
2 : * @file test_domain_history.c
3 : * @brief Unit tests for domain_get_history_self (US-06 v1).
4 : */
5 :
6 : #include "test_helpers.h"
7 : #include "domain/read/history.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 14 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
20 : uint8_t *out, size_t *out_len) {
21 14 : TlWriter w; tl_writer_init(&w);
22 14 : uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
23 14 : uint8_t header[32] = {0};
24 14 : uint32_t plen32 = (uint32_t)plen;
25 14 : memcpy(header + 28, &plen32, 4);
26 14 : tl_write_raw(&w, header, 32);
27 14 : tl_write_raw(&w, payload, plen);
28 14 : size_t enc = w.len - 24;
29 14 : if (enc % 16 != 0) {
30 12 : uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
31 : }
32 14 : out[0] = (uint8_t)(w.len / 4);
33 14 : memcpy(out + 1, w.data, w.len);
34 14 : *out_len = 1 + w.len;
35 14 : tl_writer_free(&w);
36 14 : }
37 :
38 15 : static void fix_session(MtProtoSession *s) {
39 15 : mtproto_session_init(s);
40 15 : s->session_id = 0; /* match the zero session_id in fake encrypted frames */
41 15 : uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
42 15 : mtproto_session_set_salt(s, 0xBADCAFEDEADBEEFULL);
43 15 : }
44 15 : static void fix_transport(Transport *t) {
45 15 : transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
46 15 : }
47 15 : static void fix_cfg(ApiConfig *cfg) {
48 15 : api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
49 15 : }
50 :
51 : /* Build messages.messages containing one messageEmpty entry — enough to
52 : * exercise the parse prefix without wrestling with flag-conditional
53 : * Message fields. */
54 1 : static size_t make_one_empty_message(uint8_t *buf, size_t max, int32_t id) {
55 1 : TlWriter w; tl_writer_init(&w);
56 1 : tl_write_uint32(&w, TL_messages_messages);
57 1 : tl_write_uint32(&w, TL_vector);
58 1 : tl_write_uint32(&w, 1); /* vector count */
59 1 : tl_write_uint32(&w, TL_messageEmpty);
60 1 : tl_write_uint32(&w, 0); /* flags = 0 */
61 1 : tl_write_int32 (&w, id);
62 :
63 1 : size_t n = w.len < max ? w.len : max;
64 1 : memcpy(buf, w.data, n);
65 1 : tl_writer_free(&w);
66 1 : return n;
67 : }
68 :
69 1 : static void test_history_one_empty(void) {
70 1 : mock_socket_reset(); mock_crypto_reset();
71 :
72 : uint8_t payload[256];
73 1 : size_t plen = make_one_empty_message(payload, sizeof(payload), 1234);
74 :
75 1 : uint8_t resp[1024]; size_t rlen = 0;
76 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
77 1 : mock_socket_set_response(resp, rlen);
78 :
79 : MtProtoSession s; Transport t; ApiConfig cfg;
80 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
81 :
82 1 : HistoryEntry entries[5] = {0}; int n = 0;
83 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, entries, &n);
84 1 : ASSERT(rc == 0, "history: must succeed");
85 1 : ASSERT(n == 1, "one entry parsed");
86 1 : ASSERT(entries[0].id == 1234, "id matches");
87 : }
88 :
89 1 : static void test_history_rpc_error(void) {
90 1 : mock_socket_reset(); mock_crypto_reset();
91 : uint8_t payload[128];
92 1 : TlWriter w; tl_writer_init(&w);
93 1 : tl_write_uint32(&w, TL_rpc_error);
94 1 : tl_write_int32(&w, 400);
95 1 : tl_write_string(&w, "PEER_ID_INVALID");
96 1 : memcpy(payload, w.data, w.len);
97 1 : size_t plen = w.len;
98 1 : tl_writer_free(&w);
99 :
100 1 : uint8_t resp[512]; size_t rlen = 0;
101 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
102 1 : mock_socket_set_response(resp, rlen);
103 :
104 : MtProtoSession s; Transport t; ApiConfig cfg;
105 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
106 :
107 1 : HistoryEntry e[3] = {0}; int n = 0;
108 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
109 1 : ASSERT(rc != 0, "RPC error must propagate");
110 : }
111 :
112 : /* Build a messages.channelMessages response with one messageEmpty. */
113 1 : static size_t make_channel_messages(uint8_t *buf, size_t max, int32_t id) {
114 1 : TlWriter w; tl_writer_init(&w);
115 1 : tl_write_uint32(&w, TL_messages_channelMessages);
116 1 : tl_write_uint32(&w, 0); /* flags */
117 1 : tl_write_int32 (&w, 100); /* pts */
118 1 : tl_write_int32 (&w, 1); /* count */
119 1 : tl_write_uint32(&w, TL_vector);
120 1 : tl_write_uint32(&w, 1);
121 1 : tl_write_uint32(&w, TL_messageEmpty);
122 1 : tl_write_uint32(&w, 0); /* flags */
123 1 : tl_write_int32 (&w, id);
124 1 : size_t n = w.len < max ? w.len : max;
125 1 : memcpy(buf, w.data, n);
126 1 : tl_writer_free(&w);
127 1 : return n;
128 : }
129 :
130 1 : static void test_history_channel_peer(void) {
131 1 : mock_socket_reset(); mock_crypto_reset();
132 :
133 : uint8_t payload[256];
134 1 : size_t plen = make_channel_messages(payload, sizeof(payload), 9999);
135 :
136 1 : uint8_t resp[1024]; size_t rlen = 0;
137 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
138 1 : mock_socket_set_response(resp, rlen);
139 :
140 : MtProtoSession s; Transport t; ApiConfig cfg;
141 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
142 :
143 1 : HistoryPeer peer = {
144 : .kind = HISTORY_PEER_CHANNEL,
145 : .peer_id = 1001234567890LL,
146 : .access_hash = 0xABCDEF1234567890LL,
147 : };
148 1 : HistoryEntry entries[3] = {0}; int n = 0;
149 1 : int rc = domain_get_history(&cfg, &s, &t, &peer, 0, 3, entries, &n);
150 1 : ASSERT(rc == 0, "channel history parsed");
151 1 : ASSERT(n == 1, "one entry");
152 1 : ASSERT(entries[0].id == 9999, "id matches");
153 : }
154 :
155 : /* Build a simple Message (no complex flags) with text = "hello" and
156 : * date=1700000000. Flags include out (bit 1) only. */
157 1 : static size_t make_simple_text_message(uint8_t *buf, size_t max,
158 : int32_t id, const char *text) {
159 1 : TlWriter w; tl_writer_init(&w);
160 1 : tl_write_uint32(&w, TL_messages_messages);
161 1 : tl_write_uint32(&w, TL_vector);
162 1 : tl_write_uint32(&w, 1);
163 1 : tl_write_uint32(&w, TL_message);
164 1 : tl_write_uint32(&w, (1u << 1)); /* flags: out */
165 1 : tl_write_uint32(&w, 0); /* flags2 */
166 1 : tl_write_int32 (&w, id);
167 : /* No from_id (flags.8 not set). */
168 1 : tl_write_uint32(&w, TL_peerUser); /* peer_id */
169 1 : tl_write_int64 (&w, 123LL);
170 1 : tl_write_int32 (&w, 1700000000); /* date */
171 1 : tl_write_string(&w, text);
172 1 : size_t n = w.len < max ? w.len : max;
173 1 : memcpy(buf, w.data, n);
174 1 : tl_writer_free(&w);
175 1 : return n;
176 : }
177 :
178 1 : static void test_history_text_extraction(void) {
179 1 : mock_socket_reset(); mock_crypto_reset();
180 :
181 : uint8_t payload[512];
182 1 : size_t plen = make_simple_text_message(payload, sizeof(payload),
183 : 55, "hello world");
184 1 : uint8_t resp[1024]; size_t rlen = 0;
185 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
186 1 : mock_socket_set_response(resp, rlen);
187 :
188 : MtProtoSession s; Transport t; ApiConfig cfg;
189 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
190 :
191 1 : HistoryEntry e[3] = {0}; int n = 0;
192 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
193 1 : ASSERT(rc == 0, "simple message parsed");
194 1 : ASSERT(n == 1, "one entry");
195 1 : ASSERT(e[0].id == 55, "id matches");
196 1 : ASSERT(e[0].out == 1, "out flag set");
197 1 : ASSERT(e[0].date == 1700000000, "date extracted");
198 1 : ASSERT(strcmp(e[0].text, "hello world") == 0, "text extracted");
199 1 : ASSERT(e[0].complex == 0, "not complex");
200 : }
201 :
202 1 : static void test_history_complex_flag(void) {
203 1 : mock_socket_reset(); mock_crypto_reset();
204 :
205 : /* Message with fwd_from flag set — we should mark complex and not
206 : * try to parse further. */
207 1 : TlWriter w; tl_writer_init(&w);
208 1 : tl_write_uint32(&w, TL_messages_messages);
209 1 : tl_write_uint32(&w, TL_vector);
210 1 : tl_write_uint32(&w, 1);
211 1 : tl_write_uint32(&w, TL_message);
212 1 : tl_write_uint32(&w, (1u << 2)); /* flags: fwd_from */
213 1 : tl_write_uint32(&w, 0);
214 1 : tl_write_int32 (&w, 77);
215 : /* No further bytes needed — parser bails on complex mask */
216 1 : uint8_t payload[128]; memcpy(payload, w.data, w.len);
217 1 : size_t plen = w.len; tl_writer_free(&w);
218 :
219 1 : uint8_t resp[512]; size_t rlen = 0;
220 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
221 1 : mock_socket_set_response(resp, rlen);
222 :
223 : MtProtoSession s; Transport t; ApiConfig cfg;
224 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
225 :
226 1 : HistoryEntry e[3] = {0}; int n = 0;
227 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
228 1 : ASSERT(rc == 0, "complex message parsed (without text)");
229 1 : ASSERT(n == 1, "one entry");
230 1 : ASSERT(e[0].id == 77, "id still captured");
231 1 : ASSERT(e[0].complex == 1, "complex flag set");
232 1 : ASSERT(e[0].text[0] == '\0', "text empty for complex");
233 : }
234 :
235 : /* Write a simple text Message body only (no top-level wrapper). */
236 4 : static void write_simple_message(TlWriter *w, int32_t id, const char *text) {
237 4 : tl_write_uint32(w, TL_message);
238 4 : tl_write_uint32(w, 0); /* flags: no out, no from_id */
239 4 : tl_write_uint32(w, 0); /* flags2 */
240 4 : tl_write_int32 (w, id);
241 4 : tl_write_uint32(w, TL_peerUser); /* peer_id */
242 4 : tl_write_int64 (w, 100LL);
243 4 : tl_write_int32 (w, 1700000000); /* date */
244 4 : tl_write_string(w, text);
245 4 : }
246 :
247 1 : static void test_history_iterates_multiple(void) {
248 1 : mock_socket_reset(); mock_crypto_reset();
249 :
250 : /* messages.messages + Vector<Message> with 3 simple messages. */
251 1 : TlWriter w; tl_writer_init(&w);
252 1 : tl_write_uint32(&w, TL_messages_messages);
253 1 : tl_write_uint32(&w, TL_vector);
254 1 : tl_write_uint32(&w, 3);
255 1 : write_simple_message(&w, 1, "first");
256 1 : write_simple_message(&w, 2, "second");
257 1 : write_simple_message(&w, 3, "third");
258 1 : uint8_t payload[1024]; memcpy(payload, w.data, w.len);
259 1 : size_t plen = w.len; tl_writer_free(&w);
260 :
261 1 : uint8_t resp[2048]; size_t rlen = 0;
262 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
263 1 : mock_socket_set_response(resp, rlen);
264 :
265 : MtProtoSession s; Transport t; ApiConfig cfg;
266 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
267 :
268 1 : HistoryEntry entries[10] = {0}; int n = 0;
269 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 10, entries, &n);
270 1 : ASSERT(rc == 0, "iteration ok");
271 1 : ASSERT(n == 3, "all 3 messages parsed");
272 1 : ASSERT(entries[0].id == 1, "id0");
273 1 : ASSERT(strcmp(entries[0].text, "first") == 0, "text0");
274 1 : ASSERT(entries[1].id == 2, "id1");
275 1 : ASSERT(strcmp(entries[1].text, "second") == 0, "text1");
276 1 : ASSERT(entries[2].id == 3, "id2");
277 1 : ASSERT(strcmp(entries[2].text, "third") == 0, "text2");
278 : }
279 :
280 1 : static void test_history_iterates_with_entities(void) {
281 1 : mock_socket_reset(); mock_crypto_reset();
282 :
283 1 : TlWriter w; tl_writer_init(&w);
284 1 : tl_write_uint32(&w, TL_messages_messages);
285 1 : tl_write_uint32(&w, TL_vector);
286 1 : tl_write_uint32(&w, 2);
287 :
288 : /* Msg 1: with entities (flags.7). */
289 1 : tl_write_uint32(&w, TL_message);
290 1 : tl_write_uint32(&w, (1u << 7)); /* flags: entities present */
291 1 : tl_write_uint32(&w, 0); /* flags2 */
292 1 : tl_write_int32 (&w, 11);
293 1 : tl_write_uint32(&w, TL_peerUser);
294 1 : tl_write_int64 (&w, 100LL);
295 1 : tl_write_int32 (&w, 1700000000);
296 1 : tl_write_string(&w, "bold message");
297 : /* entities vector: 1 bold */
298 1 : tl_write_uint32(&w, TL_vector);
299 1 : tl_write_uint32(&w, 1);
300 1 : tl_write_uint32(&w, 0xbd610bc9u); /* messageEntityBold */
301 1 : tl_write_int32 (&w, 0); /* offset */
302 1 : tl_write_int32 (&w, 4); /* length */
303 :
304 : /* Msg 2: plain. */
305 1 : write_simple_message(&w, 12, "plain");
306 :
307 1 : uint8_t payload[1024]; memcpy(payload, w.data, w.len);
308 1 : size_t plen = w.len; tl_writer_free(&w);
309 :
310 1 : uint8_t resp[2048]; size_t rlen = 0;
311 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
312 1 : mock_socket_set_response(resp, rlen);
313 :
314 : MtProtoSession s; Transport t; ApiConfig cfg;
315 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
316 :
317 1 : HistoryEntry entries[5] = {0}; int n = 0;
318 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, entries, &n);
319 1 : ASSERT(rc == 0, "entities iter ok");
320 1 : ASSERT(n == 2, "both messages parsed despite entities");
321 1 : ASSERT(strcmp(entries[0].text, "bold message") == 0, "text0");
322 1 : ASSERT(strcmp(entries[1].text, "plain") == 0, "text1");
323 : }
324 :
325 : /* With the MessageMedia skipper, media-bearing messages now iterate. */
326 : /* Message with messageMediaPhoto carrying a photoEmpty — parser should
327 : * populate media=MEDIA_PHOTO, media_id=photo_id. */
328 1 : static void test_history_media_photo_info(void) {
329 1 : mock_socket_reset(); mock_crypto_reset();
330 :
331 1 : TlWriter w; tl_writer_init(&w);
332 1 : tl_write_uint32(&w, TL_messages_messages);
333 1 : tl_write_uint32(&w, TL_vector);
334 1 : tl_write_uint32(&w, 1);
335 :
336 1 : tl_write_uint32(&w, TL_message);
337 1 : tl_write_uint32(&w, (1u << 9));
338 1 : tl_write_uint32(&w, 0);
339 1 : tl_write_int32 (&w, 77);
340 1 : tl_write_uint32(&w, TL_peerUser);
341 1 : tl_write_int64 (&w, 100LL);
342 1 : tl_write_int32 (&w, 1700000000);
343 1 : tl_write_string(&w, "check this");
344 : /* messageMediaPhoto with flags=0x01 (photo present) → photoEmpty#2331b22d id:long */
345 1 : tl_write_uint32(&w, 0x695150d7u); /* messageMediaPhoto */
346 1 : tl_write_uint32(&w, (1u << 0));
347 1 : tl_write_uint32(&w, 0x2331b22du); /* photoEmpty */
348 1 : tl_write_int64 (&w, 99999999LL);
349 :
350 1 : uint8_t payload[512]; memcpy(payload, w.data, w.len);
351 1 : size_t plen = w.len; tl_writer_free(&w);
352 :
353 1 : uint8_t resp[1024]; size_t rlen = 0;
354 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
355 1 : mock_socket_set_response(resp, rlen);
356 :
357 : MtProtoSession s; Transport t; ApiConfig cfg;
358 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
359 :
360 1 : HistoryEntry e[3] = {0}; int n = 0;
361 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
362 1 : ASSERT(rc == 0, "media photo info parsed");
363 1 : ASSERT(n == 1, "one entry");
364 1 : ASSERT(e[0].media == MEDIA_PHOTO, "kind=photo");
365 1 : ASSERT(e[0].media_id == 99999999LL, "photo_id captured");
366 1 : ASSERT(strcmp(e[0].text, "check this") == 0, "text preserved");
367 : }
368 :
369 1 : static void test_history_iterates_with_media_geo(void) {
370 1 : mock_socket_reset(); mock_crypto_reset();
371 :
372 1 : TlWriter w; tl_writer_init(&w);
373 1 : tl_write_uint32(&w, TL_messages_messages);
374 1 : tl_write_uint32(&w, TL_vector);
375 1 : tl_write_uint32(&w, 2);
376 :
377 : /* Msg 1: flags.9 set + messageMediaGeo (empty geoPoint). */
378 1 : tl_write_uint32(&w, TL_message);
379 1 : tl_write_uint32(&w, (1u << 9));
380 1 : tl_write_uint32(&w, 0);
381 1 : tl_write_int32 (&w, 42);
382 1 : tl_write_uint32(&w, TL_peerUser);
383 1 : tl_write_int64 (&w, 100LL);
384 1 : tl_write_int32 (&w, 1700000000);
385 1 : tl_write_string(&w, "here");
386 1 : tl_write_uint32(&w, 0x56e0d474u); /* messageMediaGeo */
387 1 : tl_write_uint32(&w, 0x1117dd5fu); /* geoPointEmpty */
388 :
389 : /* Msg 2: plain */
390 1 : tl_write_uint32(&w, TL_message);
391 1 : tl_write_uint32(&w, 0);
392 1 : tl_write_uint32(&w, 0);
393 1 : tl_write_int32 (&w, 43);
394 1 : tl_write_uint32(&w, TL_peerUser);
395 1 : tl_write_int64 (&w, 100LL);
396 1 : tl_write_int32 (&w, 1700000001);
397 1 : tl_write_string(&w, "there");
398 :
399 1 : uint8_t payload[1024]; memcpy(payload, w.data, w.len);
400 1 : size_t plen = w.len; tl_writer_free(&w);
401 :
402 1 : uint8_t resp[2048]; size_t rlen = 0;
403 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
404 1 : mock_socket_set_response(resp, rlen);
405 :
406 : MtProtoSession s; Transport t; ApiConfig cfg;
407 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
408 :
409 1 : HistoryEntry e[5] = {0}; int n = 0;
410 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
411 1 : ASSERT(rc == 0, "iter with geo media");
412 1 : ASSERT(n == 2, "both messages iterate past media");
413 1 : ASSERT(e[0].id == 42 && strcmp(e[0].text, "here") == 0, "msg0");
414 1 : ASSERT(e[1].id == 43 && strcmp(e[1].text, "there") == 0, "msg1");
415 1 : ASSERT(e[0].complex == 0, "msg0 NOT complex after media skip");
416 : }
417 :
418 1 : static void test_history_stops_on_reply_markup(void) {
419 1 : mock_socket_reset(); mock_crypto_reset();
420 :
421 1 : TlWriter w; tl_writer_init(&w);
422 1 : tl_write_uint32(&w, TL_messages_messages);
423 1 : tl_write_uint32(&w, TL_vector);
424 1 : tl_write_uint32(&w, 3);
425 :
426 : /* Msg 1: flags.6 (reply_markup) set — still no skipper, must bail. */
427 1 : tl_write_uint32(&w, TL_message);
428 1 : tl_write_uint32(&w, (1u << 6));
429 1 : tl_write_uint32(&w, 0);
430 1 : tl_write_int32 (&w, 21);
431 1 : tl_write_uint32(&w, TL_peerUser);
432 1 : tl_write_int64 (&w, 100LL);
433 1 : tl_write_int32 (&w, 1700000000);
434 1 : tl_write_string(&w, "has reply_markup");
435 :
436 : /* Msg 2 & 3 would be here but unreachable. */
437 :
438 1 : uint8_t payload[512]; memcpy(payload, w.data, w.len);
439 1 : size_t plen = w.len; tl_writer_free(&w);
440 :
441 1 : uint8_t resp[1024]; size_t rlen = 0;
442 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
443 1 : mock_socket_set_response(resp, rlen);
444 :
445 : MtProtoSession s; Transport t; ApiConfig cfg;
446 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
447 :
448 1 : HistoryEntry entries[5] = {0}; int n = 0;
449 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, entries, &n);
450 1 : ASSERT(rc == 0, "reply_markup entry still returned");
451 1 : ASSERT(n == 1, "only first captured before iteration stop");
452 1 : ASSERT(entries[0].complex == 1, "flagged complex");
453 1 : ASSERT(strcmp(entries[0].text, "has reply_markup") == 0, "text before bail");
454 : }
455 :
456 : /* After phase 3c, a well-formed reply_markup (inline keyboard) no longer
457 : * halts iteration — we can parse the message *and* continue to the
458 : * next one in the same response. */
459 1 : static void test_history_iterates_with_reply_markup(void) {
460 1 : mock_socket_reset(); mock_crypto_reset();
461 :
462 1 : TlWriter w; tl_writer_init(&w);
463 1 : tl_write_uint32(&w, TL_messages_messages);
464 1 : tl_write_uint32(&w, TL_vector);
465 1 : tl_write_uint32(&w, 2);
466 :
467 : /* Msg 1: flags.6 set + replyInlineMarkup with one URL button. */
468 1 : tl_write_uint32(&w, TL_message);
469 1 : tl_write_uint32(&w, (1u << 6));
470 1 : tl_write_uint32(&w, 0);
471 1 : tl_write_int32 (&w, 210);
472 1 : tl_write_uint32(&w, TL_peerUser);
473 1 : tl_write_int64 (&w, 100LL);
474 1 : tl_write_int32 (&w, 1700000000);
475 1 : tl_write_string(&w, "check this bot");
476 : /* reply_markup */
477 1 : tl_write_uint32(&w, 0x48a30254u); /* replyInlineMarkup */
478 1 : tl_write_uint32(&w, TL_vector);
479 1 : tl_write_uint32(&w, 1);
480 1 : tl_write_uint32(&w, 0x77608b83u); /* keyboardButtonRow */
481 1 : tl_write_uint32(&w, TL_vector);
482 1 : tl_write_uint32(&w, 1);
483 1 : tl_write_uint32(&w, 0x258aff05u); /* keyboardButtonUrl */
484 1 : tl_write_string(&w, "Docs");
485 1 : tl_write_string(&w, "https://example.com/docs");
486 :
487 : /* Msg 2: plain — must be reachable. */
488 1 : tl_write_uint32(&w, TL_message);
489 1 : tl_write_uint32(&w, 0);
490 1 : tl_write_uint32(&w, 0);
491 1 : tl_write_int32 (&w, 211);
492 1 : tl_write_uint32(&w, TL_peerUser);
493 1 : tl_write_int64 (&w, 100LL);
494 1 : tl_write_int32 (&w, 1700000001);
495 1 : tl_write_string(&w, "after the keyboard");
496 :
497 1 : uint8_t payload[512]; memcpy(payload, w.data, w.len);
498 1 : size_t plen = w.len; tl_writer_free(&w);
499 :
500 1 : uint8_t resp[1024]; size_t rlen = 0;
501 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
502 1 : mock_socket_set_response(resp, rlen);
503 :
504 : MtProtoSession s; Transport t; ApiConfig cfg;
505 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
506 :
507 1 : HistoryEntry e[5] = {0}; int n = 0;
508 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
509 1 : ASSERT(rc == 0, "history parses past reply_markup");
510 1 : ASSERT(n == 2, "both messages iterate past keyboard");
511 1 : ASSERT(e[0].id == 210 && strcmp(e[0].text, "check this bot") == 0,
512 : "msg0 text");
513 1 : ASSERT(e[0].complex == 0, "msg0 NOT complex after keyboard skip");
514 1 : ASSERT(e[1].id == 211 && strcmp(e[1].text, "after the keyboard") == 0,
515 : "msg1 text");
516 : }
517 :
518 : /* Reactions (flags.20) with a simple results vector should also no
519 : * longer halt. */
520 1 : static void test_history_iterates_with_reactions(void) {
521 1 : mock_socket_reset(); mock_crypto_reset();
522 :
523 1 : TlWriter w; tl_writer_init(&w);
524 1 : tl_write_uint32(&w, TL_messages_messages);
525 1 : tl_write_uint32(&w, TL_vector);
526 1 : tl_write_uint32(&w, 2);
527 :
528 : /* Msg 1: flags.20 = reactions. */
529 1 : tl_write_uint32(&w, TL_message);
530 1 : tl_write_uint32(&w, (1u << 20));
531 1 : tl_write_uint32(&w, 0);
532 1 : tl_write_int32 (&w, 310);
533 1 : tl_write_uint32(&w, TL_peerUser);
534 1 : tl_write_int64 (&w, 100LL);
535 1 : tl_write_int32 (&w, 1700000000);
536 1 : tl_write_string(&w, "popular message");
537 : /* messageReactions: flags=0, one emoji reaction. */
538 1 : tl_write_uint32(&w, 0x4f2b9479u);
539 1 : tl_write_uint32(&w, 0);
540 1 : tl_write_uint32(&w, TL_vector);
541 1 : tl_write_uint32(&w, 1);
542 1 : tl_write_uint32(&w, 0xa3d1cb80u); /* reactionCount */
543 1 : tl_write_uint32(&w, 0); /* flags */
544 1 : tl_write_uint32(&w, 0x1b2286b8u); /* reactionEmoji */
545 1 : tl_write_string(&w, "\xf0\x9f\x94\xa5"); /* 🔥 */
546 1 : tl_write_int32 (&w, 42);
547 :
548 : /* Msg 2: plain. */
549 1 : tl_write_uint32(&w, TL_message);
550 1 : tl_write_uint32(&w, 0);
551 1 : tl_write_uint32(&w, 0);
552 1 : tl_write_int32 (&w, 311);
553 1 : tl_write_uint32(&w, TL_peerUser);
554 1 : tl_write_int64 (&w, 100LL);
555 1 : tl_write_int32 (&w, 1700000001);
556 1 : tl_write_string(&w, "next");
557 :
558 1 : uint8_t payload[512]; memcpy(payload, w.data, w.len);
559 1 : size_t plen = w.len; tl_writer_free(&w);
560 :
561 1 : uint8_t resp[1024]; size_t rlen = 0;
562 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
563 1 : mock_socket_set_response(resp, rlen);
564 :
565 : MtProtoSession s; Transport t; ApiConfig cfg;
566 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
567 :
568 1 : HistoryEntry e[5] = {0}; int n = 0;
569 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
570 1 : ASSERT(rc == 0, "history parses past reactions");
571 1 : ASSERT(n == 2, "both messages iterate past reactions");
572 1 : ASSERT(e[0].id == 310 && strcmp(e[0].text, "popular message") == 0,
573 : "msg0 text");
574 1 : ASSERT(e[0].complex == 0, "msg0 NOT complex after reactions skip");
575 1 : ASSERT(e[1].id == 311 && strcmp(e[1].text, "next") == 0, "msg1 text");
576 : }
577 :
578 : /* Message carrying flags.23 (replies) + flags.22 (restriction_reason)
579 : * now iterates — both have skippers. */
580 1 : static void test_history_iterates_with_replies_and_restriction(void) {
581 1 : mock_socket_reset(); mock_crypto_reset();
582 :
583 1 : TlWriter w; tl_writer_init(&w);
584 1 : tl_write_uint32(&w, TL_messages_messages);
585 1 : tl_write_uint32(&w, TL_vector);
586 1 : tl_write_uint32(&w, 2);
587 :
588 : /* Msg 1: flags.23 + flags.22 set. */
589 1 : tl_write_uint32(&w, TL_message);
590 1 : tl_write_uint32(&w, (1u << 23) | (1u << 22));
591 1 : tl_write_uint32(&w, 0);
592 1 : tl_write_int32 (&w, 410);
593 1 : tl_write_uint32(&w, TL_peerUser);
594 1 : tl_write_int64 (&w, 100LL);
595 1 : tl_write_int32 (&w, 1700000000);
596 1 : tl_write_string(&w, "discussion post");
597 : /* messageReplies#83d60fc2: flags=0, replies=3, replies_pts=1 */
598 1 : tl_write_uint32(&w, 0x83d60fc2u);
599 1 : tl_write_uint32(&w, 0);
600 1 : tl_write_int32 (&w, 3);
601 1 : tl_write_int32 (&w, 1);
602 : /* restriction_reason: Vector<RestrictionReason>, 1 entry */
603 1 : tl_write_uint32(&w, TL_vector);
604 1 : tl_write_uint32(&w, 1);
605 1 : tl_write_uint32(&w, 0xd072acb4u); /* restrictionReason */
606 1 : tl_write_string(&w, "android");
607 1 : tl_write_string(&w, "sensitive");
608 1 : tl_write_string(&w, "Age-restricted");
609 :
610 : /* Msg 2: plain. */
611 1 : tl_write_uint32(&w, TL_message);
612 1 : tl_write_uint32(&w, 0);
613 1 : tl_write_uint32(&w, 0);
614 1 : tl_write_int32 (&w, 411);
615 1 : tl_write_uint32(&w, TL_peerUser);
616 1 : tl_write_int64 (&w, 100LL);
617 1 : tl_write_int32 (&w, 1700000001);
618 1 : tl_write_string(&w, "next");
619 :
620 1 : uint8_t payload[1024]; memcpy(payload, w.data, w.len);
621 1 : size_t plen = w.len; tl_writer_free(&w);
622 :
623 1 : uint8_t resp[2048]; size_t rlen = 0;
624 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
625 1 : mock_socket_set_response(resp, rlen);
626 :
627 : MtProtoSession s; Transport t; ApiConfig cfg;
628 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
629 :
630 1 : HistoryEntry e[5] = {0}; int n = 0;
631 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 5, e, &n);
632 1 : ASSERT(rc == 0, "history parses past replies + restriction_reason");
633 1 : ASSERT(n == 2, "both messages iterate");
634 1 : ASSERT(e[0].id == 410 && strcmp(e[0].text, "discussion post") == 0,
635 : "msg0 text");
636 1 : ASSERT(e[0].complex == 0, "msg0 NOT complex");
637 1 : ASSERT(e[1].id == 411 && strcmp(e[1].text, "next") == 0, "msg1 text");
638 : }
639 :
640 : /* Test that a media-only message (no caption text) has empty text and non-NONE
641 : * media — the fields relied on by the --no-media filter in cmd_history. */
642 1 : static void test_history_media_only_has_empty_text(void) {
643 1 : mock_socket_reset(); mock_crypto_reset();
644 :
645 1 : TlWriter w; tl_writer_init(&w);
646 1 : tl_write_uint32(&w, TL_messages_messages);
647 1 : tl_write_uint32(&w, TL_vector);
648 1 : tl_write_uint32(&w, 1);
649 :
650 : /* Message with flags.9 (media) set, empty caption string. */
651 1 : tl_write_uint32(&w, TL_message);
652 1 : tl_write_uint32(&w, (1u << 9)); /* flags: media present, no out */
653 1 : tl_write_uint32(&w, 0); /* flags2 */
654 1 : tl_write_int32 (&w, 500);
655 1 : tl_write_uint32(&w, TL_peerUser);
656 1 : tl_write_int64 (&w, 100LL);
657 1 : tl_write_int32 (&w, 1700000000);
658 1 : tl_write_string(&w, ""); /* empty caption */
659 : /* messageMediaPhoto with flags=0x01 (photo present) → photoEmpty */
660 1 : tl_write_uint32(&w, 0x695150d7u); /* messageMediaPhoto */
661 1 : tl_write_uint32(&w, (1u << 0));
662 1 : tl_write_uint32(&w, 0x2331b22du); /* photoEmpty */
663 1 : tl_write_int64 (&w, 88888888LL);
664 :
665 1 : uint8_t payload[512]; memcpy(payload, w.data, w.len);
666 1 : size_t plen = w.len; tl_writer_free(&w);
667 :
668 1 : uint8_t resp[1024]; size_t rlen = 0;
669 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
670 1 : mock_socket_set_response(resp, rlen);
671 :
672 : MtProtoSession s; Transport t; ApiConfig cfg;
673 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
674 :
675 1 : HistoryEntry e[3] = {0}; int n = 0;
676 1 : int rc = domain_get_history_self(&cfg, &s, &t, 0, 3, e, &n);
677 1 : ASSERT(rc == 0, "media-only message parsed");
678 1 : ASSERT(n == 1, "one entry");
679 1 : ASSERT(e[0].media == MEDIA_PHOTO, "--no-media filter: media kind is PHOTO");
680 1 : ASSERT(e[0].text[0] == '\0', "--no-media filter: text is empty for media-only");
681 : }
682 :
683 1 : static void test_history_null_args(void) {
684 1 : HistoryEntry e[1]; int n = 0;
685 1 : ASSERT(domain_get_history_self(NULL, NULL, NULL, 0, 5, e, &n) == -1,
686 : "null args rejected");
687 1 : ApiConfig cfg; fix_cfg(&cfg);
688 1 : MtProtoSession s; fix_session(&s);
689 1 : Transport t; fix_transport(&t);
690 1 : ASSERT(domain_get_history_self(&cfg, &s, &t, 0, 0, e, &n) == -1,
691 : "limit=0 rejected");
692 : }
693 :
694 1 : void run_domain_history_tests(void) {
695 1 : RUN_TEST(test_history_one_empty);
696 1 : RUN_TEST(test_history_rpc_error);
697 1 : RUN_TEST(test_history_channel_peer);
698 1 : RUN_TEST(test_history_text_extraction);
699 1 : RUN_TEST(test_history_complex_flag);
700 1 : RUN_TEST(test_history_iterates_multiple);
701 1 : RUN_TEST(test_history_iterates_with_entities);
702 1 : RUN_TEST(test_history_iterates_with_media_geo);
703 1 : RUN_TEST(test_history_media_photo_info);
704 1 : RUN_TEST(test_history_stops_on_reply_markup);
705 1 : RUN_TEST(test_history_iterates_with_reply_markup);
706 1 : RUN_TEST(test_history_iterates_with_reactions);
707 1 : RUN_TEST(test_history_iterates_with_replies_and_restriction);
708 1 : RUN_TEST(test_history_media_only_has_empty_text);
709 1 : RUN_TEST(test_history_null_args);
710 1 : }
|