Merge branch 'gchq:master' into leon/add-zstd-compress-decompress

This commit is contained in:
Leon Zandman 2026-03-22 21:17:14 +01:00 committed by GitHub
commit 074ec98ded
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 401 additions and 45 deletions

32
package-lock.json generated
View File

@ -113,12 +113,12 @@
"@babel/plugin-syntax-import-assertions": "^7.28.6",
"@babel/plugin-transform-runtime": "^7.29.0",
"@babel/preset-env": "^7.29.2",
"@babel/runtime": "^7.28.6",
"@babel/runtime": "^7.29.2",
"@codemirror/commands": "^6.10.3",
"@codemirror/language": "^6.12.2",
"@codemirror/search": "^6.6.0",
"@codemirror/state": "^6.5.4",
"@codemirror/view": "^6.39.17",
"@codemirror/view": "^6.40.0",
"autoprefixer": "^10.4.27",
"babel-loader": "^10.1.1",
"base64-loader": "^1.0.0",
@ -127,7 +127,7 @@
"colors": "^1.4.0",
"compression-webpack-plugin": "^11.1.0",
"copy-webpack-plugin": "^13.0.1",
"core-js": "^3.48.0",
"core-js": "^3.49.0",
"cspell": "^8.19.4",
"css-loader": "7.1.4",
"eslint": "^9.39.4",
@ -1755,9 +1755,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"dev": true,
"license": "MIT",
"engines": {
@ -1897,13 +1897,13 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.39.17",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.17.tgz",
"integrity": "sha512-Aim4lFqhbijnchl83RLfABWueSGs1oUCSv0mru91QdhpXQeNKprIdRO9LWA4cYkJvuYTKGJN7++9MXx8XW43ag==",
"version": "6.40.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.40.0.tgz",
"integrity": "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
"@codemirror/state": "^6.6.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
@ -6820,9 +6820,9 @@
}
},
"node_modules/core-js": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz",
"integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==",
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz",
"integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@ -9634,9 +9634,9 @@
}
},
"node_modules/flatted": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},

View File

@ -43,12 +43,12 @@
"@babel/plugin-syntax-import-assertions": "^7.28.6",
"@babel/plugin-transform-runtime": "^7.29.0",
"@babel/preset-env": "^7.29.2",
"@babel/runtime": "^7.28.6",
"@babel/runtime": "^7.29.2",
"@codemirror/commands": "^6.10.3",
"@codemirror/language": "^6.12.2",
"@codemirror/search": "^6.6.0",
"@codemirror/state": "^6.5.4",
"@codemirror/view": "^6.39.17",
"@codemirror/view": "^6.40.0",
"autoprefixer": "^10.4.27",
"babel-loader": "^10.1.1",
"base64-loader": "^1.0.0",
@ -57,7 +57,7 @@
"colors": "^1.4.0",
"compression-webpack-plugin": "^11.1.0",
"copy-webpack-plugin": "^13.0.1",
"core-js": "^3.48.0",
"core-js": "^3.49.0",
"cspell": "^8.19.4",
"css-loader": "7.1.4",
"eslint": "^9.39.4",

View File

@ -55,8 +55,15 @@ class Chef {
progress = await recipe.execute(this.dish, progress);
} catch (err) {
log.error(err);
let displayStr;
if ("displayStr" in err) {
displayStr = err.displayStr;
} else {
displayStr = err.toString();
}
error = {
displayStr: err.displayStr,
displayStr: displayStr,
};
progress = err.progress;
}

7
src/core/Ingredient.mjs Executable file → Normal file
View File

@ -5,7 +5,8 @@
*/
import Utils from "./Utils.mjs";
import {fromHex} from "./lib/Hex.mjs";
import { fromHex } from "./lib/Hex.mjs";
import OperationError from "./errors/OperationError.mjs";
/**
* The arguments to operations.
@ -119,7 +120,9 @@ class Ingredient {
number = parseFloat(data);
if (isNaN(number)) {
const sample = Utils.truncate(data.toString(), 10);
throw "Invalid ingredient value. Not a number: " + sample;
throw new OperationError(
"Invalid ingredient value. Not a number: " + sample,
);
}
return number;
default:

View File

@ -5,6 +5,7 @@
*/
import Dish from "./Dish.mjs";
import OperationError from "./errors/OperationError.mjs";
import Ingredient from "./Ingredient.mjs";
/**
@ -223,7 +224,11 @@ class Operation {
*/
set ingValues(ingValues) {
ingValues.forEach((val, i) => {
try {
this._ingList[i].value = val;
} catch (err) {
throw new OperationError(`Failed to set value of ingredient '${this._ingList[i].name}': ${err}`);
}
});
}

View File

