mirror of
https://github.com/itsPLK/ps5-pkg-manager.git
synced 2026-10-23 04:12:39 -05:00
- Redesign header storage widgets to compact height with 'INT' and 'M.2' labels below icons - Set storage widgets to min-w-[230px] with progress bars displaying used disk space fill - Reinstate storage checks: block installation if neither drive has sufficient space when M.2 is present, or if internal lacks space when no M.2 is present - Add tests in test_multipart.c verifying space enforcement with and without M.2 drive
521 lines
21 KiB
C
521 lines
21 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "multipart.h"
|
|
#include "pkg_parser.h"
|
|
#include "pkg_scanner.h"
|
|
#include "installer.h"
|
|
#include "smb_client.h"
|
|
#include "test_fixture.h"
|
|
|
|
/* Shared synthetic source package (80MB -> 2x60M / 3x35M / 2x50M parts).
|
|
* Fictional ID/title; generated once in main(). */
|
|
#define MP_SRC_PKG "/tmp/test_mpkg_src/nd.pkg"
|
|
|
|
static void test_multipart_creation_and_decompression(void) {
|
|
printf("=== 1. Testing Multi-Part Creation & Bit-for-Bit Decompression ===\n");
|
|
|
|
system("rm -rf /tmp/test_mpkg && mkdir -p /tmp/test_mpkg/parts");
|
|
|
|
/* Split test PKG into 60MB raw uncompressed sequential parts */
|
|
int comp_ret = system("python3 tools/pkg_split.py " MP_SRC_PKG " -o /tmp/test_mpkg/parts -s 60M > /dev/null 2>&1");
|
|
assert(comp_ret == 0);
|
|
|
|
/* Verify headers */
|
|
const char *p1 = "/tmp/test_mpkg/parts/nd.pkg.part1";
|
|
const char *p2 = "/tmp/test_mpkg/parts/nd.pkg.part2";
|
|
|
|
multipart_header_t h1, h2;
|
|
assert(multipart_read_header(p1, &h1) == 0);
|
|
assert(multipart_read_header(p2, &h2) == 0);
|
|
|
|
assert(h1.part_index == 1);
|
|
assert(h1.total_parts == 2);
|
|
assert(h1.compression_type == MULTIPART_COMPRESSION_NONE);
|
|
assert(h2.part_index == 2);
|
|
assert(h2.total_parts == 2);
|
|
assert(h2.compression_type == MULTIPART_COMPRESSION_NONE);
|
|
assert(strcmp(h1.title_id, "PPSA90010") == 0);
|
|
assert(strcmp(h1.title_name, "NebulaDrift") == 0);
|
|
assert(h1.icon_offset == 4096);
|
|
assert(h1.icon_size == FIXTURE_ICON_SIZE);
|
|
assert(memcmp(h1.package_uuid, h2.package_uuid, 16) == 0);
|
|
|
|
/* Decompress and verify bit-for-bit equality */
|
|
const char *part_paths[] = { p1, p2 };
|
|
volatile int cancel = 0;
|
|
int d_ret = multipart_decompress_to_pkg(part_paths, 2, "/tmp/test_mpkg/unpacked.pkg", NULL, NULL, &cancel);
|
|
assert(d_ret == 0);
|
|
|
|
int diff_res = system("diff -q /tmp/test_mpkg/unpacked.pkg " MP_SRC_PKG);
|
|
assert(diff_res == 0);
|
|
printf(" -> Decompressed PKG matches original exactly!\n");
|
|
}
|
|
|
|
static void test_scanner_multipart_detection(void) {
|
|
printf("=== 2. Testing Scanner Multi-Part Detection & Disc Part Search ===\n");
|
|
|
|
/* Create mock disc directory */
|
|
system("rm -rf /tmp/test_disc_dir && mkdir -p /tmp/test_disc_dir/pkg");
|
|
system("cp /tmp/test_mpkg/parts/nd.pkg.part1 /tmp/test_disc_dir/pkg/");
|
|
|
|
setenv("PKG_SCAN_DIR", "/tmp/test_disc_dir/pkg", 1);
|
|
pkg_scanner_init();
|
|
int count = pkg_scanner_scan();
|
|
printf(" Scanned count with Part 1: %d\n", count);
|
|
assert(count == 1);
|
|
|
|
pkg_detail_t detail;
|
|
assert(pkg_scanner_get_at(0, &detail) == 0);
|
|
assert(detail.is_multipart == 1);
|
|
assert(detail.part_index == 1);
|
|
assert(detail.total_parts == 2);
|
|
assert(strcmp(detail.title_id, "PPSA90010") == 0);
|
|
assert(strcmp(detail.title_name, "NebulaDrift") == 0);
|
|
assert(detail.has_icon == 1);
|
|
|
|
/* Verify JSON output includes multipart metadata */
|
|
char *json = pkg_scanner_to_json();
|
|
assert(strstr(json, "\"is_multipart\":true") != NULL);
|
|
assert(strstr(json, "\"total_parts\":2") != NULL);
|
|
assert(strstr(json, "\"part_index\":1") != NULL);
|
|
free(json);
|
|
|
|
/* Now copy Part 2 to disc directory as well */
|
|
system("cp /tmp/test_mpkg/parts/nd.pkg.part2 /tmp/test_disc_dir/pkg/");
|
|
count = pkg_scanner_scan();
|
|
printf(" Scanned count with Part 1 and Part 2: %d\n", count);
|
|
/* Part 2 should NOT be listed as a separate package! */
|
|
assert(count == 1);
|
|
|
|
/* Test finding Part 2 with pkg_scanner_find_part */
|
|
multipart_header_t h1;
|
|
multipart_read_header("/tmp/test_mpkg/parts/nd.pkg.part1", &h1);
|
|
char found_part2[512] = {0};
|
|
int find_res = pkg_scanner_find_part(h1.package_uuid, h1.pkg_filename, 2, found_part2, sizeof(found_part2));
|
|
assert(find_res == 0);
|
|
assert(strstr(found_part2, "part2") != NULL);
|
|
printf(" -> Found Part 2 at: %s\n", found_part2);
|
|
|
|
/* Test case-insensitive part filename matching */
|
|
uint32_t pnum = 0;
|
|
assert(multipart_is_part_filename("game.pkg.part2", 2, &pnum) == 1 && pnum == 2);
|
|
assert(multipart_is_part_filename("game.pkg.PART2", 2, &pnum) == 1 && pnum == 2);
|
|
assert(multipart_is_part_filename("game.PKG.Part02", 2, &pnum) == 1 && pnum == 2);
|
|
assert(multipart_is_part_filename("game.part2", 2, &pnum) == 1 && pnum == 2);
|
|
assert(multipart_is_part_filename("game.part2.pkg", 2, &pnum) == 1 && pnum == 2);
|
|
assert(multipart_is_part_filename("part_2.pkg.part", 2, &pnum) == 1 && pnum == 2);
|
|
assert(multipart_is_part_filename("game.pkg.part1", 2, &pnum) == 0);
|
|
assert(multipart_is_part_filename("game.pkg.part1", 0, &pnum) == 1 && pnum == 1);
|
|
assert(multipart_is_part_filename("movie.mp4", 2, &pnum) == 0);
|
|
assert(multipart_is_part_filename("game.pkg", 2, &pnum) == 0);
|
|
assert(multipart_is_part_filename("game.pkg", 0, &pnum) == 0);
|
|
|
|
/* Test finding Part 2 when renamed to uppercase .PKG.PART2 on disc */
|
|
system("mv /tmp/test_disc_dir/pkg/nd.pkg.part2 /tmp/test_disc_dir/pkg/nd.pkg.PKG.PART2");
|
|
char found_upper[512] = {0};
|
|
assert(pkg_scanner_find_part(h1.package_uuid, h1.pkg_filename, 2, found_upper, sizeof(found_upper)) == 0);
|
|
printf(" -> Found uppercase Part 2 at: %s\n", found_upper);
|
|
assert(strstr(found_upper, "PART2") != NULL);
|
|
/* Restore lowercase for subsequent tests */
|
|
system("mv /tmp/test_disc_dir/pkg/nd.pkg.PKG.PART2 /tmp/test_disc_dir/pkg/nd.pkg.part2");
|
|
|
|
/* Testing non-existent part 3 returns -1 */
|
|
char found_part3[512] = {0};
|
|
assert(pkg_scanner_find_part(h1.package_uuid, h1.pkg_filename, 3, found_part3, sizeof(found_part3)) == -1);
|
|
}
|
|
|
|
static void test_space_check_and_dir_creation(void) {
|
|
printf("=== 3. Testing Storage Space Check & Recursive Directory Creation ===\n");
|
|
|
|
const char *tmp_dir = "/tmp/pkg_installer_test_space/nested/tmp";
|
|
setenv("PKG_TMP_DIR", tmp_dir, 1);
|
|
|
|
/* 1. Space check failure simulation (enforced when no M.2 drive is present) */
|
|
unsetenv("PKG_EXT1_DIR");
|
|
setenv("PKG_FORCE_SPACE_CHECK_FAIL", "1", 1);
|
|
installer_init("http://127.0.0.1:8085/");
|
|
|
|
int res_fail = installer_start("/tmp/test_mpkg/parts/nd.pkg.part1");
|
|
printf(" Space check failure result (no M.2): %d\n", res_fail);
|
|
assert(res_fail == -10);
|
|
|
|
installer_shutdown();
|
|
unsetenv("PKG_FORCE_SPACE_CHECK_FAIL");
|
|
|
|
struct stat st;
|
|
assert(stat(tmp_dir, &st) != 0);
|
|
|
|
/* 1b. Space check failure simulation when M.2 NVMe SSD is present but neither drive has enough space */
|
|
setenv("PKG_FORCE_SPACE_CHECK_FAIL", "1", 1);
|
|
setenv("PKG_FORCE_NVME_SPACE_FAIL", "1", 1);
|
|
installer_init("http://127.0.0.1:8085/");
|
|
|
|
int res_both_fail = installer_start("/tmp/test_mpkg/parts/nd.pkg.part1");
|
|
printf(" Space check failure result (both internal and M.2 insufficient): %d\n", res_both_fail);
|
|
assert(res_both_fail == -10);
|
|
|
|
installer_shutdown();
|
|
unsetenv("PKG_FORCE_NVME_SPACE_FAIL");
|
|
unsetenv("PKG_FORCE_SPACE_CHECK_FAIL");
|
|
|
|
/* 1c. Space check pass simulation when M.2 NVMe SSD is present and has space */
|
|
system("mkdir -p /tmp/mock_nvme_test");
|
|
setenv("PKG_EXT1_DIR", "/tmp/mock_nvme_test", 1);
|
|
setenv("PKG_FORCE_SPACE_CHECK_FAIL", "1", 1);
|
|
installer_init("http://127.0.0.1:8085/");
|
|
|
|
int res_nvme = installer_start("/tmp/test_mpkg/parts/nd.pkg.part1");
|
|
printf(" Space check result with M.2 present: %d\n", res_nvme);
|
|
assert(res_nvme == 0); /* Must bypass space check when M.2 has space */
|
|
|
|
for (int i = 0; i < 50; i++) {
|
|
installer_status_t cur_st;
|
|
installer_get_status(&cur_st);
|
|
if (cur_st.is_installing) break;
|
|
usleep(10000);
|
|
}
|
|
installer_cancel();
|
|
installer_shutdown();
|
|
unsetenv("PKG_FORCE_SPACE_CHECK_FAIL");
|
|
unsetenv("PKG_EXT1_DIR");
|
|
system("rm -rf /tmp/mock_nvme_test");
|
|
usleep(200000);
|
|
|
|
|
|
/* 2. Normal directory creation on valid start */
|
|
installer_init("http://127.0.0.1:8085/");
|
|
int res = installer_start("/tmp/test_mpkg/parts/nd.pkg.part1");
|
|
assert(res == 0);
|
|
|
|
/* Verify nested tmp directory was created recursively */
|
|
assert(stat(tmp_dir, &st) == 0);
|
|
assert(S_ISDIR(st.st_mode));
|
|
printf(" -> Successfully verified directory creation at: %s\n", tmp_dir);
|
|
|
|
for (int i = 0; i < 50; i++) {
|
|
installer_status_t cur_st;
|
|
installer_get_status(&cur_st);
|
|
if (cur_st.is_installing) break;
|
|
usleep(10000);
|
|
}
|
|
installer_cancel();
|
|
installer_shutdown();
|
|
usleep(300000);
|
|
system("rm -rf /tmp/pkg_installer_test_space");
|
|
unsetenv("PKG_TMP_DIR");
|
|
}
|
|
|
|
static void test_end_to_end_disc_swapping(void) {
|
|
printf("=== 4. Testing End-to-End Disc Swapping Installation Pipeline ===\n");
|
|
|
|
/* Disc scan dir will only have Part 1 initially */
|
|
system("rm -rf /tmp/test_e2e_disc && mkdir -p /tmp/test_e2e_disc/pkg /tmp/holding_disc2 /tmp/e2e_tmp");
|
|
system("cp /tmp/test_mpkg/parts/nd.pkg.part1 /tmp/test_e2e_disc/pkg/");
|
|
system("cp /tmp/test_mpkg/parts/nd.pkg.part2 /tmp/holding_disc2/");
|
|
|
|
setenv("PKG_SCAN_DIR", "/tmp/test_e2e_disc/pkg", 1);
|
|
setenv("PKG_TMP_DIR", "/tmp/e2e_tmp", 1);
|
|
|
|
installer_init("http://127.0.0.1:8085/");
|
|
|
|
/* Start multi-part installation with Part 1 */
|
|
int ret = installer_start("/tmp/test_e2e_disc/pkg/nd.pkg.part1");
|
|
assert(ret == 0);
|
|
|
|
installer_status_t st;
|
|
installer_get_status(&st);
|
|
assert(st.is_installing == 1);
|
|
assert(st.is_multipart == 1);
|
|
assert(st.total_parts == 2);
|
|
|
|
/* Wait for Part 1 to finish copying and enter waiting_disc state */
|
|
printf(" Waiting for Part 1 to copy and prompt for Disc 2...\n");
|
|
int waited_ms = 0;
|
|
while (waited_ms < 10000) {
|
|
installer_get_status(&st);
|
|
if (st.waiting_for_disc && strcmp(st.status_str, "waiting_disc") == 0) {
|
|
break;
|
|
}
|
|
usleep(100000); /* 100ms */
|
|
waited_ms += 100;
|
|
}
|
|
|
|
assert(st.waiting_for_disc == 1);
|
|
assert(st.current_part == 2);
|
|
assert(strstr(st.prompt_message, "Disc 2") != NULL);
|
|
printf(" -> Disc prompt verified: '%s'\n", st.prompt_message);
|
|
|
|
char *json = installer_status_to_json();
|
|
assert(strstr(json, "\"waiting_for_disc\":true") != NULL);
|
|
assert(strstr(json, "\"current_part\":2") != NULL);
|
|
free(json);
|
|
|
|
/* Simulate user inserting Disc 2: move Part 2 into disc directory */
|
|
printf(" Simulating Disc 2 insertion into drive...\n");
|
|
system("cp /tmp/holding_disc2/nd.pkg.part2 /tmp/test_e2e_disc/pkg/");
|
|
|
|
/* Wait for installer to automatically detect Disc 2, copy it, decompress, and complete install */
|
|
printf(" Waiting for automatic detection, copy, decompression & install completion...\n");
|
|
waited_ms = 0;
|
|
while (waited_ms < 60000) {
|
|
installer_record_poll(); /* Simulate active browser polling */
|
|
installer_get_status(&st);
|
|
if (waited_ms % 2000 == 0) {
|
|
printf(" [+%.1fs] status='%s', is_installing=%d, completed=%d, progress=%.1f%%\n",
|
|
waited_ms / 1000.0, st.status_str, st.is_installing, st.completed, st.progress_percent);
|
|
}
|
|
if (st.completed && !st.is_installing) {
|
|
break;
|
|
}
|
|
usleep(200000); /* 200ms */
|
|
waited_ms += 200;
|
|
}
|
|
|
|
printf(" Final state: completed=%d, is_installing=%d, status='%s'\n",
|
|
st.completed, st.is_installing, st.status_str);
|
|
assert(st.completed == 1);
|
|
assert(st.is_installing == 0);
|
|
assert(st.failed == 0);
|
|
assert(st.progress_percent == 100.0f);
|
|
assert(strcmp(st.status_str, "playable") == 0);
|
|
printf(" -> Multi-disc installation completed successfully!\n");
|
|
|
|
/* Verify temporary files were cleaned up to save space */
|
|
struct stat st_check;
|
|
assert(stat("/tmp/e2e_tmp/part_1.pkg.part", &st_check) != 0);
|
|
assert(stat("/tmp/e2e_tmp/part_2.pkg.part", &st_check) != 0);
|
|
assert(stat("/tmp/e2e_tmp/nd.pkg", &st_check) != 0);
|
|
printf(" -> Temporary files in /tmp/e2e_tmp cleaned up successfully!\n");
|
|
|
|
installer_shutdown();
|
|
system("rm -rf /tmp/test_e2e_disc /tmp/holding_disc2 /tmp/e2e_tmp /tmp/test_disc_dir /tmp/test_mpkg");
|
|
}
|
|
|
|
static void test_uncompressed_multipart_and_crc(void) {
|
|
printf("=== 5. Testing Uncompressed Multi-Part with CRC32 Verification ===\n");
|
|
|
|
system("rm -rf /tmp/test_mpkg_none && mkdir -p /tmp/test_mpkg_none/parts");
|
|
int comp_ret = system("python3 tools/pkg_split.py " MP_SRC_PKG " -o /tmp/test_mpkg_none/parts -s 35M > /dev/null 2>&1");
|
|
assert(comp_ret == 0);
|
|
|
|
const char *p1 = "/tmp/test_mpkg_none/parts/nd.pkg.part1";
|
|
const char *p2 = "/tmp/test_mpkg_none/parts/nd.pkg.part2";
|
|
const char *p3 = "/tmp/test_mpkg_none/parts/nd.pkg.part3";
|
|
|
|
multipart_header_t h1;
|
|
assert(multipart_read_header(p1, &h1) == 0);
|
|
assert(h1.compression_type == MULTIPART_COMPRESSION_NONE);
|
|
assert(h1.total_parts == 3);
|
|
|
|
const char *parts[] = { p1, p2, p3 };
|
|
volatile int cancel = 0;
|
|
int d_ret = multipart_decompress_to_pkg(parts, 3, "/tmp/test_mpkg_none/unpacked.pkg", NULL, NULL, &cancel);
|
|
assert(d_ret == 0);
|
|
|
|
int diff_res = system("diff -q /tmp/test_mpkg_none/unpacked.pkg " MP_SRC_PKG);
|
|
assert(diff_res == 0);
|
|
printf(" -> Uncompressed multi-part reassembled and verified bit-for-bit against original!\n");
|
|
system("rm -rf /tmp/test_mpkg_none");
|
|
}
|
|
|
|
static void test_default_part_size_under_25gb(void) {
|
|
printf("=== 6. Testing Default Part Size Fits Standard 25GB BD-R ===\n");
|
|
int ret = system("python3 -c \"import sys; sys.path.insert(0, 'tools'); import pkg_split; "
|
|
"p = pkg_split.parse_size('23G'); "
|
|
"assert p <= 25000000000, f'Size {p} exceeds BD-25 limit!'; "
|
|
"print(f'Default part size: {p:,} bytes (< 25,000,000,000 bytes BD-25 capacity)')\"");
|
|
assert(ret == 0);
|
|
printf(" -> Default part size verified to fit on BD-R 25GB discs!\n");
|
|
}
|
|
|
|
static void test_out_of_order_disc_and_cancellation(void) {
|
|
printf("=== 7. Testing Out-of-Order Disc Detection & Installation Cancellation ===\n");
|
|
|
|
/* Create 3 parts */
|
|
system("rm -rf /tmp/test_ooo && mkdir -p /tmp/test_ooo/parts /tmp/test_ooo/disc /tmp/test_ooo/tmp");
|
|
/* Small single-part package for the post-cancel restart check. */
|
|
assert(fixture_write_ps5_pkg("/tmp/test_ooo/single.pkg",
|
|
"PPSA90012", "WaveCast", "gd", "01.003.000", 0) == 0);
|
|
|
|
int comp_ret = system("python3 tools/pkg_split.py " MP_SRC_PKG " -o /tmp/test_ooo/parts -s 35M > /dev/null 2>&1");
|
|
assert(comp_ret == 0);
|
|
|
|
/* Disc initially has Part 1 */
|
|
system("cp /tmp/test_ooo/parts/nd.pkg.part1 /tmp/test_ooo/disc/");
|
|
|
|
setenv("PKG_SCAN_DIR", "/tmp/test_ooo/disc", 1);
|
|
setenv("PKG_TMP_DIR", "/tmp/test_ooo/tmp", 1);
|
|
|
|
installer_init("http://127.0.0.1:8085/");
|
|
int ret = installer_start("/tmp/test_ooo/disc/nd.pkg.part1");
|
|
assert(ret == 0);
|
|
|
|
/* Wait for Part 1 to copy and prompt for Disc 2 */
|
|
installer_status_t st;
|
|
int waited_ms = 0;
|
|
while (waited_ms < 10000) {
|
|
installer_get_status(&st);
|
|
if (st.waiting_for_disc && st.current_part == 2) break;
|
|
usleep(100000);
|
|
waited_ms += 100;
|
|
}
|
|
assert(st.waiting_for_disc == 1);
|
|
assert(st.current_part == 2);
|
|
printf(" Waiting for Disc 2 prompt verified.\n");
|
|
|
|
/* 1. Simulate inserting Disc 3 (out of order!) */
|
|
printf(" Simulating inserting Disc 3 when Disc 2 was expected...\n");
|
|
system("rm -f /tmp/test_ooo/disc/* && cp /tmp/test_ooo/parts/nd.pkg.part3 /tmp/test_ooo/disc/");
|
|
|
|
/* Wait for scanner to detect wrong disc */
|
|
waited_ms = 0;
|
|
while (waited_ms < 5000) {
|
|
installer_get_status(&st);
|
|
if (strstr(st.prompt_message, "Disc 3 detected") != NULL) break;
|
|
usleep(100000);
|
|
waited_ms += 100;
|
|
}
|
|
printf(" -> Out-of-order prompt: '%s'\n", st.prompt_message);
|
|
assert(strstr(st.prompt_message, "Disc 3 detected") != NULL);
|
|
assert(strstr(st.prompt_message, "Disc 2") != NULL);
|
|
|
|
/* 2. Test Cancellation */
|
|
printf(" Testing installer_cancel()...\n");
|
|
int cancel_res = installer_cancel();
|
|
assert(cancel_res == 0);
|
|
|
|
installer_get_status(&st);
|
|
assert(st.is_installing == 0);
|
|
assert(st.failed == 1);
|
|
assert(strcmp(st.status_str, "canceled") == 0);
|
|
printf(" -> Installation successfully canceled! Status: '%s'\n", st.status_str);
|
|
|
|
/* Verify temporary directory cleaned up */
|
|
usleep(200000);
|
|
struct stat st_tmp;
|
|
assert(stat("/tmp/test_ooo/tmp/part_1.pkg.part", &st_tmp) != 0);
|
|
|
|
installer_shutdown();
|
|
|
|
/* Verify that after cancellation, a new install can start without being blocked */
|
|
installer_init("http://127.0.0.1:8085/");
|
|
int new_start = installer_start("/tmp/test_ooo/single.pkg");
|
|
assert(new_start == 0);
|
|
installer_shutdown();
|
|
printf(" -> Verified new install can start immediately after cancellation!\n");
|
|
|
|
system("rm -rf /tmp/test_ooo");
|
|
}
|
|
|
|
static void test_smb_multipart_rejected_and_local_streaming(void) {
|
|
printf("=== 8. Testing SMB Multi-Part Rejection & Local Virtual Streaming ===\n");
|
|
|
|
system("rm -rf /tmp/mock_smb && mkdir -p /tmp/mock_smb/storage/Games/PS4");
|
|
|
|
/* Split test PKG into 50MB parts inside mock SMB share */
|
|
int comp_ret = system("python3 tools/pkg_split.py " MP_SRC_PKG " -o /tmp/mock_smb/storage/Games/PS4 -s 50M > /dev/null 2>&1");
|
|
assert(comp_ret == 0);
|
|
|
|
const char *smb_url_p1 = "smb://192.168.1.140/16tb01/storage/Games/PS4/nd.pkg.part1";
|
|
|
|
/* 1. Verify multipart_read_header rejects SMB URLs (multi-part is strictly local USB/disc) */
|
|
multipart_header_t hdr1;
|
|
int r_ret = multipart_read_header(smb_url_p1, &hdr1);
|
|
assert(r_ret == -1);
|
|
printf(" -> multipart_read_header rejected SMB URL as expected\n");
|
|
|
|
/* 2. Verify smb_client_parse_pkg rejects PS5MPKG1 magic */
|
|
pkg_detail_t detail;
|
|
int parse_ret = smb_client_parse_pkg(smb_url_p1, &detail);
|
|
assert(parse_ret == -1);
|
|
printf(" -> smb_client_parse_pkg rejected PS5MPKG1 magic as expected\n");
|
|
|
|
/* 3. Verify virtual_stream_open on SMB path is treated as single file (is_multipart = 0) */
|
|
virtual_stream_t vs_smb;
|
|
int open_ret = virtual_stream_open(smb_url_p1, &vs_smb);
|
|
assert(open_ret == 0);
|
|
assert(vs_smb.is_multipart == 0);
|
|
assert(vs_smb.is_smb == 1);
|
|
assert(vs_smb.total_parts == 1);
|
|
virtual_stream_close(&vs_smb);
|
|
printf(" -> virtual_stream_open treated SMB path as single part\n");
|
|
|
|
/* 4. Verify installer_start gates SMB multi-part packages */
|
|
installer_init("http://127.0.0.1:8085/");
|
|
int inst_ret = installer_start(smb_url_p1);
|
|
assert(inst_ret == -13);
|
|
installer_shutdown();
|
|
printf(" -> installer_start gated SMB multi-part with -13 (USB/Disc only)\n");
|
|
|
|
system("rm -rf /tmp/mock_smb");
|
|
|
|
/* 5. Verify local multi-part virtual streaming works seamlessly */
|
|
system("rm -rf /tmp/test_mpkg_local && mkdir -p /tmp/test_mpkg_local/parts");
|
|
comp_ret = system("python3 tools/pkg_split.py " MP_SRC_PKG " -o /tmp/test_mpkg_local/parts -s 60M > /dev/null 2>&1");
|
|
assert(comp_ret == 0);
|
|
|
|
const char *local_p1 = "/tmp/test_mpkg_local/parts/nd.pkg.part1";
|
|
virtual_stream_t vs_local;
|
|
int loc_open = virtual_stream_open(local_p1, &vs_local);
|
|
assert(loc_open == 0);
|
|
assert(vs_local.is_multipart == 1);
|
|
assert(vs_local.is_smb == 0);
|
|
assert(vs_local.total_parts == 2);
|
|
printf(" -> Local virtual_stream_open resolved all parts successfully\n");
|
|
|
|
/* Verify stream read across part boundary matches original file */
|
|
int orig_fd = open(MP_SRC_PKG, O_RDONLY);
|
|
assert(orig_fd >= 0);
|
|
|
|
uint8_t buf_stream[4096];
|
|
uint8_t buf_orig[4096];
|
|
ssize_t n1 = virtual_stream_read(&vs_local, 0, buf_stream, sizeof(buf_stream));
|
|
assert(n1 == sizeof(buf_stream));
|
|
assert(pread(orig_fd, buf_orig, sizeof(buf_orig), 0) == sizeof(buf_orig));
|
|
assert(memcmp(buf_stream, buf_orig, sizeof(buf_stream)) == 0);
|
|
|
|
uint64_t boundary = vs_local.parts[0].part_data_size;
|
|
ssize_t n2 = virtual_stream_read(&vs_local, boundary, buf_stream, 1024);
|
|
assert(n2 == 1024);
|
|
assert(pread(orig_fd, buf_orig, 1024, (off_t)boundary) == 1024);
|
|
assert(memcmp(buf_stream, buf_orig, 1024) == 0);
|
|
|
|
close(orig_fd);
|
|
virtual_stream_close(&vs_local);
|
|
system("rm -rf /tmp/test_mpkg_local");
|
|
printf(" -> Local virtual_stream_read bit-for-bit verified across boundaries!\n");
|
|
}
|
|
|
|
int main(void) {
|
|
printf("==============================================\n");
|
|
printf(">>> RUNNING MULTI-PART & BLU-RAY DISC TESTS <<<\n");
|
|
printf("==============================================\n");
|
|
|
|
test_default_part_size_under_25gb();
|
|
|
|
/* Shared synthetic source package (no external files needed). */
|
|
system("rm -rf /tmp/test_mpkg_src && mkdir -p /tmp/test_mpkg_src");
|
|
assert(fixture_write_ps5_pkg(MP_SRC_PKG,
|
|
"PPSA90010", "NebulaDrift", "gd", "06.000.000", 1) == 0);
|
|
assert(fixture_grow_file(MP_SRC_PKG, 80ULL * 1024 * 1024) == 0);
|
|
|
|
test_multipart_creation_and_decompression();
|
|
test_scanner_multipart_detection();
|
|
test_space_check_and_dir_creation();
|
|
test_end_to_end_disc_swapping();
|
|
test_uncompressed_multipart_and_crc();
|
|
test_out_of_order_disc_and_cancellation();
|
|
test_smb_multipart_rejected_and_local_streaming();
|
|
|
|
printf("\n==============================================\n");
|
|
printf(">>> ALL MULTI-PART & DISC TESTS PASSED! <<<\n");
|
|
printf("==============================================\n");
|
|
return 0;
|
|
}
|