Files
bitburner-src/test/jest/Darknet/Darknet.test.ts
2026-03-19 21:07:43 -07:00

842 lines
37 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
serverFactory,
getEchoVulnConfig,
getNoPasswordConfig,
getDefaultPasswordConfig,
getMastermindHintConfig,
encodeNumberInBaseN,
parseBaseNNumberString,
getConvertToBase10Config,
parseSimpleArithmeticExpression,
generateSimpleArithmeticExpression,
getLargestPrimeFactorConfig,
largePrimes,
getDivisibilityTestConfig,
getRomanNumeralConfig,
romanNumeralDecoder,
getBufferOverflowConfig,
getParseArithmeticExpressionConfig,
getBinaryEncodedConfig,
getTimingAttackConfig,
getSpiceLevelConfig,
getGuessNumberConfig,
getCaptchaConfig,
getYesn_tConfig,
cleanArithmeticExpression,
getXorMaskEncryptedPasswordConfig,
getTripleModuloConfig,
getKingOfTheHillConfig,
getSortedEchoVulnConfig,
} from "../../../src/DarkNet/controllers/ServerGenerator";
import {
commonPasswordDictionary,
connectors,
defaultSettingsDictionary,
l33t,
lettersLowercase,
lettersUppercase,
loreNames,
notebookFileNames,
numbers,
passwordFileNames,
presetNames,
ServerNamePrefixes,
ServerNameSuffixes,
} from "../../../src/DarkNet/models/dictionaryData";
import { getAuthResult, isCloseToCorrectPassword } from "../../../src/DarkNet/effects/authentication";
import { DarknetState } from "../../../src/DarkNet/models/DarknetState";
import { GenericResponseMessage, ResponseCodeEnum } from "../../../src/DarkNet/Enums";
import { expectWithMessage, getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
import { getClueFileName, getDarkscapeNavigator } from "../../../src/DarkNet/effects/effects";
import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert";
import * as UtilityModule from "../../../src/utils/Utility";
import { mutateDarknet } from "../../../src/DarkNet/controllers/NetworkMovement";
import { launchWebstorm } from "../../../src/DarkNet/effects/webstorm";
import { isNumber } from "../../../src/types";
import { getMostRecentAuthLog, getServerLogs } from "../../../src/DarkNet/models/packetSniffing";
import { Player } from "@player";
import { assertString } from "../../../src/utils/TypeAssertion";
import { assertPasswordResponse, generateDarknetServerName } from "../../../src/DarkNet/models/DarknetServerOptions";
import { DarknetServer } from "../../../src/Server/DarknetServer";
import { isDirectoryPath } from "../../../src/Paths/Directory";
import { isFilePath } from "../../../src/Paths/FilePath";
import { LAB_CACHE_NAME } from "../../../src/DarkNet/effects/labyrinth";
import { generateCacheFilename } from "../../../src/DarkNet/effects/cacheFiles";
import { getAllDarknetServers } from "../../../src/DarkNet/utils/darknetNetworkUtils";
import { prestigeAugmentation } from "../../../src/Prestige";
beforeAll(() => {
initGameEnvironment();
});
beforeEach(() => {
setupBasicTestingEnvironment();
getDarkscapeNavigator();
});
afterEach(() => {
jest.clearAllMocks();
});
const getLatestResponseTime = (server: DarknetServer) => {
const lastPasswordResponse = getServerLogs(server, 1, true)[0].message;
assertPasswordResponse(lastPasswordResponse);
assertString(lastPasswordResponse.data);
const timeMatch = lastPasswordResponse.data.match(/Response time: (\d+\.?\d*)ms/);
if (!timeMatch) {
throw new Error(`No response time found in log: ${JSON.stringify(lastPasswordResponse)}`);
}
const responseTime = Number(timeMatch[1]);
if (!Number.isFinite(responseTime)) {
throw new Error(`No response time found in log: ${JSON.stringify(lastPasswordResponse)}`);
}
return responseTime;
};
describe("Password Tests", () => {
const difficulty = 1;
test("getEchoVulnServer creates a server and checks password correctly", () => {
const server = serverFactory(getEchoVulnConfig, difficulty, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
expect(failedAttemptResponse.response.message.includes(server.password)).toBe(true);
expect(server.hasAdminRights).toBe(false);
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
expect(server.hasAdminRights).toBe(true);
});
test("getNoPasswordServer creates a server with no password", () => {
const server = serverFactory(getNoPasswordConfig, difficulty, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
expect(server.hasAdminRights).toBe(false);
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
expect(server.hasAdminRights).toBe(true);
});
test("getDefaultPasswordServer creates a server with default password", () => {
const server = serverFactory(getDefaultPasswordConfig, difficulty, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
expect(server.hasAdminRights).toBe(false);
expect((defaultSettingsDictionary as unknown as string[]).includes(server.password)).toBe(true);
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
expect(server.hasAdminRights).toBe(true);
});
test("getMastermindHintServer creates a server with mastermind hint", () => {
const password = "11223334";
const server = serverFactory(getMastermindHintConfig, difficulty, 0, 0);
server.password = password;
expect(server).toBeDefined();
const getData = () => {
const authLog = getMostRecentAuthLog(server.hostname);
if (!authLog) {
throw new Error(`Cannot find the most recent auth log in ${server.hostname}`);
}
if (!authLog.data) {
throw new Error(`Auth log does not contain data: ${JSON.stringify(authLog)}`);
}
assertString(authLog.data);
return authLog.data.split(",").map((item) => item.trim());
};
const failedAttemptResponse1 = getAuthResult(server, "");
expect(failedAttemptResponse1.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(server.hasAdminRights).toBe(false);
const [correctCount1, closeCount1] = getData();
expect(correctCount1).toBe("0");
expect(closeCount1).toBe("0");
const failedAttemptResponse2 = getAuthResult(server, "123");
expect(failedAttemptResponse2.response.code).toBe(ResponseCodeEnum.AuthFailure);
const [correctCount2, closeCount2] = getData();
expect(correctCount2).toBe("1");
expect(closeCount2).toBe("2");
const failedAttemptResponse3 = getAuthResult(server, "11111111");
expect(failedAttemptResponse3.response.code).toBe(ResponseCodeEnum.AuthFailure);
const [correctCount3, closeCount3] = getData();
expect(correctCount3).toBe("2");
expect(closeCount3).toBe("0");
const failedAttemptResponse4 = getAuthResult(server, "1122334");
expect(failedAttemptResponse4.response.code).toBe(ResponseCodeEnum.AuthFailure);
const [correctCount4, closeCount4] = getData();
expect(correctCount4).toBe("6");
expect(closeCount4).toBe("1");
const failedAttemptResponse5 = getAuthResult(server, "22114333");
expect(failedAttemptResponse5.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(server.hasAdminRights).toBe(false);
const [correctCount5, closeCount5] = getData();
expect(correctCount5).toBe("2");
expect(closeCount5).toBe("6");
server.password = "2435";
const failedAttemptResponse6 = getAuthResult(server, "3423");
expect(failedAttemptResponse6.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(server.hasAdminRights).toBe(false);
const [correctCount6, closeCount6] = getData();
expect(correctCount6).toBe("1");
expect(closeCount6).toBe("2");
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
expect(server.hasAdminRights).toBe(true);
});
test("getConvertToBase10Server creates a server with a correct password hint", () => {
const server = serverFactory(getConvertToBase10Config, 5, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
if (!failedAttemptResponse.response.data) {
throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`);
}
assertString(failedAttemptResponse.response.data);
const [base, numberString] = failedAttemptResponse.response.data.split(",");
expect(numberString).toBe(encodeNumberInBaseN(+server.password, Number(base)));
const attemptedPassword = Number.parseInt(numberString, Number(base));
const result = getAuthResult(server, `${attemptedPassword}`, 1);
expect(isCloseToCorrectPassword(server.password, attemptedPassword, true)).toBe(true);
expect(result.response.code).toBe(ResponseCodeEnum.Success);
});
test("getConvertToBase10Server creates a server with a correct password hint, and has a non-integer solution at higher difficulties ", () => {
const server = serverFactory(getConvertToBase10Config, 15, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
if (!failedAttemptResponse.response.data) {
throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`);
}
assertString(failedAttemptResponse.response.data);
const [base, numberString] = failedAttemptResponse.response.data.split(",");
expect(numberString).toBe(encodeNumberInBaseN(+server.password, Number(base)));
// custom parser is used to handle non-integer answers
const attemptedPassword = parseBaseNNumberString(numberString, Number(base));
const result = getAuthResult(server, `${attemptedPassword}`, 1);
expect(isCloseToCorrectPassword(server.password, attemptedPassword)).toBe(true);
expect(result.response.code).toBe(ResponseCodeEnum.Success);
});
test("encodeNumberInBaseN and parseBaseNNumberString encode/decode numbers correctly", () => {
expect(encodeNumberInBaseN(15, 5.5)).toBe("24");
expect(encodeNumberInBaseN(16, 5.5)).toBe("25");
expect(encodeNumberInBaseN(17, 5.5)).toBe("30.24034");
expect(encodeNumberInBaseN(264, 17.6)).toBe("EH.A9F");
expect(parseBaseNNumberString("24", 5.5)).toBe(15);
expect(parseBaseNNumberString("25", 5.5)).toBe(16);
const aprox = parseBaseNNumberString("30.24", 5.5);
expect(Math.abs(aprox - 17) < 0.1).toBe(true);
expect(encodeNumberInBaseN(7, 2)).toBe("111");
expect(encodeNumberInBaseN(112, 2)).toBe("1110000");
});
test("parseSimpleArithmeticExpression parses expressions correctly", () => {
expect(parseSimpleArithmeticExpression("1 + 2")).toBe(3);
expect(parseSimpleArithmeticExpression("1 - 2")).toBe(-1);
expect(parseSimpleArithmeticExpression("5 + 1 * 3")).toBe(8);
expect(parseSimpleArithmeticExpression("5 * ( 6 + 7 )")).toBe(65);
expect(parseSimpleArithmeticExpression("4 + 5 * ( 6 + 7 ) / 2")).toBe(36.5);
expect(parseSimpleArithmeticExpression("1 + 3 * ( 4 / 5 ) / 2 + 4 ")).toBe(6.2);
expect(Math.abs(parseSimpleArithmeticExpression("1 + 3 * ((4 / 5) / 2 ) * 3 + 4 ")) - 8.6 < 0.01).toBe(true);
expect(
Math.abs(
parseSimpleArithmeticExpression(
"23 * ( 41 + 76 + 32 * 27 * 6 ) - 34 - 49 + 93 - ( 11 / 41 - 62 / 6 + 5 ) * 19 - 0",
),
) -
122029.235 <
0.9,
).toBe(true);
expect(parseSimpleArithmeticExpression("48 - 38 * 24 + ( 72 / 8 * 4 ) - 76 * 61 * 16")).toBe(-75004);
expect(
Math.ceil(parseSimpleArithmeticExpression("8 / 15 / 91 / ( 54 * 10 * 84 ) - 77 * 83 + ( 83 * 75 / 8 ) + 54")),
).toBe(-5558);
expect(
parseSimpleArithmeticExpression(
"37 / 8 / 81 / ( 1 + ( 80 * 31 ) - 26 - 53 ) / 52 / ( 18 * 72 / 78 ) * 83 * ( 21 * 88 + 96 ) + 23",
),
).toBeCloseTo(24.61, -1);
expect(parseSimpleArithmeticExpression("94 / ( 76 * 63 * ( 89 * 33 ) + 70 ) * 13 * 73 * 61 * 81 * 74")).toBeCloseTo(
2319.425,
);
expect(
parseSimpleArithmeticExpression(
"94 / ( 76 * 63 * ( 89 * 33 ) + 70 ) * 13 * 73 * 61 * 81 * 74;alert('injection!')",
),
).toBeCloseTo(2319.425);
const value = parseSimpleArithmeticExpression("76 + 30 * ( 3 * 10 + 14 ) - 83 / 47 + 16");
expect(isCloseToCorrectPassword(value.toString(), 1410.2340425531916)).toBe(true);
const value2 = parseSimpleArithmeticExpression(
" 5 ÷ ( 30 64 11 ) ҳ 98 ÷ 17 20 58 ( 64 4 ҳ 80 ) ҳ 90",
);
expect(isCloseToCorrectPassword(value2.toString(), 23118.274509803923)).toBe(true);
const expression = generateSimpleArithmeticExpression(20);
expect(eval(cleanArithmeticExpression(expression))).toBeCloseTo(parseSimpleArithmeticExpression(expression));
});
test("getParseArithmeticExpressionConfig server creates a server with a correct password hint", () => {
const server = serverFactory(() => getParseArithmeticExpressionConfig(25), 25, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const expression = failedAttemptResponse.response.data;
const attemptedPassword = parseSimpleArithmeticExpression(`${expression}`);
const result = getAuthResult(server, `${attemptedPassword}`, 1);
expect(isCloseToCorrectPassword(server.password, attemptedPassword, true)).toBe(true);
expect(result.response.code).toBe(ResponseCodeEnum.Success);
});
test("getBinaryEncodedConfig server creates a server with a correct password hint", () => {
const server = serverFactory(() => getBinaryEncodedConfig(20), 20, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const binaryString = failedAttemptResponse.response.data;
// convert each 8 bits to a character, then join
const attemptedPassword = String.fromCharCode(...`${binaryString}`.split(" ").map((byte) => parseInt(byte, 2)));
const result = getAuthResult(server, `${attemptedPassword}`, 1);
expect(attemptedPassword).toBe(server.password);
expect(result.response.code).toBe(ResponseCodeEnum.Success);
});
test("getXorMaskEncryptedPasswordConfig server creates a server with a correct password hint", () => {
const server = serverFactory(() => getXorMaskEncryptedPasswordConfig(), 20, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const xorData = failedAttemptResponse.response.data;
if (!xorData) {
throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`);
}
assertString(xorData);
const [encryptedPasswordString, maskString] = xorData.split(";");
const mask = maskString.split(" ").map((byte) => parseInt(byte, 2));
const encryptedPasswordChars = encryptedPasswordString.split("");
const attemptedPassword = encryptedPasswordChars
.map((char, i) => String.fromCharCode(char.charCodeAt(0) ^ mask[i]))
.join("");
const result = getAuthResult(server, `${attemptedPassword}`, 1);
expect(attemptedPassword).toBe(server.password);
expect(result.response.code).toBe(ResponseCodeEnum.Success);
});
test("getTimingAttackConfig server creates a server that takes longer the more correct characters are submitted, starting from the left", async () => {
Player.gainCharismaExp(1e200);
const server = serverFactory(getTimingAttackConfig, 20, 0, 0);
server.logTrafficInterval = -1;
const ns = getNS(server.hostname);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "$$$$$$$", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const response1 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 1) + "%%%%", 0);
const firstAuthTime = getLatestResponseTime(server);
const response2 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 2) + "%%%", 0);
const secondAuthTime = getLatestResponseTime(server);
const response3 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 3) + "%%", 0);
const thirdAuthTime = getLatestResponseTime(server);
const response4WithWorseGuess = await ns.dnet.authenticate(server.hostname, "%%%%%%", 0);
const badAuthTime = getLatestResponseTime(server);
expect(badAuthTime).toBeLessThan(firstAuthTime);
expect(secondAuthTime).toBeGreaterThan(firstAuthTime);
expect(thirdAuthTime).toBeGreaterThan(secondAuthTime);
expect(response1.code).toBe(ResponseCodeEnum.AuthFailure);
expect(response2.code).toBe(ResponseCodeEnum.AuthFailure);
expect(response3.code).toBe(ResponseCodeEnum.AuthFailure);
expect(response4WithWorseGuess.code).toBe(ResponseCodeEnum.AuthFailure);
expect(response1.message).toBe(GenericResponseMessage.AuthFailure);
const serverLogs = getServerLogs(server, 5, true);
expect(serverLogs.length).toBe(5);
assertPasswordResponse(serverLogs[4].message);
assertPasswordResponse(serverLogs[3].message);
assertPasswordResponse(serverLogs[2].message);
assertPasswordResponse(serverLogs[1].message);
assertPasswordResponse(serverLogs[0].message);
expect(serverLogs[4].message.message).toBe("Found a mismatch while checking each character (0)");
expect(serverLogs[3].message.message).toBe("Found a mismatch while checking each character (1)");
expect(serverLogs[2].message.message).toBe("Found a mismatch while checking each character (2)");
expect(serverLogs[1].message.message).toBe("Found a mismatch while checking each character (3)");
expect(serverLogs[0].message.message).toBe("Found a mismatch while checking each character (0)");
const successfulResponse = await ns.dnet.authenticate(server.hostname, server.password, 0);
expect(successfulResponse.code).toBe(ResponseCodeEnum.Success);
});
test("getTimingAttackConfig server only gives response time improvements on the exact correct character", () => {
const server = serverFactory(getTimingAttackConfig, 20, 0, 0);
server.logTrafficInterval = -1;
expect(server).toBeDefined();
getAuthResult(server, server.password.slice(0, 3), 0);
const baseAuthTime = getLatestResponseTime(server);
expect(getServerLogs(server, 10).length).toBe(1);
for (const char of [...lettersUppercase, ...lettersLowercase, ...numbers]) {
if (char === server.password[1]) continue;
getAuthResult(server, server.password.slice(0, 3) + char, 0);
const testAuthTime = getLatestResponseTime(server);
expect(testAuthTime).toBe(baseAuthTime);
expect(getServerLogs(server, 10).length).toBe(1);
}
});
test("getSpiceLevelConfig server creates a server with a correct password hint", () => {
const server = serverFactory(getSpiceLevelConfig, 20, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const correctChars = server.password.slice(0, 3);
const result1 = getAuthResult(server, `${correctChars}%%%%%%`, 1);
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(result1.response.message).toBe("Not spicy enough");
expect(result1.response.data).toBe("🌶️🌶️🌶️/10");
const result2 = getAuthResult(server, `%%%%%%%%`, 1);
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(result2.response.message).toBe("Not spicy enough");
expect(result2.response.data).toBe("0/10");
const result3 = getAuthResult(server, `${correctChars.slice(0, 2)}%%%%`, 1);
expect(result3.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(result3.response.message).toBe("Not spicy enough");
expect(result3.response.data).toBe("🌶️🌶️/10");
const result4 = getAuthResult(server, server.password, 1);
expect(result4.response.code).toBe(ResponseCodeEnum.Success);
});
test("getGuessNumberConfig server creates a server with a correct password hint", () => {
const server = serverFactory(getGuessNumberConfig, 20, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const response1 = getAuthResult(server, `${+server.password - 10}`, 1);
expect(response1.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(response1.response.data).toBe("Higher");
const response2 = getAuthResult(server, `${+server.password + 10}`, 1);
expect(response2.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(response2.response.data).toBe("Lower");
const response3 = getAuthResult(server, server.password, 1);
expect(response3.response.code).toBe(ResponseCodeEnum.Success);
});
test("getCaptchaConfig server creates a server with a correct password hint", () => {
const server = serverFactory(getCaptchaConfig, 20, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const password = server.passwordHintData
.split("")
.filter((c) => !isNaN(+c))
.join("");
const result = getAuthResult(server, `${password}`, 1);
expect(result.result.success).toBe(true);
});
test("getYesn_tConfig server creates a server with a correct password hint", () => {
const server = serverFactory(getYesn_tConfig, 20, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const result1 = getAuthResult(server, server.password.slice(0, 2) + "%%%%%", 1);
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(result1.response.data).toBe("yes,yes,yesn't,yesn't,yesn't,yesn't,yesn't");
const result2 = getAuthResult(server, `%%%${server.password.slice(3, 5)}%%%%%`, 1);
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(result2.response.data).toBe("yesn't,yesn't,yesn't,yes,yes,yesn't,yesn't,yesn't,yesn't,yesn't");
const result3 = getAuthResult(server, server.password, 1);
expect(result3.response.code).toBe(ResponseCodeEnum.Success);
});
test("getRomanNumeralsServer creates a server with a correct password hint", () => {
const server = serverFactory(getRomanNumeralConfig, 5, 0, 0);
expect(server).toBeDefined();
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const attemptedPassword = romanNumeralDecoder(server.passwordHintData);
const result = getAuthResult(server, `${attemptedPassword}`, 1);
expect(result.result.success).toBe(true);
expect(result.response.code).toBe(ResponseCodeEnum.Success);
});
test("getLargestPrimeFactor server creates valid password and hint", () => {
const server = serverFactory(getLargestPrimeFactorConfig, 20, 0, 0);
const password = +server.password;
const hint = +server.passwordHintData;
expect(isNumber(password)).toBe(true);
expect(isNumber(hint)).toBe(true);
const factor = hint / password;
expect(factor).toEqual(Math.floor(factor));
const factors = largePrimes
.filter((n) => hint / n === Math.floor(hint / n))
.sort()
.toReversed();
expect(factors[0]).toEqual(password);
});
test("getDivisibilityTestConfig server creates valid password and hint", () => {
const server = serverFactory(getDivisibilityTestConfig, 100, 0, 0);
expect(server.password.includes("+")).toBe(false);
expect(isNumber(+server.password)).toBe(true);
expect(isNumber(+server.passwordHintData)).toBe(true);
DarknetState.serverState.set(server.hostname, {
serverLogs: [],
authenticatedPIDs: [],
});
const nonDivisibleResult = getAuthResult(server, `${server.password + 1}`, 1);
expect(nonDivisibleResult.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(nonDivisibleResult.response.message).toContain("not divisible");
const authLog = getMostRecentAuthLog(server.hostname);
if (!authLog) {
throw new Error(`Cannot find the most recent auth log in ${server.hostname}`);
}
expect(authLog.message).toContain("not divisible");
let factor = 2;
while (+server.password % factor !== 0) {
++factor;
}
const divisibleResult = getAuthResult(server, `${factor}`, 1);
expect(divisibleResult.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(divisibleResult.response.message).toContain("IS divisible");
const correctResult = getAuthResult(server, `${server.password}`, 1);
expect(correctResult.response.code).toBe(ResponseCodeEnum.Success);
});
test("getTripleModuloConfig server creates valid password and hint", () => {
const server = serverFactory(getTripleModuloConfig, 60, 0, 0);
const guess1 = +server.password + 1;
const expectedResult1 = (+server.password % guess1) % (((guess1 - 1) % 32) + 1);
const result1 = getAuthResult(server, `${guess1}`, 1);
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult1}`);
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult1}`);
const guess2 = +server.password - 1;
const expectedResult2 = (+server.password % guess2) % (((guess2 - 1) % 32) + 1);
const result2 = getAuthResult(server, `${guess2}`, 1);
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult2}`);
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult2}`);
const guess3 = Math.floor(+server.password / 2);
const expectedResult3 = (+server.password % guess3) % (((guess3 - 1) % 32) + 1);
const result3 = getAuthResult(server, `${guess3}`, 1);
expect(result3.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult3}`);
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult3}`);
// Check a known factor
server.password = (BigInt(server.password) * BigInt(2)).toString();
const guessOfFactor = 2;
const expectedResultOfFactor = 0;
const resultOfFactor = getAuthResult(server, `${guessOfFactor}`, 1);
expect(resultOfFactor.response.code).toBe(ResponseCodeEnum.AuthFailure);
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResultOfFactor}`);
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResultOfFactor}`);
const correctResult = getAuthResult(server, `${server.password}`, 1);
expect(correctResult.response.code).toBe(ResponseCodeEnum.Success);
});
test("bufferOverflow server creates valid password and hint", () => {
const bufferOverflowServer = serverFactory(getBufferOverflowConfig, 5, 0, 0);
bufferOverflowServer.logTrafficInterval = -1;
const passwordLength = bufferOverflowServer.password.length;
const failedResult = getAuthResult(bufferOverflowServer, "1", 1);
expect(failedResult.response.code).toBe(ResponseCodeEnum.AuthFailure);
const authLog = getMostRecentAuthLog(bufferOverflowServer.hostname);
if (!authLog) {
throw new Error(`Cannot find the most recent auth log in ${bufferOverflowServer.hostname}`);
}
const received = "1".padEnd(passwordLength, "ˍ").slice(0, passwordLength);
const expected = "■".repeat(passwordLength);
expect(authLog.message).toBe(`auth failed: received '${received}', expected '${expected}'`);
expect(authLog.passwordAttempted).toBe("1".padEnd(passwordLength, "ˍ").slice(0, passwordLength));
const successResult = getAuthResult(bufferOverflowServer, "A".repeat(passwordLength * 2), 1);
expect(successResult.response.code).toBe(ResponseCodeEnum.Success);
});
test("sortedEchoVuln creates a valid password and hint", () => {
const sortedEchoVulnServer = serverFactory(getSortedEchoVulnConfig, 20, 0, 0);
sortedEchoVulnServer.password = "12345";
sortedEchoVulnServer.passwordHintData = "41532";
expect(sortedEchoVulnServer).toBeDefined();
const failedAttemptResponse = getAuthResult(sortedEchoVulnServer, "23456", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const logs1 = getMostRecentAuthLog(sortedEchoVulnServer.hostname);
expect(logs1?.data).toBe(`${sortedEchoVulnServer.passwordHintData}; RMS Deviation:1.000`);
getAuthResult(sortedEchoVulnServer, "23579", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const logs2 = getMostRecentAuthLog(sortedEchoVulnServer.hostname);
expect(logs2?.data).toBe(`${sortedEchoVulnServer.passwordHintData}; RMS Deviation:2.490`);
getAuthResult(sortedEchoVulnServer, "12355", 1);
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
const logs3 = getMostRecentAuthLog(sortedEchoVulnServer.hostname);
expect(logs3?.data).toBe(`${sortedEchoVulnServer.passwordHintData}; RMS Deviation:0.447`);
expect(getAuthResult(sortedEchoVulnServer, sortedEchoVulnServer.password, 1).result.code).toBe(
ResponseCodeEnum.Success,
);
expect(sortedEchoVulnServer.hasAdminRights).toBe(true);
});
test("kingOfTheHill server creates a valid password and hint", () => {
const kingOfTheHillServer = serverFactory(getKingOfTheHillConfig, 60, 0, 0);
const password = Number(kingOfTheHillServer.password);
const result1 = getAuthResult(kingOfTheHillServer, `${password - 50}`, 1);
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
const logs1 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
const result2 = getAuthResult(kingOfTheHillServer, `${password + 50}`, 1);
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
const logs2 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
const resultNearPassword = getAuthResult(kingOfTheHillServer, `${password + 1}`, 1);
expect(resultNearPassword.response.code).toBe(ResponseCodeEnum.AuthFailure);
const logsNearPassword = getMostRecentAuthLog(kingOfTheHillServer.hostname);
getAuthResult(kingOfTheHillServer, `${password - 1}`, 1);
const logsNearPassword2 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
expect(Number(logs1?.data)).toBeLessThan(Number(logsNearPassword?.data));
expect(Number(logs2?.data)).toBeLessThan(Number(logsNearPassword?.data));
expect(Number(logsNearPassword?.data)).toBeLessThan(10000);
expect(Number(logsNearPassword2?.data)).toBeLessThan(10000);
getAuthResult(kingOfTheHillServer, `12345`, 1);
const logs3 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
getAuthResult(kingOfTheHillServer, `12345`, 1);
const logs4 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
expect(Number(logs3?.data)).toBe(Number(logs4?.data));
// For testing password spreads: poll the altitude and log the resulting graph
// const results = [];
// for (let attempt = password * 0.6; attempt <= password * 1.4; attempt += password/200) {
// getAuthResult(kingOfTheHillServer, `${attempt}`, 1);
// const data = getMostRecentAuthLog(kingOfTheHillServer.hostname)?.data;
// results.push({ attempt, altitude: Number(data) });
// }
// const graph = results.map(r => {
// if (r.altitude > 0) {
// return ".".repeat(Math.min(50, Math.floor(r.altitude / 200))) + "*";
// }
// return "-".repeat(Math.min(50, Math.floor(-r.altitude / 200)));
// }).join("\n");
//
// console.log(graph);
const correctResult = getAuthResult(kingOfTheHillServer, `${password}`, 1);
expect(correctResult.response.code).toBe(ResponseCodeEnum.Success);
});
});
const serverSnapshot = () =>
getAllDarknetServers()
.map((s) => ({ hostname: s.hostname, depth: s.depth, leftOffset: s.leftOffset }))
.sort((a, b) => (a.hostname < b.hostname ? -1 : a.hostname === b.hostname ? 0 : 1));
describe("mutateDarknet and webstorm", () => {
test("mutateDarknet", () => {
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
const spiedConsoleError = jest.spyOn(console, "error").mockImplementation();
for (let i = 0; i < 5000; ++i) {
mutateDarknet();
}
expect(spiedExceptionAlert).not.toHaveBeenCalled();
expect(spiedConsoleError).not.toHaveBeenCalled();
});
test("webstorm", async () => {
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
const spiedConsoleError = jest.spyOn(console, "error").mockImplementation();
jest.spyOn(UtilityModule, "sleep").mockImplementation();
for (let i = 0; i < 100; ++i) {
await launchWebstorm();
}
expect(spiedExceptionAlert).not.toHaveBeenCalled();
expect(spiedConsoleError).not.toHaveBeenCalled();
});
test("mutation during webstorm", async () => {
const realRandom = Math.random;
try {
jest.useFakeTimers();
const promise = launchWebstorm();
expect(DarknetState.mutationLock).toBeTruthy();
await jest.advanceTimersByTimeAsync(10000);
expect(DarknetState.mutationLock).toBeTruthy();
const initialServers = serverSnapshot();
// Low rolls cause stuff to happen, we want deterministic testing.
// Jest spies keep state, make our own mock so it doesn't eat memory in
// case something goes wrong.
let count = 0;
Math.random = () => count++ * (Number.EPSILON * 1024);
mutateDarknet();
expect(serverSnapshot()).toEqual(initialServers);
await jest.runAllTimersAsync();
await promise; // Should immediately finish
expect(DarknetState.mutationLock).toBeNull();
mutateDarknet();
expect(serverSnapshot()).not.toEqual(initialServers);
} finally {
jest.useRealTimers();
Math.random = realRandom;
}
});
test("prestige during webstorm", async () => {
try {
jest.useFakeTimers();
const promise = launchWebstorm();
await jest.advanceTimersByTimeAsync(0); // Finish any promises
expect(DarknetState.mutationLock).toBeTruthy();
const beforePrestige = serverSnapshot();
prestigeAugmentation();
expect(DarknetState.mutationLock).toBeNull();
const initialServers = serverSnapshot();
// Validate that prestige changed the network
expect(initialServers).not.toEqual(beforePrestige);
await jest.runAllTimersAsync();
await promise; // Should immediately finish
expect(DarknetState.mutationLock).toBeNull();
// Webstorm should not have changed anything
expect(serverSnapshot()).toEqual(initialServers);
} finally {
jest.useRealTimers();
}
});
});
function validatePath(hostname: string): void {
expectWithMessage(isDirectoryPath(`${hostname}/`), true, `Invalid hostname: ${hostname}`);
expectWithMessage(isFilePath(`${hostname}/data.txt`), true, `Invalid hostname: ${hostname}`);
}
describe("Darknet server name generator", () => {
test("Base name and l33t", () => {
for (const name of commonPasswordDictionary) {
validatePath(name);
}
for (const name of loreNames) {
validatePath(name);
}
for (const name of presetNames) {
validatePath(name);
}
for (const prefix of ServerNamePrefixes) {
for (const connector of connectors) {
for (const suffix of ServerNameSuffixes) {
validatePath(`${prefix}${connector}${suffix}`);
}
}
}
for (const name of Object.values(l33t)) {
validatePath(name);
}
});
test("generateDarknetServerName", () => {
DarknetState.offlineServers = new Set();
for (let i = 0; i < 1000; ++i) {
validatePath(generateDarknetServerName());
}
});
});
describe("Cache filename generator", () => {
test("Random prefix", () => {
for (let i = 0; i < 10000; ++i) {
const cacheFilename = generateCacheFilename(false);
if (!cacheFilename) {
throw new Error("Invalid cache filename");
}
validatePath(cacheFilename);
}
});
test("Cache file in labyrinth server", () => {
const cacheFilename = generateCacheFilename(false, LAB_CACHE_NAME);
if (!cacheFilename) {
throw new Error("Invalid cache filename");
}
validatePath(cacheFilename);
});
});
describe("Clue filename generator", () => {
test("getClueFileName", () => {
for (let i = 0; i < 10000; ++i) {
expect(() => {
getClueFileName(passwordFileNames);
getClueFileName(notebookFileNames);
}).not.toThrow();
}
});
});