Files
qmc/qmc-c/test/test_qmc.c
T
2026-08-12 20:14:16 +08:00

194 lines
6.5 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "tea_cipher.h"
#include "static_cipher.h"
#include "map_cipher.h"
#include "rc4_cipher.h"
#include "key_derivation.h"
#include "qmc_decryptor.h"
static int tests_passed = 0;
static int tests_failed = 0;
#define TEST(name) do { printf(" %s ... ", name); } while(0)
#define PASS() do { puts("OK"); tests_passed++; } while(0)
#define FAIL(msg) do { puts("FAIL: " msg); tests_failed++; } while(0)
/* ---- TEA ---- */
static void test_tea_roundtrip(void) {
TEST("TEA roundtrip");
uint32_t key[] = {0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210};
uint32_t v[] = {0x12345678, 0x9ABCDEF0};
uint32_t orig[2]; memcpy(orig, v, 8);
tea_decrypt_block(v, key);
/* Can't easily verify without encrypt, just check it changed */
if (v[0] == orig[0] && v[1] == orig[1]) { FAIL("block unchanged"); return; }
PASS();
}
/* ---- Static Cipher ---- */
static void test_static_cipher(void) {
TEST("Static cipher roundtrip");
uint8_t buf[16];
memset(buf, 0xAB, 16);
uint8_t orig[16]; memcpy(orig, buf, 16);
static_decrypt(buf, 16, 0);
static_decrypt(buf, 16, 0);
if (memcmp(buf, orig, 16) != 0) { FAIL("not roundtrip"); return; }
PASS();
}
static void test_static_mask_values(void) {
TEST("Static mask values");
/* idx = (0+27)&0xFF = 27 → S_BOX[27] = 0xC3 */
if (static_mask(0) != 0xC3) { FAIL("mask(0)"); return; }
/* idx = (1+27)&0xFF = 28 → S_BOX[28] = 0x4A */
if (static_mask(1) != 0x4A) { FAIL("mask(1)"); return; }
PASS();
}
/* ---- Map Cipher ---- */
static void test_map_cipher(void) {
TEST("Map cipher roundtrip");
uint8_t key_data[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0};
uint8_t *k = malloc(8); memcpy(k, key_data, 8);
map_cipher_t c; map_cipher_init(&c, k, 8);
uint8_t buf[16]; memset(buf, 0xFF, 16);
uint8_t orig[16]; memcpy(orig, buf, 16);
map_decrypt(&c, buf, 16, 0);
map_decrypt(&c, buf, 16, 0);
if (memcmp(buf, orig, 16) != 0) { map_cipher_free(&c); FAIL("not roundtrip"); return; }
map_cipher_free(&c);
PASS();
}
/* ---- RC4 Cipher ---- */
static void test_rc4_cipher(void) {
TEST("RC4 cipher roundtrip");
uint8_t *key_data = malloc(256);
for (int i = 0; i < 256; i++) key_data[i] = (uint8_t)i;
rc4_cipher_t c; rc4_cipher_init(&c, key_data, 256);
uint8_t buf[128]; memset(buf, 0xAB, 128);
uint8_t orig[128]; memcpy(orig, buf, 128);
rc4_decrypt(&c, buf, 128, 0);
rc4_decrypt(&c, buf, 128, 0);
if (memcmp(buf, orig, 128) != 0) { rc4_cipher_free(&c); FAIL("not roundtrip"); return; }
rc4_cipher_free(&c);
PASS();
}
static void test_rc4_known(void) {
TEST("RC4 known key");
uint8_t *key_data = malloc(3);
key_data[0] = 0x4B; key_data[1] = 0x65; key_data[2] = 0x79;
rc4_cipher_t c; rc4_cipher_init(&c, key_data, 3);
const char *plain = "Plaintext";
size_t len = strlen(plain);
uint8_t *buf = malloc(len);
memcpy(buf, plain, len);
rc4_decrypt(&c, buf, len, 0);
/* RC4 is symmetric */
rc4_cipher_t c2; uint8_t *k2 = malloc(3); memcpy(k2, key_data, 3);
rc4_cipher_init(&c2, k2, 3);
rc4_decrypt(&c2, buf, len, 0);
rc4_cipher_free(&c2);
if (memcmp(buf, plain, len) != 0) { free(buf); rc4_cipher_free(&c); FAIL("not symmetric"); return; }
free(buf); rc4_cipher_free(&c);
PASS();
}
static void test_rc4_segment(void) {
TEST("RC4 segment roundtrip");
uint8_t *key_data = malloc(3);
key_data[0] = 0x4B; key_data[1] = 0x65; key_data[2] = 0x79;
rc4_cipher_t c; rc4_cipher_init(&c, key_data, 3);
const char *text = "This is a longer test string that spans multiple RC4 segments!";
size_t len = strlen(text);
uint8_t *buf = malloc(len);
memcpy(buf, text, len);
rc4_decrypt(&c, buf, len, 0);
rc4_cipher_t c2; uint8_t *k2 = malloc(3); memcpy(k2, key_data, 3);
rc4_cipher_init(&c2, k2, 3);
rc4_decrypt(&c2, buf, len, 0);
rc4_cipher_free(&c2);
if (memcmp(buf, text, len) != 0) { free(buf); rc4_cipher_free(&c); FAIL("not symmetric"); return; }
free(buf); rc4_cipher_free(&c);
PASS();
}
/* ---- Key Derivation ---- */
static void test_derive_key_short(void) {
TEST("derive_key invalid base64");
/* "aGVs" is "hel" in base64 - too short for valid ekey */
uint8_t *out = NULL; size_t out_len = 0;
int ret = derive_key("aGVsbG8=", &out, &out_len);
if (ret == 0) {
/* Might succeed or fail depending on input length, either is acceptable */
if (out_len > 0) {
free(out);
}
}
PASS();
}
/* ---- Real ekey test ---- */
static void test_real_ekey(void) {
TEST("Real ekey decryption");
FILE *ekey_f = fopen("f:/qqmusic_debug/test_ekey.txt", "r");
if (!ekey_f) { FAIL("cannot open ekey file (skip)"); return; }
char ekey[1024] = {0};
if (!fgets(ekey, sizeof(ekey), ekey_f)) { fclose(ekey_f); FAIL("read ekey"); return; }
fclose(ekey_f);
size_t ekey_len = strlen(ekey);
while (ekey_len > 0 && (ekey[ekey_len-1] == '\n' || ekey[ekey_len-1] == '\r')) {
ekey[--ekey_len] = '\0';
}
FILE *data_f = fopen("f:/qqmusic_debug/test_raw.mflac", "rb");
if (!data_f) { FAIL("cannot open data file (skip)"); return; }
fseek(data_f, 0, SEEK_END);
long data_len = ftell(data_f);
fseek(data_f, 0, SEEK_SET);
uint8_t *data = malloc((size_t)data_len);
if (fread(data, 1, (size_t)data_len, data_f) != (size_t)data_len) {
free(data); fclose(data_f); FAIL("read data"); return;
}
fclose(data_f);
uint8_t *out = NULL; size_t out_len = 0;
int ret = decrypt_mflac(data, (size_t)data_len, ekey, &out, &out_len);
free(data);
if (ret != 0) { FAIL("decrypt_mflac failed"); return; }
if (out_len < 4) { free(out); FAIL("output too short"); return; }
/* Check fLaC header */
if (out[0] != 0x66 || out[1] != 0x4C || out[2] != 0x61 || out[3] != 0x43) {
free(out); FAIL("not a FLAC header"); return;
}
printf("OK (%zu bytes)", out_len);
/* Save for verification */
FILE *out_f = fopen("f:/qqmusic_debug/decoded_c.flac", "wb");
if (out_f) { fwrite(out, 1, out_len, out_f); fclose(out_f); }
free(out);
tests_passed++;
}
int main(void) {
puts("QMC C Tests");
test_tea_roundtrip();
test_static_cipher();
test_static_mask_values();
test_map_cipher();
test_rc4_cipher();
test_rc4_known();
test_rc4_segment();
test_derive_key_short();
test_real_ekey();
printf("\n%d passed, %d failed\n", tests_passed, tests_failed);
return tests_failed > 0 ? 1 : 0;
}