@ -70,11 +70,15 @@ class Recipe {
if (o instanceof Operation) {
return o;
} else {
try {
const op = new modules[o.module][o.name]();
op.ingValues = o.ingValues;
op.breakpoint = o.breakpoint;
op.disabled = o.disabled;
return op;
} catch (err) {
throw new Error(`Failed to hydrate operation '${o.name}': ${err}`);
}
}
});
}

View File

@ -249,6 +249,7 @@
"DNS over HTTPS",
"Strip HTTP headers",
"Dechunk HTTP response",
"Parse Ethernet frame",
"Parse User Agent",
"Parse IP range",
"Parse IPv6 address",

View File

@ -30,7 +30,12 @@ class Jq extends Operation {
name: "Query",
type: "string",
value: ""
}
},
{
name: "Raw",
type: "boolean",
value: false
},
];
}
@ -40,7 +45,7 @@ class Jq extends Operation {
* @returns {string}
*/
run(input, args) {
const [query] = args;
const [query, raw] = args;
let result;
try {
@ -48,9 +53,12 @@ class Jq extends Operation {
} catch (err) {
throw new OperationError(`Invalid jq expression: ${err.message}`);
}
if (raw && typeof result === "string") {
return result;
} else {
return JSON.stringify(result);
}
}
}

View File

@ -0,0 +1,115 @@
/**
* @author tedk [tedk@ted.do]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import {fromHex, toHex} from "../lib/Hex.mjs";
/**
* Parse Ethernet frame operation
*/
class ParseEthernetFrame extends Operation {
/**
* ParseEthernetFrame constructor
*/
constructor() {
super();
this.name = "Parse Ethernet frame";
this.module = "Default";
this.description = "Parses an Ethernet frame and either shows the deduced values (Source and destination MAC, VLANs) or returns the packet data.<br /><br />Good for use in conjunction with the Parse IPv4, and Parse TCP/UDP recipes.";
this.infoURL = "https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer";
this.inputType = "string";
this.outputType = "html";
this.args = [
{
name: "Input type",
type: "option",
value: [
"Raw", "Hex"
],
defaultIndex: 0,
},
{
name: "Return type",
type: "option",
value: [
"Text output", "Packet data", "Packet data (hex)",
],
defaultIndex: 0,
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const format = args[0];
const outputFormat = args[1];
if (format === "Hex") {
input = fromHex(input);
} else if (format === "Raw") {
input = new Uint8Array(Utils.strToArrayBuffer(input));
} else {
throw new OperationError("Invalid input format selected.");
}
const destinationMac = input.slice(0, 6);
const sourceMac = input.slice(6, 12);
let offset = 12;
const vlans = [];
while (offset < input.length) {
const ethType = Utils.byteArrayToChars(input.slice(offset, offset+2));
offset += 2;
if (ethType === "\x08\x00") {
break;
} else if (ethType === "\x81\x00" || ethType === "\x88\xA8") {
// Parse the VLAN tag:
// [0000] 0000 0000 0000
// ^^^ PRIO - Ignored
// ^ DEI - Ignored
// ^^^^ ^^^^ ^^^^ VLAN ID
const vlanTag = input.slice(offset+2, offset+4);
vlans.push((vlanTag[0] & 0b00001111) << 4 | vlanTag[1]);
offset += 2;
} else {
break;
}
}
const packetData = input.slice(offset);
if (outputFormat === "Packet data") {
return Utils.byteArrayToChars(packetData);
} else if (outputFormat === "Packet data (hex)") {
return toHex(packetData);
} else if (outputFormat === "Text output") {
let retval = `Source MAC: ${toHex(sourceMac, ":")}\nDestination MAC: ${toHex(destinationMac, ":")}\n`;
if (vlans.length > 0) {
retval += `VLAN: ${vlans.join(", ")}\n`;
}
retval += `Data:\n${toHex(packetData)}`;
return retval;
}
}
}
export default ParseEthernetFrame;

View File

@ -33,6 +33,12 @@ class ParseIPv4Header extends Operation {
"name": "Input format",
"type": "option",
"value": ["Hex", "Raw"]
},
{
"name": "Output format",
"type": "option",
"value": ["Table", "Data (hex)", "Data (raw)"],
defaultIndex: 0,
}
];
}
@ -44,6 +50,8 @@ class ParseIPv4Header extends Operation {
*/
run(input, args) {
const format = args[0];
const outputFormat = args[1];
let output;
if (format === "Hex") {
@ -98,6 +106,9 @@ class ParseIPv4Header extends Operation {
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
}
const data = input.slice(ihl * 4);
if (outputFormat === "Table") {
output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
<tr><td>Version</td><td>${version}</td></tr>
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
@ -116,13 +127,19 @@ class ParseIPv4Header extends Operation {
<tr><td>Protocol</td><td>${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})</td></tr>
<tr><td>Header checksum</td><td>${checksumResult}</td></tr>
<tr><td>Source IP address</td><td>${ipv4ToStr(srcIP)}</td></tr>
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>`;
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>
<tr><td>Data (hex)</td><td>${toHex(data)}</td></tr>`;
if (ihl > 5) {
output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;
}
return output + "</table>";
} else if (outputFormat === "Data (hex)") {
return toHex(data);
} else if (outputFormat === "Data (raw)") {
return Utils.byteArrayToChars(data);
}
}
}

View File

@ -130,6 +130,7 @@ class Manager {
// Controls
document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
document.getElementById("auto-bake").addEventListener("keydown", this.controls.autoBakeKeyboardHandler.bind(this.controls));
document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));

View File

@ -145,7 +145,7 @@
<button type="button" aria-label="Edit Favourites" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
<i class="material-icons" aria-hidden="true">star</i>
</button>
<div id="content-wrapper">
<div tabindex="0" id="content-wrapper">
<div id="banner" class="row">
<div class="col" style="text-align: left; padding-left: 10px;">
<a href="#" data-toggle="modal" data-target="#download-modal" data-help-title="Downloading CyberChef" data-help="<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>">Download CyberChef <i class="material-icons">file_download</i></a>
@ -173,7 +173,7 @@
Operations
<span class="op-count"></span>
</div>
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="0" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
<ul id="search-results" class="op-list"></ul>
<div id="categories" class="panel-group no-select"></div>
</div>
@ -584,22 +584,22 @@
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
<a id="tab-1" class="nav-link active" href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
FAQs
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab">
<a id="tab-2" class="nav-link" href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab">
Report a bug
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" href="#about" aria-controls="messages" role="tab" data-toggle="tab">
<a id="tab-3" class="nav-link" href="#about" aria-controls="messages" role="tab" data-toggle="tab">
About
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
<a id="tab-4" class="nav-link" href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
Keybindings
</a>
</li>

View File

@ -26,6 +26,36 @@
color: var(--banner-url-colour);
}
#options:focus {
background-color: #eef3ec;
border: solid black 2px;
border-radius: 4px;
}
#support:focus {
background-color: #eef3ec;
border: solid black 2px;
border-radius: 4px;
}
#notice:focus {
background-color: #eef3ec;
border: solid black 2px;
border-radius: 4px;
}
#banner .col a:focus {
background-color: #eef3ec;
border: solid black 2px;
border-radius: 4px;
}
#notice-wrapper #notice:focus {
background-color: #eef3ec;
border: solid black 2px;
border-radius: 4px;
}
#notice-wrapper {
text-align: center;
overflow: hidden;

