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