Line data Source code
1 : /**
2 : * @file test_mt_server_smoke.c
3 : * @brief End-to-end proof that the mock Telegram server round-trips
4 : * real AES-IGE + SHA-256 against the production client code.
5 : *
6 : * Wires up:
7 : * mock_socket (in-memory transport)
8 : * ↕
9 : * production rpc_send_encrypted / rpc_recv_encrypted (real crypto)
10 : * ↕
11 : * mock_tel_server (real crypto on the other side)
12 : */
13 :
14 : #include "test_helpers.h"
15 :
16 : #include "mock_socket.h"
17 : #include "mock_tel_server.h"
18 :
19 : #include "mtproto_rpc.h"
20 : #include "mtproto_session.h"
21 : #include "transport.h"
22 : #include "app/session_store.h"
23 : #include "tl_serial.h"
24 :
25 : #include <stdio.h>
26 : #include <stdlib.h>
27 : #include <string.h>
28 : #include <unistd.h>
29 :
30 : #define CRC_ping 0x7abe77ecU /* ping#7abe77ec ping_id:long = Pong; */
31 : #define CRC_pong 0x347773c5U /* pong#347773c5 msg_id:long ping_id:long = Pong; */
32 :
33 4 : static void with_tmp_home(const char *tag) {
34 : char tmp[256];
35 4 : snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-mt-server-%s", tag);
36 : char bin[512];
37 4 : snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
38 4 : (void)unlink(bin);
39 4 : setenv("HOME", tmp, 1);
40 4 : }
41 :
42 2 : static void on_ping(MtRpcContext *ctx) {
43 : /* ping body = CRC(4) ping_id:long — echo back pong with the same id. */
44 2 : if (ctx->req_body_len < 4 + 8) return;
45 2 : uint64_t ping_id = 0;
46 18 : for (int i = 0; i < 8; ++i) {
47 16 : ping_id |= ((uint64_t)ctx->req_body[4 + i]) << (i * 8);
48 : }
49 :
50 : TlWriter w;
51 2 : tl_writer_init(&w);
52 2 : tl_write_uint32(&w, CRC_pong);
53 2 : tl_write_uint64(&w, ctx->req_msg_id);
54 2 : tl_write_uint64(&w, ping_id);
55 2 : mt_server_reply_result(ctx, w.data, w.len);
56 2 : tl_writer_free(&w);
57 : }
58 :
59 2 : static void test_ping_pong_roundtrip(void) {
60 2 : with_tmp_home("ping-pong");
61 2 : mt_server_init();
62 2 : mt_server_reset();
63 :
64 : uint8_t auth_key[256];
65 2 : uint64_t salt = 0, sid = 0;
66 2 : ASSERT(mt_server_seed_session(2, auth_key, &salt, &sid) == 0,
67 : "seed session writes disk");
68 2 : mt_server_expect(CRC_ping, on_ping, NULL);
69 :
70 : /* Load that same session into our own MtProtoSession — the production
71 : * auth_flow would do this; here we drive session_store directly. */
72 : MtProtoSession s;
73 2 : mtproto_session_init(&s);
74 2 : int dc_id = 0;
75 2 : ASSERT(session_store_load(&s, &dc_id) == 0, "session loaded");
76 2 : ASSERT(dc_id == 2, "home DC is 2");
77 :
78 : Transport t;
79 2 : transport_init(&t);
80 2 : ASSERT(transport_connect(&t, "127.0.0.1", 443) == 0, "fake connect");
81 :
82 : /* Send a ping. */
83 : TlWriter req;
84 2 : tl_writer_init(&req);
85 2 : tl_write_uint32(&req, CRC_ping);
86 2 : tl_write_uint64(&req, 0x1234567890ABCDEFULL);
87 2 : ASSERT(rpc_send_encrypted(&s, &t, req.data, req.len, 1) == 0,
88 : "send encrypted ping");
89 2 : tl_writer_free(&req);
90 :
91 : /* Read back pong. */
92 : uint8_t reply[1024];
93 2 : size_t reply_len = 0;
94 2 : ASSERT(rpc_recv_encrypted(&s, &t, reply, sizeof(reply), &reply_len) == 0,
95 : "recv encrypted pong");
96 2 : ASSERT(reply_len >= 4 + 8 + 4 + 8 + 8, "reply big enough for rpc_result+pong");
97 :
98 : /* rpc_result#f35c6d01 req_msg_id:long result:Object */
99 2 : TlReader r = tl_reader_init(reply, reply_len);
100 2 : uint32_t crc_result = tl_read_uint32(&r);
101 2 : ASSERT(crc_result == 0xf35c6d01U, "outer is rpc_result");
102 2 : tl_read_uint64(&r); /* req_msg_id */
103 2 : uint32_t crc_pong = tl_read_uint32(&r);
104 2 : ASSERT(crc_pong == CRC_pong, "inner is pong");
105 2 : tl_read_uint64(&r); /* msg_id echo */
106 2 : uint64_t returned_ping_id = tl_read_uint64(&r);
107 2 : ASSERT(returned_ping_id == 0x1234567890ABCDEFULL, "ping_id round-trips");
108 :
109 2 : ASSERT(mt_server_rpc_call_count() == 1, "exactly one RPC dispatched");
110 :
111 2 : transport_close(&t);
112 2 : mt_server_reset();
113 : }
114 :
115 2 : static void test_unknown_crc_returns_rpc_error(void) {
116 2 : with_tmp_home("no-handler");
117 2 : mt_server_init();
118 2 : mt_server_reset();
119 :
120 2 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed");
121 : /* no handler registered */
122 :
123 : MtProtoSession s;
124 2 : mtproto_session_init(&s);
125 2 : int dc_id = 0;
126 2 : ASSERT(session_store_load(&s, &dc_id) == 0, "load");
127 :
128 : Transport t;
129 2 : transport_init(&t);
130 2 : transport_connect(&t, "127.0.0.1", 443);
131 :
132 : /* Send an unregistered CRC (e.g. help.getConfig#c4f9186b). */
133 : TlWriter req;
134 2 : tl_writer_init(&req);
135 2 : tl_write_uint32(&req, 0xc4f9186bU);
136 2 : rpc_send_encrypted(&s, &t, req.data, req.len, 1);
137 2 : tl_writer_free(&req);
138 :
139 : uint8_t reply[512];
140 2 : size_t reply_len = 0;
141 2 : ASSERT(rpc_recv_encrypted(&s, &t, reply, sizeof(reply), &reply_len) == 0,
142 : "recv reply");
143 2 : uint64_t req_msg_id = 0;
144 2 : const uint8_t *inner = NULL;
145 2 : size_t inner_len = 0;
146 2 : ASSERT(rpc_unwrap_result(reply, reply_len, &req_msg_id, &inner, &inner_len) == 0,
147 : "unwrap rpc_result");
148 : RpcError err;
149 2 : ASSERT(rpc_parse_error(inner, inner_len, &err) == 0,
150 : "inner is rpc_error");
151 2 : ASSERT(err.error_code == 500, "500 status");
152 2 : ASSERT(strstr(err.error_msg, "NO_HANDLER") != NULL,
153 : "error message says NO_HANDLER");
154 :
155 2 : transport_close(&t);
156 2 : mt_server_reset();
157 : }
158 :
159 2 : void run_mt_server_smoke_tests(void) {
160 2 : RUN_TEST(test_ping_pong_roundtrip);
161 2 : RUN_TEST(test_unknown_crc_returns_rpc_error);
162 2 : }
|