Added metadata extraction for UUID strings. This includes expanding on the operation to process versions 1,6,7.

Added option to include metadata
Added tests and file to test index
This commit is contained in:
ko80240 2026-04-16 14:56:01 +00:00
parent 093346859d
commit 5d033efb35
5 changed files with 221 additions and 10 deletions

View File

@ -1,5 +1,5 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @author ko80240 [csk.dev@proton.me]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
@ -8,6 +8,7 @@ import * as uuid from "uuid";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { toHex } from "../lib/Hex.mjs";
/**
* Analyse UUID operation
@ -22,27 +23,128 @@ class AnalyseUUID extends Operation {
this.name = "Analyse UUID";
this.module = "Crypto";
this.description = "Tries to determine information about a given UUID and suggests which version may have been used to generate it";
this.description = "Operation for extracting metadata and detecting the version of a given UUID.";
this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier";
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.args = [
{
name: "Include Metadata",
type: "boolean",
value: true
}
];
}
/**
* @param {string} input
* @param {string} input - Expects a valid UUID string
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = input.trim();
let uuidVersion, uuidBytes;
try {
const uuidVersion = uuid.version(input);
return "UUID version: " + uuidVersion;
uuidVersion = uuid.version(input); // Re-using the uuid library to extract version
uuidBytes = uuid.parse(input); // Re-using the uuid library to parse bytes
} catch (error) {
throw new OperationError("Invalid UUID");
}
const [includeMetadata] = args;
const dv = new DataView(uuidBytes.buffer, uuidBytes.byteOffset, uuidBytes.byteLength); // Dataview helps handle the multi-byte ints
const uuidInteger = (dv.getBigUint64(0) << 64n) | dv.getBigUint64(8);
const sections = [`Version:\n${uuidVersion}`];
if (includeMetadata) {
const parser = UUID_PARSERS[uuidVersion];
const decoded = parser?.(uuidBytes, dv);
sections.push(formatDecoded(decoded));
}
sections.push(`UUID Integer:\n${uuidInteger}`);
return sections.filter(Boolean).join("\n\n");
}
}
export default AnalyseUUID;
/**
* Metadata can be extracted for versions 1, 6, and 7.
* Enum-like frozen mapping of UUID version to parser function.
*/
const UUID_PARSERS = Object.freeze({
1: parsev1v6,
6: parsev1v6,
7: parsev7,
});
/**
* Versions 1 and 6. Note 6 is a re-order of 1.
* Version 1 == layout: timeLow(32) | timeMid(16) | timeHi(12)
* Version 6 == layout: timeHi(32) | timeMid(16) | timeLow(12)
*/
function parsev1v6(uuidBytes, dv) {
const isV1 = (uuidBytes[6] >> 4) === 1;
const timeStamp =
isV1 ? (
(BigInt(dv.getUint16(6) & 0x0fff) << 48n) | // mask off version bits
(BigInt(dv.getUint16(4)) << 32n) |
BigInt(dv.getUint32(0))
) : (
(BigInt(dv.getUint32(0)) << 28n) |
(BigInt(dv.getUint16(4)) << 12n) |
(BigInt(dv.getUint16(6) & 0x0fff))
);
// Convert to Unix time
const milliseconds =
Number(
(timeStamp - 122192928000000000n) / 10000n
);
return {
timestamp: milliseconds,
isoTimestamp: new Date(milliseconds).toISOString(),
clock: ((uuidBytes[8] & 0x3f) << 8) | uuidBytes[9],
node: toHex(uuidBytes.slice(10), ":").toUpperCase()
};
}
/** Version 7 */
function parsev7(uuidBytes, dv) {
const milliseconds = Number((BigInt(dv.getUint32(0)) << 16n) | BigInt(dv.getUint16(4)));
return {
timestamp: milliseconds,
isoTimestamp: new Date(milliseconds).toISOString(),
randA: ((uuidBytes[6] & 0x0f) << 8) | uuidBytes[7],
randB: toHex(uuidBytes.slice(8), "").toUpperCase()
};
}
/**
* Formats metadata
*
* @param {Object|undefined} decoded
* @returns {string}
*/
function formatDecoded(decoded) {
if (!decoded) return "No metadata available. Only versions 1, 6, 7 are supported.";
return Object.entries({
"Timestamp": decoded.timestamp,
"Timestamp (ISO)": decoded.isoTimestamp,
"Node": decoded.node,
"Clock": decoded.clock,
"Rand A": decoded.randA,
"Rand B": decoded.randB
})
.filter(([, value]) => value !== undefined)
.map(([label, value]) => `${label}:\n${value}`)
.join("\n\n");
}

