Line data Source code
1 : /**
2 : * @file test_dc_session_cache_skip.c
3 : * @brief TEST-21 — cached DC session skips DH handshake on second open.
4 : *
5 : * Validates US-15 acceptance criterion:
6 : * "Cached foreign sessions skip the full handshake on every subsequent
7 : * request."
8 : *
9 : * Strategy:
10 : * 1. Seed the home DC-2 session and a DC-4 foreign session on disk via
11 : * mt_server_seed_session + mt_server_seed_extra_dc. This simulates
12 : * a prior run that performed the full DH handshake + auth.importAuth
13 : * and persisted the resulting key.
14 : * 2. Call dc_session_open(4) — the fast path (session_store_load_dc)
15 : * must be taken; zero DH-handshake CRCs are expected.
16 : * 3. Close the session and call dc_session_open(4) a second time — the
17 : * persisted key is still on disk; again zero DH CRCs expected.
18 : *
19 : * DH handshake CRCs tracked:
20 : * req_pq_multi 0xbe7e8ef1
21 : * req_pq 0x60469778
22 : * req_DH_params 0xd712e4be
23 : * set_client_DH_params 0xf5045f1f
24 : */
25 :
26 : #include "test_helpers.h"
27 : #include "mock_tel_server.h"
28 : #include "mock_socket.h"
29 :
30 : #include "app/dc_session.h"
31 : #include "app/session_store.h"
32 : #include "transport.h"
33 : #include "mtproto_session.h"
34 :
35 : #include <stdlib.h>
36 : #include <string.h>
37 : #include <sys/stat.h>
38 : #include <unistd.h>
39 :
40 : /* TL CRCs for MTProto DH-handshake messages (client → server). */
41 : #define CRC_req_pq_multi 0xbe7e8ef1U
42 : #define CRC_req_pq 0x60469778U
43 : #define CRC_req_DH_params 0xd712e4beU
44 : #define CRC_set_client_DH_params 0xf5045f1fU
45 :
46 6 : static void with_tmp_home(const char *tag) {
47 : char tmp[256];
48 6 : snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-dcsess-%s", tag);
49 : /* Ensure config dir exists (mkdir -p equivalent via three mkdir calls). */
50 : char cfg_dir[512];
51 6 : snprintf(cfg_dir, sizeof(cfg_dir), "%s/.config", tmp);
52 6 : (void)mkdir(tmp, 0700);
53 6 : (void)mkdir(cfg_dir, 0700);
54 6 : snprintf(cfg_dir, sizeof(cfg_dir), "%s/.config/tg-cli", tmp);
55 6 : (void)mkdir(cfg_dir, 0700);
56 : /* Wipe any leftover session file from a prior run. */
57 : char bin[600];
58 6 : snprintf(bin, sizeof(bin), "%s/session.bin", cfg_dir);
59 6 : (void)unlink(bin);
60 6 : setenv("HOME", tmp, 1);
61 6 : }
62 :
63 : /** Assert no DH-handshake frame was sent for the last dc_session_open call. */
64 6 : static void assert_no_handshake_crcs(void) {
65 6 : ASSERT(mt_server_request_crc_count(CRC_req_pq_multi) == 0,
66 : "no req_pq_multi (0xbe7e8ef1) — DH handshake NOT triggered");
67 6 : ASSERT(mt_server_request_crc_count(CRC_req_pq) == 0,
68 : "no req_pq (0x60469778) — DH handshake NOT triggered");
69 6 : ASSERT(mt_server_request_crc_count(CRC_req_DH_params) == 0,
70 : "no req_DH_params (0xd712e4be) — DH handshake NOT triggered");
71 6 : ASSERT(mt_server_request_crc_count(CRC_set_client_DH_params) == 0,
72 : "no set_client_DH_params (0xf5045f1f) — DH handshake NOT triggered");
73 : }
74 :
75 : /**
76 : * FT-21a — first dc_session_open on DC 4 with a pre-seeded session.
77 : *
78 : * dc_session_open must load from session_store (fast path) and open the
79 : * transport without sending any DH-handshake frames.
80 : */
81 2 : static void test_dc4_first_open_uses_cache(void) {
82 2 : with_tmp_home("first");
83 2 : mt_server_init();
84 2 : mt_server_reset();
85 :
86 : /* Seed home DC 2 and foreign DC 4 — simulates a prior successful run. */
87 2 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0,
88 : "seed home DC 2");
89 2 : ASSERT(mt_server_seed_extra_dc(4) == 0,
90 : "seed DC 4 session on disk");
91 :
92 : DcSession sess;
93 2 : ASSERT(dc_session_open(4, &sess) == 0,
94 : "dc_session_open(4) succeeds via cached key");
95 2 : ASSERT(sess.dc_id == 4, "dc_id is 4");
96 2 : ASSERT(sess.authorized == 1, "marked authorized (key was cached)");
97 :
98 : /* Core assertion: no DH handshake frames crossed the wire. */
99 2 : assert_no_handshake_crcs();
100 :
101 2 : dc_session_close(&sess);
102 2 : mt_server_reset();
103 : }
104 :
105 : /**
106 : * FT-21b — second dc_session_open on DC 4: session persisted by prior call.
107 : *
108 : * After FT-21a the DC-4 key is still in session.bin. A second open must
109 : * again take the fast path (zero handshake RPCs).
110 : */
111 2 : static void test_dc4_second_open_skips_handshake(void) {
112 2 : with_tmp_home("second");
113 2 : mt_server_init();
114 2 : mt_server_reset();
115 :
116 : /* Seed home DC 2 and DC 4. */
117 2 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0,
118 : "seed home DC 2");
119 2 : ASSERT(mt_server_seed_extra_dc(4) == 0,
120 : "seed DC 4 session on disk");
121 :
122 : /* First open — fast path, establishes session + closes. */
123 : DcSession sess1;
124 2 : ASSERT(dc_session_open(4, &sess1) == 0, "first dc_session_open ok");
125 2 : dc_session_close(&sess1);
126 :
127 : /* Reset CRC counters so the second open is measured in isolation. */
128 2 : mt_server_reset();
129 : /* Re-seed so the mock can handle the new transport connection. */
130 2 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0,
131 : "re-seed DC 2 after reset");
132 : /* Note: mt_server_reset wipes seeded flag but session.bin persists on
133 : * disk because HOME is still the same tmpdir. The server only needs to
134 : * know the key for encrypted-frame decryption; mt_server_seed_session
135 : * restores g_srv.auth_key to the same deterministic bytes. */
136 :
137 : /* Second open — must skip handshake. */
138 : DcSession sess2;
139 2 : ASSERT(dc_session_open(4, &sess2) == 0,
140 : "second dc_session_open(4) succeeds via cached key");
141 2 : ASSERT(sess2.authorized == 1, "still marked authorized");
142 :
143 : /* Core assertion: second open sent zero DH frames. */
144 2 : assert_no_handshake_crcs();
145 :
146 2 : dc_session_close(&sess2);
147 2 : mt_server_reset();
148 : }
149 :
150 : /**
151 : * FT-21c — dc_session_open on DC 4 sets authorized=1 when key is cached.
152 : *
153 : * Verifies that the fast-path branch in dc_session.c sets out->authorized=1
154 : * so that callers need not run auth.importAuthorization again.
155 : */
156 2 : static void test_dc4_cache_hit_authorized_flag(void) {
157 2 : with_tmp_home("authflag");
158 2 : mt_server_init();
159 2 : mt_server_reset();
160 :
161 2 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0,
162 : "seed DC 2");
163 2 : ASSERT(mt_server_seed_extra_dc(4) == 0,
164 : "seed DC 4");
165 :
166 : DcSession sess;
167 2 : ASSERT(dc_session_open(4, &sess) == 0, "dc_session_open ok");
168 2 : ASSERT(sess.authorized == 1,
169 : "authorized flag is 1 when key loaded from cache");
170 2 : assert_no_handshake_crcs();
171 :
172 2 : dc_session_close(&sess);
173 2 : mt_server_reset();
174 : }
175 :
176 2 : void run_dc_session_cache_skip_tests(void) {
177 2 : RUN_TEST(test_dc4_first_open_uses_cache);
178 2 : RUN_TEST(test_dc4_second_open_skips_handshake);
179 2 : RUN_TEST(test_dc4_cache_hit_authorized_flag);
180 2 : }
|