Line data Source code
1 : /**
2 : * @file test_tl_forward_compat.c
3 : * @brief TEST-75 — TL forward-compatibility of tl_skip.c.
4 : *
5 : * Exercises the "unknown constructor" contract of tl_skip.c: when a
6 : * future Telegram layer introduces a new CRC where one of the skip
7 : * functions is called, the skipper must refuse to advance silently
8 : * (returning -1) instead of corrupting the reader. The five cases map
9 : * to the ticket:
10 : *
11 : * 1. Unknown trailing field inside a messages.dialogs-style top-level
12 : * result: the known Message bytes before it are fully consumable.
13 : * 2. Unknown MessageMedia CRC inside a Message: the text/date fields
14 : * are extracted and tl_skip_message_media reports MEDIA_OTHER.
15 : * 3. Unknown message action (messageService): the bare prefix
16 : * (flags+id) is advanced past but the body refuses to iterate, so
17 : * the ID is the only thing we can present.
18 : * 4. Known Message with a trailing optional flag bit whose type we do
19 : * not know yet: all known-position fields parse correctly up to
20 : * the unknown flag, and the skipper refuses to invent a layout
21 : * for the unknown bit.
22 : * 5. Unknown Update CRC inside updates.difference: iterating a
23 : * Vector<Message> with unknown trailing fields stops cleanly at
24 : * the first unknown CRC rather than desynchronising the reader.
25 : *
26 : * The tests drive the skip layer directly with fabricated byte streams
27 : * — the same pattern as test_tl_skip_message_functional.c — so we can
28 : * assert against the cursor position and the return code deterministically
29 : * without a live mock server.
30 : */
31 :
32 : #include "test_helpers.h"
33 : #include "tl_serial.h"
34 : #include "tl_registry.h"
35 : #include "tl_skip.h"
36 :
37 : #include <stdint.h>
38 : #include <string.h>
39 :
40 : /* ---- Fabricated "future Telegram" CRCs ----
41 : *
42 : * The high byte 0xFF gives us values that the production CRC tables
43 : * (layer 170+) do not use; they are deliberately unknown so every skip
44 : * call must take the default/unknown branch.
45 : */
46 : #define CRC_future_mediaTypeX 0xFF00AA01u
47 : #define CRC_future_actionReward 0xFF00BB02u
48 : #define CRC_future_replyMarkupTodo 0xFF00CC03u
49 : #define CRC_future_entityBadge 0xFF00DD04u
50 : #define CRC_future_updateHoroscope 0xFF00EE05u
51 : #define CRC_future_peerGhost 0xFF00FF06u
52 : #define CRC_future_chatPsychic 0xFF00FF07u
53 : #define CRC_future_userTimelord 0xFF00FF08u
54 :
55 : /* CRCs not re-exposed from tl_skip.c (copied from tl_skip_message_functional). */
56 : #define CRC_messageMediaEmpty_t 0x3ded6320u
57 : #define CRC_replyInlineMarkup_t 0x48a30254u
58 : #define CRC_keyboardButtonRow_t 0x77608b83u
59 : #define CRC_keyboardButtonCallback_t 0x35bbdb6bu
60 : #define CRC_messageEntityBold_t 0xbd610bc9u
61 :
62 : /* Message flag constants — same subset used by the kitchen-sink suite. */
63 : #define FLAG_REPLY_MARKUP (1u << 6)
64 : #define FLAG_ENTITIES (1u << 7)
65 : #define FLAG_FROM_ID (1u << 8)
66 : #define FLAG_MEDIA (1u << 9)
67 : #define FLAG_VIEWS_FWDS (1u << 10)
68 :
69 : /* ---------------------------------------------------------------- */
70 : /* Case 1 — unknown trailing field after known dialog bytes */
71 : /* ---------------------------------------------------------------- */
72 :
73 : /* Build a realistic Message whose trailer carries an unknown CRC in
74 : * place of the expected reply_markup. The pre-message text/date/id all
75 : * land on the wire in their known positions; the skipper must stop at
76 : * the unknown reply_markup CRC and leave the reader at a well-defined
77 : * mid-object position so the caller can discard the tail and move on. */
78 2 : static void write_message_with_unknown_trailer(TlWriter *w) {
79 2 : uint32_t flags = FLAG_FROM_ID | FLAG_REPLY_MARKUP;
80 2 : uint32_t flags2 = 0;
81 :
82 2 : tl_write_uint32(w, TL_message);
83 2 : tl_write_uint32(w, flags);
84 2 : tl_write_uint32(w, flags2);
85 2 : tl_write_int32 (w, 42); /* id */
86 :
87 : /* from_id + peer_id */
88 2 : tl_write_uint32(w, TL_peerUser); tl_write_int64(w, 7LL);
89 2 : tl_write_uint32(w, TL_peerChannel); tl_write_int64(w, 88LL);
90 :
91 2 : tl_write_int32 (w, 1700000000); /* date */
92 2 : tl_write_string(w, "known text body"); /* message */
93 :
94 : /* reply_markup: an unknown CRC. The skipper must refuse. */
95 2 : tl_write_uint32(w, CRC_future_replyMarkupTodo);
96 2 : tl_write_int32 (w, 0xDEAD);
97 2 : tl_write_int32 (w, 0xBEEF);
98 2 : }
99 :
100 2 : static void test_unknown_top_level_result_skipped(void) {
101 2 : TlWriter w; tl_writer_init(&w);
102 2 : write_message_with_unknown_trailer(&w);
103 :
104 2 : TlReader r = tl_reader_init(w.data, w.len);
105 : /* tl_skip_message must refuse to advance because the reply_markup
106 : * CRC is unknown. The reader position is defined as
107 : * "undefined — caller stops iterating" per the API contract. */
108 2 : ASSERT(tl_skip_message(&r) == -1,
109 : "tl_skip_message rejects unknown reply_markup CRC");
110 : /* Cursor should have advanced past the mandatory prefix at minimum
111 : * (4 bytes CRC + 4 flags + 4 flags2 + 4 id = 16 bytes). */
112 2 : ASSERT(r.pos >= 16, "cursor advanced past message prefix before bailing");
113 :
114 2 : tl_writer_free(&w);
115 :
116 : /* Companion check: feed the same known reply_markup with a valid
117 : * replyInlineMarkup — the skipper must accept it, so we can be sure
118 : * the rejection in the first part was caused by the CRC alone. */
119 2 : tl_writer_init(&w);
120 2 : uint32_t flags = FLAG_FROM_ID | FLAG_REPLY_MARKUP;
121 2 : tl_write_uint32(&w, TL_message);
122 2 : tl_write_uint32(&w, flags);
123 2 : tl_write_uint32(&w, 0);
124 2 : tl_write_int32 (&w, 42);
125 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 7LL);
126 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 88LL);
127 2 : tl_write_int32 (&w, 1700000000);
128 2 : tl_write_string(&w, "known text body");
129 : /* valid inline markup with zero rows */
130 2 : tl_write_uint32(&w, CRC_replyInlineMarkup_t);
131 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
132 2 : tl_write_int32 (&w, 0xCAFE); /* trailer sentinel */
133 :
134 2 : TlReader r2 = tl_reader_init(w.data, w.len);
135 2 : ASSERT(tl_skip_message(&r2) == 0,
136 : "tl_skip_message accepts known empty inline markup");
137 2 : ASSERT(tl_read_int32(&r2) == 0x0000CAFE,
138 : "cursor lands on the sentinel after known reply_markup");
139 2 : tl_writer_free(&w);
140 : }
141 :
142 : /* ---------------------------------------------------------------- */
143 : /* Case 2 — unknown MessageMedia CRC mid-message */
144 : /* ---------------------------------------------------------------- */
145 :
146 2 : static void test_unknown_media_in_history(void) {
147 2 : TlWriter w; tl_writer_init(&w);
148 :
149 2 : uint32_t flags = FLAG_FROM_ID | FLAG_MEDIA;
150 2 : tl_write_uint32(&w, TL_message);
151 2 : tl_write_uint32(&w, flags);
152 2 : tl_write_uint32(&w, 0); /* flags2 */
153 2 : tl_write_int32 (&w, 501); /* id */
154 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 9LL);
155 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 77LL);
156 2 : tl_write_int32 (&w, 1700000100); /* date */
157 2 : tl_write_string(&w, "caption survives"); /* message */
158 :
159 : /* Unknown MessageMedia variant — three bogus trailer words so the
160 : * reader has bytes to overrun if the skipper mis-behaves. */
161 2 : tl_write_uint32(&w, CRC_future_mediaTypeX);
162 2 : tl_write_int32 (&w, 0x1111);
163 2 : tl_write_int32 (&w, 0x2222);
164 2 : tl_write_int32 (&w, 0x3333);
165 :
166 : /* tl_skip_message must return -1 and the cursor must sit at or past
167 : * the MessageMedia CRC (we have read 4 bytes of it before switching
168 : * in the default branch). */
169 2 : TlReader r = tl_reader_init(w.data, w.len);
170 2 : ASSERT(tl_skip_message(&r) == -1,
171 : "unknown media CRC halts tl_skip_message");
172 :
173 : /* Direct exercise of tl_skip_message_media_ex with the unknown CRC
174 : * verifies the "MEDIA_OTHER on unknown" contract. */
175 2 : TlWriter w2; tl_writer_init(&w2);
176 2 : tl_write_uint32(&w2, CRC_future_mediaTypeX);
177 2 : tl_write_int32 (&w2, 0);
178 2 : TlReader r2 = tl_reader_init(w2.data, w2.len);
179 2 : MediaInfo mi = {0};
180 2 : ASSERT(tl_skip_message_media_ex(&r2, &mi) == -1,
181 : "unknown MessageMedia variant returns -1");
182 2 : ASSERT(mi.kind == MEDIA_OTHER,
183 : "unknown MessageMedia labels out as MEDIA_OTHER");
184 :
185 : /* Sanity: a known messageMediaEmpty must succeed at MEDIA_EMPTY. */
186 2 : TlWriter w3; tl_writer_init(&w3);
187 2 : tl_write_uint32(&w3, CRC_messageMediaEmpty_t);
188 2 : TlReader r3 = tl_reader_init(w3.data, w3.len);
189 2 : MediaInfo mi3 = {0};
190 2 : ASSERT(tl_skip_message_media_ex(&r3, &mi3) == 0,
191 : "messageMediaEmpty accepted");
192 2 : ASSERT(mi3.kind == MEDIA_EMPTY, "empty labelled MEDIA_EMPTY");
193 2 : ASSERT(r3.pos == r3.len, "reader fully consumed on empty media");
194 :
195 2 : tl_writer_free(&w);
196 2 : tl_writer_free(&w2);
197 2 : tl_writer_free(&w3);
198 : }
199 :
200 : /* ---------------------------------------------------------------- */
201 : /* Case 3 — unknown messageService action */
202 : /* ---------------------------------------------------------------- */
203 :
204 2 : static void test_unknown_message_action(void) {
205 2 : TlWriter w; tl_writer_init(&w);
206 :
207 : /* Build a messageService envelope. tl_skip_message refuses to walk
208 : * messageService bodies today — it returns -1 after reading the
209 : * prefix — so the action CRC itself never reaches a dispatcher,
210 : * but the caller can still display id/date because they were read
211 : * before the bail. */
212 2 : tl_write_uint32(&w, TL_messageService);
213 2 : tl_write_uint32(&w, 0); /* flags */
214 2 : tl_write_uint32(&w, 0); /* flags2 */
215 2 : tl_write_int32 (&w, 9001); /* id */
216 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 3LL);
217 2 : tl_write_int32 (&w, 1700000200); /* date */
218 : /* Unknown service action — tl_skip_message never reaches it. */
219 2 : tl_write_uint32(&w, CRC_future_actionReward);
220 2 : tl_write_int64 (&w, 0xAABBCCDDEEFF0011LL);
221 :
222 2 : TlReader r = tl_reader_init(w.data, w.len);
223 : /* Contract: messageService is unsupported → -1. The reader position
224 : * is undefined but we must not have advanced past the buffer end. */
225 2 : ASSERT(tl_skip_message(&r) == -1,
226 : "messageService skip refuses to walk body");
227 2 : ASSERT(r.pos <= r.len, "reader stays in-bounds on bail");
228 2 : tl_writer_free(&w);
229 : }
230 :
231 : /* ---------------------------------------------------------------- */
232 : /* Case 4 — known Message with unknown optional flag bit */
233 : /* ---------------------------------------------------------------- */
234 :
235 2 : static void test_unknown_optional_field_preserves_layout(void) {
236 2 : TlWriter w; tl_writer_init(&w);
237 :
238 : /* Set a flag bit the current skipper does not know about (bit 31 in
239 : * flags has no meaning today). The pre-text fields — from_id, peer,
240 : * date, message — must still parse because they sit before any
241 : * optional trailer. */
242 2 : uint32_t flags = FLAG_FROM_ID | (1u << 31);
243 2 : uint32_t flags2 = 0;
244 :
245 2 : tl_write_uint32(&w, TL_message);
246 2 : tl_write_uint32(&w, flags);
247 2 : tl_write_uint32(&w, flags2);
248 2 : tl_write_int32 (&w, 777);
249 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 11LL);
250 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 99LL);
251 2 : tl_write_int32 (&w, 1700000300);
252 2 : tl_write_string(&w, "survives unknown trailing bit");
253 :
254 : /* tl_skip_message walks every known flag bit. Because bit 31 has
255 : * no known payload, the skipper is expected to return 0 (no data
256 : * is read for that bit) — confirming the "unknown bit = no-op"
257 : * forward-compat policy. If a future change bound bit 31 to a
258 : * payload, this test would need to be updated in lock-step with
259 : * the new skipper. */
260 2 : TlReader r = tl_reader_init(w.data, w.len);
261 2 : ASSERT(tl_skip_message(&r) == 0,
262 : "unknown flag bit with no payload is skipped as a no-op");
263 2 : ASSERT(r.pos == r.len, "reader fully consumed");
264 2 : tl_writer_free(&w);
265 :
266 : /* Companion: if a caller sets flags2 bit 31 — also currently
267 : * unused — the skipper must likewise advance cleanly. */
268 2 : tl_writer_init(&w);
269 2 : tl_write_uint32(&w, TL_message);
270 2 : tl_write_uint32(&w, FLAG_FROM_ID);
271 2 : tl_write_uint32(&w, (1u << 31)); /* unknown flags2 bit */
272 2 : tl_write_int32 (&w, 778);
273 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 12LL);
274 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 100LL);
275 2 : tl_write_int32 (&w, 1700000400);
276 2 : tl_write_string(&w, "flags2 variant");
277 2 : TlReader r2 = tl_reader_init(w.data, w.len);
278 2 : ASSERT(tl_skip_message(&r2) == 0,
279 : "unknown flags2 bit with no payload is skipped");
280 2 : ASSERT(r2.pos == r2.len, "reader fully consumed on flags2 variant");
281 2 : tl_writer_free(&w);
282 : }
283 :
284 : /* ---------------------------------------------------------------- */
285 : /* Case 5 — unknown Update CRC inside updates.difference */
286 : /* ---------------------------------------------------------------- */
287 :
288 : /* updates.getDifference returns a Vector<Message> as its first
289 : * sub-field; tl_skip_message is how we iterate that vector. An
290 : * unknown Message-like constructor at position i must halt iteration
291 : * at i so the caller can present whatever preceded it. */
292 2 : static void test_unknown_update_type_in_getdifference(void) {
293 2 : TlWriter w; tl_writer_init(&w);
294 :
295 : /* Simulate two plausible messages followed by one "future update". */
296 : /* 0 — real messageEmpty */
297 2 : tl_write_uint32(&w, TL_messageEmpty);
298 2 : tl_write_uint32(&w, 0); /* flags */
299 2 : tl_write_int32 (&w, 601);
300 : /* 1 — real messageEmpty */
301 2 : tl_write_uint32(&w, TL_messageEmpty);
302 2 : tl_write_uint32(&w, 0);
303 2 : tl_write_int32 (&w, 602);
304 : /* 2 — an unknown Message-like CRC (not TL_message/Empty/Service). */
305 2 : tl_write_uint32(&w, CRC_future_updateHoroscope);
306 2 : tl_write_int32 (&w, 0xDEADBEEF);
307 :
308 2 : TlReader r = tl_reader_init(w.data, w.len);
309 2 : ASSERT(tl_skip_message(&r) == 0, "first messageEmpty skipped");
310 2 : ASSERT(tl_skip_message(&r) == 0, "second messageEmpty skipped");
311 : /* Unknown CRC — tl_skip_message must refuse rather than guess. */
312 2 : ASSERT(tl_skip_message(&r) == -1,
313 : "unknown Message CRC halts iteration");
314 2 : tl_writer_free(&w);
315 : }
316 :
317 : /* ---------------------------------------------------------------- */
318 : /* Additional coverage — exercise the remaining skip surface */
319 : /* */
320 : /* These are not in the ticket's enumerated list but drive many more */
321 : /* lines of tl_skip.c that are otherwise only touched by unit tests. */
322 : /* ---------------------------------------------------------------- */
323 :
324 : /* tl_skip_peer / tl_skip_bool / tl_skip_string on unknown input. */
325 2 : static void test_skip_primitives_reject_unknown(void) {
326 : /* Unknown Peer variant. */
327 2 : TlWriter w; tl_writer_init(&w);
328 2 : tl_write_uint32(&w, CRC_future_peerGhost);
329 2 : tl_write_int64 (&w, 123LL);
330 2 : TlReader r = tl_reader_init(w.data, w.len);
331 2 : ASSERT(tl_skip_peer(&r) == -1, "tl_skip_peer rejects unknown variant");
332 2 : tl_writer_free(&w);
333 :
334 : /* Known Peer variants succeed (peerUser/Chat/Channel). */
335 2 : tl_writer_init(&w);
336 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 1LL);
337 2 : tl_write_uint32(&w, TL_peerChat); tl_write_int64(&w, 2LL);
338 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 3LL);
339 2 : TlReader rk = tl_reader_init(w.data, w.len);
340 2 : ASSERT(tl_skip_peer(&rk) == 0, "peerUser accepted");
341 2 : ASSERT(tl_skip_peer(&rk) == 0, "peerChat accepted");
342 2 : ASSERT(tl_skip_peer(&rk) == 0, "peerChannel accepted");
343 2 : ASSERT(rk.pos == rk.len, "peer reader fully consumed");
344 2 : tl_writer_free(&w);
345 :
346 : /* Bool: tl_skip_bool just reads 4 bytes regardless of value. */
347 2 : tl_writer_init(&w);
348 2 : tl_write_uint32(&w, TL_boolTrue);
349 2 : TlReader rb = tl_reader_init(w.data, w.len);
350 2 : ASSERT(tl_skip_bool(&rb) == 0, "bool skipped");
351 2 : ASSERT(rb.pos == 4, "bool consumed 4 bytes");
352 2 : tl_writer_free(&w);
353 :
354 : /* String round-trip. */
355 2 : tl_writer_init(&w);
356 2 : tl_write_string(&w, "forward-compat string payload");
357 2 : TlReader rs = tl_reader_init(w.data, w.len);
358 2 : ASSERT(tl_skip_string(&rs) == 0, "string skipped");
359 2 : ASSERT(rs.pos == rs.len, "string reader fully consumed");
360 2 : tl_writer_free(&w);
361 : }
362 :
363 : /* tl_skip_message_entity: drive several known variants to exercise the
364 : * switch body, and one unknown one. */
365 2 : static void test_message_entity_variants(void) {
366 : /* Known variants: bold (8 bytes), textUrl (8 + string), mentionName
367 : * (16 bytes), custom emoji (16 bytes), blockquote (12 bytes). */
368 2 : TlWriter w; tl_writer_init(&w);
369 2 : tl_write_uint32(&w, TL_vector);
370 2 : tl_write_uint32(&w, 5);
371 :
372 : /* bold */
373 2 : tl_write_uint32(&w, CRC_messageEntityBold_t);
374 2 : tl_write_int32 (&w, 0); tl_write_int32(&w, 4);
375 :
376 : /* textUrl */
377 2 : tl_write_uint32(&w, 0x76a6d327u);
378 2 : tl_write_int32 (&w, 0); tl_write_int32(&w, 5);
379 2 : tl_write_string(&w, "https://example.org");
380 :
381 : /* mentionName */
382 2 : tl_write_uint32(&w, 0xdc7b1140u);
383 2 : tl_write_int32 (&w, 6); tl_write_int32(&w, 7);
384 2 : tl_write_int64 (&w, 42LL);
385 :
386 : /* custom emoji */
387 2 : tl_write_uint32(&w, 0xc8cf05f8u);
388 2 : tl_write_int32 (&w, 14); tl_write_int32(&w, 2);
389 2 : tl_write_int64 (&w, 9001LL);
390 :
391 : /* blockquote */
392 2 : tl_write_uint32(&w, 0xf1ccaaacu);
393 2 : tl_write_uint32(&w, 0); /* flags */
394 2 : tl_write_int32 (&w, 17); tl_write_int32(&w, 3);
395 :
396 2 : TlReader r = tl_reader_init(w.data, w.len);
397 2 : ASSERT(tl_skip_message_entities_vector(&r) == 0,
398 : "known entity variants all accepted");
399 2 : ASSERT(r.pos == r.len, "reader fully consumed");
400 2 : tl_writer_free(&w);
401 :
402 : /* Unknown entity CRC halts the whole vector. */
403 2 : tl_writer_init(&w);
404 2 : tl_write_uint32(&w, TL_vector);
405 2 : tl_write_uint32(&w, 1);
406 2 : tl_write_uint32(&w, CRC_future_entityBadge);
407 2 : tl_write_int32 (&w, 0); tl_write_int32(&w, 8);
408 2 : TlReader ru = tl_reader_init(w.data, w.len);
409 2 : ASSERT(tl_skip_message_entities_vector(&ru) == -1,
410 : "unknown entity CRC breaks the vector");
411 2 : tl_writer_free(&w);
412 : }
413 :
414 : /* Known MessageMedia variants round-trip — geo, contact, venue, dice,
415 : * geoLive — to sweep through a broad swath of tl_skip_message_media_ex. */
416 2 : static void test_media_variants_skip_clean(void) {
417 : /* messageMediaGeo + geoPointEmpty */
418 2 : TlWriter w; tl_writer_init(&w);
419 2 : tl_write_uint32(&w, 0x56e0d474u); /* messageMediaGeo */
420 2 : tl_write_uint32(&w, 0x1117dd5fu); /* geoPointEmpty */
421 2 : TlReader r = tl_reader_init(w.data, w.len);
422 2 : ASSERT(tl_skip_message_media(&r) == 0, "media:geo/empty skipped");
423 2 : ASSERT(r.pos == r.len, "reader consumed geo/empty");
424 2 : tl_writer_free(&w);
425 :
426 : /* messageMediaContact: phone_number, first_name, last_name, vcard, user_id */
427 2 : tl_writer_init(&w);
428 2 : tl_write_uint32(&w, 0x70322949u);
429 2 : tl_write_string(&w, "+15550001234");
430 2 : tl_write_string(&w, "First");
431 2 : tl_write_string(&w, "Last");
432 2 : tl_write_string(&w, "BEGIN:VCARD\nEND:VCARD");
433 2 : tl_write_int64 (&w, 42LL);
434 2 : TlReader r2 = tl_reader_init(w.data, w.len);
435 2 : ASSERT(tl_skip_message_media(&r2) == 0, "media:contact skipped");
436 2 : ASSERT(r2.pos == r2.len, "reader consumed contact");
437 2 : tl_writer_free(&w);
438 :
439 : /* messageMediaDice: value + emoticon */
440 2 : tl_writer_init(&w);
441 2 : tl_write_uint32(&w, 0x3f7ee58bu);
442 2 : tl_write_int32 (&w, 6);
443 2 : tl_write_string(&w, "\xf0\x9f\x8e\xb2"); /* dice emoji */
444 2 : TlReader r3 = tl_reader_init(w.data, w.len);
445 2 : ASSERT(tl_skip_message_media(&r3) == 0, "media:dice skipped");
446 2 : ASSERT(r3.pos == r3.len, "reader consumed dice");
447 2 : tl_writer_free(&w);
448 :
449 : /* messageMediaVenue: geo + address strings + venue id/type */
450 2 : tl_writer_init(&w);
451 2 : tl_write_uint32(&w, 0x2ec0533fu);
452 2 : tl_write_uint32(&w, 0x1117dd5fu); /* geoPointEmpty */
453 2 : tl_write_string(&w, "123 Main St");
454 2 : tl_write_string(&w, "Coffee shop");
455 2 : tl_write_string(&w, "foursquare");
456 2 : tl_write_string(&w, "V123");
457 2 : tl_write_string(&w, "cafe");
458 2 : TlReader r4 = tl_reader_init(w.data, w.len);
459 2 : ASSERT(tl_skip_message_media(&r4) == 0, "media:venue skipped");
460 2 : ASSERT(r4.pos == r4.len, "reader consumed venue");
461 2 : tl_writer_free(&w);
462 :
463 : /* messageMediaGeoLive: flags=0 + geoPointEmpty + period */
464 2 : tl_writer_init(&w);
465 2 : tl_write_uint32(&w, 0xb940c666u);
466 2 : tl_write_uint32(&w, 0); /* flags */
467 2 : tl_write_uint32(&w, 0x1117dd5fu); /* geoPointEmpty */
468 2 : tl_write_int32 (&w, 3600); /* period */
469 2 : TlReader r5 = tl_reader_init(w.data, w.len);
470 2 : ASSERT(tl_skip_message_media(&r5) == 0, "media:geoLive skipped");
471 2 : ASSERT(r5.pos == r5.len, "reader consumed geoLive");
472 2 : tl_writer_free(&w);
473 :
474 : /* messageMediaUnsupported — a known "we cannot render this" marker. */
475 2 : tl_writer_init(&w);
476 2 : tl_write_uint32(&w, 0x9f84f49eu);
477 2 : TlReader r6 = tl_reader_init(w.data, w.len);
478 2 : MediaInfo mi6 = {0};
479 2 : ASSERT(tl_skip_message_media_ex(&r6, &mi6) == 0,
480 : "media:unsupported accepted");
481 2 : ASSERT(mi6.kind == MEDIA_UNSUPPORTED,
482 : "unsupported marker labelled MEDIA_UNSUPPORTED");
483 2 : tl_writer_free(&w);
484 : }
485 :
486 : /* ReplyMarkup variants round-trip (hide, forceReply, inline, markup). */
487 2 : static void test_reply_markup_variants(void) {
488 : /* replyKeyboardHide — flags=0 */
489 2 : TlWriter w; tl_writer_init(&w);
490 2 : tl_write_uint32(&w, 0xa03e5b85u);
491 2 : tl_write_uint32(&w, 0);
492 2 : TlReader r = tl_reader_init(w.data, w.len);
493 2 : ASSERT(tl_skip_reply_markup(&r) == 0, "keyboardHide skipped");
494 2 : ASSERT(r.pos == r.len, "hide reader consumed");
495 2 : tl_writer_free(&w);
496 :
497 : /* replyKeyboardForceReply with placeholder */
498 2 : tl_writer_init(&w);
499 2 : tl_write_uint32(&w, 0x86b40b08u);
500 2 : tl_write_uint32(&w, (1u << 3)); /* flags.3 → placeholder */
501 2 : tl_write_string(&w, "Type here...");
502 2 : TlReader r2 = tl_reader_init(w.data, w.len);
503 2 : ASSERT(tl_skip_reply_markup(&r2) == 0, "forceReply skipped");
504 2 : ASSERT(r2.pos == r2.len, "forceReply reader consumed");
505 2 : tl_writer_free(&w);
506 :
507 : /* replyInlineMarkup with one row, two buttons (button + url) */
508 2 : tl_writer_init(&w);
509 2 : tl_write_uint32(&w, CRC_replyInlineMarkup_t);
510 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
511 2 : tl_write_uint32(&w, CRC_keyboardButtonRow_t);
512 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
513 : /* keyboardButton */
514 2 : tl_write_uint32(&w, 0xa2fa4880u);
515 2 : tl_write_string(&w, "Yes");
516 : /* keyboardButtonUrl */
517 2 : tl_write_uint32(&w, 0x258aff05u);
518 2 : tl_write_string(&w, "Open");
519 2 : tl_write_string(&w, "https://example.org");
520 2 : TlReader r3 = tl_reader_init(w.data, w.len);
521 2 : ASSERT(tl_skip_reply_markup(&r3) == 0, "inlineMarkup with rows skipped");
522 2 : ASSERT(r3.pos == r3.len, "inline reader consumed");
523 2 : tl_writer_free(&w);
524 :
525 : /* Unknown ReplyMarkup */
526 2 : tl_writer_init(&w);
527 2 : tl_write_uint32(&w, CRC_future_replyMarkupTodo);
528 2 : tl_write_uint32(&w, 0);
529 2 : TlReader r4 = tl_reader_init(w.data, w.len);
530 2 : ASSERT(tl_skip_reply_markup(&r4) == -1,
531 : "unknown reply_markup CRC rejected");
532 2 : tl_writer_free(&w);
533 : }
534 :
535 : /* Chat/User extractors: unknown variant → -1; known path drives many
536 : * conditional lines. */
537 2 : static void test_chat_user_unknown_variants(void) {
538 : /* Unknown chat variant. */
539 2 : TlWriter w; tl_writer_init(&w);
540 2 : tl_write_uint32(&w, CRC_future_chatPsychic);
541 2 : tl_write_int64 (&w, 1LL);
542 2 : TlReader r = tl_reader_init(w.data, w.len);
543 2 : ASSERT(tl_skip_chat(&r) == -1, "unknown chat CRC rejected");
544 2 : tl_writer_free(&w);
545 :
546 : /* Unknown user variant. */
547 2 : tl_writer_init(&w);
548 2 : tl_write_uint32(&w, CRC_future_userTimelord);
549 2 : tl_write_int64 (&w, 1LL);
550 2 : TlReader r2 = tl_reader_init(w.data, w.len);
551 2 : ASSERT(tl_skip_user(&r2) == -1, "unknown user CRC rejected");
552 2 : tl_writer_free(&w);
553 :
554 : /* chatEmpty happy path. */
555 2 : tl_writer_init(&w);
556 2 : tl_write_uint32(&w, TL_chatEmpty);
557 2 : tl_write_int64 (&w, 42LL);
558 2 : TlReader r3 = tl_reader_init(w.data, w.len);
559 2 : ChatSummary cs = {0};
560 2 : ASSERT(tl_extract_chat(&r3, &cs) == 0, "chatEmpty extracted");
561 2 : ASSERT(cs.id == 42LL, "chatEmpty id captured");
562 2 : ASSERT(cs.title[0] == '\0', "chatEmpty title blank");
563 2 : tl_writer_free(&w);
564 :
565 : /* userEmpty happy path. */
566 2 : tl_writer_init(&w);
567 2 : tl_write_uint32(&w, TL_userEmpty);
568 2 : tl_write_int64 (&w, 99LL);
569 2 : TlReader r4 = tl_reader_init(w.data, w.len);
570 2 : UserSummary us = {0};
571 2 : ASSERT(tl_extract_user(&r4, &us) == 0, "userEmpty extracted");
572 2 : ASSERT(us.id == 99LL, "userEmpty id captured");
573 2 : tl_writer_free(&w);
574 :
575 : /* Full user with first/last name + username + phone + access_hash —
576 : * drives many of the flag branches in extract_user_inner. */
577 2 : tl_writer_init(&w);
578 2 : tl_write_uint32(&w, TL_user);
579 : /* flags: 0=access_hash, 1=first_name, 2=last_name, 3=username, 4=phone */
580 2 : uint32_t uflags = (1u << 0) | (1u << 1) | (1u << 2) | (1u << 3) | (1u << 4);
581 2 : tl_write_uint32(&w, uflags);
582 2 : tl_write_uint32(&w, 0); /* flags2 */
583 2 : tl_write_int64 (&w, 7001LL);
584 2 : tl_write_int64 (&w, 0xAABBCCDDEEFF0011LL); /* access_hash */
585 2 : tl_write_string(&w, "Alice");
586 2 : tl_write_string(&w, "Wonder");
587 2 : tl_write_string(&w, "alice_wonder");
588 2 : tl_write_string(&w, "+10000000000");
589 2 : TlReader r5 = tl_reader_init(w.data, w.len);
590 2 : UserSummary us5 = {0};
591 2 : ASSERT(tl_extract_user(&r5, &us5) == 0, "full user extracted");
592 2 : ASSERT(us5.id == 7001LL, "full user id");
593 2 : ASSERT(us5.have_access_hash == 1, "full user access_hash present");
594 2 : ASSERT(strcmp(us5.name, "Alice Wonder") == 0, "name joined");
595 2 : ASSERT(strcmp(us5.username, "alice_wonder") == 0, "username captured");
596 2 : tl_writer_free(&w);
597 : }
598 :
599 : /* Truncation — short buffers must fail cleanly instead of reading OOB. */
600 2 : static void test_truncation_rejected(void) {
601 : /* Just the message CRC, nothing else. */
602 : uint8_t only_crc[4];
603 2 : only_crc[0] = 0x42; only_crc[1] = 0x52; only_crc[2] = 0x34; only_crc[3] = 0x94;
604 2 : TlReader r = tl_reader_init(only_crc, sizeof(only_crc));
605 2 : ASSERT(tl_skip_message(&r) == -1,
606 : "tl_skip_message rejects payload shorter than header");
607 :
608 : /* Zero-length buffer. */
609 2 : TlReader r0 = tl_reader_init(NULL, 0);
610 2 : ASSERT(tl_skip_message(&r0) == -1, "empty buffer rejected");
611 2 : ASSERT(tl_skip_peer(&r0) == -1, "peer short read rejected");
612 2 : ASSERT(tl_skip_string(&r0) == -1, "string short read rejected");
613 2 : ASSERT(tl_skip_bool(&r0) == -1, "bool short read rejected");
614 : }
615 :
616 : /* ---------------------------------------------------------------- */
617 : /* Extra surface coverage — the tl_skip.c file covers dozens of */
618 : /* nested TL types. Exercising the known-CRC branches of each one */
619 : /* matters for forward-compat because it proves that "unknown */
620 : /* returns -1, known returns 0" is a uniform contract, not a special */
621 : /* case of the Message top-level only. */
622 : /* ---------------------------------------------------------------- */
623 :
624 : /* ---- PhotoSize + Photo ---- */
625 2 : static void test_photo_and_photo_size_roundtrip(void) {
626 : /* Photo with flags.0, id + access_hash + file_reference + date + sizes +
627 : * dc_id. Walks photo_full, walk_photo_size_vector, tl_skip_photo_size. */
628 2 : TlWriter w; tl_writer_init(&w);
629 2 : tl_write_uint32(&w, 0xfb197a65u); /* photo */
630 2 : tl_write_uint32(&w, 0); /* flags (no has_stickers,
631 : no video_sizes) */
632 2 : tl_write_int64 (&w, 1001LL); /* id */
633 2 : tl_write_int64 (&w, 0xABCDEF0123456789LL); /* access_hash */
634 : /* file_reference:bytes — empty is fine */
635 2 : tl_write_bytes (&w, (const unsigned char *)"", 0);
636 2 : tl_write_int32 (&w, 1700000000); /* date */
637 : /* sizes:Vector<PhotoSize> — one photoSize variant */
638 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 3);
639 : /* photoSize: type + w + h + size */
640 2 : tl_write_uint32(&w, 0x75c78e60u);
641 2 : tl_write_string(&w, "y");
642 2 : tl_write_int32 (&w, 1280); tl_write_int32(&w, 720); tl_write_int32(&w, 55555);
643 : /* photoCachedSize: type + w + h + bytes */
644 2 : tl_write_uint32(&w, 0x021e1ad6u);
645 2 : tl_write_string(&w, "s");
646 2 : tl_write_int32 (&w, 90); tl_write_int32(&w, 90);
647 2 : tl_write_bytes (&w, (const unsigned char *)"\x00\x01\x02", 3);
648 : /* photoSizeProgressive: type + w + h + Vector<int> */
649 2 : tl_write_uint32(&w, 0xfa3efb95u);
650 2 : tl_write_string(&w, "p");
651 2 : tl_write_int32 (&w, 1080); tl_write_int32(&w, 1920);
652 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
653 2 : tl_write_int32 (&w, 100); tl_write_int32(&w, 200);
654 : /* dc_id */
655 2 : tl_write_int32 (&w, 2);
656 2 : TlReader r = tl_reader_init(w.data, w.len);
657 2 : ASSERT(tl_skip_photo(&r) == 0, "photo walked");
658 2 : ASSERT(r.pos == r.len, "reader consumed photo");
659 2 : tl_writer_free(&w);
660 :
661 : /* photoEmpty */
662 2 : tl_writer_init(&w);
663 2 : tl_write_uint32(&w, 0x2331b22du);
664 2 : tl_write_int64 (&w, 7777LL);
665 2 : TlReader r2 = tl_reader_init(w.data, w.len);
666 2 : ASSERT(tl_skip_photo(&r2) == 0, "photoEmpty walked");
667 2 : tl_writer_free(&w);
668 :
669 : /* photoSize variants individually */
670 2 : tl_writer_init(&w);
671 : /* photoSizeEmpty */
672 2 : tl_write_uint32(&w, 0x0e17e23cu);
673 2 : tl_write_string(&w, "x");
674 2 : TlReader r3 = tl_reader_init(w.data, w.len);
675 2 : ASSERT(tl_skip_photo_size(&r3) == 0, "photoSizeEmpty ok");
676 2 : tl_writer_free(&w);
677 :
678 : /* photoStrippedSize */
679 2 : tl_writer_init(&w);
680 2 : tl_write_uint32(&w, 0xe0b0bc2eu);
681 2 : tl_write_string(&w, "i");
682 2 : tl_write_bytes (&w, (const unsigned char *)"\xFF\xFE\xFD", 3);
683 2 : TlReader r4 = tl_reader_init(w.data, w.len);
684 2 : ASSERT(tl_skip_photo_size(&r4) == 0, "photoStrippedSize ok");
685 2 : tl_writer_free(&w);
686 :
687 : /* photoPathSize */
688 2 : tl_writer_init(&w);
689 2 : tl_write_uint32(&w, 0xd8214d41u);
690 2 : tl_write_string(&w, "j");
691 2 : tl_write_bytes (&w, (const unsigned char *)"abc", 3);
692 2 : TlReader r5 = tl_reader_init(w.data, w.len);
693 2 : ASSERT(tl_skip_photo_size(&r5) == 0, "photoPathSize ok");
694 2 : tl_writer_free(&w);
695 :
696 : /* Unknown photoSize CRC */
697 2 : tl_writer_init(&w);
698 2 : tl_write_uint32(&w, 0xFF00BBCCu);
699 2 : TlReader r6 = tl_reader_init(w.data, w.len);
700 2 : ASSERT(tl_skip_photo_size(&r6) == -1, "unknown photoSize rejected");
701 2 : tl_writer_free(&w);
702 :
703 : /* photo_size_vector */
704 2 : tl_writer_init(&w);
705 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
706 2 : tl_write_uint32(&w, 0x75c78e60u);
707 2 : tl_write_string(&w, "m");
708 2 : tl_write_int32 (&w, 320); tl_write_int32(&w, 240); tl_write_int32(&w, 1000);
709 2 : TlReader r7 = tl_reader_init(w.data, w.len);
710 2 : ASSERT(tl_skip_photo_size_vector(&r7) == 0, "vector walked");
711 2 : tl_writer_free(&w);
712 : }
713 :
714 : /* ---- Document with attributes ---- */
715 2 : static void test_document_with_attributes(void) {
716 2 : TlWriter w; tl_writer_init(&w);
717 2 : tl_write_uint32(&w, 0x8fd4c4d8u); /* document */
718 2 : tl_write_uint32(&w, 0); /* flags (no thumbs / video) */
719 2 : tl_write_int64 (&w, 1234LL); /* id */
720 2 : tl_write_int64 (&w, 0xFEEDFACECAFED00DLL); /* access_hash */
721 2 : tl_write_bytes (&w, (const unsigned char *)"ref", 3); /* file_reference */
722 2 : tl_write_int32 (&w, 1700000000); /* date */
723 2 : tl_write_string(&w, "image/png"); /* mime_type */
724 2 : tl_write_int64 (&w, 4096LL); /* size */
725 2 : tl_write_int32 (&w, 2); /* dc_id */
726 : /* attributes: Vector<DocumentAttribute> — cover many variants */
727 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 5);
728 : /* imageSize */
729 2 : tl_write_uint32(&w, 0x6c37c15cu);
730 2 : tl_write_int32 (&w, 800); tl_write_int32(&w, 600);
731 : /* animated */
732 2 : tl_write_uint32(&w, 0x11b58939u);
733 : /* filename */
734 2 : tl_write_uint32(&w, 0x15590068u);
735 2 : tl_write_string(&w, "selfie.png");
736 : /* audio flags=0, duration only */
737 2 : tl_write_uint32(&w, 0x9852f9c6u);
738 2 : tl_write_uint32(&w, 0); /* flags */
739 2 : tl_write_int32 (&w, 30); /* duration */
740 : /* hasStickers */
741 2 : tl_write_uint32(&w, 0x9801d2f7u);
742 2 : TlReader r = tl_reader_init(w.data, w.len);
743 2 : ASSERT(tl_skip_document(&r) == 0, "document walked");
744 2 : ASSERT(r.pos == r.len, "reader consumed document");
745 2 : tl_writer_free(&w);
746 :
747 : /* documentEmpty */
748 2 : tl_writer_init(&w);
749 2 : tl_write_uint32(&w, 0x36f8c871u);
750 2 : tl_write_int64 (&w, 42LL);
751 2 : TlReader r2 = tl_reader_init(w.data, w.len);
752 2 : ASSERT(tl_skip_document(&r2) == 0, "documentEmpty walked");
753 2 : tl_writer_free(&w);
754 : }
755 :
756 : /* ---- Message forward header ---- */
757 2 : static void test_fwd_header_variants(void) {
758 : /* messageFwdHeader: flags with from_id (bit 0) + date only (simplest). */
759 2 : TlWriter w; tl_writer_init(&w);
760 2 : tl_write_uint32(&w, 0x4e4df4bbu);
761 2 : tl_write_uint32(&w, (1u << 0)); /* flags: from_id */
762 2 : tl_write_uint32(&w, TL_peerUser);
763 2 : tl_write_int64 (&w, 42LL); /* from_id peer */
764 2 : tl_write_int32 (&w, 1700001000); /* date */
765 2 : TlReader r = tl_reader_init(w.data, w.len);
766 2 : ASSERT(tl_skip_message_fwd_header(&r) == 0, "fwd header walked");
767 2 : ASSERT(r.pos == r.len, "fwd reader consumed");
768 2 : tl_writer_free(&w);
769 :
770 : /* Unknown CRC */
771 2 : tl_writer_init(&w);
772 2 : tl_write_uint32(&w, 0xFF00FF77u);
773 2 : tl_write_uint32(&w, 0);
774 2 : TlReader r2 = tl_reader_init(w.data, w.len);
775 2 : ASSERT(tl_skip_message_fwd_header(&r2) == -1,
776 : "unknown fwd header CRC rejected");
777 2 : tl_writer_free(&w);
778 :
779 : /* Fuller fwd header — from_id + from_name + channel_post + post_author */
780 2 : tl_writer_init(&w);
781 2 : tl_write_uint32(&w, 0x4e4df4bbu);
782 2 : uint32_t ff = (1u << 0) | (1u << 2) | (1u << 3) | (1u << 5);
783 2 : tl_write_uint32(&w, ff);
784 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 1000LL);
785 2 : tl_write_string(&w, "Anonymous"); /* from_name (flags.5) */
786 2 : tl_write_int32 (&w, 1700001000); /* date */
787 2 : tl_write_int32 (&w, 55); /* channel_post (flags.2) */
788 2 : tl_write_string(&w, "Bot Author"); /* post_author (flags.3) */
789 2 : TlReader r3 = tl_reader_init(w.data, w.len);
790 2 : ASSERT(tl_skip_message_fwd_header(&r3) == 0,
791 : "fuller fwd header walked");
792 2 : tl_writer_free(&w);
793 : }
794 :
795 : /* ---- Reply header ---- */
796 2 : static void test_reply_header_variants(void) {
797 : /* messageReplyHeader with reply_to_msg_id (flags.4) */
798 2 : TlWriter w; tl_writer_init(&w);
799 2 : tl_write_uint32(&w, 0xafbc09dbu);
800 2 : tl_write_uint32(&w, (1u << 4));
801 2 : tl_write_int32 (&w, 3000); /* reply_to_msg_id */
802 2 : TlReader r = tl_reader_init(w.data, w.len);
803 2 : ASSERT(tl_skip_message_reply_header(&r) == 0, "reply header walked");
804 2 : tl_writer_free(&w);
805 :
806 : /* messageReplyStoryHeader */
807 2 : tl_writer_init(&w);
808 2 : tl_write_uint32(&w, 0xe5af939u);
809 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 42LL);
810 2 : tl_write_int32 (&w, 77);
811 2 : TlReader r2 = tl_reader_init(w.data, w.len);
812 2 : ASSERT(tl_skip_message_reply_header(&r2) == 0, "story header walked");
813 2 : tl_writer_free(&w);
814 :
815 : /* Unknown reply header CRC */
816 2 : tl_writer_init(&w);
817 2 : tl_write_uint32(&w, 0xFF00FF88u);
818 2 : tl_write_uint32(&w, 0);
819 2 : TlReader r3 = tl_reader_init(w.data, w.len);
820 2 : ASSERT(tl_skip_message_reply_header(&r3) == -1,
821 : "unknown reply header rejected");
822 2 : tl_writer_free(&w);
823 : }
824 :
825 : /* ---- Draft message ---- */
826 2 : static void test_draft_message_variants(void) {
827 2 : TlWriter w; tl_writer_init(&w);
828 : /* draftMessageEmpty flags=0 */
829 2 : tl_write_uint32(&w, 0x1b0c841au);
830 2 : tl_write_uint32(&w, 0);
831 2 : TlReader r = tl_reader_init(w.data, w.len);
832 2 : ASSERT(tl_skip_draft_message(&r) == 0, "draftMessageEmpty walked");
833 2 : tl_writer_free(&w);
834 :
835 : /* draftMessageEmpty with date */
836 2 : tl_writer_init(&w);
837 2 : tl_write_uint32(&w, 0x1b0c841au);
838 2 : tl_write_uint32(&w, 1u);
839 2 : tl_write_int32 (&w, 1700000000);
840 2 : TlReader r2 = tl_reader_init(w.data, w.len);
841 2 : ASSERT(tl_skip_draft_message(&r2) == 0, "draftEmpty+date walked");
842 2 : tl_writer_free(&w);
843 :
844 : /* draftMessage non-empty: production chooses not to parse, so it
845 : * must return -1. */
846 2 : tl_writer_init(&w);
847 2 : tl_write_uint32(&w, 0x3fccf7efu);
848 2 : tl_write_uint32(&w, 0);
849 2 : TlReader r3 = tl_reader_init(w.data, w.len);
850 2 : ASSERT(tl_skip_draft_message(&r3) == -1, "non-empty draft rejected");
851 2 : tl_writer_free(&w);
852 :
853 : /* Unknown draft CRC */
854 2 : tl_writer_init(&w);
855 2 : tl_write_uint32(&w, 0xFF00FFAAu);
856 2 : TlReader r4 = tl_reader_init(w.data, w.len);
857 2 : ASSERT(tl_skip_draft_message(&r4) == -1, "unknown draft rejected");
858 2 : tl_writer_free(&w);
859 : }
860 :
861 : /* ---- Notification sound + peerNotifySettings ---- */
862 2 : static void test_notification_sound_and_settings(void) {
863 : /* All four sound variants */
864 2 : TlWriter w; tl_writer_init(&w);
865 2 : tl_write_uint32(&w, 0x97e8bebeu); /* default */
866 2 : TlReader r = tl_reader_init(w.data, w.len);
867 2 : ASSERT(tl_skip_notification_sound(&r) == 0, "default sound");
868 2 : tl_writer_free(&w);
869 :
870 2 : tl_writer_init(&w);
871 2 : tl_write_uint32(&w, 0x6f0c34dfu); /* none */
872 2 : TlReader r2 = tl_reader_init(w.data, w.len);
873 2 : ASSERT(tl_skip_notification_sound(&r2) == 0, "none sound");
874 2 : tl_writer_free(&w);
875 :
876 2 : tl_writer_init(&w);
877 2 : tl_write_uint32(&w, 0x830b9ae4u); /* local */
878 2 : tl_write_string(&w, "Chime");
879 2 : tl_write_string(&w, "chime.mp3");
880 2 : TlReader r3 = tl_reader_init(w.data, w.len);
881 2 : ASSERT(tl_skip_notification_sound(&r3) == 0, "local sound");
882 2 : tl_writer_free(&w);
883 :
884 2 : tl_writer_init(&w);
885 2 : tl_write_uint32(&w, 0xff6c8049u); /* ringtone */
886 2 : tl_write_int64 (&w, 777LL);
887 2 : TlReader r4 = tl_reader_init(w.data, w.len);
888 2 : ASSERT(tl_skip_notification_sound(&r4) == 0, "ringtone sound");
889 2 : tl_writer_free(&w);
890 :
891 2 : tl_writer_init(&w);
892 2 : tl_write_uint32(&w, 0xFF00FF11u); /* unknown */
893 2 : TlReader r5 = tl_reader_init(w.data, w.len);
894 2 : ASSERT(tl_skip_notification_sound(&r5) == -1, "unknown sound rejected");
895 2 : tl_writer_free(&w);
896 :
897 : /* peerNotifySettings with many sub-fields. */
898 2 : tl_writer_init(&w);
899 2 : tl_write_uint32(&w, 0xa83b0426u);
900 : /* flags: show_previews(0), silent(1), mute_until(2), ios(3), android(4),
901 : * other(5), stories_muted(6), stories_hide_sender(7) */
902 2 : uint32_t sflags = (1u << 0) | (1u << 1) | (1u << 2) |
903 : (1u << 3) | (1u << 4) | (1u << 5) |
904 : (1u << 6) | (1u << 7);
905 2 : tl_write_uint32(&w, sflags);
906 2 : tl_write_uint32(&w, TL_boolTrue); /* show_previews */
907 2 : tl_write_uint32(&w, TL_boolFalse); /* silent */
908 2 : tl_write_int32 (&w, 1700100000); /* mute_until */
909 2 : tl_write_uint32(&w, 0x97e8bebeu); /* ios_sound default */
910 2 : tl_write_uint32(&w, 0x6f0c34dfu); /* android none */
911 2 : tl_write_uint32(&w, 0x97e8bebeu); /* other default */
912 2 : tl_write_uint32(&w, TL_boolTrue); /* stories_muted */
913 2 : tl_write_uint32(&w, TL_boolFalse); /* stories_hide_sender */
914 2 : TlReader rs = tl_reader_init(w.data, w.len);
915 2 : ASSERT(tl_skip_peer_notify_settings(&rs) == 0, "settings walked");
916 2 : ASSERT(rs.pos == rs.len, "settings reader consumed");
917 2 : tl_writer_free(&w);
918 :
919 : /* Unknown settings CRC */
920 2 : tl_writer_init(&w);
921 2 : tl_write_uint32(&w, 0xFF00FF22u);
922 2 : tl_write_uint32(&w, 0);
923 2 : TlReader ru = tl_reader_init(w.data, w.len);
924 2 : ASSERT(tl_skip_peer_notify_settings(&ru) == -1,
925 : "unknown settings rejected");
926 2 : tl_writer_free(&w);
927 : }
928 :
929 : /* ---- Chat photo + user profile photo + user status ---- */
930 2 : static void test_chat_user_visuals(void) {
931 : /* chatPhotoEmpty / chatPhoto */
932 2 : TlWriter w; tl_writer_init(&w);
933 2 : tl_write_uint32(&w, 0x37c1011cu);
934 2 : TlReader r = tl_reader_init(w.data, w.len);
935 2 : ASSERT(tl_skip_chat_photo(&r) == 0, "chatPhotoEmpty");
936 2 : tl_writer_free(&w);
937 :
938 2 : tl_writer_init(&w);
939 2 : tl_write_uint32(&w, 0x1c6e1c11u);
940 2 : tl_write_uint32(&w, 0); /* flags */
941 2 : tl_write_int64 (&w, 5001LL); /* photo_id */
942 2 : tl_write_int32 (&w, 2); /* dc_id */
943 2 : TlReader r2 = tl_reader_init(w.data, w.len);
944 2 : ASSERT(tl_skip_chat_photo(&r2) == 0, "chatPhoto");
945 2 : tl_writer_free(&w);
946 :
947 : /* chatPhoto with stripped_thumb (flags.1) */
948 2 : tl_writer_init(&w);
949 2 : tl_write_uint32(&w, 0x1c6e1c11u);
950 2 : tl_write_uint32(&w, (1u << 1));
951 2 : tl_write_int64 (&w, 5002LL);
952 2 : tl_write_bytes (&w, (const unsigned char *)"stripped", 8);
953 2 : tl_write_int32 (&w, 4);
954 2 : TlReader r3 = tl_reader_init(w.data, w.len);
955 2 : ASSERT(tl_skip_chat_photo(&r3) == 0, "chatPhoto+stripped");
956 2 : tl_writer_free(&w);
957 :
958 : /* Unknown chatPhoto */
959 2 : tl_writer_init(&w);
960 2 : tl_write_uint32(&w, 0xFF00FF33u);
961 2 : TlReader r4 = tl_reader_init(w.data, w.len);
962 2 : ASSERT(tl_skip_chat_photo(&r4) == -1, "unknown chatPhoto rejected");
963 2 : tl_writer_free(&w);
964 :
965 : /* userProfilePhotoEmpty / userProfilePhoto */
966 2 : tl_writer_init(&w);
967 2 : tl_write_uint32(&w, 0x4f11bae1u);
968 2 : TlReader r5 = tl_reader_init(w.data, w.len);
969 2 : ASSERT(tl_skip_user_profile_photo(&r5) == 0, "userProfilePhotoEmpty");
970 2 : tl_writer_free(&w);
971 :
972 2 : tl_writer_init(&w);
973 2 : tl_write_uint32(&w, 0x82d1f706u);
974 2 : tl_write_uint32(&w, 0);
975 2 : tl_write_int64 (&w, 9000LL);
976 2 : tl_write_int32 (&w, 5);
977 2 : TlReader r6 = tl_reader_init(w.data, w.len);
978 2 : ASSERT(tl_skip_user_profile_photo(&r6) == 0, "userProfilePhoto");
979 2 : tl_writer_free(&w);
980 :
981 : /* Unknown userProfilePhoto */
982 2 : tl_writer_init(&w);
983 2 : tl_write_uint32(&w, 0xFF00FF44u);
984 2 : TlReader r7 = tl_reader_init(w.data, w.len);
985 2 : ASSERT(tl_skip_user_profile_photo(&r7) == -1, "unknown rejected");
986 2 : tl_writer_free(&w);
987 :
988 : /* userStatusEmpty carries no payload; the other three (recently,
989 : * lastWeek, lastMonth) each read an int32 per the 170+ schema. */
990 2 : tl_writer_init(&w);
991 2 : tl_write_uint32(&w, 0x09d05049u); /* empty */
992 2 : TlReader rse = tl_reader_init(w.data, w.len);
993 2 : ASSERT(tl_skip_user_status(&rse) == 0, "empty user status");
994 2 : tl_writer_free(&w);
995 :
996 2 : uint32_t statuses_with_int[] = {
997 : 0x7b197dc8u, 0x541a1d1au, 0x65899e67u
998 : };
999 8 : for (size_t i = 0; i < sizeof(statuses_with_int)/sizeof(statuses_with_int[0]); i++) {
1000 6 : tl_writer_init(&w);
1001 6 : tl_write_uint32(&w, statuses_with_int[i]);
1002 6 : tl_write_int32 (&w, 1700000000);
1003 6 : TlReader rr = tl_reader_init(w.data, w.len);
1004 6 : ASSERT(tl_skip_user_status(&rr) == 0, "recently/lastWeek/lastMonth status");
1005 6 : tl_writer_free(&w);
1006 : }
1007 :
1008 : /* Online/Offline carry an int32 expires/was_online. */
1009 2 : tl_writer_init(&w);
1010 2 : tl_write_uint32(&w, 0xedb93949u);
1011 2 : tl_write_int32 (&w, 1700200000);
1012 2 : TlReader ro = tl_reader_init(w.data, w.len);
1013 2 : ASSERT(tl_skip_user_status(&ro) == 0, "online status");
1014 2 : tl_writer_free(&w);
1015 :
1016 2 : tl_writer_init(&w);
1017 2 : tl_write_uint32(&w, 0x008c703fu);
1018 2 : tl_write_int32 (&w, 1700100000);
1019 2 : TlReader roff = tl_reader_init(w.data, w.len);
1020 2 : ASSERT(tl_skip_user_status(&roff) == 0, "offline status");
1021 2 : tl_writer_free(&w);
1022 :
1023 : /* Unknown status */
1024 2 : tl_writer_init(&w);
1025 2 : tl_write_uint32(&w, 0xFF00FF55u);
1026 2 : TlReader ru2 = tl_reader_init(w.data, w.len);
1027 2 : ASSERT(tl_skip_user_status(&ru2) == -1, "unknown status rejected");
1028 2 : tl_writer_free(&w);
1029 : }
1030 :
1031 : /* ---- Username vector + peer color + emoji status ---- */
1032 2 : static void test_username_color_emoji(void) {
1033 : /* Vector<Username> of length 2 */
1034 2 : TlWriter w; tl_writer_init(&w);
1035 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1036 2 : tl_write_uint32(&w, 0xb4073647u); /* username */
1037 2 : tl_write_uint32(&w, 0); /* flags */
1038 2 : tl_write_string(&w, "alice");
1039 2 : tl_write_uint32(&w, 0xb4073647u);
1040 2 : tl_write_uint32(&w, 0);
1041 2 : tl_write_string(&w, "bob");
1042 2 : TlReader r = tl_reader_init(w.data, w.len);
1043 2 : ASSERT(tl_skip_username_vector(&r) == 0, "username vector walked");
1044 2 : ASSERT(r.pos == r.len, "reader consumed");
1045 2 : tl_writer_free(&w);
1046 :
1047 : /* Unknown entry in username vector. */
1048 2 : tl_writer_init(&w);
1049 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1050 2 : tl_write_uint32(&w, 0xFF00FF66u);
1051 2 : tl_write_uint32(&w, 0);
1052 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1053 2 : ASSERT(tl_skip_username_vector(&r2) == -1,
1054 : "unknown username entry rejected");
1055 2 : tl_writer_free(&w);
1056 :
1057 : /* peerColor with color + emoji_id */
1058 2 : tl_writer_init(&w);
1059 2 : tl_write_uint32(&w, 0xb54b5acfu);
1060 2 : tl_write_uint32(&w, (1u << 0) | (1u << 1));
1061 2 : tl_write_int32 (&w, 5);
1062 2 : tl_write_int64 (&w, 500001LL);
1063 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1064 2 : ASSERT(tl_skip_peer_color(&r3) == 0, "peerColor walked");
1065 2 : tl_writer_free(&w);
1066 :
1067 : /* Unknown peerColor CRC */
1068 2 : tl_writer_init(&w);
1069 2 : tl_write_uint32(&w, 0xFF00FF77u);
1070 2 : tl_write_uint32(&w, 0);
1071 2 : TlReader r4 = tl_reader_init(w.data, w.len);
1072 2 : ASSERT(tl_skip_peer_color(&r4) == -1, "unknown peerColor rejected");
1073 2 : tl_writer_free(&w);
1074 :
1075 : /* EmojiStatus all three */
1076 2 : tl_writer_init(&w);
1077 2 : tl_write_uint32(&w, 0x2de11aaeu); /* empty */
1078 2 : TlReader r5 = tl_reader_init(w.data, w.len);
1079 2 : ASSERT(tl_skip_emoji_status(&r5) == 0, "emojiStatusEmpty");
1080 2 : tl_writer_free(&w);
1081 :
1082 2 : tl_writer_init(&w);
1083 2 : tl_write_uint32(&w, 0x929b619du);
1084 2 : tl_write_int64 (&w, 123LL);
1085 2 : TlReader r6 = tl_reader_init(w.data, w.len);
1086 2 : ASSERT(tl_skip_emoji_status(&r6) == 0, "emojiStatus");
1087 2 : tl_writer_free(&w);
1088 :
1089 2 : tl_writer_init(&w);
1090 2 : tl_write_uint32(&w, 0xfa30a8c7u);
1091 2 : tl_write_int64 (&w, 456LL);
1092 2 : tl_write_int32 (&w, 1700500000);
1093 2 : TlReader r7 = tl_reader_init(w.data, w.len);
1094 2 : ASSERT(tl_skip_emoji_status(&r7) == 0, "emojiStatusUntil");
1095 2 : tl_writer_free(&w);
1096 :
1097 : /* Unknown emoji status */
1098 2 : tl_writer_init(&w);
1099 2 : tl_write_uint32(&w, 0xFF00FF88u);
1100 2 : TlReader r8 = tl_reader_init(w.data, w.len);
1101 2 : ASSERT(tl_skip_emoji_status(&r8) == -1, "unknown emojiStatus rejected");
1102 2 : tl_writer_free(&w);
1103 : }
1104 :
1105 : /* ---- Restriction reason vector ---- */
1106 2 : static void test_restriction_reason_vector(void) {
1107 2 : TlWriter w; tl_writer_init(&w);
1108 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1109 2 : tl_write_uint32(&w, 0xd072acb4u);
1110 2 : tl_write_string(&w, "android");
1111 2 : tl_write_string(&w, "sensitive");
1112 2 : tl_write_string(&w, "Restricted");
1113 2 : tl_write_uint32(&w, 0xd072acb4u);
1114 2 : tl_write_string(&w, "ios");
1115 2 : tl_write_string(&w, "porn");
1116 2 : tl_write_string(&w, "NSFW");
1117 2 : TlReader r = tl_reader_init(w.data, w.len);
1118 2 : ASSERT(tl_skip_restriction_reason_vector(&r) == 0,
1119 : "restriction reason vector walked");
1120 2 : tl_writer_free(&w);
1121 :
1122 : /* Unknown inner CRC */
1123 2 : tl_writer_init(&w);
1124 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1125 2 : tl_write_uint32(&w, 0xFF00FF99u);
1126 2 : tl_write_string(&w, "x"); tl_write_string(&w, "y"); tl_write_string(&w, "z");
1127 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1128 2 : ASSERT(tl_skip_restriction_reason_vector(&r2) == -1,
1129 : "unknown restriction reason rejected");
1130 2 : tl_writer_free(&w);
1131 : }
1132 :
1133 : /* ---- Factcheck ---- */
1134 2 : static void test_factcheck_variants(void) {
1135 : /* factCheck with country + text + hash. */
1136 2 : TlWriter w; tl_writer_init(&w);
1137 2 : tl_write_uint32(&w, 0xb89bfccfu);
1138 2 : tl_write_uint32(&w, (1u << 1));
1139 2 : tl_write_string(&w, "HU");
1140 2 : tl_write_uint32(&w, 0x751f3146u); /* textWithEntities */
1141 2 : tl_write_string(&w, "fact-checked");
1142 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1143 2 : tl_write_int64 (&w, 0xDEADBEEFCAFEBABELL); /* hash */
1144 2 : TlReader r = tl_reader_init(w.data, w.len);
1145 2 : ASSERT(tl_skip_factcheck(&r) == 0, "factcheck walked");
1146 2 : tl_writer_free(&w);
1147 :
1148 : /* factCheck with no flags set (just hash). */
1149 2 : tl_writer_init(&w);
1150 2 : tl_write_uint32(&w, 0xb89bfccfu);
1151 2 : tl_write_uint32(&w, 0);
1152 2 : tl_write_int64 (&w, 77LL);
1153 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1154 2 : ASSERT(tl_skip_factcheck(&r2) == 0, "flagless factcheck");
1155 2 : tl_writer_free(&w);
1156 :
1157 : /* Unknown factcheck CRC */
1158 2 : tl_writer_init(&w);
1159 2 : tl_write_uint32(&w, 0xFF00FFAAu);
1160 2 : tl_write_uint32(&w, 0);
1161 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1162 2 : ASSERT(tl_skip_factcheck(&r3) == -1, "unknown factcheck rejected");
1163 2 : tl_writer_free(&w);
1164 : }
1165 :
1166 : /* ---- MessageReactions + MessageReplies ---- */
1167 2 : static void test_reactions_replies_trailers(void) {
1168 : /* MessageReactions with two ReactionCount entries (emoji + custom emoji). */
1169 2 : TlWriter w; tl_writer_init(&w);
1170 2 : tl_write_uint32(&w, 0x4f2b9479u); /* messageReactions */
1171 2 : tl_write_uint32(&w, 0); /* flags */
1172 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1173 : /* reactionCount: flags=0, reaction = reactionEmoji, count=5 */
1174 2 : tl_write_uint32(&w, 0xa3d1cb80u);
1175 2 : tl_write_uint32(&w, 0);
1176 2 : tl_write_uint32(&w, 0x1b2286b8u);
1177 2 : tl_write_string(&w, "\xf0\x9f\x94\xa5");
1178 2 : tl_write_int32 (&w, 5);
1179 : /* reactionCount with chosen_order flag + reactionCustomEmoji */
1180 2 : tl_write_uint32(&w, 0xa3d1cb80u);
1181 2 : tl_write_uint32(&w, (1u << 0));
1182 2 : tl_write_int32 (&w, 1);
1183 2 : tl_write_uint32(&w, 0x8935fc73u);
1184 2 : tl_write_int64 (&w, 424242LL);
1185 2 : tl_write_int32 (&w, 2);
1186 2 : TlReader r = tl_reader_init(w.data, w.len);
1187 2 : ASSERT(tl_skip_message_reactions(&r) == 0, "reactions walked");
1188 2 : tl_writer_free(&w);
1189 :
1190 : /* Reactions with unknown inner Reaction CRC. */
1191 2 : tl_writer_init(&w);
1192 2 : tl_write_uint32(&w, 0x4f2b9479u);
1193 2 : tl_write_uint32(&w, 0);
1194 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1195 2 : tl_write_uint32(&w, 0xa3d1cb80u);
1196 2 : tl_write_uint32(&w, 0);
1197 2 : tl_write_uint32(&w, 0xFF00FFBBu); /* unknown reaction */
1198 2 : tl_write_int32 (&w, 1);
1199 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1200 2 : ASSERT(tl_skip_message_reactions(&r2) == -1,
1201 : "unknown reaction inner CRC rejected");
1202 2 : tl_writer_free(&w);
1203 :
1204 : /* Reactions with recent_reactions (flags.1) — production bails. */
1205 2 : tl_writer_init(&w);
1206 2 : tl_write_uint32(&w, 0x4f2b9479u);
1207 2 : tl_write_uint32(&w, (1u << 1)); /* recent_reactions present */
1208 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1209 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1210 2 : ASSERT(tl_skip_message_reactions(&r3) == -1,
1211 : "recent_reactions bail");
1212 2 : tl_writer_free(&w);
1213 :
1214 : /* MessageReplies full: flags=0b1111 with all optionals. */
1215 2 : tl_writer_init(&w);
1216 2 : tl_write_uint32(&w, 0x83d60fc2u);
1217 2 : tl_write_uint32(&w, 0xF); /* all four bits */
1218 2 : tl_write_int32 (&w, 10); /* replies */
1219 2 : tl_write_int32 (&w, 100); /* replies_pts */
1220 : /* recent_repliers (flags.1): Vector<Peer> */
1221 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1222 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 42LL);
1223 2 : tl_write_int64 (&w, 9999LL); /* channel_id (flags.0) */
1224 2 : tl_write_int32 (&w, 20); /* max_id (flags.2) */
1225 2 : tl_write_int32 (&w, 5); /* read_max_id (flags.3) */
1226 2 : TlReader r4 = tl_reader_init(w.data, w.len);
1227 2 : ASSERT(tl_skip_message_replies(&r4) == 0, "replies walked");
1228 2 : tl_writer_free(&w);
1229 :
1230 : /* Unknown messageReplies CRC */
1231 2 : tl_writer_init(&w);
1232 2 : tl_write_uint32(&w, 0xFF00FFCCu);
1233 2 : tl_write_uint32(&w, 0);
1234 2 : TlReader r5 = tl_reader_init(w.data, w.len);
1235 2 : ASSERT(tl_skip_message_replies(&r5) == -1, "unknown replies rejected");
1236 2 : tl_writer_free(&w);
1237 : }
1238 :
1239 : /* ---- Photo MessageMedia round-trip through tl_skip_message_media ---- */
1240 2 : static void test_media_photo_and_document(void) {
1241 : /* messageMediaPhoto with a photo inner object. */
1242 2 : TlWriter w; tl_writer_init(&w);
1243 2 : tl_write_uint32(&w, 0x695150d7u); /* messageMediaPhoto */
1244 2 : tl_write_uint32(&w, (1u << 0)); /* flags.0 → photo present */
1245 : /* photo_full */
1246 2 : tl_write_uint32(&w, 0xfb197a65u);
1247 2 : tl_write_uint32(&w, 0);
1248 2 : tl_write_int64 (&w, 1LL);
1249 2 : tl_write_int64 (&w, 2LL);
1250 2 : tl_write_bytes (&w, (const unsigned char *)"", 0);
1251 2 : tl_write_int32 (&w, 1700000000);
1252 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1253 2 : tl_write_uint32(&w, 0x75c78e60u);
1254 2 : tl_write_string(&w, "y");
1255 2 : tl_write_int32 (&w, 1280); tl_write_int32(&w, 720); tl_write_int32(&w, 12345);
1256 2 : tl_write_int32 (&w, 2); /* dc_id */
1257 2 : TlReader r = tl_reader_init(w.data, w.len);
1258 2 : MediaInfo mi = {0};
1259 2 : ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "media photo walked");
1260 2 : ASSERT(mi.kind == MEDIA_PHOTO, "kind=PHOTO");
1261 2 : ASSERT(mi.photo_id == 1LL, "photo_id captured");
1262 2 : tl_writer_free(&w);
1263 :
1264 : /* messageMediaDocument with an inner document. */
1265 2 : tl_writer_init(&w);
1266 2 : tl_write_uint32(&w, 0x4cf4d72du);
1267 2 : tl_write_uint32(&w, (1u << 0)); /* document present */
1268 2 : tl_write_uint32(&w, 0x8fd4c4d8u); /* document */
1269 2 : tl_write_uint32(&w, 0); /* flags */
1270 2 : tl_write_int64 (&w, 111LL); /* id */
1271 2 : tl_write_int64 (&w, 222LL); /* access_hash */
1272 2 : tl_write_bytes (&w, (const unsigned char *)"r", 1);
1273 2 : tl_write_int32 (&w, 1700000000); /* date */
1274 2 : tl_write_string(&w, "video/mp4");
1275 2 : tl_write_int64 (&w, 1024LL); /* size */
1276 2 : tl_write_int32 (&w, 4); /* dc_id */
1277 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1278 : /* documentAttributeFilename */
1279 2 : tl_write_uint32(&w, 0x15590068u);
1280 2 : tl_write_string(&w, "movie.mp4");
1281 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1282 2 : MediaInfo mi2 = {0};
1283 2 : ASSERT(tl_skip_message_media_ex(&r2, &mi2) == 0, "media document walked");
1284 2 : ASSERT(mi2.kind == MEDIA_DOCUMENT, "kind=DOCUMENT");
1285 2 : ASSERT(mi2.document_id == 111LL, "document_id captured");
1286 2 : ASSERT(strcmp(mi2.document_mime, "video/mp4") == 0, "mime captured");
1287 2 : ASSERT(strcmp(mi2.document_filename, "movie.mp4") == 0, "filename");
1288 2 : tl_writer_free(&w);
1289 : }
1290 :
1291 : /* ---- Empty MessageReplies path (flags=0) ---- */
1292 2 : static void test_message_replies_empty(void) {
1293 2 : TlWriter w; tl_writer_init(&w);
1294 2 : tl_write_uint32(&w, 0x83d60fc2u);
1295 2 : tl_write_uint32(&w, 0); /* flags */
1296 2 : tl_write_int32 (&w, 0); /* replies */
1297 2 : tl_write_int32 (&w, 0); /* replies_pts */
1298 2 : TlReader r = tl_reader_init(w.data, w.len);
1299 2 : ASSERT(tl_skip_message_replies(&r) == 0, "empty replies walked");
1300 2 : ASSERT(r.pos == r.len, "reader consumed");
1301 2 : tl_writer_free(&w);
1302 : }
1303 :
1304 : /* ---- messageMediaGame → skip_game ---- */
1305 2 : static void test_media_game(void) {
1306 : /* messageMediaGame + game#bdf9653b flags=0 (no document). */
1307 2 : TlWriter w; tl_writer_init(&w);
1308 2 : tl_write_uint32(&w, 0xfdb19008u); /* messageMediaGame */
1309 : /* game#bdf9653b flags:# id:long access_hash:long short_name title desc photo */
1310 2 : tl_write_uint32(&w, 0xbdf9653bu);
1311 2 : tl_write_uint32(&w, 0); /* flags */
1312 2 : tl_write_int64 (&w, 5000LL); /* id */
1313 2 : tl_write_int64 (&w, 6000LL); /* access_hash */
1314 2 : tl_write_string(&w, "snake"); /* short_name */
1315 2 : tl_write_string(&w, "Snake"); /* title */
1316 2 : tl_write_string(&w, "Classic snake"); /* description */
1317 : /* photo: photoEmpty */
1318 2 : tl_write_uint32(&w, 0x2331b22du);
1319 2 : tl_write_int64 (&w, 0LL);
1320 2 : TlReader r = tl_reader_init(w.data, w.len);
1321 2 : MediaInfo mi = {0};
1322 2 : ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "mediaGame walked");
1323 2 : ASSERT(mi.kind == MEDIA_GAME, "kind=GAME");
1324 2 : ASSERT(r.pos == r.len, "reader consumed game");
1325 2 : tl_writer_free(&w);
1326 :
1327 : /* game with document (flags.0). */
1328 2 : tl_writer_init(&w);
1329 2 : tl_write_uint32(&w, 0xfdb19008u);
1330 2 : tl_write_uint32(&w, 0xbdf9653bu);
1331 2 : tl_write_uint32(&w, (1u << 0)); /* flags.0 → document present */
1332 2 : tl_write_int64 (&w, 7000LL);
1333 2 : tl_write_int64 (&w, 8000LL);
1334 2 : tl_write_string(&w, "pong");
1335 2 : tl_write_string(&w, "Pong");
1336 2 : tl_write_string(&w, "Old-school pong");
1337 2 : tl_write_uint32(&w, 0x2331b22du); /* photoEmpty */
1338 2 : tl_write_int64 (&w, 0LL);
1339 : /* documentEmpty */
1340 2 : tl_write_uint32(&w, 0x36f8c871u);
1341 2 : tl_write_int64 (&w, 42LL);
1342 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1343 2 : ASSERT(tl_skip_message_media_ex(&r2, NULL) == 0, "game+doc walked");
1344 2 : ASSERT(r2.pos == r2.len, "reader consumed game+doc");
1345 2 : tl_writer_free(&w);
1346 :
1347 : /* unknown game CRC → -1 */
1348 2 : tl_writer_init(&w);
1349 2 : tl_write_uint32(&w, 0xfdb19008u);
1350 2 : tl_write_uint32(&w, 0xFF00ABCDu);
1351 2 : tl_write_uint32(&w, 0);
1352 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1353 2 : ASSERT(tl_skip_message_media_ex(&r3, NULL) == -1,
1354 : "unknown game CRC rejected");
1355 2 : tl_writer_free(&w);
1356 : }
1357 :
1358 : /* ---- messageMediaInvoice → skip_web_document + skip_message_extended_media ---- */
1359 2 : static void test_media_invoice(void) {
1360 : /* Invoice with photo (webDocument, flags.0) — no receipt, no ext-media. */
1361 2 : TlWriter w; tl_writer_init(&w);
1362 2 : tl_write_uint32(&w, 0xf6a548d3u); /* messageMediaInvoice */
1363 2 : tl_write_uint32(&w, (1u << 0)); /* flags.0 → photo present */
1364 2 : tl_write_string(&w, "Ticket"); /* title */
1365 2 : tl_write_string(&w, "One event entry"); /* description */
1366 : /* webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attrs:Vector */
1367 2 : tl_write_uint32(&w, 0xf9c8bcc6u);
1368 2 : tl_write_string(&w, "https://img.example.com/t.jpg");
1369 2 : tl_write_int32 (&w, 1024); /* size */
1370 2 : tl_write_string(&w, "image/jpeg");
1371 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1372 : /* currency + amount + start_param */
1373 2 : tl_write_string(&w, "USD");
1374 2 : tl_write_int64 (&w, 500LL);
1375 2 : tl_write_string(&w, "start123");
1376 2 : TlReader r = tl_reader_init(w.data, w.len);
1377 2 : MediaInfo mi = {0};
1378 2 : ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "invoice+webDoc walked");
1379 2 : ASSERT(mi.kind == MEDIA_INVOICE, "kind=INVOICE");
1380 2 : ASSERT(r.pos == r.len, "reader consumed invoice");
1381 2 : tl_writer_free(&w);
1382 :
1383 : /* Invoice with webDocument (full, has access_hash), no ext-media. */
1384 2 : tl_writer_init(&w);
1385 2 : tl_write_uint32(&w, 0xf6a548d3u);
1386 2 : tl_write_uint32(&w, (1u << 0));
1387 2 : tl_write_string(&w, "Prod");
1388 2 : tl_write_string(&w, "Desc");
1389 : /* webDocument#1c570ed1 url access_hash size mime attrs */
1390 2 : tl_write_uint32(&w, 0x1c570ed1u);
1391 2 : tl_write_string(&w, "https://cdn.tg/img.png");
1392 2 : tl_write_int64 (&w, 999LL); /* access_hash */
1393 2 : tl_write_int32 (&w, 2048);
1394 2 : tl_write_string(&w, "image/png");
1395 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1396 2 : tl_write_uint32(&w, 0x11b58939u); /* documentAttributeAnimated */
1397 2 : tl_write_string(&w, "EUR");
1398 2 : tl_write_int64 (&w, 1000LL);
1399 2 : tl_write_string(&w, "");
1400 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1401 2 : ASSERT(tl_skip_message_media_ex(&r2, NULL) == 0, "invoice+webDoc full");
1402 2 : ASSERT(r2.pos == r2.len, "consumed invoice full");
1403 2 : tl_writer_free(&w);
1404 :
1405 : /* Unknown webDocument CRC → -1 */
1406 2 : tl_writer_init(&w);
1407 2 : tl_write_uint32(&w, 0xf6a548d3u);
1408 2 : tl_write_uint32(&w, (1u << 0));
1409 2 : tl_write_string(&w, "T"); tl_write_string(&w, "D");
1410 2 : tl_write_uint32(&w, 0xFF00FEFEu); /* unknown webDocument */
1411 2 : tl_write_string(&w, "USD"); tl_write_int64(&w, 0LL); tl_write_string(&w, "");
1412 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1413 2 : ASSERT(tl_skip_message_media_ex(&r3, NULL) == -1,
1414 : "unknown webDocument CRC rejected");
1415 2 : tl_writer_free(&w);
1416 : }
1417 :
1418 : /* ---- messageMediaPaidMedia → skip_message_extended_media ---- */
1419 2 : static void test_media_paid_media(void) {
1420 : /* Paid media with one messageExtendedMediaPreview (flags=0). */
1421 2 : TlWriter w; tl_writer_init(&w);
1422 2 : tl_write_uint32(&w, 0xa8852491u); /* messageMediaPaidMedia */
1423 2 : tl_write_int64 (&w, 25LL); /* stars_amount */
1424 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1425 : /* messageExtendedMediaPreview#ad628cc8 flags=0 (no w/h, no thumb, no dur) */
1426 2 : tl_write_uint32(&w, 0xad628cc8u);
1427 2 : tl_write_uint32(&w, 0);
1428 2 : TlReader r = tl_reader_init(w.data, w.len);
1429 2 : MediaInfo mi = {0};
1430 2 : ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "paidMedia preview walked");
1431 2 : ASSERT(mi.kind == MEDIA_PAID, "kind=PAID");
1432 2 : ASSERT(r.pos == r.len, "reader consumed paid preview");
1433 2 : tl_writer_free(&w);
1434 :
1435 : /* Paid media preview with all flags set (w+h + thumb + video_duration). */
1436 2 : tl_writer_init(&w);
1437 2 : tl_write_uint32(&w, 0xa8852491u);
1438 2 : tl_write_int64 (&w, 50LL);
1439 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1440 2 : tl_write_uint32(&w, 0xad628cc8u);
1441 2 : tl_write_uint32(&w, 0x7u); /* flags: bits 0,1,2 */
1442 2 : tl_write_int32 (&w, 640); tl_write_int32(&w, 480);
1443 : /* thumb: photoSizeEmpty */
1444 2 : tl_write_uint32(&w, 0x0e17e23cu);
1445 2 : tl_write_string(&w, "s");
1446 2 : tl_write_int32 (&w, 10); /* video_duration */
1447 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1448 2 : ASSERT(tl_skip_message_media_ex(&r2, NULL) == 0, "paid preview all-flags");
1449 2 : ASSERT(r2.pos == r2.len, "consumed paid preview all-flags");
1450 2 : tl_writer_free(&w);
1451 :
1452 : /* Paid with messageExtendedMedia (wraps another MessageMedia). */
1453 2 : tl_writer_init(&w);
1454 2 : tl_write_uint32(&w, 0xa8852491u);
1455 2 : tl_write_int64 (&w, 10LL);
1456 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1457 2 : tl_write_uint32(&w, 0xee479c64u); /* messageExtendedMedia */
1458 : /* wrapped: messageMediaEmpty */
1459 2 : tl_write_uint32(&w, 0x3ded6320u);
1460 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1461 2 : ASSERT(tl_skip_message_media_ex(&r3, NULL) == 0, "extMedia wrapper walked");
1462 2 : ASSERT(r3.pos == r3.len, "consumed extMedia");
1463 2 : tl_writer_free(&w);
1464 :
1465 : /* Unknown messageExtendedMedia CRC → -1 */
1466 2 : tl_writer_init(&w);
1467 2 : tl_write_uint32(&w, 0xa8852491u);
1468 2 : tl_write_int64 (&w, 5LL);
1469 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1470 2 : tl_write_uint32(&w, 0xFF00FFABu);
1471 2 : tl_write_uint32(&w, 0);
1472 2 : TlReader r4 = tl_reader_init(w.data, w.len);
1473 2 : ASSERT(tl_skip_message_media_ex(&r4, NULL) == -1,
1474 : "unknown extMedia CRC rejected");
1475 2 : tl_writer_free(&w);
1476 : }
1477 :
1478 : /* ---- messageMediaStory → skip_story_item, skip_story_views,
1479 : * skip_privacy_rule, skip_media_area, skip_geo_point_address,
1480 : * skip_media_area_coordinates, skip_story_fwd_header ---- */
1481 2 : static void test_media_story(void) {
1482 : /* Story with storyItemDeleted (simplest). */
1483 2 : TlWriter w; tl_writer_init(&w);
1484 2 : tl_write_uint32(&w, 0x68cb6283u); /* messageMediaStory */
1485 2 : tl_write_uint32(&w, (1u << 0)); /* flags.0 → story present */
1486 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 1LL);
1487 2 : tl_write_int32 (&w, 101); /* id */
1488 : /* storyItemDeleted#51e6ee4f id:int */
1489 2 : tl_write_uint32(&w, 0x51e6ee4fu);
1490 2 : tl_write_int32 (&w, 101);
1491 2 : TlReader r = tl_reader_init(w.data, w.len);
1492 2 : MediaInfo mi = {0};
1493 2 : ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "story deleted walked");
1494 2 : ASSERT(mi.kind == MEDIA_STORY, "kind=STORY");
1495 2 : tl_writer_free(&w);
1496 :
1497 : /* storyItemSkipped */
1498 2 : tl_writer_init(&w);
1499 2 : tl_write_uint32(&w, 0x68cb6283u);
1500 2 : tl_write_uint32(&w, (1u << 0));
1501 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 2LL);
1502 2 : tl_write_int32 (&w, 200);
1503 2 : tl_write_uint32(&w, 0xffadc913u); /* storyItemSkipped */
1504 2 : tl_write_uint32(&w, 0); /* flags */
1505 2 : tl_write_int32 (&w, 200); /* id */
1506 2 : tl_write_int32 (&w, 1700001000); /* date */
1507 2 : tl_write_int32 (&w, 1700087400); /* expire_date */
1508 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1509 2 : ASSERT(tl_skip_message_media_ex(&r2, NULL) == 0, "story skipped walked");
1510 2 : ASSERT(r2.pos == r2.len, "reader consumed story skipped");
1511 2 : tl_writer_free(&w);
1512 :
1513 : /* Full storyItem with caption + entities + mediaAreaVenue + privacyRule
1514 : * + storyViews + storyFwdHeader. Drives all the 0% static helpers. */
1515 2 : tl_writer_init(&w);
1516 2 : tl_write_uint32(&w, 0x68cb6283u);
1517 2 : tl_write_uint32(&w, (1u << 0));
1518 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 3LL);
1519 2 : tl_write_int32 (&w, 300);
1520 : /* storyItem#79b26a24 */
1521 2 : tl_write_uint32(&w, 0x79b26a24u);
1522 : /* flags: bit 0=caption, 1=entities, 2=privacy, 3=views, 14=media_areas,
1523 : * 17=fwd_from (storyFwdHeader) */
1524 2 : uint32_t sflags = (1u << 0) | (1u << 1) | (1u << 2) | (1u << 3) | (1u << 14) | (1u << 17);
1525 2 : tl_write_uint32(&w, sflags);
1526 2 : tl_write_int32 (&w, 300); /* id */
1527 2 : tl_write_int32 (&w, 1700001000); /* date */
1528 : /* fwd_from: storyFwdHeader#b826e150 flags=0b110 (from_name + story_id) */
1529 2 : tl_write_uint32(&w, 0xb826e150u);
1530 2 : tl_write_uint32(&w, (1u << 1) | (1u << 2));
1531 2 : tl_write_string(&w, "Original Poster"); /* from_name */
1532 2 : tl_write_int32 (&w, 77); /* story_id */
1533 2 : tl_write_int32 (&w, 1700087400); /* expire_date */
1534 2 : tl_write_string(&w, "Hello from story"); /* caption */
1535 : /* entities: Vector<MessageEntity> (bold) */
1536 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1537 2 : tl_write_uint32(&w, 0xbd610bc9u); /* messageEntityBold */
1538 2 : tl_write_int32 (&w, 0); tl_write_int32(&w, 5);
1539 : /* media: messageMediaEmpty */
1540 2 : tl_write_uint32(&w, 0x3ded6320u);
1541 : /* media_areas (flags.14): Vector<MediaArea> with two variants */
1542 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1543 : /* mediaAreaUrl: coordinates + url */
1544 2 : tl_write_uint32(&w, 0x37381085u);
1545 : /* mediaAreaCoordinates#03d1ea4e flags=0 x y w h rotation */
1546 2 : tl_write_uint32(&w, 0x03d1ea4eu);
1547 2 : tl_write_uint32(&w, 0); /* flags */
1548 2 : double dvals[5] = {0.1, 0.2, 0.3, 0.4, 0.0};
1549 12 : for (int i = 0; i < 5; i++) {
1550 10 : uint64_t bits; __builtin_memcpy(&bits, &dvals[i], 8);
1551 10 : tl_write_uint32(&w, (uint32_t)bits);
1552 10 : tl_write_uint32(&w, (uint32_t)(bits >> 32));
1553 : }
1554 2 : tl_write_string(&w, "https://example.com");
1555 : /* mediaAreaGeoPoint with flags.0 → geoPointAddress */
1556 2 : tl_write_uint32(&w, 0xdf8b3b22u);
1557 2 : tl_write_uint32(&w, (1u << 0)); /* flags.0 → address present */
1558 : /* coordinates */
1559 2 : tl_write_uint32(&w, 0x03d1ea4eu);
1560 2 : tl_write_uint32(&w, 0);
1561 12 : for (int i = 0; i < 5; i++) {
1562 10 : uint64_t bits; __builtin_memcpy(&bits, &dvals[i], 8);
1563 10 : tl_write_uint32(&w, (uint32_t)bits);
1564 10 : tl_write_uint32(&w, (uint32_t)(bits >> 32));
1565 : }
1566 : /* geoPoint#b2a2f663 flags=0 long lat access_hash */
1567 2 : tl_write_uint32(&w, 0xb2a2f663u);
1568 2 : tl_write_uint32(&w, 0);
1569 : {
1570 2 : double lng = 13.4, lat = 52.5, acc = 0.0; uint64_t b;
1571 2 : __builtin_memcpy(&b, &lng, 8);
1572 2 : tl_write_uint32(&w, (uint32_t)b); tl_write_uint32(&w, (uint32_t)(b>>32));
1573 2 : __builtin_memcpy(&b, &lat, 8);
1574 2 : tl_write_uint32(&w, (uint32_t)b); tl_write_uint32(&w, (uint32_t)(b>>32));
1575 : (void)acc;
1576 2 : tl_write_int64(&w, 0LL);
1577 : }
1578 : /* geoPointAddress#de4c5d93 flags=0b111 country state city street */
1579 2 : tl_write_uint32(&w, 0xde4c5d93u);
1580 2 : tl_write_uint32(&w, 0x7u);
1581 2 : tl_write_string(&w, "DE");
1582 2 : tl_write_string(&w, "Berlin");
1583 2 : tl_write_string(&w, "Berlin");
1584 2 : tl_write_string(&w, "Unter den Linden");
1585 : /* privacy: Vector<PrivacyRule> with two variants (no-payload + users) */
1586 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1587 2 : tl_write_uint32(&w, 0xfffe1bacu); /* privacyValueAllowContacts */
1588 2 : tl_write_uint32(&w, 0xb8905fb2u); /* privacyValueAllowUsers */
1589 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1590 2 : tl_write_int64 (&w, 11LL); tl_write_int64(&w, 22LL);
1591 : /* views: storyViews#8d595cd6 flags=0b11101 */
1592 2 : tl_write_uint32(&w, 0x8d595cd6u);
1593 2 : uint32_t vflags = (1u << 0) | (1u << 2) | (1u << 3) | (1u << 4);
1594 2 : tl_write_uint32(&w, vflags);
1595 2 : tl_write_int32 (&w, 100); /* views_count */
1596 2 : tl_write_int32 (&w, 5); /* forwards_count */
1597 : /* reactions: Vector<ReactionCount> */
1598 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1599 2 : tl_write_uint32(&w, 0xa3d1cb80u);
1600 2 : tl_write_uint32(&w, 0);
1601 2 : tl_write_uint32(&w, 0x79f5d419u); /* reactionEmpty */
1602 2 : tl_write_int32 (&w, 3);
1603 2 : tl_write_int32 (&w, 10); /* reactions_count */
1604 : /* recent_viewers: Vector<long> */
1605 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1606 2 : tl_write_int64 (&w, 99LL);
1607 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1608 2 : ASSERT(tl_skip_message_media_ex(&r3, NULL) == 0, "full story walked");
1609 2 : ASSERT(r3.pos == r3.len, "reader consumed full story");
1610 2 : tl_writer_free(&w);
1611 :
1612 : /* storyFwdHeader with from peer (flags.0) + story_id (flags.2). */
1613 2 : tl_writer_init(&w);
1614 2 : tl_write_uint32(&w, 0x68cb6283u);
1615 2 : tl_write_uint32(&w, (1u << 0));
1616 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 5LL);
1617 2 : tl_write_int32 (&w, 400);
1618 2 : tl_write_uint32(&w, 0x79b26a24u);
1619 2 : uint32_t sfwdflags = (1u << 17);
1620 2 : tl_write_uint32(&w, sfwdflags);
1621 2 : tl_write_int32 (&w, 400);
1622 2 : tl_write_int32 (&w, 1700001000);
1623 2 : tl_write_uint32(&w, 0xb826e150u);
1624 2 : tl_write_uint32(&w, (1u << 0) | (1u << 2)); /* from_peer + story_id */
1625 2 : tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 10LL);
1626 2 : tl_write_int32 (&w, 55);
1627 2 : tl_write_int32 (&w, 1700087400);
1628 2 : tl_write_uint32(&w, 0x3ded6320u); /* media: empty */
1629 2 : TlReader r4 = tl_reader_init(w.data, w.len);
1630 2 : ASSERT(tl_skip_message_media_ex(&r4, NULL) == 0,
1631 : "story fwdHeader peer+storyId walked");
1632 2 : ASSERT(r4.pos == r4.len, "consumed story fwdHeader");
1633 2 : tl_writer_free(&w);
1634 :
1635 : /* Unknown storyItem CRC → -1 */
1636 2 : tl_writer_init(&w);
1637 2 : tl_write_uint32(&w, 0x68cb6283u);
1638 2 : tl_write_uint32(&w, (1u << 0));
1639 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 6LL);
1640 2 : tl_write_int32 (&w, 500);
1641 2 : tl_write_uint32(&w, 0xFF00AAAAu); /* unknown storyItem */
1642 2 : tl_write_uint32(&w, 0);
1643 2 : TlReader r5 = tl_reader_init(w.data, w.len);
1644 2 : ASSERT(tl_skip_message_media_ex(&r5, NULL) == -1,
1645 : "unknown storyItem rejected");
1646 2 : tl_writer_free(&w);
1647 :
1648 : /* mediaAreaSuggestedReaction */
1649 2 : tl_writer_init(&w);
1650 2 : tl_write_uint32(&w, 0x68cb6283u);
1651 2 : tl_write_uint32(&w, (1u << 0));
1652 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 7LL);
1653 2 : tl_write_int32 (&w, 600);
1654 2 : tl_write_uint32(&w, 0x79b26a24u);
1655 2 : uint32_t sarf = (1u << 14);
1656 2 : tl_write_uint32(&w, sarf);
1657 2 : tl_write_int32 (&w, 600); tl_write_int32(&w, 1700001000);
1658 2 : tl_write_int32 (&w, 1700087400);
1659 2 : tl_write_uint32(&w, 0x3ded6320u);
1660 : /* media_areas Vector with mediaAreaSuggestedReaction */
1661 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1662 2 : tl_write_uint32(&w, 0x14455871u); /* mediaAreaSuggestedReaction */
1663 2 : tl_write_uint32(&w, 0); /* flags */
1664 : /* coordinates */
1665 2 : tl_write_uint32(&w, 0x03d1ea4eu);
1666 2 : tl_write_uint32(&w, 0);
1667 12 : for (int i = 0; i < 5; i++) {
1668 10 : uint64_t bits; __builtin_memcpy(&bits, &dvals[i], 8);
1669 10 : tl_write_uint32(&w, (uint32_t)bits); tl_write_uint32(&w, (uint32_t)(bits>>32));
1670 : }
1671 2 : tl_write_uint32(&w, 0x79f5d419u); /* reactionEmpty */
1672 2 : TlReader r6 = tl_reader_init(w.data, w.len);
1673 2 : ASSERT(tl_skip_message_media_ex(&r6, NULL) == 0,
1674 : "mediaAreaSuggestedReaction walked");
1675 2 : ASSERT(r6.pos == r6.len, "consumed suggestedReaction");
1676 2 : tl_writer_free(&w);
1677 :
1678 : /* mediaAreaChannelPost, mediaAreaWeather, mediaAreaStarGift, mediaAreaVenue */
1679 2 : tl_writer_init(&w);
1680 2 : tl_write_uint32(&w, 0x68cb6283u);
1681 2 : tl_write_uint32(&w, (1u << 0));
1682 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 8LL);
1683 2 : tl_write_int32 (&w, 700);
1684 2 : tl_write_uint32(&w, 0x79b26a24u);
1685 2 : uint32_t s4f = (1u << 14);
1686 2 : tl_write_uint32(&w, s4f);
1687 2 : tl_write_int32 (&w, 700); tl_write_int32(&w, 1700001000);
1688 2 : tl_write_int32 (&w, 1700087400);
1689 2 : tl_write_uint32(&w, 0x3ded6320u);
1690 : /* media_areas: Vector with 4 areas */
1691 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 4);
1692 : /* helper macro: write bare coordinates (flags=0) */
1693 : #define WRITE_COORDS(ww) do { \
1694 : tl_write_uint32(ww, 0x03d1ea4eu); tl_write_uint32(ww, 0); \
1695 : double _d[5] = {0.1,0.2,0.3,0.4,0.0}; \
1696 : for(int _i=0;_i<5;_i++){uint64_t _b; __builtin_memcpy(&_b,&_d[_i],8); \
1697 : tl_write_uint32(ww,(uint32_t)_b); tl_write_uint32(ww,(uint32_t)(_b>>32));} \
1698 : } while(0)
1699 : /* mediaAreaChannelPost */
1700 2 : tl_write_uint32(&w, 0x770416afu);
1701 12 : WRITE_COORDS(&w);
1702 2 : tl_write_int64(&w, 12345LL); /* channel_id */
1703 2 : tl_write_int32(&w, 777); /* msg_id */
1704 : /* mediaAreaWeather */
1705 2 : tl_write_uint32(&w, 0x49a6549cu);
1706 12 : WRITE_COORDS(&w);
1707 2 : tl_write_string(&w, "\xe2\x98\x80\xef\xb8\x8f"); /* emoji */
1708 2 : double temp = 22.5; uint64_t tbits; __builtin_memcpy(&tbits, &temp, 8);
1709 2 : tl_write_uint32(&w, (uint32_t)tbits); tl_write_uint32(&w, (uint32_t)(tbits>>32));
1710 2 : tl_write_int32(&w, 0xFFFFFFu); /* color */
1711 : /* mediaAreaStarGift */
1712 2 : tl_write_uint32(&w, 0x5787686du);
1713 12 : WRITE_COORDS(&w);
1714 2 : tl_write_string(&w, "gift-slug-42");
1715 : /* mediaAreaVenue */
1716 2 : tl_write_uint32(&w, 0xbe82db9cu);
1717 12 : WRITE_COORDS(&w);
1718 2 : tl_write_uint32(&w, 0x1117dd5fu); /* geoPointEmpty */
1719 2 : tl_write_string(&w, "Cafe");
1720 2 : tl_write_string(&w, "Main St 1");
1721 2 : tl_write_string(&w, "foursquare");
1722 2 : tl_write_string(&w, "v42");
1723 2 : tl_write_string(&w, "cafe");
1724 2 : TlReader r7 = tl_reader_init(w.data, w.len);
1725 2 : ASSERT(tl_skip_message_media_ex(&r7, NULL) == 0, "4 media area variants");
1726 2 : ASSERT(r7.pos == r7.len, "consumed 4 media areas");
1727 2 : tl_writer_free(&w);
1728 : #undef WRITE_COORDS
1729 :
1730 : /* Unknown mediaArea CRC → -1 */
1731 2 : tl_writer_init(&w);
1732 2 : tl_write_uint32(&w, 0x68cb6283u);
1733 2 : tl_write_uint32(&w, (1u << 0));
1734 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 9LL);
1735 2 : tl_write_int32 (&w, 800);
1736 2 : tl_write_uint32(&w, 0x79b26a24u);
1737 2 : tl_write_uint32(&w, (1u << 14));
1738 2 : tl_write_int32 (&w, 800); tl_write_int32(&w, 1700001000);
1739 2 : tl_write_int32 (&w, 1700087400);
1740 2 : tl_write_uint32(&w, 0x3ded6320u);
1741 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1742 2 : tl_write_uint32(&w, 0xFF00DDDDu); /* unknown mediaArea */
1743 2 : tl_write_uint32(&w, 0);
1744 2 : TlReader r8 = tl_reader_init(w.data, w.len);
1745 2 : ASSERT(tl_skip_message_media_ex(&r8, NULL) == -1, "unknown mediaArea rejected");
1746 2 : tl_writer_free(&w);
1747 :
1748 : /* Unknown privacyRule CRC → -1 */
1749 2 : tl_writer_init(&w);
1750 2 : tl_write_uint32(&w, 0x68cb6283u);
1751 2 : tl_write_uint32(&w, (1u << 0));
1752 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 10LL);
1753 2 : tl_write_int32 (&w, 900);
1754 2 : tl_write_uint32(&w, 0x79b26a24u);
1755 2 : tl_write_uint32(&w, (1u << 2)); /* only privacy flag */
1756 2 : tl_write_int32 (&w, 900); tl_write_int32(&w, 1700001000);
1757 2 : tl_write_int32 (&w, 1700087400);
1758 2 : tl_write_uint32(&w, 0x3ded6320u);
1759 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1760 2 : tl_write_uint32(&w, 0xFF00CCCCu); /* unknown privacyRule */
1761 2 : TlReader r9 = tl_reader_init(w.data, w.len);
1762 2 : ASSERT(tl_skip_message_media_ex(&r9, NULL) == -1, "unknown privacyRule rejected");
1763 2 : tl_writer_free(&w);
1764 :
1765 : /* storyViews unknown CRC → -1 */
1766 2 : tl_writer_init(&w);
1767 2 : tl_write_uint32(&w, 0x68cb6283u);
1768 2 : tl_write_uint32(&w, (1u << 0));
1769 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 11LL);
1770 2 : tl_write_int32 (&w, 1000);
1771 2 : tl_write_uint32(&w, 0x79b26a24u);
1772 2 : tl_write_uint32(&w, (1u << 3)); /* only views flag */
1773 2 : tl_write_int32 (&w, 1000); tl_write_int32(&w, 1700001000);
1774 2 : tl_write_int32 (&w, 1700087400);
1775 2 : tl_write_uint32(&w, 0x3ded6320u);
1776 2 : tl_write_uint32(&w, 0xFF00BBBBu); /* unknown storyViews */
1777 2 : tl_write_uint32(&w, 0);
1778 2 : TlReader r10 = tl_reader_init(w.data, w.len);
1779 2 : ASSERT(tl_skip_message_media_ex(&r10, NULL) == -1,
1780 : "unknown storyViews rejected");
1781 2 : tl_writer_free(&w);
1782 : }
1783 :
1784 : /* ---- messageMediaPoll → skip_poll_answer_voters ---- */
1785 2 : static void test_media_poll_with_results(void) {
1786 : /* Build a poll + results that exercises skip_poll_answer_voters. */
1787 2 : TlWriter w; tl_writer_init(&w);
1788 2 : tl_write_uint32(&w, 0x4bd6e798u); /* messageMediaPoll */
1789 : /* poll#58747131 flags=0 id question answers */
1790 2 : tl_write_uint32(&w, 0x58747131u);
1791 2 : tl_write_uint32(&w, 0); /* flags */
1792 2 : tl_write_int64 (&w, 77LL); /* id */
1793 : /* question: textWithEntities#751f3146 */
1794 2 : tl_write_uint32(&w, 0x751f3146u);
1795 2 : tl_write_string(&w, "Best fruit?");
1796 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1797 : /* answers: Vector<PollAnswer> — two answers */
1798 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1799 : /* PollAnswer#6ca9c2e9 text:TextWithEntities option:bytes */
1800 2 : tl_write_uint32(&w, 0x6ca9c2e9u);
1801 2 : tl_write_uint32(&w, 0x751f3146u); tl_write_string(&w, "Apple");
1802 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1803 2 : tl_write_bytes (&w, (const unsigned char *)"\x01", 1);
1804 2 : tl_write_uint32(&w, 0x6ca9c2e9u);
1805 2 : tl_write_uint32(&w, 0x751f3146u); tl_write_string(&w, "Banana");
1806 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1807 2 : tl_write_bytes (&w, (const unsigned char *)"\x02", 1);
1808 : /* PollResults#7adc669d flags=0b1110 (results + total_voters + recent) */
1809 2 : tl_write_uint32(&w, 0x7adc669du);
1810 2 : uint32_t rflags = (1u << 1) | (1u << 2) | (1u << 3);
1811 2 : tl_write_uint32(&w, rflags);
1812 : /* results: Vector<PollAnswerVoters> */
1813 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
1814 : /* PollAnswerVoters#3b6ddad2 flags option:bytes voters */
1815 2 : tl_write_uint32(&w, 0x3b6ddad2u);
1816 2 : tl_write_uint32(&w, 0); /* flags */
1817 2 : tl_write_bytes (&w, (const unsigned char *)"\x01", 1);
1818 2 : tl_write_int32 (&w, 30);
1819 2 : tl_write_uint32(&w, 0x3b6ddad2u);
1820 2 : tl_write_uint32(&w, 0);
1821 2 : tl_write_bytes (&w, (const unsigned char *)"\x02", 1);
1822 2 : tl_write_int32 (&w, 20);
1823 2 : tl_write_int32 (&w, 50); /* total_voters */
1824 : /* recent_voters: Vector<Peer> */
1825 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1826 2 : tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 42LL);
1827 2 : TlReader r = tl_reader_init(w.data, w.len);
1828 2 : MediaInfo mi = {0};
1829 2 : ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "poll+results walked");
1830 2 : ASSERT(mi.kind == MEDIA_POLL, "kind=POLL");
1831 2 : ASSERT(r.pos == r.len, "reader consumed poll");
1832 2 : tl_writer_free(&w);
1833 :
1834 : /* PollResults with solution (flags.4). */
1835 2 : tl_writer_init(&w);
1836 2 : tl_write_uint32(&w, 0x4bd6e798u);
1837 2 : tl_write_uint32(&w, 0x58747131u);
1838 2 : tl_write_uint32(&w, 0);
1839 2 : tl_write_int64 (&w, 78LL);
1840 2 : tl_write_uint32(&w, 0x751f3146u); tl_write_string(&w, "Q?");
1841 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1842 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1843 2 : tl_write_uint32(&w, 0x7adc669du);
1844 2 : tl_write_uint32(&w, (1u << 4)); /* solution flag */
1845 2 : tl_write_string(&w, "Answer is 42");
1846 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1847 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1848 2 : ASSERT(tl_skip_message_media_ex(&r2, NULL) == 0, "poll+solution walked");
1849 2 : tl_writer_free(&w);
1850 :
1851 : /* Bad PollAnswerVoters CRC → -1 */
1852 2 : tl_writer_init(&w);
1853 2 : tl_write_uint32(&w, 0x4bd6e798u);
1854 2 : tl_write_uint32(&w, 0x58747131u);
1855 2 : tl_write_uint32(&w, 0);
1856 2 : tl_write_int64 (&w, 79LL);
1857 2 : tl_write_uint32(&w, 0x751f3146u); tl_write_string(&w, "Q?");
1858 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1859 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1860 2 : tl_write_uint32(&w, 0x7adc669du);
1861 2 : tl_write_uint32(&w, (1u << 1));
1862 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
1863 2 : tl_write_uint32(&w, 0xFF00EEEEu); /* bad PollAnswerVoters */
1864 2 : tl_write_uint32(&w, 0);
1865 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1866 2 : ASSERT(tl_skip_message_media_ex(&r3, NULL) == -1,
1867 : "bad PollAnswerVoters rejected");
1868 2 : tl_writer_free(&w);
1869 : }
1870 :
1871 : /* ---- messageMediaWebPage → skip_webpage + deep nested page/block/rich ---- */
1872 2 : static void test_media_webpage_variants(void) {
1873 : /* webPageEmpty */
1874 2 : TlWriter w; tl_writer_init(&w);
1875 2 : tl_write_uint32(&w, 0xddf8c26eu); /* messageMediaWebPage */
1876 2 : tl_write_uint32(&w, 0); /* flags */
1877 2 : tl_write_uint32(&w, 0xeb1477e8u); /* webPageEmpty */
1878 2 : tl_write_uint32(&w, 0); /* flags */
1879 2 : tl_write_int64 (&w, 1LL); /* id */
1880 2 : TlReader r = tl_reader_init(w.data, w.len);
1881 2 : MediaInfo mi = {0};
1882 2 : ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "webPageEmpty walked");
1883 2 : ASSERT(mi.kind == MEDIA_WEBPAGE, "kind=WEBPAGE");
1884 2 : tl_writer_free(&w);
1885 :
1886 : /* webPagePending */
1887 2 : tl_writer_init(&w);
1888 2 : tl_write_uint32(&w, 0xddf8c26eu);
1889 2 : tl_write_uint32(&w, 0);
1890 2 : tl_write_uint32(&w, 0xb0d13e47u); /* webPagePending */
1891 2 : tl_write_uint32(&w, 0);
1892 2 : tl_write_int64 (&w, 2LL);
1893 2 : tl_write_int32 (&w, 1700000000); /* date */
1894 2 : TlReader r2 = tl_reader_init(w.data, w.len);
1895 2 : ASSERT(tl_skip_message_media_ex(&r2, NULL) == 0, "webPagePending walked");
1896 2 : tl_writer_free(&w);
1897 :
1898 : /* webPageNotModified (no flags) */
1899 2 : tl_writer_init(&w);
1900 2 : tl_write_uint32(&w, 0xddf8c26eu);
1901 2 : tl_write_uint32(&w, 0);
1902 2 : tl_write_uint32(&w, 0x7311ca11u); /* webPageNotModified */
1903 2 : tl_write_uint32(&w, (1u << 0)); /* flags.0 → cached_page_views */
1904 2 : tl_write_int32 (&w, 1234); /* cached_page_views */
1905 2 : TlReader r3 = tl_reader_init(w.data, w.len);
1906 2 : ASSERT(tl_skip_message_media_ex(&r3, NULL) == 0, "webPageNotModified walked");
1907 2 : tl_writer_free(&w);
1908 :
1909 : /* Full webPage — type + site + title + description + embed + author,
1910 : * flags 0,1,2,3,5,6,7,8 — no photo/document/page/attributes. */
1911 2 : tl_writer_init(&w);
1912 2 : tl_write_uint32(&w, 0xddf8c26eu);
1913 2 : tl_write_uint32(&w, 0);
1914 2 : tl_write_uint32(&w, 0xe89c45b2u); /* webPage */
1915 2 : uint32_t wpf = (1u<<0)|(1u<<1)|(1u<<2)|(1u<<3)|(1u<<5)|(1u<<6)|(1u<<7)|(1u<<8);
1916 2 : tl_write_uint32(&w, wpf);
1917 2 : tl_write_int64 (&w, 99LL);
1918 2 : tl_write_string(&w, "https://example.com");
1919 2 : tl_write_string(&w, "example.com");
1920 2 : tl_write_int32 (&w, 0); /* hash */
1921 2 : tl_write_string(&w, "article"); /* type */
1922 2 : tl_write_string(&w, "Example Site"); /* site_name */
1923 2 : tl_write_string(&w, "My Title"); /* title */
1924 2 : tl_write_string(&w, "Description here"); /* description */
1925 2 : tl_write_string(&w, "https://embed.example.com");
1926 2 : tl_write_string(&w, "text/html"); /* embed_type */
1927 2 : tl_write_int32 (&w, 640); tl_write_int32(&w, 360);
1928 2 : tl_write_int32 (&w, 120); /* duration */
1929 2 : tl_write_string(&w, "Author Name");
1930 2 : TlReader r4 = tl_reader_init(w.data, w.len);
1931 2 : ASSERT(tl_skip_message_media_ex(&r4, NULL) == 0, "full webPage walked");
1932 2 : ASSERT(r4.pos == r4.len, "consumed full webPage");
1933 2 : tl_writer_free(&w);
1934 :
1935 : /* webPage with cached_page (flags.10) — exercises skip_page + skip_page_block. */
1936 2 : tl_writer_init(&w);
1937 2 : tl_write_uint32(&w, 0xddf8c26eu);
1938 2 : tl_write_uint32(&w, 0);
1939 2 : tl_write_uint32(&w, 0xe89c45b2u);
1940 2 : uint32_t pgf = (1u << 10); /* cached_page */
1941 2 : tl_write_uint32(&w, pgf);
1942 2 : tl_write_int64 (&w, 100LL);
1943 2 : tl_write_string(&w, "https://telegraph.ph/article");
1944 2 : tl_write_string(&w, "telegraph.ph");
1945 2 : tl_write_int32 (&w, 0);
1946 : /* page#98657f0d flags=0 url blocks photos docs */
1947 2 : tl_write_uint32(&w, 0x98657f0du);
1948 2 : tl_write_uint32(&w, 0);
1949 2 : tl_write_string(&w, "https://telegraph.ph/article");
1950 : /* blocks: Vector<PageBlock> — cover several block variants */
1951 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 8);
1952 : /* pageBlockUnsupported */
1953 2 : tl_write_uint32(&w, 0x13567e8au);
1954 : /* pageBlockDivider */
1955 2 : tl_write_uint32(&w, 0xdb20b188u);
1956 : /* pageBlockTitle: rich text textPlain */
1957 2 : tl_write_uint32(&w, 0x70abc3fdu);
1958 2 : tl_write_uint32(&w, 0x744694e0u); tl_write_string(&w, "My Title");
1959 : /* pageBlockParagraph: textBold wrapping textPlain */
1960 2 : tl_write_uint32(&w, 0x467a0766u);
1961 2 : tl_write_uint32(&w, 0x6724abc4u); /* textBold */
1962 2 : tl_write_uint32(&w, 0x744694e0u); tl_write_string(&w, "bold");
1963 : /* pageBlockAnchor: string */
1964 2 : tl_write_uint32(&w, 0xce0d37b0u);
1965 2 : tl_write_string(&w, "anchor1");
1966 : /* pageBlockPreformatted: rich + language string */
1967 2 : tl_write_uint32(&w, 0xc070d93eu);
1968 2 : tl_write_uint32(&w, 0xdc3d824fu); /* textEmpty */
1969 2 : tl_write_string(&w, "python");
1970 : /* pageBlockAuthorDate: rich + int */
1971 2 : tl_write_uint32(&w, 0xbaafe5e0u);
1972 2 : tl_write_uint32(&w, 0x744694e0u); tl_write_string(&w, "Author");
1973 2 : tl_write_int32 (&w, 1700001000);
1974 : /* pageBlockBlockquote: two rich texts */
1975 2 : tl_write_uint32(&w, 0x263d7c26u);
1976 2 : tl_write_uint32(&w, 0xdc3d824fu); /* textEmpty */
1977 2 : tl_write_uint32(&w, 0xdc3d824fu);
1978 : /* photos: Vector<Photo> — empty */
1979 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1980 : /* docs: Vector<Document> — empty */
1981 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
1982 2 : TlReader r5 = tl_reader_init(w.data, w.len);
1983 2 : ASSERT(tl_skip_message_media_ex(&r5, NULL) == 0, "webPage+page walked");
1984 2 : ASSERT(r5.pos == r5.len, "consumed page");
1985 2 : tl_writer_free(&w);
1986 :
1987 : /* Page with more block variants: Cover, List, OrderedList, Table,
1988 : * RelatedArticles, Collage, Details. */
1989 2 : tl_writer_init(&w);
1990 2 : tl_write_uint32(&w, 0xddf8c26eu);
1991 2 : tl_write_uint32(&w, 0);
1992 2 : tl_write_uint32(&w, 0xe89c45b2u);
1993 2 : tl_write_uint32(&w, (1u << 10));
1994 2 : tl_write_int64 (&w, 101LL);
1995 2 : tl_write_string(&w, "https://t.me/a");
1996 2 : tl_write_string(&w, "t.me");
1997 2 : tl_write_int32 (&w, 0);
1998 2 : tl_write_uint32(&w, 0x98657f0du);
1999 2 : tl_write_uint32(&w, 0);
2000 2 : tl_write_string(&w, "https://t.me/a");
2001 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 7);
2002 : /* pageBlockCover wrapping a pageBlockDivider */
2003 2 : tl_write_uint32(&w, 0x39f23300u);
2004 2 : tl_write_uint32(&w, 0xdb20b188u);
2005 : /* pageBlockList: one text item */
2006 2 : tl_write_uint32(&w, 0xe4e88011u);
2007 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2008 2 : tl_write_uint32(&w, 0xb92fb6cdu); /* pageListItemText */
2009 2 : tl_write_uint32(&w, 0xdc3d824fu); /* textEmpty */
2010 : /* pageBlockOrderedList: one ordered text item */
2011 2 : tl_write_uint32(&w, 0x9a8ae1e1u);
2012 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2013 2 : tl_write_uint32(&w, 0x5e068047u); /* pageListOrderedItemText */
2014 2 : tl_write_string(&w, "1.");
2015 2 : tl_write_uint32(&w, 0xdc3d824fu);
2016 : /* pageBlockTable: flags=0 title rows */
2017 2 : tl_write_uint32(&w, 0xbf4dea82u);
2018 2 : tl_write_uint32(&w, 0);
2019 2 : tl_write_uint32(&w, 0xdc3d824fu); /* title: textEmpty */
2020 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2021 : /* pageTableRow: Vector<pageTableCell> with one cell */
2022 2 : tl_write_uint32(&w, 0xe0c0c5e5u);
2023 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2024 : /* pageTableCell: flags=0b11000010 (text=bit7, colspan=bit1, rowspan=bit2) */
2025 2 : tl_write_uint32(&w, 0x34566b6au);
2026 2 : tl_write_uint32(&w, (1u<<7)|(1u<<1)|(1u<<2));
2027 2 : tl_write_uint32(&w, 0xdc3d824fu); /* text: textEmpty */
2028 2 : tl_write_int32 (&w, 2); /* colspan */
2029 2 : tl_write_int32 (&w, 1); /* rowspan */
2030 : /* pageBlockRelatedArticles: title + articles */
2031 2 : tl_write_uint32(&w, 0x16115a96u);
2032 2 : tl_write_uint32(&w, 0xdc3d824fu); /* title */
2033 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2034 : /* pageRelatedArticle#b390dc08 flags=0b11111 */
2035 2 : tl_write_uint32(&w, 0xb390dc08u);
2036 2 : tl_write_uint32(&w, 0x1fu);
2037 2 : tl_write_string(&w, "https://rel.example.com");
2038 2 : tl_write_int64 (&w, 5LL);
2039 2 : tl_write_string(&w, "Related Title");
2040 2 : tl_write_string(&w, "Related Desc");
2041 2 : tl_write_int64 (&w, 6LL);
2042 2 : tl_write_string(&w, "Rel Author");
2043 2 : tl_write_int32 (&w, 1700000000);
2044 : /* pageBlockDetails: flags=0 blocks title */
2045 2 : tl_write_uint32(&w, 0x76768bedu);
2046 2 : tl_write_uint32(&w, 0);
2047 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2048 2 : tl_write_uint32(&w, 0xdc3d824fu); /* title */
2049 : /* pageBlockCollage: blocks caption */
2050 2 : tl_write_uint32(&w, 0x65a0fa4du);
2051 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2052 : /* caption: pageCaption#6f747657 text:RichText credit:RichText */
2053 2 : tl_write_uint32(&w, 0x6f747657u);
2054 2 : tl_write_uint32(&w, 0xdc3d824fu);
2055 2 : tl_write_uint32(&w, 0xdc3d824fu);
2056 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2057 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2058 2 : TlReader r6 = tl_reader_init(w.data, w.len);
2059 2 : ASSERT(tl_skip_message_media_ex(&r6, NULL) == 0, "page block variants walked");
2060 2 : ASSERT(r6.pos == r6.len, "consumed page block variants");
2061 2 : tl_writer_free(&w);
2062 :
2063 : /* Rich text variants: textUrl, textEmail, textPhone, textConcat,
2064 : * textImage, textAnchor. */
2065 2 : tl_writer_init(&w);
2066 2 : tl_write_uint32(&w, 0xddf8c26eu);
2067 2 : tl_write_uint32(&w, 0);
2068 2 : tl_write_uint32(&w, 0xe89c45b2u);
2069 2 : tl_write_uint32(&w, (1u << 10));
2070 2 : tl_write_int64 (&w, 102LL);
2071 2 : tl_write_string(&w, "https://t.me/b"); tl_write_string(&w, "t.me");
2072 2 : tl_write_int32 (&w, 0);
2073 2 : tl_write_uint32(&w, 0x98657f0du);
2074 2 : tl_write_uint32(&w, 0);
2075 2 : tl_write_string(&w, "https://t.me/b");
2076 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2077 : /* pageBlockPullquote: two rich text fields — use complex rich texts */
2078 2 : tl_write_uint32(&w, 0x4f4456d5u);
2079 : /* text: textConcat wrapping [textUrl, textEmail, textPhone, textImage, textAnchor] */
2080 2 : tl_write_uint32(&w, 0x7e6260d7u); /* textConcat */
2081 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 5);
2082 : /* textUrl: rich + url + webpage_id */
2083 2 : tl_write_uint32(&w, 0x3c2884c1u);
2084 2 : tl_write_uint32(&w, 0xdc3d824fu);
2085 2 : tl_write_string(&w, "https://link.example.com");
2086 2 : tl_write_int64 (&w, 7LL);
2087 : /* textEmail: rich + email */
2088 2 : tl_write_uint32(&w, 0xde5a0dd6u);
2089 2 : tl_write_uint32(&w, 0xdc3d824fu);
2090 2 : tl_write_string(&w, "a@b.com");
2091 : /* textPhone: rich + phone */
2092 2 : tl_write_uint32(&w, 0x1ccb966au);
2093 2 : tl_write_uint32(&w, 0xdc3d824fu);
2094 2 : tl_write_string(&w, "+15550001234");
2095 : /* textImage: document_id w h */
2096 2 : tl_write_uint32(&w, 0x081ccf4fu);
2097 2 : tl_write_int64 (&w, 8LL);
2098 2 : tl_write_int32 (&w, 32); tl_write_int32(&w, 32);
2099 : /* textAnchor: rich + name */
2100 2 : tl_write_uint32(&w, 0x35553762u);
2101 2 : tl_write_uint32(&w, 0xdc3d824fu);
2102 2 : tl_write_string(&w, "sec1");
2103 : /* credit: textEmpty */
2104 2 : tl_write_uint32(&w, 0xdc3d824fu);
2105 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2106 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2107 2 : TlReader r7 = tl_reader_init(w.data, w.len);
2108 2 : ASSERT(tl_skip_message_media_ex(&r7, NULL) == 0, "rich text variants walked");
2109 2 : ASSERT(r7.pos == r7.len, "consumed rich text variants");
2110 2 : tl_writer_free(&w);
2111 :
2112 : /* webPage with attributes (flags.12) — webPageAttributeTheme (no docs) +
2113 : * webPageAttributeStickerSet (empty stickers). */
2114 2 : tl_writer_init(&w);
2115 2 : tl_write_uint32(&w, 0xddf8c26eu);
2116 2 : tl_write_uint32(&w, 0);
2117 2 : tl_write_uint32(&w, 0xe89c45b2u);
2118 2 : tl_write_uint32(&w, (1u << 12));
2119 2 : tl_write_int64 (&w, 103LL);
2120 2 : tl_write_string(&w, "https://t.me/c"); tl_write_string(&w, "t.me");
2121 2 : tl_write_int32 (&w, 0);
2122 : /* attributes: Vector<WebPageAttribute> */
2123 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
2124 : /* webPageAttributeTheme flags=0 (no documents, no settings) */
2125 2 : tl_write_uint32(&w, 0x54b56617u);
2126 2 : tl_write_uint32(&w, 0);
2127 : /* webPageAttributeStickerSet flags=0 stickers=[] */
2128 2 : tl_write_uint32(&w, 0x50cc03d3u);
2129 2 : tl_write_uint32(&w, 0);
2130 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2131 2 : TlReader r8 = tl_reader_init(w.data, w.len);
2132 2 : ASSERT(tl_skip_message_media_ex(&r8, NULL) == 0, "webPage attributes walked");
2133 2 : ASSERT(r8.pos == r8.len, "consumed webPage attributes");
2134 2 : tl_writer_free(&w);
2135 :
2136 : /* Unknown webPage CRC → -1 */
2137 2 : tl_writer_init(&w);
2138 2 : tl_write_uint32(&w, 0xddf8c26eu);
2139 2 : tl_write_uint32(&w, 0);
2140 2 : tl_write_uint32(&w, 0xFF001234u);
2141 2 : tl_write_uint32(&w, 0);
2142 2 : TlReader r9 = tl_reader_init(w.data, w.len);
2143 2 : ASSERT(tl_skip_message_media_ex(&r9, NULL) == -1, "unknown webPage rejected");
2144 2 : tl_writer_free(&w);
2145 : }
2146 :
2147 : /* ---- Chat with admin rights + banned rights ---- */
2148 2 : static void test_chat_admin_banned_rights(void) {
2149 : /* chat#41cbf256 with flags.14 (admin_rights) and flags.18 (banned_rights). */
2150 2 : TlWriter w; tl_writer_init(&w);
2151 2 : tl_write_uint32(&w, TL_chat);
2152 2 : uint32_t cflags = (1u << 14) | (1u << 18);
2153 2 : tl_write_uint32(&w, cflags);
2154 2 : tl_write_int64 (&w, 1001LL);
2155 2 : tl_write_string(&w, "Test Group");
2156 : /* chatPhotoEmpty */
2157 2 : tl_write_uint32(&w, 0x37c1011cu);
2158 2 : tl_write_int32 (&w, 5); /* participants_count */
2159 2 : tl_write_int32 (&w, 1700000000); /* date */
2160 2 : tl_write_int32 (&w, 1); /* version */
2161 : /* chatAdminRights#5fb224d5 flags:# */
2162 2 : tl_write_uint32(&w, 0x5fb224d5u);
2163 2 : tl_write_uint32(&w, 0x1ff); /* all known bits */
2164 : /* chatBannedRights#9f120418 flags:# until_date:int */
2165 2 : tl_write_uint32(&w, 0x9f120418u);
2166 2 : tl_write_uint32(&w, 0);
2167 2 : tl_write_int32 (&w, 0);
2168 2 : TlReader r = tl_reader_init(w.data, w.len);
2169 2 : ChatSummary cs = {0};
2170 2 : ASSERT(tl_extract_chat(&r, &cs) == 0, "chat with admin+banned walked");
2171 2 : ASSERT(cs.id == 1001LL, "chat id captured");
2172 2 : ASSERT(r.pos == r.len, "reader consumed chat");
2173 2 : tl_writer_free(&w);
2174 :
2175 : /* channel with flags.14 (admin_rights) + flags.15 (banned_rights) +
2176 : * flags.18 (default_banned_rights). Minimal: no access_hash, no username. */
2177 2 : tl_writer_init(&w);
2178 2 : tl_write_uint32(&w, TL_channel);
2179 2 : uint32_t chflags = (1u << 14) | (1u << 15) | (1u << 18);
2180 2 : tl_write_uint32(&w, chflags);
2181 2 : tl_write_uint32(&w, 0); /* flags2 */
2182 2 : tl_write_int64 (&w, 2001LL);
2183 2 : tl_write_string(&w, "My Channel");
2184 2 : tl_write_uint32(&w, 0x37c1011cu); /* chatPhotoEmpty */
2185 2 : tl_write_int32 (&w, 1700000000); /* date */
2186 2 : tl_write_uint32(&w, 0x5fb224d5u); /* chatAdminRights */
2187 2 : tl_write_uint32(&w, 0);
2188 2 : tl_write_uint32(&w, 0x9f120418u); /* chatBannedRights (flags.15) */
2189 2 : tl_write_uint32(&w, 0); tl_write_int32(&w, 0);
2190 2 : tl_write_uint32(&w, 0x9f120418u); /* default_banned_rights (flags.18) */
2191 2 : tl_write_uint32(&w, 0); tl_write_int32(&w, 0);
2192 2 : TlReader r2 = tl_reader_init(w.data, w.len);
2193 2 : ChatSummary cs2 = {0};
2194 2 : ASSERT(tl_extract_chat(&r2, &cs2) == 0, "channel with rights walked");
2195 2 : ASSERT(cs2.id == 2001LL, "channel id captured");
2196 2 : tl_writer_free(&w);
2197 :
2198 : /* chatAdminRights unknown CRC → -1 (reach via chat) */
2199 2 : tl_writer_init(&w);
2200 2 : tl_write_uint32(&w, TL_chat);
2201 2 : tl_write_uint32(&w, (1u << 14));
2202 2 : tl_write_int64 (&w, 1002LL);
2203 2 : tl_write_string(&w, "G");
2204 2 : tl_write_uint32(&w, 0x37c1011cu);
2205 2 : tl_write_int32 (&w, 0); tl_write_int32(&w, 0); tl_write_int32(&w, 0);
2206 2 : tl_write_uint32(&w, 0xFF00DDDDu); /* unknown admin rights */
2207 2 : tl_write_uint32(&w, 0);
2208 2 : TlReader r3 = tl_reader_init(w.data, w.len);
2209 2 : ASSERT(tl_extract_chat(&r3, NULL) == -1, "unknown adminRights rejected");
2210 2 : tl_writer_free(&w);
2211 :
2212 : /* chatBannedRights unknown CRC → -1 */
2213 2 : tl_writer_init(&w);
2214 2 : tl_write_uint32(&w, TL_chat);
2215 2 : tl_write_uint32(&w, (1u << 18));
2216 2 : tl_write_int64 (&w, 1003LL);
2217 2 : tl_write_string(&w, "H");
2218 2 : tl_write_uint32(&w, 0x37c1011cu);
2219 2 : tl_write_int32 (&w, 0); tl_write_int32(&w, 0); tl_write_int32(&w, 0);
2220 2 : tl_write_uint32(&w, 0xFF00CCCCu); /* unknown banned rights */
2221 2 : tl_write_uint32(&w, 0); tl_write_int32(&w, 0);
2222 2 : TlReader r4 = tl_reader_init(w.data, w.len);
2223 2 : ASSERT(tl_extract_chat(&r4, NULL) == -1, "unknown bannedRights rejected");
2224 2 : tl_writer_free(&w);
2225 : }
2226 :
2227 : /* ---- Remaining list/ordered list blocks variant (blocks variant) ---- */
2228 2 : static void test_page_block_list_blocks_variant(void) {
2229 : /* Exercises pageListItemBlocks and pageListOrderedItemBlocks branches. */
2230 2 : TlWriter w; tl_writer_init(&w);
2231 2 : tl_write_uint32(&w, 0xddf8c26eu);
2232 2 : tl_write_uint32(&w, 0);
2233 2 : tl_write_uint32(&w, 0xe89c45b2u);
2234 2 : tl_write_uint32(&w, (1u << 10));
2235 2 : tl_write_int64 (&w, 104LL);
2236 2 : tl_write_string(&w, "https://t.me/d"); tl_write_string(&w, "t.me");
2237 2 : tl_write_int32 (&w, 0);
2238 2 : tl_write_uint32(&w, 0x98657f0du);
2239 2 : tl_write_uint32(&w, 0);
2240 2 : tl_write_string(&w, "https://t.me/d");
2241 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 2);
2242 : /* pageBlockList with a pageListItemBlocks item */
2243 2 : tl_write_uint32(&w, 0xe4e88011u);
2244 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2245 2 : tl_write_uint32(&w, 0x25e073fcu); /* pageListItemBlocks */
2246 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2247 2 : tl_write_uint32(&w, 0xdb20b188u); /* pageBlockDivider (leaf) */
2248 : /* pageBlockOrderedList with a pageListOrderedItemBlocks item */
2249 2 : tl_write_uint32(&w, 0x9a8ae1e1u);
2250 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2251 2 : tl_write_uint32(&w, 0x98dd8936u); /* pageListOrderedItemBlocks */
2252 2 : tl_write_string(&w, "a.");
2253 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2254 2 : tl_write_uint32(&w, 0xdb20b188u);
2255 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2256 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2257 2 : TlReader r = tl_reader_init(w.data, w.len);
2258 2 : ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "list blocks variant walked");
2259 2 : ASSERT(r.pos == r.len, "consumed list blocks");
2260 2 : tl_writer_free(&w);
2261 : }
2262 :
2263 : /* ---- Remaining rich text + other page blocks ---- */
2264 2 : static void test_page_block_extra_variants(void) {
2265 : /* pageBlockSlideshow, pageBlockEmbed, pageBlockEmbedPost,
2266 : * pageBlockVideo, pageBlockAudio, pageBlockPhoto, pageBlockMap,
2267 : * pageBlockChannel — and rich text subscript/superscript/marked/fixed/strike. */
2268 2 : TlWriter w; tl_writer_init(&w);
2269 2 : tl_write_uint32(&w, 0xddf8c26eu);
2270 2 : tl_write_uint32(&w, 0);
2271 2 : tl_write_uint32(&w, 0xe89c45b2u);
2272 2 : tl_write_uint32(&w, (1u << 10));
2273 2 : tl_write_int64 (&w, 105LL);
2274 2 : tl_write_string(&w, "https://t.me/e"); tl_write_string(&w, "t.me");
2275 2 : tl_write_int32 (&w, 0);
2276 2 : tl_write_uint32(&w, 0x98657f0du);
2277 2 : tl_write_uint32(&w, 0);
2278 2 : tl_write_string(&w, "https://t.me/e");
2279 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 10);
2280 : /* pageBlockSlideshow: blocks + caption */
2281 2 : tl_write_uint32(&w, 0x031f9590u);
2282 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
2283 2 : tl_write_uint32(&w, 0xdb20b188u);
2284 2 : tl_write_uint32(&w, 0x6f747657u); /* pageCaption */
2285 2 : tl_write_uint32(&w, 0xdc3d824fu); tl_write_uint32(&w, 0xdc3d824fu);
2286 : /* pageBlockEmbed: flags=0 (no url, no html, no poster, no wh) + caption */
2287 2 : tl_write_uint32(&w, 0xa8718dc5u);
2288 2 : tl_write_uint32(&w, 0);
2289 2 : tl_write_uint32(&w, 0x6f747657u);
2290 2 : tl_write_uint32(&w, 0xdc3d824fu); tl_write_uint32(&w, 0xdc3d824fu);
2291 : /* pageBlockEmbedPost: url + webpage_id + author_photo_id + author + date + blocks + caption */
2292 2 : tl_write_uint32(&w, 0xf259a80bu);
2293 2 : tl_write_string(&w, "https://t.me/post/1");
2294 2 : tl_write_int64 (&w, 8LL);
2295 2 : tl_write_int64 (&w, 9LL);
2296 2 : tl_write_string(&w, "PostAuthor");
2297 2 : tl_write_int32 (&w, 1700001000);
2298 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2299 2 : tl_write_uint32(&w, 0x6f747657u);
2300 2 : tl_write_uint32(&w, 0xdc3d824fu); tl_write_uint32(&w, 0xdc3d824fu);
2301 : /* pageBlockVideo: flags=0 video_id + caption */
2302 2 : tl_write_uint32(&w, 0x7c8fe7b6u);
2303 2 : tl_write_uint32(&w, 0); /* flags */
2304 2 : tl_write_int64 (&w, 11LL);
2305 2 : tl_write_uint32(&w, 0x6f747657u);
2306 2 : tl_write_uint32(&w, 0xdc3d824fu); tl_write_uint32(&w, 0xdc3d824fu);
2307 : /* pageBlockAudio: audio_id + caption */
2308 2 : tl_write_uint32(&w, 0x804361eau);
2309 2 : tl_write_int64 (&w, 12LL);
2310 2 : tl_write_uint32(&w, 0x6f747657u);
2311 2 : tl_write_uint32(&w, 0xdc3d824fu); tl_write_uint32(&w, 0xdc3d824fu);
2312 : /* pageBlockPhoto: flags=0 photo_id + caption */
2313 2 : tl_write_uint32(&w, 0x1759c560u);
2314 2 : tl_write_uint32(&w, 0);
2315 2 : tl_write_int64 (&w, 13LL);
2316 2 : tl_write_uint32(&w, 0x6f747657u);
2317 2 : tl_write_uint32(&w, 0xdc3d824fu); tl_write_uint32(&w, 0xdc3d824fu);
2318 : /* pageBlockMap: geoPointEmpty + zoom w h + caption */
2319 2 : tl_write_uint32(&w, 0xa44f3ef6u);
2320 2 : tl_write_uint32(&w, 0x1117dd5fu); /* geoPointEmpty */
2321 2 : tl_write_int32 (&w, 10); tl_write_int32(&w, 400); tl_write_int32(&w, 300);
2322 2 : tl_write_uint32(&w, 0x6f747657u);
2323 2 : tl_write_uint32(&w, 0xdc3d824fu); tl_write_uint32(&w, 0xdc3d824fu);
2324 : /* pageBlockChannel: a chatEmpty */
2325 2 : tl_write_uint32(&w, 0xef1751b5u);
2326 2 : tl_write_uint32(&w, TL_chatEmpty);
2327 2 : tl_write_int64 (&w, 333LL);
2328 : /* pageBlockSubtitle: rich text with subscript + superscript + marked + fixed + strike */
2329 2 : tl_write_uint32(&w, 0x8ffa9a1fu);
2330 2 : tl_write_uint32(&w, 0x7e6260d7u); /* textConcat */
2331 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 5);
2332 2 : tl_write_uint32(&w, 0xed6a8504u); tl_write_uint32(&w, 0xdc3d824fu); /* subscript */
2333 2 : tl_write_uint32(&w, 0xc7fb5e01u); tl_write_uint32(&w, 0xdc3d824fu); /* superscript */
2334 2 : tl_write_uint32(&w, 0x034b27f6u); tl_write_uint32(&w, 0xdc3d824fu); /* marked */
2335 2 : tl_write_uint32(&w, 0x6c3f19b9u); tl_write_uint32(&w, 0xdc3d824fu); /* fixed */
2336 2 : tl_write_uint32(&w, 0x9bf8bb95u); tl_write_uint32(&w, 0xdc3d824fu); /* strike */
2337 : /* pageBlockKicker + pageBlockHeader + pageBlockSubheader + pageBlockFooter */
2338 2 : tl_write_uint32(&w, 0x1e148390u); /* kicker */
2339 2 : tl_write_uint32(&w, 0xdc3d824fu);
2340 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2341 2 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
2342 2 : TlReader r = tl_reader_init(w.data, w.len);
2343 2 : ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "extra page blocks walked");
2344 2 : ASSERT(r.pos == r.len, "consumed extra page blocks");
2345 2 : tl_writer_free(&w);
2346 : }
2347 :
2348 2 : void run_tl_forward_compat_tests(void) {
2349 2 : RUN_TEST(test_unknown_top_level_result_skipped);
2350 2 : RUN_TEST(test_unknown_media_in_history);
2351 2 : RUN_TEST(test_unknown_message_action);
2352 2 : RUN_TEST(test_unknown_optional_field_preserves_layout);
2353 2 : RUN_TEST(test_unknown_update_type_in_getdifference);
2354 2 : RUN_TEST(test_skip_primitives_reject_unknown);
2355 2 : RUN_TEST(test_message_entity_variants);
2356 2 : RUN_TEST(test_media_variants_skip_clean);
2357 2 : RUN_TEST(test_reply_markup_variants);
2358 2 : RUN_TEST(test_chat_user_unknown_variants);
2359 2 : RUN_TEST(test_truncation_rejected);
2360 2 : RUN_TEST(test_photo_and_photo_size_roundtrip);
2361 2 : RUN_TEST(test_document_with_attributes);
2362 2 : RUN_TEST(test_fwd_header_variants);
2363 2 : RUN_TEST(test_reply_header_variants);
2364 2 : RUN_TEST(test_draft_message_variants);
2365 2 : RUN_TEST(test_notification_sound_and_settings);
2366 2 : RUN_TEST(test_chat_user_visuals);
2367 2 : RUN_TEST(test_username_color_emoji);
2368 2 : RUN_TEST(test_restriction_reason_vector);
2369 2 : RUN_TEST(test_factcheck_variants);
2370 2 : RUN_TEST(test_reactions_replies_trailers);
2371 2 : RUN_TEST(test_media_photo_and_document);
2372 2 : RUN_TEST(test_message_replies_empty);
2373 2 : RUN_TEST(test_media_game);
2374 2 : RUN_TEST(test_media_invoice);
2375 2 : RUN_TEST(test_media_paid_media);
2376 2 : RUN_TEST(test_media_story);
2377 2 : RUN_TEST(test_media_poll_with_results);
2378 2 : RUN_TEST(test_media_webpage_variants);
2379 2 : RUN_TEST(test_chat_admin_banned_rights);
2380 2 : RUN_TEST(test_page_block_list_blocks_variant);
2381 2 : RUN_TEST(test_page_block_extra_variants);
2382 2 : }
|