LCOV - code coverage report
Current view: top level - tests/unit - test_domain_send.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.2 % 131 126
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 10 10

            Line data    Source code
       1              : /**
       2              :  * @file test_domain_send.c
       3              :  * @brief Unit tests for domain_send_message (US-P5-03).
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "domain/write/send.h"
       8              : #include "tl_serial.h"
       9              : #include "tl_registry.h"
      10              : #include "mock_socket.h"
      11              : #include "mock_crypto.h"
      12              : #include "mtproto_session.h"
      13              : #include "transport.h"
      14              : #include "api_call.h"
      15              : 
      16              : #include <stdlib.h>
      17              : #include <string.h>
      18              : 
      19            4 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
      20              :                                           uint8_t *out, size_t *out_len) {
      21            4 :     TlWriter w; tl_writer_init(&w);
      22            4 :     uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
      23            4 :     uint8_t header[32] = {0};
      24            4 :     uint32_t plen32 = (uint32_t)plen;
      25            4 :     memcpy(header + 28, &plen32, 4);
      26            4 :     tl_write_raw(&w, header, 32);
      27            4 :     tl_write_raw(&w, payload, plen);
      28            4 :     size_t enc = w.len - 24;
      29            4 :     if (enc % 16 != 0) {
      30            4 :         uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
      31              :     }
      32            4 :     size_t dwords = w.len / 4;
      33            4 :     size_t off = 0;
      34            4 :     if (dwords < 0x7F) { out[0] = (uint8_t)dwords; off = 1; }
      35              :     else {
      36            0 :         out[0] = 0x7F;
      37            0 :         out[1] = (uint8_t)(dwords);
      38            0 :         out[2] = (uint8_t)(dwords >> 8);
      39            0 :         out[3] = (uint8_t)(dwords >> 16);
      40            0 :         off = 4;
      41              :     }
      42            4 :     memcpy(out + off, w.data, w.len);
      43            4 :     *out_len = off + w.len;
      44            4 :     tl_writer_free(&w);
      45            4 : }
      46              : 
      47            5 : static void fix_session(MtProtoSession *s) {
      48            5 :     mtproto_session_init(s);
      49            5 :     s->session_id = 0; /* match the zero session_id in fake encrypted frames */
      50            5 :     uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
      51            5 :     mtproto_session_set_salt(s, 0xDEADBEEFDEADBEEFULL);
      52            5 : }
      53            5 : static void fix_transport(Transport *t) {
      54            5 :     transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
      55            5 : }
      56            5 : static void fix_cfg(ApiConfig *cfg) {
      57            5 :     api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
      58            5 : }
      59              : 
      60              : #define CRC_updateShortSentMessage 0x9015e101u
      61              : 
      62              : /* Server acks sendMessage with updateShortSentMessage carrying id=777. */
      63            1 : static void test_send_self_parses_message_id(void) {
      64            1 :     mock_socket_reset(); mock_crypto_reset();
      65              : 
      66            1 :     TlWriter w; tl_writer_init(&w);
      67            1 :     tl_write_uint32(&w, CRC_updateShortSentMessage);
      68            1 :     tl_write_uint32(&w, 0);                /* flags */
      69            1 :     tl_write_int32 (&w, 777);              /* id */
      70            1 :     tl_write_int32 (&w, 1);                /* pts */
      71            1 :     tl_write_int32 (&w, 1);                /* pts_count */
      72            1 :     tl_write_int32 (&w, 1700000000);       /* date */
      73            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
      74            1 :     size_t plen = w.len; tl_writer_free(&w);
      75              : 
      76            1 :     uint8_t resp[256]; size_t rlen = 0;
      77            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
      78            1 :     mock_socket_set_response(resp, rlen);
      79              : 
      80              :     MtProtoSession s; Transport t; ApiConfig cfg;
      81            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      82              : 
      83            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
      84            1 :     int32_t msg_id = -1;
      85            1 :     RpcError err = {0};
      86            1 :     int rc = domain_send_message(&cfg, &s, &t, &peer, "hello", &msg_id, &err);
      87            1 :     ASSERT(rc == 0, "send returns ok");
      88            1 :     ASSERT(msg_id == 777, "outgoing message id captured");
      89              : }
      90              : 
      91              : /* Updates envelope (generic): rc == 0 but we don't try to parse id. */
      92            1 : static void test_send_generic_updates_envelope(void) {
      93            1 :     mock_socket_reset(); mock_crypto_reset();
      94              : 
      95            1 :     TlWriter w; tl_writer_init(&w);
      96            1 :     tl_write_uint32(&w, TL_updateShort);    /* simplest Updates variant */
      97            1 :     tl_write_uint32(&w, 0);                 /* update (skipped) */
      98            1 :     tl_write_int32 (&w, 1700000000);        /* date */
      99            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     100            1 :     size_t plen = w.len; tl_writer_free(&w);
     101              : 
     102            1 :     uint8_t resp[256]; size_t rlen = 0;
     103            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     104            1 :     mock_socket_set_response(resp, rlen);
     105              : 
     106              :     MtProtoSession s; Transport t; ApiConfig cfg;
     107            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     108              : 
     109            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     110            1 :     int32_t msg_id = 999;
     111            1 :     int rc = domain_send_message(&cfg, &s, &t, &peer, "ping", &msg_id, NULL);
     112            1 :     ASSERT(rc == 0, "updateShort envelope is accepted");
     113            1 :     ASSERT(msg_id == 0, "unknown envelope resets msg_id to 0");
     114              : }
     115              : 
     116            1 : static void test_send_rpc_error_propagates(void) {
     117            1 :     mock_socket_reset(); mock_crypto_reset();
     118              : 
     119            1 :     TlWriter w; tl_writer_init(&w);
     120            1 :     tl_write_uint32(&w, TL_rpc_error);
     121            1 :     tl_write_int32 (&w, 400);
     122            1 :     tl_write_string(&w, "PEER_ID_INVALID");
     123            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     124            1 :     size_t plen = w.len; tl_writer_free(&w);
     125              : 
     126            1 :     uint8_t resp[256]; size_t rlen = 0;
     127            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     128            1 :     mock_socket_set_response(resp, rlen);
     129              : 
     130              :     MtProtoSession s; Transport t; ApiConfig cfg;
     131            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     132              : 
     133            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_USER,
     134              :                           .peer_id = 1, .access_hash = 2 };
     135            1 :     RpcError err = {0};
     136            1 :     int rc = domain_send_message(&cfg, &s, &t, &peer, "hi", NULL, &err);
     137            1 :     ASSERT(rc == -1, "RPC error propagates");
     138            1 :     ASSERT(err.error_code == 400, "error_code captured");
     139            1 :     ASSERT(strcmp(err.error_msg, "PEER_ID_INVALID") == 0, "error_msg captured");
     140              : }
     141              : 
     142            1 : static void test_send_rejects_bad_inputs(void) {
     143              :     MtProtoSession s; Transport t; ApiConfig cfg;
     144            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     145            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     146              : 
     147            1 :     ASSERT(domain_send_message(NULL, NULL, NULL, NULL, NULL, NULL, NULL) == -1,
     148              :            "null args");
     149            1 :     ASSERT(domain_send_message(&cfg, &s, &t, &peer, "", NULL, NULL) == -1,
     150              :            "empty message rejected");
     151              : 
     152            1 :     char oversized[4100]; memset(oversized, 'A', sizeof(oversized) - 1);
     153            1 :     oversized[sizeof(oversized) - 1] = '\0';
     154            1 :     ASSERT(domain_send_message(&cfg, &s, &t, &peer, oversized, NULL, NULL) == -1,
     155              :            "message > 4096 chars rejected");
     156              : }
     157              : 
     158              : /* Verify the sent bytes carry messages.sendMessage CRC + the message text. */
     159            1 : static void test_send_writes_correct_query(void) {
     160            1 :     mock_socket_reset(); mock_crypto_reset();
     161              : 
     162            1 :     TlWriter w; tl_writer_init(&w);
     163            1 :     tl_write_uint32(&w, CRC_updateShortSentMessage);
     164            1 :     tl_write_uint32(&w, 0);
     165            1 :     tl_write_int32 (&w, 1);
     166            1 :     tl_write_int32 (&w, 1);
     167            1 :     tl_write_int32 (&w, 1);
     168            1 :     tl_write_int32 (&w, 1700000000);
     169            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     170            1 :     size_t plen = w.len; tl_writer_free(&w);
     171            1 :     uint8_t resp[256]; size_t rlen = 0;
     172            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     173            1 :     mock_socket_set_response(resp, rlen);
     174              : 
     175              :     MtProtoSession s; Transport t; ApiConfig cfg;
     176            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     177            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     178              : 
     179            1 :     int rc = domain_send_message(&cfg, &s, &t, &peer, "HELLO_WORLD",
     180              :                                   NULL, NULL);
     181            1 :     ASSERT(rc == 0, "send ok");
     182              : 
     183            1 :     size_t sent_len = 0;
     184            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     185            1 :     ASSERT(sent != NULL && sent_len > 0, "client transmitted bytes");
     186              :     /* Look for the literal message text in the encrypted payload — this
     187              :      * works because the mock AES block cipher is identity. */
     188            1 :     int found = 0;
     189          127 :     for (size_t i = 0; i + 11 <= sent_len; i++) {
     190          127 :         if (memcmp(sent + i, "HELLO_WORLD", 11) == 0) { found = 1; break; }
     191              :     }
     192            1 :     ASSERT(found, "message text appears in outbound wire buffer");
     193              : }
     194              : 
     195            1 : void run_domain_send_tests(void) {
     196            1 :     RUN_TEST(test_send_self_parses_message_id);
     197            1 :     RUN_TEST(test_send_generic_updates_envelope);
     198            1 :     RUN_TEST(test_send_rpc_error_propagates);
     199            1 :     RUN_TEST(test_send_rejects_bad_inputs);
     200            1 :     RUN_TEST(test_send_writes_correct_query);
     201            1 : }
        

Generated by: LCOV version 2.0-1