LCOV - code coverage report
Current view: top level - src/domain/read - search.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 78.1 % 155 121
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/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            2 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
      28            2 :     switch (p->kind) {
      29            1 :     case HISTORY_PEER_SELF:
      30            1 :         tl_write_uint32(w, TL_inputPeerSelf); return 0;
      31            1 :     case HISTORY_PEER_USER:
      32            1 :         tl_write_uint32(w, TL_inputPeerUser);
      33            1 :         tl_write_int64 (w, p->peer_id);
      34            1 :         tl_write_int64 (w, p->access_hash);
      35            1 :         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           58 : static int parse_message(TlReader *r, HistoryEntry *out) {
      53           58 :     if (!tl_reader_ok(r)) return -1;
      54           58 :     uint32_t crc = tl_read_uint32(r);
      55           58 :     if (crc == TL_messageEmpty) {
      56            0 :         uint32_t flags = tl_read_uint32(r);
      57            0 :         out->id = tl_read_int32(r);
      58            0 :         if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
      59            0 :         return 0;
      60              :     }
      61           58 :     if (crc != TL_message && crc != TL_messageService) return -1;
      62              : 
      63           58 :     uint32_t flags = tl_read_uint32(r);
      64           58 :     uint32_t flags2 = 0;
      65           58 :     if (crc == TL_message) flags2 = tl_read_uint32(r);
      66           58 :     out->out = (flags & (1u << 1)) ? 1 : 0;
      67           58 :     out->id  = tl_read_int32(r);
      68           58 :     if (crc == TL_messageService) {
      69            0 :         return domain_history_parse_service(r, out, flags);
      70              :     }
      71              : 
      72           58 :     if (flags & (1u << 8))   if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
      73           58 :     if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
      74           58 :     if (flags & (1u << 28))  if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
      75           58 :     if (flags & (1u << 2))   if (tl_skip_message_fwd_header(r) != 0) { out->complex=1; return -1; }
      76           58 :     if (flags & (1u << 11))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      77           58 :     if (flags2 & (1u << 0))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
      78           58 :     if (flags & (1u << 3))   if (tl_skip_message_reply_header(r) != 0) { out->complex=1; return -1; }
      79           58 :     if (r->len - r->pos < 4) { out->complex=1; return -1; }
      80           58 :     out->date = tl_read_int32(r);
      81              : 
      82           58 :     char *msg = tl_read_string(r);
      83           58 :     if (msg) {
      84           58 :         size_t n = strlen(msg);
      85           58 :         if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
      86           58 :         memcpy(out->text, msg, n);
      87           58 :         out->text[n] = '\0';
      88           58 :         free(msg);
      89              :     }
      90              : 
      91           58 :     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           58 :     if (flags & (1u << 6))   if (tl_skip_reply_markup(r) != 0) { out->complex=1; return -1; }
     104           58 :     if (flags & (1u << 7))   if (tl_skip_message_entities_vector(r) != 0) { out->complex=1; return -1; }
     105           58 :     if (flags & (1u << 10))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int32(r); tl_read_int32(r); }
     106           58 :     if (flags & (1u << 23))  if (tl_skip_message_replies(r) != 0) { out->complex=1; return -1; }
     107           58 :     if (flags & (1u << 15))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     108           58 :     if (flags & (1u << 16))  if (tl_skip_string(r) != 0) { out->complex=1; return -1; }
     109           58 :     if (flags & (1u << 17))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     110           58 :     if (flags & (1u << 20))  if (tl_skip_message_reactions(r) != 0) { out->complex=1; return -1; }
     111           58 :     if (flags & (1u << 22))  if (tl_skip_restriction_reason_vector(r) != 0) { out->complex=1; return -1; }
     112           58 :     if (flags & (1u << 25))  { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     113           58 :     if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
     114           58 :     if (flags2 & (1u << 2))  { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
     115           58 :     if (flags2 & (1u << 3))  if (tl_skip_factcheck(r) != 0) { out->complex=1; return -1; }
     116           58 :     return 0;
     117              : }
     118              : 
     119            4 : static int parse_top(const uint8_t *resp, size_t resp_len,
     120              :                       HistoryEntry *out, int limit, int *out_count) {
     121            4 :     TlReader r = tl_reader_init(resp, resp_len);
     122            4 :     uint32_t top = tl_read_uint32(&r);
     123              : 
     124            4 :     if (top == TL_rpc_error) {
     125            0 :         RpcError err; rpc_parse_error(resp, resp_len, &err);
     126            0 :         logger_log(LOG_ERROR, "search: RPC error %d: %s",
     127              :                    err.error_code, err.error_msg);
     128            0 :         return -1;
     129              :     }
     130              : 
     131            4 :     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            4 :     if (top == TL_messages_messagesSlice) {
     139            1 :         tl_read_uint32(&r); tl_read_int32(&r); /* flags, count */
     140            3 :     } else if (top == TL_messages_channelMessages) {
     141            0 :         tl_read_uint32(&r); tl_read_int32(&r); tl_read_int32(&r);
     142              :     }
     143              : 
     144            4 :     uint32_t vec = tl_read_uint32(&r);
     145            4 :     if (vec != TL_vector) return -1;
     146            4 :     uint32_t count = tl_read_uint32(&r);
     147            4 :     int written = 0;
     148           62 :     for (uint32_t i = 0; i < count && written < limit; i++) {
     149           58 :         HistoryEntry e = {0};
     150           58 :         int rc = parse_message(&r, &e);
     151           58 :         if (e.id != 0 || e.text[0] != '\0') out[written++] = e;
     152           58 :         if (rc != 0) break;
     153              :     }
     154            4 :     *out_count = written;
     155            4 :     return 0;
     156              : }
     157              : 
     158            2 : 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            2 :     if (!cfg || !s || !t || !peer || !query || !out || !out_count || limit <= 0)
     164            0 :         return -1;
     165            2 :     *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            2 :     TlWriter w; tl_writer_init(&w);
     175            2 :     tl_write_uint32(&w, CRC_messages_search);
     176            2 :     tl_write_uint32(&w, 0); /* flags = 0 */
     177            2 :     if (write_input_peer(&w, peer) != 0) { tl_writer_free(&w); return -1; }
     178            2 :     tl_write_string(&w, query);
     179            2 :     tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
     180            2 :     tl_write_int32 (&w, 0); /* min_date */
     181            2 :     tl_write_int32 (&w, 0); /* max_date */
     182            2 :     tl_write_int32 (&w, 0); /* offset_id */
     183            2 :     tl_write_int32 (&w, 0); /* add_offset */
     184            2 :     tl_write_int32 (&w, limit);
     185            2 :     tl_write_int32 (&w, 0); /* max_id */
     186            2 :     tl_write_int32 (&w, 0); /* min_id */
     187            2 :     tl_write_int64 (&w, 0); /* hash */
     188              : 
     189              :     uint8_t query_buf[2048];
     190            2 :     if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
     191            2 :     memcpy(query_buf, w.data, w.len);
     192            2 :     size_t qlen = w.len;
     193            2 :     tl_writer_free(&w);
     194              : 
     195            2 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
     196            2 :     if (!resp) return -1;
     197            2 :     size_t resp_len = 0;
     198            2 :     if (api_call(cfg, s, t, query_buf, qlen, resp, 262144, &resp_len) != 0)
     199            0 :         return -1;
     200            2 :     if (resp_len < 4) return -1;
     201            2 :     return parse_top(resp, resp_len, out, limit, out_count);
     202              : }
     203              : 
     204            2 : 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            2 :     if (!cfg || !s || !t || !query || !out || !out_count || limit <= 0) return -1;
     209            2 :     *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            2 :     TlWriter w; tl_writer_init(&w);
     217            2 :     tl_write_uint32(&w, CRC_messages_searchGlobal);
     218            2 :     tl_write_uint32(&w, 0); /* flags */
     219            2 :     tl_write_string(&w, query);
     220            2 :     tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
     221            2 :     tl_write_int32 (&w, 0); /* min_date */
     222            2 :     tl_write_int32 (&w, 0); /* max_date */
     223            2 :     tl_write_int32 (&w, 0); /* offset_rate */
     224            2 :     tl_write_uint32(&w, TL_inputPeerEmpty); /* offset_peer */
     225            2 :     tl_write_int32 (&w, 0); /* offset_id */
     226            2 :     tl_write_int32 (&w, limit);
     227              : 
     228              :     uint8_t query_buf[2048];
     229            2 :     if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
     230            2 :     memcpy(query_buf, w.data, w.len);
     231            2 :     size_t qlen = w.len;
     232            2 :     tl_writer_free(&w);
     233              : 
     234            2 :     RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
     235            2 :     if (!resp) return -1;
     236            2 :     size_t resp_len = 0;
     237            2 :     if (api_call(cfg, s, t, query_buf, qlen, resp, 262144, &resp_len) != 0)
     238            0 :         return -1;
     239            2 :     if (resp_len < 4) return -1;
     240            2 :     return parse_top(resp, resp_len, out, limit, out_count);
     241              : }
        

Generated by: LCOV version 2.0-1