LCOV - code coverage report
Current view: top level - tests/unit - test_domain_edit_delete_forward.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.8 % 158 153
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 13 13

            Line data    Source code
       1              : /**
       2              :  * @file test_domain_edit_delete_forward.c
       3              :  * @brief Unit tests for the P5-06 write family (edit / delete / forward).
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "domain/write/edit.h"
       8              : #include "domain/write/delete.h"
       9              : #include "domain/write/forward.h"
      10              : #include "domain/write/send.h"
      11              : #include "tl_serial.h"
      12              : #include "tl_registry.h"
      13              : #include "mock_socket.h"
      14              : #include "mock_crypto.h"
      15              : #include "mtproto_session.h"
      16              : #include "transport.h"
      17              : #include "api_call.h"
      18              : 
      19              : #include <stdlib.h>
      20              : #include <string.h>
      21              : 
      22            5 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
      23              :                                           uint8_t *out, size_t *out_len) {
      24            5 :     TlWriter w; tl_writer_init(&w);
      25            5 :     uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
      26            5 :     uint8_t header[32] = {0};
      27            5 :     uint32_t plen32 = (uint32_t)plen;
      28            5 :     memcpy(header + 28, &plen32, 4);
      29            5 :     tl_write_raw(&w, header, 32);
      30            5 :     tl_write_raw(&w, payload, plen);
      31            5 :     size_t enc = w.len - 24;
      32            5 :     if (enc % 16 != 0) {
      33            5 :         uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
      34              :     }
      35            5 :     size_t dwords = w.len / 4;
      36            5 :     size_t off = 0;
      37            5 :     if (dwords < 0x7F) { out[0] = (uint8_t)dwords; off = 1; }
      38              :     else {
      39            0 :         out[0] = 0x7F;
      40            0 :         out[1] = (uint8_t)dwords;
      41            0 :         out[2] = (uint8_t)(dwords >> 8);
      42            0 :         out[3] = (uint8_t)(dwords >> 16);
      43            0 :         off = 4;
      44              :     }
      45            5 :     memcpy(out + off, w.data, w.len);
      46            5 :     *out_len = off + w.len;
      47            5 :     tl_writer_free(&w);
      48            5 : }
      49              : 
      50            8 : static void fix_session(MtProtoSession *s) {
      51            8 :     mtproto_session_init(s);
      52            8 :     s->session_id = 0; /* match the zero session_id in fake encrypted frames */
      53            8 :     uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
      54            8 :     mtproto_session_set_salt(s, 0xDEADBEEFDEADBEEFULL);
      55            8 : }
      56            8 : static void fix_transport(Transport *t) {
      57            8 :     transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
      58            8 : }
      59            8 : static void fix_cfg(ApiConfig *cfg) {
      60            8 :     api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
      61            8 : }
      62              : 
      63              : /* ---- edit ---- */
      64              : 
      65            1 : static void test_edit_updates_envelope(void) {
      66            1 :     mock_socket_reset(); mock_crypto_reset();
      67              : 
      68            1 :     TlWriter w; tl_writer_init(&w);
      69            1 :     tl_write_uint32(&w, TL_updateShort);
      70            1 :     tl_write_uint32(&w, 0); tl_write_int32(&w, 1);
      71            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
      72            1 :     size_t plen = w.len; tl_writer_free(&w);
      73              : 
      74            1 :     uint8_t resp[256]; size_t rlen = 0;
      75            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
      76            1 :     mock_socket_set_response(resp, rlen);
      77              : 
      78              :     MtProtoSession s; Transport t; ApiConfig cfg;
      79            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      80            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
      81            1 :     int rc = domain_edit_message(&cfg, &s, &t, &peer, 42, "fixed", NULL);
      82            1 :     ASSERT(rc == 0, "edit accepts Updates envelope");
      83              : }
      84              : 
      85            1 : static void test_edit_rejects_bad_args(void) {
      86              :     MtProtoSession s; Transport t; ApiConfig cfg;
      87            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      88            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
      89            1 :     ASSERT(domain_edit_message(&cfg, &s, &t, &peer, 0, "x", NULL) == -1,
      90              :            "msg_id=0 rejected");
      91            1 :     ASSERT(domain_edit_message(&cfg, &s, &t, &peer, 10, "", NULL) == -1,
      92              :            "empty text rejected");
      93            1 :     ASSERT(domain_edit_message(NULL, NULL, NULL, NULL, 1, "x", NULL) == -1,
      94              :            "null args");
      95              : }
      96              : 
      97              : /* ---- delete ---- */
      98              : 
      99            1 : static void test_delete_affected_messages(void) {
     100            1 :     mock_socket_reset(); mock_crypto_reset();
     101              : 
     102            1 :     TlWriter w; tl_writer_init(&w);
     103            1 :     tl_write_uint32(&w, TL_messages_affectedMessages);
     104            1 :     tl_write_int32 (&w, 5); tl_write_int32(&w, 1);
     105            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     106            1 :     size_t plen = w.len; tl_writer_free(&w);
     107              : 
     108            1 :     uint8_t resp[256]; size_t rlen = 0;
     109            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     110            1 :     mock_socket_set_response(resp, rlen);
     111              : 
     112              :     MtProtoSession s; Transport t; ApiConfig cfg;
     113            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     114            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     115            1 :     int32_t ids[] = { 101, 102, 103 };
     116            1 :     int rc = domain_delete_messages(&cfg, &s, &t, &peer, ids, 3, 1, NULL);
     117            1 :     ASSERT(rc == 0, "delete accepts affectedMessages");
     118              : }
     119              : 
     120            1 : static void test_delete_channel_dispatch(void) {
     121            1 :     mock_socket_reset(); mock_crypto_reset();
     122            1 :     TlWriter w; tl_writer_init(&w);
     123            1 :     tl_write_uint32(&w, TL_messages_affectedMessages);
     124            1 :     tl_write_int32 (&w, 1); tl_write_int32(&w, 1);
     125            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     126            1 :     size_t plen = w.len; tl_writer_free(&w);
     127            1 :     uint8_t resp[256]; size_t rlen = 0;
     128            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     129            1 :     mock_socket_set_response(resp, rlen);
     130              : 
     131              :     MtProtoSession s; Transport t; ApiConfig cfg;
     132            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     133            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_CHANNEL,
     134              :                           .peer_id = 1, .access_hash = 2 };
     135            1 :     int32_t ids[] = { 55 };
     136            1 :     int rc = domain_delete_messages(&cfg, &s, &t, &peer, ids, 1, 0, NULL);
     137            1 :     ASSERT(rc == 0, "channel delete ok");
     138              : 
     139              :     /* Verify channels.deleteMessages CRC appears in the outbound buffer. */
     140            1 :     size_t sent_len = 0;
     141            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     142            1 :     uint32_t want = 0x84c1fd4eu;
     143            1 :     int found = 0;
     144          114 :     for (size_t i = 0; i + 4 <= sent_len; i++)
     145          114 :         if (memcmp(sent + i, &want, 4) == 0) { found = 1; break; }
     146            1 :     ASSERT(found, "channels.deleteMessages CRC transmitted");
     147              : }
     148              : 
     149            1 : static void test_delete_rejects_bad_args(void) {
     150              :     MtProtoSession s; Transport t; ApiConfig cfg;
     151            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     152            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     153            1 :     int32_t ids[1] = { 1 };
     154            1 :     ASSERT(domain_delete_messages(&cfg, &s, &t, &peer, NULL, 1, 0, NULL) == -1,
     155              :            "null ids");
     156            1 :     ASSERT(domain_delete_messages(&cfg, &s, &t, &peer, ids, 0, 0, NULL) == -1,
     157              :            "n_ids=0");
     158            1 :     ASSERT(domain_delete_messages(&cfg, &s, &t, &peer, ids, 999, 0, NULL) == -1,
     159              :            "n_ids too large");
     160              : }
     161              : 
     162              : /* ---- forward ---- */
     163              : 
     164            1 : static void test_forward_updates_envelope(void) {
     165            1 :     mock_socket_reset(); mock_crypto_reset();
     166              : 
     167            1 :     TlWriter w; tl_writer_init(&w);
     168            1 :     tl_write_uint32(&w, TL_updateShort);
     169            1 :     tl_write_uint32(&w, 0); tl_write_int32(&w, 1);
     170            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     171            1 :     size_t plen = w.len; tl_writer_free(&w);
     172              : 
     173            1 :     uint8_t resp[256]; size_t rlen = 0;
     174            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     175            1 :     mock_socket_set_response(resp, rlen);
     176              : 
     177              :     MtProtoSession s; Transport t; ApiConfig cfg;
     178            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     179            1 :     HistoryPeer from = { .kind = HISTORY_PEER_SELF };
     180            1 :     HistoryPeer to   = { .kind = HISTORY_PEER_USER,
     181              :                           .peer_id = 10, .access_hash = 20 };
     182            1 :     int32_t ids[] = { 1 };
     183            1 :     int rc = domain_forward_messages(&cfg, &s, &t, &from, &to, ids, 1, NULL);
     184            1 :     ASSERT(rc == 0, "forward accepts Updates envelope");
     185              : }
     186              : 
     187            1 : static void test_forward_rejects_bad_args(void) {
     188              :     MtProtoSession s; Transport t; ApiConfig cfg;
     189            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     190            1 :     HistoryPeer from = { .kind = HISTORY_PEER_SELF };
     191            1 :     int32_t ids[1] = { 1 };
     192            1 :     ASSERT(domain_forward_messages(&cfg, &s, &t, &from, NULL, ids, 1, NULL) == -1,
     193              :            "null to");
     194            1 :     ASSERT(domain_forward_messages(&cfg, &s, &t, &from, &from, NULL, 1, NULL) == -1,
     195              :            "null ids");
     196              : }
     197              : 
     198              : /* ---- reply via domain_send_message_reply ---- */
     199              : 
     200            1 : static void test_send_reply_embeds_reply_to(void) {
     201            1 :     mock_socket_reset(); mock_crypto_reset();
     202              : 
     203            1 :     TlWriter w; tl_writer_init(&w);
     204            1 :     tl_write_uint32(&w, 0x9015e101u);        /* updateShortSentMessage */
     205            1 :     tl_write_uint32(&w, 0);
     206            1 :     tl_write_int32 (&w, 888);
     207            1 :     tl_write_int32 (&w, 1);
     208            1 :     tl_write_int32 (&w, 1);
     209            1 :     tl_write_int32 (&w, 1700000000);
     210            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     211            1 :     size_t plen = w.len; tl_writer_free(&w);
     212            1 :     uint8_t resp[256]; size_t rlen = 0;
     213            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     214            1 :     mock_socket_set_response(resp, rlen);
     215              : 
     216              :     MtProtoSession s; Transport t; ApiConfig cfg;
     217            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     218            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     219            1 :     int32_t id = -1;
     220            1 :     int rc = domain_send_message_reply(&cfg, &s, &t, &peer,
     221              :                                          "thread reply", 1234, &id, NULL);
     222            1 :     ASSERT(rc == 0, "reply send ok");
     223            1 :     ASSERT(id == 888, "id captured");
     224              : 
     225              :     /* inputReplyToMessage CRC must be present in the transmitted buffer. */
     226            1 :     size_t sent_len = 0;
     227            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     228            1 :     uint32_t want = 0x22c0f6d5u;
     229            1 :     int found = 0;
     230          126 :     for (size_t i = 0; i + 4 <= sent_len; i++)
     231          126 :         if (memcmp(sent + i, &want, 4) == 0) { found = 1; break; }
     232            1 :     ASSERT(found, "inputReplyToMessage CRC transmitted");
     233              : }
     234              : 
     235            1 : void run_domain_edit_delete_forward_tests(void) {
     236            1 :     RUN_TEST(test_edit_updates_envelope);
     237            1 :     RUN_TEST(test_edit_rejects_bad_args);
     238            1 :     RUN_TEST(test_delete_affected_messages);
     239            1 :     RUN_TEST(test_delete_channel_dispatch);
     240            1 :     RUN_TEST(test_delete_rejects_bad_args);
     241            1 :     RUN_TEST(test_forward_updates_envelope);
     242            1 :     RUN_TEST(test_forward_rejects_bad_args);
     243            1 :     RUN_TEST(test_send_reply_embeds_reply_to);
     244            1 : }
        

Generated by: LCOV version 2.0-1