43
tatus Normal file
View File

@ -0,0 +1,43 @@
e5305a4f (HEAD -> feature/analyseUUID) Feature: Added metadata extraction for AnalyseUUID operation with tests and test index.
53583814 (origin/master, origin/HEAD, master) Regular Expression operation email address regex: Support IPv4 domains (#2167)
f646065c Rewriting fixCryptoApiImports and fixSnackbarMarkup to js to make it OS agnostic (#2298)
2eb8810b chore (deps): bump basic-ftp from 5.2.1 to 5.2.2 (#2317)
d8830e2b chore (deps): bump axios from 1.13.6 to 1.15.0 (#2316)
7b9ff751 chore (deps): bump webpack from 5.105.4 to 5.106.0 (#2315)
c68cc5a0 chore (deps): bump basic-ftp from 5.2.0 to 5.2.1 (#2313)
39f97a2a Update vulnerable dependencies (#2311)
42adf5b1 (tag: v10.23.0) Bump v10.23.0 (#2310)
167cc398 Properly escape HTML entities in sampleDelim to avoid XSS issue (#2307)
33314ac6 chore (deps): bump lodash from 4.17.23 to 4.18.1 (#2304)
e1352065 chore (deps): bump @codemirror/view from 6.40.0 to 6.41.0 (#2305)
eea8bcd2 chore (deps): bump the patch-updates group with 2 updates (#2303)
7d9fd4b2 chore (deps): bump @xmldom/xmldom from 0.8.11 to 0.8.12 (#2302)
80286f1e chore (deps): bump picomatch (#2299)
c00824a8 chore (deps): bump node-forge from 1.3.3 to 1.4.0 (#2297)
f9184d39 chore (deps): bump the patch-updates group with 3 updates (#2296)
088da9de chore (deps) bump chromedriver from 130.0.4 to 146.0.6 (#2292)
6aa98b4a ParseEthernetFrame - Fix vlan calculation (#2295)
b0fa1f8d Add pull request template with AI usage disclosure (#2279)
ff63ec97 fix: return empty output for zero-length To Modhex input (#2249)
9cf82cc1 Added tab focus to top banner and navigation to About/Support Modal (#1733)
78d40eab Add Parse Ethernet frame Operation, allow Parse IPv4 Header to cascade (#1722)
2b370b96 Selection and Deselection of autobake checkbox using keyboard (#1727)
38a0adaf chore (deps): bump @babel/runtime from 7.28.6 to 7.29.2 (#2263)
8c4a55b2 Add more helpful error for when numerical ingredient is left empty (#1540)
a1f92082 chore (deps): bump @codemirror/view from 6.39.17 to 6.40.0 (#2262)
32ff3cd5 Bump flatted from 3.3.2 to 3.4.2 (#2266)
290f824e feat: add Raw option for Jq operation (#2237)
7f4f90e4 chore (deps): bump core-js from 3.48.0 to 3.49.0 (#2261)
607acbd2 chore (deps): bump the patch-updates group with 6 updates (#2260)
f7bbb330 Add Extract Audio Metadata operation (#2170)
d9dacf6b Fix Jq issue (#2210)
19bc8169 Configure dependabot updates (#2259)
eef8d55d fix(A1Z26): return empty string instead of empty array for empty input (#2257)
4deaa0de Fix broken Docker link in README (#2250)
d195a51e Update some dependencies, including a number causing npm audit warnings (#2236)
47ba0585 Bump axios from 1.7.9 to 1.13.6 (#2234)
64224dac Bump jws from 3.2.2 to 3.2.3 (#2235)
76bf4f44 Bump pbkdf2 from 3.1.2 to 3.1.5 (#2229)
c39ae52f Bump form-data from 4.0.1 to 4.0.5 (#2228)
3f91df23 Bump basic-ftp from 5.0.5 to 5.2.0 (#2231)
a19261d5 feat: add ARM disassembler operation (#2156)

View File

@ -589,8 +589,7 @@ Password: 282760`;
...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => {
const uuid = chef.generateUUID("", { "version": `v${version}` }).toString();
const result = chef.analyseUUID(uuid).toString();
const expected = `UUID version: ${version}`;
assert.strictEqual(result, expected);
assert.ok(result.startsWith(`Version:\n${version}\n`), `Expected output to start with "Version:\\n${version}\\n", got: ${result}`);
})),
it("Generate UUID using defaults", () => {
@ -598,7 +597,7 @@ Password: 282760`;
assert.ok(uuid);
const analysis = chef.analyseUUID(uuid).toString();
assert.strictEqual(analysis, "UUID version: 4");
assert.ok(analysis.startsWith("Version:\n4\n"), `Expected output to start with "Version:\\n4\\n", got: ${analysis}`);
}),
it("Gzip, Gunzip", () => {

View File

@ -16,6 +16,7 @@ import { setLongTestFailure, logTestReport } from "../lib/utils.mjs";
import TestRegister from "../lib/TestRegister.mjs";
import "./tests/A1Z26CipherDecode.mjs";
import "./tests/AESKeyWrap.mjs";
import "./tests/AnalyseUUID.mjs";
import "./tests/AlternatingCaps.mjs";
import "./tests/AvroToJSON.mjs";
import "./tests/BaconCipher.mjs";

View File

@ -0,0 +1,66 @@
/**
* Analyse UUID tests
*
* @author ko80240 [csk.dev@proton.me]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
"name": "Analyse UUID: v1 UUID extracts timestamp, clock, and node",
"input": "cefa1760-28ee-11f1-9f95-1fb76af3e239",
"expectedOutput": "Version:\n1\n\nTimestamp:\n1774514156502\n\nTimestamp (ISO):\n2026-03-26T08:35:56.502Z\n\nNode:\n1F:B7:6A:F3:E2:39\n\nClock:\n8085\n\nUUID Integer:\n275119515460318071558429785403790975545",
"recipeConfig": [
{
"op": "Analyse UUID",
"args": [true]
}
]
},
{
"name": "Analyse UUID: v7 UUID extracts timestamp, randA, and randB",
"input": "019d294a-af64-7728-9524-26da08f50708",
"expectedOutput": "Version:\n7\n\nTimestamp:\n1774514253668\n\nTimestamp (ISO):\n2026-03-26T08:37:33.668Z\n\nRand A:\n1832\n\nRand B:\n952426DA08F50708\n\nUUID Integer:\n2145256098533991595556290452700595976",
"recipeConfig": [
{
"op": "Analyse UUID",
"args": [true]
}
]
},
{
"name": "Analyse UUID: v4 UUID should show no metadata - not possible",
"input": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"expectedOutput": "Version:\n4\n\nNo metadata available. Only versions 1, 6, 7 are supported.\n\nUUID Integer:\n324969006592305634633390616021200786553",
"recipeConfig": [
{
"op": "Analyse UUID",
"args": [true]
}
]
},
{
"name": "Analyse UUID: if the 'Include Metadata' option is false it should return not metadata",
"input": "cefa1760-28ee-11f1-9f95-1fb76af3e239",
"expectedOutput": "Version:\n1\n\nUUID Integer:\n275119515460318071558429785403790975545",
"recipeConfig": [
{
"op": "Analyse UUID",
"args": [false]
}
]
},
{
"name": "Analyse UUID: invalid UUID should return error message",
"input": "not-a-uuid",
"expectedOutput": "Invalid UUID",
"recipeConfig": [
{
"op": "Analyse UUID",
"args": [true]
}
]
}
]);