LCOV - code coverage report
Current view: top level - src/domain/read - updates.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 80.6 % 155 125
Test Date: 2026-04-20 19:54:24 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            4 : static int parse_message(TlReader *r, HistoryEntry *out) {
      32            4 :     if (!tl_reader_ok(r)) return -1;
      33            4 :     uint32_t crc = tl_read_uint32(r);
      34            4 :     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            4 :     if (crc != TL_message && crc != TL_messageService) return -1;
      41              : 
      42            4 :     uint32_t flags = tl_read_uint32(r);
      43            4 :     uint32_t flags2 = 0;
      44            4 :     if (crc == TL_message) flags2 = tl_read_uint32(r);
      45            4 :     out->out = (flags & (1u << 1)) ? 1 : 0;
      46            4 :     out->id  = tl_read_int32(r);
      47            4 :     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            1 :         return domain_history_parse_service(r, out, flags);
      52              :     }
      53              : 
      54            3 :     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            3 :         if (r->len - r->pos < 12) { out->complex = 1; return -1; }
      58            3 :         uint32_t pcrc = tl_read_uint32(r);
      59            3 :         int64_t  pid  = tl_read_int64(r);
      60            3 :         switch (pcrc) {
      61            3 :         case TL_peerUser: case TL_peerChat: case TL_peerChannel:
      62            3 :             out->peer_id = pid;
      63            3 :             break;
      64            0 :         default:
      65            0 :             out->complex = 1; return -1;
      66              :         }
      67              :     }
      68            3 :     if (flags & (1u << 28))  if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
      69            3 :     if (flags & (1u << 2))   if (tl_skip_message_fwd_header(r) != 0) { out->complex=1; return -1; }
      70            3 :     if (flags & (1u << 11))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      71            3 :     if (flags2 & (1u << 0))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      72            3 :     if (flags & (1u << 3))   if (tl_skip_message_reply_header(r) != 0) { out->complex=1; return -1; }
      73            3 :     if (r->len - r->pos < 4) { out->complex=1; return -1; }
      74            3 :     out->date = (int64_t)(int32_t)tl_read_int32(r);
      75              : 
      76            3 :     char *msg = tl_read_string(r);
      77            3 :     if (msg) {
      78            3 :         size_t n = strlen(msg);
      79            3 :         if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
      80            3 :         memcpy(out->text, msg, n);
      81            3 :         out->text[n] = '\0';
      82            3 :         free(msg);
      83              :     }
      84              : 
      85            3 :     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            3 :     if (flags & (1u << 6))   if (tl_skip_reply_markup(r) != 0) { out->complex=1; return -1; }
      98            3 :     if (flags & (1u << 7))   if (tl_skip_message_entities_vector(r) != 0) { out->complex=1; return -1; }
      99            3 :     if (flags & (1u << 10))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int32(r); tl_read_int32(r); }
     100            3 :     if (flags & (1u << 23))  if (tl_skip_message_replies(r) != 0) { out->complex=1; return -1; }
     101            3 :     if (flags & (1u << 15))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     102            3 :     if (flags & (1u << 16))  if (tl_skip_string(r) != 0) { out->complex=1; return -1; }
     103            3 :     if (flags & (1u << 17))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     104            3 :     if (flags & (1u << 20))  if (tl_skip_message_reactions(r) != 0) { out->complex=1; return -1; }
     105            3 :     if (flags & (1u << 22))  if (tl_skip_restriction_reason_vector(r) != 0) { out->complex=1; return -1; }
     106            3 :     if (flags & (1u << 25))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     107            3 :     if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     108            3 :     if (flags2 & (1u << 2))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     109            3 :     if (flags2 & (1u << 3))  if (tl_skip_factcheck(r) != 0) { out->complex=1; return -1; }
     110            3 :     return 0;
     111              : }
     112              : 
     113            1 : 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            1 :     tl_writer_init(&w);
     119            1 :     tl_write_uint32(&w, method_crc);
     120              :     uint8_t query[32];
     121            1 :     if (w.len > sizeof(query)) { tl_writer_free(&w); return -1; }
     122            1 :     memcpy(query, w.data, w.len);
     123            1 :     size_t qlen = w.len;
     124            1 :     tl_writer_free(&w);
     125              : 
     126            1 :     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            1 :     return 0;
     131              : }
     132              : 
     133            1 : static int parse_state(TlReader *r, UpdatesState *out) {
     134            1 :     uint32_t crc = tl_read_uint32(r);
     135            1 :     if (crc != TL_updates_state) {
     136            0 :         logger_log(LOG_ERROR, "updates: not updates.state (0x%08x)", crc);
     137            0 :         return -1;
     138              :     }
     139            1 :     out->pts  = tl_read_int32(r);
     140            1 :     out->qts  = tl_read_int32(r);
     141            1 :     out->date = (int64_t)(int32_t)tl_read_int32(r);
     142            1 :     out->seq  = tl_read_int32(r);
     143            1 :     out->unread_count = tl_read_int32(r);
     144            1 :     return 0;
     145              : }
     146              : 
     147            1 : int domain_updates_state(const ApiConfig *cfg,
     148              :                           MtProtoSession *s, Transport *t,
     149              :                           UpdatesState *out) {
     150            1 :     if (!cfg || !s || !t || !out) return -1;
     151            1 :     memset(out, 0, sizeof(*out));
     152              : 
     153            1 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(4096);
     154            1 :     if (!resp) return -1;
     155            1 :     size_t resp_len = 0;
     156            1 :     if (send_trivial(cfg, s, t, CRC_updates_getState,
     157            0 :                       resp, 4096, &resp_len) != 0) return -1;
     158            1 :     if (resp_len < 4) return -1;
     159              : 
     160              :     uint32_t top;
     161            1 :     memcpy(&top, resp, 4);
     162            1 :     if (top == TL_rpc_error) {
     163            0 :         RpcError err; rpc_parse_error(resp, resp_len, &err);
     164            0 :         logger_log(LOG_ERROR, "updates.getState RPC error %d: %s",
     165              :                    err.error_code, err.error_msg);
     166            0 :         return -1;
     167              :     }
     168              : 
     169            1 :     TlReader r = tl_reader_init(resp, resp_len);
     170            1 :     return parse_state(&r, out);
     171              : }
     172              : 
     173            6 : int domain_updates_difference(const ApiConfig *cfg,
     174              :                                MtProtoSession *s, Transport *t,
     175              :                                const UpdatesState *in,
     176              :                                UpdatesDifference *out) {
     177            6 :     if (!cfg || !s || !t || !in || !out) return -1;
     178            6 :     memset(out, 0, sizeof(*out));
     179            6 :     out->next_state = *in;
     180              : 
     181              :     /* Build getDifference(flags=0, pts, date, qts). */
     182            6 :     TlWriter w; tl_writer_init(&w);
     183            6 :     tl_write_uint32(&w, CRC_updates_getDifference);
     184            6 :     tl_write_uint32(&w, 0);                /* flags = 0 (no pts_total_limit) */
     185            6 :     tl_write_int32 (&w, in->pts);
     186            6 :     tl_write_int32 (&w, (int32_t)in->date);  /* wire is still int32 per TL schema */
     187            6 :     tl_write_int32 (&w, in->qts);
     188              : 
     189              :     uint8_t query[64];
     190            6 :     if (w.len > sizeof(query)) { tl_writer_free(&w); return -1; }
     191            6 :     memcpy(query, w.data, w.len);
     192            6 :     size_t qlen = w.len;
     193            6 :     tl_writer_free(&w);
     194              : 
     195            6 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
     196            6 :     if (!resp) return -1;
     197            6 :     size_t resp_len = 0;
     198            6 :     if (api_call(cfg, s, t, query, qlen, resp, 262144, &resp_len) != 0) return -1;
     199            6 :     if (resp_len < 4) return -1;
     200              : 
     201              :     uint32_t top;
     202            6 :     memcpy(&top, resp, 4);
     203            6 :     if (top == TL_rpc_error) {
     204            1 :         RpcError err; rpc_parse_error(resp, resp_len, &err);
     205            1 :         logger_log(LOG_ERROR, "updates.getDifference RPC error %d: %s",
     206              :                    err.error_code, err.error_msg);
     207            1 :         return -1;
     208              :     }
     209              : 
     210            5 :     TlReader r = tl_reader_init(resp, resp_len);
     211            5 :     uint32_t crc = tl_read_uint32(&r);
     212              : 
     213            5 :     if (crc == TL_updates_differenceEmpty) {
     214            2 :         out->is_empty = 1;
     215              :         /* differenceEmpty: date:int seq:int */
     216            2 :         out->next_state.date = (int64_t)(int32_t)tl_read_int32(&r);
     217            2 :         out->next_state.seq  = tl_read_int32(&r);
     218            2 :         return 0;
     219              :     }
     220            3 :     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            3 :     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            3 :     uint32_t vec = tl_read_uint32(&r);
     246            3 :     if (vec != TL_vector) {
     247            0 :         logger_log(LOG_ERROR, "updates: expected Vector, got 0x%08x", vec);
     248            0 :         return -1;
     249              :     }
     250            3 :     uint32_t count = tl_read_uint32(&r);
     251            3 :     int32_t written = 0;
     252            7 :     for (uint32_t i = 0; i < count && written < UPDATES_MAX_MESSAGES; i++) {
     253            4 :         HistoryEntry e = {0};
     254            4 :         int rc = parse_message(&r, &e);
     255            4 :         if (e.id != 0 || e.text[0] != '\0') {
     256            4 :             out->new_messages[written++] = e;
     257              :         }
     258            4 :         if (rc != 0) break; /* iteration stopped */
     259              :     }
     260            3 :     out->new_messages_count = written;
     261            3 :     out->next_state = *in;
     262            3 :     return 0;
     263              : }
        

Generated by: LCOV version 2.0-1