Line data Source code
1 : /**
2 : * @file test_auth_logout.c
3 : * @brief Unit tests for auth_logout.c (auth.logOut RPC).
4 : *
5 : * Tests:
6 : * 1. TL request byte layout for auth.logOut is correct.
7 : * 2. auth.loggedOut response without future_auth_token is accepted.
8 : * 3. auth.loggedOut response with future_auth_token flag set is accepted
9 : * (token ignored).
10 : * 4. RPC error with error_code 401 (NOT_AUTHORIZED) is treated as success.
11 : * 5. Generic RPC error returns -1.
12 : * 6. Network failure (no response) returns -1.
13 : * 7. auth_logout() clears the session file even when the RPC fails.
14 : */
15 :
16 : #include "test_helpers.h"
17 : #include "auth_logout.h"
18 : #include "mtproto_session.h"
19 : #include "transport.h"
20 : #include "tl_serial.h"
21 : #include "tl_registry.h"
22 : #include "mock_socket.h"
23 : #include "mock_crypto.h"
24 :
25 : #include <string.h>
26 : #include <stdlib.h>
27 : #include <stdint.h>
28 :
29 : /* ---- Helpers shared with test_auth_session ---- */
30 :
31 5 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
32 : uint8_t *out, size_t *out_len) {
33 : TlWriter w;
34 5 : tl_writer_init(&w);
35 :
36 5 : uint8_t zeros24[24] = {0};
37 5 : tl_write_raw(&w, zeros24, 24);
38 :
39 5 : uint8_t header[32] = {0};
40 5 : uint32_t plen32 = (uint32_t)plen;
41 5 : memcpy(header + 28, &plen32, 4);
42 5 : tl_write_raw(&w, header, 32);
43 5 : tl_write_raw(&w, payload, plen);
44 :
45 5 : size_t total = w.len;
46 5 : size_t payload_start = 24;
47 5 : size_t enc_part = total - payload_start;
48 5 : if (enc_part % 16 != 0) {
49 4 : size_t pad = 16 - (enc_part % 16);
50 4 : uint8_t zeros[16] = {0};
51 4 : tl_write_raw(&w, zeros, pad);
52 : }
53 :
54 5 : size_t wire_bytes = w.len;
55 5 : size_t wire_units = wire_bytes / 4;
56 :
57 5 : uint8_t *result = (uint8_t *)malloc(1 + wire_bytes);
58 5 : result[0] = (uint8_t)wire_units;
59 5 : memcpy(result + 1, w.data, wire_bytes);
60 5 : *out_len = 1 + wire_bytes;
61 5 : memcpy(out, result, *out_len);
62 5 : free(result);
63 5 : tl_writer_free(&w);
64 5 : }
65 :
66 6 : static void session_setup(MtProtoSession *s) {
67 6 : mtproto_session_init(s);
68 6 : s->session_id = 0; /* match the zero session_id in fake encrypted frames */
69 6 : uint8_t fake_key[256] = {0};
70 6 : mtproto_session_set_auth_key(s, fake_key);
71 6 : mtproto_session_set_salt(s, 0x1122334455667788ULL);
72 6 : }
73 :
74 6 : static void setup_session_and_transport(MtProtoSession *s, Transport *t,
75 : ApiConfig *cfg) {
76 6 : session_setup(s);
77 6 : transport_init(t);
78 6 : t->fd = 42; t->connected = 1; t->dc_id = 1;
79 6 : api_config_init(cfg);
80 6 : cfg->api_id = 12345; cfg->api_hash = "deadbeef";
81 6 : }
82 :
83 : /* ---- Test 1: TL request has correct CRC ---- */
84 1 : static void test_logout_request_crc(void) {
85 1 : mock_socket_reset();
86 1 : mock_crypto_reset();
87 :
88 : /* Build a minimal loggedOut response so api_call succeeds */
89 : uint8_t payload[16];
90 : TlWriter w;
91 1 : tl_writer_init(&w);
92 1 : tl_write_uint32(&w, CRC_auth_loggedOut);
93 1 : tl_write_uint32(&w, 0); /* flags = 0 */
94 1 : memcpy(payload, w.data, w.len);
95 1 : size_t plen = w.len;
96 1 : tl_writer_free(&w);
97 :
98 1 : uint8_t resp_buf[512]; size_t resp_len = 0;
99 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
100 1 : mock_socket_set_response(resp_buf, resp_len);
101 :
102 : MtProtoSession s; Transport t; ApiConfig cfg;
103 1 : setup_session_and_transport(&s, &t, &cfg);
104 :
105 1 : int rc = auth_logout_rpc(&cfg, &s, &t);
106 1 : ASSERT(rc == 0, "logout_rpc: must succeed with loggedOut response");
107 :
108 : /* Verify the sent bytes contain the auth.logOut CRC (0x3e72ba19). */
109 1 : size_t sent_len = 0;
110 1 : const uint8_t *sent = mock_socket_get_sent(&sent_len);
111 1 : ASSERT(sent_len > 4, "logout_rpc: must have sent data");
112 :
113 : /* The CRC appears inside the encrypted+wrapped payload, but with mock
114 : * passthrough crypto we can search the raw bytes for the little-endian
115 : * CRC_auth_logOut = 0x3e72ba19 => bytes 19 ba 72 3e. */
116 1 : uint8_t expected[4] = {0x19, 0xba, 0x72, 0x3e};
117 1 : int found = 0;
118 114 : for (size_t i = 0; i + 4 <= sent_len; i++) {
119 114 : if (memcmp(sent + i, expected, 4) == 0) { found = 1; break; }
120 : }
121 1 : ASSERT(found, "logout_rpc: sent bytes must contain auth.logOut CRC");
122 : (void)sent;
123 : }
124 :
125 : /* ---- Test 2: auth.loggedOut with no token (flags=0) accepted ---- */
126 1 : static void test_logged_out_no_token(void) {
127 1 : mock_socket_reset();
128 1 : mock_crypto_reset();
129 :
130 : uint8_t payload[16];
131 : TlWriter w;
132 1 : tl_writer_init(&w);
133 1 : tl_write_uint32(&w, CRC_auth_loggedOut);
134 1 : tl_write_uint32(&w, 0); /* flags = 0, no future_auth_token */
135 1 : memcpy(payload, w.data, w.len);
136 1 : size_t plen = w.len;
137 1 : tl_writer_free(&w);
138 :
139 1 : uint8_t resp_buf[512]; size_t resp_len = 0;
140 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
141 1 : mock_socket_set_response(resp_buf, resp_len);
142 :
143 : MtProtoSession s; Transport t; ApiConfig cfg;
144 1 : setup_session_and_transport(&s, &t, &cfg);
145 :
146 1 : int rc = auth_logout_rpc(&cfg, &s, &t);
147 1 : ASSERT(rc == 0, "logout_rpc: loggedOut no-token must succeed");
148 : }
149 :
150 : /* ---- Test 3: auth.loggedOut with future_auth_token flag set is accepted ---- */
151 1 : static void test_logged_out_with_token(void) {
152 1 : mock_socket_reset();
153 1 : mock_crypto_reset();
154 :
155 : uint8_t payload[64];
156 : TlWriter w;
157 1 : tl_writer_init(&w);
158 1 : tl_write_uint32(&w, CRC_auth_loggedOut);
159 1 : tl_write_uint32(&w, 1u); /* flags.0 = future_auth_token present */
160 : /* future_auth_token: bytes (TL bytes type — length-prefixed) */
161 1 : uint8_t token[8] = {0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44};
162 1 : tl_write_bytes(&w, token, sizeof(token));
163 1 : memcpy(payload, w.data, w.len);
164 1 : size_t plen = w.len;
165 1 : tl_writer_free(&w);
166 :
167 1 : uint8_t resp_buf[512]; size_t resp_len = 0;
168 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
169 1 : mock_socket_set_response(resp_buf, resp_len);
170 :
171 : MtProtoSession s; Transport t; ApiConfig cfg;
172 1 : setup_session_and_transport(&s, &t, &cfg);
173 :
174 1 : int rc = auth_logout_rpc(&cfg, &s, &t);
175 1 : ASSERT(rc == 0, "logout_rpc: loggedOut with future_auth_token must succeed");
176 : }
177 :
178 : /* ---- Test 4: RPC error 401 (NOT_AUTHORIZED) treated as success ---- */
179 1 : static void test_logout_rpc_401_is_ok(void) {
180 1 : mock_socket_reset();
181 1 : mock_crypto_reset();
182 :
183 : uint8_t payload[128];
184 : TlWriter w;
185 1 : tl_writer_init(&w);
186 1 : tl_write_uint32(&w, TL_rpc_error);
187 1 : tl_write_int32(&w, 401);
188 1 : tl_write_string(&w, "AUTH_KEY_UNREGISTERED");
189 1 : memcpy(payload, w.data, w.len);
190 1 : size_t plen = w.len;
191 1 : tl_writer_free(&w);
192 :
193 1 : uint8_t resp_buf[512]; size_t resp_len = 0;
194 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
195 1 : mock_socket_set_response(resp_buf, resp_len);
196 :
197 : MtProtoSession s; Transport t; ApiConfig cfg;
198 1 : setup_session_and_transport(&s, &t, &cfg);
199 :
200 1 : int rc = auth_logout_rpc(&cfg, &s, &t);
201 1 : ASSERT(rc == 0, "logout_rpc: 401 NOT_AUTHORIZED must be treated as success");
202 : }
203 :
204 : /* ---- Test 5: Generic RPC error (non-401) returns -1 ---- */
205 1 : static void test_logout_rpc_generic_error(void) {
206 1 : mock_socket_reset();
207 1 : mock_crypto_reset();
208 :
209 : uint8_t payload[128];
210 : TlWriter w;
211 1 : tl_writer_init(&w);
212 1 : tl_write_uint32(&w, TL_rpc_error);
213 1 : tl_write_int32(&w, 500);
214 1 : tl_write_string(&w, "INTERNAL");
215 1 : memcpy(payload, w.data, w.len);
216 1 : size_t plen = w.len;
217 1 : tl_writer_free(&w);
218 :
219 1 : uint8_t resp_buf[512]; size_t resp_len = 0;
220 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
221 1 : mock_socket_set_response(resp_buf, resp_len);
222 :
223 : MtProtoSession s; Transport t; ApiConfig cfg;
224 1 : setup_session_and_transport(&s, &t, &cfg);
225 :
226 1 : int rc = auth_logout_rpc(&cfg, &s, &t);
227 1 : ASSERT(rc != 0, "logout_rpc: non-401 RPC error must return -1");
228 : }
229 :
230 : /* ---- Test 6: Network failure returns -1 ---- */
231 1 : static void test_logout_rpc_network_failure(void) {
232 1 : mock_socket_reset();
233 1 : mock_crypto_reset();
234 : /* No response queued — recv returns 0 — api_call fails. */
235 :
236 : MtProtoSession s; Transport t; ApiConfig cfg;
237 1 : setup_session_and_transport(&s, &t, &cfg);
238 :
239 1 : int rc = auth_logout_rpc(&cfg, &s, &t);
240 1 : ASSERT(rc != 0, "logout_rpc: network failure must return -1");
241 : }
242 :
243 : /* ---- Test 7: null args rejected ---- */
244 1 : static void test_logout_null_args(void) {
245 1 : int rc = auth_logout_rpc(NULL, NULL, NULL);
246 1 : ASSERT(rc != 0, "logout_rpc: null args must return -1");
247 : }
248 :
249 1 : void run_auth_logout_tests(void) {
250 1 : RUN_TEST(test_logout_request_crc);
251 1 : RUN_TEST(test_logged_out_no_token);
252 1 : RUN_TEST(test_logged_out_with_token);
253 1 : RUN_TEST(test_logout_rpc_401_is_ok);
254 1 : RUN_TEST(test_logout_rpc_generic_error);
255 1 : RUN_TEST(test_logout_rpc_network_failure);
256 1 : RUN_TEST(test_logout_null_args);
257 1 : }
|