LCOV - code coverage report
Current view: top level - tests/unit - test_api_call.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 224 224
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 12 12

            Line data    Source code
       1              : /**
       2              :  * @file test_api_call.c
       3              :  * @brief Unit tests for API call wrapper (initConnection + invokeWithLayer).
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "api_call.h"
       8              : #include "tl_serial.h"
       9              : #include "mock_crypto.h"
      10              : 
      11              : #include <stdlib.h>
      12              : #include <string.h>
      13              : 
      14            1 : void test_api_config_init(void) {
      15              :     ApiConfig cfg;
      16            1 :     api_config_init(&cfg);
      17            1 :     ASSERT(cfg.api_id == 0, "api_id should be 0 before setting");
      18            1 :     ASSERT(strcmp(cfg.device_model, "tg-cli") == 0, "device_model default");
      19            1 :     ASSERT(strcmp(cfg.app_version, "0.1.0") == 0, "app_version default");
      20            1 :     ASSERT(strcmp(cfg.system_lang_code, "en") == 0, "lang_code default");
      21              : }
      22              : 
      23            1 : void test_api_wrap_query_structure(void) {
      24              :     ApiConfig cfg;
      25            1 :     api_config_init(&cfg);
      26            1 :     cfg.api_id = 12345;
      27              : 
      28              :     /* Simple inner query: just a constructor */
      29              :     TlWriter q;
      30            1 :     tl_writer_init(&q);
      31            1 :     tl_write_uint32(&q, 0xDEADBEEF); /* fake query constructor */
      32              : 
      33              :     uint8_t out[4096];
      34            1 :     size_t out_len = 0;
      35            1 :     int rc = api_wrap_query(&cfg, q.data, q.len, out, sizeof(out), &out_len);
      36            1 :     tl_writer_free(&q);
      37              : 
      38            1 :     ASSERT(rc == 0, "wrap should succeed");
      39            1 :     ASSERT(out_len > 0, "output should have data");
      40              : 
      41              :     /* Parse the wrapped output */
      42            1 :     TlReader r = tl_reader_init(out, out_len);
      43              : 
      44              :     /* invokeWithLayer constructor */
      45            1 :     uint32_t crc1 = tl_read_uint32(&r);
      46            1 :     ASSERT(crc1 == CRC_invokeWithLayer, "should start with invokeWithLayer");
      47              : 
      48              :     /* layer */
      49            1 :     int32_t layer = tl_read_int32(&r);
      50            1 :     ASSERT(layer == TL_LAYER, "layer should match TL_LAYER");
      51              : 
      52              :     /* initConnection constructor */
      53            1 :     uint32_t crc2 = tl_read_uint32(&r);
      54            1 :     ASSERT(crc2 == CRC_initConnection, "should have initConnection");
      55              : 
      56              :     /* flags */
      57            1 :     int32_t flags = tl_read_int32(&r);
      58            1 :     ASSERT(flags == 0, "flags should be 0");
      59              : 
      60              :     /* api_id */
      61            1 :     int32_t api_id = tl_read_int32(&r);
      62            1 :     ASSERT(api_id == 12345, "api_id should be 12345");
      63              : 
      64              :     /* device_model */
      65            1 :     char *dm = tl_read_string(&r);
      66            1 :     ASSERT(dm != NULL, "device_model should not be NULL");
      67            1 :     ASSERT(strcmp(dm, "tg-cli") == 0, "device_model should be tg-cli");
      68            1 :     free(dm);
      69              : 
      70              :     /* system_version */
      71            1 :     char *sv = tl_read_string(&r);
      72            1 :     ASSERT(sv != NULL, "system_version");
      73            1 :     free(sv);
      74              : 
      75              :     /* app_version */
      76            1 :     char *av = tl_read_string(&r);
      77            1 :     ASSERT(av != NULL, "app_version");
      78            1 :     free(av);
      79              : 
      80              :     /* system_lang_code */
      81            1 :     char *slc = tl_read_string(&r);
      82            1 :     ASSERT(slc != NULL, "system_lang_code");
      83            1 :     free(slc);
      84              : 
      85              :     /* lang_pack */
      86            1 :     char *lp = tl_read_string(&r);
      87            1 :     ASSERT(lp != NULL, "lang_pack");
      88            1 :     free(lp);
      89              : 
      90              :     /* lang_code */
      91            1 :     char *lc = tl_read_string(&r);
      92            1 :     ASSERT(lc != NULL, "lang_code");
      93            1 :     free(lc);
      94              : 
      95              :     /* Inner query should follow */
      96            1 :     uint32_t inner_crc = tl_read_uint32(&r);
      97            1 :     ASSERT(inner_crc == 0xDEADBEEF, "inner query constructor should be at the end");
      98              : }
      99              : 
     100            1 : void test_api_wrap_query_null_args(void) {
     101              :     ApiConfig cfg;
     102            1 :     api_config_init(&cfg);
     103            1 :     uint8_t query[4] = {0};
     104              :     uint8_t out[256];
     105              :     size_t out_len;
     106              : 
     107            1 :     ASSERT(api_wrap_query(NULL, query, 4, out, 256, &out_len) == -1, "NULL cfg");
     108            1 :     ASSERT(api_wrap_query(&cfg, NULL, 4, out, 256, &out_len) == -1, "NULL query");
     109            1 :     ASSERT(api_wrap_query(&cfg, query, 4, NULL, 256, &out_len) == -1, "NULL out");
     110            1 :     ASSERT(api_wrap_query(&cfg, query, 4, out, 256, NULL) == -1, "NULL out_len");
     111              : }
     112              : 
     113            1 : void test_api_wrap_query_buffer_too_small(void) {
     114              :     ApiConfig cfg;
     115            1 :     api_config_init(&cfg);
     116            1 :     cfg.api_id = 1;
     117              : 
     118            1 :     uint8_t query[4] = {0};
     119              :     uint8_t out[8]; /* way too small */
     120              :     size_t out_len;
     121              : 
     122            1 :     int rc = api_wrap_query(&cfg, query, 4, out, 8, &out_len);
     123            1 :     ASSERT(rc == -1, "should fail with buffer too small");
     124              : }
     125              : 
     126              : /* ---- bad_server_salt retry ---- */
     127              : 
     128              : #include "mock_socket.h"
     129              : #include "mtproto_session.h"
     130              : #include "transport.h"
     131              : #include "tl_registry.h"
     132              : 
     133            8 : static void pack_encrypted(const uint8_t *payload, size_t plen,
     134              :                             uint8_t *out, size_t *out_len) {
     135            8 :     TlWriter w; tl_writer_init(&w);
     136            8 :     uint8_t z24[24] = {0}; tl_write_raw(&w, z24, 24);
     137            8 :     uint8_t hdr[32] = {0};
     138            8 :     uint32_t pl32 = (uint32_t)plen;
     139            8 :     memcpy(hdr + 28, &pl32, 4);
     140            8 :     tl_write_raw(&w, hdr, 32);
     141            8 :     tl_write_raw(&w, payload, plen);
     142            8 :     size_t enc = w.len - 24;
     143            8 :     if (enc % 16 != 0) {
     144            8 :         uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
     145              :     }
     146            8 :     out[0] = (uint8_t)(w.len / 4);
     147            8 :     memcpy(out + 1, w.data, w.len);
     148            8 :     *out_len = 1 + w.len;
     149            8 :     tl_writer_free(&w);
     150            8 : }
     151              : 
     152            1 : static void test_bad_server_salt_retry(void) {
     153            1 :     mock_socket_reset();
     154            1 :     mock_crypto_reset();
     155              : 
     156              :     /* First response: bad_server_salt. */
     157              :     uint8_t bad_payload[64];
     158            1 :     memset(bad_payload, 0, sizeof(bad_payload));
     159            1 :     uint32_t crc = TL_bad_server_salt;
     160            1 :     memcpy(bad_payload, &crc, 4);
     161              :     /* bad_msg_id (8) + bad_msg_seqno (4) + error_code (4) zeros */
     162            1 :     uint64_t new_salt = 0x9988776655443322ULL;
     163            1 :     memcpy(bad_payload + 20, &new_salt, 8);
     164              : 
     165            1 :     uint8_t resp1[256]; size_t rlen1 = 0;
     166            1 :     pack_encrypted(bad_payload, 28, resp1, &rlen1);
     167              : 
     168              :     /* Second response (post-retry): a valid rpc_result carrying bool_true. */
     169              :     uint8_t ok_payload[32];
     170            1 :     TlWriter w2; tl_writer_init(&w2);
     171            1 :     tl_write_uint32(&w2, TL_boolTrue);
     172            1 :     memcpy(ok_payload, w2.data, w2.len);
     173            1 :     size_t ok_plen = w2.len;
     174            1 :     tl_writer_free(&w2);
     175              : 
     176            1 :     uint8_t resp2[256]; size_t rlen2 = 0;
     177            1 :     pack_encrypted(ok_payload, ok_plen, resp2, &rlen2);
     178              : 
     179            1 :     mock_socket_set_response(resp1, rlen1);
     180            1 :     mock_socket_append_response(resp2, rlen2);
     181              : 
     182              :     MtProtoSession s;
     183            1 :     mtproto_session_init(&s);
     184            1 :     s.session_id = 0; /* match the zero session_id in fake encrypted frames */
     185            1 :     uint8_t key[256] = {0};
     186            1 :     mtproto_session_set_auth_key(&s, key);
     187            1 :     mtproto_session_set_salt(&s, 0x1111111111111111ULL);
     188              : 
     189              :     Transport t;
     190            1 :     transport_init(&t); t.fd = 42; t.connected = 1; t.dc_id = 1;
     191              : 
     192            1 :     ApiConfig cfg; api_config_init(&cfg);
     193            1 :     cfg.api_id = 12345; cfg.api_hash = "deadbeef";
     194              : 
     195              :     uint8_t query[8];
     196            1 :     memset(query, 0, sizeof(query));
     197            1 :     crc = TL_boolTrue; /* any tiny query */
     198            1 :     memcpy(query, &crc, 4);
     199              : 
     200              :     uint8_t resp[128];
     201            1 :     size_t resp_len = 0;
     202            1 :     int rc = api_call(&cfg, &s, &t, query, 4, resp, sizeof(resp), &resp_len);
     203              : 
     204            1 :     ASSERT(rc == 0, "api_call succeeds after bad_salt retry");
     205            1 :     ASSERT(s.server_salt == 0x9988776655443322ULL,
     206              :            "salt updated from bad_server_salt");
     207              : }
     208              : 
     209              : /* ---- new_session_created skip ---- */
     210            1 : static void test_new_session_created_skipped(void) {
     211            1 :     mock_socket_reset();
     212            1 :     mock_crypto_reset();
     213              : 
     214              :     /* First response: new_session_created — should be silently swallowed. */
     215              :     uint8_t ns_payload[32];
     216            1 :     memset(ns_payload, 0, sizeof(ns_payload));
     217            1 :     uint32_t ns_crc = TL_new_session_created;
     218            1 :     memcpy(ns_payload, &ns_crc, 4);
     219              :     /* first_msg_id (8) + unique_id (8) + server_salt (8) — put a
     220              :      * recognisable salt at offset 20. */
     221            1 :     uint64_t salt = 0xAABBCCDD00112233ULL;
     222            1 :     memcpy(ns_payload + 20, &salt, 8);
     223              : 
     224            1 :     uint8_t resp_ns[256]; size_t rlen_ns = 0;
     225            1 :     pack_encrypted(ns_payload, 28, resp_ns, &rlen_ns);
     226              : 
     227              :     /* Second response: real bool_true. */
     228            1 :     TlWriter w; tl_writer_init(&w);
     229            1 :     tl_write_uint32(&w, TL_boolTrue);
     230            1 :     uint8_t ok_pay[8]; memcpy(ok_pay, w.data, w.len);
     231            1 :     size_t ok_plen = w.len; tl_writer_free(&w);
     232              : 
     233            1 :     uint8_t resp_ok[256]; size_t rlen_ok = 0;
     234            1 :     pack_encrypted(ok_pay, ok_plen, resp_ok, &rlen_ok);
     235              : 
     236            1 :     mock_socket_set_response(resp_ns, rlen_ns);
     237            1 :     mock_socket_append_response(resp_ok, rlen_ok);
     238              : 
     239              :     MtProtoSession s;
     240            1 :     mtproto_session_init(&s);
     241            1 :     s.session_id = 0; /* match the zero session_id in fake encrypted frames */
     242            1 :     uint8_t key[256] = {0};
     243            1 :     mtproto_session_set_auth_key(&s, key);
     244            1 :     mtproto_session_set_salt(&s, 0x1);
     245              : 
     246              :     Transport t;
     247            1 :     transport_init(&t); t.fd = 42; t.connected = 1; t.dc_id = 1;
     248              : 
     249            1 :     ApiConfig cfg; api_config_init(&cfg);
     250            1 :     cfg.api_id = 12345; cfg.api_hash = "deadbeef";
     251              : 
     252            1 :     uint32_t q_crc = TL_boolTrue;
     253            1 :     uint8_t query[4]; memcpy(query, &q_crc, 4);
     254              : 
     255            1 :     uint8_t resp[64]; size_t resp_len = 0;
     256            1 :     int rc = api_call(&cfg, &s, &t, query, 4, resp, sizeof(resp), &resp_len);
     257            1 :     ASSERT(rc == 0, "new_session_created was skipped cleanly");
     258            1 :     ASSERT(s.server_salt == 0xAABBCCDD00112233ULL,
     259              :            "salt taken from new_session_created");
     260              : }
     261              : 
     262              : /* ---- bad_msg_notification tests ---- */
     263              : 
     264              : /** Helper: build a minimal bad_msg_notification payload of given length. */
     265            2 : static void build_bad_msg_payload(uint8_t *buf, size_t len, int32_t error_code) {
     266            2 :     memset(buf, 0, len);
     267            2 :     uint32_t crc = TL_bad_msg_notification;
     268            2 :     memcpy(buf, &crc, 4);
     269              :     /* bad_msg_id (8 bytes) at offset 4 — zeros */
     270              :     /* bad_msg_seqno (4 bytes) at offset 12 — zeros */
     271            2 :     if (len >= 20) memcpy(buf + 16, &error_code, 4);
     272            2 : }
     273              : 
     274              : /** Test 1: bad_msg_notification as first response returns -1. */
     275            1 : static void test_bad_msg_notification_first_response(void) {
     276            1 :     mock_socket_reset();
     277            1 :     mock_crypto_reset();
     278              : 
     279              :     uint8_t payload[20];
     280            1 :     build_bad_msg_payload(payload, sizeof(payload), 16 /* msg_id too low */);
     281              : 
     282            1 :     uint8_t resp[256]; size_t rlen = 0;
     283            1 :     pack_encrypted(payload, sizeof(payload), resp, &rlen);
     284            1 :     mock_socket_set_response(resp, rlen);
     285              : 
     286              :     MtProtoSession s;
     287            1 :     mtproto_session_init(&s);
     288            1 :     s.session_id = 0;
     289            1 :     uint8_t key[256] = {0};
     290            1 :     mtproto_session_set_auth_key(&s, key);
     291              : 
     292              :     Transport t;
     293            1 :     transport_init(&t); t.fd = 42; t.connected = 1; t.dc_id = 1;
     294              : 
     295            1 :     ApiConfig cfg; api_config_init(&cfg);
     296            1 :     cfg.api_id = 12345; cfg.api_hash = "deadbeef";
     297              : 
     298            1 :     uint32_t q_crc = TL_boolTrue;
     299            1 :     uint8_t query[4]; memcpy(query, &q_crc, 4);
     300            1 :     uint8_t out[64]; size_t out_len = 0;
     301              : 
     302            1 :     int rc = api_call(&cfg, &s, &t, query, 4, out, sizeof(out), &out_len);
     303            1 :     ASSERT(rc == -1, "api_call returns -1 on bad_msg_notification");
     304              : }
     305              : 
     306              : /** Test 2: bad_msg_notification after a msgs_ack service frame → bails. */
     307            1 : static void test_bad_msg_notification_after_msgs_ack(void) {
     308            1 :     mock_socket_reset();
     309            1 :     mock_crypto_reset();
     310              : 
     311              :     /* First response: msgs_ack (should be silently skipped). */
     312              :     uint8_t ack_payload[8];
     313            1 :     memset(ack_payload, 0, sizeof(ack_payload));
     314            1 :     uint32_t ack_crc = TL_msgs_ack;
     315            1 :     memcpy(ack_payload, &ack_crc, 4);
     316            1 :     uint8_t resp1[256]; size_t rlen1 = 0;
     317            1 :     pack_encrypted(ack_payload, sizeof(ack_payload), resp1, &rlen1);
     318              : 
     319              :     /* Second response: bad_msg_notification. */
     320              :     uint8_t bm_payload[20];
     321            1 :     build_bad_msg_payload(bm_payload, sizeof(bm_payload), 32 /* seqno too low */);
     322            1 :     uint8_t resp2[256]; size_t rlen2 = 0;
     323            1 :     pack_encrypted(bm_payload, sizeof(bm_payload), resp2, &rlen2);
     324              : 
     325            1 :     mock_socket_set_response(resp1, rlen1);
     326            1 :     mock_socket_append_response(resp2, rlen2);
     327              : 
     328              :     MtProtoSession s;
     329            1 :     mtproto_session_init(&s);
     330            1 :     s.session_id = 0;
     331            1 :     uint8_t key[256] = {0};
     332            1 :     mtproto_session_set_auth_key(&s, key);
     333              : 
     334              :     Transport t;
     335            1 :     transport_init(&t); t.fd = 42; t.connected = 1; t.dc_id = 1;
     336              : 
     337            1 :     ApiConfig cfg; api_config_init(&cfg);
     338            1 :     cfg.api_id = 12345; cfg.api_hash = "deadbeef";
     339              : 
     340            1 :     uint32_t q_crc = TL_boolTrue;
     341            1 :     uint8_t query[4]; memcpy(query, &q_crc, 4);
     342            1 :     uint8_t out[64]; size_t out_len = 0;
     343              : 
     344            1 :     int rc = api_call(&cfg, &s, &t, query, 4, out, sizeof(out), &out_len);
     345            1 :     ASSERT(rc == -1, "api_call returns -1 after msgs_ack + bad_msg_notification");
     346              : }
     347              : 
     348              : /** Test 3: bad_msg_notification with resp_len < 20 (no error_code) → SVC_ERROR. */
     349            1 : static void test_bad_msg_notification_short_frame(void) {
     350            1 :     mock_socket_reset();
     351            1 :     mock_crypto_reset();
     352              : 
     353              :     /* Only 4 bytes: just the CRC, no fields — triggers the < 20 guard. */
     354              :     uint8_t short_payload[4];
     355            1 :     uint32_t crc = TL_bad_msg_notification;
     356            1 :     memcpy(short_payload, &crc, 4);
     357              : 
     358            1 :     uint8_t resp[256]; size_t rlen = 0;
     359            1 :     pack_encrypted(short_payload, sizeof(short_payload), resp, &rlen);
     360            1 :     mock_socket_set_response(resp, rlen);
     361              : 
     362              :     MtProtoSession s;
     363            1 :     mtproto_session_init(&s);
     364            1 :     s.session_id = 0;
     365            1 :     uint8_t key[256] = {0};
     366            1 :     mtproto_session_set_auth_key(&s, key);
     367              : 
     368              :     Transport t;
     369            1 :     transport_init(&t); t.fd = 42; t.connected = 1; t.dc_id = 1;
     370              : 
     371            1 :     ApiConfig cfg; api_config_init(&cfg);
     372            1 :     cfg.api_id = 12345; cfg.api_hash = "deadbeef";
     373              : 
     374            1 :     uint32_t q_crc = TL_boolTrue;
     375            1 :     uint8_t query[4]; memcpy(query, &q_crc, 4);
     376            1 :     uint8_t out[64]; size_t out_len = 0;
     377              : 
     378            1 :     int rc = api_call(&cfg, &s, &t, query, 4, out, sizeof(out), &out_len);
     379            1 :     ASSERT(rc == -1, "api_call returns -1 on truncated bad_msg_notification");
     380              : }
     381              : 
     382            1 : void test_api_call(void) {
     383            1 :     RUN_TEST(test_api_config_init);
     384            1 :     RUN_TEST(test_api_wrap_query_structure);
     385            1 :     RUN_TEST(test_api_wrap_query_null_args);
     386            1 :     RUN_TEST(test_api_wrap_query_buffer_too_small);
     387            1 :     RUN_TEST(test_bad_server_salt_retry);
     388            1 :     RUN_TEST(test_new_session_created_skipped);
     389            1 :     RUN_TEST(test_bad_msg_notification_first_response);
     390            1 :     RUN_TEST(test_bad_msg_notification_after_msgs_ack);
     391            1 :     RUN_TEST(test_bad_msg_notification_short_frame);
     392            1 : }
        

Generated by: LCOV version 2.0-1