View File

@ -78,6 +78,11 @@
border-left: 2px solid var(--primary-border-colour);
}
p a:focus {
color: #0a6ebd;
text-decoration: underline;
}
.checkbox label input[type=checkbox]+.checkbox-decorator .check,
.checkbox label input[type=checkbox]+.checkbox-decorator .check::before {
border-color: var(--input-border-colour);

View File

@ -57,6 +57,18 @@ class ControlsWaiter {
}
}
/**
* Checks or unchecks the Auto Bake checkbox with "Enter"
* @param {Event} ev
*/
autoBakeKeyboardHandler(ev) {
const checkBox = document.getElementById("auto-bake");
ev.preventDefault();
if (ev.key === "Enter" || ev.key === " ") {
checkBox.checked = !checkBox.checked;
}
}
/**
* Handler to trigger baking.
@ -387,6 +399,18 @@ class ControlsWaiter {
*/
supportButtonClick(e) {
e.preventDefault();
const faqs = document.getElementById("faqs");
const faqsAElement = faqs.getElementsByTagName("a");
for (let i = 0; i < faqsAElement.length; i++) {
faqsAElement[i].setAttribute("tabindex", "0");
faqsAElement[i].addEventListener("keydown", this.navigateFAQList, false);
}
const tabs = document.querySelectorAll('[role="tab"]');
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("keydown", this.changeTabs, false);
}
const reportBugInfo = document.getElementById("report-bug-info");
const saveLink = this.generateStateUrl(true, true, null, null, "https://gchq.github.io/CyberChef/");
@ -403,6 +427,64 @@ ${navigator.userAgent}
}
/**
* @param {Event} ev
*/
changeTabs(ev) {
const tab = ev.target;
ev.preventDefault();
ev.stopPropagation();
if (ev.key === "ArrowRight") {
const nextTab = tab.parentElement;
if (nextTab.nextElementSibling === null) {
tab.parentElement.parentElement.firstElementChild.firstElementChild.focus();
} else {
nextTab.nextElementSibling.firstElementChild.focus();
}
} else if (ev.key === "ArrowLeft") {
const prevTab = tab.parentElement;
if (prevTab.previousElementSibling === null) {
tab.parentElement.parentElement.lastElementChild.firstElementChild.focus();
} else {
prevTab.previousElementSibling.firstElementChild.focus();
}
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-1")) {
document.getElementById("faqs").querySelector("[class='btn btn-primary']").focus();
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-2")) {
document.getElementById("report-bug").querySelector("[class='btn btn-primary']").focus();
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-3")) {
document.getElementById("about").querySelector("[href]").focus();
} else if (ev.key === "Tab" && !ev.shiftKey && ev.target === document.getElementById("tab-4")) {
const button = document.getElementById("support-modal").getElementsByClassName("modal-footer");
const close = button[0].firstElementChild;
close.focus();
} else if (ev.key === "Enter" || ev.key === "Space" || ev.key === " ") {
tab.click();
}
}
/**
* @param {Event} ev
*/
navigateFAQList(ev) {
const el = ev.target.nextElementSibling;
if (ev.key === "Enter" || ev.key === "Space" || ev.key === " ") {
ev.preventDefault();
const question = el.classList;
if (question !== undefined && question.value) {
if (!question.value.includes("show")) {
question.add("show");
} else if (question.contains("show")) {
question.remove("show");
}
}
}
}
/**
* Shows the stale indicator to show that the input or recipe has changed
* since the last bake.

View File

@ -268,7 +268,7 @@ module.exports = {
testOpHtml(browser, "Parse colour code", "#000", ".colorpicker-preview", "rgb(0, 0, 0)");
testOpHtml(browser, "Parse DateTime", "01/12/2000 13:00:00", "", /Date: Friday 1st December 2000/);
// testOp(browser, "Parse IP range", "test input", "test_output");
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:last-child td:last-child", "192.168.12.2");
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:nth-last-child(2) td:last-child", "192.168.12.2");
// testOp(browser, "Parse IPv6 address", "test input", "test_output");
// testOp(browser, "Parse ObjectID timestamp", "test input", "test_output");
// testOp(browser, "Parse QR Code", "test input", "test_output");

View File

@ -122,6 +122,7 @@ import "./tests/NetBIOS.mjs";
import "./tests/NormaliseUnicode.mjs";
import "./tests/NTLM.mjs";
import "./tests/OTP.mjs";
import "./tests/ParseEthernetFrame.mjs";
import "./tests/ParseIPRange.mjs";
import "./tests/ParseObjectIDTimestamp.mjs";
import "./tests/ParseQRCode.mjs";

View File

@ -0,0 +1,32 @@
/**
* Jq tests.
*
* @author rtpt-romankarwacik [roman.karwacik@redteam-pentesting.de]
*
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Get raw JSON Property",
input: '{"data": "testString\\u0000"}',
expectedOutput: "testString\u0000",
recipeConfig: [
{
op: "Jq",
args: [".data", true],
},
],
},
{
name: "Get JSON Property",
input: '{"data": "testString\\u0000"}',
expectedOutput: "\"testString\\u0000\"",
recipeConfig: [
{
op: "Jq",
args: [".data", false],
},
],
},
]);

View File

@ -0,0 +1,45 @@
/**
* Parse Ethernet frame tests.
*
* @author tedk [tedk@ted.do]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Parse plain Ethernet frame",
input: "000000000000ffffffffffff08004500",
expectedOutput: "Source MAC: ff:ff:ff:ff:ff:ff\nDestination MAC: 00:00:00:00:00:00\nData:\n45 00",
recipeConfig: [
{
"op": "Parse Ethernet frame",
"args": ["Hex", "Text output"]
}
]
},
// Example PCAP data from: https://packetlife.net/captures/protocol/vlan/
{
name: "Parse Ethernet frame with one VLAN tag (802.1q)",
input: "01000ccdcdd00013c3dfae188100a0760165aaaa",
expectedOutput: "Source MAC: 00:13:c3:df:ae:18\nDestination MAC: 01:00:0c:cd:cd:d0\nVLAN: 117\nData:\naa aa",
recipeConfig: [
{
"op": "Parse Ethernet frame",
"args": ["Hex", "Text output"]
}
]
},
{
name: "Parse Ethernet frame with two VLAN tags (802.1ad)",
input: "0019aa7de688002155c8f13c810000d18100001408004500",
expectedOutput: "Source MAC: 00:21:55:c8:f1:3c\nDestination MAC: 00:19:aa:7d:e6:88\nVLAN: 16, 128\nData:\n45 00",
recipeConfig: [
{
"op": "Parse Ethernet frame",
"args": ["Hex", "Text output"]
}
]
}
]);