152 lines
4.3 KiB
JavaScript
152 lines
4.3 KiB
JavaScript
/**
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
* @author ko80240 [csk.dev@proton.me]
|
|
* @copyright Crown Copyright 2023
|
|
* @license Apache-2.0
|
|
*/
|
|
|
|
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
|
|
*/
|
|
class AnalyseUUID extends Operation {
|
|
|
|
/**
|
|
* AnalyseUUID constructor
|
|
*/
|
|
constructor() {
|
|
super();
|
|
|
|
this.name = "Analyse UUID";
|
|
this.module = "Crypto";
|
|
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 = [
|
|
{
|
|
name: "Include Metadata",
|
|
type: "boolean",
|
|
value: true
|
|
}
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {string} input - Expects a valid UUID string
|
|
* @param {Object[]} args
|
|
* @returns {string}
|
|
*/
|
|
run(input, args) {
|
|
input = input.trim();
|
|
|
|
let uuidVersion, uuidBytes;
|
|
try {
|
|
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");
|
|
}
|