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

            Line data    Source code
       1              : /**
       2              :  * @file test_domain_upload.c
       3              :  * @brief Unit tests for domain_send_file (US-P6-02).
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "domain/write/upload.h"
       8              : #include "tl_serial.h"
       9              : #include "tl_registry.h"
      10              : #include "mock_socket.h"
      11              : #include "mock_crypto.h"
      12              : #include "mtproto_session.h"
      13              : #include "transport.h"
      14              : #include "api_call.h"
      15              : 
      16              : #include <stdio.h>
      17              : #include <stdlib.h>
      18              : #include <string.h>
      19              : #include <unistd.h>
      20              : 
      21           32 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
      22              :                                           uint8_t *out, size_t *out_len) {
      23           32 :     TlWriter w; tl_writer_init(&w);
      24           32 :     uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
      25           32 :     uint8_t header[32] = {0};
      26           32 :     uint32_t plen32 = (uint32_t)plen;
      27           32 :     memcpy(header + 28, &plen32, 4);
      28           32 :     tl_write_raw(&w, header, 32);
      29           32 :     tl_write_raw(&w, payload, plen);
      30           32 :     size_t enc = w.len - 24;
      31           32 :     if (enc % 16 != 0) {
      32           32 :         uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
      33              :     }
      34           32 :     size_t dwords = w.len / 4;
      35           32 :     size_t off = 0;
      36           32 :     if (dwords < 0x7F) { out[0] = (uint8_t)dwords; off = 1; }
      37              :     else {
      38            0 :         out[0] = 0x7F;
      39            0 :         out[1] = (uint8_t)dwords;
      40            0 :         out[2] = (uint8_t)(dwords >> 8);
      41            0 :         out[3] = (uint8_t)(dwords >> 16);
      42            0 :         off = 4;
      43              :     }
      44           32 :     memcpy(out + off, w.data, w.len);
      45           32 :     *out_len = off + w.len;
      46           32 :     tl_writer_free(&w);
      47           32 : }
      48              : 
      49            9 : static void fix_session(MtProtoSession *s) {
      50            9 :     mtproto_session_init(s);
      51            9 :     s->session_id = 0; /* match the zero session_id in fake encrypted frames */
      52            9 :     uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
      53            9 :     mtproto_session_set_salt(s, 0xBADCAFEDEADBEEFULL);
      54            9 : }
      55            9 : static void fix_transport(Transport *t) {
      56            9 :     transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
      57            9 : }
      58            9 : static void fix_cfg(ApiConfig *cfg) {
      59            9 :     api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
      60            9 : }
      61              : 
      62              : /* Write a small temp file, return its path (caller unlinks). */
      63            5 : static void write_temp(const char *path, const uint8_t *data, size_t n) {
      64            5 :     FILE *fp = fopen(path, "wb");
      65            5 :     if (fp) { fwrite(data, 1, n, fp); fclose(fp); }
      66            5 : }
      67              : 
      68              : /* Build a response buffer carrying two frames: boolTrue (for
      69              :  * saveFilePart) followed by updateShort (for sendMedia). */
      70            3 : static void seed_two_frame_response(void) {
      71            3 :     mock_socket_reset();
      72              : 
      73              :     /* frame 1: boolTrue */
      74            3 :     TlWriter w1; tl_writer_init(&w1);
      75            3 :     tl_write_uint32(&w1, TL_boolTrue);
      76            3 :     uint8_t resp1[128]; size_t r1 = 0;
      77            3 :     build_fake_encrypted_response(w1.data, w1.len, resp1, &r1);
      78            3 :     tl_writer_free(&w1);
      79              : 
      80              :     /* frame 2: updateShort (crc + 4 bytes update + 4 bytes date) */
      81            3 :     TlWriter w2; tl_writer_init(&w2);
      82            3 :     tl_write_uint32(&w2, TL_updateShort);
      83            3 :     tl_write_uint32(&w2, 0);
      84            3 :     tl_write_int32 (&w2, 1700000000);
      85            3 :     uint8_t resp2[128]; size_t r2 = 0;
      86            3 :     build_fake_encrypted_response(w2.data, w2.len, resp2, &r2);
      87            3 :     tl_writer_free(&w2);
      88              : 
      89            3 :     mock_socket_set_response(resp1, r1);
      90            3 :     mock_socket_append_response(resp2, r2);
      91            3 : }
      92              : 
      93            1 : static void test_upload_small_file_success(void) {
      94            1 :     mock_crypto_reset();
      95            1 :     const char *path = "/tmp/tg-cli-upload-test.bin";
      96            1 :     unlink(path);
      97              :     uint8_t body[200];
      98          201 :     for (size_t i = 0; i < sizeof(body); i++) body[i] = (uint8_t)i;
      99            1 :     write_temp(path, body, sizeof(body));
     100              : 
     101            1 :     seed_two_frame_response();
     102              : 
     103              :     MtProtoSession s; Transport t; ApiConfig cfg;
     104            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     105            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     106            1 :     RpcError err = {0};
     107            1 :     int rc = domain_send_file(&cfg, &s, &t, &peer, path, "hello", NULL, &err);
     108            1 :     ASSERT(rc == 0, "upload ok for a small file");
     109            1 :     unlink(path);
     110              : }
     111              : 
     112            1 : static void test_upload_rejects_missing_file(void) {
     113              :     MtProtoSession s; Transport t; ApiConfig cfg;
     114            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     115            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     116            1 :     int rc = domain_send_file(&cfg, &s, &t, &peer,
     117              :                                 "/tmp/tg-cli-no-such-file", NULL, NULL, NULL);
     118            1 :     ASSERT(rc == -1, "missing file rejected");
     119              : }
     120              : 
     121            1 : static void test_upload_rejects_empty_file(void) {
     122            1 :     const char *path = "/tmp/tg-cli-upload-empty.bin";
     123            1 :     unlink(path);
     124            1 :     FILE *fp = fopen(path, "wb"); if (fp) fclose(fp);
     125              : 
     126              :     MtProtoSession s; Transport t; ApiConfig cfg;
     127            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     128            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     129            1 :     int rc = domain_send_file(&cfg, &s, &t, &peer, path, NULL, NULL, NULL);
     130            1 :     ASSERT(rc == -1, "empty file rejected");
     131            1 :     unlink(path);
     132              : }
     133              : 
     134            1 : static void test_upload_null_args(void) {
     135            1 :     ASSERT(domain_send_file(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) == -1,
     136              :            "null args rejected");
     137              : }
     138              : 
     139              : /* Wire-inspection: the outbound buffer must contain the
     140              :  * upload.saveFilePart CRC and the filename + mime type. */
     141            1 : static void test_upload_writes_expected_bytes(void) {
     142            1 :     mock_crypto_reset();
     143            1 :     const char *path = "/tmp/tg-cli-upload-inspect.bin";
     144            1 :     unlink(path);
     145              :     uint8_t body[32];
     146           33 :     for (size_t i = 0; i < sizeof(body); i++) body[i] = (uint8_t)(i + 1);
     147            1 :     write_temp(path, body, sizeof(body));
     148              : 
     149            1 :     seed_two_frame_response();
     150              : 
     151              :     MtProtoSession s; Transport t; ApiConfig cfg;
     152            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     153            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     154            1 :     int rc = domain_send_file(&cfg, &s, &t, &peer, path,
     155              :                                 "hi", "text/plain", NULL);
     156            1 :     ASSERT(rc == 0, "upload ok");
     157              : 
     158            1 :     size_t sent_len = 0;
     159            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     160              : 
     161            1 :     uint32_t crc_part = 0xb304a621u;
     162            1 :     int found_part = 0;
     163          114 :     for (size_t i = 0; i + 4 <= sent_len; i++)
     164          114 :         if (memcmp(sent + i, &crc_part, 4) == 0) { found_part = 1; break; }
     165            1 :     ASSERT(found_part, "upload.saveFilePart CRC present on the wire");
     166              : 
     167            1 :     int found_name = 0;
     168            1 :     const char *basename = "tg-cli-upload-inspect.bin";
     169          336 :     for (size_t i = 0; i + strlen(basename) <= sent_len; i++)
     170          336 :         if (memcmp(sent + i, basename, strlen(basename)) == 0) {
     171            1 :             found_name = 1; break;
     172              :         }
     173            1 :     ASSERT(found_name, "filename appears in sendMedia payload");
     174              : 
     175            1 :     int found_mime = 0;
     176            1 :     const char *mime = "text/plain";
     177          368 :     for (size_t i = 0; i + strlen(mime) <= sent_len; i++)
     178          368 :         if (memcmp(sent + i, mime, strlen(mime)) == 0) {
     179            1 :             found_mime = 1; break;
     180              :         }
     181            1 :     ASSERT(found_mime, "mime_type appears in sendMedia payload");
     182              : 
     183            1 :     unlink(path);
     184              : }
     185              : 
     186              : /* Seed the mock socket with @p n boolTrue frames (for N saveBigFilePart
     187              :  * acks) followed by an updateShort frame (for the final sendMedia). */
     188            1 : static void seed_big_response(int n_parts) {
     189            1 :     mock_socket_reset();
     190           25 :     for (int i = 0; i < n_parts; i++) {
     191           24 :         TlWriter w; tl_writer_init(&w);
     192           24 :         tl_write_uint32(&w, TL_boolTrue);
     193           24 :         uint8_t resp[128]; size_t rlen = 0;
     194           24 :         build_fake_encrypted_response(w.data, w.len, resp, &rlen);
     195           24 :         tl_writer_free(&w);
     196           24 :         if (i == 0) mock_socket_set_response(resp, rlen);
     197           23 :         else        mock_socket_append_response(resp, rlen);
     198              :     }
     199            1 :     TlWriter w; tl_writer_init(&w);
     200            1 :     tl_write_uint32(&w, TL_updateShort);
     201            1 :     tl_write_uint32(&w, 0);
     202            1 :     tl_write_int32 (&w, 1700000000);
     203            1 :     uint8_t resp[128]; size_t rlen = 0;
     204            1 :     build_fake_encrypted_response(w.data, w.len, resp, &rlen);
     205            1 :     tl_writer_free(&w);
     206            1 :     mock_socket_append_response(resp, rlen);
     207            1 : }
     208              : 
     209            1 : static void test_upload_big_file_uses_saveBigFilePart(void) {
     210            1 :     mock_crypto_reset();
     211            1 :     const char *path = "/tmp/tg-cli-upload-big.bin";
     212            1 :     unlink(path);
     213              : 
     214              :     /* 12 MiB, just over the 10 MiB big threshold. */
     215            1 :     const size_t big_size = 12 * 1024 * 1024;
     216            1 :     uint8_t *body = (uint8_t *)malloc(big_size);
     217            1 :     ASSERT(body != NULL, "alloc 12 MiB body");
     218     12582913 :     for (size_t i = 0; i < big_size; i++) body[i] = (uint8_t)(i & 0xff);
     219            1 :     write_temp(path, body, big_size);
     220            1 :     free(body);
     221              : 
     222            1 :     int expected_parts = (int)((big_size + UPLOAD_CHUNK_SIZE - 1)
     223            1 :                                 / UPLOAD_CHUNK_SIZE);
     224            1 :     seed_big_response(expected_parts);
     225              : 
     226              :     MtProtoSession s; Transport t; ApiConfig cfg;
     227            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     228            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     229            1 :     int rc = domain_send_file(&cfg, &s, &t, &peer, path, NULL, NULL, NULL);
     230            1 :     ASSERT(rc == 0, "big-file upload ok");
     231              : 
     232            1 :     size_t sent_len = 0;
     233            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     234              : 
     235            1 :     uint32_t big_part = 0xde7b673du;
     236            1 :     int found_big = 0;
     237          117 :     for (size_t i = 0; i + 4 <= sent_len; i++)
     238          117 :         if (memcmp(sent + i, &big_part, 4) == 0) { found_big = 1; break; }
     239            1 :     ASSERT(found_big, "upload.saveBigFilePart CRC transmitted");
     240              : 
     241            1 :     uint32_t input_file_big = 0xfa4f0bb5u;
     242            1 :     int found_ifb = 0;
     243     12586790 :     for (size_t i = 0; i + 4 <= sent_len; i++)
     244     12586790 :         if (memcmp(sent + i, &input_file_big, 4) == 0) { found_ifb = 1; break; }
     245            1 :     ASSERT(found_ifb, "inputFileBig CRC on sendMedia");
     246              : 
     247              :     /* Plain saveFilePart CRC must NOT appear when is_big. */
     248            1 :     uint32_t small_part = 0xb304a621u;
     249            1 :     int found_small = 0;
     250     12586919 :     for (size_t i = 0; i + 4 <= sent_len; i++)
     251     12586918 :         if (memcmp(sent + i, &small_part, 4) == 0) {
     252            0 :             found_small = 1; break;
     253              :         }
     254            1 :     ASSERT(!found_small, "saveFilePart NOT used for big file");
     255              : 
     256            1 :     unlink(path);
     257              : }
     258              : 
     259            1 : static void test_upload_rejects_oversized_file(void) {
     260              :     /* Fabricate a file just over UPLOAD_MAX_SIZE by seeking — avoids
     261              :      * actually writing 1.5 GiB. */
     262            1 :     const char *path = "/tmp/tg-cli-upload-oversize.bin";
     263            1 :     unlink(path);
     264            1 :     FILE *fp = fopen(path, "wb");
     265            1 :     ASSERT(fp != NULL, "open oversize file");
     266            1 :     if (fp) {
     267            1 :         off_t too_big = (off_t)UPLOAD_MAX_SIZE + 1024;
     268              :         /* Seek then write one byte to create a sparse file. */
     269            1 :         if (fseeko(fp, too_big - 1, SEEK_SET) == 0) {
     270            1 :             fputc(0, fp);
     271              :         }
     272            1 :         fclose(fp);
     273              :     }
     274              : 
     275              :     MtProtoSession s; Transport t; ApiConfig cfg;
     276            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     277            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     278            1 :     int rc = domain_send_file(&cfg, &s, &t, &peer, path, NULL, NULL, NULL);
     279            1 :     ASSERT(rc == -1, "over-cap file rejected");
     280            1 :     unlink(path);
     281              : }
     282              : 
     283              : /* saveFilePart returns a non-migrate RPC error — domain_send_file must
     284              :  * bail out rather than entering the cross-DC retry path. */
     285            1 : static void test_upload_non_migrate_error_bails(void) {
     286            1 :     mock_crypto_reset();
     287              : 
     288            1 :     const char *path = "/tmp/tg-cli-upload-err.bin";
     289            1 :     unlink(path);
     290              :     uint8_t body[64];
     291           65 :     for (size_t i = 0; i < sizeof(body); i++) body[i] = (uint8_t)i;
     292            1 :     write_temp(path, body, sizeof(body));
     293              : 
     294              :     /* Prime one frame with an FLOOD_WAIT_10 RPC error (no migrate_dc). */
     295              :     uint8_t payload[128];
     296            1 :     TlWriter w; tl_writer_init(&w);
     297            1 :     tl_write_uint32(&w, TL_rpc_error);
     298            1 :     tl_write_int32 (&w, 420);
     299            1 :     tl_write_string(&w, "FLOOD_WAIT_10");
     300            1 :     memcpy(payload, w.data, w.len);
     301            1 :     size_t plen = w.len;
     302            1 :     tl_writer_free(&w);
     303              : 
     304            1 :     uint8_t resp[512]; size_t rlen = 0;
     305            1 :     build_fake_encrypted_response(payload, plen, resp, &rlen);
     306            1 :     mock_socket_reset();
     307            1 :     mock_socket_set_response(resp, rlen);
     308              : 
     309              :     MtProtoSession s; Transport t; ApiConfig cfg;
     310            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     311            1 :     int creates_before = mock_socket_was_created();
     312              : 
     313            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     314            1 :     int rc = domain_send_file(&cfg, &s, &t, &peer, path, NULL, NULL, NULL);
     315            1 :     ASSERT(rc == -1, "non-migrate error propagates");
     316            1 :     ASSERT(mock_socket_was_created() == creates_before,
     317              :            "no cross-DC socket opened for non-migrate error");
     318            1 :     unlink(path);
     319              : }
     320              : 
     321              : /* ---- LIM-01: photo upload ---- */
     322              : 
     323            1 : static void test_path_is_image_detects_common_extensions(void) {
     324            1 :     ASSERT(domain_path_is_image("foo.jpg") == 1, "jpg is image");
     325            1 :     ASSERT(domain_path_is_image("/a/b.JPEG") == 1, "JPEG case-insensitive");
     326            1 :     ASSERT(domain_path_is_image("/abc/x.png") == 1, "png is image");
     327            1 :     ASSERT(domain_path_is_image("/abc/x.Webp") == 1, "Webp case");
     328            1 :     ASSERT(domain_path_is_image("a.gif") == 1, "gif is image");
     329            1 :     ASSERT(domain_path_is_image("a.pdf") == 0, "pdf not image");
     330            1 :     ASSERT(domain_path_is_image("a.txt") == 0, "txt not image");
     331            1 :     ASSERT(domain_path_is_image("noext")  == 0, "no extension not image");
     332            1 :     ASSERT(domain_path_is_image("")       == 0, "empty not image");
     333            1 :     ASSERT(domain_path_is_image(NULL)     == 0, "NULL safe");
     334              : }
     335              : 
     336            1 : static void test_send_photo_small_success(void) {
     337            1 :     mock_crypto_reset();
     338            1 :     const char *path = "/tmp/tg-cli-photo.jpg";
     339            1 :     unlink(path);
     340              :     uint8_t body[256];
     341          257 :     for (size_t i = 0; i < sizeof(body); i++) body[i] = (uint8_t)(i * 3);
     342            1 :     write_temp(path, body, sizeof(body));
     343              : 
     344            1 :     seed_two_frame_response();
     345              : 
     346              :     MtProtoSession s; Transport t; ApiConfig cfg;
     347            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     348            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     349            1 :     RpcError err = {0};
     350            1 :     int rc = domain_send_photo(&cfg, &s, &t, &peer, path, "cheese", &err);
     351            1 :     ASSERT(rc == 0, "photo upload ok");
     352              : 
     353            1 :     size_t sent_len = 0;
     354            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     355              : 
     356              :     /* inputMediaUploadedPhoto CRC must appear — not the document one. */
     357            1 :     uint32_t photo_crc = 0x1e287d04u;
     358            1 :     uint32_t doc_crc   = 0x5b38c6c1u;
     359            1 :     int found_photo = 0, found_doc = 0;
     360          608 :     for (size_t i = 0; i + 4 <= sent_len; i++) {
     361          607 :         if (memcmp(sent + i, &photo_crc, 4) == 0) found_photo = 1;
     362          607 :         if (memcmp(sent + i, &doc_crc,   4) == 0) found_doc = 1;
     363              :     }
     364            1 :     ASSERT(found_photo, "inputMediaUploadedPhoto on the wire");
     365            1 :     ASSERT(!found_doc, "inputMediaUploadedDocument NOT on the wire");
     366              : 
     367            1 :     unlink(path);
     368              : }
     369              : 
     370            1 : static void test_send_photo_null_args(void) {
     371            1 :     ASSERT(domain_send_photo(NULL, NULL, NULL, NULL, NULL, NULL, NULL) == -1,
     372              :            "null args rejected");
     373              : }
     374              : 
     375            1 : static void test_send_photo_rejects_missing_file(void) {
     376              :     MtProtoSession s; Transport t; ApiConfig cfg;
     377            1 :     fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
     378            1 :     HistoryPeer peer = { .kind = HISTORY_PEER_SELF };
     379            1 :     int rc = domain_send_photo(&cfg, &s, &t, &peer,
     380              :                                 "/tmp/tg-cli-no-such-photo", NULL, NULL);
     381            1 :     ASSERT(rc == -1, "missing photo rejected");
     382              : }
     383              : 
     384              : /* TEST-37: Empty TL string encoding of md5_checksum in inputFile.
     385              :  *
     386              :  * An empty TL string is encoded as: 0x00 (1-byte length prefix = 0) followed
     387              :  * by 3 zero padding bytes → 4 bytes total: { 0x00, 0x00, 0x00, 0x00 }.
     388              :  *
     389              :  * Build what write_input_file() would produce for a small (non-big) file and
     390              :  * verify the md5_checksum field appears as exactly these 4 bytes after the
     391              :  * filename TL string.  For inputFileBig, confirm those 4 bytes are absent
     392              :  * from the serialized body (the spec omits md5_checksum entirely).
     393              :  */
     394            1 : static void test_inputfile_md5_empty_tl_encoding(void) {
     395              :     /* Replicate write_input_file logic for a small file. */
     396            1 :     TlWriter w; tl_writer_init(&w);
     397            1 :     uint32_t crc_inputFile = 0xf52ff27fu;
     398            1 :     tl_write_uint32(&w, crc_inputFile);
     399            1 :     tl_write_int64 (&w, (int64_t)0x1122334455667788LL);  /* file_id */
     400            1 :     tl_write_int32 (&w, 1);                               /* part_count */
     401            1 :     tl_write_string(&w, "test.bin");                      /* file_name */
     402            1 :     tl_write_string(&w, "");                              /* md5_checksum */
     403              : 
     404              :     /* The last 4 bytes of the serialized buffer must be the empty TL string:
     405              :      * 0x00 (len=0) + 3 zero pad bytes. */
     406            1 :     ASSERT(w.len >= 4, "inputFile serialization has at least 4 bytes");
     407            1 :     const uint8_t *tail = w.data + w.len - 4;
     408            1 :     uint8_t expected[4] = {0x00, 0x00, 0x00, 0x00};
     409            1 :     ASSERT(memcmp(tail, expected, 4) == 0,
     410              :            "inputFile md5_checksum is 4 zero bytes (empty TL string)");
     411              : 
     412              :     /* Verify the CRC is at the start. */
     413            1 :     uint32_t got_crc = 0;
     414            1 :     memcpy(&got_crc, w.data, 4);
     415            1 :     ASSERT(got_crc == crc_inputFile, "inputFile CRC at offset 0");
     416              : 
     417            1 :     tl_writer_free(&w);
     418              : }
     419              : 
     420            1 : static void test_inputfilebig_has_no_md5_field(void) {
     421              :     /* Replicate write_input_file logic for a big file (no md5_checksum). */
     422            1 :     TlWriter w; tl_writer_init(&w);
     423            1 :     uint32_t crc_inputFileBig = 0xfa4f0bb5u;
     424            1 :     tl_write_uint32(&w, crc_inputFileBig);
     425            1 :     tl_write_int64 (&w, (int64_t)0xAABBCCDDEEFF0011LL); /* file_id */
     426            1 :     tl_write_int32 (&w, 3);                              /* part_count */
     427            1 :     tl_write_string(&w, "big.bin");                      /* file_name */
     428              :     /* No md5_checksum field for inputFileBig. */
     429              : 
     430              :     /* The last 4 bytes are the tail of the filename TL string, NOT an empty
     431              :      * md5 field.  Scan the entire buffer: an empty TL string (0 0 0 0) might
     432              :      * coincidentally appear in the padding of the filename, but it must NOT
     433              :      * be appended as an extra field.  The total length must match exactly the
     434              :      * layout without a trailing empty string. */
     435              : 
     436              :     /* Expected layout:
     437              :      *   4  CRC
     438              :      *   8  file_id
     439              :      *   4  part_count
     440              :      *   ?  tl_string("big.bin") = 1 + 7 + 0 pad = 8 bytes
     441              :      * Total = 24 bytes.  With md5 it would be 28. */
     442            1 :     ASSERT(w.len == 24, "inputFileBig serialized length is 24 (no md5 field)");
     443              : 
     444            1 :     uint32_t got_crc = 0;
     445            1 :     memcpy(&got_crc, w.data, 4);
     446            1 :     ASSERT(got_crc == crc_inputFileBig, "inputFileBig CRC at offset 0");
     447              : 
     448            1 :     tl_writer_free(&w);
     449              : }
     450              : 
     451            1 : void run_domain_upload_tests(void) {
     452            1 :     RUN_TEST(test_upload_small_file_success);
     453            1 :     RUN_TEST(test_upload_rejects_missing_file);
     454            1 :     RUN_TEST(test_upload_rejects_empty_file);
     455            1 :     RUN_TEST(test_upload_null_args);
     456            1 :     RUN_TEST(test_upload_writes_expected_bytes);
     457            1 :     RUN_TEST(test_upload_big_file_uses_saveBigFilePart);
     458            1 :     RUN_TEST(test_upload_rejects_oversized_file);
     459            1 :     RUN_TEST(test_upload_non_migrate_error_bails);
     460            1 :     RUN_TEST(test_path_is_image_detects_common_extensions);
     461            1 :     RUN_TEST(test_send_photo_small_success);
     462            1 :     RUN_TEST(test_send_photo_null_args);
     463            1 :     RUN_TEST(test_send_photo_rejects_missing_file);
     464            1 :     RUN_TEST(test_inputfile_md5_empty_tl_encoding);
     465            1 :     RUN_TEST(test_inputfilebig_has_no_md5_field);
     466            1 : }
        

Generated by: LCOV version 2.0-1