LCOV - code coverage report
Current view: top level - tests/mocks - mock_tel_server.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 91.6 % 819 750
Test Date: 2026-05-06 13:17:06 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   70   /* must exceed SERVICE_FRAME_LIMIT (64) + 1 for storm test */
      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           94 : void mt_server_reply_error(const MtRpcContext *ctx,
     263              :                            int32_t error_code, const char *error_msg) {
     264           94 :     if (!ctx) return;
     265              :     /* rpc_error#2144ca19 = error_code:int error_message:string */
     266              :     TlWriter w;
     267           94 :     tl_writer_init(&w);
     268           94 :     tl_write_uint32(&w, CRC_rpc_error);
     269           94 :     tl_write_int32(&w, error_code);
     270           94 :     tl_write_string(&w, error_msg ? error_msg : "");
     271           94 :     mt_server_reply_result(ctx, w.data, w.len);
     272           94 :     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          140 : static int svc_queue_push(const uint8_t *body, size_t body_len) {
     477          140 :     if (g_srv.pending_service_count >= MT_MAX_PENDING_SVC) return 0;
     478          140 :     uint8_t *copy = (uint8_t *)malloc(body_len);
     479          140 :     if (!copy) return 0;
     480          140 :     memcpy(copy, body, body_len);
     481          140 :     g_srv.pending_service_frames[g_srv.pending_service_count].bytes = copy;
     482          140 :     g_srv.pending_service_frames[g_srv.pending_service_count].len   = body_len;
     483          140 :     g_srv.pending_service_count++;
     484          140 :     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          132 : void mt_server_reply_msgs_ack(const uint64_t *ids, size_t n) {
     515              :     /* msgs_ack#62d6b459 msg_ids:Vector<long> */
     516          132 :     TlWriter w; tl_writer_init(&w);
     517          132 :     tl_write_uint32(&w, CRC_msgs_ack);
     518          132 :     tl_write_uint32(&w, CRC_vector);
     519          132 :     tl_write_uint32(&w, (uint32_t)n);
     520          268 :     for (size_t i = 0; i < n; ++i) {
     521          136 :         tl_write_uint64(&w, ids ? ids[i] : (uint64_t)(0xAC000000DEAD0000ULL + i));
     522              :     }
     523          132 :     svc_queue_push(w.data, w.len);
     524          132 :     tl_writer_free(&w);
     525          132 : }
     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          132 :     for (size_t i = 0; i < count; ++i) {
     557          130 :         uint64_t id = 0xAC000000DEAD0000ULL + i;
     558          130 :         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              :     /* MTProto spec: auth_key_id = last 8 bytes of SHA1(auth_key) = SHA1[12:20]. */
     770              :     uint8_t hash[20];
     771          448 :     crypto_sha1(auth_key, MT_SERVER_AUTH_KEY_SIZE, hash);
     772          448 :     uint64_t id = 0;
     773         4032 :     for (int i = 0; i < 8; ++i) id |= ((uint64_t)hash[12 + i]) << (i * 8);
     774          448 :     return id;
     775              : }
     776              : 
     777          794 : static uint64_t make_server_msg_id(void) {
     778              :     /* Monotonic even msg_ids (server → client use low-bit 1 in real MT,
     779              :      * but the client tolerates either — we just need monotonic). */
     780          794 :     uint64_t now = (uint64_t)time(NULL) << 32;
     781          794 :     if (now <= g_srv.next_server_msg_id) now = g_srv.next_server_msg_id + 4;
     782          794 :     now &= ~((uint64_t)3);
     783          794 :     now |= 1;  /* server → client msg_id is odd */
     784          794 :     g_srv.next_server_msg_id = now;
     785          794 :     return now;
     786              : }
     787              : 
     788              : /* Read an abridged length prefix from buf[cursor..len]. Returns:
     789              :  *   bytes consumed by the prefix on success (1 or 4)
     790              :  *   0 if not enough bytes yet
     791              :  * On success, *payload_len receives the payload size in bytes. */
     792         1322 : static size_t read_abridged_prefix(const uint8_t *buf, size_t len, size_t cursor,
     793              :                                     size_t *payload_len) {
     794         1322 :     if (cursor >= len) return 0;
     795         1322 :     uint8_t first = buf[cursor];
     796         1322 :     if (first < 0x7F) {
     797         1194 :         *payload_len = (size_t)first * 4;
     798         1194 :         return 1;
     799              :     }
     800          128 :     if (cursor + 4 > len) return 0;
     801          128 :     size_t wire = (size_t)buf[cursor + 1]
     802          128 :                 | ((size_t)buf[cursor + 2] << 8)
     803          128 :                 | ((size_t)buf[cursor + 3] << 16);
     804          128 :     *payload_len = wire * 4;
     805          128 :     return 4;
     806              : }
     807              : 
     808         1832 : static void on_client_sent(const uint8_t *buf, size_t len) {
     809         1832 :     if (!g_srv.seeded) return;
     810              : 
     811         2932 :     while (g_srv.parse_cursor < len) {
     812              :         /* Step 0 — consume the initial 0xEF marker on the very first byte.
     813              :          * Also handle a one-shot reconnect: if reconnect_pending is set and
     814              :          * the next byte is 0xEF, treat it as a new-connection marker (reset
     815              :          * parse state) so that a second transport session opened by production
     816              :          * code (e.g. cross-DC NETWORK_MIGRATE retry) is parsed cleanly. */
     817         1844 :         if (!g_srv.saw_marker) {
     818          492 :             if (buf[g_srv.parse_cursor] != 0xEFu) {
     819              :                 /* Unexpected first byte — not an abridged connection. */
     820          666 :                 return;
     821              :             }
     822          492 :             g_srv.parse_cursor++;
     823          492 :             g_srv.saw_marker = 1;
     824          552 :             continue;
     825              :         }
     826         1352 :         if (g_srv.reconnect_pending && buf[g_srv.parse_cursor] == 0xEFu) {
     827              :             /* A second transport connection opened by the client. Reset the
     828              :              * parser to treat this 0xEF as the new connection's abridged
     829              :              * marker. */
     830           30 :             g_srv.reconnect_pending = 0;
     831           30 :             g_srv.saw_marker = 0;
     832           30 :             continue;   /* re-enter the saw_marker=0 branch above */
     833              :         }
     834              : 
     835         1322 :         size_t payload_len = 0;
     836         1322 :         size_t prefix_bytes = read_abridged_prefix(buf, len, g_srv.parse_cursor,
     837              :                                                     &payload_len);
     838         1322 :         if (prefix_bytes == 0) return;  /* need more data */
     839         1322 :         if (payload_len == 0) {
     840              :             /* Idle keep-alive — skip. */
     841            0 :             g_srv.parse_cursor += prefix_bytes;
     842            0 :             continue;
     843              :         }
     844         1322 :         if (g_srv.parse_cursor + prefix_bytes + payload_len > len) {
     845          666 :             return;  /* wait for rest of payload */
     846              :         }
     847              : 
     848          656 :         const uint8_t *frame = buf + g_srv.parse_cursor + prefix_bytes;
     849          656 :         g_srv.parse_cursor += prefix_bytes + payload_len;
     850              : 
     851              :         /* Frame = auth_key_id(8) + msg_key(16) + ciphertext */
     852          656 :         if (payload_len < 24) continue;
     853          656 :         uint64_t key_id = 0;
     854         5904 :         for (int i = 0; i < 8; ++i) key_id |= ((uint64_t)frame[i]) << (i * 8);
     855              : 
     856              :         /* TEST-71: in cold-boot handshake mode the server's auth_key_id is
     857              :          * 0 (no session yet) and the client's frame also carries
     858              :          * auth_key_id = 0, so the equality check below would incorrectly
     859              :          * route the frame into the encrypted branch. Short-circuit here
     860              :          * and dispatch to the synthetic handshake responders instead. */
     861          656 :         if (g_srv.handshake_mode && key_id == 0 && payload_len >= 24) {
     862           28 :             uint32_t raw_crc = (uint32_t)frame[20]
     863           28 :                              | ((uint32_t)frame[21] << 8)
     864           28 :                              | ((uint32_t)frame[22] << 16)
     865           28 :                              | ((uint32_t)frame[23] << 24);
     866           28 :             size_t slot = g_srv.crc_ring_count % MT_CRC_RING_SIZE;
     867           28 :             g_srv.crc_ring[slot] = raw_crc;
     868           28 :             g_srv.crc_ring_count++;
     869              : 
     870           28 :             const uint8_t *tl_body = frame + 20;
     871           28 :             size_t tl_body_len = payload_len - 20;
     872           28 :             if (raw_crc == CRC_req_pq_multi) {
     873           18 :                 g_srv.handshake_req_pq_count++;
     874           18 :                 handshake_on_req_pq_multi(tl_body, tl_body_len);
     875           10 :             } else if (raw_crc == CRC_req_DH_params
     876            8 :                        && g_srv.handshake_mode >= 2) {
     877            8 :                 g_srv.handshake_req_dh_count++;
     878            8 :                 handshake_on_req_dh_params(tl_body, tl_body_len);
     879            2 :             } else if (raw_crc == CRC_req_DH_params) {
     880              :                 /* Count it but do not reply — tests can observe the
     881              :                  * counter to prove the client reached step 2. */
     882            0 :                 g_srv.handshake_req_dh_count++;
     883            2 :             } else if (raw_crc == CRC_set_client_DH_params
     884            2 :                        && g_srv.handshake_mode == 3) {
     885              :                 /* TEST-72: full DH — compute auth_key from client's g_b,
     886              :                  * verify key_hash, send dh_gen_ok, persist session. */
     887            2 :                 g_srv.handshake_set_client_dh_count++;
     888            2 :                 handshake_on_set_client_dh(tl_body, tl_body_len);
     889              :             }
     890           28 :             continue;
     891              :         }
     892              : 
     893          628 :         if (key_id != g_srv.auth_key_id) {
     894              :             /* Unencrypted handshake frame: auth_key_id == 0.
     895              :              * Record the leading CRC for mt_server_request_crc_count().
     896              :              * Unencrypted layout: key_id(8) + msg_id(8) + msg_len(4) + body */
     897            2 :             if (key_id == 0 && payload_len >= 24) {
     898            2 :                 uint32_t raw_crc = (uint32_t)frame[20]
     899            2 :                                  | ((uint32_t)frame[21] << 8)
     900            2 :                                  | ((uint32_t)frame[22] << 16)
     901            2 :                                  | ((uint32_t)frame[23] << 24);
     902            2 :                 size_t slot = g_srv.crc_ring_count % MT_CRC_RING_SIZE;
     903            2 :                 g_srv.crc_ring[slot] = raw_crc;
     904            2 :                 g_srv.crc_ring_count++;
     905              :             } else {
     906            0 :                 fprintf(stderr, "mt_server: auth_key_id mismatch "
     907              :                         "(got %016llx, want %016llx)\n",
     908              :                         (unsigned long long)key_id,
     909            0 :                         (unsigned long long)g_srv.auth_key_id);
     910              :             }
     911            2 :             continue;
     912              :         }
     913              : 
     914              :         uint8_t msg_key[16];
     915          626 :         memcpy(msg_key, frame + 8, 16);
     916          626 :         const uint8_t *cipher = frame + 24;
     917          626 :         size_t cipher_len = payload_len - 24;
     918              : 
     919          626 :         uint8_t *plain = (uint8_t *)malloc(cipher_len);
     920          626 :         if (!plain) continue;
     921          626 :         size_t plain_len = 0;
     922              : 
     923          626 :         int rc = mtproto_decrypt(cipher, cipher_len,
     924              :                                  g_srv.auth_key, msg_key, 0,
     925              :                                  plain, &plain_len);
     926          626 :         if (rc != 0) {
     927            0 :             fprintf(stderr, "mt_server: mtproto_decrypt failed\n");
     928            0 :             free(plain);
     929            0 :             continue;
     930              :         }
     931          626 :         dispatch_frame(plain, plain_len);
     932          626 :         free(plain);
     933              :     }
     934              : }
     935              : 
     936          626 : static void dispatch_frame(const uint8_t *plain, size_t plain_len) {
     937              :     /* plaintext header: salt(8) + session_id(8) + msg_id(8) + seq_no(4) + len(4) + body */
     938          626 :     if (plain_len < 32) return;
     939          626 :     uint64_t client_session = 0;
     940         5634 :     for (int i = 0; i < 8; ++i) {
     941         5008 :         client_session |= ((uint64_t)plain[8 + i]) << (i * 8);
     942              :     }
     943          626 :     uint64_t msg_id = 0;
     944         5634 :     for (int i = 0; i < 8; ++i) {
     945         5008 :         msg_id |= ((uint64_t)plain[16 + i]) << (i * 8);
     946              :     }
     947          626 :     uint32_t body_len = 0;
     948         3130 :     for (int i = 0; i < 4; ++i) {
     949         2504 :         body_len |= ((uint32_t)plain[28 + i]) << (i * 8);
     950              :     }
     951          626 :     if (32 + body_len > plain_len) return;
     952          626 :     const uint8_t *body = plain + 32;
     953              : 
     954              :     /* First frame carries the client session id — pin it so later msg_container
     955              :      * updates use the same one. If the client somehow changes it, that's a
     956              :      * protocol error we mirror by just echoing back whatever we last saw. */
     957          626 :     if (g_srv.session_id == 0x1122334455667788ULL) {
     958              :         /* keep the seeded value — the client echoes this from session.bin */
     959              :     }
     960              :     (void)client_session;
     961              : 
     962          626 :     unwrap_and_dispatch(msg_id, body, body_len);
     963              : }
     964              : 
     965          626 : static void unwrap_and_dispatch(uint64_t req_msg_id,
     966              :                                 const uint8_t *body, size_t body_len) {
     967         1250 :     if (body_len < 4) return;
     968          626 :     const uint8_t *cur = body;
     969          626 :     size_t remaining = body_len;
     970              : 
     971              :     /* Peel invokeWithLayer / initConnection off the front. */
     972         1842 :     for (int depth = 0; depth < 3; ++depth) {
     973         1842 :         if (remaining < 4) return;
     974         1842 :         uint32_t crc = (uint32_t)cur[0] | ((uint32_t)cur[1] << 8)
     975         1842 :                      | ((uint32_t)cur[2] << 16) | ((uint32_t)cur[3] << 24);
     976         1842 :         if (crc == CRC_invokeWithLayer) {
     977          608 :             if (remaining < 8) return;
     978          608 :             cur += 8; remaining -= 8;   /* CRC + layer:int */
     979          608 :             continue;
     980              :         }
     981         1234 :         if (crc == CRC_initConnection) {
     982              :             /* CRC(4) flags(4) api_id(4) string×6 [proxy:flags.0?] [params:flags.1?] query:!X */
     983          608 :             TlReader r = tl_reader_init(cur, remaining);
     984          608 :             tl_read_uint32(&r);                 /* CRC */
     985          608 :             uint32_t flags = tl_read_uint32(&r);
     986          608 :             tl_read_int32(&r);                  /* api_id */
     987         4256 :             for (int i = 0; i < 6; ++i) {
     988         3648 :                 if (tl_skip_string(&r) != 0) return;
     989              :             }
     990          608 :             if (flags & 0x1) {
     991              :                 /* inputClientProxy#75588b3f server:string port:int */
     992            0 :                 tl_read_uint32(&r);
     993            0 :                 if (tl_skip_string(&r) != 0) return;
     994            0 :                 tl_read_int32(&r);
     995              :             }
     996          608 :             if (flags & 0x2) {
     997              :                 /* jsonObject#7d748d04 — walking JSONValue is out of scope;
     998              :                  * the client never sets this flag so bail if we ever see it. */
     999            0 :                 return;
    1000              :             }
    1001          608 :             size_t consumed = r.pos;
    1002          608 :             cur += consumed; remaining -= consumed;
    1003          608 :             continue;
    1004              :         }
    1005          626 :         break;
    1006              :     }
    1007              : 
    1008          626 :     if (remaining < 4) return;
    1009          626 :     uint32_t inner_crc = (uint32_t)cur[0] | ((uint32_t)cur[1] << 8)
    1010          626 :                        | ((uint32_t)cur[2] << 16) | ((uint32_t)cur[3] << 24);
    1011              : 
    1012              :     /* One-shot bad_server_salt injection — bounces the client back with a
    1013              :      * fresh salt and forces it to resend. The handler is not called on this
    1014              :      * round; it fires on the retry. */
    1015          626 :     if (g_srv.bad_salt_once_pending) {
    1016            4 :         g_srv.bad_salt_once_pending = 0;
    1017              :         /* bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int
    1018              :          *                          error_code:int new_server_salt:long */
    1019              :         uint8_t buf[4 + 8 + 4 + 4 + 8];
    1020            4 :         uint32_t crc = 0xedab447bU;
    1021           20 :         for (int i = 0; i < 4; ++i) buf[i] = (uint8_t)(crc >> (i * 8));
    1022           36 :         for (int i = 0; i < 8; ++i) buf[4 + i] = (uint8_t)(req_msg_id >> (i * 8));
    1023              :         /* bad_msg_seqno + error_code 48 (= incorrect server salt) — values
    1024              :          * are informational only for the client's logger. */
    1025            4 :         int32_t seq0 = 0;
    1026           20 :         for (int i = 0; i < 4; ++i) buf[12 + i] = (uint8_t)(seq0 >> (i * 8));
    1027            4 :         int32_t ec = 48;
    1028           20 :         for (int i = 0; i < 4; ++i) buf[16 + i] = (uint8_t)(ec >> (i * 8));
    1029           36 :         for (int i = 0; i < 8; ++i) {
    1030           32 :             buf[20 + i] = (uint8_t)(g_srv.bad_salt_new_salt >> (i * 8));
    1031              :         }
    1032            4 :         queue_frame(buf, sizeof(buf));
    1033              :         /* Update server salt so subsequent responses use what the client now
    1034              :          * expects — the client discards the inbound salt, so this is purely
    1035              :          * cosmetic (a real server would). */
    1036            4 :         g_srv.server_salt = g_srv.bad_salt_new_salt;
    1037            4 :         return;
    1038              :     }
    1039              : 
    1040              :     /* Record the inner CRC in the ring buffer. */
    1041              :     {
    1042          622 :         size_t slot = g_srv.crc_ring_count % MT_CRC_RING_SIZE;
    1043          622 :         g_srv.crc_ring[slot] = inner_crc;
    1044          622 :         g_srv.crc_ring_count++;
    1045              :     }
    1046              : 
    1047              :     /* Drain any service frames queued by mt_server_reply_* helpers
    1048              :      * (TEST-88). They land on the wire ahead of the real result so the
    1049              :      * client's classify_service_frame loop observes them exactly once per
    1050              :      * iteration. queue_frame wraps each body in its own encrypted envelope,
    1051              :      * which the client treats as an independent frame. */
    1052          762 :     for (size_t i = 0; i < g_srv.pending_service_count; ++i) {
    1053          140 :         queue_frame(g_srv.pending_service_frames[i].bytes,
    1054              :                     g_srv.pending_service_frames[i].len);
    1055          140 :         free(g_srv.pending_service_frames[i].bytes);
    1056          140 :         g_srv.pending_service_frames[i].bytes = NULL;
    1057          140 :         g_srv.pending_service_frames[i].len   = 0;
    1058              :     }
    1059          622 :     g_srv.pending_service_count = 0;
    1060              : 
    1061              :     /* Record + invoke handler. */
    1062          622 :     g_srv.rpc_call_count++;
    1063          622 :     g_srv.current_req_msg_id = req_msg_id;
    1064              : 
    1065          802 :     for (size_t i = 0; i < MT_MAX_HANDLERS; ++i) {
    1066          800 :         if (g_srv.handlers[i].used && g_srv.handlers[i].crc == inner_crc) {
    1067          620 :             MtRpcContext ctx = {
    1068              :                 .req_msg_id   = req_msg_id,
    1069              :                 .req_crc      = inner_crc,
    1070              :                 .req_body     = cur,
    1071              :                 .req_body_len = remaining,
    1072          620 :                 .user_ctx     = g_srv.handlers[i].ctx,
    1073              :             };
    1074          620 :             g_srv.handlers[i].fn(&ctx);
    1075          620 :             return;
    1076              :         }
    1077              :     }
    1078              : 
    1079              :     /* No handler → auto rpc_error so the test fails explicitly rather than
    1080              :      * hanging on recv. */
    1081            2 :     MtRpcContext ctx = {
    1082              :         .req_msg_id = req_msg_id,
    1083              :         .req_crc    = inner_crc,
    1084              :         .req_body   = cur,
    1085              :         .req_body_len = remaining,
    1086              :         .user_ctx   = NULL,
    1087              :     };
    1088              :     char buf[64];
    1089            2 :     snprintf(buf, sizeof(buf), "NO_HANDLER_CRC_%08x", inner_crc);
    1090            2 :     mt_server_reply_error(&ctx, 500, buf);
    1091              : }
    1092              : 
    1093          766 : static void queue_frame(const uint8_t *body, size_t body_len) {
    1094              :     /* Build plaintext header: salt + session_id + msg_id + seq_no + len + body + padding. */
    1095              :     TlWriter plain;
    1096          766 :     tl_writer_init(&plain);
    1097          766 :     tl_write_uint64(&plain, g_srv.server_salt);
    1098          766 :     uint64_t effective_session_id = g_srv.session_id;
    1099          766 :     if (g_srv.wrong_session_id_once_pending) {
    1100            2 :         g_srv.wrong_session_id_once_pending = 0;
    1101            2 :         effective_session_id ^= 0xFFFFFFFFFFFFFFFFULL;  /* flip all bits */
    1102              :     }
    1103          766 :     tl_write_uint64(&plain, effective_session_id);
    1104          766 :     tl_write_uint64(&plain, make_server_msg_id());
    1105              :     /* seq_no: bump by 2 for content-related, start at 1 so first is 1, 3, 5… */
    1106          766 :     g_srv.seq_no += 2;
    1107          766 :     tl_write_uint32(&plain, g_srv.seq_no - 1);
    1108          766 :     tl_write_uint32(&plain, (uint32_t)body_len);
    1109          766 :     tl_write_raw(&plain, body, body_len);
    1110              : 
    1111          766 :     encrypt_and_queue(plain.data, plain.len);
    1112          766 :     tl_writer_free(&plain);
    1113          766 : }
    1114              : 
    1115              : /* ---- TEST-71 / US-20 handshake unencrypted-frame handlers ---- */
    1116              : 
    1117              : /** Queue an unencrypted (auth_key_id = 0) server → client frame with the
    1118              :  *  handshake response TL. Mirrors rpc_send_unencrypted on the client side
    1119              :  *  so the client's rpc_recv_unencrypted parser picks it up. */
    1120           28 : static void handshake_queue_unenc(const uint8_t *tl, size_t tl_len) {
    1121              :     /* Wire: auth_key_id(8) + msg_id(8) + len(4) + body */
    1122           28 :     TlWriter w; tl_writer_init(&w);
    1123           28 :     tl_write_uint64(&w, 0);                       /* auth_key_id */
    1124           28 :     tl_write_uint64(&w, make_server_msg_id());    /* server msg_id */
    1125           28 :     tl_write_uint32(&w, (uint32_t)tl_len);
    1126           28 :     tl_write_raw(&w, tl, tl_len);
    1127              : 
    1128           28 :     size_t units = w.len / 4;
    1129           28 :     if (units < 0x7F) {
    1130           28 :         uint8_t p = (uint8_t)units;
    1131           28 :         mock_socket_append_response(&p, 1);
    1132              :     } else {
    1133              :         uint8_t p[4];
    1134            0 :         p[0] = 0x7F;
    1135            0 :         p[1] = (uint8_t)(units & 0xFFu);
    1136            0 :         p[2] = (uint8_t)((units >> 8) & 0xFFu);
    1137            0 :         p[3] = (uint8_t)((units >> 16) & 0xFFu);
    1138            0 :         mock_socket_append_response(p, 4);
    1139              :     }
    1140           28 :     mock_socket_append_response(w.data, w.len);
    1141           28 :     tl_writer_free(&w);
    1142           28 : }
    1143              : 
    1144              : /** Handle an incoming req_pq_multi frame, emit a resPQ per the current
    1145              :  *  cold-boot variant. Assumes @p body points at the CRC-prefixed TL body
    1146              :  *  (CRC already consumed by the caller would break layout — we take the
    1147              :  *  whole 4+16 body here). */
    1148           18 : static void handshake_on_req_pq_multi(const uint8_t *body, size_t body_len) {
    1149           18 :     if (body_len < 4 + 16) return;
    1150              :     /* body = CRC(4) nonce(16) */
    1151              :     uint8_t client_nonce[16];
    1152           18 :     memcpy(client_nonce, body + 4, 16);
    1153              : 
    1154              :     /* Echo (possibly tampered) nonce back. */
    1155              :     uint8_t echo_nonce[16];
    1156           18 :     memcpy(echo_nonce, client_nonce, 16);
    1157           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_NONCE_TAMPER) {
    1158           34 :         for (int i = 0; i < 16; ++i) echo_nonce[i] ^= 0xFF;
    1159              :     }
    1160              : 
    1161              :     /* Deterministic server_nonce for reproducibility. */
    1162              :     uint8_t server_nonce[16];
    1163           18 :     memset(server_nonce, 0xBB, 16);
    1164              : 
    1165              :     /* PQ: default 21 (= 3 * 7 — tiny so Pollard's rho finishes instantly).
    1166              :      * MT_COLD_BOOT_BAD_PQ uses a 64-bit prime so pq_factorize fails. */
    1167           18 :     uint64_t pq_val = 21ULL;
    1168           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_BAD_PQ) {
    1169              :         /* 2^64 - 59 is a prime. Small enough to survive Pollard's rho
    1170              :          * 20-c sweep without factoring. */
    1171            2 :         pq_val = 0xFFFFFFFFFFFFFFC5ULL;
    1172              :     }
    1173              :     /* Encode pq as big-endian minimum-length bytes. */
    1174              :     uint8_t pq_be[8];
    1175           18 :     size_t pq_be_len = 0;
    1176          162 :     for (int i = 7; i >= 0; --i) {
    1177          144 :         uint8_t byte = (uint8_t)((pq_val >> (i * 8)) & 0xFFu);
    1178          144 :         if (pq_be_len > 0 || byte != 0 || i == 0) {
    1179           32 :             pq_be[pq_be_len++] = byte;
    1180              :         }
    1181              :     }
    1182              : 
    1183           18 :     uint64_t fingerprint = COLD_BOOT_FP_OK;
    1184           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_BAD_FINGERPRINT) {
    1185            2 :         fingerprint = COLD_BOOT_FP_BAD;
    1186              :     }
    1187              : 
    1188           18 :     uint32_t constructor = CRC_resPQ;
    1189           18 :     if (g_srv.handshake_cold_boot_variant == MT_COLD_BOOT_WRONG_CONSTRUCTOR) {
    1190            2 :         constructor = 0xDEADBEEFU;
    1191              :     }
    1192              : 
    1193           18 :     TlWriter tl; tl_writer_init(&tl);
    1194           18 :     tl_write_uint32(&tl, constructor);
    1195           18 :     tl_write_int128(&tl, echo_nonce);
    1196           18 :     tl_write_int128(&tl, server_nonce);
    1197           18 :     tl_write_bytes(&tl, pq_be, pq_be_len);
    1198           18 :     tl_write_uint32(&tl, CRC_vector);
    1199           18 :     tl_write_uint32(&tl, 1);                    /* one fingerprint entry */
    1200           18 :     tl_write_uint64(&tl, fingerprint);
    1201           18 :     handshake_queue_unenc(tl.data, tl.len);
    1202           18 :     tl_writer_free(&tl);
    1203              : }
    1204              : 
    1205              : /* ---- TEST-72: RSA-PAD decrypt (inverse of rsa_pad_encrypt in mtproto_auth.c) ----
    1206              :  *
    1207              :  * Reverses the MTProto 1.0 SHA1 RSA scheme: RSA_NO_PADDING decrypt → 256 bytes,
    1208              :  * skip leading zero byte, strip 20-byte SHA1 hash prefix → plain data.
    1209              :  * Returns 0 and writes data into @p plain_out (max @p plain_max bytes) and
    1210              :  * sets *plain_len to the actual data length.
    1211              :  *
    1212              :  * TEST_ONLY — only called from handshake_on_req_dh_params in mode 3. */
    1213            2 : static int rsa_sha1_decrypt(const uint8_t *ciphertext, size_t cipher_len,
    1214              :                              uint8_t *plain_out, size_t plain_max,
    1215              :                              size_t *plain_len) {
    1216            2 :     if (cipher_len != 256) return -1;
    1217              : 
    1218              :     /* TEST_ONLY private key — matches tests/mocks/telegram_server_key.c pubkey. */
    1219              :     static const char TEST_PRIVATE_KEY_PEM[] =
    1220              :         "-----BEGIN PRIVATE KEY-----\n"
    1221              :         "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbG/j8RdvTAAWv\n"
    1222              :         "870ayFCbJI73fEEB43/l/NnocYeAh9L/Zcv9HwYwFOXms9o2ccvp+e/4GE55vUzY\n"
    1223              :         "8XrM1h6dtClGYNvRNpvcthfmVrpGGIjKH2b3snip4ajtUOcZIyTynZo1vMG5uqCx\n"
    1224              :         "YaXWyhBwMPJQ87Gw5WbcaKNJWg3jZ1GI0g+tJUAqXpfPwF3KjKzIZwa/rbnJ9ick\n"
    1225              :         "OX12OaG1c2enW1+sv1K1qz6CcxmbCyRuD+xUKVTLHc5ykmcYDJ12CLES8DdfbPOt\n"
    1226              :         "UGAP082CQOptfpPm5fvnU6c53suIItnddjiT74+hyQBXQpvWXKIZzFEdgxJ2QUzp\n"
    1227              :         "TlRwqVijAgMBAAECggEAJll6rIjrKlaRkWjOgw4u28Tksize98QTTb4/9DgJnA44\n"
    1228              :         "7VtyXYFrqryoAOvL0nU9SPq6SZlc4b2bf/HofjecdzphkByHjMkXLTE6ZIFh6c3M\n"
    1229              :         "GEk+UJSoP7xi41YC5VSqoG+1/n5OWYjajTDK63mnKc34Q2qVLtrxHSKj6JFi6Kuy\n"
    1230              :         "uO1+lXUiGS5vvlPllAoDTIFn4e4PHChHeVNiwzBe8HNgVWc9JasL7IiS37gsfuZC\n"
    1231              :         "l4QlW3IkhCMkb5fj7xV/xcwQ4SuYWcNkrfuX7Fu3g4Hpeir3xJlJMxh2iqJnARvM\n"
    1232              :         "nLy9gY04ejqdxI9+VopQyXafe0tP/veuqO7RzR3x+QKBgQDQvd9drWmvWM9g7Lrj\n"
    1233              :         "jEr4oo00b5/cOQd12VJaxhWUdfzyDxsygGPeuP3/7QxgwxB+VmnsS2xfNvv4hDdd\n"
    1234              :         "vL6DhFPicTV5Xv25U52QaxNefc5XSSzY68XMFtUyWjzD+35GIUO27TnDfQhFBkbS\n"
    1235              :         "RFDg3uJ1KbcI05nM+DSQJZPaqQKBgQC+ObQxrgZBPd6K6vVM/uaoA45Htc8UVIS/\n"
    1236              :         "+eU69k1lVp6BZm1ebaTOf6CTOO/AcyM4jZu7lVFwfanVMVSfJ+Nbd1BEXtg1BKou\n"
    1237              :         "2cLlYkJX6mVmCs39sQDscvoO67RMhR4jshCE0nxaAxFatdEfpq9ZTrQIXWKnQhRq\n"
    1238              :         "nHsyIyHUawKBgQCwTf5vx70AvfkB+1BqSp8z2096X2FdBsn3TqORSccGSpVm+T1W\n"
    1239              :         "bTxs7ECUPWn7/CVdH619R8LztKQjJcEBqh4bRNP46PdqWMHiGu51AQst/wIdlQ+M\n"
    1240              :         "865vj0VorvCt8yeXIhdoVHs6Ust+SSveApdxJq+Ml7whd19q0KTMrwBvaQKBgC8C\n"
    1241              :         "i6mLXDhbVdf24NA6Xj4/QrYuFBLuIDBhTWkY3V+h3GIWMgkYB5aQq9o2Q+nHini7\n"
    1242              :         "ZjUhXZLzOzlYi5UZgnJkNg3vcncHxBb38dZGRib74jspiGadi6DjeTCex1vxudUQ\n"
    1243              :         "eEyax+hmwa8tJ5Uu2D612IAItAypo+oE6d0mGYIpAoGAS2P6e2iOW2HG3CA2kiLp\n"
    1244              :         "xw/guEi8BTTljxMSUEqS5YhDI2uVe2QEYlvgDG7zYMl2UzoadZcK1VVa01WM2LDC\n"
    1245              :         "wThzA+NhfHS4ukPuuOkJUVwGAJGUG/KcIypTTYz4RPj6BpALLXstF+Qf8F3aqDvL\n"
    1246              :         "bAd3PHlfEWOhiWzBj/dVjUY=\n"
    1247              :         "-----END PRIVATE KEY-----\n";
    1248              : 
    1249              :     (void)plain_max;
    1250              : 
    1251            2 :     CryptoRsaKey *priv = crypto_rsa_load_private(TEST_PRIVATE_KEY_PEM);
    1252            2 :     if (!priv) {
    1253            0 :         fprintf(stderr, "mock: rsa_sha1_decrypt: failed to load private key\n");
    1254            0 :         return -1;
    1255              :     }
    1256              : 
    1257              :     /* RSA_NO_PADDING decrypt → 256 bytes */
    1258              :     uint8_t rsa_output[256];
    1259            2 :     size_t rsa_out_len = sizeof(rsa_output);
    1260            2 :     if (crypto_rsa_private_decrypt(priv, ciphertext, cipher_len,
    1261              :                                    rsa_output, &rsa_out_len) != 0) {
    1262            0 :         crypto_rsa_free(priv);
    1263            0 :         fprintf(stderr, "mock: rsa_sha1_decrypt: RSA decrypt failed\n");
    1264            0 :         return -1;
    1265              :     }
    1266            2 :     crypto_rsa_free(priv);
    1267              : 
    1268              :     /* rsa_output[0] = 0x00 (zero prefix added by client)
    1269              :      * rsa_output[1..20]  = SHA1(data)
    1270              :      * rsa_output[21..255] = data + random padding */
    1271            2 :     const uint8_t *sha1_prefix = rsa_output + 1;   /* 20 bytes */
    1272            2 :     const uint8_t *data_start  = rsa_output + 21;  /* up to 235 bytes */
    1273            2 :     size_t max_data = 255 - 20;
    1274              : 
    1275              :     /* The data starts with a 4-byte TL CRC.  Find the actual data length by
    1276              :      * verifying SHA1.  We try lengths starting at 4 (minimum TL object). */
    1277          154 :     for (size_t dlen = 4; dlen <= max_data; dlen++) {
    1278              :         uint8_t sha1_check[20];
    1279          154 :         crypto_sha1(data_start, dlen, sha1_check);
    1280          154 :         if (memcmp(sha1_check, sha1_prefix, 20) == 0) {
    1281            2 :             memcpy(plain_out, data_start, dlen);
    1282            2 :             *plain_len = dlen;
    1283            2 :             return 0;
    1284              :         }
    1285              :     }
    1286            0 :     fprintf(stderr, "mock: rsa_sha1_decrypt: SHA1 verification failed\n");
    1287            0 :     return -1;
    1288              : }
    1289              : 
    1290              : /** Handle an incoming req_DH_params frame.
    1291              :  *
    1292              :  * Mode 2 (original): emits server_DH_params_ok with random encrypted_answer
    1293              :  * (garbage). The client's AES-IGE decrypt yields garbage, the inner CRC check
    1294              :  * fails, and auth_step_parse_dh returns -1 — the negative path.
    1295              :  *
    1296              :  * Mode 3 (TEST-72): RSA-PAD-decrypts the client's payload to extract new_nonce,
    1297              :  * generates a valid server_DH_inner_data (g=2, safe prime, g_a=g^b mod p),
    1298              :  * AES-IGE-encrypts it with the derived temp key, and sends server_DH_params_ok.
    1299              :  * Stores DH state for subsequent set_client_DH_params handling. */
    1300            8 : static void handshake_on_req_dh_params(const uint8_t *body, size_t body_len) {
    1301           14 :     if (body_len < 4 + 16 + 16) return;
    1302              :     /* body = CRC(4) nonce(16) server_nonce(16) p:bytes q:bytes fp:long encrypted_data:bytes */
    1303              :     uint8_t nonce[16], server_nonce[16];
    1304            8 :     memcpy(nonce,        body + 4,       16);
    1305            8 :     memcpy(server_nonce, body + 4 + 16,  16);
    1306              : 
    1307            8 :     if (g_srv.handshake_mode < 3) {
    1308              :         /* Original mode 2 path: random garbage encrypted_answer. */
    1309              :         uint8_t enc_answer[32];
    1310            6 :         crypto_rand_bytes(enc_answer, sizeof(enc_answer));
    1311            6 :         TlWriter tl; tl_writer_init(&tl);
    1312            6 :         tl_write_uint32(&tl, CRC_server_DH_params_ok);
    1313            6 :         tl_write_int128(&tl, nonce);
    1314            6 :         tl_write_int128(&tl, server_nonce);
    1315            6 :         tl_write_bytes(&tl, enc_answer, sizeof(enc_answer));
    1316            6 :         handshake_queue_unenc(tl.data, tl.len);
    1317            6 :         tl_writer_free(&tl);
    1318            6 :         return;
    1319              :     }
    1320              : 
    1321              :     /* ---- Mode 3: full DH handshake ---- */
    1322              : 
    1323              :     /* Parse the req_DH_params body to find the encrypted_data.
    1324              :      * Layout: CRC(4) nonce(16) server_nonce(16) p:bytes q:bytes fp:int64 encrypted_data:bytes */
    1325            2 :     TlReader r = tl_reader_init(body, body_len);
    1326            2 :     tl_read_uint32(&r);            /* CRC */
    1327            2 :     uint8_t _nonce[16]; tl_read_int128(&r, _nonce);
    1328            2 :     uint8_t _snonce[16]; tl_read_int128(&r, _snonce);
    1329            2 :     size_t p_len = 0, q_len = 0, enc_len = 0;
    1330            2 :     uint8_t *p_bytes = tl_read_bytes(&r, &p_len);
    1331            2 :     free(p_bytes);
    1332            2 :     uint8_t *q_bytes = tl_read_bytes(&r, &q_len);
    1333            2 :     free(q_bytes);
    1334            2 :     tl_read_uint64(&r);            /* fingerprint */
    1335            2 :     uint8_t *enc_data = tl_read_bytes(&r, &enc_len);
    1336            2 :     if (!enc_data || enc_len != 256) {
    1337            0 :         free(enc_data);
    1338            0 :         fprintf(stderr, "mock: full DH: unexpected enc_data size %zu\n", enc_len);
    1339            0 :         return;
    1340              :     }
    1341              : 
    1342              :     /* SHA1-based RSA decrypt to get p_q_inner_data (MTProto 1.0 legacy) */
    1343              :     uint8_t inner_plain[256];
    1344            2 :     size_t inner_plain_len = 0;
    1345            2 :     if (rsa_sha1_decrypt(enc_data, enc_len, inner_plain, sizeof(inner_plain),
    1346              :                          &inner_plain_len) != 0) {
    1347            0 :         free(enc_data);
    1348            0 :         fprintf(stderr, "mock: full DH: RSA SHA1 decrypt failed\n");
    1349            0 :         return;
    1350              :     }
    1351            2 :     free(enc_data);
    1352              : 
    1353              :     /* Parse p_q_inner_data (legacy, no dc field) or p_q_inner_data_dc.
    1354              :      * TL: CRC(4) pq:bytes p:bytes q:bytes nonce(16) server_nonce(16) new_nonce(32) [dc:int] */
    1355            2 :     TlReader ir = tl_reader_init(inner_plain, inner_plain_len);
    1356            2 :     uint32_t inner_crc = tl_read_uint32(&ir);
    1357            2 :     int has_dc = 0;
    1358            2 :     if (inner_crc == 0xa9f55f95U) {       /* p_q_inner_data_dc */
    1359            0 :         has_dc = 1;
    1360            2 :     } else if (inner_crc == 0x83c95aecU) { /* p_q_inner_data (legacy) */
    1361            2 :         has_dc = 0;
    1362              :     } else {
    1363            0 :         fprintf(stderr, "mock: full DH: unexpected inner CRC 0x%08x\n", inner_crc);
    1364            0 :         return;
    1365              :     }
    1366              :     /* Skip pq, p, q bytes */
    1367            2 :     size_t tmp_len = 0;
    1368            2 :     uint8_t *tmp = tl_read_bytes(&ir, &tmp_len); free(tmp);  /* pq */
    1369            2 :     tmp = tl_read_bytes(&ir, &tmp_len); free(tmp);             /* p */
    1370            2 :     tmp = tl_read_bytes(&ir, &tmp_len); free(tmp);             /* q */
    1371              :     /* Read nonce, server_nonce, new_nonce */
    1372              :     uint8_t extracted_nonce[16], extracted_snonce[16], new_nonce[32];
    1373            2 :     tl_read_int128(&ir, extracted_nonce);
    1374            2 :     tl_read_int128(&ir, extracted_snonce);
    1375            2 :     tl_read_int256(&ir, new_nonce);
    1376              :     (void)has_dc; /* dc_id field skipped if present — not used by mock */
    1377              : 
    1378              :     /* Save DH state for set_client_DH_params */
    1379            2 :     memcpy(g_srv.hs_new_nonce,    new_nonce,        32);
    1380            2 :     memcpy(g_srv.hs_nonce,        extracted_nonce,  16);
    1381            2 :     memcpy(g_srv.hs_server_nonce, extracted_snonce, 16);
    1382              : 
    1383              :     /* Use a fixed 256-bit safe prime for DH (TEST_ONLY).
    1384              :      * This prime was generated with openssl: BN_generate_prime_ex(p,256,1,...).
    1385              :      * g=2 is a generator for this group. */
    1386              :     static const uint8_t TEST_DH_PRIME[32] = {
    1387              :         0xfa, 0xd0, 0x8e, 0x08, 0xa4, 0x4d, 0x25, 0xaa,
    1388              :         0x45, 0x2b, 0xda, 0x58, 0x62, 0xac, 0xc4, 0xb2,
    1389              :         0x76, 0x23, 0xd3, 0x30, 0x4d, 0xd0, 0x9d, 0x64,
    1390              :         0xc1, 0xdd, 0xc0, 0xfb, 0x35, 0x09, 0x40, 0xdb
    1391              :     };
    1392            2 :     memcpy(g_srv.hs_dh_prime, TEST_DH_PRIME, 32);
    1393              : 
    1394              :     /* Generate server-side DH secret b (256 bytes) and compute g_b = 2^b mod p */
    1395            2 :     crypto_rand_bytes(g_srv.hs_b, 256);
    1396              : 
    1397            2 :     uint8_t g_be[1] = { 0x02 };  /* g = 2 as 1-byte big-endian */
    1398              :     uint8_t g_b[32];
    1399            2 :     size_t g_b_len = sizeof(g_b);
    1400            2 :     CryptoBnCtx *bn_ctx = crypto_bn_ctx_new();
    1401            2 :     if (!bn_ctx) { fprintf(stderr, "mock: full DH: BN ctx alloc failed\n"); return; }
    1402            2 :     if (crypto_bn_mod_exp(g_b, &g_b_len, g_be, 1,
    1403              :                           g_srv.hs_b, 256,
    1404              :                           TEST_DH_PRIME, 32, bn_ctx) != 0) {
    1405            0 :         crypto_bn_ctx_free(bn_ctx);
    1406            0 :         fprintf(stderr, "mock: full DH: g_b computation failed\n");
    1407            0 :         return;
    1408              :     }
    1409            2 :     crypto_bn_ctx_free(bn_ctx);
    1410              : 
    1411              :     /* Derive temp AES key/IV from new_nonce + server_nonce (same as production) */
    1412              :     uint8_t tmp_aes_key[32], tmp_aes_iv[32];
    1413              :     {
    1414              :         uint8_t buf[64];
    1415              :         uint8_t sha1_a[20], sha1_b[20], sha1_c[20];
    1416              : 
    1417            2 :         memcpy(buf, new_nonce, 32); memcpy(buf + 32, extracted_snonce, 16);
    1418            2 :         crypto_sha1(buf, 48, sha1_a);
    1419            2 :         memcpy(buf, extracted_snonce, 16); memcpy(buf + 16, new_nonce, 32);
    1420            2 :         crypto_sha1(buf, 48, sha1_b);
    1421            2 :         memcpy(buf, new_nonce, 32); memcpy(buf + 32, new_nonce, 32);
    1422            2 :         crypto_sha1(buf, 64, sha1_c);
    1423              : 
    1424            2 :         memcpy(tmp_aes_key,      sha1_a, 20);
    1425            2 :         memcpy(tmp_aes_key + 20, sha1_b, 12);
    1426            2 :         memcpy(tmp_aes_iv,       sha1_b + 12, 8);
    1427            2 :         memcpy(tmp_aes_iv + 8,   sha1_c, 20);
    1428            2 :         memcpy(tmp_aes_iv + 28,  new_nonce, 4);
    1429              :     }
    1430              : 
    1431              :     /* Build server_DH_inner_data TL */
    1432            2 :     TlWriter inner; tl_writer_init(&inner);
    1433            2 :     tl_write_uint32(&inner, CRC_server_DH_inner_data);
    1434            2 :     tl_write_int128(&inner, extracted_nonce);
    1435            2 :     tl_write_int128(&inner, extracted_snonce);
    1436            2 :     tl_write_int32(&inner, 2);                     /* g = 2 */
    1437            2 :     tl_write_bytes(&inner, TEST_DH_PRIME, 32);     /* dh_prime */
    1438            2 :     tl_write_bytes(&inner, g_b, g_b_len);          /* g_a (server's g^b) */
    1439            2 :     tl_write_int32(&inner, (int32_t)time(NULL));   /* server_time */
    1440              : 
    1441              :     /* Prepend SHA1 + pad to 16-byte boundary */
    1442              :     uint8_t sha1_inner[20];
    1443            2 :     crypto_sha1(inner.data, inner.len, sha1_inner);
    1444            2 :     size_t raw_len = 20 + inner.len;
    1445            2 :     size_t padded_len = (raw_len + 15) & ~(size_t)15;
    1446            2 :     uint8_t *padded = (uint8_t *)calloc(1, padded_len);
    1447            2 :     if (!padded) { tl_writer_free(&inner); return; }
    1448            2 :     memcpy(padded, sha1_inner, 20);
    1449            2 :     memcpy(padded + 20, inner.data, inner.len);
    1450            2 :     if (padded_len > raw_len) {
    1451            2 :         crypto_rand_bytes(padded + raw_len, padded_len - raw_len);
    1452              :     }
    1453            2 :     tl_writer_free(&inner);
    1454              : 
    1455              :     /* AES-IGE encrypt */
    1456            2 :     uint8_t *enc_answer = (uint8_t *)malloc(padded_len);
    1457            2 :     if (!enc_answer) { free(padded); return; }
    1458            2 :     aes_ige_encrypt(padded, padded_len, tmp_aes_key, tmp_aes_iv, enc_answer);
    1459            2 :     free(padded);
    1460              : 
    1461              :     /* Send server_DH_params_ok */
    1462            2 :     TlWriter tl; tl_writer_init(&tl);
    1463            2 :     tl_write_uint32(&tl, CRC_server_DH_params_ok);
    1464            2 :     tl_write_int128(&tl, extracted_nonce);
    1465            2 :     tl_write_int128(&tl, extracted_snonce);
    1466            2 :     tl_write_bytes(&tl, enc_answer, padded_len);
    1467            2 :     handshake_queue_unenc(tl.data, tl.len);
    1468            2 :     tl_writer_free(&tl);
    1469            2 :     free(enc_answer);
    1470              : }
    1471              : 
    1472              : /** TEST-72: Handle set_client_DH_params, compute auth_key = g_b^a mod p,
    1473              :  *  verify key_hash, send dh_gen_ok, and persist the session. */
    1474            2 : static void handshake_on_set_client_dh(const uint8_t *body, size_t body_len) {
    1475            2 :     if (body_len < 4 + 16 + 16) return;
    1476              : 
    1477              :     /* Derive temp AES key/IV from stored new_nonce + server_nonce */
    1478              :     uint8_t tmp_aes_key[32], tmp_aes_iv[32];
    1479              :     {
    1480              :         uint8_t buf[64];
    1481              :         uint8_t sha1_a[20], sha1_b[20], sha1_c[20];
    1482              : 
    1483            2 :         memcpy(buf, g_srv.hs_new_nonce, 32);
    1484            2 :         memcpy(buf + 32, g_srv.hs_server_nonce, 16);
    1485            2 :         crypto_sha1(buf, 48, sha1_a);
    1486              : 
    1487            2 :         memcpy(buf, g_srv.hs_server_nonce, 16);
    1488            2 :         memcpy(buf + 16, g_srv.hs_new_nonce, 32);
    1489            2 :         crypto_sha1(buf, 48, sha1_b);
    1490              : 
    1491            2 :         memcpy(buf, g_srv.hs_new_nonce, 32);
    1492            2 :         memcpy(buf + 32, g_srv.hs_new_nonce, 32);
    1493            2 :         crypto_sha1(buf, 64, sha1_c);
    1494              : 
    1495            2 :         memcpy(tmp_aes_key,      sha1_a, 20);
    1496            2 :         memcpy(tmp_aes_key + 20, sha1_b, 12);
    1497            2 :         memcpy(tmp_aes_iv,       sha1_b + 12, 8);
    1498            2 :         memcpy(tmp_aes_iv + 8,   sha1_c, 20);
    1499            2 :         memcpy(tmp_aes_iv + 28,  g_srv.hs_new_nonce, 4);
    1500              :     }
    1501              : 
    1502              :     /* Parse set_client_DH_params body:
    1503              :      * CRC(4) nonce(16) server_nonce(16) encrypted_data:bytes */
    1504            2 :     TlReader r = tl_reader_init(body, body_len);
    1505            2 :     tl_read_uint32(&r);              /* CRC */
    1506            2 :     uint8_t _nonce[16]; tl_read_int128(&r, _nonce);
    1507            2 :     uint8_t _snonce[16]; tl_read_int128(&r, _snonce);
    1508            2 :     size_t enc_len = 0;
    1509            2 :     uint8_t *enc_data = tl_read_bytes(&r, &enc_len);
    1510            2 :     if (!enc_data || enc_len == 0 || enc_len % 16 != 0) {
    1511            0 :         free(enc_data);
    1512            0 :         fprintf(stderr, "mock: set_client_DH: bad encrypted_data len %zu\n", enc_len);
    1513            0 :         return;
    1514              :     }
    1515              : 
    1516              :     /* AES-IGE decrypt encrypted_data */
    1517            2 :     uint8_t *plain = (uint8_t *)malloc(enc_len);
    1518            2 :     if (!plain) { free(enc_data); return; }
    1519            2 :     aes_ige_decrypt(enc_data, enc_len, tmp_aes_key, tmp_aes_iv, plain);
    1520            2 :     free(enc_data);
    1521              : 
    1522              :     /* Parse client_DH_inner_data: SHA1(20) + CRC(4) nonce(16) server_nonce(16)
    1523              :      *                               retry_id:int64 g_b:bytes */
    1524            2 :     if (enc_len < 20 + 4 + 16 + 16 + 8) {
    1525            0 :         free(plain);
    1526            0 :         fprintf(stderr, "mock: set_client_DH: plaintext too short\n");
    1527            0 :         return;
    1528              :     }
    1529            2 :     TlReader ir = tl_reader_init(plain + 20, enc_len - 20);
    1530            2 :     uint32_t crc = tl_read_uint32(&ir);
    1531            2 :     if (crc != 0x6643b654U) {  /* CRC_client_DH_inner_data */
    1532            0 :         free(plain);
    1533            0 :         fprintf(stderr, "mock: set_client_DH: wrong CRC 0x%08x\n", crc);
    1534            0 :         return;
    1535              :     }
    1536            2 :     uint8_t cli_nonce[16]; tl_read_int128(&ir, cli_nonce);
    1537            2 :     uint8_t cli_snonce[16]; tl_read_int128(&ir, cli_snonce);
    1538            2 :     tl_read_uint64(&ir);  /* retry_id */
    1539            2 :     size_t g_b_len = 0;
    1540            2 :     uint8_t *g_b = tl_read_bytes(&ir, &g_b_len);  /* client's g^client_b mod p */
    1541            2 :     if (!g_b || g_b_len == 0) {
    1542            0 :         free(g_b); free(plain);
    1543            0 :         fprintf(stderr, "mock: set_client_DH: failed to read g_b\n");
    1544            0 :         return;
    1545              :     }
    1546            2 :     free(plain);
    1547              : 
    1548              :     /* Save g_a (client's g^client_b mod p) for auth_key computation */
    1549            2 :     if (g_b_len > sizeof(g_srv.hs_g_a)) {
    1550            0 :         free(g_b);
    1551            0 :         fprintf(stderr, "mock: set_client_DH: g_b too large (%zu)\n", g_b_len);
    1552            0 :         return;
    1553              :     }
    1554            2 :     memcpy(g_srv.hs_g_a, g_b, g_b_len);
    1555            2 :     g_srv.hs_g_a_len = g_b_len;
    1556            2 :     free(g_b);
    1557              : 
    1558              :     /* Compute auth_key = g_b^server_b mod dh_prime */
    1559              :     uint8_t auth_key[256];
    1560            2 :     size_t ak_len = sizeof(auth_key);
    1561            2 :     CryptoBnCtx *bn_ctx = crypto_bn_ctx_new();
    1562            2 :     if (!bn_ctx) { fprintf(stderr, "mock: set_client_DH: BN ctx alloc\n"); return; }
    1563            2 :     if (crypto_bn_mod_exp(auth_key, &ak_len,
    1564              :                           g_srv.hs_g_a, g_srv.hs_g_a_len,
    1565              :                           g_srv.hs_b, 256,
    1566              :                           g_srv.hs_dh_prime, 32, bn_ctx) != 0) {
    1567            0 :         crypto_bn_ctx_free(bn_ctx);
    1568            0 :         fprintf(stderr, "mock: set_client_DH: auth_key exp failed\n");
    1569            0 :         return;
    1570              :     }
    1571            2 :     crypto_bn_ctx_free(bn_ctx);
    1572              : 
    1573              :     /* Pad auth_key to 256 bytes */
    1574              :     uint8_t auth_key_padded[256];
    1575            2 :     memset(auth_key_padded, 0, 256);
    1576            2 :     if (ak_len <= 256) {
    1577            2 :         memcpy(auth_key_padded + (256 - ak_len), auth_key, ak_len);
    1578              :     }
    1579              : 
    1580              :     /* Compute new_nonce_hash1 = last 16 bytes of SHA1(new_nonce||0x01||ak_aux_hash[8])
    1581              :      * where ak_aux_hash = SHA1(auth_key)[0:8] */
    1582              :     uint8_t ak_full_hash[20];
    1583            2 :     crypto_sha1(auth_key_padded, 256, ak_full_hash);
    1584              : 
    1585              :     uint8_t nnh_input[32 + 1 + 8];
    1586            2 :     memcpy(nnh_input,      g_srv.hs_new_nonce, 32);
    1587            2 :     nnh_input[32] = 0x01;
    1588            2 :     memcpy(nnh_input + 33, ak_full_hash, 8);
    1589              : 
    1590              :     uint8_t nnh_full[20];
    1591            2 :     crypto_sha1(nnh_input, sizeof(nnh_input), nnh_full);
    1592              :     /* last 16 bytes = nnh_full[4:20] */
    1593              :     uint8_t new_nonce_hash1[16];
    1594            2 :     memcpy(new_nonce_hash1, nnh_full + 4, 16);
    1595              : 
    1596              :     /* Save auth_key to mock server state so encrypted frames work after handshake */
    1597            2 :     memcpy(g_srv.auth_key, auth_key_padded, 256);
    1598            2 :     g_srv.auth_key_id  = derive_auth_key_id(auth_key_padded);
    1599              :     /* salt = new_nonce[0:8] XOR server_nonce[0:8] */
    1600            2 :     g_srv.server_salt = 0;
    1601           18 :     for (int i = 0; i < 8; i++) {
    1602           16 :         ((uint8_t *)&g_srv.server_salt)[i] =
    1603           16 :             g_srv.hs_new_nonce[i] ^ g_srv.hs_server_nonce[i];
    1604              :     }
    1605            2 :     g_srv.session_id = 0xAABBCCDD11223344ULL;
    1606              : 
    1607              :     /* Persist session so the client can verify it was written */
    1608              :     MtProtoSession sess;
    1609            2 :     mtproto_session_init(&sess);
    1610            2 :     mtproto_session_set_auth_key(&sess, auth_key_padded);
    1611            2 :     mtproto_session_set_salt(&sess, g_srv.server_salt);
    1612            2 :     sess.session_id = g_srv.session_id;
    1613            2 :     session_store_save(&sess, 2);  /* dc_id=2 matches the test */
    1614              : 
    1615              :     /* Send dh_gen_ok */
    1616            2 :     TlWriter tl; tl_writer_init(&tl);
    1617            2 :     tl_write_uint32(&tl, CRC_dh_gen_ok);
    1618            2 :     tl_write_int128(&tl, g_srv.hs_nonce);
    1619            2 :     tl_write_int128(&tl, g_srv.hs_server_nonce);
    1620            2 :     tl_write_int128(&tl, new_nonce_hash1);
    1621            2 :     handshake_queue_unenc(tl.data, tl.len);
    1622            2 :     tl_writer_free(&tl);
    1623              : }
    1624              : 
    1625          766 : static void encrypt_and_queue(const uint8_t *plain, size_t plain_len) {
    1626          766 :     uint8_t *enc = (uint8_t *)malloc(plain_len + 1024);
    1627          766 :     if (!enc) return;
    1628          766 :     size_t enc_len = 0;
    1629              :     uint8_t msg_key[16];
    1630          766 :     mtproto_encrypt(plain, plain_len, g_srv.auth_key, 8, enc, &enc_len, msg_key);
    1631              : 
    1632          766 :     size_t wire_len = 8 + 16 + enc_len;
    1633          766 :     uint8_t *wire = (uint8_t *)malloc(wire_len);
    1634          766 :     if (!wire) { free(enc); return; }
    1635         6894 :     for (int i = 0; i < 8; ++i) {
    1636         6128 :         wire[i] = (uint8_t)((g_srv.auth_key_id >> (i * 8)) & 0xFFu);
    1637              :     }
    1638          766 :     memcpy(wire + 8, msg_key, 16);
    1639          766 :     memcpy(wire + 24, enc, enc_len);
    1640          766 :     free(enc);
    1641              : 
    1642              :     /* Abridged length prefix (in 4-byte units). */
    1643          766 :     size_t units = wire_len / 4;
    1644          766 :     if (units < 0x7F) {
    1645          718 :         uint8_t p = (uint8_t)units;
    1646          718 :         mock_socket_append_response(&p, 1);
    1647              :     } else {
    1648              :         uint8_t p[4];
    1649           48 :         p[0] = 0x7F;
    1650           48 :         p[1] = (uint8_t)(units & 0xFFu);
    1651           48 :         p[2] = (uint8_t)((units >> 8) & 0xFFu);
    1652           48 :         p[3] = (uint8_t)((units >> 16) & 0xFFu);
    1653           48 :         mock_socket_append_response(p, 4);
    1654              :     }
    1655          766 :     mock_socket_append_response(wire, wire_len);
    1656          766 :     free(wire);
    1657              : }
        

Generated by: LCOV version 2.0-1