LCOV - code coverage report
Current view: top level - tests/unit - test_tl_skip.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 1925 1925
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 141 141

            Line data    Source code
       1              : /**
       2              :  * @file test_tl_skip.c
       3              :  * @brief Unit tests for TL object skippers.
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "tl_skip.h"
       8              : #include "tl_serial.h"
       9              : #include "tl_registry.h"
      10              : 
      11              : #include <stdlib.h>
      12              : #include <string.h>
      13              : 
      14              : /* CRCs duplicated from tl_skip.c for wire building in tests. */
      15              : #define CRC_peerNotifySettings        0xa83b0426u
      16              : #define CRC_notificationSoundDefault  0x97e8bebeu
      17              : #define CRC_notificationSoundNone     0x6f0c34dfu
      18              : #define CRC_notificationSoundLocal    0x830b9ae4u
      19              : #define CRC_notificationSoundRingtone 0xff6c8049u
      20              : #define CRC_draftMessageEmpty         0x1b0c841au
      21              : #define CRC_messageEntityBold         0xbd610bc9u
      22              : #define CRC_messageEntityTextUrl      0x76a6d327u
      23              : #define CRC_messageFwdHeader          0x4e4df4bbu
      24              : #define CRC_messageReplyHeader        0xafbc09dbu
      25              : #define CRC_messageMediaEmpty         0x3ded6320u
      26              : #define CRC_messageMediaUnsupported   0x9f84f49eu
      27              : #define CRC_messageMediaGeo           0x56e0d474u
      28              : #define CRC_messageMediaContact       0x70322949u
      29              : #define CRC_messageMediaDice          0x3f7ee58bu
      30              : #define CRC_messageMediaVenue         0x2ec0533fu
      31              : #define CRC_messageMediaPhoto         0x695150d7u
      32              : #define CRC_messageMediaDocument      0x4cf4d72du
      33              : #define CRC_geoPointEmpty             0x1117dd5fu
      34              : #define CRC_geoPoint                  0xb2a2f663u
      35              : #define CRC_photo                     0xfb197a65u
      36              : #define CRC_photoEmpty                0x2331b22du
      37              : #define CRC_documentEmpty             0x36f8c871u
      38              : #define CRC_photoSize                 0x75c78e60u
      39              : #define CRC_chatPhotoEmpty            0x37c1011cu
      40              : #define CRC_chatPhoto                 0x1c6e1c11u
      41              : #define CRC_userProfilePhotoEmpty     0x4f11bae1u
      42              : 
      43            1 : static void test_skip_bool(void) {
      44            1 :     TlWriter w; tl_writer_init(&w);
      45            1 :     tl_write_bool(&w, 1);
      46            1 :     tl_write_int32(&w, 42); /* sentinel to verify cursor position */
      47            1 :     TlReader r = tl_reader_init(w.data, w.len);
      48              : 
      49            1 :     ASSERT(tl_skip_bool(&r) == 0, "skip_bool ok");
      50            1 :     ASSERT(tl_read_int32(&r) == 42, "cursor past bool");
      51            1 :     tl_writer_free(&w);
      52              : }
      53              : 
      54            1 : static void test_skip_string(void) {
      55            1 :     TlWriter w; tl_writer_init(&w);
      56            1 :     tl_write_string(&w, "hello world");
      57            1 :     tl_write_int32(&w, 7);
      58            1 :     TlReader r = tl_reader_init(w.data, w.len);
      59              : 
      60            1 :     ASSERT(tl_skip_string(&r) == 0, "skip_string ok");
      61            1 :     ASSERT(tl_read_int32(&r) == 7, "cursor past string");
      62            1 :     tl_writer_free(&w);
      63              : }
      64              : 
      65            1 : static void test_skip_peer(void) {
      66            1 :     TlWriter w; tl_writer_init(&w);
      67            1 :     tl_write_uint32(&w, TL_peerUser);
      68            1 :     tl_write_int64(&w, 123456LL);
      69            1 :     tl_write_int32(&w, 99);
      70            1 :     TlReader r = tl_reader_init(w.data, w.len);
      71              : 
      72            1 :     ASSERT(tl_skip_peer(&r) == 0, "skip_peer ok");
      73            1 :     ASSERT(tl_read_int32(&r) == 99, "cursor past peer");
      74            1 :     tl_writer_free(&w);
      75              : }
      76              : 
      77            1 : static void test_skip_peer_unknown(void) {
      78            1 :     TlWriter w; tl_writer_init(&w);
      79            1 :     tl_write_uint32(&w, 0xDEADBEEF);
      80            1 :     tl_write_int64(&w, 0);
      81            1 :     TlReader r = tl_reader_init(w.data, w.len);
      82              : 
      83            1 :     ASSERT(tl_skip_peer(&r) == -1, "unknown peer rejected");
      84            1 :     tl_writer_free(&w);
      85              : }
      86              : 
      87            1 : static void test_skip_notification_sound_default(void) {
      88            1 :     TlWriter w; tl_writer_init(&w);
      89            1 :     tl_write_uint32(&w, CRC_notificationSoundDefault);
      90            1 :     tl_write_int32(&w, 55);
      91            1 :     TlReader r = tl_reader_init(w.data, w.len);
      92              : 
      93            1 :     ASSERT(tl_skip_notification_sound(&r) == 0, "default ok");
      94            1 :     ASSERT(tl_read_int32(&r) == 55, "cursor past");
      95            1 :     tl_writer_free(&w);
      96              : }
      97              : 
      98            1 : static void test_skip_notification_sound_ringtone(void) {
      99            1 :     TlWriter w; tl_writer_init(&w);
     100            1 :     tl_write_uint32(&w, CRC_notificationSoundRingtone);
     101            1 :     tl_write_int64(&w, 0x1122334455667788LL);
     102            1 :     tl_write_int32(&w, 88);
     103            1 :     TlReader r = tl_reader_init(w.data, w.len);
     104              : 
     105            1 :     ASSERT(tl_skip_notification_sound(&r) == 0, "ringtone ok");
     106            1 :     ASSERT(tl_read_int32(&r) == 88, "cursor past");
     107            1 :     tl_writer_free(&w);
     108              : }
     109              : 
     110            1 : static void test_skip_notification_sound_local(void) {
     111            1 :     TlWriter w; tl_writer_init(&w);
     112            1 :     tl_write_uint32(&w, CRC_notificationSoundLocal);
     113            1 :     tl_write_string(&w, "bell");
     114            1 :     tl_write_string(&w, "bell.mp3");
     115            1 :     tl_write_int32(&w, 33);
     116            1 :     TlReader r = tl_reader_init(w.data, w.len);
     117              : 
     118            1 :     ASSERT(tl_skip_notification_sound(&r) == 0, "local ok");
     119            1 :     ASSERT(tl_read_int32(&r) == 33, "cursor past");
     120            1 :     tl_writer_free(&w);
     121              : }
     122              : 
     123            1 : static void test_skip_peer_notify_settings_empty(void) {
     124            1 :     TlWriter w; tl_writer_init(&w);
     125            1 :     tl_write_uint32(&w, CRC_peerNotifySettings);
     126            1 :     tl_write_uint32(&w, 0); /* no flags */
     127            1 :     tl_write_int32(&w, 11);
     128            1 :     TlReader r = tl_reader_init(w.data, w.len);
     129              : 
     130            1 :     ASSERT(tl_skip_peer_notify_settings(&r) == 0, "empty notify ok");
     131            1 :     ASSERT(tl_read_int32(&r) == 11, "cursor past");
     132            1 :     tl_writer_free(&w);
     133              : }
     134              : 
     135            1 : static void test_skip_peer_notify_settings_full(void) {
     136            1 :     TlWriter w; tl_writer_init(&w);
     137            1 :     tl_write_uint32(&w, CRC_peerNotifySettings);
     138            1 :     uint32_t flags = (1u << 0)  /* show_previews (bool) */
     139              :                     | (1u << 1)  /* silent (bool) */
     140              :                     | (1u << 2)  /* mute_until (int) */
     141              :                     | (1u << 3)  /* ios_sound (NS) */
     142              :                     | (1u << 6); /* stories_muted (bool) */
     143            1 :     tl_write_uint32(&w, flags);
     144            1 :     tl_write_bool(&w, 1);
     145            1 :     tl_write_bool(&w, 0);
     146            1 :     tl_write_int32(&w, 1000);
     147            1 :     tl_write_uint32(&w, CRC_notificationSoundDefault);
     148            1 :     tl_write_bool(&w, 1);
     149            1 :     tl_write_int32(&w, 77);
     150            1 :     TlReader r = tl_reader_init(w.data, w.len);
     151              : 
     152            1 :     ASSERT(tl_skip_peer_notify_settings(&r) == 0, "full notify ok");
     153            1 :     ASSERT(tl_read_int32(&r) == 77, "cursor past");
     154            1 :     tl_writer_free(&w);
     155              : }
     156              : 
     157            1 : static void test_skip_draft_message_empty(void) {
     158            1 :     TlWriter w; tl_writer_init(&w);
     159            1 :     tl_write_uint32(&w, CRC_draftMessageEmpty);
     160            1 :     tl_write_uint32(&w, 1); /* flags.0: date present */
     161            1 :     tl_write_int32(&w, 1700000000);
     162            1 :     tl_write_int32(&w, 5);
     163            1 :     TlReader r = tl_reader_init(w.data, w.len);
     164              : 
     165            1 :     ASSERT(tl_skip_draft_message(&r) == 0, "draft empty ok");
     166            1 :     ASSERT(tl_read_int32(&r) == 5, "cursor past");
     167            1 :     tl_writer_free(&w);
     168              : }
     169              : 
     170            1 : static void test_skip_message_entity_bold(void) {
     171            1 :     TlWriter w; tl_writer_init(&w);
     172            1 :     tl_write_uint32(&w, CRC_messageEntityBold);
     173            1 :     tl_write_int32(&w, 5);   /* offset */
     174            1 :     tl_write_int32(&w, 10);  /* length */
     175            1 :     tl_write_int32(&w, 42);
     176            1 :     TlReader r = tl_reader_init(w.data, w.len);
     177            1 :     ASSERT(tl_skip_message_entity(&r) == 0, "bold entity");
     178            1 :     ASSERT(tl_read_int32(&r) == 42, "cursor past entity");
     179            1 :     tl_writer_free(&w);
     180              : }
     181              : 
     182            1 : static void test_skip_message_entity_text_url(void) {
     183            1 :     TlWriter w; tl_writer_init(&w);
     184            1 :     tl_write_uint32(&w, CRC_messageEntityTextUrl);
     185            1 :     tl_write_int32(&w, 0);
     186            1 :     tl_write_int32(&w, 4);
     187            1 :     tl_write_string(&w, "https://example.com");
     188            1 :     tl_write_int32(&w, 42);
     189            1 :     TlReader r = tl_reader_init(w.data, w.len);
     190            1 :     ASSERT(tl_skip_message_entity(&r) == 0, "text_url entity");
     191            1 :     ASSERT(tl_read_int32(&r) == 42, "cursor past entity");
     192            1 :     tl_writer_free(&w);
     193              : }
     194              : 
     195            1 : static void test_skip_entities_vector(void) {
     196            1 :     TlWriter w; tl_writer_init(&w);
     197            1 :     tl_write_uint32(&w, TL_vector);
     198            1 :     tl_write_uint32(&w, 2);
     199            1 :     tl_write_uint32(&w, CRC_messageEntityBold);
     200            1 :     tl_write_int32(&w, 0); tl_write_int32(&w, 3);
     201            1 :     tl_write_uint32(&w, CRC_messageEntityBold);
     202            1 :     tl_write_int32(&w, 10); tl_write_int32(&w, 5);
     203            1 :     tl_write_int32(&w, 99);
     204            1 :     TlReader r = tl_reader_init(w.data, w.len);
     205            1 :     ASSERT(tl_skip_message_entities_vector(&r) == 0, "entities vector");
     206            1 :     ASSERT(tl_read_int32(&r) == 99, "cursor past vector");
     207            1 :     tl_writer_free(&w);
     208              : }
     209              : 
     210            1 : static void test_skip_fwd_header_minimal(void) {
     211            1 :     TlWriter w; tl_writer_init(&w);
     212            1 :     tl_write_uint32(&w, CRC_messageFwdHeader);
     213            1 :     tl_write_uint32(&w, 0); /* no flags */
     214            1 :     tl_write_int32(&w, 1700000000); /* date */
     215            1 :     tl_write_int32(&w, 21);
     216            1 :     TlReader r = tl_reader_init(w.data, w.len);
     217            1 :     ASSERT(tl_skip_message_fwd_header(&r) == 0, "fwd header minimal");
     218            1 :     ASSERT(tl_read_int32(&r) == 21, "cursor past fwd_header");
     219            1 :     tl_writer_free(&w);
     220              : }
     221              : 
     222            1 : static void test_skip_fwd_header_with_channel(void) {
     223            1 :     TlWriter w; tl_writer_init(&w);
     224            1 :     tl_write_uint32(&w, CRC_messageFwdHeader);
     225            1 :     uint32_t flags = (1u << 0)    /* from_id */
     226              :                    | (1u << 2)    /* channel_post */
     227              :                    | (1u << 3);   /* post_author */
     228            1 :     tl_write_uint32(&w, flags);
     229            1 :     tl_write_uint32(&w, TL_peerChannel);
     230            1 :     tl_write_int64(&w, -1001234567890LL);
     231            1 :     tl_write_int32(&w, 1700000000); /* date */
     232            1 :     tl_write_int32(&w, 42);         /* channel_post */
     233            1 :     tl_write_string(&w, "author");
     234            1 :     tl_write_int32(&w, 77);
     235            1 :     TlReader r = tl_reader_init(w.data, w.len);
     236            1 :     ASSERT(tl_skip_message_fwd_header(&r) == 0, "fwd header with channel");
     237            1 :     ASSERT(tl_read_int32(&r) == 77, "cursor past");
     238            1 :     tl_writer_free(&w);
     239              : }
     240              : 
     241            1 : static void test_skip_reply_header_minimal(void) {
     242            1 :     TlWriter w; tl_writer_init(&w);
     243            1 :     tl_write_uint32(&w, CRC_messageReplyHeader);
     244            1 :     tl_write_uint32(&w, (1u << 4)); /* reply_to_msg_id */
     245            1 :     tl_write_int32(&w, 1234);
     246            1 :     tl_write_int32(&w, 88);
     247            1 :     TlReader r = tl_reader_init(w.data, w.len);
     248            1 :     ASSERT(tl_skip_message_reply_header(&r) == 0, "reply header minimal");
     249            1 :     ASSERT(tl_read_int32(&r) == 88, "cursor past");
     250            1 :     tl_writer_free(&w);
     251              : }
     252              : 
     253              : /* ---- Error / short-buffer coverage ---- */
     254              : 
     255            1 : static void test_skip_bool_short(void) {
     256            1 :     TlWriter w; tl_writer_init(&w);
     257            1 :     tl_write_uint32(&w, 0); /* only 4 bytes */
     258            1 :     TlReader r = tl_reader_init(w.data, 2); /* truncated */
     259            1 :     ASSERT(tl_skip_bool(&r) == -1, "short bool rejected");
     260            1 :     tl_writer_free(&w);
     261              : }
     262              : 
     263            1 : static void test_skip_notification_sound_unknown(void) {
     264            1 :     TlWriter w; tl_writer_init(&w);
     265            1 :     tl_write_uint32(&w, 0xDEADBEEF);
     266            1 :     TlReader r = tl_reader_init(w.data, w.len);
     267            1 :     ASSERT(tl_skip_notification_sound(&r) == -1, "unknown NS rejected");
     268            1 :     tl_writer_free(&w);
     269              : }
     270              : 
     271            1 : static void test_skip_peer_notify_settings_wrong_crc(void) {
     272            1 :     TlWriter w; tl_writer_init(&w);
     273            1 :     tl_write_uint32(&w, 0xBADF00D);
     274            1 :     tl_write_uint32(&w, 0);
     275            1 :     TlReader r = tl_reader_init(w.data, w.len);
     276            1 :     ASSERT(tl_skip_peer_notify_settings(&r) == -1, "wrong CRC rejected");
     277            1 :     tl_writer_free(&w);
     278              : }
     279              : 
     280            1 : static void test_skip_draft_message_nonempty(void) {
     281              :     /* Non-empty draftMessage (0x3fccf7ef) is not parseable yet — must bail. */
     282            1 :     TlWriter w; tl_writer_init(&w);
     283            1 :     tl_write_uint32(&w, 0x3fccf7efu);
     284            1 :     TlReader r = tl_reader_init(w.data, w.len);
     285            1 :     ASSERT(tl_skip_draft_message(&r) == -1, "non-empty draft bails");
     286            1 :     tl_writer_free(&w);
     287              : }
     288              : 
     289            1 : static void test_skip_draft_message_unknown(void) {
     290            1 :     TlWriter w; tl_writer_init(&w);
     291            1 :     tl_write_uint32(&w, 0xCAFEBABE);
     292            1 :     TlReader r = tl_reader_init(w.data, w.len);
     293            1 :     ASSERT(tl_skip_draft_message(&r) == -1, "unknown draft rejected");
     294            1 :     tl_writer_free(&w);
     295              : }
     296              : 
     297            1 : static void test_skip_message_entity_unknown(void) {
     298            1 :     TlWriter w; tl_writer_init(&w);
     299            1 :     tl_write_uint32(&w, 0xDEADCAFE);
     300            1 :     TlReader r = tl_reader_init(w.data, w.len);
     301            1 :     ASSERT(tl_skip_message_entity(&r) == -1, "unknown entity rejected");
     302            1 :     tl_writer_free(&w);
     303              : }
     304              : 
     305            1 : static void test_skip_entities_vector_wrong_header(void) {
     306            1 :     TlWriter w; tl_writer_init(&w);
     307            1 :     tl_write_uint32(&w, 0xBADBADBA); /* not TL_vector */
     308            1 :     tl_write_uint32(&w, 0);
     309            1 :     TlReader r = tl_reader_init(w.data, w.len);
     310            1 :     ASSERT(tl_skip_message_entities_vector(&r) == -1, "non-vector rejected");
     311            1 :     tl_writer_free(&w);
     312              : }
     313              : 
     314            1 : static void test_skip_fwd_header_wrong_crc(void) {
     315            1 :     TlWriter w; tl_writer_init(&w);
     316            1 :     tl_write_uint32(&w, 0x12345678);
     317            1 :     TlReader r = tl_reader_init(w.data, w.len);
     318            1 :     ASSERT(tl_skip_message_fwd_header(&r) == -1, "bad fwd crc rejected");
     319            1 :     tl_writer_free(&w);
     320              : }
     321              : 
     322            1 : static void test_skip_reply_header_wrong_crc(void) {
     323            1 :     TlWriter w; tl_writer_init(&w);
     324            1 :     tl_write_uint32(&w, 0x87654321);
     325            1 :     TlReader r = tl_reader_init(w.data, w.len);
     326            1 :     ASSERT(tl_skip_message_reply_header(&r) == -1, "bad reply crc rejected");
     327            1 :     tl_writer_free(&w);
     328              : }
     329              : 
     330            1 : static void test_skip_reply_header_reply_media_bail(void) {
     331              :     /* flags.8 set → reply_media is present but we haven't implemented the
     332              :      * MessageMedia skipper, so must bail. */
     333            1 :     TlWriter w; tl_writer_init(&w);
     334            1 :     tl_write_uint32(&w, 0xafbc09dbu);
     335            1 :     tl_write_uint32(&w, (1u << 8));
     336            1 :     TlReader r = tl_reader_init(w.data, w.len);
     337            1 :     ASSERT(tl_skip_message_reply_header(&r) == -1, "reply_media bail");
     338            1 :     tl_writer_free(&w);
     339              : }
     340              : 
     341              : /* ---- MessageMedia skipper tests ---- */
     342              : 
     343            1 : static void test_skip_media_empty(void) {
     344            1 :     TlWriter w; tl_writer_init(&w);
     345            1 :     tl_write_uint32(&w, CRC_messageMediaEmpty);
     346            1 :     tl_write_int32(&w, 101);
     347            1 :     TlReader r = tl_reader_init(w.data, w.len);
     348            1 :     ASSERT(tl_skip_message_media(&r) == 0, "media empty");
     349            1 :     ASSERT(tl_read_int32(&r) == 101, "cursor past media empty");
     350            1 :     tl_writer_free(&w);
     351              : }
     352              : 
     353            1 : static void test_skip_media_unsupported(void) {
     354            1 :     TlWriter w; tl_writer_init(&w);
     355            1 :     tl_write_uint32(&w, CRC_messageMediaUnsupported);
     356            1 :     tl_write_int32(&w, 202);
     357            1 :     TlReader r = tl_reader_init(w.data, w.len);
     358            1 :     ASSERT(tl_skip_message_media(&r) == 0, "media unsupported");
     359            1 :     ASSERT(tl_read_int32(&r) == 202, "cursor past media unsupported");
     360            1 :     tl_writer_free(&w);
     361              : }
     362              : 
     363            1 : static void test_skip_media_geo_empty_point(void) {
     364            1 :     TlWriter w; tl_writer_init(&w);
     365            1 :     tl_write_uint32(&w, CRC_messageMediaGeo);
     366            1 :     tl_write_uint32(&w, CRC_geoPointEmpty);
     367            1 :     tl_write_int32(&w, 303);
     368            1 :     TlReader r = tl_reader_init(w.data, w.len);
     369            1 :     ASSERT(tl_skip_message_media(&r) == 0, "geo empty");
     370            1 :     ASSERT(tl_read_int32(&r) == 303, "cursor past");
     371            1 :     tl_writer_free(&w);
     372              : }
     373              : 
     374            1 : static void test_skip_media_geo(void) {
     375            1 :     TlWriter w; tl_writer_init(&w);
     376            1 :     tl_write_uint32(&w, CRC_messageMediaGeo);
     377            1 :     tl_write_uint32(&w, CRC_geoPoint);
     378            1 :     tl_write_uint32(&w, 0);        /* flags */
     379            1 :     tl_write_double(&w, 19.12);    /* long */
     380            1 :     tl_write_double(&w, 47.49);    /* lat */
     381            1 :     tl_write_int64(&w, 0xCAFE);    /* access_hash */
     382            1 :     tl_write_int32(&w, 404);
     383            1 :     TlReader r = tl_reader_init(w.data, w.len);
     384            1 :     ASSERT(tl_skip_message_media(&r) == 0, "geo full");
     385            1 :     ASSERT(tl_read_int32(&r) == 404, "cursor past");
     386            1 :     tl_writer_free(&w);
     387              : }
     388              : 
     389            1 : static void test_skip_media_contact(void) {
     390            1 :     TlWriter w; tl_writer_init(&w);
     391            1 :     tl_write_uint32(&w, CRC_messageMediaContact);
     392            1 :     tl_write_string(&w, "+15551234567");
     393            1 :     tl_write_string(&w, "Alice");
     394            1 :     tl_write_string(&w, "Smith");
     395            1 :     tl_write_string(&w, "");      /* vcard */
     396            1 :     tl_write_int64(&w, 42LL);
     397            1 :     tl_write_int32(&w, 505);
     398            1 :     TlReader r = tl_reader_init(w.data, w.len);
     399            1 :     ASSERT(tl_skip_message_media(&r) == 0, "contact");
     400            1 :     ASSERT(tl_read_int32(&r) == 505, "cursor past");
     401            1 :     tl_writer_free(&w);
     402              : }
     403              : 
     404            1 : static void test_skip_media_dice(void) {
     405            1 :     TlWriter w; tl_writer_init(&w);
     406            1 :     tl_write_uint32(&w, CRC_messageMediaDice);
     407            1 :     tl_write_int32(&w, 6);
     408            1 :     tl_write_string(&w, "🎲");
     409            1 :     tl_write_int32(&w, 606);
     410            1 :     TlReader r = tl_reader_init(w.data, w.len);
     411            1 :     ASSERT(tl_skip_message_media(&r) == 0, "dice");
     412            1 :     ASSERT(tl_read_int32(&r) == 606, "cursor past");
     413            1 :     tl_writer_free(&w);
     414              : }
     415              : 
     416            1 : static void test_skip_media_venue(void) {
     417            1 :     TlWriter w; tl_writer_init(&w);
     418            1 :     tl_write_uint32(&w, CRC_messageMediaVenue);
     419            1 :     tl_write_uint32(&w, CRC_geoPointEmpty);
     420            1 :     tl_write_string(&w, "Title");
     421            1 :     tl_write_string(&w, "Address");
     422            1 :     tl_write_string(&w, "Provider");
     423            1 :     tl_write_string(&w, "VenueID");
     424            1 :     tl_write_string(&w, "Type");
     425            1 :     tl_write_int32(&w, 707);
     426            1 :     TlReader r = tl_reader_init(w.data, w.len);
     427            1 :     ASSERT(tl_skip_message_media(&r) == 0, "venue");
     428            1 :     ASSERT(tl_read_int32(&r) == 707, "cursor past");
     429            1 :     tl_writer_free(&w);
     430              : }
     431              : 
     432            1 : static void test_skip_media_photo_empty_photo(void) {
     433            1 :     TlWriter w; tl_writer_init(&w);
     434            1 :     tl_write_uint32(&w, CRC_messageMediaPhoto);
     435            1 :     tl_write_uint32(&w, (1u << 0));   /* flags: photo present */
     436            1 :     tl_write_uint32(&w, CRC_photoEmpty);
     437            1 :     tl_write_int64(&w, 1234567890LL);
     438            1 :     tl_write_int32(&w, 808);
     439            1 :     TlReader r = tl_reader_init(w.data, w.len);
     440            1 :     ASSERT(tl_skip_message_media(&r) == 0, "media-photo with photoEmpty");
     441            1 :     ASSERT(tl_read_int32(&r) == 808, "cursor past");
     442            1 :     tl_writer_free(&w);
     443              : }
     444              : 
     445            1 : static void test_skip_media_photo_with_sizes(void) {
     446            1 :     TlWriter w; tl_writer_init(&w);
     447            1 :     tl_write_uint32(&w, CRC_messageMediaPhoto);
     448            1 :     tl_write_uint32(&w, (1u << 0));        /* photo flag */
     449              :     /* photo#fb197a65 flags:# id:long access_hash:long file_reference:bytes
     450              :      *                date:int sizes:Vector<PhotoSize> dc_id:int */
     451            1 :     tl_write_uint32(&w, CRC_photo);
     452            1 :     tl_write_uint32(&w, 0);                 /* photo flags */
     453            1 :     tl_write_int64(&w, 111LL);              /* id */
     454            1 :     tl_write_int64(&w, 222LL);              /* access_hash */
     455            1 :     uint8_t fr[8] = {1,2,3,4,5,6,7,8};
     456            1 :     tl_write_bytes(&w, fr, 8);              /* file_reference */
     457            1 :     tl_write_int32(&w, 1700000000);         /* date */
     458              :     /* sizes vector: one photoSize */
     459            1 :     tl_write_uint32(&w, TL_vector);
     460            1 :     tl_write_uint32(&w, 1);
     461            1 :     tl_write_uint32(&w, CRC_photoSize);
     462            1 :     tl_write_string(&w, "x");
     463            1 :     tl_write_int32(&w, 640); tl_write_int32(&w, 480); tl_write_int32(&w, 12345);
     464            1 :     tl_write_int32(&w, 2);                  /* dc_id */
     465            1 :     tl_write_int32(&w, 909);                /* sentinel */
     466            1 :     TlReader r = tl_reader_init(w.data, w.len);
     467            1 :     ASSERT(tl_skip_message_media(&r) == 0, "media-photo with photoSize");
     468            1 :     ASSERT(tl_read_int32(&r) == 909, "cursor past");
     469            1 :     tl_writer_free(&w);
     470              : }
     471              : 
     472            1 : static void test_skip_media_document_empty(void) {
     473            1 :     TlWriter w; tl_writer_init(&w);
     474            1 :     tl_write_uint32(&w, CRC_messageMediaDocument);
     475            1 :     tl_write_uint32(&w, (1u << 0));        /* document present */
     476            1 :     tl_write_uint32(&w, CRC_documentEmpty);
     477            1 :     tl_write_int64(&w, 99999LL);
     478            1 :     tl_write_int32(&w, 1010);
     479            1 :     TlReader r = tl_reader_init(w.data, w.len);
     480            1 :     ASSERT(tl_skip_message_media(&r) == 0, "media-document empty");
     481            1 :     ASSERT(tl_read_int32(&r) == 1010, "cursor past");
     482            1 :     tl_writer_free(&w);
     483              : }
     484              : 
     485              : /* ---- Chat / User skipper tests ---- */
     486              : 
     487            1 : static void test_skip_chat_forbidden(void) {
     488            1 :     TlWriter w; tl_writer_init(&w);
     489            1 :     tl_write_uint32(&w, TL_chatForbidden);
     490            1 :     tl_write_int64(&w, 12345LL);
     491            1 :     tl_write_string(&w, "Forbidden Chat");
     492            1 :     tl_write_int32(&w, 1111);
     493            1 :     TlReader r = tl_reader_init(w.data, w.len);
     494            1 :     ASSERT(tl_skip_chat(&r) == 0, "chatForbidden skip");
     495            1 :     ASSERT(tl_read_int32(&r) == 1111, "cursor past chatForbidden");
     496            1 :     tl_writer_free(&w);
     497              : }
     498              : 
     499            1 : static void test_skip_channel_forbidden(void) {
     500            1 :     TlWriter w; tl_writer_init(&w);
     501            1 :     tl_write_uint32(&w, TL_channelForbidden);
     502            1 :     tl_write_uint32(&w, 0);                       /* flags: no until_date */
     503            1 :     tl_write_int64(&w, -1001234567LL);            /* id */
     504            1 :     tl_write_int64(&w, 0xCAFEBABELL);             /* access_hash */
     505            1 :     tl_write_string(&w, "Forbidden Channel");
     506            1 :     tl_write_int32(&w, 2222);
     507            1 :     TlReader r = tl_reader_init(w.data, w.len);
     508            1 :     ASSERT(tl_skip_chat(&r) == 0, "channelForbidden skip");
     509            1 :     ASSERT(tl_read_int32(&r) == 2222, "cursor past channelForbidden");
     510            1 :     tl_writer_free(&w);
     511              : }
     512              : 
     513            1 : static void test_skip_chat_plain(void) {
     514            1 :     TlWriter w; tl_writer_init(&w);
     515            1 :     tl_write_uint32(&w, TL_chat);
     516            1 :     tl_write_uint32(&w, 0);                        /* flags (no optionals) */
     517            1 :     tl_write_int64(&w, 42LL);                      /* id */
     518            1 :     tl_write_string(&w, "Group Name");
     519            1 :     tl_write_uint32(&w, CRC_chatPhotoEmpty);       /* photo */
     520            1 :     tl_write_int32(&w, 10);                        /* participants_count */
     521            1 :     tl_write_int32(&w, 1700000000);                /* date */
     522            1 :     tl_write_int32(&w, 3);                         /* version */
     523            1 :     tl_write_int32(&w, 3333);                      /* sentinel */
     524            1 :     TlReader r = tl_reader_init(w.data, w.len);
     525            1 :     ASSERT(tl_skip_chat(&r) == 0, "chat skip");
     526            1 :     ASSERT(tl_read_int32(&r) == 3333, "cursor past chat");
     527            1 :     tl_writer_free(&w);
     528              : }
     529              : 
     530            1 : static void test_skip_channel_minimal(void) {
     531            1 :     TlWriter w; tl_writer_init(&w);
     532            1 :     tl_write_uint32(&w, TL_channel);
     533            1 :     tl_write_uint32(&w, 0);                        /* flags */
     534            1 :     tl_write_uint32(&w, 0);                        /* flags2 */
     535            1 :     tl_write_int64(&w, -1009876543LL);             /* id */
     536            1 :     tl_write_string(&w, "Chan");
     537            1 :     tl_write_uint32(&w, CRC_chatPhotoEmpty);       /* photo */
     538            1 :     tl_write_int32(&w, 1700000000);                /* date */
     539            1 :     tl_write_int32(&w, 4444);                      /* sentinel */
     540            1 :     TlReader r = tl_reader_init(w.data, w.len);
     541            1 :     ASSERT(tl_skip_chat(&r) == 0, "channel skip");
     542            1 :     ASSERT(tl_read_int32(&r) == 4444, "cursor past channel");
     543            1 :     tl_writer_free(&w);
     544              : }
     545              : 
     546            1 : static void test_skip_user_empty(void) {
     547            1 :     TlWriter w; tl_writer_init(&w);
     548            1 :     tl_write_uint32(&w, TL_userEmpty);
     549            1 :     tl_write_int64(&w, 777LL);
     550            1 :     tl_write_int32(&w, 5555);
     551            1 :     TlReader r = tl_reader_init(w.data, w.len);
     552            1 :     ASSERT(tl_skip_user(&r) == 0, "userEmpty skip");
     553            1 :     ASSERT(tl_read_int32(&r) == 5555, "cursor past userEmpty");
     554            1 :     tl_writer_free(&w);
     555              : }
     556              : 
     557            1 : static void test_skip_user_full(void) {
     558            1 :     TlWriter w; tl_writer_init(&w);
     559            1 :     tl_write_uint32(&w, TL_user);
     560            1 :     uint32_t flags = (1u << 1)   /* first_name */
     561              :                    | (1u << 2)   /* last_name */
     562              :                    | (1u << 3);  /* username */
     563            1 :     tl_write_uint32(&w, flags);
     564            1 :     tl_write_uint32(&w, 0);                /* flags2 */
     565            1 :     tl_write_int64(&w, 999LL);             /* id */
     566            1 :     tl_write_string(&w, "Alice");
     567            1 :     tl_write_string(&w, "Smith");
     568            1 :     tl_write_string(&w, "asmith");
     569            1 :     tl_write_int32(&w, 6666);
     570            1 :     TlReader r = tl_reader_init(w.data, w.len);
     571            1 :     ASSERT(tl_skip_user(&r) == 0, "user skip");
     572            1 :     ASSERT(tl_read_int32(&r) == 6666, "cursor past user");
     573            1 :     tl_writer_free(&w);
     574              : }
     575              : 
     576            1 : static void test_extract_chat_forbidden(void) {
     577            1 :     TlWriter w; tl_writer_init(&w);
     578            1 :     tl_write_uint32(&w, TL_chatForbidden);
     579            1 :     tl_write_int64(&w, 0xABCDEF00LL);
     580            1 :     tl_write_string(&w, "Secret Room");
     581            1 :     tl_write_int32(&w, 7777);
     582            1 :     TlReader r = tl_reader_init(w.data, w.len);
     583              :     ChatSummary cs;
     584            1 :     ASSERT(tl_extract_chat(&r, &cs) == 0, "extract chatForbidden");
     585            1 :     ASSERT(cs.id == 0xABCDEF00LL, "chat id");
     586            1 :     ASSERT(strcmp(cs.title, "Secret Room") == 0, "chat title");
     587            1 :     ASSERT(tl_read_int32(&r) == 7777, "cursor past extract");
     588            1 :     tl_writer_free(&w);
     589              : }
     590              : 
     591            1 : static void test_extract_user(void) {
     592            1 :     TlWriter w; tl_writer_init(&w);
     593            1 :     tl_write_uint32(&w, TL_user);
     594            1 :     uint32_t flags = (1u << 1) | (1u << 2) | (1u << 3);
     595            1 :     tl_write_uint32(&w, flags);
     596            1 :     tl_write_uint32(&w, 0);
     597            1 :     tl_write_int64(&w, 0x1234567890LL);
     598            1 :     tl_write_string(&w, "Alice");
     599            1 :     tl_write_string(&w, "Smith");
     600            1 :     tl_write_string(&w, "asmith");
     601            1 :     tl_write_int32(&w, 8888);
     602            1 :     TlReader r = tl_reader_init(w.data, w.len);
     603              :     UserSummary us;
     604            1 :     ASSERT(tl_extract_user(&r, &us) == 0, "extract user");
     605            1 :     ASSERT(us.id == 0x1234567890LL, "user id");
     606            1 :     ASSERT(us.have_access_hash == 0, "no access_hash when flag clear");
     607            1 :     ASSERT(us.access_hash == 0, "access_hash zero when absent");
     608            1 :     ASSERT(strcmp(us.name, "Alice Smith") == 0, "user name joined");
     609            1 :     ASSERT(strcmp(us.username, "asmith") == 0, "user username");
     610            1 :     ASSERT(tl_read_int32(&r) == 8888, "cursor past extract user");
     611            1 :     tl_writer_free(&w);
     612              : }
     613              : 
     614            1 : static void test_extract_user_access_hash(void) {
     615            1 :     TlWriter w; tl_writer_init(&w);
     616            1 :     tl_write_uint32(&w, TL_user);
     617            1 :     uint32_t flags = (1u << 0) | (1u << 1) | (1u << 3); /* access_hash + first_name + username */
     618            1 :     tl_write_uint32(&w, flags);
     619            1 :     tl_write_uint32(&w, 0);
     620            1 :     tl_write_int64(&w, 123LL);
     621            1 :     tl_write_int64(&w, 0xDEADBEEFCAFEBABELL); /* access_hash */
     622            1 :     tl_write_string(&w, "Bob");
     623            1 :     tl_write_string(&w, "bob");
     624            1 :     tl_write_int32(&w, 9999);
     625            1 :     TlReader r = tl_reader_init(w.data, w.len);
     626              :     UserSummary us;
     627            1 :     ASSERT(tl_extract_user(&r, &us) == 0, "extract user with access_hash");
     628            1 :     ASSERT(us.id == 123LL, "user id");
     629            1 :     ASSERT(us.have_access_hash == 1, "have_access_hash set");
     630            1 :     ASSERT(us.access_hash == (int64_t)0xDEADBEEFCAFEBABELL, "access_hash value");
     631            1 :     ASSERT(strcmp(us.name, "Bob") == 0, "user name");
     632            1 :     ASSERT(strcmp(us.username, "bob") == 0, "user username");
     633            1 :     ASSERT(tl_read_int32(&r) == 9999, "cursor past user");
     634            1 :     tl_writer_free(&w);
     635              : }
     636              : 
     637            1 : static void test_extract_channel_access_hash(void) {
     638            1 :     TlWriter w; tl_writer_init(&w);
     639            1 :     tl_write_uint32(&w, TL_channel);
     640            1 :     uint32_t flags = (1u << 13);               /* access_hash */
     641            1 :     tl_write_uint32(&w, flags);
     642            1 :     tl_write_uint32(&w, 0);                    /* flags2 */
     643            1 :     tl_write_int64(&w, -1001234567LL);         /* id */
     644            1 :     tl_write_int64(&w, 0x1111222233334444LL);  /* access_hash */
     645            1 :     tl_write_string(&w, "My Channel");
     646            1 :     tl_write_uint32(&w, CRC_chatPhotoEmpty);   /* photo */
     647            1 :     tl_write_int32(&w, 1700000000);            /* date */
     648            1 :     tl_write_int32(&w, 5555);                  /* sentinel */
     649            1 :     TlReader r = tl_reader_init(w.data, w.len);
     650              :     ChatSummary cs;
     651            1 :     ASSERT(tl_extract_chat(&r, &cs) == 0, "extract channel with access_hash");
     652            1 :     ASSERT(cs.id == -1001234567LL, "channel id");
     653            1 :     ASSERT(cs.have_access_hash == 1, "have_access_hash set");
     654            1 :     ASSERT(cs.access_hash == 0x1111222233334444LL, "access_hash value");
     655            1 :     ASSERT(strcmp(cs.title, "My Channel") == 0, "channel title");
     656            1 :     ASSERT(tl_read_int32(&r) == 5555, "cursor past channel");
     657            1 :     tl_writer_free(&w);
     658              : }
     659              : 
     660            1 : static void test_extract_channel_forbidden_access_hash(void) {
     661            1 :     TlWriter w; tl_writer_init(&w);
     662            1 :     tl_write_uint32(&w, TL_channelForbidden);
     663            1 :     tl_write_uint32(&w, 0);                    /* flags: no until_date */
     664            1 :     tl_write_int64(&w, -1009999999LL);
     665            1 :     tl_write_int64(&w, 0xA5A5A5A5A5A5A5A5LL);  /* access_hash */
     666            1 :     tl_write_string(&w, "Forbidden");
     667            1 :     tl_write_int32(&w, 6666);
     668            1 :     TlReader r = tl_reader_init(w.data, w.len);
     669              :     ChatSummary cs;
     670            1 :     ASSERT(tl_extract_chat(&r, &cs) == 0, "extract channelForbidden");
     671            1 :     ASSERT(cs.have_access_hash == 1, "have_access_hash set");
     672            1 :     ASSERT(cs.access_hash == (int64_t)0xA5A5A5A5A5A5A5A5LL, "access_hash value");
     673            1 :     ASSERT(strcmp(cs.title, "Forbidden") == 0, "title extracted");
     674            1 :     ASSERT(tl_read_int32(&r) == 6666, "cursor past channelForbidden");
     675            1 :     tl_writer_free(&w);
     676              : }
     677              : 
     678            1 : static void test_extract_chat_no_access_hash(void) {
     679            1 :     TlWriter w; tl_writer_init(&w);
     680            1 :     tl_write_uint32(&w, TL_chat);
     681            1 :     tl_write_uint32(&w, 0);                    /* flags */
     682            1 :     tl_write_int64(&w, 42LL);
     683            1 :     tl_write_string(&w, "Legacy Group");
     684            1 :     tl_write_uint32(&w, CRC_chatPhotoEmpty);
     685            1 :     tl_write_int32(&w, 5);
     686            1 :     tl_write_int32(&w, 1700000000);
     687            1 :     tl_write_int32(&w, 1);
     688            1 :     tl_write_int32(&w, 7777);
     689            1 :     TlReader r = tl_reader_init(w.data, w.len);
     690              :     ChatSummary cs;
     691            1 :     ASSERT(tl_extract_chat(&r, &cs) == 0, "extract legacy chat");
     692            1 :     ASSERT(cs.id == 42LL, "chat id");
     693            1 :     ASSERT(cs.have_access_hash == 0, "legacy chat has no access_hash");
     694            1 :     ASSERT(strcmp(cs.title, "Legacy Group") == 0, "title");
     695            1 :     ASSERT(tl_read_int32(&r) == 7777, "cursor past chat");
     696            1 :     tl_writer_free(&w);
     697              : }
     698              : 
     699              : /* ---- ReplyMarkup skipper ---- */
     700              : 
     701              : #define CRC_replyKeyboardHide            0xa03e5b85u
     702              : #define CRC_replyKeyboardForceReply      0x86b40b08u
     703              : #define CRC_replyInlineMarkup_test       0x48a30254u
     704              : #define CRC_keyboardButtonRow_test       0x77608b83u
     705              : #define CRC_keyboardButtonUrl_test       0x258aff05u
     706              : #define CRC_keyboardButtonCallback_test  0x35bbdb6bu
     707              : 
     708            1 : static void test_skip_reply_markup_hide(void) {
     709            1 :     TlWriter w; tl_writer_init(&w);
     710            1 :     tl_write_uint32(&w, CRC_replyKeyboardHide);
     711            1 :     tl_write_uint32(&w, 0);                 /* flags */
     712            1 :     tl_write_int32 (&w, 777);                /* trailer */
     713            1 :     TlReader r = tl_reader_init(w.data, w.len);
     714            1 :     ASSERT(tl_skip_reply_markup(&r) == 0, "replyKeyboardHide skipped");
     715            1 :     ASSERT(tl_read_int32(&r) == 777, "cursor past hide");
     716            1 :     tl_writer_free(&w);
     717              : }
     718              : 
     719            1 : static void test_skip_reply_markup_force_reply_with_placeholder(void) {
     720            1 :     TlWriter w; tl_writer_init(&w);
     721            1 :     tl_write_uint32(&w, CRC_replyKeyboardForceReply);
     722            1 :     tl_write_uint32(&w, (1u << 3));         /* placeholder flag */
     723            1 :     tl_write_string(&w, "Type here...");
     724            1 :     tl_write_int32 (&w, 42);
     725            1 :     TlReader r = tl_reader_init(w.data, w.len);
     726            1 :     ASSERT(tl_skip_reply_markup(&r) == 0, "forceReply w/ placeholder skipped");
     727            1 :     ASSERT(tl_read_int32(&r) == 42, "cursor past forceReply");
     728            1 :     tl_writer_free(&w);
     729              : }
     730              : 
     731            1 : static void test_skip_reply_markup_inline_url_callback(void) {
     732            1 :     TlWriter w; tl_writer_init(&w);
     733              :     /* replyInlineMarkup with one row, two buttons (url + callback). */
     734            1 :     tl_write_uint32(&w, CRC_replyInlineMarkup_test);
     735            1 :     tl_write_uint32(&w, TL_vector);
     736            1 :     tl_write_uint32(&w, 1);                             /* row count */
     737            1 :     tl_write_uint32(&w, CRC_keyboardButtonRow_test);
     738            1 :     tl_write_uint32(&w, TL_vector);
     739            1 :     tl_write_uint32(&w, 2);                             /* button count */
     740            1 :     tl_write_uint32(&w, CRC_keyboardButtonUrl_test);
     741            1 :     tl_write_string(&w, "Open");
     742            1 :     tl_write_string(&w, "https://example.com");
     743            1 :     tl_write_uint32(&w, CRC_keyboardButtonCallback_test);
     744            1 :     tl_write_uint32(&w, 0);                             /* inner flags */
     745            1 :     tl_write_string(&w, "Click");
     746            1 :     tl_write_string(&w, "payload-bytes");
     747            1 :     tl_write_int32 (&w, 12345);                         /* trailer */
     748            1 :     TlReader r = tl_reader_init(w.data, w.len);
     749            1 :     ASSERT(tl_skip_reply_markup(&r) == 0, "inline url+callback skipped");
     750            1 :     ASSERT(tl_read_int32(&r) == 12345, "cursor past inline markup");
     751            1 :     tl_writer_free(&w);
     752              : }
     753              : 
     754            1 : static void test_skip_reply_markup_unknown_bails(void) {
     755            1 :     TlWriter w; tl_writer_init(&w);
     756            1 :     tl_write_uint32(&w, 0xdeadbeefu);       /* unknown ReplyMarkup variant */
     757            1 :     TlReader r = tl_reader_init(w.data, w.len);
     758            1 :     ASSERT(tl_skip_reply_markup(&r) == -1, "unknown variant rejected");
     759            1 :     tl_writer_free(&w);
     760              : }
     761              : 
     762            1 : static void test_skip_reply_markup_unknown_button_bails(void) {
     763            1 :     TlWriter w; tl_writer_init(&w);
     764            1 :     tl_write_uint32(&w, CRC_replyInlineMarkup_test);
     765            1 :     tl_write_uint32(&w, TL_vector);
     766            1 :     tl_write_uint32(&w, 1);
     767            1 :     tl_write_uint32(&w, CRC_keyboardButtonRow_test);
     768            1 :     tl_write_uint32(&w, TL_vector);
     769            1 :     tl_write_uint32(&w, 1);
     770            1 :     tl_write_uint32(&w, 0xfeedfacu);        /* unknown button */
     771            1 :     TlReader r = tl_reader_init(w.data, w.len);
     772            1 :     ASSERT(tl_skip_reply_markup(&r) == -1, "unknown button rejected");
     773            1 :     tl_writer_free(&w);
     774              : }
     775              : 
     776              : /* ---- MessageReactions skipper ---- */
     777              : 
     778              : #define CRC_messageReactions_test 0x4f2b9479u
     779              : #define CRC_reactionCount_test    0xa3d1cb80u
     780              : #define CRC_reactionEmoji_test    0x1b2286b8u
     781              : #define CRC_reactionCustomEmoji_t 0x8935fc73u
     782              : #define CRC_reactionEmpty_test    0x79f5d419u
     783              : 
     784            1 : static void test_skip_reactions_empty_results(void) {
     785            1 :     TlWriter w; tl_writer_init(&w);
     786            1 :     tl_write_uint32(&w, CRC_messageReactions_test);
     787            1 :     tl_write_uint32(&w, 0);                  /* flags */
     788            1 :     tl_write_uint32(&w, TL_vector);
     789            1 :     tl_write_uint32(&w, 0);                  /* empty results */
     790            1 :     tl_write_int32 (&w, 555);
     791            1 :     TlReader r = tl_reader_init(w.data, w.len);
     792            1 :     ASSERT(tl_skip_message_reactions(&r) == 0, "empty reactions");
     793            1 :     ASSERT(tl_read_int32(&r) == 555, "cursor past reactions");
     794            1 :     tl_writer_free(&w);
     795              : }
     796              : 
     797            1 : static void test_skip_reactions_emoji_and_custom(void) {
     798            1 :     TlWriter w; tl_writer_init(&w);
     799            1 :     tl_write_uint32(&w, CRC_messageReactions_test);
     800            1 :     tl_write_uint32(&w, 0);                  /* flags */
     801            1 :     tl_write_uint32(&w, TL_vector);
     802            1 :     tl_write_uint32(&w, 2);                  /* 2 reaction counts */
     803              : 
     804              :     /* #1: emoji reaction, chosen_order set. */
     805            1 :     tl_write_uint32(&w, CRC_reactionCount_test);
     806            1 :     tl_write_uint32(&w, (1u << 0));          /* chosen_order present */
     807            1 :     tl_write_int32 (&w, 1);                  /* chosen_order */
     808            1 :     tl_write_uint32(&w, CRC_reactionEmoji_test);
     809            1 :     tl_write_string(&w, "\xf0\x9f\x91\x8d");  /* 👍 */
     810            1 :     tl_write_int32 (&w, 7);                  /* count */
     811              : 
     812              :     /* #2: custom emoji reaction, no chosen_order. */
     813            1 :     tl_write_uint32(&w, CRC_reactionCount_test);
     814            1 :     tl_write_uint32(&w, 0);
     815            1 :     tl_write_uint32(&w, CRC_reactionCustomEmoji_t);
     816            1 :     tl_write_int64 (&w, 0x1234567890abcdefLL);
     817            1 :     tl_write_int32 (&w, 3);
     818              : 
     819            1 :     tl_write_int32 (&w, 9999);               /* trailer */
     820            1 :     TlReader r = tl_reader_init(w.data, w.len);
     821            1 :     ASSERT(tl_skip_message_reactions(&r) == 0, "reactions skipped");
     822            1 :     ASSERT(tl_read_int32(&r) == 9999, "cursor past reactions");
     823            1 :     tl_writer_free(&w);
     824              : }
     825              : 
     826              : /* ---- MessageReplies skipper ---- */
     827              : 
     828              : #define CRC_messageReplies_test 0x83d60fc2u
     829              : 
     830            1 : static void test_skip_message_replies_minimal(void) {
     831            1 :     TlWriter w; tl_writer_init(&w);
     832            1 :     tl_write_uint32(&w, CRC_messageReplies_test);
     833            1 :     tl_write_uint32(&w, 0);                     /* flags = 0 */
     834            1 :     tl_write_int32 (&w, 5);                     /* replies */
     835            1 :     tl_write_int32 (&w, 100);                   /* replies_pts */
     836            1 :     tl_write_int32 (&w, 0xCAFE);                /* trailer */
     837            1 :     TlReader r = tl_reader_init(w.data, w.len);
     838            1 :     ASSERT(tl_skip_message_replies(&r) == 0, "minimal replies");
     839            1 :     ASSERT(tl_read_int32(&r) == 0xCAFE, "cursor past replies");
     840            1 :     tl_writer_free(&w);
     841              : }
     842              : 
     843            1 : static void test_skip_message_replies_with_commenters(void) {
     844            1 :     TlWriter w; tl_writer_init(&w);
     845            1 :     tl_write_uint32(&w, CRC_messageReplies_test);
     846            1 :     tl_write_uint32(&w, (1u << 0) | (1u << 1) | (1u << 2));
     847            1 :     tl_write_int32 (&w, 9);                     /* replies */
     848            1 :     tl_write_int32 (&w, 200);                   /* replies_pts */
     849              :     /* recent_repliers:Vector<Peer> = 2 peers */
     850            1 :     tl_write_uint32(&w, TL_vector);
     851            1 :     tl_write_uint32(&w, 2);
     852            1 :     tl_write_uint32(&w, TL_peerUser);    tl_write_int64(&w, 111LL);
     853            1 :     tl_write_uint32(&w, TL_peerChannel); tl_write_int64(&w, 222LL);
     854            1 :     tl_write_int64 (&w, 0xDEADBEEFLL);          /* channel_id */
     855            1 :     tl_write_int32 (&w, 2048);                  /* max_id */
     856            1 :     tl_write_int32 (&w, 1337);                  /* trailer */
     857            1 :     TlReader r = tl_reader_init(w.data, w.len);
     858            1 :     ASSERT(tl_skip_message_replies(&r) == 0, "full replies skipped");
     859            1 :     ASSERT(tl_read_int32(&r) == 1337, "cursor past replies");
     860            1 :     tl_writer_free(&w);
     861              : }
     862              : 
     863            1 : static void test_skip_message_replies_unknown_crc_bails(void) {
     864            1 :     TlWriter w; tl_writer_init(&w);
     865            1 :     tl_write_uint32(&w, 0xbadbadbu);
     866            1 :     TlReader r = tl_reader_init(w.data, w.len);
     867            1 :     ASSERT(tl_skip_message_replies(&r) == -1, "unknown crc rejected");
     868            1 :     tl_writer_free(&w);
     869              : }
     870              : 
     871              : /* ---- FactCheck skipper ---- */
     872              : 
     873              : #define CRC_factCheck_test          0xb89bfccfu
     874              : #define CRC_textWithEntities_test   0x751f3146u
     875              : 
     876            1 : static void test_skip_factcheck_need_check_only(void) {
     877            1 :     TlWriter w; tl_writer_init(&w);
     878            1 :     tl_write_uint32(&w, CRC_factCheck_test);
     879            1 :     tl_write_uint32(&w, (1u << 0));              /* only need_check */
     880            1 :     tl_write_int64 (&w, 0x1234567890LL);         /* hash */
     881            1 :     tl_write_int32 (&w, 777);
     882            1 :     TlReader r = tl_reader_init(w.data, w.len);
     883            1 :     ASSERT(tl_skip_factcheck(&r) == 0, "factcheck need_check skipped");
     884            1 :     ASSERT(tl_read_int32(&r) == 777, "cursor past factcheck");
     885            1 :     tl_writer_free(&w);
     886              : }
     887              : 
     888            1 : static void test_skip_factcheck_with_text(void) {
     889            1 :     TlWriter w; tl_writer_init(&w);
     890            1 :     tl_write_uint32(&w, CRC_factCheck_test);
     891            1 :     tl_write_uint32(&w, (1u << 1));              /* country + text set */
     892            1 :     tl_write_string(&w, "HU");
     893            1 :     tl_write_uint32(&w, CRC_textWithEntities_test);
     894            1 :     tl_write_string(&w, "Fact-checked claim");
     895            1 :     tl_write_uint32(&w, TL_vector);
     896            1 :     tl_write_uint32(&w, 1);
     897            1 :     tl_write_uint32(&w, 0xbd610bc9u);           /* messageEntityBold */
     898            1 :     tl_write_int32 (&w, 0);
     899            1 :     tl_write_int32 (&w, 4);
     900            1 :     tl_write_int64 (&w, 0xabcdefLL);             /* hash */
     901            1 :     tl_write_int32 (&w, 0x1234);
     902            1 :     TlReader r = tl_reader_init(w.data, w.len);
     903            1 :     ASSERT(tl_skip_factcheck(&r) == 0, "factcheck with text skipped");
     904            1 :     ASSERT(tl_read_int32(&r) == 0x1234, "cursor past factcheck");
     905            1 :     tl_writer_free(&w);
     906              : }
     907              : 
     908              : /* ---- messageMediaPoll ---- */
     909              : 
     910              : #define CRC_messageMediaPoll_t  0x4bd6e798u
     911              : #define CRC_poll_t              0x58747131u
     912              : #define CRC_pollAnswer_t        0x6ca9c2e9u
     913              : #define CRC_pollResults_t       0x7adc669du
     914              : #define CRC_textWithEntities_p  0x751f3146u
     915              : 
     916            5 : static void write_textWithEntities(TlWriter *w, const char *text) {
     917            5 :     tl_write_uint32(w, CRC_textWithEntities_p);
     918            5 :     tl_write_string(w, text);
     919            5 :     tl_write_uint32(w, TL_vector);
     920            5 :     tl_write_uint32(w, 0);                       /* no entities */
     921            5 : }
     922              : 
     923            1 : static void test_skip_media_poll_minimal(void) {
     924            1 :     TlWriter w; tl_writer_init(&w);
     925            1 :     tl_write_uint32(&w, CRC_messageMediaPoll_t);
     926              :     /* Poll */
     927            1 :     tl_write_uint32(&w, CRC_poll_t);
     928            1 :     tl_write_uint32(&w, 0);                      /* flags */
     929            1 :     tl_write_int64 (&w, 1234567LL);              /* id */
     930            1 :     write_textWithEntities(&w, "Favourite colour?");
     931            1 :     tl_write_uint32(&w, TL_vector);
     932            1 :     tl_write_uint32(&w, 2);
     933            1 :     tl_write_uint32(&w, CRC_pollAnswer_t);
     934            1 :     write_textWithEntities(&w, "Blue");
     935            1 :     tl_write_string(&w, "b");
     936            1 :     tl_write_uint32(&w, CRC_pollAnswer_t);
     937            1 :     write_textWithEntities(&w, "Red");
     938            1 :     tl_write_string(&w, "r");
     939              : 
     940              :     /* PollResults: empty flags = no results, no voters, no solution */
     941            1 :     tl_write_uint32(&w, CRC_pollResults_t);
     942            1 :     tl_write_uint32(&w, 0);                      /* flags */
     943            1 :     tl_write_int32 (&w, 0xC0FFEE);
     944            1 :     TlReader r = tl_reader_init(w.data, w.len);
     945            1 :     MediaInfo mi = {0};
     946            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "poll minimal ok");
     947            1 :     ASSERT(mi.kind == MEDIA_POLL, "kind = poll");
     948            1 :     ASSERT(tl_read_int32(&r) == 0xC0FFEE, "cursor past poll");
     949            1 :     tl_writer_free(&w);
     950              : }
     951              : 
     952            1 : static void test_skip_media_poll_with_results(void) {
     953            1 :     TlWriter w; tl_writer_init(&w);
     954            1 :     tl_write_uint32(&w, CRC_messageMediaPoll_t);
     955              :     /* Poll — quiz w/ close_date (flags.3 + flags.5) */
     956            1 :     tl_write_uint32(&w, CRC_poll_t);
     957            1 :     tl_write_uint32(&w, (1u << 3) | (1u << 5));
     958            1 :     tl_write_int64 (&w, 9LL);
     959            1 :     write_textWithEntities(&w, "2+2?");
     960            1 :     tl_write_uint32(&w, TL_vector);
     961            1 :     tl_write_uint32(&w, 1);
     962            1 :     tl_write_uint32(&w, CRC_pollAnswer_t);
     963            1 :     write_textWithEntities(&w, "4");
     964            1 :     tl_write_string(&w, "1");
     965            1 :     tl_write_int32 (&w, 1700000000);            /* close_date */
     966              : 
     967              :     /* PollResults with results + total_voters + recent_voters + solution */
     968            1 :     tl_write_uint32(&w, CRC_pollResults_t);
     969            1 :     tl_write_uint32(&w, (1u << 1) | (1u << 2) | (1u << 3) | (1u << 4));
     970              :     /* results */
     971            1 :     tl_write_uint32(&w, TL_vector);
     972            1 :     tl_write_uint32(&w, 1);
     973            1 :     tl_write_uint32(&w, 0x3b6ddad2u);            /* pollAnswerVoters */
     974            1 :     tl_write_uint32(&w, (1u << 0) | (1u << 1));  /* chosen + correct */
     975            1 :     tl_write_string(&w, "1");                    /* option:bytes */
     976            1 :     tl_write_int32 (&w, 42);                     /* voters */
     977              :     /* total_voters */
     978            1 :     tl_write_int32 (&w, 42);
     979              :     /* recent_voters */
     980            1 :     tl_write_uint32(&w, TL_vector);
     981            1 :     tl_write_uint32(&w, 1);
     982            1 :     tl_write_uint32(&w, TL_peerUser);
     983            1 :     tl_write_int64 (&w, 999LL);
     984              :     /* solution + solution_entities */
     985            1 :     tl_write_string(&w, "Trivia says 4.");
     986            1 :     tl_write_uint32(&w, TL_vector);
     987            1 :     tl_write_uint32(&w, 0);
     988              : 
     989            1 :     tl_write_int32 (&w, 0xDEAD);
     990            1 :     TlReader r = tl_reader_init(w.data, w.len);
     991            1 :     MediaInfo mi = {0};
     992            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "poll w/ results ok");
     993            1 :     ASSERT(mi.kind == MEDIA_POLL, "kind = poll");
     994            1 :     ASSERT(tl_read_int32(&r) == 0xDEAD, "cursor past poll");
     995            1 :     tl_writer_free(&w);
     996              : }
     997              : 
     998              : /* ---- messageMediaInvoice / Story / Giveaway ---- */
     999              : 
    1000              : #define CRC_messageMediaInvoice_t  0xf6a548d3u
    1001              : #define CRC_messageMediaStory_t    0x68cb6283u
    1002              : #define CRC_messageMediaGiveaway_t 0xaa073beeu
    1003              : 
    1004            1 : static void test_skip_media_invoice_minimal(void) {
    1005            1 :     TlWriter w; tl_writer_init(&w);
    1006            1 :     tl_write_uint32(&w, CRC_messageMediaInvoice_t);
    1007            1 :     tl_write_uint32(&w, 0);                       /* flags */
    1008            1 :     tl_write_string(&w, "Pizza Margherita");
    1009            1 :     tl_write_string(&w, "1 piece");
    1010            1 :     tl_write_string(&w, "EUR");
    1011            1 :     tl_write_int64 (&w, 999);                     /* cents */
    1012            1 :     tl_write_string(&w, "start_abc");
    1013            1 :     tl_write_int32 (&w, 0xBEEF);
    1014            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1015            1 :     MediaInfo mi = {0};
    1016            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "invoice minimal ok");
    1017            1 :     ASSERT(mi.kind == MEDIA_INVOICE, "kind=invoice");
    1018            1 :     ASSERT(tl_read_int32(&r) == 0xBEEF, "cursor past invoice");
    1019            1 :     tl_writer_free(&w);
    1020              : }
    1021              : 
    1022            1 : static void test_skip_media_invoice_photo_bails(void) {
    1023            1 :     TlWriter w; tl_writer_init(&w);
    1024            1 :     tl_write_uint32(&w, CRC_messageMediaInvoice_t);
    1025            1 :     tl_write_uint32(&w, (1u << 0));               /* photo flag */
    1026            1 :     tl_write_string(&w, "T"); tl_write_string(&w, "D");
    1027            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1028            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == -1,
    1029              :            "invoice photo still bails");
    1030            1 :     tl_writer_free(&w);
    1031              : }
    1032              : 
    1033            1 : static void test_skip_media_story_minimal(void) {
    1034            1 :     TlWriter w; tl_writer_init(&w);
    1035            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    1036            1 :     tl_write_uint32(&w, 0);                       /* flags */
    1037            1 :     tl_write_uint32(&w, TL_peerUser);
    1038            1 :     tl_write_int64 (&w, 42LL);
    1039            1 :     tl_write_int32 (&w, 77);                      /* story id */
    1040            1 :     tl_write_int32 (&w, 0x1010);
    1041            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1042            1 :     MediaInfo mi = {0};
    1043            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "story minimal ok");
    1044            1 :     ASSERT(mi.kind == MEDIA_STORY, "kind=story");
    1045            1 :     ASSERT(tl_read_int32(&r) == 0x1010, "cursor past story");
    1046            1 :     tl_writer_free(&w);
    1047              : }
    1048              : 
    1049            1 : static void test_skip_media_giveaway_minimal(void) {
    1050            1 :     TlWriter w; tl_writer_init(&w);
    1051            1 :     tl_write_uint32(&w, CRC_messageMediaGiveaway_t);
    1052            1 :     tl_write_uint32(&w, 0);                       /* flags */
    1053              :     /* channels vector */
    1054            1 :     tl_write_uint32(&w, TL_vector);
    1055            1 :     tl_write_uint32(&w, 2);
    1056            1 :     tl_write_int64 (&w, 10LL);
    1057            1 :     tl_write_int64 (&w, 20LL);
    1058            1 :     tl_write_int32 (&w, 5);                       /* quantity */
    1059            1 :     tl_write_int32 (&w, 1700000999);              /* until_date */
    1060            1 :     tl_write_int32 (&w, 0xFACE);
    1061            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1062            1 :     MediaInfo mi = {0};
    1063            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "giveaway minimal ok");
    1064            1 :     ASSERT(mi.kind == MEDIA_GIVEAWAY, "kind=giveaway");
    1065            1 :     ASSERT(tl_read_int32(&r) == 0xFACE, "cursor past giveaway");
    1066            1 :     tl_writer_free(&w);
    1067              : }
    1068              : 
    1069            1 : static void test_skip_media_giveaway_countries_prize(void) {
    1070            1 :     TlWriter w; tl_writer_init(&w);
    1071            1 :     tl_write_uint32(&w, CRC_messageMediaGiveaway_t);
    1072            1 :     tl_write_uint32(&w, (1u << 1) | (1u << 3) | (1u << 4));
    1073            1 :     tl_write_uint32(&w, TL_vector);
    1074            1 :     tl_write_uint32(&w, 1);
    1075            1 :     tl_write_int64 (&w, 99LL);
    1076              :     /* countries_iso2 */
    1077            1 :     tl_write_uint32(&w, TL_vector);
    1078            1 :     tl_write_uint32(&w, 2);
    1079            1 :     tl_write_string(&w, "HU");
    1080            1 :     tl_write_string(&w, "DE");
    1081              :     /* prize_description */
    1082            1 :     tl_write_string(&w, "Premium subscription");
    1083            1 :     tl_write_int32 (&w, 10);                      /* quantity */
    1084            1 :     tl_write_int32 (&w, 3);                       /* months (flag.4) */
    1085            1 :     tl_write_int32 (&w, 1700001000);              /* until_date */
    1086            1 :     tl_write_int32 (&w, 0xEEEE);
    1087            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1088            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "giveaway rich ok");
    1089            1 :     ASSERT(tl_read_int32(&r) == 0xEEEE, "cursor past giveaway");
    1090            1 :     tl_writer_free(&w);
    1091              : }
    1092              : 
    1093              : /* ---- messageMediaDocument with full Document ---- */
    1094              : 
    1095              : #define CRC_messageMediaDocument_t    0x4cf4d72du
    1096              : #define CRC_document_t                0x8fd4c4d8u
    1097              : #define CRC_documentAttributeFilename_t 0x15590068u
    1098              : 
    1099            1 : static void test_skip_media_document_full(void) {
    1100            1 :     TlWriter w; tl_writer_init(&w);
    1101            1 :     tl_write_uint32(&w, CRC_messageMediaDocument_t);
    1102            1 :     tl_write_uint32(&w, (1u << 0));                /* document present */
    1103            1 :     tl_write_uint32(&w, CRC_document_t);
    1104            1 :     tl_write_uint32(&w, 0);                        /* flags (no thumbs) */
    1105            1 :     tl_write_int64 (&w, 5551212LL);
    1106            1 :     tl_write_int64 (&w, 0xCAFECAFEDEADBEEFLL);
    1107            1 :     uint8_t fr[4] = { 0x01, 0x02, 0x03, 0x04 };
    1108            1 :     tl_write_bytes (&w, fr, sizeof(fr));
    1109            1 :     tl_write_int32 (&w, 1700000000);               /* date */
    1110            1 :     tl_write_string(&w, "application/pdf");
    1111            1 :     tl_write_int64 (&w, 1024LL);                   /* size */
    1112            1 :     tl_write_int32 (&w, 2);                        /* dc_id */
    1113            1 :     tl_write_uint32(&w, TL_vector);
    1114            1 :     tl_write_uint32(&w, 1);                        /* one attribute */
    1115            1 :     tl_write_uint32(&w, CRC_documentAttributeFilename_t);
    1116            1 :     tl_write_string(&w, "report.pdf");
    1117            1 :     tl_write_int32 (&w, 0xfeed);                   /* trailer */
    1118            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1119            1 :     MediaInfo mi = {0};
    1120            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "document extract ok");
    1121            1 :     ASSERT(mi.kind == MEDIA_DOCUMENT, "kind=document");
    1122            1 :     ASSERT(mi.document_id == 5551212LL, "document_id captured");
    1123            1 :     ASSERT(mi.access_hash == (int64_t)0xCAFECAFEDEADBEEFLL, "access_hash");
    1124            1 :     ASSERT(mi.file_reference_len == 4, "file_reference_len");
    1125            1 :     ASSERT(mi.file_reference[0] == 0x01 && mi.file_reference[3] == 0x04,
    1126              :            "file_reference bytes");
    1127            1 :     ASSERT(mi.document_size == 1024, "size captured");
    1128            1 :     ASSERT(strcmp(mi.document_mime, "application/pdf") == 0, "mime");
    1129            1 :     ASSERT(strcmp(mi.document_filename, "report.pdf") == 0, "filename");
    1130            1 :     ASSERT(tl_read_int32(&r) == 0xfeed, "cursor past document");
    1131            1 :     tl_writer_free(&w);
    1132              : }
    1133              : 
    1134              : /* LIM-02: Document with thumbs (Vector<PhotoSize>) set via flags.0. */
    1135              : #define CRC_photoSize_t              0x75c78e60u
    1136            1 : static void test_skip_media_document_with_thumbs(void) {
    1137            1 :     TlWriter w; tl_writer_init(&w);
    1138            1 :     tl_write_uint32(&w, CRC_messageMediaDocument_t);
    1139            1 :     tl_write_uint32(&w, (1u << 0));
    1140            1 :     tl_write_uint32(&w, CRC_document_t);
    1141            1 :     tl_write_uint32(&w, (1u << 0));                /* thumbs present */
    1142            1 :     tl_write_int64 (&w, 777LL);
    1143            1 :     tl_write_int64 (&w, 888LL);
    1144            1 :     uint8_t fr[2] = { 0xaa, 0xbb };
    1145            1 :     tl_write_bytes (&w, fr, sizeof(fr));
    1146            1 :     tl_write_int32 (&w, 1700000000);
    1147            1 :     tl_write_string(&w, "video/mp4");
    1148            1 :     tl_write_int64 (&w, 42LL);
    1149              :     /* thumbs: [photoSize "m" 320x240 size=1234] */
    1150            1 :     tl_write_uint32(&w, TL_vector);
    1151            1 :     tl_write_uint32(&w, 1);
    1152            1 :     tl_write_uint32(&w, CRC_photoSize_t);
    1153            1 :     tl_write_string(&w, "m");
    1154            1 :     tl_write_int32 (&w, 320);
    1155            1 :     tl_write_int32 (&w, 240);
    1156            1 :     tl_write_int32 (&w, 1234);
    1157            1 :     tl_write_int32 (&w, 4);                        /* dc_id */
    1158            1 :     tl_write_uint32(&w, TL_vector);
    1159            1 :     tl_write_uint32(&w, 0);                        /* no attributes */
    1160            1 :     tl_write_int32 (&w, 0x1234);
    1161            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1162            1 :     MediaInfo mi = {0};
    1163            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0,
    1164              :            "document with thumbs iterates");
    1165            1 :     ASSERT(mi.kind == MEDIA_DOCUMENT, "kind=document");
    1166            1 :     ASSERT(mi.document_id == 777LL, "id");
    1167            1 :     ASSERT(tl_read_int32(&r) == 0x1234, "cursor past document");
    1168            1 :     tl_writer_free(&w);
    1169              : }
    1170              : 
    1171              : /* LIM-02: Document with video_thumbs (Vector<VideoSize>). */
    1172              : #define CRC_videoSize_t              0xde33b094u
    1173            1 : static void test_skip_media_document_with_video_thumbs(void) {
    1174            1 :     TlWriter w; tl_writer_init(&w);
    1175            1 :     tl_write_uint32(&w, CRC_messageMediaDocument_t);
    1176            1 :     tl_write_uint32(&w, (1u << 0));
    1177            1 :     tl_write_uint32(&w, CRC_document_t);
    1178            1 :     tl_write_uint32(&w, (1u << 1));                /* video_thumbs present */
    1179            1 :     tl_write_int64 (&w, 99LL);
    1180            1 :     tl_write_int64 (&w, 100LL);
    1181            1 :     uint8_t fr[1] = { 0x01 };
    1182            1 :     tl_write_bytes (&w, fr, sizeof(fr));
    1183            1 :     tl_write_int32 (&w, 1700000000);
    1184            1 :     tl_write_string(&w, "video/mp4");
    1185            1 :     tl_write_int64 (&w, 999LL);
    1186              :     /* video_thumbs: [videoSize flags=0 "u" 100x100 size=50] */
    1187            1 :     tl_write_uint32(&w, TL_vector);
    1188            1 :     tl_write_uint32(&w, 1);
    1189            1 :     tl_write_uint32(&w, CRC_videoSize_t);
    1190            1 :     tl_write_uint32(&w, 0);
    1191            1 :     tl_write_string(&w, "u");
    1192            1 :     tl_write_int32 (&w, 100);
    1193            1 :     tl_write_int32 (&w, 100);
    1194            1 :     tl_write_int32 (&w, 50);
    1195            1 :     tl_write_int32 (&w, 2);                        /* dc_id */
    1196            1 :     tl_write_uint32(&w, TL_vector);
    1197            1 :     tl_write_uint32(&w, 0);
    1198            1 :     tl_write_int32 (&w, 0x5678);
    1199            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1200            1 :     MediaInfo mi = {0};
    1201            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0,
    1202              :            "document with video_thumbs iterates");
    1203            1 :     ASSERT(mi.kind == MEDIA_DOCUMENT, "kind=document");
    1204            1 :     ASSERT(tl_read_int32(&r) == 0x5678, "cursor past document");
    1205            1 :     tl_writer_free(&w);
    1206              : }
    1207              : 
    1208              : /* LIM-02: Document with documentAttributeSticker (inputStickerSetID). */
    1209              : #define CRC_documentAttributeSticker_t  0x6319d612u
    1210              : #define CRC_inputStickerSetID_t         0x9de7a269u
    1211              : #define CRC_inputStickerSetEmpty_t      0xffb62b95u
    1212              : #define CRC_maskCoords_t                0xaed6dbb2u
    1213            1 : static void test_skip_media_document_sticker_attr(void) {
    1214            1 :     TlWriter w; tl_writer_init(&w);
    1215            1 :     tl_write_uint32(&w, CRC_messageMediaDocument_t);
    1216            1 :     tl_write_uint32(&w, (1u << 0));
    1217            1 :     tl_write_uint32(&w, CRC_document_t);
    1218            1 :     tl_write_uint32(&w, 0);                        /* no thumbs */
    1219            1 :     tl_write_int64 (&w, 1);
    1220            1 :     tl_write_int64 (&w, 2);
    1221            1 :     uint8_t fr[1] = { 0 };
    1222            1 :     tl_write_bytes (&w, fr, sizeof(fr));
    1223            1 :     tl_write_int32 (&w, 1700000000);
    1224            1 :     tl_write_string(&w, "image/webp");
    1225            1 :     tl_write_int64 (&w, 5000);
    1226            1 :     tl_write_int32 (&w, 2);
    1227            1 :     tl_write_uint32(&w, TL_vector);
    1228            1 :     tl_write_uint32(&w, 1);
    1229            1 :     tl_write_uint32(&w, CRC_documentAttributeSticker_t);
    1230            1 :     tl_write_uint32(&w, 0);                        /* no mask, no mask_coords */
    1231            1 :     tl_write_string(&w, "🦊");                     /* alt */
    1232            1 :     tl_write_uint32(&w, CRC_inputStickerSetID_t);
    1233            1 :     tl_write_int64 (&w, 42);
    1234            1 :     tl_write_int64 (&w, 43);
    1235            1 :     tl_write_int32 (&w, 0x9ABC);
    1236            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1237            1 :     MediaInfo mi = {0};
    1238            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0,
    1239              :            "document with Sticker attr iterates");
    1240            1 :     ASSERT(mi.kind == MEDIA_DOCUMENT, "kind=document");
    1241            1 :     ASSERT(tl_read_int32(&r) == 0x9ABC, "cursor past document");
    1242            1 :     tl_writer_free(&w);
    1243              : }
    1244              : 
    1245              : /* LIM-02: Document with documentAttributeSticker + mask_coords. */
    1246            1 : static void test_skip_media_document_sticker_with_mask(void) {
    1247            1 :     TlWriter w; tl_writer_init(&w);
    1248            1 :     tl_write_uint32(&w, CRC_messageMediaDocument_t);
    1249            1 :     tl_write_uint32(&w, (1u << 0));
    1250            1 :     tl_write_uint32(&w, CRC_document_t);
    1251            1 :     tl_write_uint32(&w, 0);
    1252            1 :     tl_write_int64 (&w, 1);
    1253            1 :     tl_write_int64 (&w, 2);
    1254            1 :     uint8_t fr[1] = { 0 };
    1255            1 :     tl_write_bytes (&w, fr, sizeof(fr));
    1256            1 :     tl_write_int32 (&w, 1700000000);
    1257            1 :     tl_write_string(&w, "image/webp");
    1258            1 :     tl_write_int64 (&w, 5000);
    1259            1 :     tl_write_int32 (&w, 2);
    1260            1 :     tl_write_uint32(&w, TL_vector);
    1261            1 :     tl_write_uint32(&w, 1);
    1262            1 :     tl_write_uint32(&w, CRC_documentAttributeSticker_t);
    1263            1 :     tl_write_uint32(&w, (1u << 0));                /* mask_coords present */
    1264            1 :     tl_write_string(&w, "😀");
    1265            1 :     tl_write_uint32(&w, CRC_inputStickerSetEmpty_t);
    1266            1 :     tl_write_uint32(&w, CRC_maskCoords_t);
    1267            1 :     tl_write_int32 (&w, 1);                        /* n */
    1268            1 :     tl_write_double(&w, 0.5);                      /* x */
    1269            1 :     tl_write_double(&w, 0.25);                     /* y */
    1270            1 :     tl_write_double(&w, 1.5);                      /* zoom */
    1271            1 :     tl_write_int32 (&w, 0xDEF0);
    1272            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1273            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    1274              :            "sticker + mask_coords iterates");
    1275            1 :     ASSERT(tl_read_int32(&r) == 0xDEF0, "cursor past document");
    1276            1 :     tl_writer_free(&w);
    1277              : }
    1278              : 
    1279              : /* LIM-02: Document with documentAttributeCustomEmoji. */
    1280              : #define CRC_documentAttributeCustomEmoji_t  0xfd149899u
    1281              : #define CRC_inputStickerSetShortName_t      0x861cc8a0u
    1282            1 : static void test_skip_media_document_custom_emoji_attr(void) {
    1283            1 :     TlWriter w; tl_writer_init(&w);
    1284            1 :     tl_write_uint32(&w, CRC_messageMediaDocument_t);
    1285            1 :     tl_write_uint32(&w, (1u << 0));
    1286            1 :     tl_write_uint32(&w, CRC_document_t);
    1287            1 :     tl_write_uint32(&w, 0);
    1288            1 :     tl_write_int64 (&w, 11);
    1289            1 :     tl_write_int64 (&w, 22);
    1290            1 :     uint8_t fr[1] = { 0 };
    1291            1 :     tl_write_bytes (&w, fr, sizeof(fr));
    1292            1 :     tl_write_int32 (&w, 1700000000);
    1293            1 :     tl_write_string(&w, "image/webp");
    1294            1 :     tl_write_int64 (&w, 123);
    1295            1 :     tl_write_int32 (&w, 2);
    1296            1 :     tl_write_uint32(&w, TL_vector);
    1297            1 :     tl_write_uint32(&w, 1);
    1298            1 :     tl_write_uint32(&w, CRC_documentAttributeCustomEmoji_t);
    1299            1 :     tl_write_uint32(&w, 0);
    1300            1 :     tl_write_string(&w, "⚡");
    1301            1 :     tl_write_uint32(&w, CRC_inputStickerSetShortName_t);
    1302            1 :     tl_write_string(&w, "animated_pack");
    1303            1 :     tl_write_int32 (&w, 0xFACE);
    1304            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1305            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    1306              :            "document with CustomEmoji attr iterates");
    1307            1 :     ASSERT(tl_read_int32(&r) == 0xFACE, "cursor past document");
    1308            1 :     tl_writer_free(&w);
    1309              : }
    1310              : 
    1311              : /* ---- MessageMediaWebPage ---- */
    1312              : 
    1313              : #define CRC_messageMediaWebPage_test 0xddf8c26eu
    1314              : #define CRC_webPage_test             0xe89c45b2u
    1315              : #define CRC_webPageEmpty_test        0xeb1477e8u
    1316              : #define CRC_webPagePending_test      0xb0d13e47u
    1317              : 
    1318            1 : static void test_skip_media_webpage_empty(void) {
    1319            1 :     TlWriter w; tl_writer_init(&w);
    1320            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1321            1 :     tl_write_uint32(&w, 0);                     /* outer flags */
    1322            1 :     tl_write_uint32(&w, CRC_webPageEmpty_test);
    1323            1 :     tl_write_uint32(&w, (1u << 0));             /* url flag */
    1324            1 :     tl_write_int64 (&w, 0x123LL);
    1325            1 :     tl_write_string(&w, "https://example.com");
    1326            1 :     tl_write_int32 (&w, 42);
    1327            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1328            1 :     MediaInfo mi = {0};
    1329            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "webpage empty ok");
    1330            1 :     ASSERT(mi.kind == MEDIA_WEBPAGE, "kind == webpage");
    1331            1 :     ASSERT(tl_read_int32(&r) == 42, "cursor past webpage");
    1332            1 :     tl_writer_free(&w);
    1333              : }
    1334              : 
    1335            1 : static void test_skip_media_webpage_rich(void) {
    1336              :     /* webPage with url + display_url + hash + site_name + title. */
    1337            1 :     TlWriter w; tl_writer_init(&w);
    1338            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1339            1 :     tl_write_uint32(&w, 0);                     /* outer flags */
    1340            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1341            1 :     tl_write_uint32(&w, (1u << 1) | (1u << 2)); /* site_name + title */
    1342            1 :     tl_write_int64 (&w, 0xABC);
    1343            1 :     tl_write_string(&w, "https://example.com/page");
    1344            1 :     tl_write_string(&w, "example.com/page");
    1345            1 :     tl_write_int32 (&w, 12345);                 /* hash */
    1346            1 :     tl_write_string(&w, "Example");             /* site_name */
    1347            1 :     tl_write_string(&w, "A Title");             /* title */
    1348            1 :     tl_write_int32 (&w, 0xdead);                /* trailer */
    1349            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1350            1 :     MediaInfo mi = {0};
    1351            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "webpage rich ok");
    1352            1 :     ASSERT(mi.kind == MEDIA_WEBPAGE, "kind == webpage");
    1353            1 :     ASSERT(tl_read_int32(&r) == 0xdead, "cursor past webpage");
    1354            1 :     tl_writer_free(&w);
    1355              : }
    1356              : 
    1357            1 : static void test_skip_media_webpage_pending(void) {
    1358            1 :     TlWriter w; tl_writer_init(&w);
    1359            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1360            1 :     tl_write_uint32(&w, 0);
    1361            1 :     tl_write_uint32(&w, CRC_webPagePending_test);
    1362            1 :     tl_write_uint32(&w, (1u << 0));             /* url */
    1363            1 :     tl_write_int64 (&w, 0x999);
    1364            1 :     tl_write_string(&w, "https://pending.example");
    1365            1 :     tl_write_int32 (&w, 1700000000);
    1366            1 :     tl_write_int32 (&w, 0xCAFE);
    1367            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1368            1 :     MediaInfo mi = {0};
    1369            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "webpage pending ok");
    1370            1 :     ASSERT(tl_read_int32(&r) == 0xCAFE, "cursor past pending");
    1371            1 :     tl_writer_free(&w);
    1372              : }
    1373              : 
    1374              : /* TUI-11: webPage with cached_page set, Page contains two simple
    1375              :  * PageBlock rows (title + paragraph). Skipper must iterate past them. */
    1376              : #define CRC_page_test                0x98657f0du
    1377              : #define CRC_pageBlockTitle_test      0x70abc3fdu
    1378              : #define CRC_pageBlockParagraph_test  0x467a0766u
    1379              : #define CRC_pageBlockDivider_test    0xdb20b188u
    1380              : #define CRC_pageBlockAnchor_test     0xce0d37b0u
    1381              : #define CRC_pageBlockAuthorDate_t    0xbaafe5e0u
    1382              : #define CRC_textPlain_test           0x744694e0u
    1383              : #define CRC_textEmpty_test           0xdc3d824fu
    1384              : #define CRC_textBold_test            0x6724abc4u
    1385              : #define CRC_textConcat_test          0x7e6260d7u
    1386              : 
    1387            1 : static void test_skip_media_webpage_cached_page_simple(void) {
    1388            1 :     TlWriter w; tl_writer_init(&w);
    1389            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1390            1 :     tl_write_uint32(&w, 0);
    1391            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1392            1 :     tl_write_uint32(&w, (1u << 10));            /* cached_page flag */
    1393            1 :     tl_write_int64 (&w, 0x111);
    1394            1 :     tl_write_string(&w, "u");
    1395            1 :     tl_write_string(&w, "u");
    1396            1 :     tl_write_int32 (&w, 1);                     /* hash */
    1397              :     /* cached_page body — page#98657f0d. */
    1398            1 :     tl_write_uint32(&w, CRC_page_test);
    1399            1 :     tl_write_uint32(&w, 0);                     /* page flags */
    1400            1 :     tl_write_string(&w, "https://x/a");         /* url */
    1401              :     /* blocks: [Title(plain), Paragraph(bold(plain)), Divider, Anchor] */
    1402            1 :     tl_write_uint32(&w, TL_vector);
    1403            1 :     tl_write_uint32(&w, 4);
    1404              :     /* block 0: Title(textPlain) */
    1405            1 :     tl_write_uint32(&w, CRC_pageBlockTitle_test);
    1406            1 :     tl_write_uint32(&w, CRC_textPlain_test);
    1407            1 :     tl_write_string(&w, "The Article");
    1408              :     /* block 1: Paragraph(textBold(textPlain)) */
    1409            1 :     tl_write_uint32(&w, CRC_pageBlockParagraph_test);
    1410            1 :     tl_write_uint32(&w, CRC_textBold_test);
    1411            1 :     tl_write_uint32(&w, CRC_textPlain_test);
    1412            1 :     tl_write_string(&w, "Body");
    1413              :     /* block 2: Divider */
    1414            1 :     tl_write_uint32(&w, CRC_pageBlockDivider_test);
    1415              :     /* block 3: Anchor */
    1416            1 :     tl_write_uint32(&w, CRC_pageBlockAnchor_test);
    1417            1 :     tl_write_string(&w, "section-1");
    1418              :     /* photos: empty */
    1419            1 :     tl_write_uint32(&w, TL_vector);
    1420            1 :     tl_write_uint32(&w, 0);
    1421              :     /* documents: empty */
    1422            1 :     tl_write_uint32(&w, TL_vector);
    1423            1 :     tl_write_uint32(&w, 0);
    1424              :     /* sentinel past the whole media */
    1425            1 :     tl_write_int32(&w, 0xFACE);
    1426            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1427            1 :     MediaInfo mi = {0};
    1428            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "cached_page iterates");
    1429            1 :     ASSERT(mi.kind == MEDIA_WEBPAGE, "kind=webpage");
    1430            1 :     ASSERT(tl_read_int32(&r) == 0xFACE, "cursor past whole media");
    1431            1 :     tl_writer_free(&w);
    1432              : }
    1433              : 
    1434              : /* TUI-11: same but with textConcat (vector of RichTexts). */
    1435            1 : static void test_skip_media_webpage_cached_page_textconcat(void) {
    1436            1 :     TlWriter w; tl_writer_init(&w);
    1437            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1438            1 :     tl_write_uint32(&w, 0);
    1439            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1440            1 :     tl_write_uint32(&w, (1u << 10));
    1441            1 :     tl_write_int64 (&w, 0x222);
    1442            1 :     tl_write_string(&w, "u");
    1443            1 :     tl_write_string(&w, "u");
    1444            1 :     tl_write_int32 (&w, 1);
    1445            1 :     tl_write_uint32(&w, CRC_page_test);
    1446            1 :     tl_write_uint32(&w, 0);
    1447            1 :     tl_write_string(&w, "x");
    1448            1 :     tl_write_uint32(&w, TL_vector);
    1449            1 :     tl_write_uint32(&w, 1);
    1450              :     /* block: Paragraph(textConcat([textPlain, textEmpty])) */
    1451            1 :     tl_write_uint32(&w, CRC_pageBlockParagraph_test);
    1452            1 :     tl_write_uint32(&w, CRC_textConcat_test);
    1453            1 :     tl_write_uint32(&w, TL_vector);
    1454            1 :     tl_write_uint32(&w, 2);
    1455            1 :     tl_write_uint32(&w, CRC_textPlain_test);
    1456            1 :     tl_write_string(&w, "foo");
    1457            1 :     tl_write_uint32(&w, CRC_textEmpty_test);
    1458            1 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);  /* photos */
    1459            1 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);  /* documents */
    1460            1 :     tl_write_int32(&w, 0xDEAD);
    1461            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1462            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "textConcat iterates");
    1463            1 :     ASSERT(tl_read_int32(&r) == 0xDEAD, "cursor past media");
    1464            1 :     tl_writer_free(&w);
    1465              : }
    1466              : 
    1467              : /* LIM-03: PageBlock CRCs beyond the TUI-11 whitelist. */
    1468              : #define CRC_pageCaption_t                0x6f747657u
    1469              : #define CRC_pageBlockBlockquote_t        0x263d7c26u
    1470              : #define CRC_pageBlockPullquote_t         0x4f4456d5u
    1471              : #define CRC_pageBlockPhoto_t             0x1759c560u
    1472              : #define CRC_pageBlockVideo_t             0x7c8fe7b6u
    1473              : #define CRC_pageBlockAudio_t             0x804361eau
    1474              : #define CRC_pageBlockCover_t             0x39f23300u
    1475              : #define CRC_pageBlockChannel_t           0xef1751b5u
    1476              : #define CRC_pageBlockMap_t               0xa44f3ef6u
    1477              : #define CRC_pageBlockList_t              0xe4e88011u
    1478              : #define CRC_pageBlockOrderedList_t       0x9a8ae1e1u
    1479              : #define CRC_pageBlockCollage_t           0x65a0fa4du
    1480              : #define CRC_pageBlockSlideshow_t         0x031f9590u
    1481              : #define CRC_pageBlockDetails_t           0x76768bedu
    1482              : #define CRC_pageBlockRelatedArticles_t   0x16115a96u
    1483              : #define CRC_pageBlockTable_t             0xbf4dea82u
    1484              : #define CRC_pageBlockEmbed_t             0xa8718dc5u
    1485              : #define CRC_pageBlockEmbedPost_t         0xf259a80bu
    1486              : #define CRC_pageListItemText_t           0xb92fb6cdu
    1487              : #define CRC_pageListItemBlocks_t         0x25e073fcu
    1488              : #define CRC_pageListOrderedItemText_t    0x5e068047u
    1489              : #define CRC_pageTableRow_t               0xe0c0c5e5u
    1490              : #define CRC_pageTableCell_t              0x34566b6au
    1491              : #define CRC_pageRelatedArticle_t         0xb390dc08u
    1492              : #define CRC_geoPointEmpty_t              0x1117dd5fu
    1493              : #define CRC_chatEmpty_t                  0x29562865u
    1494              : 
    1495              : /* Helper: wraps @p builder output into messageMediaWebPage + webPage
    1496              :  *         with flags.10 (cached_page) set, writes sentinel 0xCAFE after.
    1497              :  *         @p builder emits the Page body (flags, url, vectors). */
    1498           16 : static void run_cached_page_roundtrip(void (*builder)(TlWriter *)) {
    1499           16 :     TlWriter w; tl_writer_init(&w);
    1500           16 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1501           16 :     tl_write_uint32(&w, 0);
    1502           16 :     tl_write_uint32(&w, CRC_webPage_test);
    1503           16 :     tl_write_uint32(&w, (1u << 10));
    1504           16 :     tl_write_int64 (&w, 1);
    1505           16 :     tl_write_string(&w, "u");
    1506           16 :     tl_write_string(&w, "u");
    1507           16 :     tl_write_int32 (&w, 1);
    1508              :     /* Page wrapper. */
    1509           16 :     tl_write_uint32(&w, CRC_page_test);
    1510           16 :     tl_write_uint32(&w, 0);
    1511           16 :     tl_write_string(&w, "x");
    1512           16 :     builder(&w);
    1513              :     /* photos + documents — empty. */
    1514           16 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
    1515           16 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
    1516           16 :     tl_write_int32(&w, 0xCAFE);
    1517           16 :     TlReader r = tl_reader_init(w.data, w.len);
    1518           16 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "cached_page iterates");
    1519           16 :     ASSERT(tl_read_int32(&r) == 0xCAFE, "cursor past media");
    1520           16 :     tl_writer_free(&w);
    1521              : }
    1522              : 
    1523            8 : static void empty_caption(TlWriter *w) {
    1524            8 :     tl_write_uint32(w, CRC_pageCaption_t);
    1525            8 :     tl_write_uint32(w, CRC_textEmpty_test);
    1526            8 :     tl_write_uint32(w, CRC_textEmpty_test);
    1527            8 : }
    1528              : 
    1529            1 : static void build_blockquote(TlWriter *w) {
    1530            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1531            1 :     tl_write_uint32(w, CRC_pageBlockBlockquote_t);
    1532            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "quote");
    1533            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "credit");
    1534            1 : }
    1535            1 : static void test_skip_cached_page_blockquote(void) {
    1536            1 :     run_cached_page_roundtrip(build_blockquote);
    1537            1 : }
    1538              : 
    1539            1 : static void build_photo_block(TlWriter *w) {
    1540            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1541            1 :     tl_write_uint32(w, CRC_pageBlockPhoto_t);
    1542            1 :     tl_write_uint32(w, 0);                 /* no url */
    1543            1 :     tl_write_int64 (w, 99LL);
    1544            1 :     empty_caption(w);
    1545            1 : }
    1546            1 : static void test_skip_cached_page_photo(void) {
    1547            1 :     run_cached_page_roundtrip(build_photo_block);
    1548            1 : }
    1549              : 
    1550            1 : static void build_video_block(TlWriter *w) {
    1551            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1552            1 :     tl_write_uint32(w, CRC_pageBlockVideo_t);
    1553            1 :     tl_write_uint32(w, 0);
    1554            1 :     tl_write_int64 (w, 42LL);
    1555            1 :     empty_caption(w);
    1556            1 : }
    1557            1 : static void test_skip_cached_page_video(void) {
    1558            1 :     run_cached_page_roundtrip(build_video_block);
    1559            1 : }
    1560              : 
    1561            1 : static void build_audio_block(TlWriter *w) {
    1562            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1563            1 :     tl_write_uint32(w, CRC_pageBlockAudio_t);
    1564            1 :     tl_write_int64 (w, 7LL);
    1565            1 :     empty_caption(w);
    1566            1 : }
    1567            1 : static void test_skip_cached_page_audio(void) {
    1568            1 :     run_cached_page_roundtrip(build_audio_block);
    1569            1 : }
    1570              : 
    1571            1 : static void build_cover_block(TlWriter *w) {
    1572            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1573            1 :     tl_write_uint32(w, CRC_pageBlockCover_t);
    1574              :     /* nested: paragraph(textEmpty) */
    1575            1 :     tl_write_uint32(w, CRC_pageBlockParagraph_test);
    1576            1 :     tl_write_uint32(w, CRC_textEmpty_test);
    1577            1 : }
    1578            1 : static void test_skip_cached_page_cover(void) {
    1579            1 :     run_cached_page_roundtrip(build_cover_block);
    1580            1 : }
    1581              : 
    1582            1 : static void build_channel_block(TlWriter *w) {
    1583            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1584            1 :     tl_write_uint32(w, CRC_pageBlockChannel_t);
    1585            1 :     tl_write_uint32(w, CRC_chatEmpty_t);
    1586            1 :     tl_write_int64 (w, 123LL);
    1587            1 : }
    1588            1 : static void test_skip_cached_page_channel(void) {
    1589            1 :     run_cached_page_roundtrip(build_channel_block);
    1590            1 : }
    1591              : 
    1592            1 : static void build_map_block(TlWriter *w) {
    1593            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1594            1 :     tl_write_uint32(w, CRC_pageBlockMap_t);
    1595            1 :     tl_write_uint32(w, CRC_geoPointEmpty_t);
    1596            1 :     tl_write_int32 (w, 14);                /* zoom */
    1597            1 :     tl_write_int32 (w, 800);               /* w */
    1598            1 :     tl_write_int32 (w, 600);               /* h */
    1599            1 :     empty_caption(w);
    1600            1 : }
    1601            1 : static void test_skip_cached_page_map(void) {
    1602            1 :     run_cached_page_roundtrip(build_map_block);
    1603            1 : }
    1604              : 
    1605            1 : static void build_list_block(TlWriter *w) {
    1606            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1607            1 :     tl_write_uint32(w, CRC_pageBlockList_t);
    1608            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 2);
    1609            1 :     tl_write_uint32(w, CRC_pageListItemText_t);
    1610            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "one");
    1611            1 :     tl_write_uint32(w, CRC_pageListItemBlocks_t);
    1612            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1613            1 :     tl_write_uint32(w, CRC_pageBlockParagraph_test);
    1614            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "two");
    1615            1 : }
    1616            1 : static void test_skip_cached_page_list(void) {
    1617            1 :     run_cached_page_roundtrip(build_list_block);
    1618            1 : }
    1619              : 
    1620            1 : static void build_ordered_list_block(TlWriter *w) {
    1621            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1622            1 :     tl_write_uint32(w, CRC_pageBlockOrderedList_t);
    1623            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1624            1 :     tl_write_uint32(w, CRC_pageListOrderedItemText_t);
    1625            1 :     tl_write_string(w, "1.");
    1626            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "first");
    1627            1 : }
    1628            1 : static void test_skip_cached_page_ordered_list(void) {
    1629            1 :     run_cached_page_roundtrip(build_ordered_list_block);
    1630            1 : }
    1631              : 
    1632            1 : static void build_collage_block(TlWriter *w) {
    1633            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1634            1 :     tl_write_uint32(w, CRC_pageBlockCollage_t);
    1635              :     /* items: empty vector (still covers Collage's structure) */
    1636            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 0);
    1637            1 :     empty_caption(w);
    1638            1 : }
    1639            1 : static void test_skip_cached_page_collage(void) {
    1640            1 :     run_cached_page_roundtrip(build_collage_block);
    1641            1 : }
    1642              : 
    1643            1 : static void build_slideshow_block(TlWriter *w) {
    1644            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1645            1 :     tl_write_uint32(w, CRC_pageBlockSlideshow_t);
    1646            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1647            1 :     tl_write_uint32(w, CRC_pageBlockParagraph_test);
    1648            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "slide");
    1649            1 :     empty_caption(w);
    1650            1 : }
    1651            1 : static void test_skip_cached_page_slideshow(void) {
    1652            1 :     run_cached_page_roundtrip(build_slideshow_block);
    1653            1 : }
    1654              : 
    1655            1 : static void build_details_block(TlWriter *w) {
    1656            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1657            1 :     tl_write_uint32(w, CRC_pageBlockDetails_t);
    1658            1 :     tl_write_uint32(w, 0);
    1659            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1660            1 :     tl_write_uint32(w, CRC_pageBlockParagraph_test);
    1661            1 :     tl_write_uint32(w, CRC_textEmpty_test);
    1662            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "Title");
    1663            1 : }
    1664            1 : static void test_skip_cached_page_details(void) {
    1665            1 :     run_cached_page_roundtrip(build_details_block);
    1666            1 : }
    1667              : 
    1668            1 : static void build_related_articles_block(TlWriter *w) {
    1669            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1670            1 :     tl_write_uint32(w, CRC_pageBlockRelatedArticles_t);
    1671            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "Related");
    1672            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1673            1 :     tl_write_uint32(w, CRC_pageRelatedArticle_t);
    1674            1 :     tl_write_uint32(w, 0);                 /* flags */
    1675            1 :     tl_write_string(w, "https://a");
    1676            1 :     tl_write_int64 (w, 111LL);
    1677            1 : }
    1678            1 : static void test_skip_cached_page_related_articles(void) {
    1679            1 :     run_cached_page_roundtrip(build_related_articles_block);
    1680            1 : }
    1681              : 
    1682            1 : static void build_table_block(TlWriter *w) {
    1683            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1684            1 :     tl_write_uint32(w, CRC_pageBlockTable_t);
    1685            1 :     tl_write_uint32(w, 0);
    1686            1 :     tl_write_uint32(w, CRC_textEmpty_test);
    1687            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1688            1 :     tl_write_uint32(w, CRC_pageTableRow_t);
    1689            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1690            1 :     tl_write_uint32(w, CRC_pageTableCell_t);
    1691            1 :     tl_write_uint32(w, (1u << 7));         /* text:flags.7 */
    1692            1 :     tl_write_uint32(w, CRC_textPlain_test); tl_write_string(w, "cell");
    1693            1 : }
    1694            1 : static void test_skip_cached_page_table(void) {
    1695            1 :     run_cached_page_roundtrip(build_table_block);
    1696            1 : }
    1697              : 
    1698            1 : static void build_embed_block(TlWriter *w) {
    1699            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1700            1 :     tl_write_uint32(w, CRC_pageBlockEmbed_t);
    1701            1 :     tl_write_uint32(w, (1u << 1));         /* url present */
    1702            1 :     tl_write_string(w, "https://example/e");
    1703            1 :     empty_caption(w);
    1704            1 : }
    1705            1 : static void test_skip_cached_page_embed(void) {
    1706            1 :     run_cached_page_roundtrip(build_embed_block);
    1707            1 : }
    1708              : 
    1709            1 : static void build_embed_post_block(TlWriter *w) {
    1710            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 1);
    1711            1 :     tl_write_uint32(w, CRC_pageBlockEmbedPost_t);
    1712            1 :     tl_write_string(w, "https://p");
    1713            1 :     tl_write_int64 (w, 1);                 /* webpage_id */
    1714            1 :     tl_write_int64 (w, 2);                 /* author_photo_id */
    1715            1 :     tl_write_string(w, "author");
    1716            1 :     tl_write_int32 (w, 1700000000);
    1717            1 :     tl_write_uint32(w, TL_vector); tl_write_uint32(w, 0);
    1718            1 :     empty_caption(w);
    1719            1 : }
    1720            1 : static void test_skip_cached_page_embed_post(void) {
    1721            1 :     run_cached_page_roundtrip(build_embed_post_block);
    1722            1 : }
    1723              : 
    1724              : /* TUI-11: cached_page with an unsupported PageBlock (Cover etc.) bails. */
    1725            1 : static void test_skip_media_webpage_cached_page_unsupported(void) {
    1726            1 :     TlWriter w; tl_writer_init(&w);
    1727            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1728            1 :     tl_write_uint32(&w, 0);
    1729            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1730            1 :     tl_write_uint32(&w, (1u << 10));
    1731            1 :     tl_write_int64 (&w, 0x333);
    1732            1 :     tl_write_string(&w, "u");
    1733            1 :     tl_write_string(&w, "u");
    1734            1 :     tl_write_int32 (&w, 1);
    1735            1 :     tl_write_uint32(&w, CRC_page_test);
    1736            1 :     tl_write_uint32(&w, 0);
    1737            1 :     tl_write_string(&w, "x");
    1738            1 :     tl_write_uint32(&w, TL_vector);
    1739            1 :     tl_write_uint32(&w, 1);
    1740              :     /* unknown PageBlock CRC */
    1741            1 :     tl_write_uint32(&w, 0xDEADBEEFu);
    1742            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1743            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == -1,
    1744              :            "unknown PageBlock bails");
    1745            1 :     tl_writer_free(&w);
    1746              : }
    1747              : 
    1748              : /* TUI-11: webPageAttribute — Vector with a theme (no settings). */
    1749              : #define CRC_webPageAttributeTheme_t      0x54b56617u
    1750              : #define CRC_webPageAttributeStickerSet_t 0x50cc03d3u
    1751              : #define CRC_webPageAttributeStory_t      0x2e94c3e7u
    1752              : #define CRC_storyItemDeleted_t           0x51e6ee4fu
    1753              : 
    1754            1 : static void test_skip_media_webpage_attributes_theme(void) {
    1755            1 :     TlWriter w; tl_writer_init(&w);
    1756            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1757            1 :     tl_write_uint32(&w, 0);
    1758            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1759            1 :     tl_write_uint32(&w, (1u << 12));             /* attributes flag */
    1760            1 :     tl_write_int64 (&w, 1);
    1761            1 :     tl_write_string(&w, "u");
    1762            1 :     tl_write_string(&w, "u");
    1763            1 :     tl_write_int32 (&w, 1);
    1764              :     /* attributes: [Theme{documents=[]}] */
    1765            1 :     tl_write_uint32(&w, TL_vector);
    1766            1 :     tl_write_uint32(&w, 1);
    1767            1 :     tl_write_uint32(&w, CRC_webPageAttributeTheme_t);
    1768            1 :     tl_write_uint32(&w, (1u << 0));              /* documents present */
    1769            1 :     tl_write_uint32(&w, TL_vector);
    1770            1 :     tl_write_uint32(&w, 0);
    1771            1 :     tl_write_int32(&w, 0xBEEF);
    1772            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1773            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "theme attrs iterate");
    1774            1 :     ASSERT(tl_read_int32(&r) == 0xBEEF, "cursor past media");
    1775            1 :     tl_writer_free(&w);
    1776              : }
    1777              : 
    1778              : /* TUI-11: webPageAttribute — StickerSet with a couple of documents. */
    1779            1 : static void test_skip_media_webpage_attributes_stickerset(void) {
    1780            1 :     TlWriter w; tl_writer_init(&w);
    1781            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1782            1 :     tl_write_uint32(&w, 0);
    1783            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1784            1 :     tl_write_uint32(&w, (1u << 12));
    1785            1 :     tl_write_int64 (&w, 1);
    1786            1 :     tl_write_string(&w, "u");
    1787            1 :     tl_write_string(&w, "u");
    1788            1 :     tl_write_int32 (&w, 1);
    1789            1 :     tl_write_uint32(&w, TL_vector);
    1790            1 :     tl_write_uint32(&w, 1);
    1791            1 :     tl_write_uint32(&w, CRC_webPageAttributeStickerSet_t);
    1792            1 :     tl_write_uint32(&w, 0);                      /* flags */
    1793              :     /* stickers: [documentEmpty, documentEmpty] */
    1794            1 :     tl_write_uint32(&w, TL_vector);
    1795            1 :     tl_write_uint32(&w, 2);
    1796            1 :     tl_write_uint32(&w, CRC_documentEmpty);
    1797            1 :     tl_write_int64 (&w, 11LL);
    1798            1 :     tl_write_uint32(&w, CRC_documentEmpty);
    1799            1 :     tl_write_int64 (&w, 22LL);
    1800            1 :     tl_write_int32(&w, 0xABCD);
    1801            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1802            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "stickerset iterates");
    1803            1 :     ASSERT(tl_read_int32(&r) == 0xABCD, "cursor past media");
    1804            1 :     tl_writer_free(&w);
    1805              : }
    1806              : 
    1807              : /* TUI-11: webPageAttribute — Story without a nested story (flag.0 clear). */
    1808            1 : static void test_skip_media_webpage_attributes_story_noinline(void) {
    1809            1 :     TlWriter w; tl_writer_init(&w);
    1810            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1811            1 :     tl_write_uint32(&w, 0);
    1812            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1813            1 :     tl_write_uint32(&w, (1u << 12));
    1814            1 :     tl_write_int64 (&w, 1);
    1815            1 :     tl_write_string(&w, "u");
    1816            1 :     tl_write_string(&w, "u");
    1817            1 :     tl_write_int32 (&w, 1);
    1818            1 :     tl_write_uint32(&w, TL_vector);
    1819            1 :     tl_write_uint32(&w, 1);
    1820            1 :     tl_write_uint32(&w, CRC_webPageAttributeStory_t);
    1821            1 :     tl_write_uint32(&w, 0);                      /* no inline story */
    1822            1 :     tl_write_uint32(&w, TL_peerUser);            /* peer */
    1823            1 :     tl_write_int64 (&w, 42LL);
    1824            1 :     tl_write_int32 (&w, 7);                      /* story id */
    1825            1 :     tl_write_int32(&w, 0x1234);
    1826            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1827            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "story attr iterates");
    1828            1 :     ASSERT(tl_read_int32(&r) == 0x1234, "cursor past media");
    1829            1 :     tl_writer_free(&w);
    1830              : }
    1831              : 
    1832              : /* TUI-11: webPageAttribute — Story with an inline storyItemDeleted. */
    1833            1 : static void test_skip_media_webpage_attributes_story_inline(void) {
    1834            1 :     TlWriter w; tl_writer_init(&w);
    1835            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1836            1 :     tl_write_uint32(&w, 0);
    1837            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1838            1 :     tl_write_uint32(&w, (1u << 12));
    1839            1 :     tl_write_int64 (&w, 1);
    1840            1 :     tl_write_string(&w, "u");
    1841            1 :     tl_write_string(&w, "u");
    1842            1 :     tl_write_int32 (&w, 1);
    1843            1 :     tl_write_uint32(&w, TL_vector);
    1844            1 :     tl_write_uint32(&w, 1);
    1845            1 :     tl_write_uint32(&w, CRC_webPageAttributeStory_t);
    1846            1 :     tl_write_uint32(&w, (1u << 0));              /* inline story */
    1847            1 :     tl_write_uint32(&w, TL_peerUser);
    1848            1 :     tl_write_int64 (&w, 42LL);
    1849            1 :     tl_write_int32 (&w, 7);
    1850              :     /* story: storyItemDeleted{id=7} — simplest variant */
    1851            1 :     tl_write_uint32(&w, CRC_storyItemDeleted_t);
    1852            1 :     tl_write_int32 (&w, 7);
    1853            1 :     tl_write_int32(&w, 0xBABE);
    1854            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1855            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "story inline iterates");
    1856            1 :     ASSERT(tl_read_int32(&r) == 0xBABE, "cursor past media");
    1857            1 :     tl_writer_free(&w);
    1858              : }
    1859              : 
    1860              : /* TUI-11: unknown WebPageAttribute variant bails. */
    1861            1 : static void test_skip_media_webpage_attributes_unknown(void) {
    1862            1 :     TlWriter w; tl_writer_init(&w);
    1863            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1864            1 :     tl_write_uint32(&w, 0);
    1865            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1866            1 :     tl_write_uint32(&w, (1u << 12));
    1867            1 :     tl_write_int64 (&w, 1);
    1868            1 :     tl_write_string(&w, "u");
    1869            1 :     tl_write_string(&w, "u");
    1870            1 :     tl_write_int32 (&w, 1);
    1871            1 :     tl_write_uint32(&w, TL_vector);
    1872            1 :     tl_write_uint32(&w, 1);
    1873            1 :     tl_write_uint32(&w, 0xDEADC0DE);
    1874            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1875            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == -1,
    1876              :            "unknown WebPageAttribute bails");
    1877            1 :     tl_writer_free(&w);
    1878              : }
    1879              : 
    1880              : /* TUI-11: both cached_page AND attributes set, combined page + stickerset. */
    1881            1 : static void test_skip_media_webpage_cached_page_plus_attributes(void) {
    1882            1 :     TlWriter w; tl_writer_init(&w);
    1883            1 :     tl_write_uint32(&w, CRC_messageMediaWebPage_test);
    1884            1 :     tl_write_uint32(&w, 0);
    1885            1 :     tl_write_uint32(&w, CRC_webPage_test);
    1886            1 :     tl_write_uint32(&w, (1u << 10) | (1u << 12));
    1887            1 :     tl_write_int64 (&w, 1);
    1888            1 :     tl_write_string(&w, "u");
    1889            1 :     tl_write_string(&w, "u");
    1890            1 :     tl_write_int32 (&w, 1);
    1891              :     /* cached_page: empty page */
    1892            1 :     tl_write_uint32(&w, CRC_page_test);
    1893            1 :     tl_write_uint32(&w, 0);
    1894            1 :     tl_write_string(&w, "p");
    1895            1 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);  /* blocks */
    1896            1 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);  /* photos */
    1897            1 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);  /* documents */
    1898              :     /* attributes: one StickerSet with no stickers */
    1899            1 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
    1900            1 :     tl_write_uint32(&w, CRC_webPageAttributeStickerSet_t);
    1901            1 :     tl_write_uint32(&w, 0);
    1902            1 :     tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
    1903            1 :     tl_write_int32(&w, 0x5678);
    1904            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1905            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    1906              :            "cached_page + attributes iterate together");
    1907            1 :     ASSERT(tl_read_int32(&r) == 0x5678, "cursor past media");
    1908            1 :     tl_writer_free(&w);
    1909              : }
    1910              : 
    1911              : /* ---- messageMediaGame ---- */
    1912              : 
    1913              : #define CRC_messageMediaGame_t  0xfdb19008u
    1914              : #define CRC_game_t              0xbdf9653bu
    1915              : 
    1916            1 : static void test_skip_media_game_photo_only(void) {
    1917            1 :     TlWriter w; tl_writer_init(&w);
    1918            1 :     tl_write_uint32(&w, CRC_messageMediaGame_t);
    1919            1 :     tl_write_uint32(&w, CRC_game_t);
    1920            1 :     tl_write_uint32(&w, 0);                    /* game flags (no document) */
    1921            1 :     tl_write_int64 (&w, 0x11LL);
    1922            1 :     tl_write_int64 (&w, 0x22LL);
    1923            1 :     tl_write_string(&w, "short");
    1924            1 :     tl_write_string(&w, "title");
    1925            1 :     tl_write_string(&w, "desc");
    1926              :     /* photo:photoEmpty */
    1927            1 :     tl_write_uint32(&w, CRC_photoEmpty);
    1928            1 :     tl_write_int64 (&w, 7777LL);
    1929            1 :     tl_write_int32 (&w, 0xCAFE);
    1930            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1931            1 :     MediaInfo mi = {0};
    1932            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "game photo-only ok");
    1933            1 :     ASSERT(mi.kind == MEDIA_GAME, "kind=game");
    1934            1 :     ASSERT(tl_read_int32(&r) == 0xCAFE, "cursor past game");
    1935            1 :     tl_writer_free(&w);
    1936              : }
    1937              : 
    1938            1 : static void test_skip_media_game_with_document(void) {
    1939            1 :     TlWriter w; tl_writer_init(&w);
    1940            1 :     tl_write_uint32(&w, CRC_messageMediaGame_t);
    1941            1 :     tl_write_uint32(&w, CRC_game_t);
    1942            1 :     tl_write_uint32(&w, (1u << 0));            /* document flag set */
    1943            1 :     tl_write_int64 (&w, 1LL);
    1944            1 :     tl_write_int64 (&w, 2LL);
    1945            1 :     tl_write_string(&w, "s");
    1946            1 :     tl_write_string(&w, "t");
    1947            1 :     tl_write_string(&w, "d");
    1948            1 :     tl_write_uint32(&w, CRC_photoEmpty);
    1949            1 :     tl_write_int64 (&w, 3LL);
    1950              :     /* document:documentEmpty (id only) */
    1951            1 :     tl_write_uint32(&w, 0x36f8c871u);
    1952            1 :     tl_write_int64 (&w, 4LL);
    1953            1 :     tl_write_int32 (&w, 0xABC);
    1954            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1955            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "game+doc ok");
    1956            1 :     ASSERT(tl_read_int32(&r) == 0xABC, "cursor past game+doc");
    1957            1 :     tl_writer_free(&w);
    1958              : }
    1959              : 
    1960            1 : static void test_skip_media_game_wrong_inner_crc_bails(void) {
    1961            1 :     TlWriter w; tl_writer_init(&w);
    1962            1 :     tl_write_uint32(&w, CRC_messageMediaGame_t);
    1963            1 :     tl_write_uint32(&w, 0xdeadbeefu);          /* not a Game CRC */
    1964            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1965            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == -1, "non-Game inner rejected");
    1966            1 :     tl_writer_free(&w);
    1967              : }
    1968              : 
    1969              : /* ---- messageMediaPaidMedia ---- */
    1970              : 
    1971              : #define CRC_messageMediaPaidMedia_t        0xa8852491u
    1972              : #define CRC_messageExtendedMediaPreview_t  0xad628cc8u
    1973              : #define CRC_messageExtendedMedia_t         0xee479c64u
    1974              : 
    1975            1 : static void test_skip_media_paid_preview_only(void) {
    1976            1 :     TlWriter w; tl_writer_init(&w);
    1977            1 :     tl_write_uint32(&w, CRC_messageMediaPaidMedia_t);
    1978            1 :     tl_write_int64 (&w, 500LL);                /* stars_amount */
    1979            1 :     tl_write_uint32(&w, TL_vector);
    1980            1 :     tl_write_uint32(&w, 1);                    /* one extended_media */
    1981            1 :     tl_write_uint32(&w, CRC_messageExtendedMediaPreview_t);
    1982            1 :     tl_write_uint32(&w, 0);                    /* no flags */
    1983            1 :     tl_write_int32 (&w, 0x55AA);
    1984            1 :     TlReader r = tl_reader_init(w.data, w.len);
    1985            1 :     MediaInfo mi = {0};
    1986            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0, "paid preview ok");
    1987            1 :     ASSERT(mi.kind == MEDIA_PAID, "kind=paid");
    1988            1 :     ASSERT(tl_read_int32(&r) == 0x55AA, "cursor past paid");
    1989            1 :     tl_writer_free(&w);
    1990              : }
    1991              : 
    1992            1 : static void test_skip_media_paid_preview_with_dims_and_thumb(void) {
    1993            1 :     TlWriter w; tl_writer_init(&w);
    1994            1 :     tl_write_uint32(&w, CRC_messageMediaPaidMedia_t);
    1995            1 :     tl_write_int64 (&w, 1000LL);
    1996            1 :     tl_write_uint32(&w, TL_vector);
    1997            1 :     tl_write_uint32(&w, 1);
    1998            1 :     tl_write_uint32(&w, CRC_messageExtendedMediaPreview_t);
    1999            1 :     tl_write_uint32(&w, (1u << 0) | (1u << 1) | (1u << 2));
    2000            1 :     tl_write_int32 (&w, 1280); tl_write_int32(&w, 720);
    2001              :     /* thumb: photoStrippedSize#e0b0bc2e type:string bytes:bytes */
    2002            1 :     tl_write_uint32(&w, 0xe0b0bc2eu);
    2003            1 :     tl_write_string(&w, "i");
    2004            1 :     uint8_t stripped[3] = { 0x01, 0x02, 0x03 };
    2005            1 :     tl_write_bytes(&w, stripped, sizeof(stripped));
    2006            1 :     tl_write_int32 (&w, 12);                   /* video_duration */
    2007            1 :     tl_write_int32 (&w, 0xFACE);
    2008            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2009            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "paid preview full ok");
    2010            1 :     ASSERT(tl_read_int32(&r) == 0xFACE, "cursor past paid");
    2011            1 :     tl_writer_free(&w);
    2012              : }
    2013              : 
    2014            1 : static void test_skip_media_paid_wrapped_inner_media(void) {
    2015              :     /* extended_media variant that wraps an inner MessageMedia (here
    2016              :      * messageMediaEmpty). Confirms the recursive tl_skip_message_media_ex
    2017              :      * dispatch path. */
    2018            1 :     TlWriter w; tl_writer_init(&w);
    2019            1 :     tl_write_uint32(&w, CRC_messageMediaPaidMedia_t);
    2020            1 :     tl_write_int64 (&w, 42LL);
    2021            1 :     tl_write_uint32(&w, TL_vector);
    2022            1 :     tl_write_uint32(&w, 2);                    /* two extended_media entries */
    2023            1 :     tl_write_uint32(&w, CRC_messageExtendedMediaPreview_t);
    2024            1 :     tl_write_uint32(&w, 0);
    2025            1 :     tl_write_uint32(&w, CRC_messageExtendedMedia_t);
    2026            1 :     tl_write_uint32(&w, CRC_messageMediaEmpty);
    2027            1 :     tl_write_int32 (&w, 0xBEEF);
    2028            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2029            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "paid wrapped ok");
    2030            1 :     ASSERT(tl_read_int32(&r) == (int32_t)0xBEEF, "cursor past wrapped paid");
    2031            1 :     tl_writer_free(&w);
    2032              : }
    2033              : 
    2034              : /* ---- messageMediaInvoice with WebDocument photo + extended_media ---- */
    2035              : 
    2036              : #define CRC_webDocument_t         0x1c570ed1u
    2037              : #define CRC_webDocumentNoProxy_t  0xf9c8bcc6u
    2038              : #define CRC_documentAttributeFilename_test 0x15590068u
    2039              : 
    2040            1 : static void test_skip_media_invoice_with_webdocument_photo(void) {
    2041            1 :     TlWriter w; tl_writer_init(&w);
    2042            1 :     tl_write_uint32(&w, CRC_messageMediaInvoice_t);
    2043            1 :     tl_write_uint32(&w, (1u << 0));             /* photo flag */
    2044            1 :     tl_write_string(&w, "Pro");
    2045            1 :     tl_write_string(&w, "Subscription");
    2046              :     /* webDocument with one Filename attribute */
    2047            1 :     tl_write_uint32(&w, CRC_webDocument_t);
    2048            1 :     tl_write_string(&w, "https://icon.example");
    2049            1 :     tl_write_int64 (&w, 0x12345LL);             /* access_hash */
    2050            1 :     tl_write_int32 (&w, 4096);                  /* size */
    2051            1 :     tl_write_string(&w, "image/png");
    2052            1 :     tl_write_uint32(&w, TL_vector);
    2053            1 :     tl_write_uint32(&w, 1);
    2054            1 :     tl_write_uint32(&w, CRC_documentAttributeFilename_test);
    2055            1 :     tl_write_string(&w, "icon.png");
    2056              :     /* continuing messageMediaInvoice fields */
    2057            1 :     tl_write_string(&w, "EUR");
    2058            1 :     tl_write_int64 (&w, 1999);
    2059            1 :     tl_write_string(&w, "start");
    2060            1 :     tl_write_int32 (&w, 0xBEEF);
    2061            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2062            1 :     MediaInfo mi = {0};
    2063            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0,
    2064              :            "invoice + webDocument iterates");
    2065            1 :     ASSERT(mi.kind == MEDIA_INVOICE, "kind=invoice");
    2066            1 :     ASSERT(tl_read_int32(&r) == 0xBEEF, "cursor past invoice");
    2067            1 :     tl_writer_free(&w);
    2068              : }
    2069              : 
    2070            1 : static void test_skip_media_invoice_noproxy_webdocument(void) {
    2071            1 :     TlWriter w; tl_writer_init(&w);
    2072            1 :     tl_write_uint32(&w, CRC_messageMediaInvoice_t);
    2073            1 :     tl_write_uint32(&w, (1u << 0));
    2074            1 :     tl_write_string(&w, "T"); tl_write_string(&w, "D");
    2075              :     /* webDocumentNoProxy — no access_hash */
    2076            1 :     tl_write_uint32(&w, CRC_webDocumentNoProxy_t);
    2077            1 :     tl_write_string(&w, "https://x");
    2078            1 :     tl_write_int32 (&w, 512);
    2079            1 :     tl_write_string(&w, "image/jpeg");
    2080            1 :     tl_write_uint32(&w, TL_vector);
    2081            1 :     tl_write_uint32(&w, 0);                     /* no attributes */
    2082            1 :     tl_write_string(&w, "USD");
    2083            1 :     tl_write_int64 (&w, 99);
    2084            1 :     tl_write_string(&w, "s");
    2085            1 :     tl_write_int32 (&w, 0xDEAD);
    2086            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2087            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0, "noProxy variant ok");
    2088            1 :     ASSERT(tl_read_int32(&r) == (int32_t)0xDEAD, "cursor past");
    2089            1 :     tl_writer_free(&w);
    2090              : }
    2091              : 
    2092            1 : static void test_skip_media_invoice_extended_media_preview(void) {
    2093              :     /* flags.4 (extended_media) now walks through messageExtendedMediaPreview. */
    2094            1 :     TlWriter w; tl_writer_init(&w);
    2095            1 :     tl_write_uint32(&w, CRC_messageMediaInvoice_t);
    2096            1 :     tl_write_uint32(&w, (1u << 4));             /* extended_media flag only */
    2097            1 :     tl_write_string(&w, "T"); tl_write_string(&w, "D");
    2098            1 :     tl_write_string(&w, "EUR");
    2099            1 :     tl_write_int64 (&w, 42);
    2100            1 :     tl_write_string(&w, "s");
    2101              :     /* extended_media: messageExtendedMediaPreview#ad628cc8 with no inner
    2102              :      * flags — just the flags int. */
    2103            1 :     tl_write_uint32(&w, 0xad628cc8u);
    2104            1 :     tl_write_uint32(&w, 0);
    2105            1 :     tl_write_int32 (&w, 0xBABE);
    2106            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2107            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    2108              :            "invoice.extended_media now handled");
    2109            1 :     ASSERT(tl_read_int32(&r) == (int32_t)0xBABE, "cursor past invoice");
    2110            1 :     tl_writer_free(&w);
    2111              : }
    2112              : 
    2113              : /* ---- messageMediaStory with inline StoryItem variants ---- */
    2114              : 
    2115              : #define CRC_storyItemDeleted_t 0x51e6ee4fu
    2116              : #define CRC_storyItemSkipped_t 0xffadc913u
    2117              : #define CRC_storyItem_t        0x79b26a24u
    2118              : 
    2119            1 : static void test_skip_media_story_inline_deleted(void) {
    2120            1 :     TlWriter w; tl_writer_init(&w);
    2121            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    2122            1 :     tl_write_uint32(&w, (1u << 0));             /* inline story */
    2123            1 :     tl_write_uint32(&w, TL_peerUser);
    2124            1 :     tl_write_int64 (&w, 7LL);
    2125            1 :     tl_write_int32 (&w, 11);                    /* id */
    2126              :     /* storyItemDeleted */
    2127            1 :     tl_write_uint32(&w, CRC_storyItemDeleted_t);
    2128            1 :     tl_write_int32 (&w, 11);
    2129            1 :     tl_write_int32 (&w, 0xFACE);
    2130            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2131            1 :     MediaInfo mi = {0};
    2132            1 :     ASSERT(tl_skip_message_media_ex(&r, &mi) == 0,
    2133              :            "inline storyItemDeleted iterates");
    2134            1 :     ASSERT(mi.kind == MEDIA_STORY, "kind=story");
    2135            1 :     ASSERT(tl_read_int32(&r) == (int32_t)0xFACE, "cursor past story");
    2136            1 :     tl_writer_free(&w);
    2137              : }
    2138              : 
    2139            1 : static void test_skip_media_story_inline_skipped(void) {
    2140            1 :     TlWriter w; tl_writer_init(&w);
    2141            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    2142            1 :     tl_write_uint32(&w, (1u << 0));
    2143            1 :     tl_write_uint32(&w, TL_peerUser);
    2144            1 :     tl_write_int64 (&w, 7LL);
    2145            1 :     tl_write_int32 (&w, 11);
    2146              :     /* storyItemSkipped#ffadc913 flags:# id:int date:int expire_date:int */
    2147            1 :     tl_write_uint32(&w, CRC_storyItemSkipped_t);
    2148            1 :     tl_write_uint32(&w, 0);                     /* no close_friends flag */
    2149            1 :     tl_write_int32 (&w, 11);
    2150            1 :     tl_write_int32 (&w, 1700000000);
    2151            1 :     tl_write_int32 (&w, 1700086400);
    2152            1 :     tl_write_int32 (&w, 0xFEED);
    2153            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2154            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    2155              :            "inline storyItemSkipped iterates");
    2156            1 :     ASSERT(tl_read_int32(&r) == (int32_t)0xFEED, "cursor past story");
    2157            1 :     tl_writer_free(&w);
    2158              : }
    2159              : 
    2160            1 : static void test_skip_media_story_truncated_full_bails(void) {
    2161              :     /* Full storyItem body missing: the skipper must bail cleanly rather
    2162              :      * than overrun the buffer. */
    2163            1 :     TlWriter w; tl_writer_init(&w);
    2164            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    2165            1 :     tl_write_uint32(&w, (1u << 0));
    2166            1 :     tl_write_uint32(&w, TL_peerUser);
    2167            1 :     tl_write_int64 (&w, 7LL);
    2168            1 :     tl_write_int32 (&w, 11);
    2169            1 :     tl_write_uint32(&w, CRC_storyItem_t);
    2170              :     /* No body bytes — skipper should detect truncation. */
    2171            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2172            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == -1,
    2173              :            "truncated full storyItem bails");
    2174            1 :     tl_writer_free(&w);
    2175              : }
    2176              : 
    2177              : /* Full storyItem#79b26a24 minimal: only mandatory fields + empty media. */
    2178            1 : static void test_skip_media_story_full_minimal(void) {
    2179            1 :     TlWriter w; tl_writer_init(&w);
    2180            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    2181            1 :     tl_write_uint32(&w, (1u << 0));              /* inline story */
    2182            1 :     tl_write_uint32(&w, TL_peerUser);
    2183            1 :     tl_write_int64 (&w, 42LL);
    2184            1 :     tl_write_int32 (&w, 99);                     /* outer id */
    2185              :     /* storyItem with no optional flags. */
    2186            1 :     tl_write_uint32(&w, CRC_storyItem_t);
    2187            1 :     tl_write_uint32(&w, 0);                      /* flags */
    2188            1 :     tl_write_int32 (&w, 99);                     /* id */
    2189            1 :     tl_write_int32 (&w, 1700000000);             /* date */
    2190            1 :     tl_write_int32 (&w, 1700086400);             /* expire_date */
    2191            1 :     tl_write_uint32(&w, CRC_messageMediaEmpty);  /* media */
    2192            1 :     tl_write_int32 (&w, 0x12345678);
    2193            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2194            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    2195              :            "minimal full storyItem iterates");
    2196            1 :     ASSERT(tl_read_int32(&r) == 0x12345678, "cursor past full storyItem");
    2197            1 :     tl_writer_free(&w);
    2198              : }
    2199              : 
    2200              : /* Full storyItem with caption + entities + views (flags.0|1|3). */
    2201            1 : static void test_skip_media_story_full_with_views(void) {
    2202            1 :     TlWriter w; tl_writer_init(&w);
    2203            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    2204            1 :     tl_write_uint32(&w, (1u << 0));
    2205            1 :     tl_write_uint32(&w, TL_peerChannel);
    2206            1 :     tl_write_int64 (&w, 500LL);
    2207            1 :     tl_write_int32 (&w, 1);
    2208              :     /* storyItem w/ caption (flags.0) + entities (flags.1) + views (flags.3). */
    2209            1 :     tl_write_uint32(&w, CRC_storyItem_t);
    2210            1 :     tl_write_uint32(&w, (1u << 0) | (1u << 1) | (1u << 3));
    2211            1 :     tl_write_int32 (&w, 1);
    2212            1 :     tl_write_int32 (&w, 1700000000);
    2213            1 :     tl_write_int32 (&w, 1700086400);
    2214            1 :     tl_write_string(&w, "Hello story");
    2215              :     /* entities: empty vector */
    2216            1 :     tl_write_uint32(&w, TL_vector);
    2217            1 :     tl_write_uint32(&w, 0);
    2218              :     /* media: empty */
    2219            1 :     tl_write_uint32(&w, CRC_messageMediaEmpty);
    2220              :     /* views: storyViews flags=0, just views_count */
    2221            1 :     tl_write_uint32(&w, 0x8d595cd6u);            /* storyViews */
    2222            1 :     tl_write_uint32(&w, 0);
    2223            1 :     tl_write_int32 (&w, 1234);                   /* views_count */
    2224            1 :     tl_write_int32 (&w, 0xABBA);
    2225            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2226            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    2227              :            "full storyItem with views iterates");
    2228            1 :     ASSERT(tl_read_int32(&r) == (int32_t)0xABBA, "cursor past");
    2229            1 :     tl_writer_free(&w);
    2230              : }
    2231              : 
    2232              : /* LIM-04: storyItem whose inner media is a supported variant (photo /
    2233              :  * document / webpage / empty) must iterate end-to-end. */
    2234            1 : static void test_skip_media_story_inner_media_photo(void) {
    2235            1 :     TlWriter w; tl_writer_init(&w);
    2236            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    2237            1 :     tl_write_uint32(&w, (1u << 0));
    2238            1 :     tl_write_uint32(&w, TL_peerUser);
    2239            1 :     tl_write_int64 (&w, 1LL);
    2240            1 :     tl_write_int32 (&w, 1);
    2241            1 :     tl_write_uint32(&w, CRC_storyItem_t);
    2242            1 :     tl_write_uint32(&w, 0);
    2243            1 :     tl_write_int32 (&w, 1);
    2244            1 :     tl_write_int32 (&w, 1700000000);
    2245            1 :     tl_write_int32 (&w, 1700086400);
    2246              :     /* inner media: messageMediaPhoto with photoEmpty. */
    2247            1 :     tl_write_uint32(&w, 0x695150d7u);             /* CRC_messageMediaPhoto */
    2248            1 :     tl_write_uint32(&w, (1u << 0));               /* photo flag set */
    2249            1 :     tl_write_uint32(&w, CRC_photoEmpty);          /* photoEmpty body */
    2250            1 :     tl_write_int64 (&w, 42LL);
    2251            1 :     tl_write_int32 (&w, 0xBEE);
    2252            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2253            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    2254              :            "storyItem with inner photoEmpty iterates");
    2255            1 :     ASSERT(tl_read_int32(&r) == 0xBEE, "cursor past outer story");
    2256            1 :     tl_writer_free(&w);
    2257              : }
    2258              : 
    2259              : /* LIM-04: doubly-nested storyItem is blocked by the recursion guard
    2260              :  * (returns -1) instead of blowing the stack. */
    2261            1 : static void test_skip_media_story_nested_recursion_guard(void) {
    2262            1 :     TlWriter w; tl_writer_init(&w);
    2263              :     /* depth 1: messageMediaStory with inline storyItem whose inner
    2264              :      * media is another messageMediaStory (depth 2) … up to 6 levels to
    2265              :      * exceed the configured MEDIA_RECURSION_MAX (4). */
    2266            1 :     const int DEPTH = 6;
    2267            7 :     for (int d = 0; d < DEPTH; d++) {
    2268            6 :         tl_write_uint32(&w, CRC_messageMediaStory_t);
    2269            6 :         tl_write_uint32(&w, (1u << 0));
    2270            6 :         tl_write_uint32(&w, TL_peerUser);
    2271            6 :         tl_write_int64 (&w, 1LL);
    2272            6 :         tl_write_int32 (&w, 1);
    2273            6 :         tl_write_uint32(&w, CRC_storyItem_t);
    2274            6 :         tl_write_uint32(&w, 0);
    2275            6 :         tl_write_int32 (&w, 1);
    2276            6 :         tl_write_int32 (&w, 1700000000);
    2277            6 :         tl_write_int32 (&w, 1700086400);
    2278              :         /* inner media will be written by next iteration, or final Empty. */
    2279              :     }
    2280            1 :     tl_write_uint32(&w, CRC_messageMediaEmpty); /* terminator */
    2281            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2282            1 :     int rc = tl_skip_message_media_ex(&r, NULL);
    2283            1 :     ASSERT(rc == -1, "nested recursion beyond guard bails");
    2284            1 :     tl_writer_free(&w);
    2285              : }
    2286              : 
    2287              : /* Full storyItem with a privacy list + a media_area. */
    2288            1 : static void test_skip_media_story_full_with_privacy_and_area(void) {
    2289            1 :     TlWriter w; tl_writer_init(&w);
    2290            1 :     tl_write_uint32(&w, CRC_messageMediaStory_t);
    2291            1 :     tl_write_uint32(&w, (1u << 0));
    2292            1 :     tl_write_uint32(&w, TL_peerUser);
    2293            1 :     tl_write_int64 (&w, 1LL);
    2294            1 :     tl_write_int32 (&w, 1);
    2295              :     /* flags.14 media_areas + flags.2 privacy. */
    2296            1 :     tl_write_uint32(&w, CRC_storyItem_t);
    2297            1 :     tl_write_uint32(&w, (1u << 14) | (1u << 2));
    2298            1 :     tl_write_int32 (&w, 1);
    2299            1 :     tl_write_int32 (&w, 1700000000);
    2300            1 :     tl_write_int32 (&w, 1700086400);
    2301            1 :     tl_write_uint32(&w, CRC_messageMediaEmpty);
    2302              :     /* media_areas: [mediaAreaUrl with coordinates + url]. */
    2303            1 :     tl_write_uint32(&w, TL_vector);
    2304            1 :     tl_write_uint32(&w, 1);
    2305            1 :     tl_write_uint32(&w, 0x37381085u);            /* mediaAreaUrl */
    2306              :     /* mediaAreaCoordinates flags=0 + 5 doubles */
    2307            1 :     tl_write_uint32(&w, 0x03d1ea4eu);
    2308            1 :     tl_write_uint32(&w, 0);
    2309            6 :     for (int i = 0; i < 5; i++) tl_write_double(&w, 0.0);
    2310            1 :     tl_write_string(&w, "https://example.org");
    2311              :     /* privacy: [allowAll, allowUsers([7,11])]. */
    2312            1 :     tl_write_uint32(&w, TL_vector);
    2313            1 :     tl_write_uint32(&w, 2);
    2314            1 :     tl_write_uint32(&w, 0x65427b82u);            /* privacyValueAllowAll */
    2315            1 :     tl_write_uint32(&w, 0xb8905fb2u);            /* privacyValueAllowUsers */
    2316            1 :     tl_write_uint32(&w, TL_vector);
    2317            1 :     tl_write_uint32(&w, 2);
    2318            1 :     tl_write_int64 (&w, 7LL);
    2319            1 :     tl_write_int64 (&w, 11LL);
    2320            1 :     tl_write_int32 (&w, 0xC0DE);
    2321            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2322            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == 0,
    2323              :            "full storyItem with privacy + media_area iterates");
    2324            1 :     ASSERT(tl_read_int32(&r) == (int32_t)0xC0DE, "cursor past");
    2325            1 :     tl_writer_free(&w);
    2326              : }
    2327              : 
    2328            1 : static void test_skip_media_paid_unknown_variant_bails(void) {
    2329            1 :     TlWriter w; tl_writer_init(&w);
    2330            1 :     tl_write_uint32(&w, CRC_messageMediaPaidMedia_t);
    2331            1 :     tl_write_int64 (&w, 1LL);
    2332            1 :     tl_write_uint32(&w, TL_vector);
    2333            1 :     tl_write_uint32(&w, 1);
    2334            1 :     tl_write_uint32(&w, 0xdeadbeefu);          /* unknown ExtendedMedia */
    2335            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2336            1 :     ASSERT(tl_skip_message_media_ex(&r, NULL) == -1, "unknown variant bails");
    2337            1 :     tl_writer_free(&w);
    2338              : }
    2339              : 
    2340            1 : static void test_skip_factcheck_unknown_crc_bails(void) {
    2341            1 :     TlWriter w; tl_writer_init(&w);
    2342            1 :     tl_write_uint32(&w, 0xdeadbeefu);
    2343            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2344            1 :     ASSERT(tl_skip_factcheck(&r) == -1, "unknown crc rejected");
    2345            1 :     tl_writer_free(&w);
    2346              : }
    2347              : 
    2348            1 : static void test_skip_reactions_recent_reactors_bails(void) {
    2349            1 :     TlWriter w; tl_writer_init(&w);
    2350            1 :     tl_write_uint32(&w, CRC_messageReactions_test);
    2351            1 :     tl_write_uint32(&w, (1u << 1));          /* recent_reactions present */
    2352            1 :     tl_write_uint32(&w, TL_vector);
    2353            1 :     tl_write_uint32(&w, 0);                  /* empty results */
    2354              :     /* recent_reactions would follow — we bail before reading. */
    2355            1 :     TlReader r = tl_reader_init(w.data, w.len);
    2356            1 :     ASSERT(tl_skip_message_reactions(&r) == -1, "bails on recent_reactions");
    2357            1 :     tl_writer_free(&w);
    2358              : }
    2359              : 
    2360            1 : void run_tl_skip_tests(void) {
    2361            1 :     RUN_TEST(test_skip_bool);
    2362            1 :     RUN_TEST(test_skip_string);
    2363            1 :     RUN_TEST(test_skip_peer);
    2364            1 :     RUN_TEST(test_skip_peer_unknown);
    2365            1 :     RUN_TEST(test_skip_notification_sound_default);
    2366            1 :     RUN_TEST(test_skip_notification_sound_ringtone);
    2367            1 :     RUN_TEST(test_skip_notification_sound_local);
    2368            1 :     RUN_TEST(test_skip_peer_notify_settings_empty);
    2369            1 :     RUN_TEST(test_skip_peer_notify_settings_full);
    2370            1 :     RUN_TEST(test_skip_draft_message_empty);
    2371            1 :     RUN_TEST(test_skip_message_entity_bold);
    2372            1 :     RUN_TEST(test_skip_message_entity_text_url);
    2373            1 :     RUN_TEST(test_skip_entities_vector);
    2374            1 :     RUN_TEST(test_skip_fwd_header_minimal);
    2375            1 :     RUN_TEST(test_skip_fwd_header_with_channel);
    2376            1 :     RUN_TEST(test_skip_reply_header_minimal);
    2377            1 :     RUN_TEST(test_skip_bool_short);
    2378            1 :     RUN_TEST(test_skip_notification_sound_unknown);
    2379            1 :     RUN_TEST(test_skip_peer_notify_settings_wrong_crc);
    2380            1 :     RUN_TEST(test_skip_draft_message_nonempty);
    2381            1 :     RUN_TEST(test_skip_draft_message_unknown);
    2382            1 :     RUN_TEST(test_skip_message_entity_unknown);
    2383            1 :     RUN_TEST(test_skip_entities_vector_wrong_header);
    2384            1 :     RUN_TEST(test_skip_fwd_header_wrong_crc);
    2385            1 :     RUN_TEST(test_skip_reply_header_wrong_crc);
    2386            1 :     RUN_TEST(test_skip_reply_header_reply_media_bail);
    2387            1 :     RUN_TEST(test_skip_media_empty);
    2388            1 :     RUN_TEST(test_skip_media_unsupported);
    2389            1 :     RUN_TEST(test_skip_media_geo_empty_point);
    2390            1 :     RUN_TEST(test_skip_media_geo);
    2391            1 :     RUN_TEST(test_skip_media_contact);
    2392            1 :     RUN_TEST(test_skip_media_dice);
    2393            1 :     RUN_TEST(test_skip_media_venue);
    2394            1 :     RUN_TEST(test_skip_media_photo_empty_photo);
    2395            1 :     RUN_TEST(test_skip_media_photo_with_sizes);
    2396            1 :     RUN_TEST(test_skip_media_document_empty);
    2397            1 :     RUN_TEST(test_skip_chat_forbidden);
    2398            1 :     RUN_TEST(test_skip_channel_forbidden);
    2399            1 :     RUN_TEST(test_skip_chat_plain);
    2400            1 :     RUN_TEST(test_skip_channel_minimal);
    2401            1 :     RUN_TEST(test_skip_user_empty);
    2402            1 :     RUN_TEST(test_skip_user_full);
    2403            1 :     RUN_TEST(test_extract_chat_forbidden);
    2404            1 :     RUN_TEST(test_extract_user);
    2405            1 :     RUN_TEST(test_extract_user_access_hash);
    2406            1 :     RUN_TEST(test_extract_channel_access_hash);
    2407            1 :     RUN_TEST(test_extract_channel_forbidden_access_hash);
    2408            1 :     RUN_TEST(test_extract_chat_no_access_hash);
    2409            1 :     RUN_TEST(test_skip_reply_markup_hide);
    2410            1 :     RUN_TEST(test_skip_reply_markup_force_reply_with_placeholder);
    2411            1 :     RUN_TEST(test_skip_reply_markup_inline_url_callback);
    2412            1 :     RUN_TEST(test_skip_reply_markup_unknown_bails);
    2413            1 :     RUN_TEST(test_skip_reply_markup_unknown_button_bails);
    2414            1 :     RUN_TEST(test_skip_reactions_empty_results);
    2415            1 :     RUN_TEST(test_skip_reactions_emoji_and_custom);
    2416            1 :     RUN_TEST(test_skip_reactions_recent_reactors_bails);
    2417            1 :     RUN_TEST(test_skip_message_replies_minimal);
    2418            1 :     RUN_TEST(test_skip_message_replies_with_commenters);
    2419            1 :     RUN_TEST(test_skip_message_replies_unknown_crc_bails);
    2420            1 :     RUN_TEST(test_skip_factcheck_need_check_only);
    2421            1 :     RUN_TEST(test_skip_factcheck_with_text);
    2422            1 :     RUN_TEST(test_skip_factcheck_unknown_crc_bails);
    2423            1 :     RUN_TEST(test_skip_media_webpage_empty);
    2424            1 :     RUN_TEST(test_skip_media_webpage_rich);
    2425            1 :     RUN_TEST(test_skip_media_webpage_pending);
    2426            1 :     RUN_TEST(test_skip_media_webpage_cached_page_simple);
    2427            1 :     RUN_TEST(test_skip_media_webpage_cached_page_textconcat);
    2428            1 :     RUN_TEST(test_skip_media_webpage_cached_page_unsupported);
    2429            1 :     RUN_TEST(test_skip_cached_page_blockquote);
    2430            1 :     RUN_TEST(test_skip_cached_page_photo);
    2431            1 :     RUN_TEST(test_skip_cached_page_video);
    2432            1 :     RUN_TEST(test_skip_cached_page_audio);
    2433            1 :     RUN_TEST(test_skip_cached_page_cover);
    2434            1 :     RUN_TEST(test_skip_cached_page_channel);
    2435            1 :     RUN_TEST(test_skip_cached_page_map);
    2436            1 :     RUN_TEST(test_skip_cached_page_list);
    2437            1 :     RUN_TEST(test_skip_cached_page_ordered_list);
    2438            1 :     RUN_TEST(test_skip_cached_page_collage);
    2439            1 :     RUN_TEST(test_skip_cached_page_slideshow);
    2440            1 :     RUN_TEST(test_skip_cached_page_details);
    2441            1 :     RUN_TEST(test_skip_cached_page_related_articles);
    2442            1 :     RUN_TEST(test_skip_cached_page_table);
    2443            1 :     RUN_TEST(test_skip_cached_page_embed);
    2444            1 :     RUN_TEST(test_skip_cached_page_embed_post);
    2445            1 :     RUN_TEST(test_skip_media_webpage_attributes_theme);
    2446            1 :     RUN_TEST(test_skip_media_webpage_attributes_stickerset);
    2447            1 :     RUN_TEST(test_skip_media_webpage_attributes_story_noinline);
    2448            1 :     RUN_TEST(test_skip_media_webpage_attributes_story_inline);
    2449            1 :     RUN_TEST(test_skip_media_webpage_attributes_unknown);
    2450            1 :     RUN_TEST(test_skip_media_webpage_cached_page_plus_attributes);
    2451            1 :     RUN_TEST(test_skip_media_document_full);
    2452            1 :     RUN_TEST(test_skip_media_document_with_thumbs);
    2453            1 :     RUN_TEST(test_skip_media_document_with_video_thumbs);
    2454            1 :     RUN_TEST(test_skip_media_document_sticker_attr);
    2455            1 :     RUN_TEST(test_skip_media_document_sticker_with_mask);
    2456            1 :     RUN_TEST(test_skip_media_document_custom_emoji_attr);
    2457            1 :     RUN_TEST(test_skip_media_poll_minimal);
    2458            1 :     RUN_TEST(test_skip_media_poll_with_results);
    2459            1 :     RUN_TEST(test_skip_media_invoice_minimal);
    2460            1 :     RUN_TEST(test_skip_media_invoice_photo_bails);
    2461            1 :     RUN_TEST(test_skip_media_story_minimal);
    2462            1 :     RUN_TEST(test_skip_media_giveaway_minimal);
    2463            1 :     RUN_TEST(test_skip_media_giveaway_countries_prize);
    2464            1 :     RUN_TEST(test_skip_media_game_photo_only);
    2465            1 :     RUN_TEST(test_skip_media_game_with_document);
    2466            1 :     RUN_TEST(test_skip_media_game_wrong_inner_crc_bails);
    2467            1 :     RUN_TEST(test_skip_media_paid_preview_only);
    2468            1 :     RUN_TEST(test_skip_media_paid_preview_with_dims_and_thumb);
    2469            1 :     RUN_TEST(test_skip_media_paid_wrapped_inner_media);
    2470            1 :     RUN_TEST(test_skip_media_paid_unknown_variant_bails);
    2471            1 :     RUN_TEST(test_skip_media_invoice_with_webdocument_photo);
    2472            1 :     RUN_TEST(test_skip_media_invoice_noproxy_webdocument);
    2473            1 :     RUN_TEST(test_skip_media_invoice_extended_media_preview);
    2474            1 :     RUN_TEST(test_skip_media_story_inline_deleted);
    2475            1 :     RUN_TEST(test_skip_media_story_inline_skipped);
    2476            1 :     RUN_TEST(test_skip_media_story_truncated_full_bails);
    2477            1 :     RUN_TEST(test_skip_media_story_full_minimal);
    2478            1 :     RUN_TEST(test_skip_media_story_full_with_views);
    2479            1 :     RUN_TEST(test_skip_media_story_inner_media_photo);
    2480            1 :     RUN_TEST(test_skip_media_story_nested_recursion_guard);
    2481            1 :     RUN_TEST(test_skip_media_story_full_with_privacy_and_area);
    2482            1 : }
        

Generated by: LCOV version 2.0-1