Line data Source code
1 : /**
2 : * @file test_domain_read_history.c
3 : * @brief Unit tests for domain_mark_read (US-P5-04).
4 : */
5 :
6 : #include "test_helpers.h"
7 : #include "domain/write/read_history.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 4 : static void fix_session(MtProtoSession *s) {
48 4 : mtproto_session_init(s);
49 4 : s->session_id = 0; /* match the zero session_id in fake encrypted frames */
50 4 : uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
51 4 : mtproto_session_set_salt(s, 0xBADCAFEDEADBEEFULL);
52 4 : }
53 4 : static void fix_transport(Transport *t) {
54 4 : transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
55 4 : }
56 4 : static void fix_cfg(ApiConfig *cfg) {
57 4 : api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
58 4 : }
59 :
60 1 : static void test_mark_read_self(void) {
61 1 : mock_socket_reset(); mock_crypto_reset();
62 :
63 1 : TlWriter w; tl_writer_init(&w);
64 1 : tl_write_uint32(&w, TL_messages_affectedMessages);
65 1 : tl_write_int32 (&w, 10); /* pts */
66 1 : tl_write_int32 (&w, 2); /* pts_count */
67 1 : uint8_t payload[64]; memcpy(payload, w.data, w.len);
68 1 : size_t plen = w.len; tl_writer_free(&w);
69 :
70 1 : uint8_t resp[256]; size_t rlen = 0;
71 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
72 1 : mock_socket_set_response(resp, rlen);
73 :
74 : MtProtoSession s; Transport t; ApiConfig cfg;
75 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
76 1 : HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
77 1 : int rc = domain_mark_read(&cfg, &s, &t, &peer, 100, NULL);
78 1 : ASSERT(rc == 0, "mark_read accepts messages.affectedMessages");
79 : }
80 :
81 1 : static void test_mark_read_channel_bool_true(void) {
82 1 : mock_socket_reset(); mock_crypto_reset();
83 :
84 1 : TlWriter w; tl_writer_init(&w);
85 1 : tl_write_uint32(&w, TL_boolTrue);
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 1 : HistoryPeer peer = { .kind = HISTORY_PEER_CHANNEL,
96 : .peer_id = 1, .access_hash = 2 };
97 1 : int rc = domain_mark_read(&cfg, &s, &t, &peer, 50, NULL);
98 1 : ASSERT(rc == 0, "mark_read accepts boolTrue on channel");
99 : }
100 :
101 1 : static void test_mark_read_channel_bool_false(void) {
102 1 : mock_socket_reset(); mock_crypto_reset();
103 :
104 1 : TlWriter w; tl_writer_init(&w);
105 1 : tl_write_uint32(&w, TL_boolFalse);
106 1 : uint8_t payload[32]; memcpy(payload, w.data, w.len);
107 1 : size_t plen = w.len; tl_writer_free(&w);
108 :
109 1 : uint8_t resp[128]; size_t rlen = 0;
110 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
111 1 : mock_socket_set_response(resp, rlen);
112 :
113 : MtProtoSession s; Transport t; ApiConfig cfg;
114 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
115 1 : HistoryPeer peer = { .kind = HISTORY_PEER_CHANNEL,
116 : .peer_id = 1, .access_hash = 2 };
117 1 : int rc = domain_mark_read(&cfg, &s, &t, &peer, 50, NULL);
118 1 : ASSERT(rc == -1, "mark_read rejects boolFalse on channel");
119 : }
120 :
121 1 : static void test_mark_read_rpc_error(void) {
122 1 : mock_socket_reset(); mock_crypto_reset();
123 :
124 1 : TlWriter w; tl_writer_init(&w);
125 1 : tl_write_uint32(&w, TL_rpc_error);
126 1 : tl_write_int32 (&w, 403);
127 1 : tl_write_string(&w, "CHAT_ADMIN_REQUIRED");
128 1 : uint8_t payload[64]; memcpy(payload, w.data, w.len);
129 1 : size_t plen = w.len; tl_writer_free(&w);
130 :
131 1 : uint8_t resp[256]; size_t rlen = 0;
132 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
133 1 : mock_socket_set_response(resp, rlen);
134 :
135 : MtProtoSession s; Transport t; ApiConfig cfg;
136 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
137 1 : HistoryPeer peer = { .kind = HISTORY_PEER_CHAT,
138 : .peer_id = 42 };
139 1 : RpcError err = {0};
140 1 : int rc = domain_mark_read(&cfg, &s, &t, &peer, 0, &err);
141 1 : ASSERT(rc != 0, "RPC error propagates");
142 1 : ASSERT(err.error_code == 403, "error_code captured");
143 : }
144 :
145 1 : static void test_mark_read_null_args(void) {
146 1 : ASSERT(domain_mark_read(NULL, NULL, NULL, NULL, 0, NULL) == -1,
147 : "null args");
148 : }
149 :
150 1 : void run_domain_read_history_tests(void) {
151 1 : RUN_TEST(test_mark_read_self);
152 1 : RUN_TEST(test_mark_read_channel_bool_true);
153 1 : RUN_TEST(test_mark_read_channel_bool_false);
154 1 : RUN_TEST(test_mark_read_rpc_error);
155 1 : RUN_TEST(test_mark_read_null_args);
156 1 : }
|