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 : }
|