diff --git a/Gruntfile.js b/Gruntfile.js
index f073a1363..78c265323 100755
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -217,7 +217,8 @@ module.exports = function (grunt) {
client: {
logging: "error",
overlay: true
- }
+ },
+ hot: "only"
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS),
diff --git a/package-lock.json b/package-lock.json
index d956ea2f5..16c88c1be 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -98,7 +98,7 @@
"autoprefixer": "^10.4.4",
"babel-loader": "^8.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3",
- "chromedriver": "^99.0.0",
+ "chromedriver": "^101.0.0",
"cli-progress": "^3.10.0",
"colors": "^1.4.0",
"copy-webpack-plugin": "^10.2.4",
@@ -4467,9 +4467,9 @@
}
},
"node_modules/chromedriver": {
- "version": "99.0.0",
- "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz",
- "integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==",
+ "version": "101.0.0",
+ "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz",
+ "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -19171,9 +19171,9 @@
"dev": true
},
"chromedriver": {
- "version": "99.0.0",
- "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz",
- "integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==",
+ "version": "101.0.0",
+ "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz",
+ "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==",
"dev": true,
"requires": {
"@testim/chrome-version": "^1.1.2",
diff --git a/package.json b/package.json
index 73d2d19ab..d2d712722 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
"autoprefixer": "^10.4.4",
"babel-loader": "^8.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3",
- "chromedriver": "^99.0.0",
+ "chromedriver": "^101.0.0",
"cli-progress": "^3.10.0",
"colors": "^1.4.0",
"copy-webpack-plugin": "^10.2.4",
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 9540f8a37..c15ec3d2a 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -190,6 +190,7 @@
"Parse IP range",
"Parse IPv6 address",
"Parse IPv4 header",
+ "Parse TCP",
"Parse UDP",
"Parse SSH Host Key",
"Parse URI",
diff --git a/src/core/lib/Binary.mjs b/src/core/lib/Binary.mjs
index dc63fc586..4e41d2a14 100644
--- a/src/core/lib/Binary.mjs
+++ b/src/core/lib/Binary.mjs
@@ -13,7 +13,7 @@ import OperationError from "../errors/OperationError.mjs";
/**
* Convert a byte array into a binary string.
*
- * @param {Uint8Array|byteArray} data
+ * @param {Uint8Array|byteArray|number} data
* @param {string} [delim="Space"]
* @param {number} [padding=8]
* @returns {string}
@@ -26,13 +26,17 @@ import OperationError from "../errors/OperationError.mjs";
* toBinary([10,20,30], ":");
*/
export function toBinary(data, delim="Space", padding=8) {
- if (!data) return "";
-
delim = Utils.charRep(delim);
let output = "";
- for (let i = 0; i < data.length; i++) {
- output += data[i].toString(2).padStart(padding, "0") + delim;
+ if (data.length) { // array
+ for (let i = 0; i < data.length; i++) {
+ output += data[i].toString(2).padStart(padding, "0") + delim;
+ }
+ } else if (typeof data === "number") { // Single value
+ return data.toString(2).padStart(padding, "0");
+ } else {
+ return "";
}
if (delim.length) {
diff --git a/src/core/lib/FileSignatures.mjs b/src/core/lib/FileSignatures.mjs
index a515a9e6b..7b77f2d9e 100644
--- a/src/core/lib/FileSignatures.mjs
+++ b/src/core/lib/FileSignatures.mjs
@@ -3778,8 +3778,8 @@ function parseDEFLATE(stream) {
while (!finalBlock) {
// Read header
- finalBlock = stream.readBits(1);
- const blockType = stream.readBits(2);
+ finalBlock = stream.readBits(1, "le");
+ const blockType = stream.readBits(2, "le");
if (blockType === 0) {
/* No compression */
@@ -3798,16 +3798,16 @@ function parseDEFLATE(stream) {
/* Dynamic Huffman */
// Read the number of liternal and length codes
- const hlit = stream.readBits(5) + 257;
+ const hlit = stream.readBits(5, "le") + 257;
// Read the number of distance codes
- const hdist = stream.readBits(5) + 1;
+ const hdist = stream.readBits(5, "le") + 1;
// Read the number of code lengths
- const hclen = stream.readBits(4) + 4;
+ const hclen = stream.readBits(4, "le") + 4;
// Parse code lengths
const codeLengths = new Uint8Array(huffmanOrder.length);
for (let i = 0; i < hclen; i++) {
- codeLengths[huffmanOrder[i]] = stream.readBits(3);
+ codeLengths[huffmanOrder[i]] = stream.readBits(3, "le");
}
// Parse length table
@@ -3819,16 +3819,16 @@ function parseDEFLATE(stream) {
code = readHuffmanCode(stream, codeLengthsTable);
switch (code) {
case 16:
- repeat = 3 + stream.readBits(2);
+ repeat = 3 + stream.readBits(2, "le");
while (repeat--) lengthTable[i++] = prev;
break;
case 17:
- repeat = 3 + stream.readBits(3);
+ repeat = 3 + stream.readBits(3, "le");
while (repeat--) lengthTable[i++] = 0;
prev = 0;
break;
case 18:
- repeat = 11 + stream.readBits(7);
+ repeat = 11 + stream.readBits(7, "le");
while (repeat--) lengthTable[i++] = 0;
prev = 0;
break;
@@ -3886,11 +3886,11 @@ function parseHuffmanBlock(stream, litTab, distTab) {
if (code < 256) continue;
// Length code
- stream.readBits(lengthExtraTable[code - 257]);
+ stream.readBits(lengthExtraTable[code - 257], "le");
// Dist code
code = readHuffmanCode(stream, distTab);
- stream.readBits(distanceExtraTable[code]);
+ stream.readBits(distanceExtraTable[code], "le");
}
}
@@ -3948,7 +3948,7 @@ function readHuffmanCode(stream, table) {
const [codeTable, maxCodeLength] = table;
// Read max length
- const bitsBuf = stream.readBits(maxCodeLength);
+ const bitsBuf = stream.readBits(maxCodeLength, "le");
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
const codeLength = codeWithLength >>> 16;
diff --git a/src/core/lib/Protocol.mjs b/src/core/lib/Protocol.mjs
new file mode 100644
index 000000000..57d2374a8
--- /dev/null
+++ b/src/core/lib/Protocol.mjs
@@ -0,0 +1,47 @@
+/**
+ * Protocol parsing functions.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2022
+ * @license Apache-2.0
+ */
+
+import BigNumber from "bignumber.js";
+import {toHexFast} from "../lib/Hex.mjs";
+
+/**
+ * Recursively displays a JSON object as an HTML table
+ *
+ * @param {Object} obj
+ * @returns string
+ */
+export function objToTable(obj, nested=false) {
+ let html = `
`;
+ if (!nested)
+ html += `
+ Field |
+ Value |
+
`;
+
+ for (const key in obj) {
+ html += `${key} | `;
+ if (typeof obj[key] === "object")
+ html += `${objToTable(obj[key], true)} | `;
+ else
+ html += `${obj[key]} | `;
+ html += "
";
+ }
+ html += "
";
+ return html;
+}
+
+/**
+ * Converts bytes into a BigNumber string
+ * @param {Uint8Array} bs
+ * @returns {string}
+ */
+export function bytesToLargeNumber(bs) {
+ return BigNumber(toHexFast(bs), 16).toString();
+}
diff --git a/src/core/lib/Stream.mjs b/src/core/lib/Stream.mjs
index f332f9087..b2a183f4a 100644
--- a/src/core/lib/Stream.mjs
+++ b/src/core/lib/Stream.mjs
@@ -27,15 +27,17 @@ export default class Stream {
}
/**
- * Get a number of bytes from the current position.
+ * Get a number of bytes from the current position, or all remaining bytes.
*
- * @param {number} numBytes
+ * @param {number} [numBytes=null]
* @returns {Uint8Array}
*/
- getBytes(numBytes) {
+ getBytes(numBytes=null) {
if (this.position > this.length) return undefined;
- const newPosition = this.position + numBytes;
+ const newPosition = numBytes !== null ?
+ this.position + numBytes :
+ this.length;
const bytes = this.bytes.slice(this.position, newPosition);
this.position = newPosition;
this.bitPos = 0;
@@ -91,34 +93,40 @@ export default class Stream {
}
/**
- * Reads a number of bits from the buffer.
- *
- * @TODO Add endianness
+ * Reads a number of bits from the buffer in big or little endian.
*
* @param {number} numBits
+ * @param {string} [endianness="be"]
* @returns {number}
*/
- readBits(numBits) {
+ readBits(numBits, endianness="be") {
if (this.position > this.length) return undefined;
let bitBuf = 0,
bitBufLen = 0;
// Add remaining bits from current byte
- bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
+ bitBuf = this.bytes[this.position++] & bitMask(this.bitPos);
+ if (endianness !== "be") bitBuf >>>= this.bitPos;
bitBufLen = 8 - this.bitPos;
this.bitPos = 0;
// Not enough bits yet
while (bitBufLen < numBits) {
- bitBuf |= this.bytes[this.position++] << bitBufLen;
+ if (endianness === "be")
+ bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++];
+ else
+ bitBuf |= this.bytes[this.position++] << bitBufLen;
bitBufLen += 8;
}
// Reverse back to numBits
if (bitBufLen > numBits) {
const excess = bitBufLen - numBits;
- bitBuf &= (1 << numBits) - 1;
+ if (endianness === "be")
+ bitBuf >>>= excess;
+ else
+ bitBuf &= (1 << numBits) - 1;
bitBufLen -= excess;
this.position--;
this.bitPos = 8 - excess;
@@ -133,7 +141,9 @@ export default class Stream {
* @returns {number} The bit mask
*/
function bitMask(bitPos) {
- return 256 - (1 << bitPos);
+ return endianness === "be" ?
+ (1 << (8 - bitPos)) - 1 :
+ 256 - (1 << bitPos);
}
}
diff --git a/src/core/operations/ParseTCP.mjs b/src/core/operations/ParseTCP.mjs
new file mode 100644
index 000000000..7adb37e0c
--- /dev/null
+++ b/src/core/operations/ParseTCP.mjs
@@ -0,0 +1,245 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2022
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import Stream from "../lib/Stream.mjs";
+import {toHexFast, fromHex} from "../lib/Hex.mjs";
+import {toBinary} from "../lib/Binary.mjs";
+import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs";
+import Utils from "../Utils.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import BigNumber from "bignumber.js";
+
+/**
+ * Parse TCP operation
+ */
+class ParseTCP extends Operation {
+
+ /**
+ * ParseTCP constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Parse TCP";
+ this.module = "Default";
+ this.description = "Parses a TCP header and payload (if present).";
+ this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
+ this.inputType = "string";
+ this.outputType = "json";
+ this.presentType = "html";
+ this.args = [
+ {
+ name: "Input format",
+ type: "option",
+ value: ["Hex", "Raw"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {html}
+ */
+ run(input, args) {
+ const format = args[0];
+
+ if (format === "Hex") {
+ input = fromHex(input);
+ } else if (format === "Raw") {
+ input = Utils.strToArrayBuffer(input);
+ } else {
+ throw new OperationError("Unrecognised input format.");
+ }
+
+ const s = new Stream(new Uint8Array(input));
+ if (s.length < 20) {
+ throw new OperationError("Need at least 20 bytes for a TCP Header");
+ }
+
+ // Parse Header
+ const TCPPacket = {
+ "Source port": s.readInt(2),
+ "Destination port": s.readInt(2),
+ "Sequence number": bytesToLargeNumber(s.getBytes(4)),
+ "Acknowledgement number": s.readInt(4),
+ "Data offset": s.readBits(4),
+ "Flags": {
+ "Reserved": toBinary(s.readBits(3), "", 3),
+ "NS": s.readBits(1),
+ "CWR": s.readBits(1),
+ "ECE": s.readBits(1),
+ "URG": s.readBits(1),
+ "ACK": s.readBits(1),
+ "PSH": s.readBits(1),
+ "RST": s.readBits(1),
+ "SYN": s.readBits(1),
+ "FIN": s.readBits(1),
+ },
+ "Window size": s.readInt(2),
+ "Checksum": "0x" + toHexFast(s.getBytes(2)),
+ "Urgent pointer": "0x" + toHexFast(s.getBytes(2))
+ };
+
+ // Parse options if present
+ let windowScaleShift = 0;
+ if (TCPPacket["Data offset"] > 5) {
+ let remainingLength = TCPPacket["Data offset"] * 4 - 20;
+
+ const options = {};
+ while (remainingLength > 0) {
+ const option = {
+ "Kind": s.readInt(1)
+ };
+
+ let opt = { name: "Reserved", length: true };
+ if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) {
+ opt = TCP_OPTION_KIND_LOOKUP[option.Kind];
+ }
+
+ // Add Length and Value fields
+ if (opt.length) {
+ option.Length = s.readInt(1);
+
+ if (option.Length > 2) {
+ if (Object.prototype.hasOwnProperty.call(opt, "parser")) {
+ option.Value = opt.parser(s.getBytes(option.Length - 2));
+ } else {
+ option.Value = option.Length <= 6 ?
+ s.readInt(option.Length - 2):
+ "0x" + toHexFast(s.getBytes(option.Length - 2));
+ }
+
+ // Store Window Scale shift for later
+ if (option.Kind === 3 && option.Value) {
+ windowScaleShift = option.Value["Shift count"];
+ }
+ }
+ }
+ options[opt.name] = option;
+
+ const length = option.Length || 1;
+ remainingLength -= length;
+ }
+ TCPPacket.Options = options;
+ }
+
+ if (s.hasMore()) {
+ TCPPacket.Data = "0x" + toHexFast(s.getBytes());
+ }
+
+ // Improve values
+ TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`;
+ const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift)));
+ TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`;
+
+ return TCPPacket;
+ }
+
+ /**
+ * Displays the TCP Packet in a tabular style
+ * @param {Object} data
+ * @returns {html}
+ */
+ present(data) {
+ return objToTable(data);
+ }
+
+}
+
+// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml
+// on 2022-05-30
+const TCP_OPTION_KIND_LOOKUP = {
+ 0: { name: "End of Option List", length: false },
+ 1: { name: "No-Operation", length: false },
+ 2: { name: "Maximum Segment Size", length: true },
+ 3: { name: "Window Scale", length: true, parser: windowScaleParser },
+ 4: { name: "SACK Permitted", length: true },
+ 5: { name: "SACK", length: true },
+ 6: { name: "Echo (obsoleted by option 8)", length: true },
+ 7: { name: "Echo Reply (obsoleted by option 8)", length: true },
+ 8: { name: "Timestamps", length: true, parser: tcpTimestampParser },
+ 9: { name: "Partial Order Connection Permitted (obsolete)", length: true },
+ 10: { name: "Partial Order Service Profile (obsolete)", length: true },
+ 11: { name: "CC (obsolete)", length: true },
+ 12: { name: "CC.NEW (obsolete)", length: true },
+ 13: { name: "CC.ECHO (obsolete)", length: true },
+ 14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser },
+ 15: { name: "TCP Alternate Checksum Data (obsolete)", length: true },
+ 16: { name: "Skeeter", length: true },
+ 17: { name: "Bubba", length: true },
+ 18: { name: "Trailer Checksum Option", length: true },
+ 19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true },
+ 20: { name: "SCPS Capabilities", length: true },
+ 21: { name: "Selective Negative Acknowledgements", length: true },
+ 22: { name: "Record Boundaries", length: true },
+ 23: { name: "Corruption experienced", length: true },
+ 24: { name: "SNAP", length: true },
+ 25: { name: "Unassigned (released 2000-12-18)", length: true },
+ 26: { name: "TCP Compression Filter", length: true },
+ 27: { name: "Quick-Start Response", length: true },
+ 28: { name: "User Timeout Option (also, other known unauthorized use)", length: true },
+ 29: { name: "TCP Authentication Option (TCP-AO)", length: true },
+ 30: { name: "Multipath TCP (MPTCP)", length: true },
+ 69: { name: "Encryption Negotiation (TCP-ENO)", length: true },
+ 70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
+ 76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
+ 77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
+ 78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
+ 253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true },
+ 254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true }
+};
+
+/**
+ * Parses the TCP Alternate Checksum Request field
+ * @param {Uint8Array} data
+ */
+function tcpAlternateChecksumParser(data) {
+ const lookup = {
+ 0: "TCP Checksum",
+ 1: "8-bit Fletchers's algorithm",
+ 2: "16-bit Fletchers's algorithm",
+ 3: "Redundant Checksum Avoidance"
+ }[data[0]];
+
+ return `${lookup} (0x${toHexFast(data)})`;
+}
+
+/**
+ * Parses the TCP Timestamp field
+ * @param {Uint8Array} data
+ */
+function tcpTimestampParser(data) {
+ const s = new Stream(data);
+
+ if (s.length !== 8)
+ return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`;
+
+ const tsval = bytesToLargeNumber(s.getBytes(4)),
+ tsecr = bytesToLargeNumber(s.getBytes(4));
+
+ return {
+ "Current Timestamp": tsval,
+ "Echo Reply": tsecr
+ };
+}
+
+/**
+ * Parses the Window Scale field
+ * @param {Uint8Array} data
+ */
+function windowScaleParser(data) {
+ if (data.length !== 1)
+ return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`;
+
+ return {
+ "Shift count": data[0],
+ "Multiplier": 1 << data[0]
+ };
+}
+
+export default ParseTCP;
diff --git a/src/core/operations/ParseUDP.mjs b/src/core/operations/ParseUDP.mjs
index 0a88fd5df..2aa762ae5 100644
--- a/src/core/operations/ParseUDP.mjs
+++ b/src/core/operations/ParseUDP.mjs
@@ -6,7 +6,9 @@
import Operation from "../Operation.mjs";
import Stream from "../lib/Stream.mjs";
-import {toHex} from "../lib/Hex.mjs";
+import {toHexFast, fromHex} from "../lib/Hex.mjs";
+import {objToTable} from "../lib/Protocol.mjs";
+import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
@@ -24,58 +26,61 @@ class ParseUDP extends Operation {
this.module = "Default";
this.description = "Parses a UDP header and payload (if present).";
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
- this.inputType = "ArrayBuffer";
+ this.inputType = "string";
this.outputType = "json";
this.presentType = "html";
- this.args = [];
+ this.args = [
+ {
+ name: "Input format",
+ type: "option",
+ value: ["Hex", "Raw"]
+ }
+ ];
}
/**
- * @param {ArrayBuffer} input
+ * @param {string} input
+ * @param {Object[]} args
* @returns {Object}
*/
run(input, args) {
- if (input.byteLength < 8) {
- throw new OperationError("Need 8 bytes for a UDP Header");
+ const format = args[0];
+
+ if (format === "Hex") {
+ input = fromHex(input);
+ } else if (format === "Raw") {
+ input = Utils.strToArrayBuffer(input);
+ } else {
+ throw new OperationError("Unrecognised input format.");
}
const s = new Stream(new Uint8Array(input));
+ if (s.length < 8) {
+ throw new OperationError("Need 8 bytes for a UDP Header");
+ }
+
// Parse Header
const UDPPacket = {
"Source port": s.readInt(2),
"Destination port": s.readInt(2),
"Length": s.readInt(2),
- "Checksum": toHex(s.getBytes(2), "")
+ "Checksum": "0x" + toHexFast(s.getBytes(2))
};
// Parse data if present
if (s.hasMore()) {
- UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), "");
+ UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8));
}
return UDPPacket;
}
/**
- * Displays the UDP Packet in a table style
+ * Displays the UDP Packet in a tabular style
* @param {Object} data
* @returns {html}
*/
present(data) {
- const html = [];
- html.push("");
- html.push("");
- html.push("Field | ");
- html.push("Value | ");
- html.push("
");
-
- for (const key in data) {
- html.push("");
- html.push("" + key + " | ");
- html.push("" + data[key] + " | ");
- html.push("
");
- }
- html.push("
");
- return html.join("");
+ return objToTable(data);
}
}
diff --git a/src/web/waiters/OperationsWaiter.mjs b/src/web/waiters/OperationsWaiter.mjs
index 6efbab72c..dee0dd060 100755
--- a/src/web/waiters/OperationsWaiter.mjs
+++ b/src/web/waiters/OperationsWaiter.mjs
@@ -109,11 +109,15 @@ class OperationsWaiter {
const matchedOps = [];
const matchedDescs = [];
+ // Create version with no whitespace for the fuzzy match
+ // Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
+ const inStrNWS = inStr.replace(/\s/g, "");
+
for (const opName in this.app.operations) {
const op = this.app.operations[opName];
// Match op name using fuzzy match
- const [nameMatch, score, idxs] = fuzzyMatch(inStr, opName);
+ const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
// Match description based on exact match
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index 4dd469cef..67d16ab51 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -96,6 +96,7 @@ import "./tests/Protobuf.mjs";
import "./tests/ParseSSHHostKey.mjs";
import "./tests/DefangIP.mjs";
import "./tests/ParseUDP.mjs";
+import "./tests/ParseTCP.mjs";
import "./tests/AvroToJSON.mjs";
import "./tests/Lorenz.mjs";
import "./tests/LuhnChecksum.mjs";
diff --git a/tests/operations/tests/ParseTCP.mjs b/tests/operations/tests/ParseTCP.mjs
new file mode 100644
index 000000000..acecb5d76
--- /dev/null
+++ b/tests/operations/tests/ParseTCP.mjs
@@ -0,0 +1,44 @@
+/**
+ * Parse TCP tests.
+ *
+ * @author n1474335
+ * @copyright Crown Copyright 2022
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "Parse TCP: No options",
+ input: "c2eb0050a138132e70dc9fb9501804025ea70000",
+ expectedMatch: /1026 \(Scaled: 1026\)/,
+ recipeConfig: [
+ {
+ op: "Parse TCP",
+ args: ["Hex"],
+ }
+ ],
+ },
+ {
+ name: "Parse TCP: Options",
+ input: "c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402",
+ expectedMatch: /1460/,
+ recipeConfig: [
+ {
+ op: "Parse TCP",
+ args: ["Hex"],
+ }
+ ],
+ },
+ {
+ name: "Parse TCP: Timestamps",
+ input: "9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308",
+ expectedMatch: /2766719221/,
+ recipeConfig: [
+ {
+ op: "Parse TCP",
+ args: ["Hex"],
+ }
+ ],
+ }
+]);
diff --git a/tests/operations/tests/ParseUDP.mjs b/tests/operations/tests/ParseUDP.mjs
index 2c519232c..6d1b55186 100644
--- a/tests/operations/tests/ParseUDP.mjs
+++ b/tests/operations/tests/ParseUDP.mjs
@@ -2,7 +2,6 @@
* Parse UDP tests.
*
* @author h345983745
- *
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
@@ -12,15 +11,11 @@ TestRegister.addTests([
{
name: "Parse UDP: No Data - JSON",
input: "04 89 00 35 00 2c 01 01",
- expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\"}",
+ expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\"}",
recipeConfig: [
- {
- op: "From Hex",
- args: ["Auto"],
- },
{
op: "Parse UDP",
- args: [],
+ args: ["Hex"],
},
{
op: "JSON Minify",
@@ -30,15 +25,11 @@ TestRegister.addTests([
}, {
name: "Parse UDP: With Data - JSON",
input: "04 89 00 35 00 2c 01 01 02 02",
- expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\",\"Data\":\"0202\"}",
+ expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\",\"Data\":\"0x0202\"}",
recipeConfig: [
- {
- op: "From Hex",
- args: ["Auto"],
- },
{
op: "Parse UDP",
- args: [],
+ args: ["Hex"],
},
{
op: "JSON Minify",
@@ -51,13 +42,9 @@ TestRegister.addTests([
input: "04 89 00",
expectedOutput: "Need 8 bytes for a UDP Header",
recipeConfig: [
- {
- op: "From Hex",
- args: ["Auto"],
- },
{
op: "Parse UDP",
- args: [],
+ args: ["Hex"],
},
{
op: "JSON Minify",