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

            Line data    Source code
       1              : /**
       2              :  * @file test_domain_updates.c
       3              :  * @brief Unit tests for domain_updates_state / domain_updates_difference.
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "domain/read/updates.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            7 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
      20              :                                           uint8_t *out, size_t *out_len) {
      21            7 :     TlWriter w; tl_writer_init(&w);
      22            7 :     uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
      23            7 :     uint8_t header[32] = {0};
      24            7 :     uint32_t plen32 = (uint32_t)plen;
      25            7 :     memcpy(header + 28, &plen32, 4);
      26            7 :     tl_write_raw(&w, header, 32);
      27            7 :     tl_write_raw(&w, payload, plen);
      28            7 :     size_t enc = w.len - 24;
      29            7 :     if (enc % 16 != 0) {
      30            6 :         uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
      31              :     }
      32            7 :     out[0] = (uint8_t)(w.len / 4);
      33            7 :     memcpy(out + 1, w.data, w.len);
      34            7 :     *out_len = 1 + w.len;
      35            7 :     tl_writer_free(&w);
      36            7 : }
      37              : 
      38            7 : static void fix_session(MtProtoSession *s) {
      39            7 :     mtproto_session_init(s);
      40            7 :     s->session_id = 0; /* match the zero session_id in fake encrypted frames */
      41            7 :     uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
      42            7 :     mtproto_session_set_salt(s, 0xAABBCCDDEEFF1122ULL);
      43            7 : }
      44            7 : static void fix_transport(Transport *t) {
      45            7 :     transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
      46            7 : }
      47            7 : static void fix_cfg(ApiConfig *cfg) {
      48            7 :     api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
      49            7 : }
      50              : 
      51            1 : static void test_updates_state_parse(void) {
      52            1 :     mock_socket_reset(); mock_crypto_reset();
      53              : 
      54            1 :     TlWriter w; tl_writer_init(&w);
      55            1 :     tl_write_uint32(&w, TL_updates_state);
      56            1 :     tl_write_int32(&w, 100);  /* pts */
      57            1 :     tl_write_int32(&w, 0);    /* qts */
      58            1 :     tl_write_int32(&w, 1700000000); /* date */
      59            1 :     tl_write_int32(&w, 7);    /* seq */
      60            1 :     tl_write_int32(&w, 3);    /* unread_count */
      61            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
      62            1 :     size_t plen = w.len; tl_writer_free(&w);
      63              : 
      64            1 :     uint8_t resp[512]; size_t rlen = 0;
      65            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
      66            1 :     mock_socket_set_response(resp, rlen);
      67              : 
      68              :     MtProtoSession s; Transport t; ApiConfig cfg;
      69            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      70              : 
      71            1 :     UpdatesState st = {0};
      72            1 :     int rc = domain_updates_state(&cfg, &s, &t, &st);
      73            1 :     ASSERT(rc == 0, "updates.state parsed");
      74            1 :     ASSERT(st.pts == 100, "pts");
      75            1 :     ASSERT(st.seq == 7, "seq");
      76            1 :     ASSERT(st.unread_count == 3, "unread_count");
      77              : }
      78              : 
      79            1 : static void test_updates_difference_empty(void) {
      80            1 :     mock_socket_reset(); mock_crypto_reset();
      81              : 
      82            1 :     TlWriter w; tl_writer_init(&w);
      83            1 :     tl_write_uint32(&w, TL_updates_differenceEmpty);
      84            1 :     tl_write_int32(&w, 1700000100); /* date */
      85            1 :     tl_write_int32(&w, 8);          /* seq */
      86            1 :     uint8_t payload[32]; memcpy(payload, w.data, w.len);
      87            1 :     size_t plen = w.len; tl_writer_free(&w);
      88              : 
      89            1 :     uint8_t resp[128]; size_t rlen = 0;
      90            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
      91            1 :     mock_socket_set_response(resp, rlen);
      92              : 
      93              :     MtProtoSession s; Transport t; ApiConfig cfg;
      94            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
      95              : 
      96            1 :     UpdatesState in = { .pts=100, .qts=0, .date=1700000000, .seq=7 };
      97            1 :     UpdatesDifference diff = {0};
      98            1 :     int rc = domain_updates_difference(&cfg, &s, &t, &in, &diff);
      99            1 :     ASSERT(rc == 0, "differenceEmpty parsed");
     100            1 :     ASSERT(diff.is_empty == 1, "is_empty flag");
     101            1 :     ASSERT(diff.next_state.date == 1700000100, "new date propagated");
     102              : }
     103              : 
     104            1 : static void test_updates_rpc_error(void) {
     105            1 :     mock_socket_reset(); mock_crypto_reset();
     106              : 
     107            1 :     TlWriter w; tl_writer_init(&w);
     108            1 :     tl_write_uint32(&w, TL_rpc_error);
     109            1 :     tl_write_int32(&w, 401);
     110            1 :     tl_write_string(&w, "AUTH_KEY_UNREGISTERED");
     111            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     112            1 :     size_t plen = w.len; tl_writer_free(&w);
     113              : 
     114            1 :     uint8_t resp[256]; size_t rlen = 0;
     115            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     116            1 :     mock_socket_set_response(resp, rlen);
     117              : 
     118              :     MtProtoSession s; Transport t; ApiConfig cfg;
     119            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     120              : 
     121            1 :     UpdatesState st = {0};
     122            1 :     int rc = domain_updates_state(&cfg, &s, &t, &st);
     123            1 :     ASSERT(rc != 0, "RPC error propagates");
     124              : }
     125              : 
     126            1 : static void test_updates_difference_messages(void) {
     127            1 :     mock_socket_reset(); mock_crypto_reset();
     128              : 
     129              :     /* updates.difference with 2 simple new messages, then empty
     130              :      * Vectors for encrypted/other/chats/users, then a state tail. */
     131            1 :     TlWriter w; tl_writer_init(&w);
     132            1 :     tl_write_uint32(&w, TL_updates_difference);
     133              : 
     134              :     /* new_messages */
     135            1 :     tl_write_uint32(&w, TL_vector);
     136            1 :     tl_write_uint32(&w, 2);
     137              :     /* msg 1 */
     138            1 :     tl_write_uint32(&w, TL_message);
     139            1 :     tl_write_uint32(&w, 0); tl_write_uint32(&w, 0);
     140            1 :     tl_write_int32 (&w, 501);
     141            1 :     tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 10LL);
     142            1 :     tl_write_int32 (&w, 1700000000);
     143            1 :     tl_write_string(&w, "alpha");
     144              :     /* msg 2 */
     145            1 :     tl_write_uint32(&w, TL_message);
     146            1 :     tl_write_uint32(&w, 0); tl_write_uint32(&w, 0);
     147            1 :     tl_write_int32 (&w, 502);
     148            1 :     tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 10LL);
     149            1 :     tl_write_int32 (&w, 1700000001);
     150            1 :     tl_write_string(&w, "beta");
     151              : 
     152            1 :     uint8_t payload[1024]; memcpy(payload, w.data, w.len);
     153            1 :     size_t plen = w.len; tl_writer_free(&w);
     154              : 
     155            1 :     uint8_t resp[2048]; size_t rlen = 0;
     156            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     157            1 :     mock_socket_set_response(resp, rlen);
     158              : 
     159              :     MtProtoSession s; Transport t; ApiConfig cfg;
     160            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     161              : 
     162            1 :     UpdatesState in = { .pts=10, .qts=0, .date=1700000000, .seq=1 };
     163            1 :     UpdatesDifference diff = {0};
     164            1 :     int rc = domain_updates_difference(&cfg, &s, &t, &in, &diff);
     165            1 :     ASSERT(rc == 0, "difference with messages parsed");
     166            1 :     ASSERT(diff.new_messages_count == 2, "both messages captured");
     167            1 :     ASSERT(diff.new_messages[0].id == 501, "msg0 id");
     168            1 :     ASSERT(strcmp(diff.new_messages[0].text, "alpha") == 0, "msg0 text");
     169            1 :     ASSERT(diff.new_messages[1].id == 502, "msg1 id");
     170            1 :     ASSERT(strcmp(diff.new_messages[1].text, "beta") == 0, "msg1 text");
     171              : }
     172              : 
     173            1 : static void test_updates_null_args(void) {
     174            1 :     ASSERT(domain_updates_state(NULL, NULL, NULL, NULL) == -1, "null rejected");
     175            1 :     ASSERT(domain_updates_difference(NULL, NULL, NULL, NULL, NULL) == -1,
     176              :            "null diff rejected");
     177              : }
     178              : 
     179              : /* ---- FEAT-14: peer_id is extracted from Message.peer_id ---- */
     180            1 : static void test_updates_difference_peer_id(void) {
     181            1 :     mock_socket_reset(); mock_crypto_reset();
     182              : 
     183              :     /* Two messages: one from peer 111, one from peer 222. */
     184            1 :     TlWriter w; tl_writer_init(&w);
     185            1 :     tl_write_uint32(&w, TL_updates_difference);
     186              : 
     187              :     /* new_messages */
     188            1 :     tl_write_uint32(&w, TL_vector);
     189            1 :     tl_write_uint32(&w, 2);
     190              :     /* msg 1: peer_id = peerUser 111 */
     191            1 :     tl_write_uint32(&w, TL_message);
     192            1 :     tl_write_uint32(&w, 0); tl_write_uint32(&w, 0); /* flags, flags2 */
     193            1 :     tl_write_int32 (&w, 601);                         /* id */
     194            1 :     tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 111LL); /* peer_id */
     195            1 :     tl_write_int32 (&w, 1700000010);                  /* date */
     196            1 :     tl_write_string(&w, "from 111");
     197              : 
     198              :     /* msg 2: peer_id = peerUser 222 */
     199            1 :     tl_write_uint32(&w, TL_message);
     200            1 :     tl_write_uint32(&w, 0); tl_write_uint32(&w, 0);
     201            1 :     tl_write_int32 (&w, 602);
     202            1 :     tl_write_uint32(&w, TL_peerUser); tl_write_int64(&w, 222LL);
     203            1 :     tl_write_int32 (&w, 1700000011);
     204            1 :     tl_write_string(&w, "from 222");
     205              : 
     206            1 :     uint8_t payload[1024]; memcpy(payload, w.data, w.len);
     207            1 :     size_t plen = w.len; tl_writer_free(&w);
     208              : 
     209            1 :     uint8_t resp[2048]; size_t rlen = 0;
     210            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     211            1 :     mock_socket_set_response(resp, rlen);
     212              : 
     213              :     MtProtoSession s; Transport t; ApiConfig cfg;
     214            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     215              : 
     216            1 :     UpdatesState in = { .pts=10, .qts=0, .date=1700000000, .seq=1 };
     217            1 :     UpdatesDifference diff = {0};
     218            1 :     int rc = domain_updates_difference(&cfg, &s, &t, &in, &diff);
     219            1 :     ASSERT(rc == 0, "peer_id: difference parsed ok");
     220            1 :     ASSERT(diff.new_messages_count == 2, "peer_id: two messages");
     221            1 :     ASSERT(diff.new_messages[0].peer_id == 111LL, "peer_id: msg0 peer_id==111");
     222            1 :     ASSERT(diff.new_messages[1].peer_id == 222LL, "peer_id: msg1 peer_id==222");
     223              : 
     224              :     /* Simulate the filter: only allow peer 111. */
     225            1 :     int matched = 0;
     226            3 :     for (int i = 0; i < diff.new_messages_count; i++) {
     227            2 :         if (diff.new_messages[i].peer_id == 111LL) {
     228            1 :             ASSERT(strcmp(diff.new_messages[i].text, "from 111") == 0,
     229              :                    "peer_id: msg from 111 has correct text");
     230            1 :             matched++;
     231              :         }
     232              :     }
     233            1 :     ASSERT(matched == 1, "peer_id: exactly one message from peer 111");
     234              : }
     235              : 
     236              : /* ---- FEAT-24: int64_t date — 2038-safety tests ---- */
     237              : 
     238              : /* A date value of 2^30 (1073741824) fits in int32 but is close to the 2038
     239              :  * boundary; verify it round-trips through HistoryEntry.date without truncation. */
     240            1 : static void test_date_large_value_roundtrip(void) {
     241            1 :     mock_socket_reset(); mock_crypto_reset();
     242              : 
     243            1 :     int32_t wire_date = (int32_t)(1 << 30); /* 1073741824 — fits in int32 */
     244              : 
     245            1 :     TlWriter w; tl_writer_init(&w);
     246            1 :     tl_write_uint32(&w, TL_updates_state);
     247            1 :     tl_write_int32(&w, 1);            /* pts */
     248            1 :     tl_write_int32(&w, 0);            /* qts */
     249            1 :     tl_write_int32(&w, wire_date);    /* date = 2^30 */
     250            1 :     tl_write_int32(&w, 1);            /* seq */
     251            1 :     tl_write_int32(&w, 0);            /* unread_count */
     252            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     253            1 :     size_t plen = w.len; tl_writer_free(&w);
     254              : 
     255            1 :     uint8_t resp[256]; size_t rlen = 0;
     256            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     257            1 :     mock_socket_set_response(resp, rlen);
     258              : 
     259              :     MtProtoSession s; Transport t; ApiConfig cfg;
     260            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     261              : 
     262            1 :     UpdatesState st = {0};
     263            1 :     int rc = domain_updates_state(&cfg, &s, &t, &st);
     264            1 :     ASSERT(rc == 0, "FEAT-24: large date parse ok");
     265            1 :     ASSERT(st.date == (int64_t)(1 << 30),
     266              :            "FEAT-24: large date (2^30) preserved as int64");
     267              : }
     268              : 
     269              : /* A negative int32 date sentinel (e.g. -1) must sign-extend correctly to
     270              :  * int64_t -1, not become a large positive value. */
     271            1 : static void test_date_negative_sentinel_preserved(void) {
     272            1 :     mock_socket_reset(); mock_crypto_reset();
     273              : 
     274            1 :     TlWriter w; tl_writer_init(&w);
     275            1 :     tl_write_uint32(&w, TL_updates_state);
     276            1 :     tl_write_int32(&w, 0);   /* pts */
     277            1 :     tl_write_int32(&w, 0);   /* qts */
     278            1 :     tl_write_int32(&w, -1);  /* date = -1 sentinel */
     279            1 :     tl_write_int32(&w, 0);   /* seq */
     280            1 :     tl_write_int32(&w, 0);   /* unread_count */
     281            1 :     uint8_t payload[64]; memcpy(payload, w.data, w.len);
     282            1 :     size_t plen = w.len; tl_writer_free(&w);
     283              : 
     284            1 :     uint8_t resp[256]; size_t rlen = 0;
     285            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     286            1 :     mock_socket_set_response(resp, rlen);
     287              : 
     288              :     MtProtoSession s; Transport t; ApiConfig cfg;
     289            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     290              : 
     291            1 :     UpdatesState st = {0};
     292            1 :     int rc = domain_updates_state(&cfg, &s, &t, &st);
     293            1 :     ASSERT(rc == 0, "FEAT-24: negative date parse ok");
     294            1 :     ASSERT(st.date == (int64_t)-1LL,
     295              :            "FEAT-24: negative sentinel (-1) sign-extended correctly to int64");
     296              : }
     297              : 
     298            1 : void run_domain_updates_tests(void) {
     299            1 :     RUN_TEST(test_updates_state_parse);
     300            1 :     RUN_TEST(test_updates_difference_empty);
     301            1 :     RUN_TEST(test_updates_rpc_error);
     302            1 :     RUN_TEST(test_updates_difference_messages);
     303            1 :     RUN_TEST(test_updates_null_args);
     304            1 :     RUN_TEST(test_updates_difference_peer_id);
     305            1 :     RUN_TEST(test_date_large_value_roundtrip);
     306            1 :     RUN_TEST(test_date_negative_sentinel_preserved);
     307            1 : }
        

Generated by: LCOV version 2.0-1