LCOV - code coverage report
Current view: top level - src/domain/read - updates.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 82.6 % 155 128
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 5 5

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file domain/read/updates.c
       6              :  * @brief updates.getState / updates.getDifference minimal parsers.
       7              :  */
       8              : 
       9              : #include "domain/read/updates.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_updates_getState      0xedd4882au
      22              : #define CRC_updates_getDifference 0x19c2f763u
      23              : #define CRC_updates_differenceTooLong 0x4afe8f6du
      24              : 
      25              : /* All trailer flags now have skippers; factcheck (flags2.3) still halts. */
      26              : 
      27              : /* Parse one Message, advance the cursor past it. Populates entry with
      28              :  * id/out/date/text; sets complex=1 if we had to bail on a trailing
      29              :  * unsupported flag. Return 0 = advanced cleanly, -1 = cursor is at an
      30              :  * unknown offset and iteration must stop. */
      31           12 : static int parse_message(TlReader *r, HistoryEntry *out) {
      32           12 :     if (!tl_reader_ok(r)) return -1;
      33           12 :     uint32_t crc = tl_read_uint32(r);
      34           12 :     if (crc == TL_messageEmpty) {
      35            0 :         uint32_t flags = tl_read_uint32(r);
      36            0 :         out->id = tl_read_int32(r);
      37            0 :         if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
      38            0 :         return 0;
      39              :     }
      40           12 :     if (crc != TL_message && crc != TL_messageService) return -1;
      41              : 
      42           12 :     uint32_t flags = tl_read_uint32(r);
      43           12 :     uint32_t flags2 = 0;
      44           12 :     if (crc == TL_message) flags2 = tl_read_uint32(r);
      45           12 :     out->out = (flags & (1u << 1)) ? 1 : 0;
      46           12 :     out->id  = tl_read_int32(r);
      47           12 :     if (crc == TL_messageService) {
      48              :         /* US-29: service messages surface through watch with the
      49              :          * rendered action in `text`. Delegate to the history parser so
      50              :          * the action table stays in one place. */
      51            2 :         return domain_history_parse_service(r, out, flags);
      52              :     }
      53              : 
      54           10 :     if (flags & (1u << 8))   if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
      55              :     /* peer_id:Peer — mandatory; extract the numeric id for filtering. */
      56              :     {
      57           10 :         if (r->len - r->pos < 12) { out->complex = 1; return -1; }
      58           10 :         uint32_t pcrc = tl_read_uint32(r);
      59           10 :         int64_t  pid  = tl_read_int64(r);
      60           10 :         switch (pcrc) {
      61           10 :         case TL_peerUser: case TL_peerChat: case TL_peerChannel:
      62           10 :             out->peer_id = pid;
      63           10 :             break;
      64            0 :         default:
      65            0 :             out->complex = 1; return -1;
      66              :         }
      67              :     }
      68           10 :     if (flags & (1u << 28))  if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
      69           10 :     if (flags & (1u << 2))   if (tl_skip_message_fwd_header(r) != 0) { out->complex=1; return -1; }
      70           10 :     if (flags & (1u << 11))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      71           10 :     if (flags2 & (1u << 0))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      72           10 :     if (flags & (1u << 3))   if (tl_skip_message_reply_header(r) != 0) { out->complex=1; return -1; }
      73           10 :     if (r->len - r->pos < 4) { out->complex=1; return -1; }
      74           10 :     out->date = (int64_t)(int32_t)tl_read_int32(r);
      75              : 
      76           10 :     char *msg = tl_read_string(r);
      77           10 :     if (msg) {
      78           10 :         size_t n = strlen(msg);
      79           10 :         if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
      80           10 :         memcpy(out->text, msg, n);
      81           10 :         out->text[n] = '\0';
      82           10 :         free(msg);
      83              :     }
      84              : 
      85           10 :     if (flags & (1u << 9)) {
      86            0 :         MediaInfo mi = {0};
      87            0 :         if (tl_skip_message_media_ex(r, &mi) != 0) {
      88            0 :             out->media = mi.kind;
      89            0 :             out->complex = 1;
      90            0 :             return -1;
      91              :         }
      92            0 :         out->media    = mi.kind;
      93            0 :         out->media_id = (mi.kind == MEDIA_PHOTO) ? mi.photo_id
      94            0 :                       : (mi.kind == MEDIA_DOCUMENT) ? mi.document_id : 0;
      95            0 :         out->media_dc = mi.dc_id;
      96              :     }
      97           10 :     if (flags & (1u << 6))   if (tl_skip_reply_markup(r) != 0) { out->complex=1; return -1; }
      98           10 :     if (flags & (1u << 7))   if (tl_skip_message_entities_vector(r) != 0) { out->complex=1; return -1; }
      99           10 :     if (flags & (1u << 10))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int32(r); tl_read_int32(r); }
     100           10 :     if (flags & (1u << 23))  if (tl_skip_message_replies(r) != 0) { out->complex=1; return -1; }
     101           10 :     if (flags & (1u << 15))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     102           10 :     if (flags & (1u << 16))  if (tl_skip_string(r) != 0) { out->complex=1; return -1; }
     103           10 :     if (flags & (1u << 17))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     104           10 :     if (flags & (1u << 20))  if (tl_skip_message_reactions(r) != 0) { out->complex=1; return -1; }
     105           10 :     if (flags & (1u << 22))  if (tl_skip_restriction_reason_vector(r) != 0) { out->complex=1; return -1; }
     106           10 :     if (flags & (1u << 25))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     107           10 :     if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     108           10 :     if (flags2 & (1u << 2))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     109           10 :     if (flags2 & (1u << 3))  if (tl_skip_factcheck(r) != 0) { out->complex=1; return -1; }
     110           10 :     return 0;
     111              : }
     112              : 
     113           10 : static int send_trivial(const ApiConfig *cfg,
     114              :                          MtProtoSession *s, Transport *t,
     115              :                          uint32_t method_crc,
     116              :                          uint8_t *resp, size_t resp_cap, size_t *resp_len) {
     117              :     TlWriter w;
     118           10 :     tl_writer_init(&w);
     119           10 :     tl_write_uint32(&w, method_crc);
     120              :     uint8_t query[32];
     121           10 :     if (w.len > sizeof(query)) { tl_writer_free(&w); return -1; }
     122           10 :     memcpy(query, w.data, w.len);
     123           10 :     size_t qlen = w.len;
     124           10 :     tl_writer_free(&w);
     125              : 
     126           10 :     if (api_call(cfg, s, t, query, qlen, resp, resp_cap, resp_len) != 0) {
     127            0 :         logger_log(LOG_ERROR, "updates: api_call failed");
     128            0 :         return -1;
     129              :     }
     130           10 :     return 0;
     131              : }
     132              : 
     133            5 : static int parse_state(TlReader *r, UpdatesState *out) {
     134            5 :     uint32_t crc = tl_read_uint32(r);
     135            5 :     if (crc != TL_updates_state) {
     136            0 :         logger_log(LOG_ERROR, "updates: not updates.state (0x%08x)", crc);
     137            0 :         return -1;
     138              :     }
     139            5 :     out->pts  = tl_read_int32(r);
     140            5 :     out->qts  = tl_read_int32(r);
     141            5 :     out->date = (int64_t)(int32_t)tl_read_int32(r);
     142            5 :     out->seq  = tl_read_int32(r);
     143            5 :     out->unread_count = tl_read_int32(r);
     144            5 :     return 0;
     145              : }
     146              : 
     147           11 : int domain_updates_state(const ApiConfig *cfg,
     148              :                           MtProtoSession *s, Transport *t,
     149              :                           UpdatesState *out) {
     150           11 :     if (!cfg || !s || !t || !out) return -1;
     151           10 :     memset(out, 0, sizeof(*out));
     152              : 
     153           10 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(4096);
     154           10 :     if (!resp) return -1;
     155           10 :     size_t resp_len = 0;
     156           10 :     if (send_trivial(cfg, s, t, CRC_updates_getState,
     157            0 :                       resp, 4096, &resp_len) != 0) return -1;
     158           10 :     if (resp_len < 4) return -1;
     159              : 
     160              :     uint32_t top;
     161           10 :     memcpy(&top, resp, 4);
     162           10 :     if (top == TL_rpc_error) {
     163            5 :         RpcError err; rpc_parse_error(resp, resp_len, &err);
     164            5 :         logger_log(LOG_ERROR, "updates.getState RPC error %d: %s",
     165              :                    err.error_code, err.error_msg);
     166            5 :         return -1;
     167              :     }
     168              : 
     169            5 :     TlReader r = tl_reader_init(resp, resp_len);
     170            5 :     return parse_state(&r, out);
     171              : }
     172              : 
     173           16 : int domain_updates_difference(const ApiConfig *cfg,
     174              :                                MtProtoSession *s, Transport *t,
     175              :                                const UpdatesState *in,
     176              :                                UpdatesDifference *out) {
     177           16 :     if (!cfg || !s || !t || !in || !out) return -1;
     178           15 :     memset(out, 0, sizeof(*out));
     179           15 :     out->next_state = *in;
     180              : 
     181              :     /* Build getDifference(flags=0, pts, date, qts). */
     182           15 :     TlWriter w; tl_writer_init(&w);
     183           15 :     tl_write_uint32(&w, CRC_updates_getDifference);
     184           15 :     tl_write_uint32(&w, 0);                /* flags = 0 (no pts_total_limit) */
     185           15 :     tl_write_int32 (&w, in->pts);
     186           15 :     tl_write_int32 (&w, (int32_t)in->date);  /* wire is still int32 per TL schema */
     187           15 :     tl_write_int32 (&w, in->qts);
     188              : 
     189              :     uint8_t query[64];
     190           15 :     if (w.len > sizeof(query)) { tl_writer_free(&w); return -1; }
     191           15 :     memcpy(query, w.data, w.len);
     192           15 :     size_t qlen = w.len;
     193           15 :     tl_writer_free(&w);
     194              : 
     195           15 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
     196           15 :     if (!resp) return -1;
     197           15 :     size_t resp_len = 0;
     198           15 :     if (api_call(cfg, s, t, query, qlen, resp, 262144, &resp_len) != 0) return -1;
     199           15 :     if (resp_len < 4) return -1;
     200              : 
     201              :     uint32_t top;
     202           15 :     memcpy(&top, resp, 4);
     203           15 :     if (top == TL_rpc_error) {
     204            2 :         RpcError err; rpc_parse_error(resp, resp_len, &err);
     205            2 :         logger_log(LOG_ERROR, "updates.getDifference RPC error %d: %s",
     206              :                    err.error_code, err.error_msg);
     207            2 :         return -1;
     208              :     }
     209              : 
     210           13 :     TlReader r = tl_reader_init(resp, resp_len);
     211           13 :     uint32_t crc = tl_read_uint32(&r);
     212              : 
     213           13 :     if (crc == TL_updates_differenceEmpty) {
     214            5 :         out->is_empty = 1;
     215              :         /* differenceEmpty: date:int seq:int */
     216            5 :         out->next_state.date = (int64_t)(int32_t)tl_read_int32(&r);
     217            5 :         out->next_state.seq  = tl_read_int32(&r);
     218            5 :         return 0;
     219              :     }
     220            8 :     if (crc == CRC_updates_differenceTooLong) {
     221            0 :         out->is_too_long = 1;
     222              :         /* Next call must reseed via updates.getState. */
     223            0 :         out->next_state.pts = tl_read_int32(&r);
     224            0 :         return 0;
     225              :     }
     226            8 :     if (crc != TL_updates_difference && crc != TL_updates_differenceSlice) {
     227            0 :         logger_log(LOG_ERROR, "updates: unexpected Difference 0x%08x", crc);
     228            0 :         return -1;
     229              :     }
     230              : 
     231              :     /* difference / differenceSlice both start with:
     232              :      *   new_messages:Vector<Message>
     233              :      *   new_encrypted_messages:Vector<EncryptedMessage>
     234              :      *   other_updates:Vector<Update>
     235              :      *   chats:Vector<Chat>
     236              :      *   users:Vector<User>
     237              :      *   state:updates.State            (only for plain difference)
     238              :      *   intermediate_state:updates.State (only for differenceSlice)
     239              :      *
     240              :      * We only read the top-level vector counts so we can report
     241              :      * activity. Actual message/update bodies need a per-element TL
     242              :      * walker which is deferred to v2.
     243              :      */
     244              :     /* new_messages: Vector<Message> */
     245            8 :     uint32_t vec = tl_read_uint32(&r);
     246            8 :     if (vec != TL_vector) {
     247            0 :         logger_log(LOG_ERROR, "updates: expected Vector, got 0x%08x", vec);
     248            0 :         return -1;
     249              :     }
     250            8 :     uint32_t count = tl_read_uint32(&r);
     251            8 :     int32_t written = 0;
     252           20 :     for (uint32_t i = 0; i < count && written < UPDATES_MAX_MESSAGES; i++) {
     253           12 :         HistoryEntry e = {0};
     254           12 :         int rc = parse_message(&r, &e);
     255           12 :         if (e.id != 0 || e.text[0] != '\0') {
     256           12 :             out->new_messages[written++] = e;
     257              :         }
     258           12 :         if (rc != 0) break; /* iteration stopped */
     259              :     }
     260            8 :     out->new_messages_count = written;
     261            8 :     out->next_state = *in;
     262            8 :     return 0;
     263              : }
        

Generated by: LCOV version 2.0-1