LCOV - code coverage report
Current view: top level - src/domain/read - search.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 83.2 % 155 129
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/search.c
       6              :  * @brief messages.search / messages.searchGlobal minimal parser.
       7              :  */
       8              : 
       9              : #include "domain/read/search.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_search       0x29ee847au
      22              : #define CRC_messages_searchGlobal 0x4bc6589au
      23              : #define CRC_inputMessagesFilterEmpty 0x57e9a944u
      24              : 
      25              : /* Input-peer writer shared with history.c would live under app/, but to
      26              :  * keep the library split tidy we duplicate the small switch here. */
      27            6 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
      28            6 :     switch (p->kind) {
      29            3 :     case HISTORY_PEER_SELF:
      30            3 :         tl_write_uint32(w, TL_inputPeerSelf); return 0;
      31            3 :     case HISTORY_PEER_USER:
      32            3 :         tl_write_uint32(w, TL_inputPeerUser);
      33            3 :         tl_write_int64 (w, p->peer_id);
      34            3 :         tl_write_int64 (w, p->access_hash);
      35            3 :         return 0;
      36            0 :     case HISTORY_PEER_CHAT:
      37            0 :         tl_write_uint32(w, TL_inputPeerChat);
      38            0 :         tl_write_int64 (w, p->peer_id);
      39            0 :         return 0;
      40            0 :     case HISTORY_PEER_CHANNEL:
      41            0 :         tl_write_uint32(w, TL_inputPeerChannel);
      42            0 :         tl_write_int64 (w, p->peer_id);
      43            0 :         tl_write_int64 (w, p->access_hash);
      44            0 :         return 0;
      45            0 :     default: return -1;
      46              :     }
      47              : }
      48              : 
      49              : /* All trailer flags now have skippers; factcheck (flags2.3) still halts. */
      50              : 
      51              : /* Message parser — identical semantics to history.c::parse_message. */
      52          119 : static int parse_message(TlReader *r, HistoryEntry *out) {
      53          119 :     if (!tl_reader_ok(r)) return -1;
      54          119 :     uint32_t crc = tl_read_uint32(r);
      55          119 :     if (crc == TL_messageEmpty) {
      56            3 :         uint32_t flags = tl_read_uint32(r);
      57            3 :         out->id = tl_read_int32(r);
      58            3 :         if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
      59            3 :         return 0;
      60              :     }
      61          116 :     if (crc != TL_message && crc != TL_messageService) return -1;
      62              : 
      63          116 :     uint32_t flags = tl_read_uint32(r);
      64          116 :     uint32_t flags2 = 0;
      65          116 :     if (crc == TL_message) flags2 = tl_read_uint32(r);
      66          116 :     out->out = (flags & (1u << 1)) ? 1 : 0;
      67          116 :     out->id  = tl_read_int32(r);
      68          116 :     if (crc == TL_messageService) {
      69            0 :         return domain_history_parse_service(r, out, flags);
      70              :     }
      71              : 
      72          116 :     if (flags & (1u << 8))   if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
      73          116 :     if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
      74          116 :     if (flags & (1u << 28))  if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
      75          116 :     if (flags & (1u << 2))   if (tl_skip_message_fwd_header(r) != 0) { out->complex=1; return -1; }
      76          116 :     if (flags & (1u << 11))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      77          116 :     if (flags2 & (1u << 0))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      78          116 :     if (flags & (1u << 3))   if (tl_skip_message_reply_header(r) != 0) { out->complex=1; return -1; }
      79          116 :     if (r->len - r->pos < 4) { out->complex=1; return -1; }
      80          116 :     out->date = tl_read_int32(r);
      81              : 
      82          116 :     char *msg = tl_read_string(r);
      83          116 :     if (msg) {
      84          116 :         size_t n = strlen(msg);
      85          116 :         if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
      86          116 :         memcpy(out->text, msg, n);
      87          116 :         out->text[n] = '\0';
      88          116 :         free(msg);
      89              :     }
      90              : 
      91          116 :     if (flags & (1u << 9)) {
      92            0 :         MediaInfo mi = {0};
      93            0 :         if (tl_skip_message_media_ex(r, &mi) != 0) {
      94            0 :             out->media = mi.kind;
      95            0 :             out->complex = 1;
      96            0 :             return -1;
      97              :         }
      98            0 :         out->media    = mi.kind;
      99            0 :         out->media_id = (mi.kind == MEDIA_PHOTO) ? mi.photo_id
     100            0 :                       : (mi.kind == MEDIA_DOCUMENT) ? mi.document_id : 0;
     101            0 :         out->media_dc = mi.dc_id;
     102              :     }
     103          116 :     if (flags & (1u << 6))   if (tl_skip_reply_markup(r) != 0) { out->complex=1; return -1; }
     104          116 :     if (flags & (1u << 7))   if (tl_skip_message_entities_vector(r) != 0) { out->complex=1; return -1; }
     105          116 :     if (flags & (1u << 10))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int32(r); tl_read_int32(r); }
     106          116 :     if (flags & (1u << 23))  if (tl_skip_message_replies(r) != 0) { out->complex=1; return -1; }
     107          116 :     if (flags & (1u << 15))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     108          116 :     if (flags & (1u << 16))  if (tl_skip_string(r) != 0) { out->complex=1; return -1; }
     109          116 :     if (flags & (1u << 17))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     110          116 :     if (flags & (1u << 20))  if (tl_skip_message_reactions(r) != 0) { out->complex=1; return -1; }
     111          116 :     if (flags & (1u << 22))  if (tl_skip_restriction_reason_vector(r) != 0) { out->complex=1; return -1; }
     112          116 :     if (flags & (1u << 25))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     113          116 :     if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     114          116 :     if (flags2 & (1u << 2))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     115          116 :     if (flags2 & (1u << 3))  if (tl_skip_factcheck(r) != 0) { out->complex=1; return -1; }
     116          116 :     return 0;
     117              : }
     118              : 
     119           12 : static int parse_top(const uint8_t *resp, size_t resp_len,
     120              :                       HistoryEntry *out, int limit, int *out_count) {
     121           12 :     TlReader r = tl_reader_init(resp, resp_len);
     122           12 :     uint32_t top = tl_read_uint32(&r);
     123              : 
     124           12 :     if (top == TL_rpc_error) {
     125            1 :         RpcError err; rpc_parse_error(resp, resp_len, &err);
     126            1 :         logger_log(LOG_ERROR, "search: RPC error %d: %s",
     127              :                    err.error_code, err.error_msg);
     128            1 :         return -1;
     129              :     }
     130              : 
     131           11 :     if (top != TL_messages_messages &&
     132            0 :         top != TL_messages_messagesSlice &&
     133              :         top != TL_messages_channelMessages) {
     134            0 :         logger_log(LOG_ERROR, "search: unexpected top 0x%08x", top);
     135            0 :         return -1;
     136              :     }
     137              : 
     138           11 :     if (top == TL_messages_messagesSlice) {
     139            2 :         tl_read_uint32(&r); tl_read_int32(&r); /* flags, count */
     140            9 :     } else if (top == TL_messages_channelMessages) {
     141            0 :         tl_read_uint32(&r); tl_read_int32(&r); tl_read_int32(&r);
     142              :     }
     143              : 
     144           11 :     uint32_t vec = tl_read_uint32(&r);
     145           11 :     if (vec != TL_vector) return -1;
     146           11 :     uint32_t count = tl_read_uint32(&r);
     147           11 :     int written = 0;
     148          130 :     for (uint32_t i = 0; i < count && written < limit; i++) {
     149          119 :         HistoryEntry e = {0};
     150          119 :         int rc = parse_message(&r, &e);
     151          119 :         if (e.id != 0 || e.text[0] != '\0') out[written++] = e;
     152          119 :         if (rc != 0) break;
     153              :     }
     154           11 :     *out_count = written;
     155           11 :     return 0;
     156              : }
     157              : 
     158            7 : int domain_search_peer(const ApiConfig *cfg,
     159              :                         MtProtoSession *s, Transport *t,
     160              :                         const HistoryPeer *peer, const char *query,
     161              :                         int limit,
     162              :                         HistoryEntry *out, int *out_count) {
     163            7 :     if (!cfg || !s || !t || !peer || !query || !out || !out_count || limit <= 0)
     164            1 :         return -1;
     165            6 :     *out_count = 0;
     166              : 
     167              :     /* messages.search#29ee847a flags:# peer:InputPeer q:string
     168              :      *     from_id:flags.0?InputPeer saved_peer_id:flags.2?InputPeer
     169              :      *     saved_reaction:flags.3?Vector<Reaction>
     170              :      *     top_msg_id:flags.1?int filter:MessagesFilter
     171              :      *     min_date:int max_date:int offset_id:int add_offset:int
     172              :      *     limit:int max_id:int min_id:int hash:long = messages.Messages
     173              :      */
     174            6 :     TlWriter w; tl_writer_init(&w);
     175            6 :     tl_write_uint32(&w, CRC_messages_search);
     176            6 :     tl_write_uint32(&w, 0); /* flags = 0 */
     177            6 :     if (write_input_peer(&w, peer) != 0) { tl_writer_free(&w); return -1; }
     178            6 :     tl_write_string(&w, query);
     179            6 :     tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
     180            6 :     tl_write_int32 (&w, 0); /* min_date */
     181            6 :     tl_write_int32 (&w, 0); /* max_date */
     182            6 :     tl_write_int32 (&w, 0); /* offset_id */
     183            6 :     tl_write_int32 (&w, 0); /* add_offset */
     184            6 :     tl_write_int32 (&w, limit);
     185            6 :     tl_write_int32 (&w, 0); /* max_id */
     186            6 :     tl_write_int32 (&w, 0); /* min_id */
     187            6 :     tl_write_int64 (&w, 0); /* hash */
     188              : 
     189              :     uint8_t query_buf[2048];
     190            6 :     if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
     191            6 :     memcpy(query_buf, 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_buf, qlen, resp, 262144, &resp_len) != 0)
     199            0 :         return -1;
     200            6 :     if (resp_len < 4) return -1;
     201            6 :     return parse_top(resp, resp_len, out, limit, out_count);
     202              : }
     203              : 
     204            7 : int domain_search_global(const ApiConfig *cfg,
     205              :                           MtProtoSession *s, Transport *t,
     206              :                           const char *query, int limit,
     207              :                           HistoryEntry *out, int *out_count) {
     208            7 :     if (!cfg || !s || !t || !query || !out || !out_count || limit <= 0) return -1;
     209            6 :     *out_count = 0;
     210              : 
     211              :     /* messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int
     212              :      *     q:string filter:MessagesFilter min_date:int max_date:int
     213              :      *     offset_rate:int offset_peer:InputPeer offset_id:int
     214              :      *     limit:int = messages.Messages
     215              :      */
     216            6 :     TlWriter w; tl_writer_init(&w);
     217            6 :     tl_write_uint32(&w, CRC_messages_searchGlobal);
     218            6 :     tl_write_uint32(&w, 0); /* flags */
     219            6 :     tl_write_string(&w, query);
     220            6 :     tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
     221            6 :     tl_write_int32 (&w, 0); /* min_date */
     222            6 :     tl_write_int32 (&w, 0); /* max_date */
     223            6 :     tl_write_int32 (&w, 0); /* offset_rate */
     224            6 :     tl_write_uint32(&w, TL_inputPeerEmpty); /* offset_peer */
     225            6 :     tl_write_int32 (&w, 0); /* offset_id */
     226            6 :     tl_write_int32 (&w, limit);
     227              : 
     228              :     uint8_t query_buf[2048];
     229            6 :     if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
     230            6 :     memcpy(query_buf, w.data, w.len);
     231            6 :     size_t qlen = w.len;
     232            6 :     tl_writer_free(&w);
     233              : 
     234            6 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
     235            6 :     if (!resp) return -1;
     236            6 :     size_t resp_len = 0;
     237            6 :     if (api_call(cfg, s, t, query_buf, qlen, resp, 262144, &resp_len) != 0)
     238            0 :         return -1;
     239            6 :     if (resp_len < 4) return -1;
     240            6 :     return parse_top(resp, resp_len, out, limit, out_count);
     241              : }
        

Generated by: LCOV version 2.0-1