LCOV - code coverage report
Current view: top level - src/core - tl_skip.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 80.9 % 1988 1609
Test Date: 2026-04-20 19:54:24 Functions: 96.2 % 79 76

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file tl_skip.c
       6              :  * @brief TL object skippers for vector iteration.
       7              :  */
       8              : 
       9              : #include "tl_skip.h"
      10              : #include "tl_registry.h"
      11              : #include "logger.h"
      12              : 
      13              : #include <stdlib.h>
      14              : #include <string.h>
      15              : 
      16              : /* ---- CRCs not yet in tl_registry.h ---- */
      17              : 
      18              : #define CRC_peerNotifySettings          0xa83b0426u
      19              : #define CRC_notificationSoundDefault    0x97e8bebeu
      20              : #define CRC_notificationSoundNone       0x6f0c34dfu
      21              : #define CRC_notificationSoundLocal      0x830b9ae4u
      22              : #define CRC_notificationSoundRingtone   0xff6c8049u
      23              : #define CRC_draftMessage                0x3fccf7efu
      24              : #define CRC_draftMessageEmpty           0x1b0c841au
      25              : 
      26              : /* MessageEntity variants (layer 170+). */
      27              : #define CRC_messageEntityUnknown        0xbb92ba95u
      28              : #define CRC_messageEntityMention        0xfa04579du
      29              : #define CRC_messageEntityHashtag        0x6f635b0du
      30              : #define CRC_messageEntityBotCommand     0x6cef8ac7u
      31              : #define CRC_messageEntityUrl            0x6ed02538u
      32              : #define CRC_messageEntityEmail          0x64e475c2u
      33              : #define CRC_messageEntityBold           0xbd610bc9u
      34              : #define CRC_messageEntityItalic         0x826f8b60u
      35              : #define CRC_messageEntityCode           0x28a20571u
      36              : #define CRC_messageEntityPre            0x73924be0u
      37              : #define CRC_messageEntityTextUrl        0x76a6d327u
      38              : #define CRC_messageEntityMentionName    0xdc7b1140u
      39              : #define CRC_messageEntityPhone          0x9b69e34bu
      40              : #define CRC_messageEntityCashtag        0x4c4e743fu
      41              : #define CRC_messageEntityUnderline      0x9c4e7e8bu
      42              : #define CRC_messageEntityStrike         0xbf0693d4u
      43              : #define CRC_messageEntityBlockquote     0xf1ccaaacu
      44              : #define CRC_messageEntityBankCard       0x761e6af4u
      45              : #define CRC_messageEntitySpoiler        0x32ca960fu
      46              : #define CRC_messageEntityCustomEmoji    0xc8cf05f8u
      47              : 
      48              : /* MessageFwdHeader (layer 170+). */
      49              : #define CRC_messageFwdHeader            0x4e4df4bbu
      50              : 
      51              : /* MessageReplyHeader variants. */
      52              : #define CRC_messageReplyHeader          0xafbc09dbu
      53              : #define CRC_messageReplyStoryHeader     0xe5af939u
      54              : 
      55              : /* PhotoSize variants (layer 170+). */
      56              : #define CRC_photoSizeEmpty              0x0e17e23cu
      57              : #define CRC_photoSize                   0x75c78e60u
      58              : #define CRC_photoCachedSize             0x021e1ad6u
      59              : #define CRC_photoStrippedSize           0xe0b0bc2eu
      60              : #define CRC_photoSizeProgressive        0xfa3efb95u
      61              : #define CRC_photoPathSize               0xd8214d41u
      62              : 
      63              : /* Photo variants. */
      64              : #define CRC_photo                       0xfb197a65u
      65              : #define CRC_photoEmpty                  0x2331b22du
      66              : 
      67              : /* Document variants. */
      68              : #define CRC_document                    0x8fd4c4d8u
      69              : #define CRC_documentEmpty               0x36f8c871u
      70              : 
      71              : /* DocumentAttribute — present inside document.attributes. We don't walk
      72              :  * them; we just skip via Vector count by bailing when document is set. */
      73              : 
      74              : /* GeoPoint. */
      75              : #define CRC_geoPointEmpty               0x1117dd5fu
      76              : #define CRC_geoPoint                    0xb2a2f663u
      77              : 
      78              : /* MessageMedia variants. */
      79              : #define CRC_messageMediaEmpty           0x3ded6320u
      80              : #define CRC_messageMediaPhoto           0x695150d7u
      81              : #define CRC_messageMediaDocument        0x4cf4d72du
      82              : #define CRC_messageMediaGeo             0x56e0d474u
      83              : #define CRC_messageMediaContact         0x70322949u
      84              : #define CRC_messageMediaUnsupported     0x9f84f49eu
      85              : #define CRC_messageMediaVenue           0x2ec0533fu
      86              : #define CRC_messageMediaGeoLive         0xb940c666u
      87              : #define CRC_messageMediaDice            0x3f7ee58bu
      88              : #define CRC_messageMediaWebPage         0xddf8c26eu
      89              : #define CRC_messageMediaPoll            0x4bd6e798u
      90              : #define CRC_messageMediaInvoice         0xf6a548d3u
      91              : #define CRC_messageMediaStory           0x68cb6283u
      92              : #define CRC_messageMediaGiveaway        0xaa073beeu
      93              : #define CRC_messageMediaGame            0xfdb19008u
      94              : #define CRC_messageMediaPaidMedia       0xa8852491u
      95              : 
      96              : /* Game (inside messageMediaGame). */
      97              : #define CRC_game                        0xbdf9653bu
      98              : 
      99              : /* MessageExtendedMedia (inside messageMediaPaidMedia). */
     100              : #define CRC_messageExtendedMediaPreview 0xad628cc8u
     101              : #define CRC_messageExtendedMedia        0xee479c64u
     102              : 
     103              : /* WebDocument (inside messageMediaInvoice.photo). */
     104              : #define CRC_webDocument                 0x1c570ed1u
     105              : #define CRC_webDocumentNoProxy          0xf9c8bcc6u
     106              : 
     107              : /* StoryItem variants (inside messageMediaStory when flags.0). */
     108              : #define CRC_storyItemDeleted            0x51e6ee4fu
     109              : #define CRC_storyItemSkipped            0xffadc913u
     110              : #define CRC_storyItem                   0x79b26a24u
     111              : 
     112              : /* StoryFwdHeader (inside full storyItem.fwd_from). */
     113              : #define CRC_storyFwdHeader              0xb826e150u
     114              : 
     115              : /* MediaAreaCoordinates (inner of every MediaArea). */
     116              : #define CRC_mediaAreaCoordinates        0x03d1ea4eu
     117              : 
     118              : /* GeoPointAddress (inside mediaAreaGeoPoint when flags.0). */
     119              : #define CRC_geoPointAddress             0xde4c5d93u
     120              : 
     121              : /* MediaArea variants. */
     122              : #define CRC_mediaAreaVenue              0xbe82db9cu
     123              : #define CRC_mediaAreaGeoPoint           0xdf8b3b22u
     124              : #define CRC_mediaAreaSuggestedReaction  0x14455871u
     125              : #define CRC_mediaAreaChannelPost        0x770416afu
     126              : #define CRC_mediaAreaUrl                0x37381085u
     127              : #define CRC_mediaAreaWeather            0x49a6549cu
     128              : #define CRC_mediaAreaStarGift           0x5787686du
     129              : 
     130              : /* PrivacyRule variants. */
     131              : #define CRC_privacyValueAllowContacts          0xfffe1bacu
     132              : #define CRC_privacyValueAllowAll               0x65427b82u
     133              : #define CRC_privacyValueAllowUsers             0xb8905fb2u
     134              : #define CRC_privacyValueDisallowContacts       0xf888fa1au
     135              : #define CRC_privacyValueDisallowAll            0x8b73e763u
     136              : #define CRC_privacyValueDisallowUsers          0xe4621141u
     137              : #define CRC_privacyValueAllowChatParticipants  0x6b134e8eu
     138              : #define CRC_privacyValueDisallowChatParticipants 0x41c87565u
     139              : #define CRC_privacyValueAllowCloseFriends      0xf7e8d89bu
     140              : #define CRC_privacyValueAllowPremium           0xece9814bu
     141              : #define CRC_privacyValueAllowBots              0x21461b5du
     142              : #define CRC_privacyValueDisallowBots           0xf6a5f82fu
     143              : 
     144              : /* StoryViews. */
     145              : #define CRC_storyViews                  0x8d595cd6u
     146              : 
     147              : /* WebPage variants (inside messageMediaWebPage). */
     148              : #define CRC_webPage                     0xe89c45b2u
     149              : #define CRC_webPageEmpty                0xeb1477e8u
     150              : #define CRC_webPagePending              0xb0d13e47u
     151              : #define CRC_webPageNotModified          0x7311ca11u
     152              : 
     153              : /* WebPageAttribute variants (webPage.attributes, flags.12). */
     154              : #define CRC_webPageAttributeTheme       0x54b56617u
     155              : #define CRC_webPageAttributeStory       0x2e94c3e7u
     156              : #define CRC_webPageAttributeStickerSet  0x50cc03d3u
     157              : 
     158              : /* Page (webPage.cached_page, flags.10). */
     159              : #define CRC_page                        0x98657f0du
     160              : 
     161              : /* PageBlock variants — full set for cached_page iteration. */
     162              : #define CRC_pageBlockUnsupported        0x13567e8au
     163              : #define CRC_pageBlockTitle              0x70abc3fdu
     164              : #define CRC_pageBlockSubtitle           0x8ffa9a1fu
     165              : #define CRC_pageBlockHeader             0xbfd064ecu
     166              : #define CRC_pageBlockSubheader          0xf12bb6e1u
     167              : #define CRC_pageBlockKicker             0x1e148390u
     168              : #define CRC_pageBlockParagraph          0x467a0766u
     169              : #define CRC_pageBlockPreformatted       0xc070d93eu
     170              : #define CRC_pageBlockFooter             0x48870999u
     171              : #define CRC_pageBlockDivider            0xdb20b188u
     172              : #define CRC_pageBlockAnchor             0xce0d37b0u
     173              : #define CRC_pageBlockAuthorDate         0xbaafe5e0u
     174              : #define CRC_pageBlockBlockquote         0x263d7c26u
     175              : #define CRC_pageBlockPullquote          0x4f4456d5u
     176              : #define CRC_pageBlockPhoto              0x1759c560u
     177              : #define CRC_pageBlockVideo              0x7c8fe7b6u
     178              : #define CRC_pageBlockAudio              0x804361eau
     179              : #define CRC_pageBlockCover              0x39f23300u
     180              : #define CRC_pageBlockChannel            0xef1751b5u
     181              : #define CRC_pageBlockMap                0xa44f3ef6u
     182              : #define CRC_pageBlockList               0xe4e88011u
     183              : #define CRC_pageBlockOrderedList        0x9a8ae1e1u
     184              : #define CRC_pageBlockCollage            0x65a0fa4du
     185              : #define CRC_pageBlockSlideshow          0x031f9590u
     186              : #define CRC_pageBlockDetails            0x76768bedu
     187              : #define CRC_pageBlockRelatedArticles    0x16115a96u
     188              : #define CRC_pageBlockTable              0xbf4dea82u
     189              : #define CRC_pageBlockEmbed              0xa8718dc5u
     190              : #define CRC_pageBlockEmbedPost          0xf259a80bu
     191              : 
     192              : /* PageCaption, PageListItem, PageListOrderedItem, PageTableRow/Cell,
     193              :  * PageRelatedArticle — all reached from PageBlock variants. */
     194              : #define CRC_pageCaption                 0x6f747657u
     195              : #define CRC_pageListItemText            0xb92fb6cdu
     196              : #define CRC_pageListItemBlocks          0x25e073fcu
     197              : #define CRC_pageListOrderedItemText     0x5e068047u
     198              : #define CRC_pageListOrderedItemBlocks   0x98dd8936u
     199              : #define CRC_pageTableRow                0xe0c0c5e5u
     200              : #define CRC_pageTableCell               0x34566b6au
     201              : #define CRC_pageRelatedArticle          0xb390dc08u
     202              : 
     203              : /* RichText variants — full tree so every supported PageBlock can walk
     204              :  * its RichText payload. */
     205              : #define CRC_textEmpty                   0xdc3d824fu
     206              : #define CRC_textPlain                   0x744694e0u
     207              : #define CRC_textBold                    0x6724abc4u
     208              : #define CRC_textItalic                  0xd912a59cu
     209              : #define CRC_textUnderline               0xc12622c4u
     210              : #define CRC_textStrike                  0x9bf8bb95u
     211              : #define CRC_textFixed                   0x6c3f19b9u
     212              : #define CRC_textUrl                     0x3c2884c1u
     213              : #define CRC_textEmail                   0xde5a0dd6u
     214              : #define CRC_textConcat                  0x7e6260d7u
     215              : #define CRC_textSubscript               0xed6a8504u
     216              : #define CRC_textSuperscript             0xc7fb5e01u
     217              : #define CRC_textMarked                  0x034b27f6u
     218              : #define CRC_textPhone                   0x1ccb966au
     219              : #define CRC_textImage                   0x081ccf4fu
     220              : #define CRC_textAnchor                  0x35553762u
     221              : 
     222              : /* Poll + PollAnswer + PollResults + PollAnswerVoters. */
     223              : #define CRC_poll                        0x58747131u
     224              : #define CRC_pollAnswer                  0x6ca9c2e9u
     225              : #define CRC_pollResults                 0x7adc669du
     226              : #define CRC_pollAnswerVoters            0x3b6ddad2u
     227              : #define CRC_textWithEntities_poll       0x751f3146u
     228              : 
     229              : /* ChatPhoto. */
     230              : #define CRC_chatPhotoEmpty              0x37c1011cu
     231              : #define CRC_chatPhoto                   0x1c6e1c11u
     232              : 
     233              : /* UserProfilePhoto. */
     234              : #define CRC_userProfilePhotoEmpty       0x4f11bae1u
     235              : #define CRC_userProfilePhoto            0x82d1f706u
     236              : 
     237              : /* UserStatus. */
     238              : #define CRC_userStatusEmpty             0x09d05049u
     239              : #define CRC_userStatusOnline            0xedb93949u
     240              : #define CRC_userStatusOffline           0x008c703fu
     241              : #define CRC_userStatusRecently          0x7b197dc8u
     242              : #define CRC_userStatusLastWeek          0x541a1d1au
     243              : #define CRC_userStatusLastMonth         0x65899e67u
     244              : 
     245              : /* RestrictionReason. */
     246              : #define CRC_restrictionReason           0xd072acb4u
     247              : 
     248              : /* Username. */
     249              : #define CRC_username                    0xb4073647u
     250              : 
     251              : /* PeerColor. */
     252              : #define CRC_peerColor                   0xb54b5acfu
     253              : 
     254              : /* EmojiStatus. */
     255              : #define CRC_emojiStatusEmpty            0x2de11aaeu
     256              : #define CRC_emojiStatus                 0x929b619du
     257              : #define CRC_emojiStatusUntil            0xfa30a8c7u
     258              : 
     259              : /* ChatAdminRights / ChatBannedRights. */
     260              : #define CRC_chatAdminRights             0x5fb224d5u
     261              : #define CRC_chatBannedRights            0x9f120418u
     262              : 
     263              : /* ReplyMarkup variants. */
     264              : #define CRC_replyKeyboardHide           0xa03e5b85u
     265              : #define CRC_replyKeyboardForceReply     0x86b40b08u
     266              : #define CRC_replyKeyboardMarkup         0x85dd99d1u
     267              : #define CRC_replyInlineMarkup           0x48a30254u
     268              : 
     269              : /* KeyboardButtonRow. */
     270              : #define CRC_keyboardButtonRow           0x77608b83u
     271              : 
     272              : /* KeyboardButton variants we handle inline. */
     273              : #define CRC_keyboardButton              0xa2fa4880u
     274              : #define CRC_keyboardButtonUrl           0x258aff05u
     275              : #define CRC_keyboardButtonCallback      0x35bbdb6bu
     276              : #define CRC_keyboardButtonRequestPhone  0xb16a6c29u
     277              : #define CRC_keyboardButtonRequestGeoLoc 0xfc796b3fu
     278              : #define CRC_keyboardButtonSwitchInline  0x93b9fbb5u
     279              : #define CRC_keyboardButtonGame          0x50f41ccfu
     280              : #define CRC_keyboardButtonBuy           0xafd93fbbu
     281              : #define CRC_keyboardButtonUrlAuth       0x10b78d29u
     282              : #define CRC_keyboardButtonRequestPoll   0xbbc7515du
     283              : #define CRC_keyboardButtonUserProfile   0x308660c1u
     284              : #define CRC_keyboardButtonWebView       0x13767230u
     285              : #define CRC_keyboardButtonSimpleWebView 0xa0c0505cu
     286              : 
     287              : /* MessageReplies. */
     288              : #define CRC_messageReplies              0x83d60fc2u
     289              : 
     290              : /* FactCheck + TextWithEntities. */
     291              : #define CRC_factCheck                   0xb89bfccfu
     292              : #define CRC_textWithEntities            0x751f3146u
     293              : 
     294              : /* MessageReactions + Reaction variants. */
     295              : #define CRC_messageReactions            0x4f2b9479u
     296              : #define CRC_reactionCount               0xa3d1cb80u
     297              : #define CRC_reactionEmpty               0x79f5d419u
     298              : #define CRC_reactionEmoji               0x1b2286b8u
     299              : #define CRC_reactionCustomEmoji         0x8935fc73u
     300              : #define CRC_reactionPaid                0x523da4ebu
     301              : 
     302            6 : int tl_skip_bool(TlReader *r) {
     303            6 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     304            5 :     tl_read_uint32(r);
     305            5 :     return 0;
     306              : }
     307              : 
     308         2001 : int tl_skip_string(TlReader *r) {
     309              :     /* tl_read_string already advances the cursor and handles padding. */
     310         2001 :     if (!tl_reader_ok(r)) return -1;
     311         2000 :     char *s = tl_read_string(r);
     312         2000 :     if (!s) return -1;
     313         2000 :     free(s);
     314         2000 :     return 0;
     315              : }
     316              : 
     317         1141 : int tl_skip_peer(TlReader *r) {
     318         1141 :     if (!tl_reader_ok(r) || r->len - r->pos < 12) return -1;
     319         1140 :     uint32_t crc = tl_read_uint32(r);
     320         1140 :     (void)tl_read_int64(r);
     321         1140 :     switch (crc) {
     322         1139 :     case TL_peerUser:
     323              :     case TL_peerChat:
     324              :     case TL_peerChannel:
     325         1139 :         return 0;
     326            1 :     default:
     327            1 :         logger_log(LOG_WARN, "tl_skip_peer: unknown Peer 0x%08x", crc);
     328            1 :         return -1;
     329              :     }
     330              : }
     331              : 
     332            8 : int tl_skip_notification_sound(TlReader *r) {
     333            8 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     334            8 :     uint32_t crc = tl_read_uint32(r);
     335            8 :     switch (crc) {
     336            5 :     case CRC_notificationSoundDefault:
     337              :     case CRC_notificationSoundNone:
     338            5 :         return 0;                              /* no payload */
     339            1 :     case CRC_notificationSoundRingtone:
     340            1 :         if (r->len - r->pos < 8) return -1;
     341            1 :         tl_read_int64(r);                      /* id */
     342            1 :         return 0;
     343            1 :     case CRC_notificationSoundLocal:
     344            1 :         if (tl_skip_string(r) != 0) return -1; /* title */
     345            1 :         if (tl_skip_string(r) != 0) return -1; /* data */
     346            1 :         return 0;
     347            1 :     default:
     348            1 :         logger_log(LOG_WARN, "tl_skip_notification_sound: unknown 0x%08x", crc);
     349            1 :         return -1;
     350              :     }
     351              : }
     352              : 
     353              : /* peerNotifySettings#a83b0426 flags:#
     354              :  *   show_previews:flags.0?Bool  silent:flags.1?Bool  mute_until:flags.2?int
     355              :  *   ios_sound:flags.3?NotificationSound
     356              :  *   android_sound:flags.4?NotificationSound
     357              :  *   other_sound:flags.5?NotificationSound
     358              :  *   stories_muted:flags.6?Bool  stories_hide_sender:flags.7?Bool
     359              :  *   stories_ios_sound:flags.8?NotificationSound
     360              :  *   stories_android_sound:flags.9?NotificationSound
     361              :  *   stories_other_sound:flags.10?NotificationSound
     362              :  */
     363          420 : int tl_skip_peer_notify_settings(TlReader *r) {
     364          420 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
     365          420 :     uint32_t crc = tl_read_uint32(r);
     366          420 :     if (crc != CRC_peerNotifySettings) {
     367            1 :         logger_log(LOG_WARN,
     368              :                    "tl_skip_peer_notify_settings: unexpected 0x%08x", crc);
     369            1 :         return -1;
     370              :     }
     371          419 :     uint32_t flags = tl_read_uint32(r);
     372              : 
     373          419 :     if (flags & (1u << 0)) if (tl_skip_bool(r) != 0) return -1;
     374          419 :     if (flags & (1u << 1)) if (tl_skip_bool(r) != 0) return -1;
     375          419 :     if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
     376          419 :     if (flags & (1u << 3)) if (tl_skip_notification_sound(r) != 0) return -1;
     377          419 :     if (flags & (1u << 4)) if (tl_skip_notification_sound(r) != 0) return -1;
     378          419 :     if (flags & (1u << 5)) if (tl_skip_notification_sound(r) != 0) return -1;
     379          419 :     if (flags & (1u << 6)) if (tl_skip_bool(r) != 0) return -1;
     380          419 :     if (flags & (1u << 7)) if (tl_skip_bool(r) != 0) return -1;
     381          419 :     if (flags & (1u << 8)) if (tl_skip_notification_sound(r) != 0) return -1;
     382          419 :     if (flags & (1u << 9)) if (tl_skip_notification_sound(r) != 0) return -1;
     383          419 :     if (flags & (1u << 10)) if (tl_skip_notification_sound(r) != 0) return -1;
     384          419 :     return 0;
     385              : }
     386              : 
     387              : /* draftMessageEmpty#1b0c841a flags:# date:flags.0?int
     388              :  * draftMessage#3fccf7ef      — we do NOT fully parse this (it contains
     389              :  * InputReplyTo, Vector<MessageEntity>, InputMedia which are deep nested).
     390              :  * Return -1 so the caller stops iteration; callers wrapping dialogs can
     391              :  * skip entries with draft by first checking Dialog.flags.1.
     392              :  */
     393            4 : int tl_skip_draft_message(TlReader *r) {
     394            4 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     395            4 :     uint32_t crc = tl_read_uint32(r);
     396            4 :     if (crc == CRC_draftMessageEmpty) {
     397            2 :         if (r->len - r->pos < 4) return -1;
     398            2 :         uint32_t flags = tl_read_uint32(r);
     399            2 :         if (flags & 1u) {
     400            1 :             if (r->len - r->pos < 4) return -1;
     401            1 :             tl_read_int32(r); /* date */
     402              :         }
     403            2 :         return 0;
     404              :     }
     405            2 :     if (crc == CRC_draftMessage) {
     406            1 :         logger_log(LOG_WARN,
     407              :                    "tl_skip_draft_message: non-empty draft not parseable yet");
     408            1 :         return -1;
     409              :     }
     410            1 :     logger_log(LOG_WARN, "tl_skip_draft_message: unknown 0x%08x", crc);
     411            1 :     return -1;
     412              : }
     413              : 
     414           10 : int tl_skip_message_entity(TlReader *r) {
     415           10 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     416           10 :     uint32_t crc = tl_read_uint32(r);
     417              : 
     418              :     /* All entities start with offset:int length:int (8 bytes). */
     419           10 :     switch (crc) {
     420            5 :     case CRC_messageEntityUnknown:
     421              :     case CRC_messageEntityMention:
     422              :     case CRC_messageEntityHashtag:
     423              :     case CRC_messageEntityBotCommand:
     424              :     case CRC_messageEntityUrl:
     425              :     case CRC_messageEntityEmail:
     426              :     case CRC_messageEntityBold:
     427              :     case CRC_messageEntityItalic:
     428              :     case CRC_messageEntityCode:
     429              :     case CRC_messageEntityPhone:
     430              :     case CRC_messageEntityCashtag:
     431              :     case CRC_messageEntityUnderline:
     432              :     case CRC_messageEntityStrike:
     433              :     case CRC_messageEntityBankCard:
     434              :     case CRC_messageEntitySpoiler:
     435            5 :         if (r->len - r->pos < 8) return -1;
     436            5 :         tl_read_int32(r); tl_read_int32(r);
     437            5 :         return 0;
     438              : 
     439            0 :     case CRC_messageEntityPre:
     440            0 :         if (r->len - r->pos < 8) return -1;
     441            0 :         tl_read_int32(r); tl_read_int32(r);
     442            0 :         return tl_skip_string(r); /* language */
     443              : 
     444            1 :     case CRC_messageEntityTextUrl:
     445            1 :         if (r->len - r->pos < 8) return -1;
     446            1 :         tl_read_int32(r); tl_read_int32(r);
     447            1 :         return tl_skip_string(r); /* url */
     448              : 
     449            1 :     case CRC_messageEntityMentionName:
     450            1 :         if (r->len - r->pos < 16) return -1;
     451            1 :         tl_read_int32(r); tl_read_int32(r);
     452            1 :         tl_read_int64(r); /* user_id */
     453            1 :         return 0;
     454              : 
     455            1 :     case CRC_messageEntityCustomEmoji:
     456            1 :         if (r->len - r->pos < 16) return -1;
     457            1 :         tl_read_int32(r); tl_read_int32(r);
     458            1 :         tl_read_int64(r); /* document_id */
     459            1 :         return 0;
     460              : 
     461            1 :     case CRC_messageEntityBlockquote:
     462              :         /* flags:# offset:int length:int — no string payload */
     463            1 :         if (r->len - r->pos < 12) return -1;
     464            1 :         tl_read_uint32(r); /* flags */
     465            1 :         tl_read_int32(r); tl_read_int32(r);
     466            1 :         return 0;
     467              : 
     468            1 :     default:
     469            1 :         logger_log(LOG_WARN, "tl_skip_message_entity: unknown 0x%08x", crc);
     470            1 :         return -1;
     471              :     }
     472              : }
     473              : 
     474           18 : int tl_skip_message_entities_vector(TlReader *r) {
     475           18 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
     476           18 :     uint32_t vec_crc = tl_read_uint32(r);
     477           18 :     if (vec_crc != TL_vector) {
     478            0 :         logger_log(LOG_WARN,
     479              :                    "tl_skip_message_entities_vector: expected vector 0x%08x",
     480              :                    vec_crc);
     481            0 :         return -1;
     482              :     }
     483           18 :     uint32_t count = tl_read_uint32(r);
     484           27 :     for (uint32_t i = 0; i < count; i++) {
     485           10 :         if (tl_skip_message_entity(r) != 0) return -1;
     486              :     }
     487           17 :     return 0;
     488              : }
     489              : 
     490              : /* ---- ReplyMarkup skipper ---- */
     491              : 
     492              : /* Skip a KeyboardButton. Returns -1 on unknown variant. */
     493            5 : static int skip_keyboard_button(TlReader *r) {
     494            5 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     495            5 :     uint32_t crc = tl_read_uint32(r);
     496            5 :     switch (crc) {
     497            1 :     case CRC_keyboardButton:
     498              :     case CRC_keyboardButtonRequestPhone:
     499              :     case CRC_keyboardButtonRequestGeoLoc:
     500              :     case CRC_keyboardButtonGame:
     501              :     case CRC_keyboardButtonBuy:
     502            1 :         return tl_skip_string(r);                 /* text */
     503            1 :     case CRC_keyboardButtonUrl: {
     504            1 :         if (tl_skip_string(r) != 0) return -1;    /* text */
     505            1 :         return tl_skip_string(r);                 /* url */
     506              :     }
     507            3 :     case CRC_keyboardButtonCallback: {
     508            3 :         if (r->len - r->pos < 4) return -1;
     509            3 :         tl_read_uint32(r);                        /* flags */
     510            3 :         if (tl_skip_string(r) != 0) return -1;    /* text */
     511            3 :         return tl_skip_string(r);                 /* data:bytes */
     512              :     }
     513            0 :     case CRC_keyboardButtonSwitchInline: {
     514            0 :         if (r->len - r->pos < 4) return -1;
     515            0 :         uint32_t flags = tl_read_uint32(r);
     516            0 :         if (tl_skip_string(r) != 0) return -1;    /* text */
     517            0 :         if (tl_skip_string(r) != 0) return -1;    /* query */
     518            0 :         if (flags & (1u << 1)) {
     519              :             /* peer_types:Vector<InlineQueryPeerType> — unknown nested
     520              :              * variants; bail. */
     521            0 :             logger_log(LOG_WARN,
     522              :                        "skip_keyboard_button: peer_types on switchInline");
     523            0 :             return -1;
     524              :         }
     525            0 :         return 0;
     526              :     }
     527            0 :     case CRC_keyboardButtonUrlAuth: {
     528            0 :         if (r->len - r->pos < 4) return -1;
     529            0 :         uint32_t flags = tl_read_uint32(r);
     530            0 :         if (tl_skip_string(r) != 0) return -1;    /* text */
     531            0 :         if (flags & (1u << 0))
     532            0 :             if (tl_skip_string(r) != 0) return -1;/* fwd_text */
     533            0 :         if (tl_skip_string(r) != 0) return -1;    /* url */
     534            0 :         if (r->len - r->pos < 4) return -1;
     535            0 :         tl_read_int32(r);                         /* button_id */
     536            0 :         return 0;
     537              :     }
     538            0 :     case CRC_keyboardButtonRequestPoll: {
     539            0 :         if (r->len - r->pos < 4) return -1;
     540            0 :         uint32_t flags = tl_read_uint32(r);
     541            0 :         if (flags & (1u << 0)) {
     542            0 :             if (r->len - r->pos < 4) return -1;
     543            0 :             tl_read_uint32(r);                    /* Bool (crc only) */
     544              :         }
     545            0 :         return tl_skip_string(r);                 /* text */
     546              :     }
     547            0 :     case CRC_keyboardButtonUserProfile: {
     548            0 :         if (tl_skip_string(r) != 0) return -1;    /* text */
     549            0 :         if (r->len - r->pos < 8) return -1;
     550            0 :         tl_read_int64(r);                         /* user_id */
     551            0 :         return 0;
     552              :     }
     553            0 :     case CRC_keyboardButtonWebView:
     554              :     case CRC_keyboardButtonSimpleWebView: {
     555            0 :         if (tl_skip_string(r) != 0) return -1;    /* text */
     556            0 :         return tl_skip_string(r);                 /* url */
     557              :     }
     558            0 :     default:
     559            0 :         logger_log(LOG_WARN, "skip_keyboard_button: unknown 0x%08x", crc);
     560            0 :         return -1;
     561              :     }
     562              : }
     563              : 
     564              : /* Skip a KeyboardButtonRow = crc + Vector<KeyboardButton>. */
     565            4 : static int skip_keyboard_button_row(TlReader *r) {
     566            4 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     567            4 :     uint32_t crc = tl_read_uint32(r);
     568            4 :     if (crc != CRC_keyboardButtonRow) {
     569            0 :         logger_log(LOG_WARN,
     570              :                    "skip_keyboard_button_row: expected 0x77608b83 got 0x%08x",
     571              :                    crc);
     572            0 :         return -1;
     573              :     }
     574            4 :     if (r->len - r->pos < 8) return -1;
     575            4 :     uint32_t vec_crc = tl_read_uint32(r);
     576            4 :     if (vec_crc != TL_vector) return -1;
     577            4 :     uint32_t n = tl_read_uint32(r);
     578            9 :     for (uint32_t i = 0; i < n; i++) {
     579            5 :         if (skip_keyboard_button(r) != 0) return -1;
     580              :     }
     581            4 :     return 0;
     582              : }
     583              : 
     584              : /* Skip a Vector<KeyboardButtonRow>. */
     585            5 : static int skip_button_row_vector(TlReader *r) {
     586            5 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
     587            5 :     uint32_t vec_crc = tl_read_uint32(r);
     588            5 :     if (vec_crc != TL_vector) return -1;
     589            5 :     uint32_t n = tl_read_uint32(r);
     590            9 :     for (uint32_t i = 0; i < n; i++) {
     591            4 :         if (skip_keyboard_button_row(r) != 0) return -1;
     592              :     }
     593            5 :     return 0;
     594              : }
     595              : 
     596            9 : int tl_skip_reply_markup(TlReader *r) {
     597            9 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     598            9 :     uint32_t crc = tl_read_uint32(r);
     599            9 :     switch (crc) {
     600            2 :     case CRC_replyKeyboardHide:
     601              :     case CRC_replyKeyboardForceReply: {
     602              :         /* Body: flags:# (+ optional placeholder:flags.3?string for
     603              :          * forceReply). */
     604            2 :         if (r->len - r->pos < 4) return -1;
     605            2 :         uint32_t flags = tl_read_uint32(r);
     606            2 :         if (crc == CRC_replyKeyboardForceReply && (flags & (1u << 3))) {
     607            1 :             if (tl_skip_string(r) != 0) return -1; /* placeholder */
     608              :         }
     609            2 :         return 0;
     610              :     }
     611            0 :     case CRC_replyKeyboardMarkup: {
     612            0 :         if (r->len - r->pos < 4) return -1;
     613            0 :         uint32_t flags = tl_read_uint32(r);
     614            0 :         if (skip_button_row_vector(r) != 0) return -1;
     615            0 :         if (flags & (1u << 3)) {
     616            0 :             if (tl_skip_string(r) != 0) return -1; /* placeholder */
     617              :         }
     618            0 :         return 0;
     619              :     }
     620            5 :     case CRC_replyInlineMarkup:
     621            5 :         return skip_button_row_vector(r);
     622            2 :     default:
     623            2 :         logger_log(LOG_WARN, "tl_skip_reply_markup: unknown 0x%08x", crc);
     624            2 :         return -1;
     625              :     }
     626              : }
     627              : 
     628              : /* ---- MessageReactions skipper ---- */
     629              : 
     630              : /* Skip a Reaction variant. */
     631            8 : static int skip_reaction(TlReader *r) {
     632            8 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     633            8 :     uint32_t crc = tl_read_uint32(r);
     634            8 :     switch (crc) {
     635            2 :     case CRC_reactionEmpty:
     636              :     case CRC_reactionPaid:
     637            2 :         return 0;
     638            4 :     case CRC_reactionEmoji:
     639            4 :         return tl_skip_string(r);
     640            1 :     case CRC_reactionCustomEmoji:
     641            1 :         if (r->len - r->pos < 8) return -1;
     642            1 :         tl_read_int64(r);                       /* document_id */
     643            1 :         return 0;
     644            1 :     default:
     645            1 :         logger_log(LOG_WARN, "skip_reaction: unknown 0x%08x", crc);
     646            1 :         return -1;
     647              :     }
     648              : }
     649              : 
     650              : /* Skip a ReactionCount#a3d1cb80: flags + (chosen_order) + Reaction + count. */
     651            7 : static int skip_reaction_count(TlReader *r) {
     652            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     653            7 :     uint32_t crc = tl_read_uint32(r);
     654            7 :     if (crc != CRC_reactionCount) {
     655            0 :         logger_log(LOG_WARN, "skip_reaction_count: expected 0xa3d1cb80 got 0x%08x",
     656              :                    crc);
     657            0 :         return -1;
     658              :     }
     659            7 :     if (r->len - r->pos < 4) return -1;
     660            7 :     uint32_t flags = tl_read_uint32(r);
     661            7 :     if (flags & (1u << 0)) {
     662            1 :         if (r->len - r->pos < 4) return -1;
     663            1 :         tl_read_int32(r);                       /* chosen_order */
     664              :     }
     665            7 :     if (skip_reaction(r) != 0) return -1;
     666            6 :     if (r->len - r->pos < 4) return -1;
     667            6 :     tl_read_int32(r);                           /* count */
     668            6 :     return 0;
     669              : }
     670              : 
     671            6 : int tl_skip_message_reactions(TlReader *r) {
     672            6 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     673            6 :     uint32_t crc = tl_read_uint32(r);
     674            6 :     if (crc != CRC_messageReactions) {
     675            0 :         logger_log(LOG_WARN, "tl_skip_message_reactions: unknown 0x%08x", crc);
     676            0 :         return -1;
     677              :     }
     678            6 :     if (r->len - r->pos < 4) return -1;
     679            6 :     uint32_t flags = tl_read_uint32(r);
     680              :     /* results:Vector<ReactionCount> — required. */
     681            6 :     if (r->len - r->pos < 8) return -1;
     682            6 :     uint32_t vec_crc = tl_read_uint32(r);
     683            6 :     if (vec_crc != TL_vector) return -1;
     684            6 :     uint32_t n = tl_read_uint32(r);
     685           11 :     for (uint32_t i = 0; i < n; i++) {
     686            6 :         if (skip_reaction_count(r) != 0) return -1;
     687              :     }
     688              :     /* recent_reactions:flags.1?Vector<MessagePeerReaction>  — BAIL */
     689              :     /* top_reactors:flags.2?Vector<MessagePeerVote>           — BAIL */
     690            5 :     if (flags & ((1u << 1) | (1u << 2))) {
     691            1 :         logger_log(LOG_WARN,
     692              :                    "tl_skip_message_reactions: recent/top reactors present "
     693              :                    "(flags=0x%08x) — not supported", flags);
     694            1 :         return -1;
     695              :     }
     696            4 :     return 0;
     697              : }
     698              : 
     699              : /* ---- MessageReplies skipper ----
     700              :  * messageReplies#83d60fc2 flags:# comments:flags.0?true
     701              :  *   replies:int replies_pts:int
     702              :  *   recent_repliers:flags.1?Vector<Peer>
     703              :  *   channel_id:flags.0?long
     704              :  *   max_id:flags.2?int  read_max_id:flags.3?int
     705              :  */
     706            6 : int tl_skip_message_replies(TlReader *r) {
     707            6 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     708            6 :     uint32_t crc = tl_read_uint32(r);
     709            6 :     if (crc != CRC_messageReplies) {
     710            1 :         logger_log(LOG_WARN, "tl_skip_message_replies: unknown 0x%08x", crc);
     711            1 :         return -1;
     712              :     }
     713            5 :     if (r->len - r->pos < 12) return -1;
     714            5 :     uint32_t flags = tl_read_uint32(r);
     715            5 :     tl_read_int32(r);                                 /* replies */
     716            5 :     tl_read_int32(r);                                 /* replies_pts */
     717            5 :     if (flags & (1u << 1)) {
     718            1 :         if (r->len - r->pos < 8) return -1;
     719            1 :         uint32_t vec_crc = tl_read_uint32(r);
     720            1 :         if (vec_crc != TL_vector) return -1;
     721            1 :         uint32_t n = tl_read_uint32(r);
     722            2 :         for (uint32_t i = 0; i < n; i++) {
     723            1 :             if (tl_skip_peer(r) != 0) return -1;
     724              :         }
     725              :     }
     726            5 :     if (flags & (1u << 0)) {
     727            1 :         if (r->len - r->pos < 8) return -1;
     728            1 :         tl_read_int64(r);                             /* channel_id */
     729              :     }
     730            5 :     if (flags & (1u << 2)) {
     731            1 :         if (r->len - r->pos < 4) return -1;
     732            1 :         tl_read_int32(r);                             /* max_id */
     733              :     }
     734            5 :     if (flags & (1u << 3)) {
     735            1 :         if (r->len - r->pos < 4) return -1;
     736            1 :         tl_read_int32(r);                             /* read_max_id */
     737              :     }
     738            5 :     return 0;
     739              : }
     740              : 
     741              : /* ---- FactCheck skipper ----
     742              :  * factCheck#b89bfccf flags:#
     743              :  *   need_check:flags.0?true
     744              :  *   country:flags.1?string
     745              :  *   text:flags.1?TextWithEntities
     746              :  *   hash:long
     747              :  *
     748              :  * textWithEntities#751f3146 text:string entities:Vector<MessageEntity>
     749              :  */
     750            6 : int tl_skip_factcheck(TlReader *r) {
     751            6 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     752            6 :     uint32_t crc = tl_read_uint32(r);
     753            6 :     if (crc != CRC_factCheck) {
     754            1 :         logger_log(LOG_WARN, "tl_skip_factcheck: unknown 0x%08x", crc);
     755            1 :         return -1;
     756              :     }
     757            5 :     if (r->len - r->pos < 4) return -1;
     758            5 :     uint32_t flags = tl_read_uint32(r);
     759            5 :     if (flags & (1u << 1)) {
     760            4 :         if (tl_skip_string(r) != 0) return -1;  /* country */
     761              :         /* text:TextWithEntities */
     762            4 :         if (r->len - r->pos < 4) return -1;
     763            4 :         uint32_t twe_crc = tl_read_uint32(r);
     764            4 :         if (twe_crc != CRC_textWithEntities) {
     765            0 :             logger_log(LOG_WARN,
     766              :                        "tl_skip_factcheck: expected textWithEntities got 0x%08x",
     767              :                        twe_crc);
     768            0 :             return -1;
     769              :         }
     770            4 :         if (tl_skip_string(r) != 0) return -1;  /* text */
     771            4 :         if (tl_skip_message_entities_vector(r) != 0) return -1;
     772              :     }
     773            5 :     if (r->len - r->pos < 8) return -1;
     774            5 :     tl_read_int64(r);                            /* hash */
     775            5 :     return 0;
     776              : }
     777              : 
     778              : /* messageFwdHeader#4e4df4bb flags:#
     779              :  *   imported:flags.7?true           (no data)
     780              :  *   saved_out:flags.11?true         (no data)
     781              :  *   from_id:flags.0?Peer
     782              :  *   from_name:flags.5?string
     783              :  *   date:int                        (always present)
     784              :  *   channel_post:flags.2?int
     785              :  *   post_author:flags.3?string
     786              :  *   saved_from_peer:flags.4?Peer
     787              :  *   saved_from_msg_id:flags.4?int
     788              :  *   saved_from_id:flags.8?Peer
     789              :  *   saved_from_name:flags.9?string
     790              :  *   saved_date:flags.10?int
     791              :  *   psa_type:flags.6?string
     792              :  */
     793            5 : int tl_skip_message_fwd_header(TlReader *r) {
     794            5 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
     795            5 :     uint32_t crc = tl_read_uint32(r);
     796            5 :     if (crc != CRC_messageFwdHeader) {
     797            1 :         logger_log(LOG_WARN,
     798              :                    "tl_skip_message_fwd_header: unexpected 0x%08x", crc);
     799            1 :         return -1;
     800              :     }
     801            4 :     uint32_t flags = tl_read_uint32(r);
     802              : 
     803            4 :     if (flags & (1u << 0)) if (tl_skip_peer(r) != 0) return -1;
     804            4 :     if (flags & (1u << 5)) if (tl_skip_string(r) != 0) return -1;
     805            4 :     if (r->len - r->pos < 4) return -1;
     806            4 :     tl_read_int32(r); /* date */
     807            4 :     if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
     808            4 :     if (flags & (1u << 3)) if (tl_skip_string(r) != 0) return -1;
     809            4 :     if (flags & (1u << 4)) {
     810            0 :         if (tl_skip_peer(r) != 0) return -1;
     811            0 :         if (r->len - r->pos < 4) return -1;
     812            0 :         tl_read_int32(r);
     813              :     }
     814            4 :     if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) return -1;
     815            4 :     if (flags & (1u << 9)) if (tl_skip_string(r) != 0) return -1;
     816            4 :     if (flags & (1u << 10)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
     817            4 :     if (flags & (1u << 6)) if (tl_skip_string(r) != 0) return -1;
     818            4 :     return 0;
     819              : }
     820              : 
     821              : /* messageReplyHeader#afbc09db flags:#
     822              :  *   reply_to_scheduled:flags.2?true   (no data)
     823              :  *   forum_topic:flags.3?true          (no data)
     824              :  *   quote:flags.9?true                (no data)
     825              :  *   reply_to_msg_id:flags.4?int
     826              :  *   reply_to_peer_id:flags.0?Peer
     827              :  *   reply_from:flags.5?MessageFwdHeader
     828              :  *   reply_media:flags.8?MessageMedia           <- NOT IMPLEMENTED
     829              :  *   reply_to_top_id:flags.1?int
     830              :  *   quote_text:flags.6?string
     831              :  *   quote_entities:flags.7?Vector<MessageEntity>
     832              :  *   quote_offset:flags.10?int
     833              :  */
     834            7 : int tl_skip_message_reply_header(TlReader *r) {
     835            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     836            7 :     uint32_t crc = tl_read_uint32(r);
     837            7 :     if (crc == CRC_messageReplyStoryHeader) {
     838              :         /* peer_id:Peer story_id:int */
     839            1 :         if (tl_skip_peer(r) != 0) return -1;
     840            1 :         if (r->len - r->pos < 4) return -1;
     841            1 :         tl_read_int32(r);
     842            1 :         return 0;
     843              :     }
     844            6 :     if (crc != CRC_messageReplyHeader) {
     845            1 :         logger_log(LOG_WARN,
     846              :                    "tl_skip_message_reply_header: unexpected 0x%08x", crc);
     847            1 :         return -1;
     848              :     }
     849            5 :     if (r->len - r->pos < 4) return -1;
     850            5 :     uint32_t flags = tl_read_uint32(r);
     851              : 
     852            5 :     if (flags & (1u << 4)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
     853            5 :     if (flags & (1u << 0)) if (tl_skip_peer(r) != 0) return -1;
     854            5 :     if (flags & (1u << 5)) if (tl_skip_message_fwd_header(r) != 0) return -1;
     855            5 :     if (flags & (1u << 8)) {
     856            0 :         logger_log(LOG_WARN,
     857              :                    "tl_skip_message_reply_header: reply_media not implemented");
     858            0 :         return -1;
     859              :     }
     860            5 :     if (flags & (1u << 1)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
     861            5 :     if (flags & (1u << 6)) if (tl_skip_string(r) != 0) return -1;
     862            5 :     if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) return -1;
     863            5 :     if (flags & (1u << 10)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
     864            5 :     return 0;
     865              : }
     866              : 
     867              : /* ---- PhotoSize skipper ----
     868              :  * Variants:
     869              :  *   photoSizeEmpty#0e17e23c type:string
     870              :  *   photoSize#75c78e60 type:string w:int h:int size:int
     871              :  *   photoCachedSize#021e1ad6 type:string w:int h:int bytes:bytes
     872              :  *   photoStrippedSize#e0b0bc2e type:string bytes:bytes
     873              :  *   photoSizeProgressive#fa3efb95 type:string w:int h:int sizes:Vector<int>
     874              :  *   photoPathSize#d8214d41 type:string bytes:bytes
     875              :  */
     876            6 : int tl_skip_photo_size(TlReader *r) {
     877            6 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     878            6 :     uint32_t crc = tl_read_uint32(r);
     879            6 :     switch (crc) {
     880            2 :     case CRC_photoSizeEmpty:
     881            2 :         return tl_skip_string(r);
     882            1 :     case CRC_photoSize:
     883            1 :         if (tl_skip_string(r) != 0) return -1;
     884            1 :         if (r->len - r->pos < 12) return -1;
     885            1 :         tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
     886            1 :         return 0;
     887            0 :     case CRC_photoCachedSize:
     888            0 :         if (tl_skip_string(r) != 0) return -1;
     889            0 :         if (r->len - r->pos < 8) return -1;
     890            0 :         tl_read_int32(r); tl_read_int32(r);
     891            0 :         return tl_skip_string(r); /* bytes */
     892            2 :     case CRC_photoStrippedSize:
     893              :     case CRC_photoPathSize:
     894            2 :         if (tl_skip_string(r) != 0) return -1;
     895            2 :         return tl_skip_string(r); /* bytes */
     896            0 :     case CRC_photoSizeProgressive: {
     897            0 :         if (tl_skip_string(r) != 0) return -1;
     898            0 :         if (r->len - r->pos < 8) return -1;
     899            0 :         tl_read_int32(r); tl_read_int32(r);
     900              :         /* sizes:Vector<int> */
     901            0 :         if (r->len - r->pos < 8) return -1;
     902            0 :         uint32_t vec_crc = tl_read_uint32(r);
     903            0 :         if (vec_crc != TL_vector) return -1;
     904            0 :         uint32_t count = tl_read_uint32(r);
     905            0 :         if (r->len - r->pos < count * 4ULL) return -1;
     906            0 :         for (uint32_t i = 0; i < count; i++) tl_read_int32(r);
     907            0 :         return 0;
     908              :     }
     909            1 :     default:
     910            1 :         logger_log(LOG_WARN, "tl_skip_photo_size: unknown 0x%08x", crc);
     911            1 :         return -1;
     912              :     }
     913              : }
     914              : 
     915            1 : int tl_skip_photo_size_vector(TlReader *r) {
     916            1 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
     917            1 :     uint32_t vec_crc = tl_read_uint32(r);
     918            1 :     if (vec_crc != TL_vector) return -1;
     919            1 :     uint32_t count = tl_read_uint32(r);
     920            2 :     for (uint32_t i = 0; i < count; i++) {
     921            1 :         if (tl_skip_photo_size(r) != 0) return -1;
     922              :     }
     923            1 :     return 0;
     924              : }
     925              : 
     926              : /* ---- Photo skipper / extractor ----
     927              :  * photoEmpty#2331b22d id:long
     928              :  * photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long
     929              :  *                file_reference:bytes date:int
     930              :  *                sizes:Vector<PhotoSize>
     931              :  *                video_sizes:flags.1?Vector<VideoSize>     <- BAIL
     932              :  *                dc_id:int
     933              :  *
     934              :  * photo_full walks the full object and, when @p out is non-NULL, fills
     935              :  * access_hash, file_reference, dc_id and the largest PhotoSize.type.
     936              :  * Used by tl_skip_message_media_ex for MEDIA_PHOTO + file-download
     937              :  * support. tl_skip_photo is a thin wrapper that passes NULL.
     938              :  */
     939              : 
     940              : /* Walk a Vector<PhotoSize> and record the `type` of the largest entry
     941              :  * (by pixel dimensions for photoSize / photoSizeProgressive, by byte
     942              :  * count as a tie-breaker for cached/stripped forms). Leaves the cursor
     943              :  * past the vector. */
     944            3 : static int walk_photo_size_vector(TlReader *r,
     945              :                                     char *best_type, size_t best_type_cap) {
     946            3 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
     947            3 :     uint32_t vec_crc = tl_read_uint32(r);
     948            3 :     if (vec_crc != TL_vector) return -1;
     949            3 :     uint32_t count = tl_read_uint32(r);
     950              : 
     951            3 :     long best_score = -1;
     952            3 :     if (best_type && best_type_cap) best_type[0] = '\0';
     953              : 
     954            8 :     for (uint32_t i = 0; i < count; i++) {
     955            5 :         if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
     956            5 :         uint32_t crc = tl_read_uint32(r);
     957              : 
     958              :         /* Capture type:string into a small buffer we may keep as best. */
     959            5 :         char type_buf[8] = {0};
     960            5 :         size_t type_n = 0;
     961              :         {
     962            5 :             size_t before = r->pos;
     963            5 :             char *s = tl_read_string(r);
     964            5 :             if (!s) return -1;
     965            5 :             type_n = strlen(s);
     966            5 :             if (type_n >= sizeof(type_buf)) type_n = sizeof(type_buf) - 1;
     967            5 :             memcpy(type_buf, s, type_n);
     968            5 :             type_buf[type_n] = '\0';
     969            5 :             free(s);
     970              :             (void)before;
     971              :         }
     972              : 
     973            5 :         long score = 0;
     974            5 :         switch (crc) {
     975            0 :         case CRC_photoSizeEmpty:
     976            0 :             score = -1; /* empty — never best */
     977            0 :             break;
     978            3 :         case CRC_photoSize: {
     979            3 :             if (r->len - r->pos < 12) return -1;
     980            3 :             int32_t w = tl_read_int32(r);
     981            3 :             int32_t h = tl_read_int32(r);
     982            3 :             int32_t sz = tl_read_int32(r); (void)sz;
     983            3 :             score = (long)w * (long)h;
     984            3 :             break;
     985              :         }
     986            1 :         case CRC_photoCachedSize: {
     987            1 :             if (r->len - r->pos < 8) return -1;
     988            1 :             int32_t w = tl_read_int32(r);
     989            1 :             int32_t h = tl_read_int32(r);
     990            1 :             if (tl_skip_string(r) != 0) return -1; /* bytes */
     991            1 :             score = (long)w * (long)h;
     992            1 :             break;
     993              :         }
     994            0 :         case CRC_photoStrippedSize:
     995              :         case CRC_photoPathSize:
     996            0 :             if (tl_skip_string(r) != 0) return -1; /* bytes */
     997            0 :             score = 0;
     998            0 :             break;
     999            1 :         case CRC_photoSizeProgressive: {
    1000            1 :             if (r->len - r->pos < 8) return -1;
    1001            1 :             int32_t w = tl_read_int32(r);
    1002            1 :             int32_t h = tl_read_int32(r);
    1003            1 :             if (r->len - r->pos < 8) return -1;
    1004            1 :             uint32_t vc = tl_read_uint32(r);
    1005            1 :             if (vc != TL_vector) return -1;
    1006            1 :             uint32_t nvals = tl_read_uint32(r);
    1007            1 :             if (r->len - r->pos < nvals * 4ULL) return -1;
    1008            3 :             for (uint32_t j = 0; j < nvals; j++) tl_read_int32(r);
    1009            1 :             score = (long)w * (long)h;
    1010            1 :             break;
    1011              :         }
    1012            0 :         default:
    1013            0 :             logger_log(LOG_WARN, "walk_photo_size_vector: unknown 0x%08x", crc);
    1014            0 :             return -1;
    1015              :         }
    1016              : 
    1017            5 :         if (score > best_score && best_type && best_type_cap) {
    1018            2 :             best_score = score;
    1019            2 :             size_t n = type_n < best_type_cap - 1 ? type_n : best_type_cap - 1;
    1020            2 :             memcpy(best_type, type_buf, n);
    1021            2 :             best_type[n] = '\0';
    1022              :         }
    1023              :     }
    1024            3 :     return 0;
    1025              : }
    1026              : 
    1027            7 : static int photo_full(TlReader *r, MediaInfo *out) {
    1028            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1029            7 :     uint32_t crc = tl_read_uint32(r);
    1030            7 :     if (crc == CRC_photoEmpty) {
    1031            4 :         if (r->len - r->pos < 8) return -1;
    1032            4 :         int64_t id = tl_read_int64(r);
    1033            4 :         if (out) out->photo_id = id;
    1034            4 :         return 0;
    1035              :     }
    1036            3 :     if (crc != CRC_photo) {
    1037            0 :         logger_log(LOG_WARN, "tl_skip_photo: unknown 0x%08x", crc);
    1038            0 :         return -1;
    1039              :     }
    1040            3 :     if (r->len - r->pos < 4) return -1;
    1041            3 :     uint32_t flags = tl_read_uint32(r);
    1042            3 :     if (r->len - r->pos < 16) return -1;
    1043            3 :     int64_t id = tl_read_int64(r);
    1044            3 :     int64_t access = tl_read_int64(r);
    1045            3 :     if (out) { out->photo_id = id; out->access_hash = access; }
    1046              : 
    1047              :     /* file_reference is a TL bytes field. Capture it into MediaInfo
    1048              :      * (truncating if it exceeds MEDIA_FILE_REF_MAX) while advancing. */
    1049            3 :     size_t fr_len = 0;
    1050            3 :     uint8_t *fr = tl_read_bytes(r, &fr_len);
    1051            3 :     if (!fr && fr_len != 0) return -1;
    1052            3 :     if (out) {
    1053            2 :         size_t n = fr_len;
    1054            2 :         if (n > MEDIA_FILE_REF_MAX) n = MEDIA_FILE_REF_MAX;
    1055            2 :         if (fr) memcpy(out->file_reference, fr, n);
    1056            2 :         out->file_reference_len = n;
    1057              :     }
    1058            3 :     free(fr);
    1059              : 
    1060            3 :     if (r->len - r->pos < 4) return -1;
    1061            3 :     tl_read_int32(r); /* date */
    1062              : 
    1063            3 :     if (walk_photo_size_vector(r,
    1064              :                                 out ? out->thumb_type : NULL,
    1065              :                                 out ? sizeof(out->thumb_type) : 0) != 0)
    1066            0 :         return -1;
    1067              : 
    1068            3 :     if (flags & (1u << 1)) {
    1069            0 :         logger_log(LOG_WARN, "tl_skip_photo: video_sizes not implemented");
    1070            0 :         return -1;
    1071              :     }
    1072            3 :     if (r->len - r->pos < 4) return -1;
    1073            3 :     int32_t dc = tl_read_int32(r);
    1074            3 :     if (out) out->dc_id = dc;
    1075            3 :     return 0;
    1076              : }
    1077              : 
    1078            5 : int tl_skip_photo(TlReader *r) {
    1079            5 :     return photo_full(r, NULL);
    1080              : }
    1081              : 
    1082              : /* ---- Document skipper ----
    1083              :  * documentEmpty#36f8c871 id:long
    1084              :  * document — flag-heavy; bail out without implementing for now. The layer-
    1085              :  * specific layout changes frequently, and ordinary chat messages that
    1086              :  * reach us typically have Photo, not Document. Callers treating a
    1087              :  * Document as complex+stop-iter is acceptable for v1.
    1088              :  */
    1089              : /* DocumentAttribute CRCs we can skip without bailing. */
    1090              : #define CRC_documentAttributeImageSize   0x6c37c15cu
    1091              : #define CRC_documentAttributeAnimated    0x11b58939u
    1092              : #define CRC_documentAttributeHasStickers 0x9801d2f7u
    1093              : #define CRC_documentAttributeFilename    0x15590068u
    1094              : #define CRC_documentAttributeVideo       0x43c57c48u
    1095              : #define CRC_documentAttributeAudio       0x9852f9c6u
    1096              : #define CRC_documentAttributeSticker     0x6319d612u
    1097              : #define CRC_documentAttributeCustomEmoji 0xfd149899u
    1098              : 
    1099              : /* InputStickerSet variants (documentAttributeSticker / CustomEmoji). */
    1100              : #define CRC_inputStickerSetEmpty                    0xffb62b95u
    1101              : #define CRC_inputStickerSetID                       0x9de7a269u
    1102              : #define CRC_inputStickerSetShortName                0x861cc8a0u
    1103              : #define CRC_inputStickerSetAnimatedEmoji            0x028703c8u
    1104              : #define CRC_inputStickerSetDice                     0xe67f520eu
    1105              : #define CRC_inputStickerSetAnimatedEmojiAnimations  0x0cde3739u
    1106              : #define CRC_inputStickerSetPremiumGifts             0xc88b3b02u
    1107              : #define CRC_inputStickerSetEmojiGenericAnimations   0x04c4d4ceu
    1108              : #define CRC_inputStickerSetEmojiDefaultStatuses     0x29d0f5eeu
    1109              : #define CRC_inputStickerSetEmojiDefaultTopicIcons   0x44c1f8e9u
    1110              : #define CRC_inputStickerSetEmojiChannelDefaultStatuses 0x49748553u
    1111              : 
    1112              : /* MaskCoords (documentAttributeSticker.mask_coords). */
    1113              : #define CRC_maskCoords                   0xaed6dbb2u
    1114              : 
    1115              : /* VideoSize variants (document.video_thumbs, flags.1). */
    1116              : #define CRC_videoSize                    0xde33b094u
    1117              : #define CRC_videoSizeEmojiMarkup         0xf85c413cu
    1118              : #define CRC_videoSizeStickerMarkup       0x0da082feu
    1119              : 
    1120              : /* Skip an InputStickerSet variant. Used by documentAttributeSticker /
    1121              :  * CustomEmoji. Handles every declared variant; on unknown ones we bail. */
    1122            1 : static int skip_input_sticker_set(TlReader *r) {
    1123            1 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1124            1 :     uint32_t crc = tl_read_uint32(r);
    1125            1 :     switch (crc) {
    1126            1 :     case CRC_inputStickerSetEmpty:
    1127              :     case CRC_inputStickerSetAnimatedEmoji:
    1128              :     case CRC_inputStickerSetAnimatedEmojiAnimations:
    1129              :     case CRC_inputStickerSetPremiumGifts:
    1130              :     case CRC_inputStickerSetEmojiGenericAnimations:
    1131              :     case CRC_inputStickerSetEmojiDefaultStatuses:
    1132              :     case CRC_inputStickerSetEmojiDefaultTopicIcons:
    1133              :     case CRC_inputStickerSetEmojiChannelDefaultStatuses:
    1134            1 :         return 0;
    1135            0 :     case CRC_inputStickerSetID:
    1136            0 :         if (r->len - r->pos < 16) return -1;
    1137            0 :         tl_read_int64(r);                            /* id */
    1138            0 :         tl_read_int64(r);                            /* access_hash */
    1139            0 :         return 0;
    1140            0 :     case CRC_inputStickerSetShortName:
    1141            0 :         return tl_skip_string(r);
    1142            0 :     case CRC_inputStickerSetDice:
    1143            0 :         return tl_skip_string(r);                    /* emoticon */
    1144            0 :     default:
    1145            0 :         logger_log(LOG_WARN,
    1146              :                    "skip_input_sticker_set: unknown 0x%08x", crc);
    1147            0 :         return -1;
    1148              :     }
    1149              : }
    1150              : 
    1151              : /* Skip a MaskCoords#aed6dbb2: n:int + 3 doubles. */
    1152            0 : static int skip_mask_coords(TlReader *r) {
    1153            0 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1154            0 :     uint32_t crc = tl_read_uint32(r);
    1155            0 :     if (crc != CRC_maskCoords) {
    1156            0 :         logger_log(LOG_WARN, "skip_mask_coords: unknown 0x%08x", crc);
    1157            0 :         return -1;
    1158              :     }
    1159            0 :     if (r->len - r->pos < 4 + 3 * 8) return -1;
    1160            0 :     tl_read_int32(r);                                /* n */
    1161            0 :     tl_read_double(r); tl_read_double(r); tl_read_double(r); /* x, y, zoom */
    1162            0 :     return 0;
    1163              : }
    1164              : 
    1165              : /* Skip a single VideoSize variant (document.video_thumbs element). */
    1166            0 : static int skip_video_size(TlReader *r) {
    1167            0 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1168            0 :     uint32_t crc = tl_read_uint32(r);
    1169            0 :     switch (crc) {
    1170            0 :     case CRC_videoSize: {
    1171              :         /* flags:# type:string w:int h:int size:int video_start_ts:flags.0?double */
    1172            0 :         if (r->len - r->pos < 4) return -1;
    1173            0 :         uint32_t flags = tl_read_uint32(r);
    1174            0 :         if (tl_skip_string(r) != 0) return -1;
    1175            0 :         if (r->len - r->pos < 12) return -1;
    1176            0 :         tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
    1177            0 :         if (flags & (1u << 0)) {
    1178            0 :             if (r->len - r->pos < 8) return -1;
    1179            0 :             tl_read_double(r);
    1180              :         }
    1181            0 :         return 0;
    1182              :     }
    1183            0 :     case CRC_videoSizeEmojiMarkup: {
    1184            0 :         if (r->len - r->pos < 8) return -1;
    1185            0 :         tl_read_int64(r);                            /* emoji_id */
    1186              :         /* background_colors:Vector<int> */
    1187            0 :         if (r->len - r->pos < 8) return -1;
    1188            0 :         uint32_t vec = tl_read_uint32(r);
    1189            0 :         if (vec != TL_vector) return -1;
    1190            0 :         uint32_t n = tl_read_uint32(r);
    1191            0 :         if (r->len - r->pos < (size_t)n * 4) return -1;
    1192            0 :         for (uint32_t i = 0; i < n; i++) tl_read_int32(r);
    1193            0 :         return 0;
    1194              :     }
    1195            0 :     case CRC_videoSizeStickerMarkup: {
    1196            0 :         if (skip_input_sticker_set(r) != 0) return -1;
    1197            0 :         if (r->len - r->pos < 8) return -1;
    1198            0 :         tl_read_int64(r);                            /* sticker_id */
    1199            0 :         if (r->len - r->pos < 8) return -1;
    1200            0 :         uint32_t vec = tl_read_uint32(r);
    1201            0 :         if (vec != TL_vector) return -1;
    1202            0 :         uint32_t n = tl_read_uint32(r);
    1203            0 :         if (r->len - r->pos < (size_t)n * 4) return -1;
    1204            0 :         for (uint32_t i = 0; i < n; i++) tl_read_int32(r);
    1205            0 :         return 0;
    1206              :     }
    1207            0 :     default:
    1208            0 :         logger_log(LOG_WARN, "skip_video_size: unknown 0x%08x", crc);
    1209            0 :         return -1;
    1210              :     }
    1211              : }
    1212              : 
    1213              : /* Skip a Vector<VideoSize>. */
    1214            0 : static int skip_video_size_vector(TlReader *r) {
    1215            0 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
    1216            0 :     uint32_t vec = tl_read_uint32(r);
    1217            0 :     if (vec != TL_vector) return -1;
    1218            0 :     uint32_t n = tl_read_uint32(r);
    1219            0 :     for (uint32_t i = 0; i < n; i++) {
    1220            0 :         if (skip_video_size(r) != 0) return -1;
    1221              :     }
    1222            0 :     return 0;
    1223              : }
    1224              : 
    1225              : /* Skip a single DocumentAttribute, optionally extracting the filename. */
    1226           14 : static int skip_document_attribute(TlReader *r,
    1227              :                                     char *filename_out, size_t fn_cap) {
    1228           14 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1229           14 :     uint32_t crc = tl_read_uint32(r);
    1230           14 :     switch (crc) {
    1231            1 :     case CRC_documentAttributeImageSize:
    1232            1 :         if (r->len - r->pos < 8) return -1;
    1233            1 :         tl_read_int32(r); tl_read_int32(r);              /* w, h */
    1234            1 :         return 0;
    1235            4 :     case CRC_documentAttributeAnimated:
    1236              :     case CRC_documentAttributeHasStickers:
    1237            4 :         return 0;
    1238            3 :     case CRC_documentAttributeFilename: {
    1239            3 :         size_t before = r->pos;
    1240            3 :         char *s = tl_read_string(r);
    1241            3 :         if (!s) { r->pos = before; return -1; }
    1242            3 :         if (filename_out && fn_cap) {
    1243            2 :             size_t n = strlen(s);
    1244            2 :             if (n >= fn_cap) n = fn_cap - 1;
    1245            2 :             memcpy(filename_out, s, n);
    1246            2 :             filename_out[n] = '\0';
    1247              :         }
    1248            3 :         free(s);
    1249            3 :         return 0;
    1250              :     }
    1251            2 :     case CRC_documentAttributeVideo: {
    1252            2 :         if (r->len - r->pos < 4) return -1;
    1253            2 :         uint32_t flags = tl_read_uint32(r);
    1254            2 :         if (r->len - r->pos < 8 + 4 + 4) return -1;
    1255            2 :         tl_read_double(r);                               /* duration */
    1256            2 :         tl_read_int32(r); tl_read_int32(r);              /* w, h */
    1257            2 :         if (flags & (1u << 2)) {
    1258            0 :             if (r->len - r->pos < 4) return -1;
    1259            0 :             tl_read_int32(r);                            /* preload_prefix_size */
    1260              :         }
    1261            2 :         if (flags & (1u << 4)) {
    1262            0 :             if (r->len - r->pos < 8) return -1;
    1263            0 :             tl_read_double(r);                           /* video_start_ts */
    1264              :         }
    1265            2 :         if (flags & (1u << 5))
    1266            0 :             if (tl_skip_string(r) != 0) return -1;       /* video_codec */
    1267            2 :         return 0;
    1268              :     }
    1269            3 :     case CRC_documentAttributeAudio: {
    1270            3 :         if (r->len - r->pos < 8) return -1;
    1271            3 :         uint32_t flags = tl_read_uint32(r);
    1272            3 :         tl_read_int32(r);                                /* duration */
    1273            3 :         if (flags & (1u << 0))
    1274            0 :             if (tl_skip_string(r) != 0) return -1;       /* title */
    1275            3 :         if (flags & (1u << 1))
    1276            0 :             if (tl_skip_string(r) != 0) return -1;       /* performer */
    1277            3 :         if (flags & (1u << 2))
    1278            0 :             if (tl_skip_string(r) != 0) return -1;       /* waveform:bytes */
    1279            3 :         return 0;
    1280              :     }
    1281            1 :     case CRC_documentAttributeSticker: {
    1282              :         /* flags:# mask:flags.1?true alt:string stickerset:InputStickerSet
    1283              :          *        mask_coords:flags.0?MaskCoords */
    1284            1 :         if (r->len - r->pos < 4) return -1;
    1285            1 :         uint32_t flags = tl_read_uint32(r);
    1286            1 :         if (tl_skip_string(r) != 0) return -1;           /* alt */
    1287            1 :         if (skip_input_sticker_set(r) != 0) return -1;
    1288            1 :         if (flags & (1u << 0)) {
    1289            0 :             if (skip_mask_coords(r) != 0) return -1;
    1290              :         }
    1291            1 :         return 0;
    1292              :     }
    1293            0 :     case CRC_documentAttributeCustomEmoji: {
    1294              :         /* flags:# free:flags.0?true text_color:flags.1?true alt:string
    1295              :          *        stickerset:InputStickerSet */
    1296            0 :         if (r->len - r->pos < 4) return -1;
    1297            0 :         (void)tl_read_uint32(r);                         /* flags */
    1298            0 :         if (tl_skip_string(r) != 0) return -1;           /* alt */
    1299            0 :         return skip_input_sticker_set(r);
    1300              :     }
    1301            0 :     default:
    1302            0 :         logger_log(LOG_WARN, "skip_document_attribute: unknown 0x%08x", crc);
    1303            0 :         return -1;
    1304              :     }
    1305              : }
    1306              : 
    1307              : /* Walk a Document. When @p out is non-NULL, fills id + access_hash +
    1308              :  * file_reference + dc_id + size + mime_type + filename. Returns -1 on
    1309              :  * thumbs / video_thumbs (flags.0 / flags.1) or an unknown attribute. */
    1310           11 : static int document_inner(TlReader *r, MediaInfo *out) {
    1311           11 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1312           11 :     uint32_t crc = tl_read_uint32(r);
    1313           11 :     if (crc == CRC_documentEmpty) {
    1314            2 :         if (r->len - r->pos < 8) return -1;
    1315            2 :         int64_t id = tl_read_int64(r);
    1316            2 :         if (out) out->document_id = id;
    1317            2 :         return 0;
    1318              :     }
    1319            9 :     if (crc != CRC_document) {
    1320            0 :         logger_log(LOG_WARN, "tl_skip_document: unknown 0x%08x", crc);
    1321            0 :         return -1;
    1322              :     }
    1323            9 :     if (r->len - r->pos < 4) return -1;
    1324            9 :     uint32_t flags = tl_read_uint32(r);
    1325            9 :     if (r->len - r->pos < 16) return -1;
    1326            9 :     int64_t id     = tl_read_int64(r);
    1327            9 :     int64_t access = tl_read_int64(r);
    1328            9 :     if (out) { out->document_id = id; out->access_hash = access; }
    1329              : 
    1330            9 :     size_t fr_len = 0;
    1331            9 :     uint8_t *fr = tl_read_bytes(r, &fr_len);
    1332            9 :     if (!fr && fr_len != 0) return -1;
    1333            9 :     if (out) {
    1334            8 :         size_t n = fr_len;
    1335            8 :         if (n > MEDIA_FILE_REF_MAX) n = MEDIA_FILE_REF_MAX;
    1336            8 :         if (fr) memcpy(out->file_reference, fr, n);
    1337            8 :         out->file_reference_len = n;
    1338              :     }
    1339            9 :     free(fr);
    1340              : 
    1341            9 :     if (r->len - r->pos < 4) return -1;
    1342            9 :     tl_read_int32(r);                                    /* date */
    1343              : 
    1344            9 :     size_t mime_before = r->pos;
    1345            9 :     char *mime = tl_read_string(r);
    1346            9 :     if (!mime) { r->pos = mime_before; return -1; }
    1347            9 :     if (out) {
    1348            8 :         size_t n = strlen(mime);
    1349            8 :         if (n >= sizeof(out->document_mime)) n = sizeof(out->document_mime) - 1;
    1350            8 :         memcpy(out->document_mime, mime, n);
    1351            8 :         out->document_mime[n] = '\0';
    1352              :     }
    1353            9 :     free(mime);
    1354              : 
    1355            9 :     if (r->len - r->pos < 8) return -1;
    1356            9 :     int64_t size = tl_read_int64(r);
    1357            9 :     if (out) out->document_size = size;
    1358              : 
    1359            9 :     if (flags & (1u << 0)) {
    1360            0 :         if (tl_skip_photo_size_vector(r) != 0) return -1;
    1361              :     }
    1362            9 :     if (flags & (1u << 1)) {
    1363            0 :         if (skip_video_size_vector(r) != 0) return -1;
    1364              :     }
    1365              : 
    1366            9 :     if (r->len - r->pos < 4) return -1;
    1367            9 :     int32_t dc = tl_read_int32(r);
    1368            9 :     if (out) out->dc_id = dc;
    1369              : 
    1370              :     /* attributes:Vector<DocumentAttribute> */
    1371            9 :     if (r->len - r->pos < 8) return -1;
    1372            9 :     uint32_t vec_crc = tl_read_uint32(r);
    1373            9 :     if (vec_crc != TL_vector) return -1;
    1374            9 :     uint32_t n_attrs = tl_read_uint32(r);
    1375           22 :     for (uint32_t i = 0; i < n_attrs; i++) {
    1376           13 :         if (skip_document_attribute(r,
    1377              :                                      out ? out->document_filename : NULL,
    1378              :                                      out ? sizeof(out->document_filename) : 0) != 0)
    1379            0 :             return -1;
    1380              :     }
    1381            9 :     return 0;
    1382              : }
    1383              : 
    1384            3 : int tl_skip_document(TlReader *r) {
    1385            3 :     return document_inner(r, NULL);
    1386              : }
    1387              : 
    1388              : /* Skip GeoPoint: empty → CRC-only; geoPoint → CRC + long lat + long long +
    1389              :  * long access_hash + flags:# + optional accuracy_radius:flags.0?int.
    1390              :  * Actually:
    1391              :  *   geoPointEmpty#1117dd5f
    1392              :  *   geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long
    1393              :  *                    accuracy_radius:flags.0?int
    1394              :  */
    1395            7 : static int skip_geo_point(TlReader *r) {
    1396            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1397            7 :     uint32_t crc = tl_read_uint32(r);
    1398            7 :     if (crc == CRC_geoPointEmpty) return 0;
    1399            2 :     if (crc != CRC_geoPoint) {
    1400            0 :         logger_log(LOG_WARN, "skip_geo_point: unknown 0x%08x", crc);
    1401            0 :         return -1;
    1402              :     }
    1403            2 :     if (r->len - r->pos < 28) return -1;
    1404            2 :     uint32_t flags = tl_read_uint32(r);
    1405            2 :     tl_read_double(r); tl_read_double(r); tl_read_int64(r);
    1406            2 :     if (flags & 1u) {
    1407            0 :         if (r->len - r->pos < 4) return -1;
    1408            0 :         tl_read_int32(r);
    1409              :     }
    1410            2 :     return 0;
    1411              : }
    1412              : 
    1413              : /* Forward decl: skip_story_item is defined later in the file (next to
    1414              :  * the MessageMedia switch) and is re-used by webPageAttributeStory. */
    1415              : static int skip_story_item(TlReader *r);
    1416              : 
    1417              : /* Forward decls: PageBlock recursion (Cover → PageBlock, Details →
    1418              :  * Vector<PageBlock>, EmbedPost → Vector<PageBlock>, List item blocks,
    1419              :  * etc.) and reliance on skip_geo_point for pageBlockMap. */
    1420              : static int skip_page_block(TlReader *r);
    1421              : static int skip_geo_point(TlReader *r);
    1422              : 
    1423              : /* RichText tree (used by cached_page's PageBlock variants). Recursive:
    1424              :  * wrap-type variants contain a nested RichText. textConcat is a vector. */
    1425           52 : static int skip_rich_text(TlReader *r) {
    1426           52 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1427           52 :     uint32_t crc = tl_read_uint32(r);
    1428           52 :     switch (crc) {
    1429           36 :     case CRC_textEmpty:
    1430           36 :         return 0;
    1431            3 :     case CRC_textPlain:
    1432            3 :         return tl_skip_string(r);
    1433            6 :     case CRC_textBold:
    1434              :     case CRC_textItalic:
    1435              :     case CRC_textUnderline:
    1436              :     case CRC_textStrike:
    1437              :     case CRC_textFixed:
    1438              :     case CRC_textSubscript:
    1439              :     case CRC_textSuperscript:
    1440              :     case CRC_textMarked:
    1441            6 :         return skip_rich_text(r);
    1442            1 :     case CRC_textUrl: {
    1443            1 :         if (skip_rich_text(r) != 0) return -1;
    1444            1 :         if (tl_skip_string(r) != 0) return -1;
    1445            1 :         if (r->len - r->pos < 8) return -1;
    1446            1 :         tl_read_int64(r);                            /* webpage_id */
    1447            1 :         return 0;
    1448              :     }
    1449            2 :     case CRC_textEmail:
    1450              :     case CRC_textPhone: {
    1451            2 :         if (skip_rich_text(r) != 0) return -1;
    1452            2 :         return tl_skip_string(r);
    1453              :     }
    1454            2 :     case CRC_textConcat: {
    1455            2 :         if (r->len - r->pos < 8) return -1;
    1456            2 :         uint32_t vec = tl_read_uint32(r);
    1457            2 :         if (vec != TL_vector) return -1;
    1458            2 :         uint32_t n = tl_read_uint32(r);
    1459           12 :         for (uint32_t i = 0; i < n; i++) {
    1460           10 :             if (skip_rich_text(r) != 0) return -1;
    1461              :         }
    1462            2 :         return 0;
    1463              :     }
    1464            1 :     case CRC_textImage: {
    1465            1 :         if (r->len - r->pos < 16) return -1;
    1466            1 :         tl_read_int64(r);                            /* document_id */
    1467            1 :         tl_read_int32(r);                            /* w */
    1468            1 :         tl_read_int32(r);                            /* h */
    1469            1 :         return 0;
    1470              :     }
    1471            1 :     case CRC_textAnchor: {
    1472            1 :         if (skip_rich_text(r) != 0) return -1;
    1473            1 :         return tl_skip_string(r);
    1474              :     }
    1475            0 :     default:
    1476            0 :         logger_log(LOG_WARN, "skip_rich_text: unknown 0x%08x", crc);
    1477            0 :         return -1;
    1478              :     }
    1479              : }
    1480              : 
    1481              : /* Skip a pageCaption#6f747657 text:RichText credit:RichText. */
    1482            8 : static int skip_page_caption(TlReader *r) {
    1483            8 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1484            8 :     uint32_t crc = tl_read_uint32(r);
    1485            8 :     if (crc != CRC_pageCaption) {
    1486            0 :         logger_log(LOG_WARN, "skip_page_caption: unknown 0x%08x", crc);
    1487            0 :         return -1;
    1488              :     }
    1489            8 :     if (skip_rich_text(r) != 0) return -1;
    1490            8 :     return skip_rich_text(r);
    1491              : }
    1492              : 
    1493              : /* Skip a Vector<PageBlock> — recursive into skip_page_block. */
    1494            6 : static int skip_page_block_vector(TlReader *r) {
    1495            6 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
    1496            6 :     uint32_t vec = tl_read_uint32(r);
    1497            6 :     if (vec != TL_vector) return -1;
    1498            6 :     uint32_t n = tl_read_uint32(r);
    1499            9 :     for (uint32_t i = 0; i < n; i++) {
    1500            3 :         if (skip_page_block(r) != 0) return -1;
    1501              :     }
    1502            6 :     return 0;
    1503              : }
    1504              : 
    1505              : /* Skip a single PageListItem. */
    1506            2 : static int skip_page_list_item(TlReader *r) {
    1507            2 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1508            2 :     uint32_t crc = tl_read_uint32(r);
    1509            2 :     switch (crc) {
    1510            1 :     case CRC_pageListItemText:
    1511            1 :         return skip_rich_text(r);
    1512            1 :     case CRC_pageListItemBlocks:
    1513            1 :         return skip_page_block_vector(r);
    1514            0 :     default:
    1515            0 :         logger_log(LOG_WARN, "skip_page_list_item: unknown 0x%08x", crc);
    1516            0 :         return -1;
    1517              :     }
    1518              : }
    1519              : 
    1520            2 : static int skip_page_list_ordered_item(TlReader *r) {
    1521            2 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1522            2 :     uint32_t crc = tl_read_uint32(r);
    1523            2 :     switch (crc) {
    1524            1 :     case CRC_pageListOrderedItemText:
    1525            1 :         if (tl_skip_string(r) != 0) return -1;       /* num */
    1526            1 :         return skip_rich_text(r);
    1527            1 :     case CRC_pageListOrderedItemBlocks:
    1528            1 :         if (tl_skip_string(r) != 0) return -1;       /* num */
    1529            1 :         return skip_page_block_vector(r);
    1530            0 :     default:
    1531            0 :         logger_log(LOG_WARN,
    1532              :                    "skip_page_list_ordered_item: unknown 0x%08x", crc);
    1533            0 :         return -1;
    1534              :     }
    1535              : }
    1536              : 
    1537              : /* pageTableCell#34566b6a flags:# header/align/valign:flags
    1538              :  *   text:flags.7?RichText colspan:flags.1?int rowspan:flags.2?int */
    1539            1 : static int skip_page_table_cell(TlReader *r) {
    1540            1 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1541            1 :     uint32_t crc = tl_read_uint32(r);
    1542            1 :     if (crc != CRC_pageTableCell) {
    1543            0 :         logger_log(LOG_WARN, "skip_page_table_cell: unknown 0x%08x", crc);
    1544            0 :         return -1;
    1545              :     }
    1546            1 :     if (r->len - r->pos < 4) return -1;
    1547            1 :     uint32_t flags = tl_read_uint32(r);
    1548            1 :     if (flags & (1u << 7)) {
    1549            1 :         if (skip_rich_text(r) != 0) return -1;
    1550              :     }
    1551            1 :     if (flags & (1u << 1)) {
    1552            1 :         if (r->len - r->pos < 4) return -1;
    1553            1 :         tl_read_int32(r);                            /* colspan */
    1554              :     }
    1555            1 :     if (flags & (1u << 2)) {
    1556            1 :         if (r->len - r->pos < 4) return -1;
    1557            1 :         tl_read_int32(r);                            /* rowspan */
    1558              :     }
    1559            1 :     return 0;
    1560              : }
    1561              : 
    1562              : /* pageTableRow#e0c0c5e5 cells:Vector<PageTableCell>. */
    1563            1 : static int skip_page_table_row(TlReader *r) {
    1564            1 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1565            1 :     uint32_t crc = tl_read_uint32(r);
    1566            1 :     if (crc != CRC_pageTableRow) {
    1567            0 :         logger_log(LOG_WARN, "skip_page_table_row: unknown 0x%08x", crc);
    1568            0 :         return -1;
    1569              :     }
    1570            1 :     if (r->len - r->pos < 8) return -1;
    1571            1 :     uint32_t vec = tl_read_uint32(r);
    1572            1 :     if (vec != TL_vector) return -1;
    1573            1 :     uint32_t n = tl_read_uint32(r);
    1574            2 :     for (uint32_t i = 0; i < n; i++) {
    1575            1 :         if (skip_page_table_cell(r) != 0) return -1;
    1576              :     }
    1577            1 :     return 0;
    1578              : }
    1579              : 
    1580              : /* pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long
    1581              :  *   title:flags.0?string description:flags.1?string
    1582              :  *   photo_id:flags.2?long author:flags.3?string
    1583              :  *   published_date:flags.4?int */
    1584            1 : static int skip_page_related_article(TlReader *r) {
    1585            1 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1586            1 :     uint32_t crc = tl_read_uint32(r);
    1587            1 :     if (crc != CRC_pageRelatedArticle) {
    1588            0 :         logger_log(LOG_WARN,
    1589              :                    "skip_page_related_article: unknown 0x%08x", crc);
    1590            0 :         return -1;
    1591              :     }
    1592            1 :     if (r->len - r->pos < 4) return -1;
    1593            1 :     uint32_t flags = tl_read_uint32(r);
    1594            1 :     if (tl_skip_string(r) != 0) return -1;           /* url */
    1595            1 :     if (r->len - r->pos < 8) return -1;
    1596            1 :     tl_read_int64(r);                                /* webpage_id */
    1597            1 :     if (flags & (1u << 0)) if (tl_skip_string(r) != 0) return -1;
    1598            1 :     if (flags & (1u << 1)) if (tl_skip_string(r) != 0) return -1;
    1599            1 :     if (flags & (1u << 2)) {
    1600            1 :         if (r->len - r->pos < 8) return -1;
    1601            1 :         tl_read_int64(r);                            /* photo_id */
    1602              :     }
    1603            1 :     if (flags & (1u << 3)) if (tl_skip_string(r) != 0) return -1;
    1604            1 :     if (flags & (1u << 4)) {
    1605            1 :         if (r->len - r->pos < 4) return -1;
    1606            1 :         tl_read_int32(r);                            /* published_date */
    1607              :     }
    1608            1 :     return 0;
    1609              : }
    1610              : 
    1611              : /* Skip a single PageBlock — full coverage of the PageBlock tree. */
    1612           32 : static int skip_page_block(TlReader *r) {
    1613           32 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1614           32 :     uint32_t crc = tl_read_uint32(r);
    1615           32 :     switch (crc) {
    1616            6 :     case CRC_pageBlockUnsupported:
    1617              :     case CRC_pageBlockDivider:
    1618            6 :         return 0;
    1619            4 :     case CRC_pageBlockTitle:
    1620              :     case CRC_pageBlockSubtitle:
    1621              :     case CRC_pageBlockHeader:
    1622              :     case CRC_pageBlockSubheader:
    1623              :     case CRC_pageBlockKicker:
    1624              :     case CRC_pageBlockParagraph:
    1625              :     case CRC_pageBlockFooter:
    1626            4 :         return skip_rich_text(r);
    1627            1 :     case CRC_pageBlockAnchor:
    1628            1 :         return tl_skip_string(r);
    1629            1 :     case CRC_pageBlockPreformatted: {
    1630            1 :         if (skip_rich_text(r) != 0) return -1;
    1631            1 :         return tl_skip_string(r);                    /* language */
    1632              :     }
    1633            1 :     case CRC_pageBlockAuthorDate: {
    1634            1 :         if (skip_rich_text(r) != 0) return -1;       /* author */
    1635            1 :         if (r->len - r->pos < 4) return -1;
    1636            1 :         tl_read_int32(r);                            /* published_date */
    1637            1 :         return 0;
    1638              :     }
    1639            2 :     case CRC_pageBlockBlockquote:
    1640              :     case CRC_pageBlockPullquote: {
    1641            2 :         if (skip_rich_text(r) != 0) return -1;       /* text */
    1642            2 :         return skip_rich_text(r);                    /* caption */
    1643              :     }
    1644            1 :     case CRC_pageBlockPhoto: {
    1645            1 :         if (r->len - r->pos < 4) return -1;
    1646            1 :         uint32_t flags = tl_read_uint32(r);
    1647            1 :         if (r->len - r->pos < 8) return -1;
    1648            1 :         tl_read_int64(r);                            /* photo_id */
    1649            1 :         if (skip_page_caption(r) != 0) return -1;
    1650            1 :         if (flags & (1u << 0)) {
    1651            0 :             if (tl_skip_string(r) != 0) return -1;   /* url */
    1652            0 :             if (r->len - r->pos < 8) return -1;
    1653            0 :             tl_read_int64(r);                        /* webpage_id */
    1654              :         }
    1655            1 :         return 0;
    1656              :     }
    1657            1 :     case CRC_pageBlockVideo: {
    1658            1 :         if (r->len - r->pos < 12) return -1;
    1659            1 :         tl_read_uint32(r);                           /* flags (autoplay/loop only) */
    1660            1 :         tl_read_int64(r);                            /* video_id */
    1661            1 :         return skip_page_caption(r);
    1662              :     }
    1663            1 :     case CRC_pageBlockAudio: {
    1664            1 :         if (r->len - r->pos < 8) return -1;
    1665            1 :         tl_read_int64(r);                            /* audio_id */
    1666            1 :         return skip_page_caption(r);
    1667              :     }
    1668            1 :     case CRC_pageBlockCover:
    1669            1 :         return skip_page_block(r);
    1670            1 :     case CRC_pageBlockChannel:
    1671            1 :         return tl_skip_chat(r);
    1672            1 :     case CRC_pageBlockMap: {
    1673            1 :         if (skip_geo_point(r) != 0) return -1;
    1674            1 :         if (r->len - r->pos < 12) return -1;
    1675            1 :         tl_read_int32(r);                            /* zoom */
    1676            1 :         tl_read_int32(r); tl_read_int32(r);          /* w, h */
    1677            1 :         return skip_page_caption(r);
    1678              :     }
    1679            2 :     case CRC_pageBlockList: {
    1680            2 :         if (r->len - r->pos < 8) return -1;
    1681            2 :         uint32_t vec = tl_read_uint32(r);
    1682            2 :         if (vec != TL_vector) return -1;
    1683            2 :         uint32_t n = tl_read_uint32(r);
    1684            4 :         for (uint32_t i = 0; i < n; i++) {
    1685            2 :             if (skip_page_list_item(r) != 0) return -1;
    1686              :         }
    1687            2 :         return 0;
    1688              :     }
    1689            2 :     case CRC_pageBlockOrderedList: {
    1690            2 :         if (r->len - r->pos < 8) return -1;
    1691            2 :         uint32_t vec = tl_read_uint32(r);
    1692            2 :         if (vec != TL_vector) return -1;
    1693            2 :         uint32_t n = tl_read_uint32(r);
    1694            4 :         for (uint32_t i = 0; i < n; i++) {
    1695            2 :             if (skip_page_list_ordered_item(r) != 0) return -1;
    1696              :         }
    1697            2 :         return 0;
    1698              :     }
    1699            2 :     case CRC_pageBlockCollage:
    1700              :     case CRC_pageBlockSlideshow: {
    1701            2 :         if (skip_page_block_vector(r) != 0) return -1;
    1702            2 :         return skip_page_caption(r);
    1703              :     }
    1704            1 :     case CRC_pageBlockDetails: {
    1705            1 :         if (r->len - r->pos < 4) return -1;
    1706            1 :         tl_read_uint32(r);                           /* flags (open only) */
    1707            1 :         if (skip_page_block_vector(r) != 0) return -1;
    1708            1 :         return skip_rich_text(r);                    /* title */
    1709              :     }
    1710            1 :     case CRC_pageBlockRelatedArticles: {
    1711            1 :         if (skip_rich_text(r) != 0) return -1;       /* title */
    1712            1 :         if (r->len - r->pos < 8) return -1;
    1713            1 :         uint32_t vec = tl_read_uint32(r);
    1714            1 :         if (vec != TL_vector) return -1;
    1715            1 :         uint32_t n = tl_read_uint32(r);
    1716            2 :         for (uint32_t i = 0; i < n; i++) {
    1717            1 :             if (skip_page_related_article(r) != 0) return -1;
    1718              :         }
    1719            1 :         return 0;
    1720              :     }
    1721            1 :     case CRC_pageBlockTable: {
    1722            1 :         if (r->len - r->pos < 4) return -1;
    1723            1 :         tl_read_uint32(r);                           /* flags (bordered/striped) */
    1724            1 :         if (skip_rich_text(r) != 0) return -1;       /* title */
    1725            1 :         if (r->len - r->pos < 8) return -1;
    1726            1 :         uint32_t vec = tl_read_uint32(r);
    1727            1 :         if (vec != TL_vector) return -1;
    1728            1 :         uint32_t n = tl_read_uint32(r);
    1729            2 :         for (uint32_t i = 0; i < n; i++) {
    1730            1 :             if (skip_page_table_row(r) != 0) return -1;
    1731              :         }
    1732            1 :         return 0;
    1733              :     }
    1734            1 :     case CRC_pageBlockEmbed: {
    1735            1 :         if (r->len - r->pos < 4) return -1;
    1736            1 :         uint32_t flags = tl_read_uint32(r);
    1737            1 :         if (flags & (1u << 1)) if (tl_skip_string(r) != 0) return -1;
    1738            1 :         if (flags & (1u << 2)) if (tl_skip_string(r) != 0) return -1;
    1739            1 :         if (flags & (1u << 4)) {
    1740            0 :             if (r->len - r->pos < 8) return -1;
    1741            0 :             tl_read_int64(r);                        /* poster_photo_id */
    1742              :         }
    1743            1 :         if (flags & (1u << 5)) {
    1744            0 :             if (r->len - r->pos < 8) return -1;
    1745            0 :             tl_read_int32(r); tl_read_int32(r);      /* w, h */
    1746              :         }
    1747            1 :         return skip_page_caption(r);
    1748              :     }
    1749            1 :     case CRC_pageBlockEmbedPost: {
    1750            1 :         if (tl_skip_string(r) != 0) return -1;       /* url */
    1751            1 :         if (r->len - r->pos < 16) return -1;
    1752            1 :         tl_read_int64(r);                            /* webpage_id */
    1753            1 :         tl_read_int64(r);                            /* author_photo_id */
    1754            1 :         if (tl_skip_string(r) != 0) return -1;       /* author */
    1755            1 :         if (r->len - r->pos < 4) return -1;
    1756            1 :         tl_read_int32(r);                            /* date */
    1757            1 :         if (skip_page_block_vector(r) != 0) return -1;
    1758            1 :         return skip_page_caption(r);
    1759              :     }
    1760            0 :     default:
    1761            0 :         logger_log(LOG_WARN, "skip_page_block: unsupported 0x%08x", crc);
    1762            0 :         return -1;
    1763              :     }
    1764              : }
    1765              : 
    1766              : /* Skip a Page object (webPage.cached_page). Best-effort: simple
    1767              :  * Instant View article bodies iterate; anything that contains a
    1768              :  * complex PageBlock variant bails so the caller stops walking. */
    1769            5 : static int skip_page(TlReader *r) {
    1770            5 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1771            5 :     uint32_t crc = tl_read_uint32(r);
    1772            5 :     if (crc != CRC_page) {
    1773            0 :         logger_log(LOG_WARN, "skip_page: unknown Page variant 0x%08x", crc);
    1774            0 :         return -1;
    1775              :     }
    1776            5 :     if (r->len - r->pos < 4) return -1;
    1777            5 :     uint32_t flags = tl_read_uint32(r);
    1778            5 :     if (tl_skip_string(r) != 0) return -1;           /* url */
    1779              : 
    1780              :     /* blocks:Vector<PageBlock> */
    1781            5 :     if (r->len - r->pos < 8) return -1;
    1782            5 :     uint32_t vec = tl_read_uint32(r);
    1783            5 :     if (vec != TL_vector) return -1;
    1784            5 :     uint32_t n = tl_read_uint32(r);
    1785           33 :     for (uint32_t i = 0; i < n; i++) {
    1786           28 :         if (skip_page_block(r) != 0) return -1;
    1787              :     }
    1788              : 
    1789              :     /* photos:Vector<Photo> */
    1790            5 :     if (r->len - r->pos < 8) return -1;
    1791            5 :     vec = tl_read_uint32(r);
    1792            5 :     if (vec != TL_vector) return -1;
    1793            5 :     n = tl_read_uint32(r);
    1794            5 :     for (uint32_t i = 0; i < n; i++) {
    1795            0 :         if (tl_skip_photo(r) != 0) return -1;
    1796              :     }
    1797              : 
    1798              :     /* documents:Vector<Document> */
    1799            5 :     if (r->len - r->pos < 8) return -1;
    1800            5 :     vec = tl_read_uint32(r);
    1801            5 :     if (vec != TL_vector) return -1;
    1802            5 :     n = tl_read_uint32(r);
    1803            5 :     for (uint32_t i = 0; i < n; i++) {
    1804            0 :         if (tl_skip_document(r) != 0) return -1;
    1805              :     }
    1806              : 
    1807              :     /* views:flags.3?int */
    1808            5 :     if (flags & (1u << 3)) {
    1809            0 :         if (r->len - r->pos < 4) return -1;
    1810            0 :         tl_read_int32(r);
    1811              :     }
    1812            5 :     return 0;
    1813              : }
    1814              : 
    1815              : /* Skip a Vector<WebPageAttribute> (webPage.attributes, flags.12). Only
    1816              :  * the three known WebPageAttribute variants are supported; anything
    1817              :  * else makes the caller bail. */
    1818            1 : static int skip_webpage_attributes_vector(TlReader *r) {
    1819            1 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
    1820            1 :     uint32_t vec = tl_read_uint32(r);
    1821            1 :     if (vec != TL_vector) return -1;
    1822            1 :     uint32_t n = tl_read_uint32(r);
    1823            3 :     for (uint32_t i = 0; i < n; i++) {
    1824            2 :         if (r->len - r->pos < 4) return -1;
    1825            2 :         uint32_t crc = tl_read_uint32(r);
    1826            2 :         switch (crc) {
    1827            1 :         case CRC_webPageAttributeTheme: {
    1828              :             /* flags:# documents:flags.0?Vector<Document>
    1829              :              *        settings:flags.1?ThemeSettings
    1830              :              * v1 bails if settings is present — ThemeSettings is a
    1831              :              * fat nested object we don't otherwise walk. */
    1832            1 :             if (r->len - r->pos < 4) return -1;
    1833            1 :             uint32_t flags = tl_read_uint32(r);
    1834            1 :             if (flags & (1u << 0)) {
    1835            0 :                 if (r->len - r->pos < 8) return -1;
    1836            0 :                 uint32_t dvec = tl_read_uint32(r);
    1837            0 :                 if (dvec != TL_vector) return -1;
    1838            0 :                 uint32_t dn = tl_read_uint32(r);
    1839            0 :                 for (uint32_t j = 0; j < dn; j++) {
    1840            0 :                     if (tl_skip_document(r) != 0) return -1;
    1841              :                 }
    1842              :             }
    1843            1 :             if (flags & (1u << 1)) {
    1844            0 :                 logger_log(LOG_WARN,
    1845              :                     "skip_webpage_attributes: theme settings not supported");
    1846            0 :                 return -1;
    1847              :             }
    1848            1 :             break;
    1849              :         }
    1850            0 :         case CRC_webPageAttributeStory: {
    1851              :             /* flags:# peer:Peer id:int story:flags.0?StoryItem */
    1852            0 :             if (r->len - r->pos < 4) return -1;
    1853            0 :             uint32_t flags = tl_read_uint32(r);
    1854            0 :             if (tl_skip_peer(r) != 0) return -1;
    1855            0 :             if (r->len - r->pos < 4) return -1;
    1856            0 :             tl_read_int32(r);                        /* id */
    1857            0 :             if (flags & (1u << 0)) {
    1858            0 :                 if (skip_story_item(r) != 0) return -1;
    1859              :             }
    1860            0 :             break;
    1861              :         }
    1862            1 :         case CRC_webPageAttributeStickerSet: {
    1863              :             /* flags:# emojis:flags.0?true text_color:flags.1?true
    1864              :              *        stickers:Vector<Document> */
    1865            1 :             if (r->len - r->pos < 4) return -1;
    1866            1 :             (void)tl_read_uint32(r);                 /* flags */
    1867            1 :             if (r->len - r->pos < 8) return -1;
    1868            1 :             uint32_t svec = tl_read_uint32(r);
    1869            1 :             if (svec != TL_vector) return -1;
    1870            1 :             uint32_t sn = tl_read_uint32(r);
    1871            1 :             for (uint32_t j = 0; j < sn; j++) {
    1872            0 :                 if (tl_skip_document(r) != 0) return -1;
    1873              :             }
    1874            1 :             break;
    1875              :         }
    1876            0 :         default:
    1877            0 :             logger_log(LOG_WARN,
    1878              :                 "skip_webpage_attributes: unknown variant 0x%08x", crc);
    1879            0 :             return -1;
    1880              :         }
    1881              :     }
    1882            1 :     return 0;
    1883              : }
    1884              : 
    1885              : /* Skip a WebPage variant. */
    1886           12 : static int skip_webpage(TlReader *r) {
    1887           12 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1888           12 :     uint32_t crc = tl_read_uint32(r);
    1889           12 :     switch (crc) {
    1890            1 :     case CRC_webPageEmpty: {
    1891            1 :         if (r->len - r->pos < 12) return -1;
    1892            1 :         uint32_t flags = tl_read_uint32(r);
    1893            1 :         tl_read_int64(r);                        /* id */
    1894            1 :         if (flags & (1u << 0))
    1895            0 :             if (tl_skip_string(r) != 0) return -1;
    1896            1 :         return 0;
    1897              :     }
    1898            1 :     case CRC_webPagePending: {
    1899            1 :         if (r->len - r->pos < 12) return -1;
    1900            1 :         uint32_t flags = tl_read_uint32(r);
    1901            1 :         tl_read_int64(r);
    1902            1 :         if (flags & (1u << 0))
    1903            0 :             if (tl_skip_string(r) != 0) return -1;
    1904            1 :         if (r->len - r->pos < 4) return -1;
    1905            1 :         tl_read_int32(r);                        /* date */
    1906            1 :         return 0;
    1907              :     }
    1908            1 :     case CRC_webPageNotModified: {
    1909            1 :         if (r->len - r->pos < 4) return -1;
    1910            1 :         uint32_t flags = tl_read_uint32(r);
    1911            1 :         if (flags & (1u << 0)) {
    1912            1 :             if (r->len - r->pos < 4) return -1;
    1913            1 :             tl_read_int32(r);                    /* cached_page_views */
    1914              :         }
    1915            1 :         return 0;
    1916              :     }
    1917            8 :     case CRC_webPage: {
    1918              :         /* webPage#e89c45b2 flags:# id:long url:string display_url:string
    1919              :          *   hash:int type:flags.0?string site_name:flags.1?string
    1920              :          *   title:flags.2?string description:flags.3?string
    1921              :          *   photo:flags.4?Photo embed_url:flags.5?string
    1922              :          *   embed_type:flags.5?string embed_width/height:flags.6?int
    1923              :          *   duration:flags.7?int author:flags.8?string
    1924              :          *   document:flags.9?Document cached_page:flags.10?Page
    1925              :          *   attributes:flags.12?Vector<WebPageAttribute>
    1926              :          */
    1927            8 :         if (r->len - r->pos < 16) return -1;
    1928            8 :         uint32_t flags = tl_read_uint32(r);
    1929            8 :         tl_read_int64(r);                        /* id */
    1930            8 :         if (tl_skip_string(r) != 0) return -1;   /* url */
    1931            8 :         if (tl_skip_string(r) != 0) return -1;   /* display_url */
    1932            8 :         if (r->len - r->pos < 4) return -1;
    1933            8 :         tl_read_int32(r);                        /* hash */
    1934            8 :         if (flags & (1u << 0))
    1935            1 :             if (tl_skip_string(r) != 0) return -1;
    1936            8 :         if (flags & (1u << 1))
    1937            1 :             if (tl_skip_string(r) != 0) return -1;
    1938            8 :         if (flags & (1u << 2))
    1939            1 :             if (tl_skip_string(r) != 0) return -1;
    1940            8 :         if (flags & (1u << 3))
    1941            1 :             if (tl_skip_string(r) != 0) return -1;
    1942            8 :         if (flags & (1u << 4)) {
    1943            0 :             if (photo_full(r, NULL) != 0) return -1;
    1944              :         }
    1945            8 :         if (flags & (1u << 5)) {
    1946            1 :             if (tl_skip_string(r) != 0) return -1;
    1947            1 :             if (tl_skip_string(r) != 0) return -1;
    1948              :         }
    1949            8 :         if (flags & (1u << 6)) {
    1950            1 :             if (r->len - r->pos < 8) return -1;
    1951            1 :             tl_read_int32(r); tl_read_int32(r);
    1952              :         }
    1953            8 :         if (flags & (1u << 7)) {
    1954            1 :             if (r->len - r->pos < 4) return -1;
    1955            1 :             tl_read_int32(r);
    1956              :         }
    1957            8 :         if (flags & (1u << 8))
    1958            1 :             if (tl_skip_string(r) != 0) return -1;
    1959            8 :         if (flags & (1u << 9)) {
    1960              :             /* document:Document — reuse tl_skip_document. */
    1961            0 :             if (tl_skip_document(r) != 0) return -1;
    1962              :         }
    1963            8 :         if (flags & (1u << 10)) {
    1964            5 :             if (skip_page(r) != 0) return -1;
    1965              :         }
    1966            8 :         if (flags & (1u << 12)) {
    1967            1 :             if (skip_webpage_attributes_vector(r) != 0) return -1;
    1968              :         }
    1969            8 :         return 0;
    1970              :     }
    1971            1 :     default:
    1972            1 :         logger_log(LOG_WARN, "skip_webpage: unknown 0x%08x", crc);
    1973            1 :         return -1;
    1974              :     }
    1975              : }
    1976              : 
    1977              : /* Skip a textWithEntities#751f3146: text:string + Vector<MessageEntity>. */
    1978            7 : static int skip_text_with_entities(TlReader *r) {
    1979            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1980            7 :     uint32_t crc = tl_read_uint32(r);
    1981            7 :     if (crc != CRC_textWithEntities_poll) {
    1982            0 :         logger_log(LOG_WARN, "skip_text_with_entities: unknown 0x%08x", crc);
    1983            0 :         return -1;
    1984              :     }
    1985            7 :     if (tl_skip_string(r) != 0) return -1;
    1986            7 :     return tl_skip_message_entities_vector(r);
    1987              : }
    1988              : 
    1989              : /* Skip a Poll#58747131 object. */
    1990            4 : static int skip_poll(TlReader *r) {
    1991            4 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    1992            4 :     uint32_t crc = tl_read_uint32(r);
    1993            4 :     if (crc != CRC_poll) {
    1994            0 :         logger_log(LOG_WARN, "skip_poll: unknown 0x%08x", crc);
    1995            0 :         return -1;
    1996              :     }
    1997            4 :     if (r->len - r->pos < 12) return -1;
    1998            4 :     uint32_t flags = tl_read_uint32(r);
    1999            4 :     tl_read_int64(r);                                /* id */
    2000            4 :     if (skip_text_with_entities(r) != 0) return -1;  /* question */
    2001              : 
    2002              :     /* answers:Vector<PollAnswer> */
    2003            4 :     if (r->len - r->pos < 8) return -1;
    2004            4 :     uint32_t vec_crc = tl_read_uint32(r);
    2005            4 :     if (vec_crc != TL_vector) return -1;
    2006            4 :     uint32_t n_answers = tl_read_uint32(r);
    2007            7 :     for (uint32_t i = 0; i < n_answers; i++) {
    2008            3 :         if (r->len - r->pos < 4) return -1;
    2009            3 :         uint32_t pa_crc = tl_read_uint32(r);
    2010            3 :         if (pa_crc != CRC_pollAnswer) {
    2011            0 :             logger_log(LOG_WARN, "skip_poll: bad PollAnswer 0x%08x", pa_crc);
    2012            0 :             return -1;
    2013              :         }
    2014            3 :         if (skip_text_with_entities(r) != 0) return -1;
    2015            3 :         if (tl_skip_string(r) != 0) return -1;       /* option:bytes */
    2016              :     }
    2017            4 :     if (flags & (1u << 4)) {
    2018            0 :         if (r->len - r->pos < 4) return -1;
    2019            0 :         tl_read_int32(r);                            /* close_period */
    2020              :     }
    2021            4 :     if (flags & (1u << 5)) {
    2022            0 :         if (r->len - r->pos < 4) return -1;
    2023            0 :         tl_read_int32(r);                            /* close_date */
    2024              :     }
    2025            4 :     return 0;
    2026              : }
    2027              : 
    2028              : /* Skip a PollAnswerVoters#3b6ddad2. */
    2029            3 : static int skip_poll_answer_voters(TlReader *r) {
    2030            3 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2031            3 :     uint32_t crc = tl_read_uint32(r);
    2032            3 :     if (crc != CRC_pollAnswerVoters) {
    2033            1 :         logger_log(LOG_WARN, "skip_poll_answer_voters: 0x%08x", crc);
    2034            1 :         return -1;
    2035              :     }
    2036            2 :     if (r->len - r->pos < 4) return -1;
    2037            2 :     tl_read_uint32(r);                               /* flags */
    2038            2 :     if (tl_skip_string(r) != 0) return -1;           /* option:bytes */
    2039            2 :     if (r->len - r->pos < 4) return -1;
    2040            2 :     tl_read_int32(r);                                /* voters */
    2041            2 :     return 0;
    2042              : }
    2043              : 
    2044              : /* Skip a PollResults#7adc669d object. */
    2045            4 : static int skip_poll_results(TlReader *r) {
    2046            4 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2047            4 :     uint32_t crc = tl_read_uint32(r);
    2048            4 :     if (crc != CRC_pollResults) {
    2049            0 :         logger_log(LOG_WARN, "skip_poll_results: 0x%08x", crc);
    2050            0 :         return -1;
    2051              :     }
    2052            4 :     if (r->len - r->pos < 4) return -1;
    2053            4 :     uint32_t flags = tl_read_uint32(r);
    2054            4 :     if (flags & (1u << 1)) {
    2055            2 :         if (r->len - r->pos < 8) return -1;
    2056            2 :         uint32_t vc = tl_read_uint32(r);
    2057            2 :         if (vc != TL_vector) return -1;
    2058            2 :         uint32_t n = tl_read_uint32(r);
    2059            4 :         for (uint32_t i = 0; i < n; i++) {
    2060            3 :             if (skip_poll_answer_voters(r) != 0) return -1;
    2061              :         }
    2062              :     }
    2063            3 :     if (flags & (1u << 2)) {
    2064            1 :         if (r->len - r->pos < 4) return -1;
    2065            1 :         tl_read_int32(r);                            /* total_voters */
    2066              :     }
    2067            3 :     if (flags & (1u << 3)) {
    2068            1 :         if (r->len - r->pos < 8) return -1;
    2069            1 :         uint32_t vc = tl_read_uint32(r);
    2070            1 :         if (vc != TL_vector) return -1;
    2071            1 :         uint32_t n = tl_read_uint32(r);
    2072            2 :         for (uint32_t i = 0; i < n; i++) {
    2073            1 :             if (tl_skip_peer(r) != 0) return -1;
    2074              :         }
    2075              :     }
    2076            3 :     if (flags & (1u << 4)) {
    2077            1 :         if (tl_skip_string(r) != 0) return -1;       /* solution */
    2078            1 :         if (tl_skip_message_entities_vector(r) != 0) return -1;
    2079              :     }
    2080            3 :     return 0;
    2081              : }
    2082              : 
    2083              : /* ---- Game ----
    2084              :  * game#bdf9653b flags:# id:long access_hash:long short_name:string
    2085              :  *   title:string description:string photo:Photo document:flags.0?Document
    2086              :  */
    2087            3 : static int skip_game(TlReader *r) {
    2088            3 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2089            3 :     uint32_t crc = tl_read_uint32(r);
    2090            3 :     if (crc != CRC_game) {
    2091            1 :         logger_log(LOG_WARN, "skip_game: unexpected 0x%08x", crc);
    2092            1 :         return -1;
    2093              :     }
    2094            2 :     if (r->len - r->pos < 4 + 8 + 8) return -1;
    2095            2 :     uint32_t flags = tl_read_uint32(r);
    2096            2 :     tl_read_int64(r);                                /* id */
    2097            2 :     tl_read_int64(r);                                /* access_hash */
    2098            2 :     if (tl_skip_string(r) != 0) return -1;           /* short_name */
    2099            2 :     if (tl_skip_string(r) != 0) return -1;           /* title */
    2100            2 :     if (tl_skip_string(r) != 0) return -1;           /* description */
    2101            2 :     if (tl_skip_photo(r) != 0) return -1;            /* photo */
    2102            2 :     if (flags & (1u << 0)) {
    2103            1 :         if (tl_skip_document(r) != 0) return -1;     /* document */
    2104              :     }
    2105            2 :     return 0;
    2106              : }
    2107              : 
    2108              : /* ---- MessageExtendedMedia ----
    2109              :  * messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int
    2110              :  *   thumb:flags.1?PhotoSize video_duration:flags.2?int
    2111              :  * messageExtendedMedia#ee479c64 media:MessageMedia
    2112              :  */
    2113            4 : static int skip_message_extended_media(TlReader *r) {
    2114            4 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2115            4 :     uint32_t crc = tl_read_uint32(r);
    2116            4 :     switch (crc) {
    2117            2 :     case CRC_messageExtendedMediaPreview: {
    2118            2 :         if (r->len - r->pos < 4) return -1;
    2119            2 :         uint32_t flags = tl_read_uint32(r);
    2120            2 :         if (flags & (1u << 0)) {
    2121            1 :             if (r->len - r->pos < 8) return -1;
    2122            1 :             tl_read_int32(r); tl_read_int32(r);       /* w, h */
    2123              :         }
    2124            2 :         if (flags & (1u << 1)) {
    2125            1 :             if (tl_skip_photo_size(r) != 0) return -1;
    2126              :         }
    2127            2 :         if (flags & (1u << 2)) {
    2128            1 :             if (r->len - r->pos < 4) return -1;
    2129            1 :             tl_read_int32(r);                         /* video_duration */
    2130              :         }
    2131            2 :         return 0;
    2132              :     }
    2133            1 :     case CRC_messageExtendedMedia:
    2134              :         /* Recurse into the wrapped MessageMedia. The inner metadata is
    2135              :          * discarded — paid-media iteration only needs the outer kind. */
    2136            1 :         return tl_skip_message_media_ex(r, NULL);
    2137            1 :     default:
    2138            1 :         logger_log(LOG_WARN,
    2139              :                    "skip_message_extended_media: unknown 0x%08x", crc);
    2140            1 :         return -1;
    2141              :     }
    2142              : }
    2143              : 
    2144              : /* ---- WebDocument ----
    2145              :  * webDocument#1c570ed1        url:string access_hash:long size:int
    2146              :  *                             mime_type:string attributes:Vector<DocumentAttribute>
    2147              :  * webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string
    2148              :  *                             attributes:Vector<DocumentAttribute>
    2149              :  */
    2150            3 : static int skip_web_document(TlReader *r) {
    2151            3 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2152            3 :     uint32_t crc = tl_read_uint32(r);
    2153              :     int has_access_hash;
    2154            3 :     switch (crc) {
    2155            1 :     case CRC_webDocument:        has_access_hash = 1; break;
    2156            1 :     case CRC_webDocumentNoProxy: has_access_hash = 0; break;
    2157            1 :     default:
    2158            1 :         logger_log(LOG_WARN, "skip_web_document: unknown 0x%08x", crc);
    2159            1 :         return -1;
    2160              :     }
    2161            2 :     if (tl_skip_string(r) != 0) return -1;                /* url */
    2162            2 :     if (has_access_hash) {
    2163            1 :         if (r->len - r->pos < 8) return -1;
    2164            1 :         tl_read_int64(r);                                 /* access_hash */
    2165              :     }
    2166            2 :     if (r->len - r->pos < 4) return -1;
    2167            2 :     tl_read_int32(r);                                     /* size */
    2168            2 :     if (tl_skip_string(r) != 0) return -1;                /* mime_type */
    2169              :     /* attributes:Vector<DocumentAttribute> */
    2170            2 :     if (r->len - r->pos < 8) return -1;
    2171            2 :     uint32_t vc = tl_read_uint32(r);
    2172            2 :     if (vc != TL_vector) return -1;
    2173            2 :     uint32_t n = tl_read_uint32(r);
    2174            3 :     for (uint32_t i = 0; i < n; i++) {
    2175            1 :         if (skip_document_attribute(r, NULL, 0) != 0) return -1;
    2176              :     }
    2177            2 :     return 0;
    2178              : }
    2179              : 
    2180              : /* ---- StoryFwdHeader ----
    2181              :  * storyFwdHeader#b826e150 flags:# modified:flags.3?true
    2182              :  *   from:flags.0?Peer from_name:flags.1?string story_id:flags.2?int
    2183              :  */
    2184            2 : static int skip_story_fwd_header(TlReader *r) {
    2185            2 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2186            2 :     uint32_t crc = tl_read_uint32(r);
    2187            2 :     if (crc != CRC_storyFwdHeader) {
    2188            0 :         logger_log(LOG_WARN, "skip_story_fwd_header: unknown 0x%08x", crc);
    2189            0 :         return -1;
    2190              :     }
    2191            2 :     if (r->len - r->pos < 4) return -1;
    2192            2 :     uint32_t flags = tl_read_uint32(r);
    2193            2 :     if (flags & (1u << 0))
    2194            1 :         if (tl_skip_peer(r) != 0) return -1;              /* from */
    2195            2 :     if (flags & (1u << 1))
    2196            1 :         if (tl_skip_string(r) != 0) return -1;            /* from_name */
    2197            2 :     if (flags & (1u << 2)) {
    2198            2 :         if (r->len - r->pos < 4) return -1;
    2199            2 :         tl_read_int32(r);                                 /* story_id */
    2200              :     }
    2201            2 :     return 0;
    2202              : }
    2203              : 
    2204              : /* ---- MediaAreaCoordinates ----
    2205              :  * mediaAreaCoordinates#03d1ea4e flags:# x:double y:double w:double h:double
    2206              :  *                                rotation:double radius:flags.0?double
    2207              :  */
    2208            7 : static int skip_media_area_coordinates(TlReader *r) {
    2209            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2210            7 :     uint32_t crc = tl_read_uint32(r);
    2211            7 :     if (crc != CRC_mediaAreaCoordinates) {
    2212            0 :         logger_log(LOG_WARN,
    2213              :                    "skip_media_area_coordinates: unknown 0x%08x", crc);
    2214            0 :         return -1;
    2215              :     }
    2216            7 :     if (r->len - r->pos < 4 + 5 * 8) return -1;
    2217            7 :     uint32_t flags = tl_read_uint32(r);
    2218           42 :     for (int i = 0; i < 5; i++) tl_read_double(r);        /* x, y, w, h, rot */
    2219            7 :     if (flags & (1u << 0)) {
    2220            0 :         if (r->len - r->pos < 8) return -1;
    2221            0 :         tl_read_double(r);                                /* radius */
    2222              :     }
    2223            7 :     return 0;
    2224              : }
    2225              : 
    2226              : /* ---- GeoPointAddress ----
    2227              :  * geoPointAddress#de4c5d93 flags:# country_iso2:string
    2228              :  *   state:flags.0?string city:flags.1?string street:flags.2?string
    2229              :  */
    2230            1 : static int skip_geo_point_address(TlReader *r) {
    2231            1 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2232            1 :     uint32_t crc = tl_read_uint32(r);
    2233            1 :     if (crc != CRC_geoPointAddress) {
    2234            0 :         logger_log(LOG_WARN,
    2235              :                    "skip_geo_point_address: unknown 0x%08x", crc);
    2236            0 :         return -1;
    2237              :     }
    2238            1 :     if (r->len - r->pos < 4) return -1;
    2239            1 :     uint32_t flags = tl_read_uint32(r);
    2240            1 :     if (tl_skip_string(r) != 0) return -1;                /* country_iso2 */
    2241            1 :     if (flags & (1u << 0))
    2242            1 :         if (tl_skip_string(r) != 0) return -1;            /* state */
    2243            1 :     if (flags & (1u << 1))
    2244            1 :         if (tl_skip_string(r) != 0) return -1;            /* city */
    2245            1 :     if (flags & (1u << 2))
    2246            1 :         if (tl_skip_string(r) != 0) return -1;            /* street */
    2247            1 :     return 0;
    2248              : }
    2249              : 
    2250              : /* ---- MediaArea ---- dispatch over 7 known variants. */
    2251            8 : static int skip_media_area(TlReader *r) {
    2252            8 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2253            8 :     uint32_t crc = tl_read_uint32(r);
    2254            8 :     switch (crc) {
    2255            1 :     case CRC_mediaAreaVenue:
    2256            1 :         if (skip_media_area_coordinates(r) != 0) return -1;
    2257            1 :         if (skip_geo_point(r) != 0) return -1;
    2258            1 :         if (tl_skip_string(r) != 0) return -1;            /* title */
    2259            1 :         if (tl_skip_string(r) != 0) return -1;            /* address */
    2260            1 :         if (tl_skip_string(r) != 0) return -1;            /* provider */
    2261            1 :         if (tl_skip_string(r) != 0) return -1;            /* venue_id */
    2262            1 :         if (tl_skip_string(r) != 0) return -1;            /* venue_type */
    2263            1 :         return 0;
    2264            1 :     case CRC_mediaAreaGeoPoint: {
    2265            1 :         if (r->len - r->pos < 4) return -1;
    2266            1 :         uint32_t flags = tl_read_uint32(r);
    2267            1 :         if (skip_media_area_coordinates(r) != 0) return -1;
    2268            1 :         if (skip_geo_point(r) != 0) return -1;
    2269            1 :         if (flags & (1u << 0))
    2270            1 :             if (skip_geo_point_address(r) != 0) return -1;
    2271            1 :         return 0;
    2272              :     }
    2273            1 :     case CRC_mediaAreaSuggestedReaction: {
    2274            1 :         if (r->len - r->pos < 4) return -1;
    2275            1 :         tl_read_uint32(r);                                /* flags */
    2276            1 :         if (skip_media_area_coordinates(r) != 0) return -1;
    2277            1 :         return skip_reaction(r);
    2278              :     }
    2279            1 :     case CRC_mediaAreaChannelPost:
    2280            1 :         if (skip_media_area_coordinates(r) != 0) return -1;
    2281            1 :         if (r->len - r->pos < 12) return -1;
    2282            1 :         tl_read_int64(r);                                 /* channel_id */
    2283            1 :         tl_read_int32(r);                                 /* msg_id */
    2284            1 :         return 0;
    2285            1 :     case CRC_mediaAreaUrl:
    2286            1 :         if (skip_media_area_coordinates(r) != 0) return -1;
    2287            1 :         return tl_skip_string(r);                         /* url */
    2288            1 :     case CRC_mediaAreaWeather:
    2289            1 :         if (skip_media_area_coordinates(r) != 0) return -1;
    2290            1 :         if (tl_skip_string(r) != 0) return -1;            /* emoji */
    2291            1 :         if (r->len - r->pos < 8 + 4) return -1;
    2292            1 :         tl_read_double(r);                                /* temperature_c */
    2293            1 :         tl_read_int32(r);                                 /* color */
    2294            1 :         return 0;
    2295            1 :     case CRC_mediaAreaStarGift:
    2296            1 :         if (skip_media_area_coordinates(r) != 0) return -1;
    2297            1 :         return tl_skip_string(r);                         /* slug */
    2298            1 :     default:
    2299            1 :         logger_log(LOG_WARN, "skip_media_area: unknown 0x%08x", crc);
    2300            1 :         return -1;
    2301              :     }
    2302              : }
    2303              : 
    2304              : /* ---- PrivacyRule ---- 12 variants; most are CRC-only. */
    2305            3 : static int skip_privacy_rule(TlReader *r) {
    2306            3 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2307            3 :     uint32_t crc = tl_read_uint32(r);
    2308            3 :     switch (crc) {
    2309            1 :     case CRC_privacyValueAllowContacts:
    2310              :     case CRC_privacyValueAllowAll:
    2311              :     case CRC_privacyValueDisallowContacts:
    2312              :     case CRC_privacyValueDisallowAll:
    2313              :     case CRC_privacyValueAllowCloseFriends:
    2314              :     case CRC_privacyValueAllowPremium:
    2315              :     case CRC_privacyValueAllowBots:
    2316              :     case CRC_privacyValueDisallowBots:
    2317            1 :         return 0;
    2318            1 :     case CRC_privacyValueAllowUsers:
    2319              :     case CRC_privacyValueDisallowUsers:
    2320              :     case CRC_privacyValueAllowChatParticipants:
    2321              :     case CRC_privacyValueDisallowChatParticipants: {
    2322              :         /* Vector<long> */
    2323            1 :         if (r->len - r->pos < 8) return -1;
    2324            1 :         uint32_t vc = tl_read_uint32(r);
    2325            1 :         if (vc != TL_vector) return -1;
    2326            1 :         uint32_t n = tl_read_uint32(r);
    2327            1 :         if (r->len - r->pos < (size_t)n * 8) return -1;
    2328            3 :         for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
    2329            1 :         return 0;
    2330              :     }
    2331            1 :     default:
    2332            1 :         logger_log(LOG_WARN, "skip_privacy_rule: unknown 0x%08x", crc);
    2333            1 :         return -1;
    2334              :     }
    2335              : }
    2336              : 
    2337              : /* ---- StoryViews ----
    2338              :  * storyViews#8d595cd6 flags:# has_viewers:flags.1?true views_count:int
    2339              :  *   forwards_count:flags.2?int reactions:flags.3?Vector<ReactionCount>
    2340              :  *   reactions_count:flags.4?int recent_viewers:flags.0?Vector<long>
    2341              :  */
    2342            2 : static int skip_story_views(TlReader *r) {
    2343            2 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2344            2 :     uint32_t crc = tl_read_uint32(r);
    2345            2 :     if (crc != CRC_storyViews) {
    2346            1 :         logger_log(LOG_WARN, "skip_story_views: unknown 0x%08x", crc);
    2347            1 :         return -1;
    2348              :     }
    2349            1 :     if (r->len - r->pos < 4) return -1;
    2350            1 :     uint32_t flags = tl_read_uint32(r);
    2351            1 :     if (r->len - r->pos < 4) return -1;
    2352            1 :     tl_read_int32(r);                                     /* views_count */
    2353            1 :     if (flags & (1u << 2)) {
    2354            1 :         if (r->len - r->pos < 4) return -1;
    2355            1 :         tl_read_int32(r);                                 /* forwards_count */
    2356              :     }
    2357            1 :     if (flags & (1u << 3)) {
    2358            1 :         if (r->len - r->pos < 8) return -1;
    2359            1 :         uint32_t vc = tl_read_uint32(r);
    2360            1 :         if (vc != TL_vector) return -1;
    2361            1 :         uint32_t n = tl_read_uint32(r);
    2362            2 :         for (uint32_t i = 0; i < n; i++)
    2363            1 :             if (skip_reaction_count(r) != 0) return -1;
    2364              :     }
    2365            1 :     if (flags & (1u << 4)) {
    2366            1 :         if (r->len - r->pos < 4) return -1;
    2367            1 :         tl_read_int32(r);                                 /* reactions_count */
    2368              :     }
    2369            1 :     if (flags & (1u << 0)) {
    2370            1 :         if (r->len - r->pos < 8) return -1;
    2371            1 :         uint32_t vc = tl_read_uint32(r);
    2372            1 :         if (vc != TL_vector) return -1;
    2373            1 :         uint32_t n = tl_read_uint32(r);
    2374            1 :         if (r->len - r->pos < (size_t)n * 8) return -1;
    2375            2 :         for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
    2376              :     }
    2377            1 :     return 0;
    2378              : }
    2379              : 
    2380              : /* ---- StoryItem ----
    2381              :  * storyItemDeleted#51e6ee4f id:int
    2382              :  * storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true
    2383              :  *                           id:int date:int expire_date:int
    2384              :  * storyItem#79b26a24 — full layout. The inner `media:MessageMedia`
    2385              :  * dispatches recursively through tl_skip_message_media_ex, which
    2386              :  * handles the common sub-cases (empty / photo / document / webpage /
    2387              :  * …). Deeply nested stories-inside-stories would bail by the inner
    2388              :  * dispatcher's own bail rules.
    2389              :  */
    2390           10 : static int skip_story_item(TlReader *r) {
    2391           10 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2392           10 :     uint32_t crc = tl_read_uint32(r);
    2393           10 :     switch (crc) {
    2394            1 :     case CRC_storyItemDeleted:
    2395            1 :         if (r->len - r->pos < 4) return -1;
    2396            1 :         tl_read_int32(r);                                 /* id */
    2397            1 :         return 0;
    2398            1 :     case CRC_storyItemSkipped: {
    2399            1 :         if (r->len - r->pos < 4) return -1;
    2400            1 :         tl_read_uint32(r);                                /* flags */
    2401            1 :         if (r->len - r->pos < 12) return -1;
    2402            1 :         tl_read_int32(r);                                 /* id */
    2403            1 :         tl_read_int32(r);                                 /* date */
    2404            1 :         tl_read_int32(r);                                 /* expire_date */
    2405            1 :         return 0;
    2406              :     }
    2407            7 :     case CRC_storyItem: {
    2408            7 :         if (r->len - r->pos < 4) return -1;
    2409            7 :         uint32_t flags = tl_read_uint32(r);
    2410            7 :         if (r->len - r->pos < 8) return -1;
    2411            7 :         tl_read_int32(r);                                 /* id */
    2412            7 :         tl_read_int32(r);                                 /* date */
    2413            7 :         if (flags & (1u << 18))
    2414            0 :             if (tl_skip_peer(r) != 0) return -1;          /* from_id */
    2415            7 :         if (flags & (1u << 17))
    2416            2 :             if (skip_story_fwd_header(r) != 0) return -1; /* fwd_from */
    2417            7 :         if (r->len - r->pos < 4) return -1;
    2418            7 :         tl_read_int32(r);                                 /* expire_date */
    2419            7 :         if (flags & (1u << 0))
    2420            1 :             if (tl_skip_string(r) != 0) return -1;        /* caption */
    2421            7 :         if (flags & (1u << 1))
    2422            1 :             if (tl_skip_message_entities_vector(r) != 0) return -1;
    2423            7 :         if (tl_skip_message_media_ex(r, NULL) != 0) return -1;
    2424            7 :         if (flags & (1u << 14)) {
    2425            4 :             if (r->len - r->pos < 8) return -1;
    2426            4 :             uint32_t vc = tl_read_uint32(r);
    2427            4 :             if (vc != TL_vector) return -1;
    2428            4 :             uint32_t n = tl_read_uint32(r);
    2429           11 :             for (uint32_t i = 0; i < n; i++)
    2430            8 :                 if (skip_media_area(r) != 0) return -1;
    2431              :         }
    2432            6 :         if (flags & (1u << 2)) {
    2433            2 :             if (r->len - r->pos < 8) return -1;
    2434            2 :             uint32_t vc = tl_read_uint32(r);
    2435            2 :             if (vc != TL_vector) return -1;
    2436            2 :             uint32_t n = tl_read_uint32(r);
    2437            4 :             for (uint32_t i = 0; i < n; i++)
    2438            3 :                 if (skip_privacy_rule(r) != 0) return -1;
    2439              :         }
    2440            5 :         if (flags & (1u << 3))
    2441            2 :             if (skip_story_views(r) != 0) return -1;
    2442            4 :         if (flags & (1u << 15))
    2443            0 :             if (skip_reaction(r) != 0) return -1;         /* sent_reaction */
    2444            4 :         return 0;
    2445              :     }
    2446            1 :     default:
    2447            1 :         logger_log(LOG_WARN, "skip_story_item: unknown 0x%08x", crc);
    2448            1 :         return -1;
    2449              :     }
    2450              : }
    2451              : 
    2452              : /* ---- MessageMedia skipper ----
    2453              :  * Covers all normal variants (photo, document, geo, contact, venue,
    2454              :  * poll, invoice, story-deleted/skipped, giveaway, game, paid, webpage).
    2455              :  *
    2456              :  * The dispatcher is re-entrant via messageMediaStory → storyItem →
    2457              :  * inner media:MessageMedia. An adversarial server could attempt
    2458              :  * unbounded recursion by chaining messageMediaStory inside itself.
    2459              :  * A depth counter caps the recursion at MEDIA_RECURSION_MAX; beyond
    2460              :  * that we bail with -1 and the caller stops iterating the enclosing
    2461              :  * Message. Realistic payloads never exceed depth 2.
    2462              :  */
    2463              : #define MEDIA_RECURSION_MAX 4
    2464              : 
    2465              : static int skip_message_media_body(TlReader *r, MediaInfo *out);
    2466              : 
    2467           68 : int tl_skip_message_media_ex(TlReader *r, MediaInfo *out) {
    2468              :     static int recursion_depth = 0;
    2469           68 :     if (recursion_depth >= MEDIA_RECURSION_MAX) {
    2470            0 :         logger_log(LOG_WARN,
    2471              :             "tl_skip_message_media_ex: recursion depth limit (%d) reached",
    2472              :             MEDIA_RECURSION_MAX);
    2473            0 :         return -1;
    2474              :     }
    2475           68 :     recursion_depth++;
    2476           68 :     int rc = skip_message_media_body(r, out);
    2477           68 :     recursion_depth--;
    2478           68 :     return rc;
    2479              : }
    2480              : 
    2481           68 : static int skip_message_media_body(TlReader *r, MediaInfo *out) {
    2482           68 :     if (out) memset(out, 0, sizeof(*out));
    2483           68 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2484           68 :     uint32_t crc = tl_read_uint32(r);
    2485           68 :     switch (crc) {
    2486           12 :     case CRC_messageMediaEmpty:
    2487           12 :         if (out) out->kind = MEDIA_EMPTY;
    2488           12 :         return 0;
    2489            1 :     case CRC_messageMediaUnsupported:
    2490            1 :         if (out) out->kind = MEDIA_UNSUPPORTED;
    2491            1 :         return 0;
    2492              : 
    2493            2 :     case CRC_messageMediaGeo:
    2494            2 :         if (out) out->kind = MEDIA_GEO;
    2495            2 :         return skip_geo_point(r);
    2496              : 
    2497            2 :     case CRC_messageMediaContact:
    2498            2 :         if (out) out->kind = MEDIA_CONTACT;
    2499            2 :         if (tl_skip_string(r) != 0) return -1;
    2500            2 :         if (tl_skip_string(r) != 0) return -1;
    2501            2 :         if (tl_skip_string(r) != 0) return -1;
    2502            2 :         if (tl_skip_string(r) != 0) return -1;
    2503            2 :         if (r->len - r->pos < 8) return -1;
    2504            2 :         tl_read_int64(r);
    2505            2 :         return 0;
    2506              : 
    2507            1 :     case CRC_messageMediaVenue:
    2508            1 :         if (out) out->kind = MEDIA_VENUE;
    2509            1 :         if (skip_geo_point(r) != 0) return -1;
    2510            1 :         if (tl_skip_string(r) != 0) return -1;
    2511            1 :         if (tl_skip_string(r) != 0) return -1;
    2512            1 :         if (tl_skip_string(r) != 0) return -1;
    2513            1 :         if (tl_skip_string(r) != 0) return -1;
    2514            1 :         if (tl_skip_string(r) != 0) return -1;
    2515            1 :         return 0;
    2516              : 
    2517            1 :     case CRC_messageMediaGeoLive: {
    2518            1 :         if (out) out->kind = MEDIA_GEO_LIVE;
    2519            1 :         if (r->len - r->pos < 4) return -1;
    2520            1 :         uint32_t flags = tl_read_uint32(r);
    2521            1 :         if (skip_geo_point(r) != 0) return -1;
    2522            1 :         if (flags & 1u) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    2523            1 :         if (r->len - r->pos < 4) return -1;
    2524            1 :         tl_read_int32(r);
    2525            1 :         if (flags & (1u << 1)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    2526            1 :         return 0;
    2527              :     }
    2528              : 
    2529            1 :     case CRC_messageMediaDice:
    2530            1 :         if (out) out->kind = MEDIA_DICE;
    2531            1 :         if (r->len - r->pos < 4) return -1;
    2532            1 :         tl_read_int32(r);
    2533            1 :         return tl_skip_string(r);
    2534              : 
    2535            2 :     case CRC_messageMediaPhoto: {
    2536            2 :         if (out) out->kind = MEDIA_PHOTO;
    2537            2 :         if (r->len - r->pos < 4) return -1;
    2538            2 :         uint32_t flags = tl_read_uint32(r);
    2539            2 :         if (flags & (1u << 0)) {
    2540            2 :             if (photo_full(r, out) != 0) return -1;
    2541              :         }
    2542            2 :         if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    2543            2 :         return 0;
    2544              :     }
    2545              : 
    2546           12 :     case CRC_messageMediaWebPage: {
    2547           12 :         if (out) out->kind = MEDIA_WEBPAGE;
    2548           12 :         if (r->len - r->pos < 4) return -1;
    2549           12 :         uint32_t flags = tl_read_uint32(r);
    2550              :         (void)flags;                              /* only boolean previews */
    2551           12 :         return skip_webpage(r);
    2552              :     }
    2553              : 
    2554            4 :     case CRC_messageMediaPoll: {
    2555            4 :         if (out) out->kind = MEDIA_POLL;
    2556            4 :         if (skip_poll(r) != 0) return -1;
    2557            4 :         return skip_poll_results(r);
    2558              :     }
    2559              : 
    2560            3 :     case CRC_messageMediaInvoice: {
    2561            3 :         if (out) out->kind = MEDIA_INVOICE;
    2562            3 :         if (r->len - r->pos < 4) return -1;
    2563            3 :         uint32_t flags = tl_read_uint32(r);
    2564            3 :         if (tl_skip_string(r) != 0) return -1;    /* title */
    2565            3 :         if (tl_skip_string(r) != 0) return -1;    /* description */
    2566            3 :         if (flags & (1u << 0)) {
    2567            3 :             if (skip_web_document(r) != 0) return -1;
    2568              :         }
    2569            2 :         if (flags & (1u << 2)) {
    2570            0 :             if (r->len - r->pos < 4) return -1;
    2571            0 :             tl_read_int32(r);                     /* receipt_msg_id */
    2572              :         }
    2573            2 :         if (tl_skip_string(r) != 0) return -1;    /* currency */
    2574            2 :         if (r->len - r->pos < 8) return -1;
    2575            2 :         tl_read_int64(r);                         /* total_amount */
    2576            2 :         if (tl_skip_string(r) != 0) return -1;    /* start_param */
    2577            2 :         if (flags & (1u << 4)) {
    2578            0 :             if (skip_message_extended_media(r) != 0) return -1;
    2579              :         }
    2580            2 :         return 0;
    2581              :     }
    2582              : 
    2583           10 :     case CRC_messageMediaStory: {
    2584           10 :         if (out) out->kind = MEDIA_STORY;
    2585           10 :         if (r->len - r->pos < 4) return -1;
    2586           10 :         uint32_t flags = tl_read_uint32(r);
    2587           10 :         if (tl_skip_peer(r) != 0) return -1;      /* peer */
    2588           10 :         if (r->len - r->pos < 4) return -1;
    2589           10 :         tl_read_int32(r);                         /* id */
    2590           10 :         if (flags & (1u << 0)) {
    2591           10 :             if (skip_story_item(r) != 0) return -1;
    2592              :         }
    2593            6 :         return 0;
    2594              :     }
    2595              : 
    2596            0 :     case CRC_messageMediaGiveaway: {
    2597            0 :         if (out) out->kind = MEDIA_GIVEAWAY;
    2598            0 :         if (r->len - r->pos < 4) return -1;
    2599            0 :         uint32_t flags = tl_read_uint32(r);
    2600              :         /* channels:Vector<long> */
    2601            0 :         if (r->len - r->pos < 8) return -1;
    2602            0 :         uint32_t vc = tl_read_uint32(r);
    2603            0 :         if (vc != TL_vector) return -1;
    2604            0 :         uint32_t n_ch = tl_read_uint32(r);
    2605            0 :         if (r->len - r->pos < (size_t)n_ch * 8) return -1;
    2606            0 :         for (uint32_t i = 0; i < n_ch; i++) tl_read_int64(r);
    2607            0 :         if (flags & (1u << 1)) {
    2608            0 :             if (r->len - r->pos < 8) return -1;
    2609            0 :             uint32_t cvc = tl_read_uint32(r);
    2610            0 :             if (cvc != TL_vector) return -1;
    2611            0 :             uint32_t n_c = tl_read_uint32(r);
    2612            0 :             for (uint32_t i = 0; i < n_c; i++)
    2613            0 :                 if (tl_skip_string(r) != 0) return -1;
    2614              :         }
    2615            0 :         if (flags & (1u << 3))
    2616            0 :             if (tl_skip_string(r) != 0) return -1;
    2617            0 :         if (r->len - r->pos < 4) return -1;
    2618            0 :         tl_read_int32(r);                         /* quantity */
    2619            0 :         if (flags & (1u << 4)) {
    2620            0 :             if (r->len - r->pos < 4) return -1;
    2621            0 :             tl_read_int32(r);                     /* months */
    2622              :         }
    2623            0 :         if (flags & (1u << 5)) {
    2624            0 :             if (r->len - r->pos < 8) return -1;
    2625            0 :             tl_read_int64(r);                     /* stars */
    2626              :         }
    2627            0 :         if (r->len - r->pos < 4) return -1;
    2628            0 :         tl_read_int32(r);                         /* until_date */
    2629            0 :         return 0;
    2630              :     }
    2631              : 
    2632            3 :     case CRC_messageMediaGame: {
    2633            3 :         if (out) out->kind = MEDIA_GAME;
    2634            3 :         return skip_game(r);
    2635              :     }
    2636              : 
    2637            4 :     case CRC_messageMediaPaidMedia: {
    2638            4 :         if (out) out->kind = MEDIA_PAID;
    2639            4 :         if (r->len - r->pos < 8) return -1;
    2640            4 :         tl_read_int64(r);                         /* stars_amount */
    2641            4 :         if (r->len - r->pos < 8) return -1;
    2642            4 :         uint32_t vc = tl_read_uint32(r);
    2643            4 :         if (vc != TL_vector) return -1;
    2644            4 :         uint32_t n = tl_read_uint32(r);
    2645            7 :         for (uint32_t i = 0; i < n; i++) {
    2646            4 :             if (skip_message_extended_media(r) != 0) return -1;
    2647              :         }
    2648            3 :         return 0;
    2649              :     }
    2650              : 
    2651            8 :     case CRC_messageMediaDocument: {
    2652            8 :         if (out) out->kind = MEDIA_DOCUMENT;
    2653            8 :         if (r->len - r->pos < 4) return -1;
    2654            8 :         uint32_t flags = tl_read_uint32(r);
    2655            8 :         if (flags & ((1u << 5) | (1u << 9) | (1u << 10) | (1u << 11))) {
    2656            0 :             logger_log(LOG_WARN,
    2657              :                        "tl_skip_message_media: document with unsupported flags 0x%x",
    2658              :                        flags);
    2659            0 :             return -1;
    2660              :         }
    2661            8 :         if (flags & (1u << 0)) {
    2662            8 :             if (document_inner(r, out) != 0) return -1;
    2663              :         }
    2664            8 :         if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    2665            8 :         return 0;
    2666              :     }
    2667              : 
    2668            2 :     default:
    2669            2 :         if (out) out->kind = MEDIA_OTHER;
    2670            2 :         logger_log(LOG_WARN, "tl_skip_message_media: unsupported 0x%08x", crc);
    2671            2 :         return -1;
    2672              :     }
    2673              : }
    2674              : 
    2675            9 : int tl_skip_message_media(TlReader *r) {
    2676            9 :     return tl_skip_message_media_ex(r, NULL);
    2677              : }
    2678              : 
    2679              : /* ---- Chat / User helpers ---- */
    2680              : 
    2681              : /* Read a TL string into a fixed-size buffer, truncating if necessary.
    2682              :  * Always NUL-terminates `out` unless out_cap == 0. Advances the cursor.
    2683              :  * Returns 0 on success, -1 on reader error. */
    2684            9 : static int read_string_into(TlReader *r, char *out, size_t out_cap) {
    2685            9 :     char *s = tl_read_string(r);
    2686            9 :     if (!s) {
    2687            0 :         if (out_cap > 0) out[0] = '\0';
    2688            0 :         return -1;
    2689              :     }
    2690            9 :     if (out_cap > 0) {
    2691            9 :         size_t n = strlen(s);
    2692            9 :         if (n >= out_cap) n = out_cap - 1;
    2693            9 :         memcpy(out, s, n);
    2694            9 :         out[n] = '\0';
    2695              :     }
    2696            9 :     free(s);
    2697            9 :     return 0;
    2698              : }
    2699              : 
    2700              : /* Append `src` to `dst` (NUL-terminated), respecting dst_cap. */
    2701            6 : static void str_append(char *dst, size_t dst_cap, const char *src) {
    2702            6 :     if (dst_cap == 0) return;
    2703            6 :     size_t cur = strlen(dst);
    2704            6 :     if (cur >= dst_cap - 1) return;
    2705            6 :     size_t room = dst_cap - 1 - cur;
    2706            6 :     size_t n = strlen(src);
    2707            6 :     if (n > room) n = room;
    2708            6 :     memcpy(dst + cur, src, n);
    2709            6 :     dst[cur + n] = '\0';
    2710              : }
    2711              : 
    2712              : /* ---- ChatPhoto ----
    2713              :  * chatPhotoEmpty#37c1011c
    2714              :  * chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long
    2715              :  *                    stripped_thumb:flags.1?bytes dc_id:int
    2716              :  */
    2717            8 : int tl_skip_chat_photo(TlReader *r) {
    2718            8 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2719            8 :     uint32_t crc = tl_read_uint32(r);
    2720            8 :     if (crc == CRC_chatPhotoEmpty) return 0;
    2721            3 :     if (crc != CRC_chatPhoto) {
    2722            1 :         logger_log(LOG_WARN, "tl_skip_chat_photo: unknown 0x%08x", crc);
    2723            1 :         return -1;
    2724              :     }
    2725            2 :     if (r->len - r->pos < 4) return -1;
    2726            2 :     uint32_t flags = tl_read_uint32(r);
    2727              :     /* photo_id:long */
    2728            2 :     if (r->len - r->pos < 8) return -1;
    2729            2 :     tl_read_int64(r);
    2730            2 :     if (flags & (1u << 1)) {
    2731            1 :         if (tl_skip_string(r) != 0) return -1; /* stripped_thumb:bytes */
    2732              :     }
    2733              :     /* dc_id:int */
    2734            2 :     if (r->len - r->pos < 4) return -1;
    2735            2 :     tl_read_int32(r);
    2736            2 :     return 0;
    2737              : }
    2738              : 
    2739              : /* ---- UserProfilePhoto ----
    2740              :  * userProfilePhotoEmpty#4f11bae1
    2741              :  * userProfilePhoto#82d1f706 flags:# has_video:flags.0?true
    2742              :  *                           personal:flags.2?true photo_id:long
    2743              :  *                           stripped_thumb:flags.1?bytes dc_id:int
    2744              :  */
    2745            3 : int tl_skip_user_profile_photo(TlReader *r) {
    2746            3 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2747            3 :     uint32_t crc = tl_read_uint32(r);
    2748            3 :     if (crc == CRC_userProfilePhotoEmpty) return 0;
    2749            2 :     if (crc != CRC_userProfilePhoto) {
    2750            1 :         logger_log(LOG_WARN, "tl_skip_user_profile_photo: unknown 0x%08x", crc);
    2751            1 :         return -1;
    2752              :     }
    2753            1 :     if (r->len - r->pos < 4) return -1;
    2754            1 :     uint32_t flags = tl_read_uint32(r);
    2755              :     /* photo_id:long */
    2756            1 :     if (r->len - r->pos < 8) return -1;
    2757            1 :     tl_read_int64(r);
    2758            1 :     if (flags & (1u << 1)) {
    2759            0 :         if (tl_skip_string(r) != 0) return -1; /* stripped_thumb:bytes */
    2760              :     }
    2761              :     /* dc_id:int */
    2762            1 :     if (r->len - r->pos < 4) return -1;
    2763            1 :     tl_read_int32(r);
    2764            1 :     return 0;
    2765              : }
    2766              : 
    2767              : /* ---- UserStatus ---- */
    2768            7 : int tl_skip_user_status(TlReader *r) {
    2769            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2770            7 :     uint32_t crc = tl_read_uint32(r);
    2771            7 :     switch (crc) {
    2772            1 :     case CRC_userStatusEmpty:
    2773            1 :         return 0;
    2774            5 :     case CRC_userStatusOnline:
    2775              :     case CRC_userStatusOffline:
    2776              :     case CRC_userStatusRecently:
    2777              :     case CRC_userStatusLastWeek:
    2778              :     case CRC_userStatusLastMonth:
    2779            5 :         if (r->len - r->pos < 4) return -1;
    2780            5 :         tl_read_int32(r);
    2781            5 :         return 0;
    2782            1 :     default:
    2783            1 :         logger_log(LOG_WARN, "tl_skip_user_status: unknown 0x%08x", crc);
    2784            1 :         return -1;
    2785              :     }
    2786              : }
    2787              : 
    2788              : /* ---- Vector<RestrictionReason> ----
    2789              :  * restrictionReason#d072acb4 platform:string reason:string text:string
    2790              :  */
    2791            5 : int tl_skip_restriction_reason_vector(TlReader *r) {
    2792            5 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
    2793            5 :     uint32_t vec_crc = tl_read_uint32(r);
    2794            5 :     if (vec_crc != TL_vector) {
    2795            0 :         logger_log(LOG_WARN,
    2796              :                    "tl_skip_restriction_reason_vector: expected vector 0x%08x",
    2797              :                    vec_crc);
    2798            0 :         return -1;
    2799              :     }
    2800            5 :     uint32_t count = tl_read_uint32(r);
    2801           10 :     for (uint32_t i = 0; i < count; i++) {
    2802            6 :         if (r->len - r->pos < 4) return -1;
    2803            6 :         uint32_t crc = tl_read_uint32(r);
    2804            6 :         if (crc != CRC_restrictionReason) {
    2805            1 :             logger_log(LOG_WARN,
    2806              :                        "tl_skip_restriction_reason_vector: bad entry 0x%08x",
    2807              :                        crc);
    2808            1 :             return -1;
    2809              :         }
    2810            5 :         if (tl_skip_string(r) != 0) return -1; /* platform */
    2811            5 :         if (tl_skip_string(r) != 0) return -1; /* reason */
    2812            5 :         if (tl_skip_string(r) != 0) return -1; /* text */
    2813              :     }
    2814            4 :     return 0;
    2815              : }
    2816              : 
    2817              : /* ---- Vector<Username> ----
    2818              :  * username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string
    2819              :  */
    2820            2 : int tl_skip_username_vector(TlReader *r) {
    2821            2 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
    2822            2 :     uint32_t vec_crc = tl_read_uint32(r);
    2823            2 :     if (vec_crc != TL_vector) {
    2824            0 :         logger_log(LOG_WARN,
    2825              :                    "tl_skip_username_vector: expected vector 0x%08x", vec_crc);
    2826            0 :         return -1;
    2827              :     }
    2828            2 :     uint32_t count = tl_read_uint32(r);
    2829            4 :     for (uint32_t i = 0; i < count; i++) {
    2830            3 :         if (r->len - r->pos < 8) return -1;
    2831            3 :         uint32_t crc = tl_read_uint32(r);
    2832            3 :         if (crc != CRC_username) {
    2833            1 :             logger_log(LOG_WARN,
    2834              :                        "tl_skip_username_vector: bad entry 0x%08x", crc);
    2835            1 :             return -1;
    2836              :         }
    2837            2 :         tl_read_uint32(r); /* flags — only 'true' bits, no data */
    2838            2 :         if (tl_skip_string(r) != 0) return -1;
    2839              :     }
    2840            1 :     return 0;
    2841              : }
    2842              : 
    2843              : /* ---- PeerColor ----
    2844              :  * peerColor#b54b5acf flags:# color:flags.0?int background_emoji_id:flags.1?long
    2845              :  */
    2846            2 : int tl_skip_peer_color(TlReader *r) {
    2847            2 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
    2848            2 :     uint32_t crc = tl_read_uint32(r);
    2849            2 :     if (crc != CRC_peerColor) {
    2850            1 :         logger_log(LOG_WARN, "tl_skip_peer_color: unknown 0x%08x", crc);
    2851            1 :         return -1;
    2852              :     }
    2853            1 :     uint32_t flags = tl_read_uint32(r);
    2854            1 :     if (flags & (1u << 0)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    2855            1 :     if (flags & (1u << 1)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
    2856            1 :     return 0;
    2857              : }
    2858              : 
    2859              : /* ---- EmojiStatus ---- */
    2860            4 : int tl_skip_emoji_status(TlReader *r) {
    2861            4 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2862            4 :     uint32_t crc = tl_read_uint32(r);
    2863            4 :     switch (crc) {
    2864            1 :     case CRC_emojiStatusEmpty:
    2865            1 :         return 0;
    2866            1 :     case CRC_emojiStatus:
    2867            1 :         if (r->len - r->pos < 8) return -1;
    2868            1 :         tl_read_int64(r);
    2869            1 :         return 0;
    2870            1 :     case CRC_emojiStatusUntil:
    2871            1 :         if (r->len - r->pos < 12) return -1;
    2872            1 :         tl_read_int64(r); tl_read_int32(r);
    2873            1 :         return 0;
    2874            1 :     default:
    2875            1 :         logger_log(LOG_WARN, "tl_skip_emoji_status: unknown 0x%08x", crc);
    2876            1 :         return -1;
    2877              :     }
    2878              : }
    2879              : 
    2880              : /* ChatAdminRights#5fb224d5 flags:# — single uint32. */
    2881            3 : static int skip_chat_admin_rights(TlReader *r) {
    2882            3 :     if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
    2883            3 :     uint32_t crc = tl_read_uint32(r);
    2884            3 :     if (crc != CRC_chatAdminRights) {
    2885            1 :         logger_log(LOG_WARN, "skip_chat_admin_rights: unknown 0x%08x", crc);
    2886            1 :         return -1;
    2887              :     }
    2888            2 :     tl_read_uint32(r); /* flags */
    2889            2 :     return 0;
    2890              : }
    2891              : 
    2892              : /* ChatBannedRights#9f120418 flags:# until_date:int. */
    2893            4 : static int skip_chat_banned_rights(TlReader *r) {
    2894            4 :     if (!tl_reader_ok(r) || r->len - r->pos < 12) return -1;
    2895            4 :     uint32_t crc = tl_read_uint32(r);
    2896            4 :     if (crc != CRC_chatBannedRights) {
    2897            1 :         logger_log(LOG_WARN, "skip_chat_banned_rights: unknown 0x%08x", crc);
    2898            1 :         return -1;
    2899              :     }
    2900            3 :     tl_read_uint32(r); /* flags */
    2901            3 :     tl_read_int32(r);  /* until_date */
    2902            3 :     return 0;
    2903              : }
    2904              : 
    2905              : /* ---- Core Chat extractor ----
    2906              :  *
    2907              :  * Covers:
    2908              :  *   chatEmpty#29562865 id:long
    2909              :  *   chat#41cbf256                       (see header comment)
    2910              :  *   chatForbidden#6592a1a7 id:long title:string
    2911              :  *   channel#0aadfc8f                    (layer 170+)
    2912              :  *   channelForbidden#17d493d5
    2913              :  *
    2914              :  * If `out` is non-NULL, populates (id, title). chatEmpty leaves title empty.
    2915              :  */
    2916            8 : static int extract_chat_inner(TlReader *r, ChatSummary *out) {
    2917            8 :     if (out) {
    2918            4 :         out->id = 0;
    2919            4 :         out->access_hash = 0;
    2920            4 :         out->have_access_hash = 0;
    2921            4 :         out->title[0] = '\0';
    2922              :     }
    2923              : 
    2924            8 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    2925            8 :     uint32_t crc = tl_read_uint32(r);
    2926              : 
    2927            8 :     if (crc == TL_chatEmpty) {
    2928            2 :         if (r->len - r->pos < 8) return -1;
    2929            2 :         int64_t id = tl_read_int64(r);
    2930            2 :         if (out) out->id = id;
    2931            2 :         return 0;
    2932              :     }
    2933              : 
    2934            6 :     if (crc == TL_chatForbidden) {
    2935            1 :         if (r->len - r->pos < 8) return -1;
    2936            1 :         int64_t id = tl_read_int64(r);
    2937            1 :         if (out) out->id = id;
    2938            1 :         if (out) {
    2939            1 :             if (read_string_into(r, out->title, sizeof(out->title)) != 0)
    2940            0 :                 return -1;
    2941              :         } else {
    2942            0 :             if (tl_skip_string(r) != 0) return -1;
    2943              :         }
    2944            1 :         return 0;
    2945              :     }
    2946              : 
    2947            5 :     if (crc == TL_chat) {
    2948            3 :         if (r->len - r->pos < 4) return -1;
    2949            3 :         uint32_t flags = tl_read_uint32(r);
    2950              :         /* migrated_to:flags.6?InputChannel — too complex to dispatch here. */
    2951            3 :         if (flags & (1u << 6)) {
    2952            0 :             logger_log(LOG_WARN,
    2953              :                        "extract_chat: chat with migrated_to not supported");
    2954            0 :             return -1;
    2955              :         }
    2956              :         /* id:long title:string photo:ChatPhoto */
    2957            3 :         if (r->len - r->pos < 8) return -1;
    2958            3 :         int64_t id = tl_read_int64(r);
    2959            3 :         if (out) out->id = id;
    2960            3 :         if (out) {
    2961            1 :             if (read_string_into(r, out->title, sizeof(out->title)) != 0)
    2962            0 :                 return -1;
    2963              :         } else {
    2964            2 :             if (tl_skip_string(r) != 0) return -1;
    2965              :         }
    2966            3 :         if (tl_skip_chat_photo(r) != 0) return -1;
    2967              :         /* participants_count:int date:int version:int */
    2968            3 :         if (r->len - r->pos < 12) return -1;
    2969            3 :         tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
    2970            3 :         if (flags & (1u << 14)) {
    2971            2 :             if (skip_chat_admin_rights(r) != 0) return -1;
    2972              :         }
    2973            2 :         if (flags & (1u << 18)) {
    2974            2 :             if (skip_chat_banned_rights(r) != 0) return -1;
    2975              :         }
    2976            1 :         return 0;
    2977              :     }
    2978              : 
    2979            2 :     if (crc == TL_channelForbidden) {
    2980            0 :         if (r->len - r->pos < 4) return -1;
    2981            0 :         uint32_t flags = tl_read_uint32(r);
    2982            0 :         if (r->len - r->pos < 16) return -1;
    2983            0 :         int64_t id = tl_read_int64(r);
    2984            0 :         if (out) out->id = id;
    2985            0 :         int64_t access_hash = tl_read_int64(r);
    2986            0 :         if (out) {
    2987            0 :             out->access_hash = access_hash;
    2988            0 :             out->have_access_hash = 1;
    2989              :         }
    2990            0 :         if (out) {
    2991            0 :             if (read_string_into(r, out->title, sizeof(out->title)) != 0)
    2992            0 :                 return -1;
    2993              :         } else {
    2994            0 :             if (tl_skip_string(r) != 0) return -1;
    2995              :         }
    2996            0 :         if (flags & (1u << 16)) {
    2997            0 :             if (r->len - r->pos < 4) return -1;
    2998            0 :             tl_read_int32(r); /* until_date */
    2999              :         }
    3000            0 :         return 0;
    3001              :     }
    3002              : 
    3003            2 :     if (crc == TL_channel) {
    3004            1 :         if (r->len - r->pos < 8) return -1;
    3005            1 :         uint32_t flags  = tl_read_uint32(r);
    3006            1 :         uint32_t flags2 = tl_read_uint32(r);
    3007              :         /* Known flags2 bits: 0,4,7,8,9,10,11. Reject any others. */
    3008            1 :         const uint32_t flags2_known =
    3009              :             (1u << 0) | (1u << 4) | (1u << 7) | (1u << 8) |
    3010              :             (1u << 9) | (1u << 10) | (1u << 11);
    3011            1 :         if (flags2 & ~flags2_known) {
    3012            0 :             logger_log(LOG_WARN,
    3013              :                        "extract_chat: channel with unknown flags2 bits 0x%x",
    3014              :                        flags2);
    3015            0 :             return -1;
    3016              :         }
    3017            1 :         if (r->len - r->pos < 8) return -1;
    3018            1 :         int64_t id = tl_read_int64(r);
    3019            1 :         if (out) out->id = id;
    3020            1 :         if (flags & (1u << 13)) {
    3021            0 :             if (r->len - r->pos < 8) return -1;
    3022            0 :             int64_t access_hash = tl_read_int64(r);
    3023            0 :             if (out) {
    3024            0 :                 out->access_hash = access_hash;
    3025            0 :                 out->have_access_hash = 1;
    3026              :             }
    3027              :         }
    3028            1 :         if (out) {
    3029            1 :             if (read_string_into(r, out->title, sizeof(out->title)) != 0)
    3030            0 :                 return -1;
    3031              :         } else {
    3032            0 :             if (tl_skip_string(r) != 0) return -1;
    3033              :         }
    3034            1 :         if (flags & (1u << 6)) {
    3035            0 :             if (tl_skip_string(r) != 0) return -1; /* username */
    3036              :         }
    3037            1 :         if (tl_skip_chat_photo(r) != 0) return -1;
    3038              :         /* date:int */
    3039            1 :         if (r->len - r->pos < 4) return -1;
    3040            1 :         tl_read_int32(r);
    3041            1 :         if (flags & (1u << 9)) {
    3042            0 :             if (tl_skip_restriction_reason_vector(r) != 0) return -1;
    3043              :         }
    3044            1 :         if (flags & (1u << 14)) {
    3045            1 :             if (skip_chat_admin_rights(r) != 0) return -1;
    3046              :         }
    3047            1 :         if (flags & (1u << 15)) {
    3048            1 :             if (skip_chat_banned_rights(r) != 0) return -1;
    3049              :         }
    3050            1 :         if (flags & (1u << 18)) {
    3051            1 :             if (skip_chat_banned_rights(r) != 0) return -1;
    3052              :         }
    3053            1 :         if (flags & (1u << 17)) {
    3054            0 :             if (r->len - r->pos < 4) return -1;
    3055            0 :             tl_read_int32(r); /* participants_count */
    3056              :         }
    3057            1 :         if (flags2 & (1u << 0)) {
    3058            0 :             if (tl_skip_username_vector(r) != 0) return -1;
    3059              :         }
    3060            1 :         if (flags2 & (1u << 4)) {
    3061            0 :             if (r->len - r->pos < 4) return -1;
    3062            0 :             tl_read_int32(r); /* stories_max_id */
    3063              :         }
    3064            1 :         if (flags2 & (1u << 7)) {
    3065            0 :             if (tl_skip_peer_color(r) != 0) return -1;
    3066              :         }
    3067            1 :         if (flags2 & (1u << 8)) {
    3068            0 :             if (tl_skip_peer_color(r) != 0) return -1;
    3069              :         }
    3070            1 :         if (flags2 & (1u << 9)) {
    3071            0 :             if (tl_skip_emoji_status(r) != 0) return -1;
    3072              :         }
    3073            1 :         if (flags2 & (1u << 10)) {
    3074            0 :             if (r->len - r->pos < 4) return -1;
    3075            0 :             tl_read_int32(r); /* level */
    3076              :         }
    3077            1 :         if (flags2 & (1u << 11)) {
    3078            0 :             if (r->len - r->pos < 4) return -1;
    3079            0 :             tl_read_int32(r); /* subscription_until_date */
    3080              :         }
    3081            1 :         return 0;
    3082              :     }
    3083              : 
    3084            1 :     logger_log(LOG_WARN, "tl_skip_chat: unknown Chat variant 0x%08x", crc);
    3085            1 :     return -1;
    3086              : }
    3087              : 
    3088            2 : int tl_skip_chat(TlReader *r) {
    3089            2 :     return extract_chat_inner(r, NULL);
    3090              : }
    3091              : 
    3092            6 : int tl_extract_chat(TlReader *r, ChatSummary *out) {
    3093            6 :     if (!out) return extract_chat_inner(r, NULL);
    3094            4 :     return extract_chat_inner(r, out);
    3095              : }
    3096              : 
    3097              : /* ---- Core User extractor ----
    3098              :  *
    3099              :  * Covers:
    3100              :  *   userEmpty#d3bc4b7a id:long
    3101              :  *   user#3ff6ecb0 (layer 170+)
    3102              :  *
    3103              :  * If `out` is non-NULL, populates (id, name, username). `name` is
    3104              :  * `first_name` and `last_name` joined with a single space where both are
    3105              :  * present.
    3106              :  */
    3107            7 : static int extract_user_inner(TlReader *r, UserSummary *out) {
    3108            7 :     if (out) {
    3109            6 :         out->id = 0;
    3110            6 :         out->access_hash = 0;
    3111            6 :         out->have_access_hash = 0;
    3112            6 :         out->name[0] = '\0';
    3113            6 :         out->username[0] = '\0';
    3114              :     }
    3115              : 
    3116            7 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    3117            7 :     uint32_t crc = tl_read_uint32(r);
    3118              : 
    3119            7 :     if (crc == TL_userEmpty) {
    3120            1 :         if (r->len - r->pos < 8) return -1;
    3121            1 :         int64_t id = tl_read_int64(r);
    3122            1 :         if (out) out->id = id;
    3123            1 :         return 0;
    3124              :     }
    3125              : 
    3126            6 :     if (crc != TL_user) {
    3127            1 :         logger_log(LOG_WARN, "tl_skip_user: unknown User variant 0x%08x", crc);
    3128            1 :         return -1;
    3129              :     }
    3130              : 
    3131            5 :     if (r->len - r->pos < 8) return -1;
    3132            5 :     uint32_t flags  = tl_read_uint32(r);
    3133            5 :     uint32_t flags2 = tl_read_uint32(r);
    3134              :     /* Known flags2 bits go up to 13. Reject any bits above that. */
    3135            5 :     if (flags2 & ~((1u << 14) - 1u)) {
    3136            0 :         logger_log(LOG_WARN,
    3137              :                    "tl_skip_user: unknown flags2 bits 0x%x", flags2);
    3138            0 :         return -1;
    3139              :     }
    3140            5 :     if (r->len - r->pos < 8) return -1;
    3141            5 :     int64_t id = tl_read_int64(r);
    3142            5 :     if (out) out->id = id;
    3143              : 
    3144            5 :     if (flags & (1u << 0)) {
    3145            5 :         if (r->len - r->pos < 8) return -1;
    3146            5 :         int64_t access_hash = tl_read_int64(r);
    3147            5 :         if (out) {
    3148            5 :             out->access_hash = access_hash;
    3149            5 :             out->have_access_hash = 1;
    3150              :         }
    3151              :     }
    3152              :     /* first_name */
    3153            5 :     if (flags & (1u << 1)) {
    3154            4 :         if (out) {
    3155            4 :             char first[96] = {0};
    3156            4 :             if (read_string_into(r, first, sizeof(first)) != 0) return -1;
    3157            4 :             str_append(out->name, sizeof(out->name), first);
    3158              :         } else {
    3159            0 :             if (tl_skip_string(r) != 0) return -1;
    3160              :         }
    3161              :     }
    3162              :     /* last_name */
    3163            5 :     if (flags & (1u << 2)) {
    3164            1 :         if (out) {
    3165            1 :             char last[96] = {0};
    3166            1 :             if (read_string_into(r, last, sizeof(last)) != 0) return -1;
    3167            1 :             if (last[0] != '\0') {
    3168            1 :                 if (out->name[0] != '\0') {
    3169            1 :                     str_append(out->name, sizeof(out->name), " ");
    3170              :                 }
    3171            1 :                 str_append(out->name, sizeof(out->name), last);
    3172              :             }
    3173              :         } else {
    3174            0 :             if (tl_skip_string(r) != 0) return -1;
    3175              :         }
    3176              :     }
    3177              :     /* username */
    3178            5 :     if (flags & (1u << 3)) {
    3179            1 :         if (out) {
    3180            1 :             if (read_string_into(r, out->username, sizeof(out->username)) != 0)
    3181            0 :                 return -1;
    3182              :         } else {
    3183            0 :             if (tl_skip_string(r) != 0) return -1;
    3184              :         }
    3185              :     }
    3186              :     /* phone */
    3187            5 :     if (flags & (1u << 4)) {
    3188            1 :         if (tl_skip_string(r) != 0) return -1;
    3189              :     }
    3190              :     /* photo */
    3191            5 :     if (flags & (1u << 5)) {
    3192            0 :         if (tl_skip_user_profile_photo(r) != 0) return -1;
    3193              :     }
    3194              :     /* status */
    3195            5 :     if (flags & (1u << 6)) {
    3196            0 :         if (tl_skip_user_status(r) != 0) return -1;
    3197              :     }
    3198              :     /* bot_info_version */
    3199            5 :     if (flags & (1u << 14)) {
    3200            0 :         if (r->len - r->pos < 4) return -1;
    3201            0 :         tl_read_int32(r);
    3202              :     }
    3203              :     /* restriction_reason */
    3204            5 :     if (flags & (1u << 18)) {
    3205            0 :         if (tl_skip_restriction_reason_vector(r) != 0) return -1;
    3206              :     }
    3207              :     /* bot_inline_placeholder */
    3208            5 :     if (flags & (1u << 19)) {
    3209            0 :         if (tl_skip_string(r) != 0) return -1;
    3210              :     }
    3211              :     /* lang_code */
    3212            5 :     if (flags & (1u << 22)) {
    3213            0 :         if (tl_skip_string(r) != 0) return -1;
    3214              :     }
    3215              :     /* emoji_status */
    3216            5 :     if (flags & (1u << 30)) {
    3217            0 :         if (tl_skip_emoji_status(r) != 0) return -1;
    3218              :     }
    3219              :     /* usernames */
    3220            5 :     if (flags2 & (1u << 0)) {
    3221            0 :         if (tl_skip_username_vector(r) != 0) return -1;
    3222              :     }
    3223              :     /* stories_max_id */
    3224            5 :     if (flags2 & (1u << 5)) {
    3225            0 :         if (r->len - r->pos < 4) return -1;
    3226            0 :         tl_read_int32(r);
    3227              :     }
    3228              :     /* color */
    3229            5 :     if (flags2 & (1u << 8)) {
    3230            0 :         if (tl_skip_peer_color(r) != 0) return -1;
    3231              :     }
    3232              :     /* profile_color */
    3233            5 :     if (flags2 & (1u << 9)) {
    3234            0 :         if (tl_skip_peer_color(r) != 0) return -1;
    3235              :     }
    3236              :     /* bot_active_users */
    3237            5 :     if (flags2 & (1u << 12)) {
    3238            0 :         if (r->len - r->pos < 4) return -1;
    3239            0 :         tl_read_int32(r);
    3240              :     }
    3241            5 :     return 0;
    3242              : }
    3243              : 
    3244            1 : int tl_skip_user(TlReader *r) {
    3245            1 :     return extract_user_inner(r, NULL);
    3246              : }
    3247              : 
    3248            6 : int tl_extract_user(TlReader *r, UserSummary *out) {
    3249            6 :     if (!out) return extract_user_inner(r, NULL);
    3250            6 :     return extract_user_inner(r, out);
    3251              : }
    3252              : 
    3253              : /* ---- tl_skip_message ----
    3254              :  * Mirrors the parser in domain/read/history.c but discards all output.
    3255              :  * Returns 0 if the cursor is safely past the whole Message, -1 if we
    3256              :  * had to bail (cursor possibly mid-object).
    3257              :  */
    3258           14 : int tl_skip_message(TlReader *r) {
    3259           14 :     if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
    3260           13 :     uint32_t crc = tl_read_uint32(r);
    3261              : 
    3262           13 :     if (crc == TL_messageEmpty) {
    3263              :         /* flags:# id:int peer_id:flags.0?Peer */
    3264            2 :         if (r->len - r->pos < 8) return -1;
    3265            2 :         uint32_t flags = tl_read_uint32(r);
    3266            2 :         tl_read_int32(r); /* id */
    3267            2 :         if (flags & 1u) {
    3268            0 :             if (tl_skip_peer(r) != 0) return -1;
    3269              :         }
    3270            2 :         return 0;
    3271              :     }
    3272              : 
    3273           11 :     if (crc == TL_messageService) {
    3274              :         /* action-heavy; we do not implement skipping yet. */
    3275            1 :         return -1;
    3276              :     }
    3277              : 
    3278           10 :     if (crc != TL_message) {
    3279            1 :         logger_log(LOG_WARN, "tl_skip_message: unknown 0x%08x", crc);
    3280            1 :         return -1;
    3281              :     }
    3282              : 
    3283            9 :     if (r->len - r->pos < 12) return -1;
    3284            8 :     uint32_t flags  = tl_read_uint32(r);
    3285            8 :     uint32_t flags2 = tl_read_uint32(r);
    3286            8 :     tl_read_int32(r); /* id */
    3287              : 
    3288            8 :     if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) return -1; /* from_id */
    3289            8 :     if (tl_skip_peer(r) != 0) return -1;                         /* peer_id */
    3290            8 :     if (flags & (1u << 28)) if (tl_skip_peer(r) != 0) return -1; /* saved_peer_id */
    3291            8 :     if (flags & (1u << 2))  if (tl_skip_message_fwd_header(r) != 0) return -1;
    3292            8 :     if (flags & (1u << 11)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
    3293            8 :     if (flags2 & (1u << 0)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
    3294            8 :     if (flags & (1u << 3))  if (tl_skip_message_reply_header(r) != 0) return -1;
    3295              : 
    3296            8 :     if (r->len - r->pos < 4) return -1;
    3297            8 :     tl_read_int32(r); /* date */
    3298            8 :     if (tl_skip_string(r) != 0) return -1; /* message */
    3299              : 
    3300            8 :     if (flags & (1u << 9)) if (tl_skip_message_media(r) != 0) return -1;
    3301              : 
    3302            7 :     if (flags & (1u << 6))  if (tl_skip_reply_markup(r) != 0) return -1;
    3303            6 :     if (flags & (1u << 7))  if (tl_skip_message_entities_vector(r) != 0) return -1;
    3304            6 :     if (flags & (1u << 10)) { if (r->len - r->pos < 8) return -1; tl_read_int32(r); tl_read_int32(r); }
    3305            6 :     if (flags & (1u << 23)) if (tl_skip_message_replies(r) != 0) return -1;
    3306            6 :     if (flags & (1u << 15)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    3307            6 :     if (flags & (1u << 16)) if (tl_skip_string(r) != 0) return -1;
    3308            6 :     if (flags & (1u << 17)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
    3309            6 :     if (flags & (1u << 20)) if (tl_skip_message_reactions(r) != 0) return -1;
    3310            6 :     if (flags & (1u << 22)) if (tl_skip_restriction_reason_vector(r) != 0) return -1;
    3311            6 :     if (flags & (1u << 25)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    3312            6 :     if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
    3313            6 :     if (flags2 & (1u << 2))  { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
    3314            6 :     if (flags2 & (1u << 3))  if (tl_skip_factcheck(r) != 0) return -1;
    3315            6 :     return 0;
    3316              : }
        

Generated by: LCOV version 2.0-1