Files
km b1c3ec3af4 Initial commit: OTA chunk transfer protocol implementation
- Protocol definition with 6-byte header (chunk_idx: 2B, payload: N B, crc32: 4B)
- Node.js server with chunk-based HTTP delivery
- C client library with resumable download support
- CRC32 implementation (IEEE 802.3)
- Test suites for both server and CRC32
- 1024B default chunk size (0.59% overhead)
2026-03-30 06:35:25 +09:00

216 lines
6.3 KiB
JavaScript

/**
* @file test_server.js
* @brief Server Test Suite
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const ChunkServer = require('../server/server');
const PORT = 18080;
const TEST_DIR = path.join(__dirname, 'test_files');
let passed = 0;
let failed = 0;
function assert(cond, msg) {
if (cond) {
console.log(`[PASS] ${msg}`);
passed++;
} else {
console.log(`[FAIL] ${msg}`);
failed++;
}
}
/**
* HTTP GET helper
*/
function httpRequest(options, expectedStatus = 200) {
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
let data = Buffer.alloc(0);
res.on('data', chunk => {
data = Buffer.concat([data, chunk]);
});
res.on('end', () => {
resolve({
statusCode: res.statusCode,
headers: res.headers,
body: data
});
});
});
req.on('error', reject);
req.end();
});
}
/**
* Setup test files
*/
function setupTestFiles() {
if (!fs.existsSync(TEST_DIR)) {
fs.mkdirSync(TEST_DIR);
}
// Create test file: 3072 bytes (3 chunks of 1024)
const testData = Buffer.alloc(3072);
for (let i = 0; i < 3072; i++) {
testData[i] = i % 256;
}
fs.writeFileSync(path.join(TEST_DIR, 'test.bin'), testData);
// Create small file: 100 bytes
const smallData = Buffer.alloc(100);
for (let i = 0; i < 100; i++) {
smallData[i] = 0x42;
}
fs.writeFileSync(path.join(TEST_DIR, 'small.bin'), smallData);
}
/**
* Cleanup test files
*/
function cleanupTestFiles() {
try {
fs.rmSync(TEST_DIR, { recursive: true, force: true });
} catch (e) {
// Ignore
}
}
/**
* Test suite
*/
async function runTests() {
console.log("========================================");
console.log(" Chunk Server Test Suite");
console.log("========================================\n");
// Setup
setupTestFiles();
const server = new ChunkServer({
port: PORT,
rootPath: TEST_DIR,
chunkSize: 1024
});
await server.start();
console.log("\n--- Server Tests ---");
try {
// Test 1: Get first chunk
const res1 = await httpRequest({
hostname: 'localhost',
port: PORT,
path: '/ota?file=test.bin',
method: 'GET'
});
assert(res1.statusCode === 200, "First chunk returns 200");
assert(res1.headers['content-type'] === 'application/octet-stream', "Content-Type is octet-stream");
assert(res1.headers['x-file-size'] === '3072', "X-File-Size header present");
assert(res1.headers['x-total-chunks'] === '3', "X-Total-Chunks header present");
assert(res1.body.length === 2 + 1024 + 4, "First chunk size correct (header + payload + crc)");
// Verify chunk index in payload
assert(res1.body.readUInt16LE(0) === 0, "Chunk index is 0");
// Test 2: Get second chunk
const res2 = await httpRequest({
hostname: 'localhost',
port: PORT,
path: '/ota?file=test.bin&chunk=1',
method: 'GET'
});
assert(res2.statusCode === 206, "Second chunk returns 206");
assert(res2.body.readUInt16LE(0) === 1, "Chunk index is 1");
assert(res2.body.length === 2 + 1024 + 4, "Second chunk size correct");
// Test 3: Get last chunk (smaller)
const res3 = await httpRequest({
hostname: 'localhost',
port: PORT,
path: '/ota?file=test.bin&chunk=2',
method: 'GET'
});
assert(res3.statusCode === 206, "Last chunk returns 206");
assert(res3.body.readUInt16LE(0) === 2, "Chunk index is 2");
assert(res3.body.length === 2 + 1024 + 4, "Last chunk size correct");
// Test 4: Invalid chunk index
const res4 = await httpRequest({
hostname: 'localhost',
port: PORT,
path: '/ota?file=test.bin&chunk=10',
method: 'GET'
});
assert(res4.statusCode === 416, "Invalid chunk returns 416");
// Test 5: File not found
const res5 = await httpRequest({
hostname: 'localhost',
port: PORT,
path: '/ota?file=nonexistent.bin',
method: 'GET'
});
assert(res5.statusCode === 404, "Missing file returns 404");
// Test 6: Missing file parameter
const res6 = await httpRequest({
hostname: 'localhost',
port: PORT,
path: '/ota',
method: 'GET'
});
assert(res6.statusCode === 400, "Missing file param returns 400");
// Test 7: Small file (single chunk)
const res7 = await httpRequest({
hostname: 'localhost',
port: PORT,
path: '/ota?file=small.bin',
method: 'GET'
});
assert(res7.statusCode === 200, "Small file returns 200");
assert(res7.headers['x-total-chunks'] === '1', "Small file has 1 chunk");
assert(res7.body.length === 2 + 100 + 4, "Small file chunk size correct");
// Test 8: Verify CRC32
const payload = res1.body.slice(2, 2 + 1024);
const receivedCrc = res1.body.readUInt32LE(2 + 1024);
const calculatedCrc = ChunkServer.crc32(payload);
console.log(`Received CRC: ${receivedCrc.toString(16)}`);
console.log(`Calculated CRC: ${calculatedCrc.toString(16)}`);
console.log(`Calculated >>>0: ${(calculatedCrc >>> 0).toString(16)}`);
assert(receivedCrc === (calculatedCrc >>> 0), "CRC32 verification passed");
} catch (err) {
console.error("Test error:", err);
failed++;
} finally {
await server.stop();
cleanupTestFiles();
}
console.log("\n========================================");
console.log(` Results: ${passed} passed, ${failed} failed`);
console.log("========================================");
process.exit(failed === 0 ? 0 : 1);
}
runTests().catch(err => {
console.error("Fatal error:", err);
process.exit(1);
});