mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
842 lines
37 KiB
TypeScript
842 lines
37 KiB
TypeScript
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();
|
||
}
|
||
});
|
||
});
|