LCOV - code coverage report
Current view: top level - tests/mocks - mock_tel_server.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 91.7 % 818 750
Test Date: 2026-04-20 19:54:22 Functions: 98.1 % 54 53

            Line data    Source code
       1              : /**
       2              :  * @file mock_tel_server.c
       3              :  * @brief In-process Telegram server emulator — see mock_tel_server.h.
       4              :  */
       5              : 
       6              : #include "mock_tel_server.h"
       7              : #include "mock_socket.h"
       8              : 
       9              : #include "crypto.h"
      10              : #include "ige_aes.h"
      11              : #include "mtproto_crypto.h"
      12              : #include "mtproto_session.h"
      13              : #include "tl_serial.h"
      14              : #include "tl_skip.h"
      15              : #include "session_store.h"
      16              : #include "tinf.h"
      17              : 
      18              : #include <stdio.h>
      19              : #include <stdlib.h>
      20              : #include <string.h>
      21              : #include <time.h>
      22              : 
      23              : #define CRC_invokeWithLayer  0xda9b0d0dU
      24              : #define CRC_initConnection   0xc1cd5ea9U
      25              : #define CRC_rpc_result       0xf35c6d01U
      26              : #define CRC_rpc_error        0x2144ca19U
      27              : #define CRC_gzip_packed      0x3072cfa1U
      28              : #define CRC_msg_container    0x73f1f8dcU
      29              : 
      30              : /* TL CRCs for auth.sendCode / auth.signIn — local copies so the mock
      31              :  * server does not have to link against src/infrastructure/auth_session.h.
      32              :  * Kept aligned with CRC_auth_sendCode / CRC_auth_signIn in that header. */
      33              : #define CRC_auth_sendCode_local  0xa677244fU
      34              : #define CRC_auth_signIn_local    0x8d52a951U
      35              : 
      36              : /* TL CRCs for auth.exportAuthorization / auth.importAuthorization and
      37              :  * their reply constructors — local copies so the mock stays decoupled
      38              :  * from src/infrastructure/auth_transfer.c. Kept aligned with the
      39              :  * CRC_* macros in that file. */
      40              : #define CRC_auth_exportAuthorization_local        0xe5bfffcdU
      41              : #define CRC_auth_exportedAuthorization_local      0xb434e2b8U
      42              : #define CRC_auth_importAuthorization_local        0xa57a7dadU
      43              : #define CRC_auth_authorization_local              0x2ea2c0d4U
      44              : #define CRC_auth_authorizationSignUpRequired_local 0x44747e9aU
      45              : #define CRC_user_local                            0x3ff6ecb0U
      46              : 
      47              : #define MT_MAX_HANDLERS      64
      48              : #define MT_MAX_UPDATES       32
      49              : #define MT_FRAME_MAX         (256 * 1024)
      50              : #define MT_CRC_RING_SIZE     256
      51              : #define MT_MAX_PENDING_SVC   16   /* stacked service frames ahead of next reply */
      52              : 
      53              : /* TL CRCs for the service frames TEST-88 exercises. Keep these local so
      54              :  * the mock server does not have to link against src/core/tl_registry.h. */
      55              : #define CRC_bad_server_salt      0xedab447bU
      56              : #define CRC_bad_msg_notification 0xa7eff811U
      57              : #define CRC_new_session_created  0x9ec20908U
      58              : #define CRC_msgs_ack             0x62d6b459U
      59              : #define CRC_pong                 0x347773c5U
      60              : #define CRC_vector               0x1cb5c415U
      61              : 
      62              : typedef struct {
      63              :     uint32_t    crc;
      64              :     MtResponder fn;
      65              :     void       *ctx;
      66              :     int         used;
      67              : } MtHandler;
      68              : 
      69              : typedef struct {
      70              :     uint8_t *bytes;
      71              :     size_t   len;
      72              : } MtBlob;
      73              : 
      74              : static struct {
      75              :     int         initialized;
      76              :     int         seeded;
      77              : 
      78              :     uint8_t     auth_key[MT_SERVER_AUTH_KEY_SIZE];
      79              :     uint64_t    auth_key_id;
      80              :     uint64_t    server_salt;
      81              :     uint64_t    session_id;
      82              : 
      83              :     uint64_t    next_server_msg_id;
      84              :     uint32_t    seq_no;
      85              : 
      86              :     size_t      parse_cursor;    /* next byte in mock_socket sent-buffer */
      87              :     int         saw_marker;      /* consumed the initial 0xEF yet? */
      88              : 
      89              :     MtHandler   handlers[MT_MAX_HANDLERS];
      90              : 
      91              :     MtBlob      pending_updates[MT_MAX_UPDATES];
      92              :     size_t      pending_update_count;
      93              : 
      94              :     int         rpc_call_count;
      95              : 
      96              :     /* Scratch state that reply builders read to know which client msg_id
      97              :      * they are answering. Valid only during responder execution. */
      98              :     uint64_t    current_req_msg_id;
      99              : 
     100              :     /* One-shot bad_server_salt injection. When non-zero, the next dispatched
     101              :      * RPC triggers a bad_server_salt reply instead of running the handler. */
     102              :     int         bad_salt_once_pending;
     103              :     uint64_t    bad_salt_new_salt;
     104              : 
     105              :     /* One-shot reconnect detection: when set, the next 0xEF byte at the
     106              :      * parse cursor is treated as a fresh connection marker rather than a
     107              :      * frame length prefix. Cleared automatically after use. */
     108              :     int         reconnect_pending;
     109              : 
     110              :     /* One-shot wrong session_id injection. When set, the next reply frame
     111              :      * uses a deliberately wrong session_id in the plaintext header so that
     112              :      * rpc_recv_encrypted() rejects it. Cleared automatically after use. */
     113              :     int         wrong_session_id_once_pending;
     114              : 
     115              :     /* Ring buffer of leading CRCs from every frame received (both
     116              :      * unencrypted handshake frames and encrypted inner-RPC frames).
     117              :      * Used by mt_server_request_crc_count(). */
     118              :     uint32_t    crc_ring[MT_CRC_RING_SIZE];
     119              :     size_t      crc_ring_count;   /* total recorded; wraps at MT_CRC_RING_SIZE */
     120              : 
     121              :     /* Stack of service frames to send ahead of the next real reply.
     122              :      * Each entry holds a raw TL body (with leading CRC). Drained in
     123              :      * unwrap_and_dispatch before the registered handler runs. */
     124              :     MtBlob      pending_service_frames[MT_MAX_PENDING_SVC];
     125              :     size_t      pending_service_count;
     126              : 
     127              :     /* TEST-71 / US-20 cold-boot handshake state.
     128              :      * When handshake_mode != 0 the mock processes unencrypted frames
     129              :      * (auth_key_id == 0) alongside encrypted ones, emitting synthetic
     130              :      * resPQ / server_DH_params_ok envelopes so functional tests can
     131              :      * exercise src/infrastructure/mtproto_auth.c against real OpenSSL.
     132              :      * Mode 3 (TEST-72) completes the full DH exchange: RSA-PAD decrypt,
     133              :      * server_DH_params_ok, dh_gen_ok — enabling full auth_key derivation. */
     134              :     int         handshake_mode;          /* 0=off 1=resPQ-only 2=through step3 3=full DH */
     135              :     int         handshake_cold_boot_variant; /* MtColdBootMode value */
     136              :     int         handshake_req_pq_count;
     137              :     int         handshake_req_dh_count;
     138              :     int         handshake_set_client_dh_count; /* TEST-72 */
     139              : 
     140              :     /* TEST-72: DH state preserved between req_DH_params and set_client_DH_params. */
     141              :     uint8_t     hs_new_nonce[32];      /* extracted from client's RSA_PAD payload */
     142              :     uint8_t     hs_server_nonce[16];   /* echoed from resPQ */
     143              :     uint8_t     hs_nonce[16];          /* client nonce */
     144              :     uint8_t     hs_b[256];             /* server's DH secret (random) */
     145              :     uint8_t     hs_dh_prime[32];       /* safe prime for DH */
     146              :     uint8_t     hs_g_a[256];           /* g^a mod p (received from client) */
     147              :     size_t      hs_g_a_len;
     148              : } g_srv;
     149              : 
     150              : /* ---- forward decls ---- */
     151              : static void on_client_sent(const uint8_t *buf, size_t len);
     152              : static void dispatch_frame(const uint8_t *plain, size_t plain_len);
     153              : static void unwrap_and_dispatch(uint64_t req_msg_id,
     154              :                                 const uint8_t *body, size_t body_len);
     155              : static void encrypt_and_queue(const uint8_t *plain, size_t plain_len);
     156              : static void queue_frame(const uint8_t *body, size_t body_len);
     157              : static uint64_t derive_auth_key_id(const uint8_t *auth_key);
     158              : static uint64_t make_server_msg_id(void);
     159              : static void handshake_on_req_pq_multi(const uint8_t *body, size_t body_len);
     160              : static void handshake_on_req_dh_params(const uint8_t *body, size_t body_len);
     161              : static void handshake_on_set_client_dh(const uint8_t *body, size_t body_len);
     162              : static void handshake_queue_unenc(const uint8_t *tl, size_t tl_len);
     163              : 
     164              : /* ================================================================ */
     165              : /* Public API                                                       */
     166              : /* ================================================================ */
     167              : 
     168          486 : void mt_server_init(void) {
     169          486 :     if (g_srv.initialized) return;
     170            2 :     memset(&g_srv, 0, sizeof(g_srv));
     171            2 :     g_srv.initialized = 1;
     172              : }
     173              : 
     174          950 : void mt_server_reset(void) {
     175              :     /* Preserve initialized flag, wipe everything else. */
     176          950 :     mock_socket_set_on_sent(NULL);
     177          950 :     for (size_t i = 0; i < g_srv.pending_update_count; ++i) {
     178            0 :         free(g_srv.pending_updates[i].bytes);
     179              :     }
     180          950 :     for (size_t i = 0; i < g_srv.pending_service_count; ++i) {
     181            0 :         free(g_srv.pending_service_frames[i].bytes);
     182              :     }
     183          950 :     memset(&g_srv, 0, sizeof(g_srv));
     184          950 :     g_srv.initialized = 1;
     185              : 
     186          950 :     mock_socket_reset();
     187          950 :     mock_socket_set_on_sent(on_client_sent);
     188          950 : }
     189              : 
     190          446 : int mt_server_seed_session(int dc_id,
     191              :                            uint8_t auth_key_out[MT_SERVER_AUTH_KEY_SIZE],
     192              :                            uint64_t *salt_out,
     193              :                            uint64_t *session_id_out) {
     194              :     /* Deterministic-ish auth_key so tests stay reproducible across runs. */
     195       114622 :     for (int i = 0; i < MT_SERVER_AUTH_KEY_SIZE; ++i) {
     196       114176 :         g_srv.auth_key[i] = (uint8_t)((i * 31 + 7) & 0xFFu);
     197              :     }
     198          446 :     g_srv.auth_key_id  = derive_auth_key_id(g_srv.auth_key);
     199          446 :     g_srv.server_salt  = 0xABCDEF0123456789ULL;
     200          446 :     g_srv.session_id   = 0x1122334455667788ULL;
     201          446 :     g_srv.next_server_msg_id = 0;
     202          446 :     g_srv.seq_no = 0;
     203              : 
     204              :     MtProtoSession s;
     205          446 :     mtproto_session_init(&s);
     206          446 :     mtproto_session_set_auth_key(&s, g_srv.auth_key);
     207          446 :     mtproto_session_set_salt(&s, g_srv.server_salt);
     208          446 :     s.session_id = g_srv.session_id;
     209              : 
     210          446 :     if (session_store_save(&s, dc_id) != 0) return -1;
     211              : 
     212          446 :     if (auth_key_out)   memcpy(auth_key_out, g_srv.auth_key, MT_SERVER_AUTH_KEY_SIZE);
     213          446 :     if (salt_out)       *salt_out       = g_srv.server_salt;
     214          446 :     if (session_id_out) *session_id_out = g_srv.session_id;
     215              : 
     216          446 :     g_srv.seeded = 1;
     217          446 :     return 0;
     218              : }
     219              : 
     220          474 : void mt_server_expect(uint32_t crc, MtResponder fn, void *ctx) {
     221              :     /* Replace existing handler for the same CRC if present. */
     222        29788 :     for (size_t i = 0; i < MT_MAX_HANDLERS; ++i) {
     223        29330 :         if (g_srv.handlers[i].used && g_srv.handlers[i].crc == crc) {
     224           16 :             g_srv.handlers[i].fn  = fn;
     225           16 :             g_srv.handlers[i].ctx = ctx;
     226           16 :             return;
     227              :         }
     228              :     }
     229          510 :     for (size_t i = 0; i < MT_MAX_HANDLERS; ++i) {
     230          510 :         if (!g_srv.handlers[i].used) {
     231          458 :             g_srv.handlers[i].used = 1;
     232          458 :             g_srv.handlers[i].crc  = crc;
     233          458 :             g_srv.handlers[i].fn   = fn;
     234          458 :             g_srv.handlers[i].ctx  = ctx;
     235          458 :             return;
     236              :         }
     237              :     }
     238              :     /* Table full — test setup bug. Abort loudly. */
     239            0 :     fprintf(stderr, "mt_server_expect: handler table full\n");
     240            0 :     abort();
     241              : }
     242              : 
     243          620 : void mt_server_reply_result(const MtRpcContext *ctx,
     244              :                             const uint8_t *body, size_t body_len) {
     245          620 :     if (!ctx) return;
     246              :     /* rpc_result#f35c6d01 = req_msg_id:long result:Object */
     247          620 :     size_t wrapped_len = 4 + 8 + body_len;
     248          620 :     uint8_t *wrapped = (uint8_t *)malloc(wrapped_len);
     249          620 :     if (!wrapped) return;
     250          620 :     wrapped[0] = (uint8_t)(CRC_rpc_result);
     251          620 :     wrapped[1] = (uint8_t)(CRC_rpc_result >> 8);
     252          620 :     wrapped[2] = (uint8_t)(CRC_rpc_result >> 16);
     253          620 :     wrapped[3] = (uint8_t)(CRC_rpc_result >> 24);
     254         5580 :     for (int i = 0; i < 8; ++i) {
     255         4960 :         wrapped[4 + i] = (uint8_t)((ctx->req_msg_id >> (i * 8)) & 0xFFu);
     256              :     }
     257          620 :     memcpy(wrapped + 12, body, body_len);
     258          620 :     queue_frame(wrapped, wrapped_len);
     259          620 :     free(wrapped);
     260              : }
     261              : 
     262           96 : void mt_server_reply_error(const MtRpcContext *ctx,
     263              :                            int32_t error_code, const char *error_msg) {
     264           96 :     if (!ctx) return;
     265              :     /* rpc_error#2144ca19 = error_code:int error_message:string */
     266              :     TlWriter w;
     267           96 :     tl_writer_init(&w);
     268           96 :     tl_write_uint32(&w, CRC_rpc_error);
     269           96 :     tl_write_int32(&w, error_code);
     270           96 :     tl_write_string(&w, error_msg ? error_msg : "");
     271           96 :     mt_server_reply_result(ctx, w.data, w.len);
     272           96 :     tl_writer_free(&w);
     273              : }
     274              : 
     275              : /* ---- Minimal gzip encoder (deflate stored blocks) ----
     276              :  *
     277              :  * Writes @p payload as a sequence of uncompressed deflate blocks ("stored"
     278              :  * blocks) wrapped in the gzip member format. This avoids pulling in a real
     279              :  * compressor (zlib) for test fixtures — the stored-block path is a
     280              :  * well-defined subset of deflate that tinf's inflater handles. Each stored
     281              :  * block carries at most 65535 bytes; larger payloads span multiple blocks.
     282              :  *
     283              :  * Format per RFC 1951 §3.2.4 + RFC 1952:
     284              :  *   gzip header (10)  1f 8b 08 00 00 00 00 00 00 ff
     285              :  *   deflate stream    [block_header(1) LEN(2 LE) NLEN(2 LE) data(LEN)]+
     286              :  *                     (block_header bit0 = BFINAL, bits1-2 = BTYPE=00)
     287              :  *   CRC32(payload)    4 bytes LE
     288              :  *   ISIZE             4 bytes LE (original length mod 2^32)
     289              :  *
     290              :  * Returns a heap-allocated buffer the caller must free; NULL on OOM.
     291              :  */
     292            4 : static uint8_t *gzip_stored(const uint8_t *payload, size_t payload_len,
     293              :                             size_t *out_len) {
     294            4 :     if (!out_len) return NULL;
     295              : 
     296              :     /* Upper bound on output size: 10 header + per 65535-byte block
     297              :      * overhead (1 + 2 + 2 = 5) + payload + 8 trailer. */
     298            4 :     size_t blocks = (payload_len + 65534) / 65535;
     299            4 :     if (payload_len == 0) blocks = 1;
     300            4 :     size_t cap = 10 + blocks * 5 + payload_len + 8;
     301              : 
     302            4 :     uint8_t *buf = (uint8_t *)malloc(cap);
     303            4 :     if (!buf) return NULL;
     304            4 :     size_t off = 0;
     305              : 
     306              :     /* gzip header */
     307            4 :     buf[off++] = 0x1F;  /* ID1 */
     308            4 :     buf[off++] = 0x8B;  /* ID2 */
     309            4 :     buf[off++] = 0x08;  /* CM = deflate */
     310            4 :     buf[off++] = 0x00;  /* FLG = 0 (no name, comment, extra, crc16) */
     311            4 :     buf[off++] = 0x00;  /* MTIME[0] */
     312            4 :     buf[off++] = 0x00;  /* MTIME[1] */
     313            4 :     buf[off++] = 0x00;  /* MTIME[2] */
     314            4 :     buf[off++] = 0x00;  /* MTIME[3] */
     315            4 :     buf[off++] = 0x00;  /* XFL */
     316            4 :     buf[off++] = 0xFF;  /* OS = unknown */
     317              : 
     318              :     /* Deflate stored blocks. */
     319            4 :     size_t pos = 0;
     320            4 :     if (payload_len == 0) {
     321              :         /* Emit one empty final stored block: header=0x01, LEN=0, NLEN=0xFFFF. */
     322            0 :         buf[off++] = 0x01;
     323            0 :         buf[off++] = 0x00; buf[off++] = 0x00;
     324            0 :         buf[off++] = 0xFF; buf[off++] = 0xFF;
     325              :     } else {
     326            8 :         while (pos < payload_len) {
     327            4 :             size_t chunk = payload_len - pos;
     328            4 :             if (chunk > 65535) chunk = 65535;
     329            4 :             int is_final = (pos + chunk == payload_len) ? 1 : 0;
     330            4 :             buf[off++] = (uint8_t)(is_final ? 0x01 : 0x00);
     331            4 :             buf[off++] = (uint8_t)(chunk & 0xFFu);
     332            4 :             buf[off++] = (uint8_t)((chunk >> 8) & 0xFFu);
     333            4 :             uint16_t nlen = (uint16_t)~chunk;
     334            4 :             buf[off++] = (uint8_t)(nlen & 0xFFu);
     335            4 :             buf[off++] = (uint8_t)((nlen >> 8) & 0xFFu);
     336            4 :             memcpy(buf + off, payload + pos, chunk);
     337            4 :             off += chunk;
     338            4 :             pos += chunk;
     339              :         }
     340              :     }
     341              : 
     342              :     /* Trailer: CRC32 of raw payload, then ISIZE mod 2^32. tinf_crc32 matches
     343              :      * the standard gzip polynomial. Guard the empty-payload case because
     344              :      * tinf_crc32 returns 0 for empty input (which happens to be correct). */
     345            4 :     uint32_t crc = tinf_crc32(payload, (unsigned int)payload_len);
     346            4 :     buf[off++] = (uint8_t)(crc & 0xFFu);
     347            4 :     buf[off++] = (uint8_t)((crc >> 8) & 0xFFu);
     348            4 :     buf[off++] = (uint8_t)((crc >> 16) & 0xFFu);
     349            4 :     buf[off++] = (uint8_t)((crc >> 24) & 0xFFu);
     350            4 :     uint32_t isize = (uint32_t)(payload_len & 0xFFFFFFFFu);
     351            4 :     buf[off++] = (uint8_t)(isize & 0xFFu);
     352            4 :     buf[off++] = (uint8_t)((isize >> 8) & 0xFFu);
     353            4 :     buf[off++] = (uint8_t)((isize >> 16) & 0xFFu);
     354            4 :     buf[off++] = (uint8_t)((isize >> 24) & 0xFFu);
     355              : 
     356            4 :     *out_len = off;
     357            4 :     return buf;
     358              : }
     359              : 
     360            4 : void mt_server_reply_gzip_wrapped_result(const MtRpcContext *ctx,
     361              :                                           const uint8_t *body,
     362              :                                           size_t body_len) {
     363            4 :     if (!ctx || (!body && body_len > 0)) return;
     364            4 :     size_t gz_len = 0;
     365            4 :     uint8_t *gz = gzip_stored(body, body_len, &gz_len);
     366            4 :     if (!gz) return;
     367              : 
     368              :     /* gzip_packed#3072cfa1 = packed_data:bytes = Object */
     369              :     TlWriter w;
     370            4 :     tl_writer_init(&w);
     371            4 :     tl_write_uint32(&w, CRC_gzip_packed);
     372            4 :     tl_write_bytes(&w, gz, gz_len);
     373            4 :     mt_server_reply_result(ctx, w.data, w.len);
     374            4 :     tl_writer_free(&w);
     375            4 :     free(gz);
     376              : }
     377              : 
     378            2 : void mt_server_reply_gzip_corrupt(const MtRpcContext *ctx) {
     379            2 :     if (!ctx) return;
     380              :     /* Bytes that fail gzip header validation: wrong magic + too short for
     381              :      * the 18-byte minimum tinf requires. rpc_unwrap_gzip propagates the
     382              :      * TINF_DATA_ERROR as -1. */
     383              :     static const uint8_t garbage[] = {
     384              :         0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22,
     385              :         0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
     386              :         0xDE, 0xAD, 0xBE, 0xEF
     387              :     };
     388              :     TlWriter w;
     389            2 :     tl_writer_init(&w);
     390            2 :     tl_write_uint32(&w, CRC_gzip_packed);
     391            2 :     tl_write_bytes(&w, garbage, sizeof(garbage));
     392            2 :     mt_server_reply_result(ctx, w.data, w.len);
     393            2 :     tl_writer_free(&w);
     394              : }
     395              : 
     396            2 : void mt_server_reply_msg_container(const MtRpcContext *ctx,
     397              :                                     const uint8_t *const *children,
     398              :                                     const size_t *child_lens,
     399              :                                     size_t n_children) {
     400            2 :     if (!ctx || !children || !child_lens || n_children == 0) return;
     401              : 
     402              :     /* msg_container#73f1f8dc messages:vector<message>
     403              :      * message { msg_id:long seqno:int bytes:int body:bytes_untyped }
     404              :      * Each body is concatenated raw (no length prefix on the body itself —
     405              :      * the bytes:int field already specifies its length, and the body is
     406              :      * 4-byte-aligned TL data). */
     407              :     TlWriter w;
     408            2 :     tl_writer_init(&w);
     409            2 :     tl_write_uint32(&w, CRC_msg_container);
     410            2 :     tl_write_uint32(&w, (uint32_t)n_children);
     411              :     /* Use a monotonic msg_id/seqno pair per child. The parser's alignment
     412              :      * guard only cares that each body_len is a multiple of 4. */
     413            2 :     uint64_t base_msg_id = (uint64_t)time(NULL) << 32;
     414            4 :     for (size_t i = 0; i < n_children; ++i) {
     415            2 :         tl_write_uint64(&w, base_msg_id + ((uint64_t)i << 2));
     416            2 :         tl_write_uint32(&w, (uint32_t)(i * 2 + 1));
     417            2 :         tl_write_uint32(&w, (uint32_t)child_lens[i]);
     418            2 :         tl_write_raw(&w, children[i], child_lens[i]);
     419              :     }
     420              :     /* Send the container as the outer body — NOT wrapped in rpc_result. */
     421            2 :     queue_frame(w.data, w.len);
     422            2 :     tl_writer_free(&w);
     423              : }
     424              : 
     425            0 : void mt_server_push_update(const uint8_t *tl, size_t tl_len) {
     426            0 :     if (g_srv.pending_update_count >= MT_MAX_UPDATES) return;
     427            0 :     uint8_t *copy = (uint8_t *)malloc(tl_len);
     428            0 :     if (!copy) return;
     429            0 :     memcpy(copy, tl, tl_len);
     430            0 :     g_srv.pending_updates[g_srv.pending_update_count].bytes = copy;
     431            0 :     g_srv.pending_updates[g_srv.pending_update_count].len   = tl_len;
     432            0 :     g_srv.pending_update_count++;
     433              : }
     434              : 
     435           54 : int mt_server_rpc_call_count(void) { return g_srv.rpc_call_count; }
     436              : 
     437           84 : int mt_server_request_crc_count(uint32_t crc) {
     438           84 :     int count = 0;
     439           84 :     size_t n = g_srv.crc_ring_count;
     440           84 :     if (n > MT_CRC_RING_SIZE) n = MT_CRC_RING_SIZE;
     441          354 :     for (size_t i = 0; i < n; ++i) {
     442          270 :         if (g_srv.crc_ring[i] == crc) count++;
     443              :     }
     444           84 :     return count;
     445              : }
     446              : 
     447           34 : void mt_server_arm_reconnect(void) {
     448           34 :     g_srv.reconnect_pending = 1;
     449           34 : }
     450              : 
     451           32 : int mt_server_seed_extra_dc(int dc_id) {
     452           32 :     if (!g_srv.seeded) return -1;
     453              :     MtProtoSession s;
     454           32 :     mtproto_session_init(&s);
     455           32 :     mtproto_session_set_auth_key(&s, g_srv.auth_key);
     456           32 :     mtproto_session_set_salt(&s, g_srv.server_salt);
     457           32 :     s.session_id = g_srv.session_id;
     458           32 :     return session_store_save_dc(dc_id, &s);
     459              : }
     460              : 
     461            4 : void mt_server_set_bad_salt_once(uint64_t new_salt) {
     462            4 :     g_srv.bad_salt_once_pending = 1;
     463            4 :     g_srv.bad_salt_new_salt = new_salt;
     464            4 : }
     465              : 
     466            2 : void mt_server_set_wrong_session_id_once(void) {
     467            2 :     g_srv.wrong_session_id_once_pending = 1;
     468            2 : }
     469              : 
     470              : /* ---- TEST-88 service-frame helpers ---------------------------------- */
     471              : 
     472              : /** Append a service-frame body (raw TL, CRC-prefixed) to the queue drained
     473              :  *  ahead of the next handler dispatch. Silently drops if the queue is full
     474              :  *  (tests that need more than MT_MAX_PENDING_SVC would need to raise the
     475              :  *  cap). Returns 1 on success, 0 on drop. */
     476           28 : static int svc_queue_push(const uint8_t *body, size_t body_len) {
     477           28 :     if (g_srv.pending_service_count >= MT_MAX_PENDING_SVC) return 0;
     478           28 :     uint8_t *copy = (uint8_t *)malloc(body_len);
     479           28 :     if (!copy) return 0;
     480           28 :     memcpy(copy, body, body_len);
     481           28 :     g_srv.pending_service_frames[g_srv.pending_service_count].bytes = copy;
     482           28 :     g_srv.pending_service_frames[g_srv.pending_service_count].len   = body_len;
     483           28 :     g_srv.pending_service_count++;
     484           28 :     return 1;
     485              : }
     486              : 
     487            2 : void mt_server_reply_bad_server_salt(uint64_t new_salt) {
     488              :     /* Reuse the existing one-shot path — the SVC_BAD_SALT branch is
     489              :      * sensitive to ordering (the client retries BEFORE reading any other
     490              :      * queued frame) so piping through set_bad_salt_once keeps the flow
     491              :      * identical to what rpc_send_encrypted observes on real hardware. */
     492            2 :     mt_server_set_bad_salt_once(new_salt);
     493            2 : }
     494              : 
     495            2 : void mt_server_reply_new_session_created(void) {
     496              :     /* new_session_created#9ec20908 first_msg_id:long unique_id:long
     497              :      *                              server_salt:long = NewSession */
     498              :     uint8_t body[4 + 8 + 8 + 8];
     499            2 :     uint32_t crc = CRC_new_session_created;
     500           10 :     for (int i = 0; i < 4; ++i) body[i] = (uint8_t)(crc >> (i * 8));
     501              :     /* first_msg_id — arbitrary recognisable pattern. */
     502            2 :     uint64_t first = 0xF111F111F111F111ULL;
     503           18 :     for (int i = 0; i < 8; ++i) body[4 + i]  = (uint8_t)(first >> (i * 8));
     504              :     /* unique_id. */
     505            2 :     uint64_t uniq = 0xABC0ABC0ABC0ABC0ULL;
     506           18 :     for (int i = 0; i < 8; ++i) body[12 + i] = (uint8_t)(uniq >> (i * 8));
     507              :     /* server_salt — the value the client must adopt. Keep this stable so
     508              :      * tests can assert against a constant. */
     509            2 :     uint64_t fresh = 0xCAFEF00DBAADC0DEULL;
     510           18 :     for (int i = 0; i < 8; ++i) body[20 + i] = (uint8_t)(fresh >> (i * 8));
     511            2 :     svc_queue_push(body, sizeof(body));
     512            2 : }
     513              : 
     514           20 : void mt_server_reply_msgs_ack(const uint64_t *ids, size_t n) {
     515              :     /* msgs_ack#62d6b459 msg_ids:Vector<long> */
     516           20 :     TlWriter w; tl_writer_init(&w);
     517           20 :     tl_write_uint32(&w, CRC_msgs_ack);
     518           20 :     tl_write_uint32(&w, CRC_vector);
     519           20 :     tl_write_uint32(&w, (uint32_t)n);
     520           44 :     for (size_t i = 0; i < n; ++i) {
     521           24 :         tl_write_uint64(&w, ids ? ids[i] : (uint64_t)(0xAC000000DEAD0000ULL + i));
     522              :     }
     523           20 :     svc_queue_push(w.data, w.len);
     524           20 :     tl_writer_free(&w);
     525           20 : }
     526              : 
     527            2 : void mt_server_reply_pong(uint64_t msg_id, uint64_t ping_id) {
     528              :     /* pong#347773c5 msg_id:long ping_id:long = Pong */
     529              :     uint8_t body[4 + 8 + 8];
     530            2 :     uint32_t crc = CRC_pong;
     531           10 :     for (int i = 0; i < 4; ++i) body[i] = (uint8_t)(crc >> (i * 8));
     532           18 :     for (int i = 0; i < 8; ++i) body[4 + i]  = (uint8_t)(msg_id  >> (i * 8));
     533           18 :     for (int i = 0; i < 8; ++i) body[12 + i] = (uint8_t)(ping_id >> (i * 8));
     534            2 :     svc_queue_push(body, sizeof(body));
     535            2 : }
     536              : 
     537            4 : void mt_server_reply_bad_msg_notification(uint64_t bad_id, int code) {
     538              :     /* bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int
     539              :      *                               error_code:int = BadMsgNotification */
     540              :     uint8_t body[4 + 8 + 4 + 4];
     541            4 :     uint32_t crc = CRC_bad_msg_notification;
     542           20 :     for (int i = 0; i < 4; ++i) body[i] = (uint8_t)(crc >> (i * 8));
     543           36 :     for (int i = 0; i < 8; ++i) body[4 + i]  = (uint8_t)(bad_id >> (i * 8));
     544            4 :     int32_t seqno = 0;
     545           20 :     for (int i = 0; i < 4; ++i) body[12 + i] = (uint8_t)(seqno >> (i * 8));
     546            4 :     int32_t ec = code;
     547           20 :     for (int i = 0; i < 4; ++i) body[16 + i] = (uint8_t)(ec >> (i * 8));
     548            4 :     svc_queue_push(body, sizeof(body));
     549            4 : }
     550              : 
     551            2 : void mt_server_stack_service_frames(size_t count) {
     552              :     /* Stack N msgs_ack frames. Each carries a distinct synthetic msg_id
     553              :      * so the client can log them individually (not that classify_service_frame
     554              :      * inspects the body — but giving them unique ids keeps the wire traffic
     555              :      * realistic). */
     556           20 :     for (size_t i = 0; i < count; ++i) {
     557           18 :         uint64_t id = 0xAC000000DEAD0000ULL + i;
     558           18 :         mt_server_reply_msgs_ack(&id, 1);
     559              :     }
     560            2 : }
     561              : 
     562              : /* ---- TEST-86 PHONE/USER/NETWORK_MIGRATE helpers ----------------------
     563              :  *
     564              :  * Three internal int slots carry the target DC across handler dispatch.
     565              :  * Tests can reassign a new DC between calls by re-invoking the helper.
     566              :  * Using static slots (rather than malloc'd ctx) keeps the helpers
     567              :  * alloc-free and matches the rest of the mock-server setup style. */
     568              : static int g_phone_migrate_dc   = 0;
     569              : static int g_user_migrate_dc    = 0;
     570              : static int g_network_migrate_dc = 0;
     571              : 
     572           10 : static void on_phone_migrate(MtRpcContext *ctx) {
     573           10 :     int dc = g_phone_migrate_dc;
     574              :     char buf[64];
     575           10 :     snprintf(buf, sizeof(buf), "PHONE_MIGRATE_%d", dc);
     576           10 :     mt_server_reply_error(ctx, 303, buf);
     577           10 : }
     578              : 
     579            2 : static void on_user_migrate(MtRpcContext *ctx) {
     580            2 :     int dc = g_user_migrate_dc;
     581              :     char buf[64];
     582            2 :     snprintf(buf, sizeof(buf), "USER_MIGRATE_%d", dc);
     583            2 :     mt_server_reply_error(ctx, 303, buf);
     584            2 : }
     585              : 
     586            2 : static void on_network_migrate(MtRpcContext *ctx) {
     587            2 :     int dc = g_network_migrate_dc;
     588              :     char buf[64];
     589            2 :     snprintf(buf, sizeof(buf), "NETWORK_MIGRATE_%d", dc);
     590            2 :     mt_server_reply_error(ctx, 303, buf);
     591            2 : }
     592              : 
     593           10 : void mt_server_reply_phone_migrate(int dc_id) {
     594           10 :     g_phone_migrate_dc = dc_id;
     595           10 :     mt_server_expect(CRC_auth_sendCode_local, on_phone_migrate, NULL);
     596           10 : }
     597              : 
     598            2 : void mt_server_reply_user_migrate(int dc_id) {
     599            2 :     g_user_migrate_dc = dc_id;
     600            2 :     mt_server_expect(CRC_auth_signIn_local, on_user_migrate, NULL);
     601            2 : }
     602              : 
     603            2 : void mt_server_reply_network_migrate(int dc_id) {
     604            2 :     g_network_migrate_dc = dc_id;
     605            2 :     mt_server_expect(CRC_auth_sendCode_local, on_network_migrate, NULL);
     606            2 : }
     607              : 
     608              : /* ---- TEST-70 / US-19 auth.exportAuthorization / importAuthorization ----
     609              :  *
     610              :  * Static slots hold the export token (id + bytes) and an override flag
     611              :  * for the one-shot AUTH_KEY_INVALID case. Using statics keeps the helper
     612              :  * API alloc-free and mirrors how the PHONE/USER/NETWORK_MIGRATE helpers
     613              :  * store their target DC. */
     614              : #define MT_EXPORT_BYTES_MAX 1024
     615              : static int64_t g_export_id = 0;
     616              : static uint8_t g_export_bytes[MT_EXPORT_BYTES_MAX];
     617              : static size_t  g_export_bytes_len = 0;
     618              : static int     g_import_sign_up = 0;
     619              : static int     g_import_auth_key_invalid_pending = 0;
     620              : 
     621            8 : static void on_export_authorization(MtRpcContext *ctx) {
     622              :     /* auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization */
     623              :     TlWriter w;
     624            8 :     tl_writer_init(&w);
     625            8 :     tl_write_uint32(&w, CRC_auth_exportedAuthorization_local);
     626            8 :     tl_write_int64 (&w, g_export_id);
     627            8 :     tl_write_bytes (&w, g_export_bytes, g_export_bytes_len);
     628            8 :     mt_server_reply_result(ctx, w.data, w.len);
     629            8 :     tl_writer_free(&w);
     630            8 : }
     631              : 
     632            6 : static void on_import_authorization(MtRpcContext *ctx) {
     633            6 :     if (g_import_auth_key_invalid_pending) {
     634              :         /* Simulate a server-side token expiry race: the token we just
     635              :          * issued has gone stale before the client imported it. */
     636            2 :         g_import_auth_key_invalid_pending = 0;
     637            2 :         mt_server_reply_error(ctx, 401, "AUTH_KEY_INVALID");
     638            2 :         return;
     639              :     }
     640              :     TlWriter w;
     641            4 :     tl_writer_init(&w);
     642            4 :     if (g_import_sign_up) {
     643              :         /* auth.authorizationSignUpRequired#44747e9a has flags:#
     644              :          * terms_of_service:flags.0?help.TermsOfService = auth.Authorization.
     645              :          * Emit flags=0 (no TOS attached) — the client only cares about the
     646              :          * constructor CRC. */
     647            2 :         tl_write_uint32(&w, CRC_auth_authorizationSignUpRequired_local);
     648            2 :         tl_write_uint32(&w, 0);
     649              :     } else {
     650              :         /* auth.authorization#2ea2c0d4 flags:# ... user:User = auth.Authorization.
     651              :          * Emit the minimal shape accepted by the client's parser:
     652              :          * flags=0 + a user#3ff6ecb0 stub with flags=0 and id=101. */
     653            2 :         tl_write_uint32(&w, CRC_auth_authorization_local);
     654            2 :         tl_write_uint32(&w, 0);                       /* outer flags */
     655            2 :         tl_write_uint32(&w, CRC_user_local);          /* user constructor */
     656            2 :         tl_write_uint32(&w, 0);                       /* user.flags */
     657            2 :         tl_write_int64 (&w, 101LL);                   /* user.id */
     658              :     }
     659            4 :     mt_server_reply_result(ctx, w.data, w.len);
     660            4 :     tl_writer_free(&w);
     661              : }
     662              : 
     663            8 : void mt_server_reply_export_authorization(int64_t id,
     664              :                                            const uint8_t *bytes, size_t len) {
     665            8 :     if (!bytes || len == 0 || len > MT_EXPORT_BYTES_MAX) return;
     666            8 :     g_export_id = id;
     667            8 :     memcpy(g_export_bytes, bytes, len);
     668            8 :     g_export_bytes_len = len;
     669            8 :     mt_server_expect(CRC_auth_exportAuthorization_local,
     670              :                      on_export_authorization, NULL);
     671              : }
     672              : 
     673            6 : void mt_server_reply_import_authorization(int sign_up) {
     674            6 :     g_import_sign_up = sign_up ? 1 : 0;
     675            6 :     mt_server_expect(CRC_auth_importAuthorization_local,
     676              :                      on_import_authorization, NULL);
     677            6 : }
     678              : 
     679            2 : void mt_server_reply_import_authorization_auth_key_invalid_once(void) {
     680            2 :     g_import_auth_key_invalid_pending = 1;
     681              :     /* Next dispatch after the AUTH_KEY_INVALID one-shot falls through to
     682              :      * the happy auth.authorization path (sign_up=0) so callers that want
     683              :      * a full reject→retry cycle do not need two helper calls. */
     684            2 :     g_import_sign_up = 0;
     685            2 :     mt_server_expect(CRC_auth_importAuthorization_local,
     686              :                      on_import_authorization, NULL);
     687            2 : }
     688              : 
     689              : /* ---- TEST-71 / US-20 cold-boot DH handshake helpers ----
     690              :  *
     691              :  * The mock cannot decrypt the client's RSA_PAD(inner_data) because the
     692              :  * canonical Telegram RSA private key is not available. These helpers
     693              :  * therefore cover exhaustively only the paths reachable without that
     694              :  * private key: resPQ generation (step 1), plus a synthetic
     695              :  * server_DH_params_ok whose AES-IGE-wrapped payload decrypts to random
     696              :  * bytes and is rejected by the client's inner CRC check (step 3 error
     697              :  * path). Tests assert handshake start, negative variants, and
     698              :  * no-persistence-on-failure. */
     699              : 
     700              : /* TEST-72: The functional test runner links tests/mocks/telegram_server_key.c
     701              :  * which defines TELEGRAM_RSA_FINGERPRINT = 0x8671de275f1cabc5ULL (test-only
     702              :  * 2048-bit key pair). This constant must match that value so resPQ lists a
     703              :  * fingerprint the production client recognises during handshake tests.
     704              :  * NEVER use this fingerprint in production. */
     705              : #define COLD_BOOT_FP_OK           0x8671de275f1cabc5ULL
     706              : #define COLD_BOOT_FP_BAD          0xDEADBEEFCAFEBABEULL
     707              : #define CRC_resPQ                 0x05162463U
     708              : #define CRC_req_pq_multi          0xbe7e8ef1U
     709              : #define CRC_req_DH_params         0xd712e4beU
     710              : #define CRC_server_DH_params_ok   0xd0e8075cU
     711              : #define CRC_server_DH_inner_data  0xb5890dbaU
     712              : #define CRC_set_client_DH_params  0xf5045f1fU
     713              : #define CRC_dh_gen_ok             0x3bcbf734U
     714              : 
     715           18 : void mt_server_simulate_cold_boot(MtColdBootMode mode) {
     716              :     /* A cold-boot session has no persisted auth_key_id. Clear the seeded
     717              :      * flag so on_client_sent's guard lets unencrypted frames through, but
     718              :      * keep initialised state + mock_socket hook registered via reset(). */
     719           18 :     g_srv.handshake_mode = 1;
     720           18 :     g_srv.handshake_cold_boot_variant = (int)mode;
     721           18 :     g_srv.handshake_req_pq_count = 0;
     722           18 :     g_srv.handshake_req_dh_count = 0;
     723              :     /* Ensure auth_key_id is 0 so the parser takes the unencrypted branch. */
     724           18 :     g_srv.auth_key_id = 0;
     725           18 :     memset(g_srv.auth_key, 0, MT_SERVER_AUTH_KEY_SIZE);
     726              :     /* Allow on_client_sent to parse — the "seeded" label in this mock
     727              :      * means "ready to parse wire traffic", not "holds a post-handshake
     728              :      * auth key". Treat handshake_mode as an alternate seed state. */
     729           18 :     g_srv.seeded = 1;
     730           18 : }
     731              : 
     732            6 : void mt_server_simulate_cold_boot_through_step3(void) {
     733              :     /* Same as cold_boot(OK) but the mock also replies to req_DH_params
     734              :      * with a synthetic server_DH_params_ok. */
     735            6 :     if (g_srv.handshake_mode == 0) {
     736            6 :         mt_server_simulate_cold_boot(MT_COLD_BOOT_OK);
     737              :     }
     738            6 :     g_srv.handshake_mode = 2;
     739            6 : }
     740              : 
     741            2 : void mt_server_simulate_full_dh_handshake(void) {
     742              :     /* TEST-72: Full DH handshake — the mock RSA-PAD-decrypts the client's
     743              :      * req_DH_params payload (using the test RSA private key), generates
     744              :      * valid server_DH_inner_data, and handles set_client_DH_params to
     745              :      * return dh_gen_ok. On success the session auth_key is set and the
     746              :      * session is persisted. NEVER call this outside functional tests. */
     747            2 :     mt_server_simulate_cold_boot(MT_COLD_BOOT_OK);
     748            2 :     g_srv.handshake_mode = 3;
     749            2 :     g_srv.handshake_set_client_dh_count = 0;
     750            2 : }
     751              : 
     752            8 : int mt_server_handshake_req_pq_count(void) {
     753            8 :     return g_srv.handshake_req_pq_count;
     754              : }
     755              : 
     756            6 : int mt_server_handshake_req_dh_count(void) {
     757            6 :     return g_srv.handshake_req_dh_count;
     758              : }
     759              : 
     760            2 : int mt_server_handshake_set_client_dh_count(void) {
     761            2 :     return g_srv.handshake_set_client_dh_count;
     762              : }
     763              : 
     764              : /* ================================================================ */
     765              : /* Internals                                                         */
     766              : /* ================================================================ */
     767              : 
     768          448 : static uint64_t derive_auth_key_id(const uint8_t *auth_key) {
     769              :     uint8_t hash[32];
     770          448 :     crypto_sha256(auth_key, MT_SERVER_AUTH_KEY_SIZE, hash);
     771          448 :     uint64_t id = 0;
     772         4032 :     for (int i = 0; i < 8; ++i) id |= ((uint64_t)hash[24 + i]) << (i * 8);
     773          448 :     return id;
     774              : }
     775              : 
     776          682 : static uint64_t make_server_msg_id(void) {
     777              :     /* Monotonic even msg_ids (server → client use low-bit 1 in real MT,
     778              :      * but the client tolerates either — we just need monotonic). */
     779          682 :     uint64_t now = (uint64_t)time(NULL) << 32;
     780          682 :     if (now <= g_srv.next_server_msg_id) now = g_srv.next_server_msg_id + 4;
     781          682 :     now &= ~((uint64_t)3);
     782          682 :     now |= 1;  /* server → client msg_id is odd */
     783          682 :     g_srv.next_server_msg_id = now;
     784          682 :     return now;
     785              : }
     786              : 
     787              : /* Read an abridged length prefix from buf[cursor..len]. Returns:
     788              :  *   bytes consumed by the prefix on success (1 or 4)
     789              :  *   0 if not enough bytes yet
     790              :  * On success, *payload_len receives the payload size in bytes. */
     791         1322 : static size_t read_abridged_prefix(const uint8_t *buf, size_t len, size_t cursor,
     792              :                                     size_t *payload_len) {
     793         1322 :     if (cursor >= len) return 0;
     794         1322 :     uint8_t first = buf[cursor];
     795         1322 :     if (first < 0x7F) {
     796         1194 :         *payload_len = (size_t)first * 4;
     797         1194 :         return 1;
     798              :     }
     799          128 :     if (cursor + 4 > len) return 0;
     800          128 :     size_t wire = (size_t)buf[cursor + 1]
     801          128 :                 | ((size_t)buf[cursor + 2] << 8)
     802          128 :                 | ((size_t)buf[cursor + 3] << 16);
     803          128 :     *payload_len = wire * 4;
     804          128 :     return 4;
     805              : }
     806              : 
     807         1832 : static void on_client_sent(const uint8_t *buf, size_t len) {
     808         1832 :     if (!g_srv.seeded) return;
     809              : 
     810         2932 :     while (g_srv.parse_cursor < len) {
     811              :         /* Step 0 — consume the initial 0xEF marker on the very first byte.
     812              :          * Also handle a one-shot reconnect: if reconnect_pending is set and
     813              :          * the next byte is 0xEF, treat it as a new-connection marker (reset
     814              :          * parse state) so that a second transport session opened by production
     815              :          * code (e.g. cross-DC NETWORK_MIGRATE retry) is parsed cleanly. */
     816         1844 :         if (!g_srv.saw_marker) {
     817          492 :             if (buf[g_srv.parse_cursor] != 0xEFu) {
     818              :                 /* Unexpected first byte — not an abridged connection. */
     819          666 :                 return;
     820              :             }
     821          492 :             g_srv.parse_cursor++;
     822          492 :             g_srv.saw_marker = 1;
     823          552 :             continue;
     824              :         }
     825         1352 :         if (g_srv.reconnect_pending && buf[g_srv.parse_cursor] == 0xEFu) {
     826              :             /* A second transport connection opened by the client. Reset the
     827              :              * parser to treat this 0xEF as the new connection's abridged
     828              :              * marker. */
     829           30 :             g_srv.reconnect_pending = 0;
     830           30 :             g_srv.saw_marker = 0;
     831           30 :             continue;   /* re-enter the saw_marker=0 branch above */
     832              :         }
     833              : 
     834         1322 :         size_t payload_len = 0;
     835         1322 :         size_t prefix_bytes = read_abridged_prefix(buf, len, g_srv.parse_cursor,
     836              :                                                     &payload_len);
     837         1322 :         if (prefix_bytes == 0) return;  /* need more data */
     838         1322 :         if (payload_len == 0) {
     839              :             /* Idle keep-alive — skip. */
     840            0 :             g_srv.parse_cursor += prefix_bytes;
     841            0 :             continue;
     842              :         }
     843         1322 :         if (g_srv.parse_cursor + prefix_bytes + payload_len > len) {
     844          666 :             return;  /* wait for rest of payload */
     845              :         }
     846              : 
     847          656 :         const uint8_t *frame = buf + g_srv.parse_cursor + prefix_bytes;
     848          656 :         g_srv.parse_cursor += prefix_bytes + payload_len;
     849              : 
     850              :         /* Frame = auth_key_id(8) + msg_key(16) + ciphertext */
     851          656 :         if (payload_len < 24) continue;
     852          656 :         uint64_t key_id = 0;
     853         5904 :         for (int i = 0; i < 8; ++i) key_id |= ((uint64_t)frame[i]) << (i * 8);
     854              : 
     855              :         /* TEST-71: in cold-boot handshake mode the server's auth_key_id is
     856              :          * 0 (no session yet) and the client's frame also carries
     857              :          * auth_key_id = 0, so the equality check below would incorrectly
     858              :          * route the frame into the encrypted branch. Short-circuit here
     859              :          * and dispatch to the synthetic handshake responders instead. */
     860          656 :         if (g_srv.handshake_mode && key_id == 0 && payload_len >= 24) {
     861           28 :             uint32_t raw_crc = (uint32_t)frame[20]
     862           28 :                              | ((uint32_t)frame[21] << 8)
     863           28 :                              | ((uint32_t)frame[22] << 16)
     864           28 :                              | ((uint32_t)frame[23] << 24);
     865           28 :             size_t slot = g_srv.crc_ring_count % MT_CRC_RING_SIZE;
     866           28 :             g_srv.crc_ring[slot] = raw_crc;
     867           28 :             g_srv.crc_ring_count++;
     868              : 
     869           28 :             const uint8_t *tl_body = frame + 20;
     870           28 :             size_t tl_body_len = payload_len - 20;
     871           28 :             if (raw_crc == CRC_req_pq_multi) {
     872           18 :                 g_srv.handshake_req_pq_count++;
     873           18 :                 handshake_on_req_pq_multi(tl_body, tl_body_len);
     874           10 :             } else if (raw_crc == CRC_req_DH_params
     875            8 :                        && g_srv.handshake_mode >= 2) {
     876            8 :                 g_srv.handshake_req_dh_count++;
     877            8 :                 handshake_on_req_dh_params(tl_body, tl_body_len);
     878            2 :             } else if (raw_crc == CRC_req_DH_params) {
     879              :                 /* Count it but do not reply — tests can observe the
     880              :                  * counter to prove the client reached step 2. */
     881            0 :                 g_srv.handshake_req_dh_count++;
     882            2 :             } else if (raw_crc == CRC_set_client_DH_params
     883            2 :                        && g_srv.handshake_mode == 3) {
     884              :                 /* TEST-72: full DH — compute auth_key from client's g_b,
     885              :                  * verify key_hash, send dh_gen_ok, persist session. */
     886            2 :                 g_srv.handshake_set_client_dh_count++;
     887            2 :                 handshake_on_set_client_dh(tl_body, tl_body_len);
     888              :             }
     889           28 :             continue;
     890              :         }
     891              : 
     892          628 :         if (key_id != g_srv.auth_key_id) {
     893              :             /* Unencrypted handshake frame: auth_key_id == 0.
     894              :              * Record the leading CRC for mt_server_request_crc_count().
     895              :              * Unencrypted layout: key_id(8) + msg_id(8) + msg_len(4) + body */
     896            2 :             if (key_id == 0 && payload_len >= 24) {
     897            2 :                 uint32_t raw_crc = (uint32_t)frame[20]
     898            2 :                                  | ((uint32_t)frame[21] << 8)
     899            2 :                                  | ((uint32_t)frame[22] << 16)
     900            2 :                                  | ((uint32_t)frame[23] << 24);
     901            2 :                 size_t slot = g_srv.crc_ring_count % MT_CRC_RING_SIZE;
     902            2 :                 g_srv.crc_ring[slot] = raw_crc;
     903            2 :                 g_srv.crc_ring_count++;
     904              :             } else {
     905            0 :                 fprintf(stderr, "mt_server: auth_key_id mismatch "
     906              :                         "(got %016llx, want %016llx)\n",
     907              :                         (unsigned long long)key_id,
     908            0 :                         (unsigned long long)g_srv.auth_key_id);
     909              :             }
     910            2 :             continue;
     911              :         }
     912              : 
     913              :         uint8_t msg_key[16];
     914          626 :         memcpy(msg_key, frame + 8, 16);
     915          626 :         const uint8_t *cipher = frame + 24;
     916          626 :         size_t cipher_len = payload_len - 24;
     917              : 
     918          626 :         uint8_t *plain = (uint8_t *)malloc(cipher_len);
     919          626 :         if (!plain) continue;
     920          626 :         size_t plain_len = 0;
     921              : 
     922          626 :         int rc = mtproto_decrypt(cipher, cipher_len,
     923              :                                  g_srv.auth_key, msg_key, 0,
     924              :                                  plain, &plain_len);
     925          626 :         if (rc != 0) {
     926            0 :             fprintf(stderr, "mt_server: mtproto_decrypt failed\n");
     927            0 :             free(plain);
     928            0 :             continue;
     929              :         }
     930          626 :         dispatch_frame(plain, plain_len);
     931          626 :         free(plain);
     932              :     }
     933              : }
     934              : 
     935          626 : static void dispatch_frame(const uint8_t *plain, size_t plain_len) {
     936              :     /* plaintext header: salt(8) + session_id(8) + msg_id(8) + seq_no(4) + len(4) + body */
     937          626 :     if (plain_len < 32) return;
     938          626 :     uint64_t client_session = 0;
     939         5634 :     for (int i = 0; i < 8; ++i) {
     940         5008 :         client_session |= ((uint64_t)plain[8 + i]) << (i * 8);
     941              :     }
     942          626 :     uint64_t msg_id = 0;
     943         5634 :     for (int i = 0; i < 8; ++i) {
     944         5008 :         msg_id |= ((uint64_t)plain[16 + i]) << (i * 8);
     945              :     }
     946          626 :     uint32_t body_len = 0;
     947         3130 :     for (int i = 0; i < 4; ++i) {
     948         2504 :         body_len |= ((uint32_t)plain[28 + i]) << (i * 8);
     949              :     }
     950          626 :     if (32 + body_len > plain_len) return;
     951          626 :     const uint8_t *body = plain + 32;
     952              : 
     953              :     /* First frame carries the client session id — pin it so later msg_container
     954              :      * updates use the same one. If the client somehow changes it, that's a
     955              :      * protocol error we mirror by just echoing back whatever we last saw. */
     956          626 :     if (g_srv.session_id == 0x1122334455667788ULL) {
     957              :         /* keep the seeded value — the client echoes this from session.bin */
     958              :     }
     959              :     (void)client_session;
     960              : 
     961          626 :     unwrap_and_dispatch(msg_id, body, body_len);
     962              : }
     963              : 
     964          626 : static void unwrap_and_dispatch(uint64_t req_msg_id,
     965              :                                 const uint8_t *body, size_t body_len) {
     966         1250 :     if (body_len < 4) return;
     967          626 :     const uint8_t *cur = body;
     968          626 :     size_t remaining = body_len;
     969              : 
     970              :     /* Peel invokeWithLayer / initConnection off the front. */
     971         1842 :     for (int depth = 0; depth < 3; ++depth) {
     972         1842 :         if (remaining < 4) return;
     973         1842 :         uint32_t crc = (uint32_t)cur[0] | ((uint32_t)cur[1] << 8)
     974         1842 :                      | ((uint32_t)cur[2] << 16) | ((uint32_t)cur[3] << 24);
     975         1842 :         if (crc == CRC_invokeWithLayer) {
     976          608 :             if (remaining < 8) return;
     977          608 :             cur += 8; remaining -= 8;   /* CRC + layer:int */
     978          608 :             continue;
     979              :         }
     980         1234 :         if (crc == CRC_initConnection) {
     981              :             /* CRC(4) flags(4) api_id(4) string×6 [proxy:flags.0?] [params:flags.1?] query:!X */
     982          608 :             TlReader r = tl_reader_init(cur, remaining);
     983          608 :             tl_read_uint32(&r);                 /* CRC */
     984          608 :             uint32_t flags = tl_read_uint32(&r);
     985          608 :             tl_read_int32(&r);                  /* api_id */
     986         4256 :             for (int i = 0; i < 6; ++i) {
     987         3648 :                 if (tl_skip_string(&r) != 0) return;
     988              :             }
     989          608 :             if (flags & 0x1) {
     990              :                 /* inputClientProxy#75588b3f server:string port:int */
     991            0 :                 tl_read_uint32(&r);
     992            0 :                 if (tl_skip_string(&r) != 0) return;
     993            0 :                 tl_read_int32(&r);
     994              :             }
     995          608 :             if (flags & 0x2) {
     996              :                 /* jsonObject#7d748d04 — walking JSONValue is out of scope;
     997              :                  * the client never sets this flag so bail if we ever see it. */
     998            0 :                 return;
     999              :             }
    1000          608 :             size_t consumed = r.pos;
    1001          608 :             cur += consumed; remaining -= consumed;
    1002          608 :             continue;
    1003              :         }
    1004          626 :         break;
    1005              :     }
    1006              : 
    1007          626 :     if (remaining < 4) return;
    1008          626 :     uint32_t inner_crc = (uint32_t)cur[0] | ((uint32_t)cur[1] << 8)
    1009          626 :                        | ((uint32_t)cur[2] << 16) | ((uint32_t)cur[3] << 24);
    1010              : 
    1011              :     /* One-shot bad_server_salt injection — bounces the client back with a
    1012              :      * fresh salt and forces it to resend. The handler is not called on this
    1013              :      * round; it fires on the retry. */
    1014          626 :     if (g_srv.bad_salt_once_pending) {
    1015            4 :         g_srv.bad_salt_once_pending = 0;
    1016              :         /* bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int
    1017              :          *                          error_code:int new_server_salt:long */
    1018              :         uint8_t buf[4 + 8 + 4 + 4 + 8];
    1019            4 :         uint32_t crc = 0xedab447bU;
    1020           20 :         for (int i = 0; i < 4; ++i) buf[i] = (uint8_t)(crc >> (i * 8));
    1021           36 :         for (int i = 0; i < 8; ++i) buf[4 + i] = (uint8_t)(req_msg_id >> (i * 8));
    1022              :         /* bad_msg_seqno + error_code 48 (= incorrect server salt) — values
    1023              :          * are informational only for the client's logger. */
    1024            4 :         int32_t seq0 = 0;
    1025           20 :         for (int i = 0; i < 4; ++i) buf[12 + i] = (uint8_t)(seq0 >> (i * 8));
    1026            4 :         int32_t ec = 48;
    1027           20 :         for (int i = 0; i < 4; ++i) buf[16 + i] = (uint8_t)(ec >> (i * 8));
    1028           36 :         for (int i = 0; i < 8; ++i) {
    1029           32 :             buf[20 + i] = (uint8_t)(g_srv.bad_salt_new_salt >> (i * 8));
    1030              :         }
    1031            4 :         queue_frame(buf, sizeof(buf));
    1032              :         /* Update server salt so subsequent responses use what the client now
    1033              :          * expects — the client discards the inbound salt, so this is purely
    1034              :          * cosmetic (a real server would). */
    1035            4 :         g_srv.server_salt = g_srv.bad_salt_new_salt;
    1036            4 :         return;
    1037              :     }
    1038              : 
    1039              :     /* Record the inner CRC in the ring buffer. */
    1040              :     {
    1041          622 :         size_t slot = g_srv.crc_ring_count % MT_CRC_RING_SIZE;
    1042          622 :         g_srv.crc_ring[slot] = inner_crc;
    1043          622 :         g_srv.crc_ring_count++;
    1044              :     }
    1045              : 
    1046              :     /* Drain any service frames queued by mt_server_reply_* helpers
    1047              :      * (TEST-88). They land on the wire ahead of the real result so the
    1048              :      * client's classify_service_frame loop observes them exactly once per
    1049              :      * iteration. queue_frame wraps each body in its own encrypted envelope,
    1050              :      * which the client treats as an independent frame. */
    1051          650 :     for (size_t i = 0; i < g_srv.pending_service_count; ++i) {
    1052           28 :         queue_frame(g_srv.pending_service_frames[i].bytes,
    1053              :                     g_srv.pending_service_frames[i].len);
    1054           28 :         free(g_srv.pending_service_frames[i].bytes);
    1055           28 :         g_srv.pending_service_frames[i].bytes = NULL;
    1056           28 :         g_srv.pending_service_frames[i].len   = 0;
    1057              :     }
    1058          622 :     g_srv.pending_service_count = 0;
    1059              : 
    1060              :     /* Record + invoke handler. */
    1061          622 :     g_srv.rpc_call_count++;
    1062          622 :     g_srv.current_req_msg_id = req_msg_id;
    1063              : 
    1064          802 :     for (size_t i = 0; i < MT_MAX_HANDLERS; ++i) {
    1065          800 :         if (g_srv.handlers[i].used && g_srv.handlers[i].crc == inner_crc) {
    1066          620 :             MtRpcContext ctx = {
    1067              :                 .req_msg_id   = req_msg_id,
    1068              :                 .req_crc      = inner_crc,
    1069              :                 .req_body     = cur,
    1070              :                 .req_body_len = remaining,
    1071          620 :                 .user_ctx     = g_srv.handlers[i].ctx,
    1072              :             };
    1073          620 :             g_srv.handlers[i].fn(&ctx);
    1074          620 :             return;
    1075              :         }
    1076              :     }
    1077              : 
    1078              :     /* No handler → auto rpc_error so the test fails explicitly rather than
    1079              :      * hanging on recv. */
    1080            2 :     MtRpcContext ctx = {
    1081              :         .req_msg_id = req_msg_id,
    1082              :         .req_crc    = inner_crc,
    1083              :         .req_body   = cur,
    1084              :         .req_body_len = remaining,
    1085              :         .user_ctx   = NULL,
    1086              :     };
    1087              :     char buf[64];
    1088            2 :     snprintf(buf, sizeof(buf), "NO_HANDLER_CRC_%08x", inner_crc);
    1089            2 :     mt_server_reply_error(&ctx, 500, buf);
    1090              : }
    1091              : 
    1092          654 : static void queue_frame(const uint8_t *body, size_t body_len) {
    1093              :     /* Build plaintext header: salt + session_id + msg_id + seq_no + len + body + padding. */
    1094              :     TlWriter plain;
    1095          654 :     tl_writer_init(&plain);
    1096          654 :     tl_write_uint64(&plain, g_srv.server_salt);
    1097          654 :     uint64_t effective_session_id = g_srv.session_id;
    1098          654 :     if (g_srv.wrong_session_id_once_pending) {
    1099            2 :         g_srv.wrong_session_id_once_pending = 0;
    1100            2 :         effective_session_id ^= 0xFFFFFFFFFFFFFFFFULL;  /* flip all bits */
    1101              :     }
    1102          654 :     tl_write_uint64(&plain, effective_session_id);
    1103          654 :     tl_write_uint64(&plain, make_server_msg_id());
    1104              :     /* seq_no: bump by 2 for content-related, start at 1 so first is 1, 3, 5… */
    1105          654 :     g_srv.seq_no += 2;
    1106          654 :     tl_write_uint32(&plain, g_srv.seq_no - 1);
    1107          654 :     tl_write_uint32(&plain, (uint32_t)body_len);
    1108          654 :     tl_write_raw(&plain, body, body_len);
    1109              : 
    1110          654 :     encrypt_and_queue(plain.data, plain.len);
    1111          654 :     tl_writer_free(&plain);
    1112          654 : }
    1113              : 
    1114              : /* ---- TEST-71 / US-20 handshake unencrypted-frame handlers ---- */
    1115              : 
    1116              : /** Queue an unencrypted (auth_key_id = 0) server → client frame with the
    1117              :  *  handshake response TL. Mirrors rpc_send_unencrypted on the client side
    1118              :  *  so the client's rpc_recv_unencrypted parser picks it up. */
    1119           28 : static void handshake_queue_unenc(const uint8_t *tl, size_t tl_len) {
    1120              :     /* Wire: auth_key_id(8) + msg_id(8) + len(4) + body */
    1121           28 :     TlWriter w; tl_writer_init(&w);
    1122           28 :     tl_write_uint64(&w, 0);                       /* auth_key_id */
    1123           28 :     tl_write_uint64(&w, make_server_msg_id());    /* server msg_id */
    1124           28 :     tl_write_uint32(&w, (uint32_t)tl_len);
    1125           28 :     tl_write_raw(&w, tl, tl_len);
    1126              : 
    1127           28 :     size_t units = w.len / 4;
    1128           28 :     if (units < 0x7F) {
    1129           28 :         uint8_t p = (uint8_t)units;
    1130           28 :         mock_socket_append_response(&p, 1);
    1131              :     } else {
    1132              :         uint8_t p[4];
    1133            0 :         p[0] = 0x7F;
    1134            0 :         p[1] = (uint8_t)(units & 0xFFu);
    1135            0 :         p[2] = (uint8_t)((units >> 8) & 0xFFu);
    1136            0 :         p[3] = (uint8_t)((units >> 16) & 0xFFu);
    1137            0 :         mock_socket_append_response(p, 4);
    1138              :     }
    1139           28 :     mock_socket_append_response(w.data, w.len);
    1140           28 :     tl_writer_free(&w);
    1141           28 : }
    1142              : 
    1143              : /** Handle an incoming req_pq_multi frame, emit a resPQ per the current
    1144              :  *  cold-boot variant. Assumes @p body points at the CRC-prefixed TL body
    1145              :  *  (CRC already consumed by the caller would break layout — we take the
    1146              :  *  whole 4+16 body here). */
    1147           18 : static void handshake_on_req_pq_multi(const uint8_t *body, size_t body_len) {
    1148           18 :     if (body_len < 4 + 16) return;
    1149              :     /* body = CRC(4) nonce(16) */
    1150              :     uint8_t client_nonce[16];
    1151           18 :     memcpy(client_nonce, body + 4, 16);
    1152              : 
    1153              :     /* Echo (possibly tampered) nonce back. */
    1154              :     uint8_t echo_nonce[16];
    1155           18 :     memcpy(echo_nonce, client_nonce, 16);
    1156           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_NONCE_TAMPER) {
    1157           34 :         for (int i = 0; i < 16; ++i) echo_nonce[i] ^= 0xFF;
    1158              :     }
    1159              : 
    1160              :     /* Deterministic server_nonce for reproducibility. */
    1161              :     uint8_t server_nonce[16];
    1162           18 :     memset(server_nonce, 0xBB, 16);
    1163              : 
    1164              :     /* PQ: default 21 (= 3 * 7 — tiny so Pollard's rho finishes instantly).
    1165              :      * MT_COLD_BOOT_BAD_PQ uses a 64-bit prime so pq_factorize fails. */
    1166           18 :     uint64_t pq_val = 21ULL;
    1167           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_BAD_PQ) {
    1168              :         /* 2^64 - 59 is a prime. Small enough to survive Pollard's rho
    1169              :          * 20-c sweep without factoring. */
    1170            2 :         pq_val = 0xFFFFFFFFFFFFFFC5ULL;
    1171              :     }
    1172              :     /* Encode pq as big-endian minimum-length bytes. */
    1173              :     uint8_t pq_be[8];
    1174           18 :     size_t pq_be_len = 0;
    1175          162 :     for (int i = 7; i >= 0; --i) {
    1176          144 :         uint8_t byte = (uint8_t)((pq_val >> (i * 8)) & 0xFFu);
    1177          144 :         if (pq_be_len > 0 || byte != 0 || i == 0) {
    1178           32 :             pq_be[pq_be_len++] = byte;
    1179              :         }
    1180              :     }
    1181              : 
    1182           18 :     uint64_t fingerprint = COLD_BOOT_FP_OK;
    1183           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_BAD_FINGERPRINT) {
    1184            2 :         fingerprint = COLD_BOOT_FP_BAD;
    1185              :     }
    1186              : 
    1187           18 :     uint32_t constructor = CRC_resPQ;
    1188           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_WRONG_CONSTRUCTOR) {
    1189            2 :         constructor = 0xDEADBEEFU;
    1190              :     }
    1191              : 
    1192           18 :     TlWriter tl; tl_writer_init(&tl);
    1193           18 :     tl_write_uint32(&tl, constructor);
    1194           18 :     tl_write_int128(&tl, echo_nonce);
    1195           18 :     tl_write_int128(&tl, server_nonce);
    1196           18 :     tl_write_bytes(&tl, pq_be, pq_be_len);
    1197           18 :     tl_write_uint32(&tl, CRC_vector);
    1198           18 :     tl_write_uint32(&tl, 1);                    /* one fingerprint entry */
    1199           18 :     tl_write_uint64(&tl, fingerprint);
    1200           18 :     handshake_queue_unenc(tl.data, tl.len);
    1201           18 :     tl_writer_free(&tl);
    1202              : }
    1203              : 
    1204              : /* ---- TEST-72: RSA-PAD decrypt (inverse of rsa_pad_encrypt in mtproto_auth.c) ----
    1205              :  *
    1206              :  * Reverses the RSA_PAD scheme used by the client to encrypt p_q_inner_data_dc.
    1207              :  * Returns 0 on success and writes the decrypted payload into @p plain_out[192].
    1208              :  *
    1209              :  * TEST_ONLY — only called from handshake_on_req_dh_params in mode 3. */
    1210            2 : static int rsa_pad_decrypt(const uint8_t *ciphertext, size_t cipher_len,
    1211              :                            uint8_t *plain_out  /* must be >= 192 bytes */) {
    1212            2 :     if (cipher_len != 256) return -1;
    1213              : 
    1214              :     /* TEST_ONLY private key — matches tests/mocks/telegram_server_key.c pubkey. */
    1215              :     static const char TEST_PRIVATE_KEY_PEM[] =
    1216              :         "-----BEGIN PRIVATE KEY-----\n"
    1217              :         "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbG/j8RdvTAAWv\n"
    1218              :         "870ayFCbJI73fEEB43/l/NnocYeAh9L/Zcv9HwYwFOXms9o2ccvp+e/4GE55vUzY\n"
    1219              :         "8XrM1h6dtClGYNvRNpvcthfmVrpGGIjKH2b3snip4ajtUOcZIyTynZo1vMG5uqCx\n"
    1220              :         "YaXWyhBwMPJQ87Gw5WbcaKNJWg3jZ1GI0g+tJUAqXpfPwF3KjKzIZwa/rbnJ9ick\n"
    1221              :         "OX12OaG1c2enW1+sv1K1qz6CcxmbCyRuD+xUKVTLHc5ykmcYDJ12CLES8DdfbPOt\n"
    1222              :         "UGAP082CQOptfpPm5fvnU6c53suIItnddjiT74+hyQBXQpvWXKIZzFEdgxJ2QUzp\n"
    1223              :         "TlRwqVijAgMBAAECggEAJll6rIjrKlaRkWjOgw4u28Tksize98QTTb4/9DgJnA44\n"
    1224              :         "7VtyXYFrqryoAOvL0nU9SPq6SZlc4b2bf/HofjecdzphkByHjMkXLTE6ZIFh6c3M\n"
    1225              :         "GEk+UJSoP7xi41YC5VSqoG+1/n5OWYjajTDK63mnKc34Q2qVLtrxHSKj6JFi6Kuy\n"
    1226              :         "uO1+lXUiGS5vvlPllAoDTIFn4e4PHChHeVNiwzBe8HNgVWc9JasL7IiS37gsfuZC\n"
    1227              :         "l4QlW3IkhCMkb5fj7xV/xcwQ4SuYWcNkrfuX7Fu3g4Hpeir3xJlJMxh2iqJnARvM\n"
    1228              :         "nLy9gY04ejqdxI9+VopQyXafe0tP/veuqO7RzR3x+QKBgQDQvd9drWmvWM9g7Lrj\n"
    1229              :         "jEr4oo00b5/cOQd12VJaxhWUdfzyDxsygGPeuP3/7QxgwxB+VmnsS2xfNvv4hDdd\n"
    1230              :         "vL6DhFPicTV5Xv25U52QaxNefc5XSSzY68XMFtUyWjzD+35GIUO27TnDfQhFBkbS\n"
    1231              :         "RFDg3uJ1KbcI05nM+DSQJZPaqQKBgQC+ObQxrgZBPd6K6vVM/uaoA45Htc8UVIS/\n"
    1232              :         "+eU69k1lVp6BZm1ebaTOf6CTOO/AcyM4jZu7lVFwfanVMVSfJ+Nbd1BEXtg1BKou\n"
    1233              :         "2cLlYkJX6mVmCs39sQDscvoO67RMhR4jshCE0nxaAxFatdEfpq9ZTrQIXWKnQhRq\n"
    1234              :         "nHsyIyHUawKBgQCwTf5vx70AvfkB+1BqSp8z2096X2FdBsn3TqORSccGSpVm+T1W\n"
    1235              :         "bTxs7ECUPWn7/CVdH619R8LztKQjJcEBqh4bRNP46PdqWMHiGu51AQst/wIdlQ+M\n"
    1236              :         "865vj0VorvCt8yeXIhdoVHs6Ust+SSveApdxJq+Ml7whd19q0KTMrwBvaQKBgC8C\n"
    1237              :         "i6mLXDhbVdf24NA6Xj4/QrYuFBLuIDBhTWkY3V+h3GIWMgkYB5aQq9o2Q+nHini7\n"
    1238              :         "ZjUhXZLzOzlYi5UZgnJkNg3vcncHxBb38dZGRib74jspiGadi6DjeTCex1vxudUQ\n"
    1239              :         "eEyax+hmwa8tJ5Uu2D612IAItAypo+oE6d0mGYIpAoGAS2P6e2iOW2HG3CA2kiLp\n"
    1240              :         "xw/guEi8BTTljxMSUEqS5YhDI2uVe2QEYlvgDG7zYMl2UzoadZcK1VVa01WM2LDC\n"
    1241              :         "wThzA+NhfHS4ukPuuOkJUVwGAJGUG/KcIypTTYz4RPj6BpALLXstF+Qf8F3aqDvL\n"
    1242              :         "bAd3PHlfEWOhiWzBj/dVjUY=\n"
    1243              :         "-----END PRIVATE KEY-----\n";
    1244              : 
    1245            2 :     CryptoRsaKey *priv = crypto_rsa_load_private(TEST_PRIVATE_KEY_PEM);
    1246            2 :     if (!priv) {
    1247            0 :         fprintf(stderr, "mock: rsa_pad_decrypt: failed to load private key\n");
    1248            0 :         return -1;
    1249              :     }
    1250              : 
    1251              :     /* Step 1: RSA decrypt (NO_PADDING) → rsa_output[256] */
    1252              :     uint8_t rsa_output[256];
    1253            2 :     size_t rsa_out_len = sizeof(rsa_output);
    1254            2 :     if (crypto_rsa_private_decrypt(priv, ciphertext, cipher_len,
    1255              :                                    rsa_output, &rsa_out_len) != 0) {
    1256            0 :         crypto_rsa_free(priv);
    1257            0 :         fprintf(stderr, "mock: rsa_pad_decrypt: RSA decrypt failed\n");
    1258            0 :         return -1;
    1259              :     }
    1260            2 :     crypto_rsa_free(priv);
    1261              : 
    1262              :     /* Step 2: Split into temp_key_xor(32) + aes_encrypted(224) */
    1263            2 :     const uint8_t *temp_key_xor = rsa_output;
    1264            2 :     const uint8_t *aes_encrypted = rsa_output + 32;
    1265              : 
    1266              :     /* Step 3: SHA256(aes_encrypted) → aes_hash */
    1267              :     uint8_t aes_hash[32];
    1268            2 :     crypto_sha256(aes_encrypted, 224, aes_hash);
    1269              : 
    1270              :     /* Step 4: temp_key = temp_key_xor XOR aes_hash */
    1271              :     uint8_t temp_key[32];
    1272           66 :     for (int i = 0; i < 32; i++) temp_key[i] = temp_key_xor[i] ^ aes_hash[i];
    1273              : 
    1274              :     /* Step 5: AES-IGE decrypt(aes_encrypted, key=temp_key, IV=zeros) → data_with_hash */
    1275              :     uint8_t zero_iv[32];
    1276            2 :     memset(zero_iv, 0, 32);
    1277              :     uint8_t data_with_hash[224];
    1278            2 :     aes_ige_decrypt(aes_encrypted, 224, temp_key, zero_iv, data_with_hash);
    1279              : 
    1280              :     /* Step 6: data_with_hash = reversed(192) + hash(32)
    1281              :      * Verify SHA256(temp_key + reversed) == hash */
    1282              :     uint8_t verify_buf[32 + 192];
    1283            2 :     memcpy(verify_buf, temp_key, 32);
    1284            2 :     memcpy(verify_buf + 32, data_with_hash, 192);
    1285              :     uint8_t expected_hash[32];
    1286            2 :     crypto_sha256(verify_buf, sizeof(verify_buf), expected_hash);
    1287            2 :     if (memcmp(expected_hash, data_with_hash + 192, 32) != 0) {
    1288            0 :         fprintf(stderr, "mock: rsa_pad_decrypt: hash mismatch\n");
    1289            0 :         return -1;
    1290              :     }
    1291              : 
    1292              :     /* Step 7: un-reverse to get the original padded data */
    1293            2 :     const uint8_t *reversed = data_with_hash;
    1294          386 :     for (int i = 0; i < 192; i++) plain_out[i] = reversed[191 - i];
    1295              : 
    1296            2 :     return 0;
    1297              : }
    1298              : 
    1299              : /** Handle an incoming req_DH_params frame.
    1300              :  *
    1301              :  * Mode 2 (original): emits server_DH_params_ok with random encrypted_answer
    1302              :  * (garbage). The client's AES-IGE decrypt yields garbage, the inner CRC check
    1303              :  * fails, and auth_step_parse_dh returns -1 — the negative path.
    1304              :  *
    1305              :  * Mode 3 (TEST-72): RSA-PAD-decrypts the client's payload to extract new_nonce,
    1306              :  * generates a valid server_DH_inner_data (g=2, safe prime, g_a=g^b mod p),
    1307              :  * AES-IGE-encrypts it with the derived temp key, and sends server_DH_params_ok.
    1308              :  * Stores DH state for subsequent set_client_DH_params handling. */
    1309            8 : static void handshake_on_req_dh_params(const uint8_t *body, size_t body_len) {
    1310           14 :     if (body_len < 4 + 16 + 16) return;
    1311              :     /* body = CRC(4) nonce(16) server_nonce(16) p:bytes q:bytes fp:long encrypted_data:bytes */
    1312              :     uint8_t nonce[16], server_nonce[16];
    1313            8 :     memcpy(nonce,        body + 4,       16);
    1314            8 :     memcpy(server_nonce, body + 4 + 16,  16);
    1315              : 
    1316            8 :     if (g_srv.handshake_mode < 3) {
    1317              :         /* Original mode 2 path: random garbage encrypted_answer. */
    1318              :         uint8_t enc_answer[32];
    1319            6 :         crypto_rand_bytes(enc_answer, sizeof(enc_answer));
    1320            6 :         TlWriter tl; tl_writer_init(&tl);
    1321            6 :         tl_write_uint32(&tl, CRC_server_DH_params_ok);
    1322            6 :         tl_write_int128(&tl, nonce);
    1323            6 :         tl_write_int128(&tl, server_nonce);
    1324            6 :         tl_write_bytes(&tl, enc_answer, sizeof(enc_answer));
    1325            6 :         handshake_queue_unenc(tl.data, tl.len);
    1326            6 :         tl_writer_free(&tl);
    1327            6 :         return;
    1328              :     }
    1329              : 
    1330              :     /* ---- Mode 3: full DH handshake ---- */
    1331              : 
    1332              :     /* Parse the req_DH_params body to find the encrypted_data.
    1333              :      * Layout: CRC(4) nonce(16) server_nonce(16) p:bytes q:bytes fp:int64 encrypted_data:bytes */
    1334            2 :     TlReader r = tl_reader_init(body, body_len);
    1335            2 :     tl_read_uint32(&r);            /* CRC */
    1336            2 :     uint8_t _nonce[16]; tl_read_int128(&r, _nonce);
    1337            2 :     uint8_t _snonce[16]; tl_read_int128(&r, _snonce);
    1338            2 :     size_t p_len = 0, q_len = 0, enc_len = 0;
    1339            2 :     uint8_t *p_bytes = tl_read_bytes(&r, &p_len);
    1340            2 :     free(p_bytes);
    1341            2 :     uint8_t *q_bytes = tl_read_bytes(&r, &q_len);
    1342            2 :     free(q_bytes);
    1343            2 :     tl_read_uint64(&r);            /* fingerprint */
    1344            2 :     uint8_t *enc_data = tl_read_bytes(&r, &enc_len);
    1345            2 :     if (!enc_data || enc_len != 256) {
    1346            0 :         free(enc_data);
    1347            0 :         fprintf(stderr, "mock: full DH: unexpected enc_data size %zu\n", enc_len);
    1348            0 :         return;
    1349              :     }
    1350              : 
    1351              :     /* RSA-PAD decrypt to get padded p_q_inner_data_dc (192 bytes) */
    1352              :     uint8_t inner_plain[192];
    1353            2 :     if (rsa_pad_decrypt(enc_data, enc_len, inner_plain) != 0) {
    1354            0 :         free(enc_data);
    1355            0 :         fprintf(stderr, "mock: full DH: RSA_PAD decrypt failed\n");
    1356            0 :         return;
    1357              :     }
    1358            2 :     free(enc_data);
    1359              : 
    1360              :     /* Parse p_q_inner_data_dc from inner_plain.
    1361              :      * TL: CRC(4) pq:bytes p:bytes q:bytes nonce(16) server_nonce(16) new_nonce(32) dc:int */
    1362            2 :     TlReader ir = tl_reader_init(inner_plain, sizeof(inner_plain));
    1363            2 :     uint32_t inner_crc = tl_read_uint32(&ir);
    1364            2 :     if (inner_crc != 0xb936a01aU) {  /* CRC_p_q_inner_data_dc */
    1365            0 :         fprintf(stderr, "mock: full DH: unexpected inner CRC 0x%08x\n", inner_crc);
    1366            0 :         return;
    1367              :     }
    1368              :     /* Skip pq, p, q bytes */
    1369            2 :     size_t tmp_len = 0;
    1370            2 :     uint8_t *tmp = tl_read_bytes(&ir, &tmp_len); free(tmp);  /* pq */
    1371            2 :     tmp = tl_read_bytes(&ir, &tmp_len); free(tmp);             /* p */
    1372            2 :     tmp = tl_read_bytes(&ir, &tmp_len); free(tmp);             /* q */
    1373              :     /* Read nonce, server_nonce, new_nonce */
    1374              :     uint8_t extracted_nonce[16], extracted_snonce[16], new_nonce[32];
    1375            2 :     tl_read_int128(&ir, extracted_nonce);
    1376            2 :     tl_read_int128(&ir, extracted_snonce);
    1377            2 :     tl_read_int256(&ir, new_nonce);
    1378              : 
    1379              :     /* Save DH state for set_client_DH_params */
    1380            2 :     memcpy(g_srv.hs_new_nonce,    new_nonce,        32);
    1381            2 :     memcpy(g_srv.hs_nonce,        extracted_nonce,  16);
    1382            2 :     memcpy(g_srv.hs_server_nonce, extracted_snonce, 16);
    1383              : 
    1384              :     /* Use a fixed 256-bit safe prime for DH (TEST_ONLY).
    1385              :      * This prime was generated with openssl: BN_generate_prime_ex(p,256,1,...).
    1386              :      * g=2 is a generator for this group. */
    1387              :     static const uint8_t TEST_DH_PRIME[32] = {
    1388              :         0xfa, 0xd0, 0x8e, 0x08, 0xa4, 0x4d, 0x25, 0xaa,
    1389              :         0x45, 0x2b, 0xda, 0x58, 0x62, 0xac, 0xc4, 0xb2,
    1390              :         0x76, 0x23, 0xd3, 0x30, 0x4d, 0xd0, 0x9d, 0x64,
    1391              :         0xc1, 0xdd, 0xc0, 0xfb, 0x35, 0x09, 0x40, 0xdb
    1392              :     };
    1393            2 :     memcpy(g_srv.hs_dh_prime, TEST_DH_PRIME, 32);
    1394              : 
    1395              :     /* Generate server-side DH secret b (256 bytes) and compute g_b = 2^b mod p */
    1396            2 :     crypto_rand_bytes(g_srv.hs_b, 256);
    1397              : 
    1398            2 :     uint8_t g_be[1] = { 0x02 };  /* g = 2 as 1-byte big-endian */
    1399              :     uint8_t g_b[32];
    1400            2 :     size_t g_b_len = sizeof(g_b);
    1401            2 :     CryptoBnCtx *bn_ctx = crypto_bn_ctx_new();
    1402            2 :     if (!bn_ctx) { fprintf(stderr, "mock: full DH: BN ctx alloc failed\n"); return; }
    1403            2 :     if (crypto_bn_mod_exp(g_b, &g_b_len, g_be, 1,
    1404              :                           g_srv.hs_b, 256,
    1405              :                           TEST_DH_PRIME, 32, bn_ctx) != 0) {
    1406            0 :         crypto_bn_ctx_free(bn_ctx);
    1407            0 :         fprintf(stderr, "mock: full DH: g_b computation failed\n");
    1408            0 :         return;
    1409              :     }
    1410            2 :     crypto_bn_ctx_free(bn_ctx);
    1411              : 
    1412              :     /* Derive temp AES key/IV from new_nonce + server_nonce (same as production) */
    1413              :     uint8_t tmp_aes_key[32], tmp_aes_iv[32];
    1414              :     {
    1415              :         uint8_t buf[64];
    1416              :         uint8_t sha1_a[20], sha1_b[20], sha1_c[20];
    1417              : 
    1418            2 :         memcpy(buf, new_nonce, 32); memcpy(buf + 32, extracted_snonce, 16);
    1419            2 :         crypto_sha1(buf, 48, sha1_a);
    1420            2 :         memcpy(buf, extracted_snonce, 16); memcpy(buf + 16, new_nonce, 32);
    1421            2 :         crypto_sha1(buf, 48, sha1_b);
    1422            2 :         memcpy(buf, new_nonce, 32); memcpy(buf + 32, new_nonce, 32);
    1423            2 :         crypto_sha1(buf, 64, sha1_c);
    1424              : 
    1425            2 :         memcpy(tmp_aes_key,      sha1_a, 20);
    1426            2 :         memcpy(tmp_aes_key + 20, sha1_b, 12);
    1427            2 :         memcpy(tmp_aes_iv,       sha1_b + 12, 8);
    1428            2 :         memcpy(tmp_aes_iv + 8,   sha1_c, 20);
    1429            2 :         memcpy(tmp_aes_iv + 28,  new_nonce, 4);
    1430              :     }
    1431              : 
    1432              :     /* Build server_DH_inner_data TL */
    1433            2 :     TlWriter inner; tl_writer_init(&inner);
    1434            2 :     tl_write_uint32(&inner, CRC_server_DH_inner_data);
    1435            2 :     tl_write_int128(&inner, extracted_nonce);
    1436            2 :     tl_write_int128(&inner, extracted_snonce);
    1437            2 :     tl_write_int32(&inner, 2);                     /* g = 2 */
    1438            2 :     tl_write_bytes(&inner, TEST_DH_PRIME, 32);     /* dh_prime */
    1439            2 :     tl_write_bytes(&inner, g_b, g_b_len);          /* g_a (server's g^b) */
    1440            2 :     tl_write_int32(&inner, (int32_t)time(NULL));   /* server_time */
    1441              : 
    1442              :     /* Prepend SHA1 + pad to 16-byte boundary */
    1443              :     uint8_t sha1_inner[20];
    1444            2 :     crypto_sha1(inner.data, inner.len, sha1_inner);
    1445            2 :     size_t raw_len = 20 + inner.len;
    1446            2 :     size_t padded_len = (raw_len + 15) & ~(size_t)15;
    1447            2 :     uint8_t *padded = (uint8_t *)calloc(1, padded_len);
    1448            2 :     if (!padded) { tl_writer_free(&inner); return; }
    1449            2 :     memcpy(padded, sha1_inner, 20);
    1450            2 :     memcpy(padded + 20, inner.data, inner.len);
    1451            2 :     if (padded_len > raw_len) {
    1452            2 :         crypto_rand_bytes(padded + raw_len, padded_len - raw_len);
    1453              :     }
    1454            2 :     tl_writer_free(&inner);
    1455              : 
    1456              :     /* AES-IGE encrypt */
    1457            2 :     uint8_t *enc_answer = (uint8_t *)malloc(padded_len);
    1458            2 :     if (!enc_answer) { free(padded); return; }
    1459            2 :     aes_ige_encrypt(padded, padded_len, tmp_aes_key, tmp_aes_iv, enc_answer);
    1460            2 :     free(padded);
    1461              : 
    1462              :     /* Send server_DH_params_ok */
    1463            2 :     TlWriter tl; tl_writer_init(&tl);
    1464            2 :     tl_write_uint32(&tl, CRC_server_DH_params_ok);
    1465            2 :     tl_write_int128(&tl, extracted_nonce);
    1466            2 :     tl_write_int128(&tl, extracted_snonce);
    1467            2 :     tl_write_bytes(&tl, enc_answer, padded_len);
    1468            2 :     handshake_queue_unenc(tl.data, tl.len);
    1469            2 :     tl_writer_free(&tl);
    1470            2 :     free(enc_answer);
    1471              : }
    1472              : 
    1473              : /** TEST-72: Handle set_client_DH_params, compute auth_key = g_b^a mod p,
    1474              :  *  verify key_hash, send dh_gen_ok, and persist the session. */
    1475            2 : static void handshake_on_set_client_dh(const uint8_t *body, size_t body_len) {
    1476            2 :     if (body_len < 4 + 16 + 16) return;
    1477              : 
    1478              :     /* Derive temp AES key/IV from stored new_nonce + server_nonce */
    1479              :     uint8_t tmp_aes_key[32], tmp_aes_iv[32];
    1480              :     {
    1481              :         uint8_t buf[64];
    1482              :         uint8_t sha1_a[20], sha1_b[20], sha1_c[20];
    1483              : 
    1484            2 :         memcpy(buf, g_srv.hs_new_nonce, 32);
    1485            2 :         memcpy(buf + 32, g_srv.hs_server_nonce, 16);
    1486            2 :         crypto_sha1(buf, 48, sha1_a);
    1487              : 
    1488            2 :         memcpy(buf, g_srv.hs_server_nonce, 16);
    1489            2 :         memcpy(buf + 16, g_srv.hs_new_nonce, 32);
    1490            2 :         crypto_sha1(buf, 48, sha1_b);
    1491              : 
    1492            2 :         memcpy(buf, g_srv.hs_new_nonce, 32);
    1493            2 :         memcpy(buf + 32, g_srv.hs_new_nonce, 32);
    1494            2 :         crypto_sha1(buf, 64, sha1_c);
    1495              : 
    1496            2 :         memcpy(tmp_aes_key,      sha1_a, 20);
    1497            2 :         memcpy(tmp_aes_key + 20, sha1_b, 12);
    1498            2 :         memcpy(tmp_aes_iv,       sha1_b + 12, 8);
    1499            2 :         memcpy(tmp_aes_iv + 8,   sha1_c, 20);
    1500            2 :         memcpy(tmp_aes_iv + 28,  g_srv.hs_new_nonce, 4);
    1501              :     }
    1502              : 
    1503              :     /* Parse set_client_DH_params body:
    1504              :      * CRC(4) nonce(16) server_nonce(16) encrypted_data:bytes */
    1505            2 :     TlReader r = tl_reader_init(body, body_len);
    1506            2 :     tl_read_uint32(&r);              /* CRC */
    1507            2 :     uint8_t _nonce[16]; tl_read_int128(&r, _nonce);
    1508            2 :     uint8_t _snonce[16]; tl_read_int128(&r, _snonce);
    1509            2 :     size_t enc_len = 0;
    1510            2 :     uint8_t *enc_data = tl_read_bytes(&r, &enc_len);
    1511            2 :     if (!enc_data || enc_len == 0 || enc_len % 16 != 0) {
    1512            0 :         free(enc_data);
    1513            0 :         fprintf(stderr, "mock: set_client_DH: bad encrypted_data len %zu\n", enc_len);
    1514            0 :         return;
    1515              :     }
    1516              : 
    1517              :     /* AES-IGE decrypt encrypted_data */
    1518            2 :     uint8_t *plain = (uint8_t *)malloc(enc_len);
    1519            2 :     if (!plain) { free(enc_data); return; }
    1520            2 :     aes_ige_decrypt(enc_data, enc_len, tmp_aes_key, tmp_aes_iv, plain);
    1521            2 :     free(enc_data);
    1522              : 
    1523              :     /* Parse client_DH_inner_data: SHA1(20) + CRC(4) nonce(16) server_nonce(16)
    1524              :      *                               retry_id:int64 g_b:bytes */
    1525            2 :     if (enc_len < 20 + 4 + 16 + 16 + 8) {
    1526            0 :         free(plain);
    1527            0 :         fprintf(stderr, "mock: set_client_DH: plaintext too short\n");
    1528            0 :         return;
    1529              :     }
    1530            2 :     TlReader ir = tl_reader_init(plain + 20, enc_len - 20);
    1531            2 :     uint32_t crc = tl_read_uint32(&ir);
    1532            2 :     if (crc != 0x6643b654U) {  /* CRC_client_DH_inner_data */
    1533            0 :         free(plain);
    1534            0 :         fprintf(stderr, "mock: set_client_DH: wrong CRC 0x%08x\n", crc);
    1535            0 :         return;
    1536              :     }
    1537            2 :     uint8_t cli_nonce[16]; tl_read_int128(&ir, cli_nonce);
    1538            2 :     uint8_t cli_snonce[16]; tl_read_int128(&ir, cli_snonce);
    1539            2 :     tl_read_uint64(&ir);  /* retry_id */
    1540            2 :     size_t g_b_len = 0;
    1541            2 :     uint8_t *g_b = tl_read_bytes(&ir, &g_b_len);  /* client's g^client_b mod p */
    1542            2 :     if (!g_b || g_b_len == 0) {
    1543            0 :         free(g_b); free(plain);
    1544            0 :         fprintf(stderr, "mock: set_client_DH: failed to read g_b\n");
    1545            0 :         return;
    1546              :     }
    1547            2 :     free(plain);
    1548              : 
    1549              :     /* Save g_a (client's g^client_b mod p) for auth_key computation */
    1550            2 :     if (g_b_len > sizeof(g_srv.hs_g_a)) {
    1551            0 :         free(g_b);
    1552            0 :         fprintf(stderr, "mock: set_client_DH: g_b too large (%zu)\n", g_b_len);
    1553            0 :         return;
    1554              :     }
    1555            2 :     memcpy(g_srv.hs_g_a, g_b, g_b_len);
    1556            2 :     g_srv.hs_g_a_len = g_b_len;
    1557            2 :     free(g_b);
    1558              : 
    1559              :     /* Compute auth_key = g_b^server_b mod dh_prime */
    1560              :     uint8_t auth_key[256];
    1561            2 :     size_t ak_len = sizeof(auth_key);
    1562            2 :     CryptoBnCtx *bn_ctx = crypto_bn_ctx_new();
    1563            2 :     if (!bn_ctx) { fprintf(stderr, "mock: set_client_DH: BN ctx alloc\n"); return; }
    1564            2 :     if (crypto_bn_mod_exp(auth_key, &ak_len,
    1565              :                           g_srv.hs_g_a, g_srv.hs_g_a_len,
    1566              :                           g_srv.hs_b, 256,
    1567              :                           g_srv.hs_dh_prime, 32, bn_ctx) != 0) {
    1568            0 :         crypto_bn_ctx_free(bn_ctx);
    1569            0 :         fprintf(stderr, "mock: set_client_DH: auth_key exp failed\n");
    1570            0 :         return;
    1571              :     }
    1572            2 :     crypto_bn_ctx_free(bn_ctx);
    1573              : 
    1574              :     /* Pad auth_key to 256 bytes */
    1575              :     uint8_t auth_key_padded[256];
    1576            2 :     memset(auth_key_padded, 0, 256);
    1577            2 :     if (ak_len <= 256) {
    1578            2 :         memcpy(auth_key_padded + (256 - ak_len), auth_key, ak_len);
    1579              :     }
    1580              : 
    1581              :     /* Compute new_nonce_hash1 = last 16 bytes of SHA1(new_nonce||0x01||ak_aux_hash[8])
    1582              :      * where ak_aux_hash = SHA1(auth_key)[0:8] */
    1583              :     uint8_t ak_full_hash[20];
    1584            2 :     crypto_sha1(auth_key_padded, 256, ak_full_hash);
    1585              : 
    1586              :     uint8_t nnh_input[32 + 1 + 8];
    1587            2 :     memcpy(nnh_input,      g_srv.hs_new_nonce, 32);
    1588            2 :     nnh_input[32] = 0x01;
    1589            2 :     memcpy(nnh_input + 33, ak_full_hash, 8);
    1590              : 
    1591              :     uint8_t nnh_full[20];
    1592            2 :     crypto_sha1(nnh_input, sizeof(nnh_input), nnh_full);
    1593              :     /* last 16 bytes = nnh_full[4:20] */
    1594              :     uint8_t new_nonce_hash1[16];
    1595            2 :     memcpy(new_nonce_hash1, nnh_full + 4, 16);
    1596              : 
    1597              :     /* Save auth_key to mock server state so encrypted frames work after handshake */
    1598            2 :     memcpy(g_srv.auth_key, auth_key_padded, 256);
    1599            2 :     g_srv.auth_key_id  = derive_auth_key_id(auth_key_padded);
    1600              :     /* salt = new_nonce[0:8] XOR server_nonce[0:8] */
    1601            2 :     g_srv.server_salt = 0;
    1602           18 :     for (int i = 0; i < 8; i++) {
    1603           16 :         ((uint8_t *)&g_srv.server_salt)[i] =
    1604           16 :             g_srv.hs_new_nonce[i] ^ g_srv.hs_server_nonce[i];
    1605              :     }
    1606            2 :     g_srv.session_id = 0xAABBCCDD11223344ULL;
    1607              : 
    1608              :     /* Persist session so the client can verify it was written */
    1609              :     MtProtoSession sess;
    1610            2 :     mtproto_session_init(&sess);
    1611            2 :     mtproto_session_set_auth_key(&sess, auth_key_padded);
    1612            2 :     mtproto_session_set_salt(&sess, g_srv.server_salt);
    1613            2 :     sess.session_id = g_srv.session_id;
    1614            2 :     session_store_save(&sess, 2);  /* dc_id=2 matches the test */
    1615              : 
    1616              :     /* Send dh_gen_ok */
    1617            2 :     TlWriter tl; tl_writer_init(&tl);
    1618            2 :     tl_write_uint32(&tl, CRC_dh_gen_ok);
    1619            2 :     tl_write_int128(&tl, g_srv.hs_nonce);
    1620            2 :     tl_write_int128(&tl, g_srv.hs_server_nonce);
    1621            2 :     tl_write_int128(&tl, new_nonce_hash1);
    1622            2 :     handshake_queue_unenc(tl.data, tl.len);
    1623            2 :     tl_writer_free(&tl);
    1624              : }
    1625              : 
    1626          654 : static void encrypt_and_queue(const uint8_t *plain, size_t plain_len) {
    1627          654 :     uint8_t *enc = (uint8_t *)malloc(plain_len + 1024);
    1628          654 :     if (!enc) return;
    1629          654 :     size_t enc_len = 0;
    1630              :     uint8_t msg_key[16];
    1631          654 :     mtproto_encrypt(plain, plain_len, g_srv.auth_key, 8, enc, &enc_len, msg_key);
    1632              : 
    1633          654 :     size_t wire_len = 8 + 16 + enc_len;
    1634          654 :     uint8_t *wire = (uint8_t *)malloc(wire_len);
    1635          654 :     if (!wire) { free(enc); return; }
    1636         5886 :     for (int i = 0; i < 8; ++i) {
    1637         5232 :         wire[i] = (uint8_t)((g_srv.auth_key_id >> (i * 8)) & 0xFFu);
    1638              :     }
    1639          654 :     memcpy(wire + 8, msg_key, 16);
    1640          654 :     memcpy(wire + 24, enc, enc_len);
    1641          654 :     free(enc);
    1642              : 
    1643              :     /* Abridged length prefix (in 4-byte units). */
    1644          654 :     size_t units = wire_len / 4;
    1645          654 :     if (units < 0x7F) {
    1646          606 :         uint8_t p = (uint8_t)units;
    1647          606 :         mock_socket_append_response(&p, 1);
    1648              :     } else {
    1649              :         uint8_t p[4];
    1650           48 :         p[0] = 0x7F;
    1651           48 :         p[1] = (uint8_t)(units & 0xFFu);
    1652           48 :         p[2] = (uint8_t)((units >> 8) & 0xFFu);
    1653           48 :         p[3] = (uint8_t)((units >> 16) & 0xFFu);
    1654           48 :         mock_socket_append_response(p, 4);
    1655              :     }
    1656          654 :     mock_socket_append_response(wire, wire_len);
    1657          654 :     free(wire);
    1658              : }
        

Generated by: LCOV version 2.0-1