first commit
This commit is contained in:
@@ -0,0 +1,514 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const stego = require("../src/stego/engine");
|
||||
|
||||
function createPixels(width, height) {
|
||||
const pixels =
|
||||
Buffer.alloc(
|
||||
width * height * 4
|
||||
);
|
||||
|
||||
// Give the test image valid RGBA values.
|
||||
for (
|
||||
let i = 0;
|
||||
i < pixels.length;
|
||||
i += 4
|
||||
) {
|
||||
pixels[i] = 128;
|
||||
pixels[i + 1] = 128;
|
||||
pixels[i + 2] = 128;
|
||||
pixels[i + 3] = 255;
|
||||
}
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
|
||||
test(
|
||||
"encodes and decodes a text message",
|
||||
async () => {
|
||||
const width = 100;
|
||||
const height = 100;
|
||||
|
||||
const pixels =
|
||||
createPixels(
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
const originalMessage =
|
||||
"kokiiii is the best :3";
|
||||
|
||||
const password =
|
||||
"kokithebestie";
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
message:
|
||||
originalMessage,
|
||||
password
|
||||
});
|
||||
|
||||
const decoded =
|
||||
await stego.decode({
|
||||
pixels:
|
||||
encoded.pixels,
|
||||
password
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
decoded.type,
|
||||
"message"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
decoded.message,
|
||||
originalMessage
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"encodes and decodes a file",
|
||||
async () => {
|
||||
const width = 200;
|
||||
const height = 200;
|
||||
|
||||
const pixels =
|
||||
createPixels(
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
const originalFile =
|
||||
Buffer.from(
|
||||
"This is a secret file.\n"
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
password:
|
||||
"test-password",
|
||||
file: {
|
||||
originalname:
|
||||
"secret.txt",
|
||||
mimetype:
|
||||
"text/plain",
|
||||
buffer:
|
||||
originalFile
|
||||
}
|
||||
});
|
||||
|
||||
const decoded =
|
||||
await stego.decode({
|
||||
pixels:
|
||||
encoded.pixels,
|
||||
password:
|
||||
"test-password"
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
decoded.type,
|
||||
"file"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
decoded.filename,
|
||||
"secret.txt"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
decoded.mimetype,
|
||||
"text/plain"
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
decoded.file,
|
||||
originalFile
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"rejects an incorrect password",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width: 100,
|
||||
height: 100,
|
||||
message:
|
||||
"secret message",
|
||||
password:
|
||||
"correct-password"
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
stego.decode({
|
||||
pixels:
|
||||
encoded.pixels,
|
||||
password:
|
||||
"wrong-password"
|
||||
}),
|
||||
{
|
||||
message:
|
||||
"Invalid password or corrupted payload"
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"requires a password for encrypted payloads",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width: 100,
|
||||
height: 100,
|
||||
message:
|
||||
"encrypted secret",
|
||||
password:
|
||||
"secret-password"
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
stego.decode({
|
||||
pixels:
|
||||
encoded.pixels
|
||||
}),
|
||||
{
|
||||
message:
|
||||
"Password is required for this payload"
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"rejects a missing password during encrypted encoding",
|
||||
async () => {
|
||||
/*
|
||||
* This test intentionally verifies that
|
||||
* passwordless encoding is now allowed.
|
||||
*
|
||||
* It should NOT reject.
|
||||
*/
|
||||
const pixels =
|
||||
createPixels(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width: 100,
|
||||
height: 100,
|
||||
message:
|
||||
"no password"
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
encoded.pixels
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"encodes and decodes a message without a password",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width: 100,
|
||||
height: 100,
|
||||
message:
|
||||
"passwordless secret"
|
||||
});
|
||||
|
||||
const decoded =
|
||||
await stego.decode({
|
||||
pixels:
|
||||
encoded.pixels
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
decoded.type,
|
||||
"message"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
decoded.message,
|
||||
"passwordless secret"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"encodes and decodes a file without a password",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
200,
|
||||
200
|
||||
);
|
||||
|
||||
const originalFile =
|
||||
Buffer.from(
|
||||
"passwordless file"
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width: 200,
|
||||
height: 200,
|
||||
file: {
|
||||
originalname:
|
||||
"secret.txt",
|
||||
mimetype:
|
||||
"text/plain",
|
||||
buffer:
|
||||
originalFile
|
||||
}
|
||||
});
|
||||
|
||||
const decoded =
|
||||
await stego.decode({
|
||||
pixels:
|
||||
encoded.pixels
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
decoded.type,
|
||||
"file"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
decoded.filename,
|
||||
"secret.txt"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
decoded.mimetype,
|
||||
"text/plain"
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
decoded.file,
|
||||
originalFile
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"rejects when neither message nor file is supplied",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
stego.encode({
|
||||
pixels,
|
||||
width: 100,
|
||||
height: 100,
|
||||
password:
|
||||
"password"
|
||||
}),
|
||||
{
|
||||
message:
|
||||
"Message or file is required"
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"rejects a payload that is too large",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
10,
|
||||
10
|
||||
);
|
||||
|
||||
const hugeMessage =
|
||||
"A".repeat(10000);
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
stego.encode({
|
||||
pixels,
|
||||
width: 10,
|
||||
height: 10,
|
||||
message:
|
||||
hugeMessage,
|
||||
password:
|
||||
"password"
|
||||
}),
|
||||
{
|
||||
message:
|
||||
/Payload too large/
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"reports image capacity",
|
||||
() => {
|
||||
const capacity =
|
||||
stego.getCapacity(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
capacity,
|
||||
3740
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"rejects a tampered image",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width: 100,
|
||||
height: 100,
|
||||
message:
|
||||
"secret message",
|
||||
password:
|
||||
"test-password"
|
||||
});
|
||||
|
||||
const tampered =
|
||||
Buffer.from(
|
||||
encoded.pixels
|
||||
);
|
||||
|
||||
/*
|
||||
* Flip a bit inside the embedded
|
||||
* payload.
|
||||
*/
|
||||
tampered[100] ^= 1;
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
stego.decode({
|
||||
pixels:
|
||||
tampered,
|
||||
password:
|
||||
"test-password"
|
||||
}),
|
||||
{
|
||||
message:
|
||||
"Invalid password or corrupted payload"
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
test(
|
||||
"rejects a corrupted payload",
|
||||
async () => {
|
||||
const pixels =
|
||||
createPixels(
|
||||
100,
|
||||
100
|
||||
);
|
||||
|
||||
const encoded =
|
||||
await stego.encode({
|
||||
pixels,
|
||||
width: 100,
|
||||
height: 100,
|
||||
message:
|
||||
"secret message",
|
||||
password:
|
||||
"test-password"
|
||||
});
|
||||
|
||||
const corrupted =
|
||||
Buffer.from(
|
||||
encoded.pixels
|
||||
);
|
||||
|
||||
/*
|
||||
* Corrupt several embedded bits
|
||||
* in the payload area.
|
||||
*/
|
||||
for (
|
||||
let i = 100;
|
||||
i < 110;
|
||||
i++
|
||||
) {
|
||||
corrupted[i] ^= 1;
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
stego.decode({
|
||||
pixels:
|
||||
corrupted,
|
||||
password:
|
||||
"test-password"
|
||||
}),
|
||||
{
|
||||
message:
|
||||
"Invalid password or corrupted payload"
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user