LCOV - code coverage report
Current view: top level - src/domain/read - history.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 85.6 % 354 303
Test Date: 2026-05-06 13:17:08 Functions: 100.0 % 10 10

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file domain/read/history.c
       6              :  * @brief Minimal messages.getHistory parser (US-06 v1).
       7              :  */
       8              : 
       9              : #include "domain/read/history.h"
      10              : 
      11              : #include "tl_serial.h"
      12              : #include "tl_registry.h"
      13              : #include "tl_skip.h"
      14              : #include "mtproto_rpc.h"
      15              : #include "logger.h"
      16              : #include "raii.h"
      17              : 
      18              : #include <stdlib.h>
      19              : #include <string.h>
      20              : 
      21              : #define CRC_messages_getHistory 0x4423e6c5u
      22              : #define CRC_inputPeerSelf_local TL_inputPeerSelf /* alias for readability */
      23              : 
      24              : /* ---- MessageAction CRCs (US-29) ----
      25              :  * Stable across recent TL layers (170+). See docs/userstory/US-29 for
      26              :  * the rendering policy. Unknown action CRCs fall through to a labelled
      27              :  * "[service action 0x%08x]" string to keep group histories dense even
      28              :  * when Telegram adds new variants. */
      29              : #define CRC_messageActionEmpty                0xb6aef7b0u
      30              : #define CRC_messageActionChatCreate           0xbd47cbadu
      31              : #define CRC_messageActionChatEditTitle        0xb5a1ce5au
      32              : #define CRC_messageActionChatEditPhoto        0x7fcb13a8u
      33              : #define CRC_messageActionChatDeletePhoto      0x95e3fbefu
      34              : #define CRC_messageActionChatAddUser          0x15cefd00u
      35              : #define CRC_messageActionChatDeleteUser       0xa43f30ccu
      36              : #define CRC_messageActionChatJoinedByLink     0x031224c3u
      37              : #define CRC_messageActionChannelCreate        0x95d2ac92u
      38              : #define CRC_messageActionChatMigrateTo        0xe1037f92u
      39              : #define CRC_messageActionChannelMigrateFrom   0xea3948e9u
      40              : #define CRC_messageActionPinMessage           0x94bd38edu
      41              : #define CRC_messageActionHistoryClear         0x9fbab604u
      42              : #define CRC_messageActionPhoneCall            0x80e11a7fu
      43              : #define CRC_messageActionScreenshotTaken      0x4792929bu
      44              : #define CRC_messageActionCustomAction         0xfae69f56u
      45              : #define CRC_messageActionGroupCall            0x7a0d7f42u
      46              : #define CRC_messageActionGroupCallScheduled   0xb3a07661u
      47              : #define CRC_messageActionInviteToGroupCall    0x502f92f7u
      48              : 
      49              : /* Phone-call discard reason CRCs (flags.0 on PhoneCall). */
      50              : #define CRC_phoneCallDiscardReasonMissed      0x85e42301u
      51              : #define CRC_phoneCallDiscardReasonDisconnect  0xe095c1a0u
      52              : #define CRC_phoneCallDiscardReasonHangup      0x57adc690u
      53              : #define CRC_phoneCallDiscardReasonBusy        0xfaf7e8c9u
      54              : 
      55           64 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
      56           64 :     switch (p->kind) {
      57           56 :     case HISTORY_PEER_SELF:
      58           56 :         tl_write_uint32(w, TL_inputPeerSelf);
      59           56 :         return 0;
      60            4 :     case HISTORY_PEER_USER:
      61            4 :         tl_write_uint32(w, TL_inputPeerUser);
      62            4 :         tl_write_int64 (w, p->peer_id);
      63            4 :         tl_write_int64 (w, p->access_hash);
      64            4 :         return 0;
      65            0 :     case HISTORY_PEER_CHAT:
      66            0 :         tl_write_uint32(w, TL_inputPeerChat);
      67            0 :         tl_write_int64 (w, p->peer_id);
      68            0 :         return 0;
      69            4 :     case HISTORY_PEER_CHANNEL:
      70            4 :         tl_write_uint32(w, TL_inputPeerChannel);
      71            4 :         tl_write_int64 (w, p->peer_id);
      72            4 :         tl_write_int64 (w, p->access_hash);
      73            4 :         return 0;
      74            0 :     default:
      75            0 :         return -1;
      76              :     }
      77              : }
      78              : 
      79              : /* Field bit positions used to decide whether to skip the first-stage
      80              :  * Message prefix (before we abort and move on to the next message).
      81              :  * These correspond to layer 170+ but are stable across recent layers. */
      82              : #define MSG_FLAG_OUT              (1u << 1)
      83              : #define MSG_FLAG_HAS_FROM_ID      (1u << 8)
      84              : 
      85              : /* All trailer flags have skippers; complex=1 only on parse errors. */
      86              : 
      87           64 : static int build_request(const HistoryPeer *peer, int32_t offset_id, int limit,
      88              :                           uint8_t *buf, size_t cap, size_t *out_len) {
      89              :     TlWriter w;
      90           64 :     tl_writer_init(&w);
      91           64 :     tl_write_uint32(&w, CRC_messages_getHistory);
      92           64 :     if (write_input_peer(&w, peer) != 0) {
      93            0 :         tl_writer_free(&w);
      94            0 :         return -1;
      95              :     }
      96           64 :     tl_write_int32 (&w, offset_id);
      97           64 :     tl_write_int32 (&w, 0);  /* offset_date */
      98           64 :     tl_write_int32 (&w, 0);  /* add_offset */
      99           64 :     tl_write_int32 (&w, limit);
     100           64 :     tl_write_int32 (&w, 0);  /* max_id */
     101           64 :     tl_write_int32 (&w, 0);  /* min_id */
     102           64 :     tl_write_int64 (&w, 0);  /* hash */
     103              : 
     104           64 :     int rc = -1;
     105           64 :     if (w.len <= cap) {
     106           64 :         memcpy(buf, w.data, w.len);
     107           64 :         *out_len = w.len;
     108           64 :         rc = 0;
     109              :     }
     110           64 :     tl_writer_free(&w);
     111           64 :     return rc;
     112              : }
     113              : 
     114              : /* Copy src into out->text with HISTORY_TEXT_MAX truncation. */
     115           22 : static void set_text(HistoryEntry *out, const char *src) {
     116           22 :     if (!src) { out->text[0] = '\0'; return; }
     117           22 :     size_t n = strlen(src);
     118           22 :     if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
     119           22 :     memcpy(out->text, src, n);
     120           22 :     out->text[n] = '\0';
     121              : }
     122              : 
     123              : /* Read and discard a Vector<long>. Returns 0 on success. */
     124            2 : static int skip_long_vector(TlReader *r) {
     125            2 :     if (r->len - r->pos < 8) return -1;
     126            2 :     if (tl_read_uint32(r) != TL_vector) return -1;
     127            2 :     uint32_t n = tl_read_uint32(r);
     128            2 :     if (n > 4096) return -1; /* sanity cap */
     129            2 :     if (r->len - r->pos < (size_t)n * 8) return -1;
     130            5 :     for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
     131            2 :     return 0;
     132              : }
     133              : 
     134              : /* Discard the remaining Vector<long>; return the first element (or 0). */
     135            1 : static int read_long_vector_first(TlReader *r, int64_t *first) {
     136            1 :     if (r->len - r->pos < 8) return -1;
     137            1 :     if (tl_read_uint32(r) != TL_vector) return -1;
     138            1 :     uint32_t n = tl_read_uint32(r);
     139            1 :     if (n > 4096) return -1;
     140            1 :     if (r->len - r->pos < (size_t)n * 8) return -1;
     141            1 :     if (first) *first = 0;
     142            2 :     for (uint32_t i = 0; i < n; i++) {
     143            1 :         int64_t v = tl_read_int64(r);
     144            1 :         if (i == 0 && first) *first = v;
     145              :     }
     146            1 :     return 0;
     147              : }
     148              : 
     149              : /* Read a MessageAction and render a human-readable string into out->text.
     150              :  * Sets out->is_service=1 in all cases. Returns 0 if the action was fully
     151              :  * consumed (cursor past), -1 on truncation / unknown substructure. */
     152           22 : static int parse_service_action(TlReader *r, HistoryEntry *out) {
     153           22 :     out->is_service = 1;
     154           22 :     if (r->len - r->pos < 4) { set_text(out, "[service action truncated]"); return -1; }
     155           22 :     uint32_t acrc = tl_read_uint32(r);
     156              :     char buf[HISTORY_TEXT_MAX];
     157              : 
     158           22 :     switch (acrc) {
     159            1 :     case CRC_messageActionEmpty:
     160            1 :         set_text(out, "");
     161            1 :         return 0;
     162            1 :     case CRC_messageActionChatCreate: {
     163              :         /* title:string users:Vector<long> */
     164            2 :         RAII_STRING char *title = tl_read_string(r);
     165            1 :         if (!title) return -1;
     166            1 :         if (skip_long_vector(r) != 0) return -1;
     167            1 :         snprintf(buf, sizeof(buf), "created group '%s'", title);
     168            1 :         set_text(out, buf);
     169            1 :         return 0;
     170              :     }
     171            1 :     case CRC_messageActionChatEditTitle: {
     172              :         /* title:string */
     173            2 :         RAII_STRING char *title = tl_read_string(r);
     174            1 :         if (!title) return -1;
     175            1 :         snprintf(buf, sizeof(buf), "changed title to '%s'", title);
     176            1 :         set_text(out, buf);
     177            1 :         return 0;
     178              :     }
     179            1 :     case CRC_messageActionChatEditPhoto: {
     180              :         /* photo:Photo — skip via tl_skip_photo */
     181            1 :         if (tl_skip_photo(r) != 0) { set_text(out, "changed group photo"); return -1; }
     182            1 :         set_text(out, "changed group photo");
     183            1 :         return 0;
     184              :     }
     185            1 :     case CRC_messageActionChatDeletePhoto:
     186            1 :         set_text(out, "removed group photo");
     187            1 :         return 0;
     188            1 :     case CRC_messageActionChatAddUser: {
     189              :         /* users:Vector<long> */
     190            1 :         int64_t first = 0;
     191            1 :         if (read_long_vector_first(r, &first) != 0) return -1;
     192            1 :         snprintf(buf, sizeof(buf), "added @%lld", (long long)first);
     193            1 :         set_text(out, buf);
     194            1 :         return 0;
     195              :     }
     196            1 :     case CRC_messageActionChatDeleteUser: {
     197              :         /* user_id:long */
     198            1 :         if (r->len - r->pos < 8) return -1;
     199            1 :         int64_t uid = tl_read_int64(r);
     200            1 :         snprintf(buf, sizeof(buf), "removed @%lld", (long long)uid);
     201            1 :         set_text(out, buf);
     202            1 :         return 0;
     203              :     }
     204            1 :     case CRC_messageActionChatJoinedByLink: {
     205              :         /* inviter_id:long */
     206            1 :         if (r->len - r->pos < 8) return -1;
     207            1 :         (void)tl_read_int64(r);
     208            1 :         set_text(out, "joined via invite link");
     209            1 :         return 0;
     210              :     }
     211            1 :     case CRC_messageActionChannelCreate: {
     212              :         /* title:string */
     213            2 :         RAII_STRING char *title = tl_read_string(r);
     214            1 :         if (!title) return -1;
     215            1 :         snprintf(buf, sizeof(buf), "created channel '%s'", title);
     216            1 :         set_text(out, buf);
     217            1 :         return 0;
     218              :     }
     219            1 :     case CRC_messageActionChatMigrateTo: {
     220              :         /* channel_id:long */
     221            1 :         if (r->len - r->pos < 8) return -1;
     222            1 :         int64_t cid = tl_read_int64(r);
     223            1 :         snprintf(buf, sizeof(buf), "migrated to channel %lld", (long long)cid);
     224            1 :         set_text(out, buf);
     225            1 :         return 0;
     226              :     }
     227            1 :     case CRC_messageActionChannelMigrateFrom: {
     228              :         /* title:string chat_id:long */
     229            2 :         RAII_STRING char *title = tl_read_string(r);
     230            1 :         if (!title) return -1;
     231            1 :         if (r->len - r->pos < 8) return -1;
     232            1 :         int64_t cid = tl_read_int64(r);
     233            1 :         snprintf(buf, sizeof(buf), "migrated from group %lld '%s'",
     234              :                  (long long)cid, title);
     235            1 :         set_text(out, buf);
     236            1 :         return 0;
     237              :     }
     238            2 :     case CRC_messageActionPinMessage:
     239              :         /* Pinned message id is carried on the outer reply_to header, not
     240              :          * on the action itself. The outer parser captured it already. */
     241            2 :         if (out->text[0] == '\0') set_text(out, "pinned message");
     242            2 :         return 0;
     243            1 :     case CRC_messageActionHistoryClear:
     244            1 :         set_text(out, "history cleared");
     245            1 :         return 0;
     246            2 :     case CRC_messageActionPhoneCall: {
     247              :         /* flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int */
     248            2 :         if (r->len - r->pos < 4 + 8) return -1;
     249            2 :         uint32_t pflags = tl_read_uint32(r);
     250            2 :         (void)tl_read_int64(r); /* call_id */
     251            2 :         const char *reason = "completed";
     252            2 :         if (pflags & 1u) {
     253            2 :             if (r->len - r->pos < 4) return -1;
     254            2 :             uint32_t rcrc = tl_read_uint32(r);
     255            2 :             switch (rcrc) {
     256            1 :             case CRC_phoneCallDiscardReasonMissed:     reason = "missed"; break;
     257            0 :             case CRC_phoneCallDiscardReasonDisconnect: reason = "disconnected"; break;
     258            1 :             case CRC_phoneCallDiscardReasonHangup:     reason = "hangup"; break;
     259            0 :             case CRC_phoneCallDiscardReasonBusy:       reason = "busy"; break;
     260            0 :             default: break;
     261              :             }
     262              :         }
     263            2 :         int duration = 0;
     264            2 :         if (pflags & (1u << 1)) {
     265            1 :             if (r->len - r->pos < 4) return -1;
     266            1 :             duration = tl_read_int32(r);
     267              :         }
     268            2 :         snprintf(buf, sizeof(buf), "called (duration %ds, %s)", duration, reason);
     269            2 :         set_text(out, buf);
     270            2 :         return 0;
     271              :     }
     272            1 :     case CRC_messageActionScreenshotTaken:
     273            1 :         set_text(out, "took screenshot");
     274            1 :         return 0;
     275            1 :     case CRC_messageActionCustomAction: {
     276              :         /* message:string — the raw action label chosen by the server. */
     277            2 :         RAII_STRING char *msg = tl_read_string(r);
     278            1 :         if (!msg) return -1;
     279            1 :         set_text(out, msg);
     280            1 :         return 0;
     281              :     }
     282            1 :     case CRC_messageActionGroupCall: {
     283              :         /* flags:# call:InputGroupCall duration:flags.0?int
     284              :          * inputGroupCall#d8aa840f id:long access_hash:long */
     285            1 :         if (r->len - r->pos < 4) return -1;
     286            1 :         uint32_t gflags = tl_read_uint32(r);
     287            1 :         if (r->len - r->pos < 4 + 16) return -1;
     288            1 :         tl_read_uint32(r); /* inputGroupCall crc */
     289            1 :         tl_read_int64(r);  /* id */
     290            1 :         tl_read_int64(r);  /* access_hash */
     291            1 :         if (gflags & 1u) {
     292            0 :             if (r->len - r->pos < 4) return -1;
     293            0 :             tl_read_int32(r); /* duration */
     294              :         }
     295            1 :         set_text(out, "started video chat");
     296            1 :         return 0;
     297              :     }
     298            1 :     case CRC_messageActionGroupCallScheduled: {
     299              :         /* call:InputGroupCall schedule_date:int */
     300            1 :         if (r->len - r->pos < 4 + 16 + 4) return -1;
     301            1 :         tl_read_uint32(r); tl_read_int64(r); tl_read_int64(r);
     302            1 :         int sched = tl_read_int32(r);
     303            1 :         snprintf(buf, sizeof(buf), "scheduled video chat for %d", sched);
     304            1 :         set_text(out, buf);
     305            1 :         return 0;
     306              :     }
     307            1 :     case CRC_messageActionInviteToGroupCall: {
     308              :         /* call:InputGroupCall users:Vector<long> */
     309            1 :         if (r->len - r->pos < 4 + 16) return -1;
     310            1 :         tl_read_uint32(r); tl_read_int64(r); tl_read_int64(r);
     311            1 :         if (skip_long_vector(r) != 0) return -1;
     312            1 :         set_text(out, "invited to video chat");
     313            1 :         return 0;
     314              :     }
     315            1 :     default:
     316            1 :         snprintf(buf, sizeof(buf), "[service action 0x%08x]", acrc);
     317            1 :         set_text(out, buf);
     318              :         /* Unknown trailing data: we cannot advance reliably, but the
     319              :          * enclosing vector iterator will stop on rc=-1 from parse_message.
     320              :          * Return 0 so the entry is emitted with its label. */
     321            1 :         return 0;
     322              :     }
     323              : }
     324              : 
     325              : /* Parse the messageService layout and render the action into out->text.
     326              :  * Cursor is positioned just after (flags, id) on entry — messageService
     327              :  * does not carry flags2 on the current TL schema. Shared with updates.c
     328              :  * and search.c via `domain_history_parse_service` (see header). */
     329           22 : int domain_history_parse_service(TlReader *r, HistoryEntry *out, uint32_t flags) {
     330           22 :     out->is_service = 1;
     331           22 :     if (flags & MSG_FLAG_HAS_FROM_ID) {
     332            0 :         if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
     333              :     }
     334           22 :     if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
     335           22 :     if (flags & (1u << 3)) { /* reply_to — for pinMessage the target id lives here. */
     336              :         /* Peek the first 3 words (crc + flags + first optional) without
     337              :          * advancing: messageReplyHeader#afbc09db always has flags bit 4 =
     338              :          * reply_to_msg_id as the first int32 payload when present. */
     339            2 :         if (r->len - r->pos >= 12) {
     340              :             uint32_t rh_crc, rh_flags;
     341            2 :             memcpy(&rh_crc,   r->data + r->pos,     4);
     342            2 :             memcpy(&rh_flags, r->data + r->pos + 4, 4);
     343            2 :             if (rh_crc == 0xafbc09dbu && (rh_flags & (1u << 4))) {
     344              :                 int32_t pinned;
     345            2 :                 memcpy(&pinned, r->data + r->pos + 8, 4);
     346              :                 char buf[HISTORY_TEXT_MAX];
     347            2 :                 snprintf(buf, sizeof(buf), "pinned message %d", pinned);
     348            2 :                 set_text(out, buf);
     349              :             }
     350              :         }
     351            2 :         if (tl_skip_message_reply_header(r) != 0) { out->complex = 1; return -1; }
     352              :     }
     353           22 :     if (r->len - r->pos < 4) { out->complex = 1; return -1; }
     354           22 :     out->date = (int64_t)(int32_t)tl_read_int32(r);
     355              :     /* Preserve any pre-populated text (e.g. "pinned message N") across
     356              :      * parse_service_action for the PinMessage case, which has no body. */
     357              :     char saved_text[HISTORY_TEXT_MAX];
     358           22 :     memcpy(saved_text, out->text, sizeof(saved_text));
     359           22 :     int rc = parse_service_action(r, out);
     360           22 :     if (out->text[0] == '\0' && saved_text[0] != '\0')
     361            0 :         memcpy(out->text, saved_text, sizeof(saved_text));
     362              :     /* ttl_period (flags.25) — optional. Best-effort skip. */
     363           22 :     if (rc == 0 && (flags & (1u << 25))) {
     364            0 :         if (r->len - r->pos >= 4) tl_read_int32(r);
     365              :     }
     366           22 :     return rc;
     367              : }
     368              : 
     369              : /* Parse a Message. On success advance r past the whole object so the
     370              :  * caller can read the next vector element. Returns 0 on success, -1 on
     371              :  * parse failure, and sets `out->complex = 1` when we got the text/date
     372              :  * but had to bail before reaching the end of the object. */
     373         1044 : static int parse_message(TlReader *r, HistoryEntry *out) {
     374         1044 :     if (!tl_reader_ok(r)) return -1;
     375         1044 :     uint32_t crc = tl_read_uint32(r);
     376              : 
     377         1044 :     if (crc == TL_messageEmpty) {
     378            1 :         uint32_t flags = tl_read_uint32(r);
     379            1 :         out->id = tl_read_int32(r);
     380            1 :         if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
     381            1 :         return 0;
     382              :     }
     383              : 
     384         1043 :     if (crc != TL_message && crc != TL_messageService) {
     385            0 :         logger_log(LOG_WARN, "history: unknown Message 0x%08x", crc);
     386            0 :         return -1;
     387              :     }
     388              : 
     389         1043 :     uint32_t flags = tl_read_uint32(r);
     390         1043 :     uint32_t flags2 = 0;
     391              :     /* TL_message carries flags2; TL_messageService does not on the upstream
     392              :      * schema. Tests and the in-process mock server mirror this layout. */
     393         1043 :     if (crc == TL_message) flags2 = tl_read_uint32(r);
     394         1043 :     out->out = (flags & MSG_FLAG_OUT) ? 1 : 0;
     395         1043 :     out->id  = tl_read_int32(r);
     396              : 
     397         1043 :     if (crc == TL_messageService) {
     398              :         /* US-29 — render the action into out->text and return it as a
     399              :          * normal row. The enclosing vector iterator continues. */
     400           21 :         return domain_history_parse_service(r, out, flags);
     401              :     }
     402              : 
     403              :     /* Pre-text fields (message is read AFTER these). */
     404         1022 :     if (flags & MSG_FLAG_HAS_FROM_ID) {
     405            0 :         if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
     406              :     }
     407         1022 :     if (tl_skip_peer(r) != 0)         { out->complex = 1; return -1; }
     408         1022 :     if (flags & (1u << 28)) { /* saved_peer_id (layer 185+) */
     409            1 :         if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
     410              :     }
     411         1022 :     if (flags & (1u << 2)) { /* fwd_from */
     412            2 :         if (tl_skip_message_fwd_header(r) != 0) { out->complex = 1; return -1; }
     413              :     }
     414         1022 :     if (flags & (1u << 11)) { /* via_bot_id */
     415            3 :         if (r->len - r->pos < 8) { out->complex = 1; return -1; }
     416            3 :         tl_read_int64(r);
     417              :     }
     418         1022 :     if (flags2 & (1u << 0)) { /* via_business_bot_id */
     419            1 :         if (r->len - r->pos < 8) { out->complex = 1; return -1; }
     420            1 :         tl_read_int64(r);
     421              :     }
     422         1022 :     if (flags & (1u << 3)) { /* reply_to */
     423            2 :         if (tl_skip_message_reply_header(r) != 0) { out->complex = 1; return -1; }
     424              :     }
     425         1022 :     if (r->len - r->pos < 4) { out->complex = 1; return -1; }
     426         1022 :     out->date = (int64_t)(int32_t)tl_read_int32(r);
     427              : 
     428              :     /* message:string — always present. */
     429         2044 :     RAII_STRING char *msg = tl_read_string(r);
     430         1022 :     if (msg) {
     431         1022 :         size_t n = strlen(msg);
     432         1022 :         if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
     433         1022 :         memcpy(out->text, msg, n);
     434         1022 :         out->text[n] = '\0';
     435              :     }
     436              : 
     437              :     /* Skippable optionals after `message` — in schema order:
     438              :      *   flags.9  media
     439              :      *   flags.6  reply_markup
     440              :      *   flags.7  entities
     441              :      *   flags.10 views + forwards
     442              :      *   ... (more scalars below)
     443              :      *
     444              :      * Media is attempted first; if it fails the Message is left
     445              :      * mid-parse but we have at least captured id/date/text. */
     446         1022 :     if (flags & (1u << 9)) {
     447           12 :         MediaInfo mi = {0};
     448           12 :         if (tl_skip_message_media_ex(r, &mi) != 0) {
     449            0 :             out->media = mi.kind;
     450            0 :             out->complex = 1;
     451            0 :             return -1;
     452              :         }
     453           12 :         out->media    = mi.kind;
     454           24 :         out->media_id = (mi.kind == MEDIA_PHOTO) ? mi.photo_id
     455           12 :                       : (mi.kind == MEDIA_DOCUMENT) ? mi.document_id : 0;
     456           12 :         out->media_dc = mi.dc_id;
     457           12 :         out->media_info = mi;
     458              :     }
     459              : 
     460              :     /* Per-layer order: media → reply_markup → entities → views/forwards
     461              :      * → replies → edit_date → post_author → grouped_id → reactions →
     462              :      * restriction_reason → ttl_period → flags2 fields */
     463         1022 :     if (flags & (1u << 6)) { /* reply_markup */
     464            0 :         if (tl_skip_reply_markup(r) != 0) { out->complex = 1; return -1; }
     465              :     }
     466         1022 :     if (flags & (1u << 7))  { /* entities */
     467            0 :         if (tl_skip_message_entities_vector(r) != 0) { out->complex = 1; return -1; }
     468              :     }
     469         1022 :     if (flags & (1u << 10)) { /* views + forwards */
     470            0 :         if (r->len - r->pos < 8) { out->complex = 1; return -1; }
     471            0 :         tl_read_int32(r); tl_read_int32(r);
     472              :     }
     473         1022 :     if (flags & (1u << 23)) { /* replies */
     474            0 :         if (tl_skip_message_replies(r) != 0) { out->complex = 1; return -1; }
     475              :     }
     476         1022 :     if (flags & (1u << 15)) { /* edit_date */
     477            0 :         if (r->len - r->pos < 4) { out->complex = 1; return -1; }
     478            0 :         tl_read_int32(r);
     479              :     }
     480         1022 :     if (flags & (1u << 16)) { /* post_author */
     481            0 :         if (tl_skip_string(r) != 0) { out->complex = 1; return -1; }
     482              :     }
     483         1022 :     if (flags & (1u << 17)) { /* grouped_id */
     484            0 :         if (r->len - r->pos < 8) { out->complex = 1; return -1; }
     485            0 :         tl_read_int64(r);
     486              :     }
     487         1022 :     if (flags & (1u << 20)) { /* reactions */
     488            0 :         if (tl_skip_message_reactions(r) != 0) { out->complex = 1; return -1; }
     489              :     }
     490         1022 :     if (flags & (1u << 22)) { /* restriction_reason */
     491            0 :         if (tl_skip_restriction_reason_vector(r) != 0) {
     492            0 :             out->complex = 1; return -1;
     493              :         }
     494              :     }
     495         1022 :     if (flags & (1u << 25)) { /* ttl_period */
     496            0 :         if (r->len - r->pos < 4) { out->complex = 1; return -1; }
     497            0 :         tl_read_int32(r);
     498              :     }
     499         1022 :     if (flags2 & (1u << 30)) { /* quick_reply_shortcut_id */
     500            0 :         if (r->len - r->pos < 4) { out->complex = 1; return -1; }
     501            0 :         tl_read_int32(r);
     502              :     }
     503         1022 :     if (flags2 & (1u << 2)) { /* effect */
     504            0 :         if (r->len - r->pos < 8) { out->complex = 1; return -1; }
     505            0 :         tl_read_int64(r);
     506              :     }
     507         1022 :     if (flags2 & (1u << 3)) { /* factcheck */
     508            0 :         if (tl_skip_factcheck(r) != 0) { out->complex = 1; return -1; }
     509              :     }
     510         1022 :     return 0;
     511              : }
     512              : 
     513           64 : int domain_get_history(const ApiConfig *cfg,
     514              :                         MtProtoSession *s, Transport *t,
     515              :                         const HistoryPeer *peer,
     516              :                         int32_t offset_id, int limit,
     517              :                         HistoryEntry *out, int *out_count) {
     518           64 :     if (!cfg || !s || !t || !peer || !out || !out_count || limit <= 0) return -1;
     519           64 :     *out_count = 0;
     520              : 
     521              :     uint8_t query[128];
     522           64 :     size_t qlen = 0;
     523           64 :     if (build_request(peer, offset_id, limit, query, sizeof(query), &qlen) != 0) {
     524            0 :         logger_log(LOG_ERROR, "history: build_request overflow");
     525            0 :         return -1;
     526              :     }
     527              : 
     528           64 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
     529           64 :     if (!resp) return -1;
     530           64 :     size_t resp_len = 0;
     531           64 :     if (api_call(cfg, s, t, query, qlen, resp, 262144, &resp_len) != 0) {
     532            1 :         logger_log(LOG_ERROR, "history: api_call failed");
     533            1 :         return -1;
     534              :     }
     535           63 :     if (resp_len < 8) return -1;
     536              : 
     537              :     uint32_t top;
     538           63 :     memcpy(&top, resp, 4);
     539           63 :     if (top == TL_rpc_error) {
     540              :         RpcError err;
     541            0 :         rpc_parse_error(resp, resp_len, &err);
     542            0 :         logger_log(LOG_ERROR, "history: RPC error %d: %s",
     543              :                    err.error_code, err.error_msg);
     544            0 :         return -1;
     545              :     }
     546              : 
     547           63 :     if (top != TL_messages_messages &&
     548           11 :         top != TL_messages_messagesSlice &&
     549            3 :         top != TL_messages_channelMessages) {
     550            0 :         logger_log(LOG_ERROR, "history: unexpected top 0x%08x", top);
     551            0 :         return -1;
     552              :     }
     553              : 
     554           63 :     TlReader r = tl_reader_init(resp, resp_len);
     555           63 :     tl_read_uint32(&r); /* top */
     556              : 
     557              :     /* messagesSlice/channelMessages prepend some counters we skip. */
     558           63 :     if (top == TL_messages_messagesSlice) {
     559            8 :         tl_read_uint32(&r); /* flags */
     560            8 :         tl_read_int32 (&r); /* count */
     561              :         /* next_rate + offset_id_offset are optional (flags.0/.2) — we
     562              :          * don't read them; the messages vector follows after them in the
     563              :          * wire. For robustness the parse stops after one entry anyway. */
     564           55 :     } else if (top == TL_messages_channelMessages) {
     565            3 :         tl_read_uint32(&r); /* flags */
     566            3 :         tl_read_int32 (&r); /* pts */
     567            3 :         tl_read_int32 (&r); /* count */
     568              :         /* optional offset_id_offset (flags.2) not read */
     569              :     }
     570              : 
     571           63 :     uint32_t vec = tl_read_uint32(&r);
     572           63 :     if (vec != TL_vector) {
     573            0 :         logger_log(LOG_ERROR, "history: expected Vector<Message>, got 0x%08x",
     574              :                    vec);
     575            0 :         return -1;
     576              :     }
     577           63 :     uint32_t count = tl_read_uint32(&r);
     578           63 :     int written = 0;
     579         1107 :     for (uint32_t i = 0; i < count && written < limit; i++) {
     580         1044 :         HistoryEntry e = {0};
     581         1044 :         int rc2 = parse_message(&r, &e);
     582         1044 :         if (e.id != 0 || e.text[0] != '\0') {
     583         1044 :             out[written++] = e;
     584              :         }
     585         1044 :         if (rc2 != 0) break; /* could not advance past message — stop */
     586              :     }
     587           63 :     *out_count = written;
     588           63 :     return 0;
     589              : }
     590              : 
     591           54 : int domain_get_history_self(const ApiConfig *cfg,
     592              :                              MtProtoSession *s, Transport *t,
     593              :                              int32_t offset_id, int limit,
     594              :                              HistoryEntry *out, int *out_count) {
     595           54 :     HistoryPeer self = { .kind = HISTORY_PEER_SELF };
     596           54 :     return domain_get_history(cfg, s, t, &self, offset_id, limit, out, out_count);
     597              : }
        

Generated by: LCOV version 2.0-1