Formatted.

This commit is contained in:
Vasyl Palamarchuk
2026-09-15 12:00:52 -07:00
parent 60fde23b28
commit f67eebf32f
100 changed files with 1859 additions and 751 deletions
+194 -33
View File
@@ -6,38 +6,199 @@ export function requireBytes(data) {
}
export function assertRange(data, offset, length) {
requireBytes(data);
if (!Number.isInteger(offset)||!Number.isInteger(length)||offset<0||length<0||offset+length>data.length) throw new RangeError("Range outside buffer");
if (
!Number.isInteger(offset) ||
!Number.isInteger(length) ||
offset < 0 ||
length < 0 ||
offset + length > data.length
)
throw new RangeError("Range outside buffer");
}
export function cloneBytes(data){ requireBytes(data); return new Uint8Array(data); }
export function sliceBytes(data,offset,length){ assertRange(data,offset,length); return data.slice(offset,offset+length); }
export function bytesToHex(data,separator=" "){ requireBytes(data); return Array.from(data,b=>b.toString(16).padStart(2,"0")).join(separator).toUpperCase(); }
export function hexToBytes(text){
const input=String(text);
if(/[^0-9a-f\s,:-]/i.test(input)) throw new Error("Invalid character in hex text");
const s=input.replace(/[\s,:-]/g,"");
if(s.length%2) throw new Error("Hex text must contain complete bytes");
const out=new Uint8Array(s.length/2); for(let i=0;i<out.length;i++) out[i]=parseInt(s.slice(i*2,i*2+2),16); return out;
export function cloneBytes(data) {
requireBytes(data);
return new Uint8Array(data);
}
export function sliceBytes(data, offset, length) {
assertRange(data, offset, length);
return data.slice(offset, offset + length);
}
export function bytesToHex(data, separator = " ") {
requireBytes(data);
return Array.from(data, (b) => b.toString(16).padStart(2, "0"))
.join(separator)
.toUpperCase();
}
export function hexToBytes(text) {
const input = String(text);
if (/[^0-9a-f\s,:-]/i.test(input))
throw new Error("Invalid character in hex text");
const s = input.replace(/[\s,:-]/g, "");
if (s.length % 2) throw new Error("Hex text must contain complete bytes");
const out = new Uint8Array(s.length / 2);
for (let i = 0; i < out.length; i++)
out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
return out;
}
export function asciiPreview(data, replacement = ".") {
requireBytes(data);
return Array.from(data, (b) =>
b >= 32 && b <= 126 ? String.fromCharCode(b) : replacement,
).join("");
}
export function readU16LE(d, o) {
assertRange(d, o, 2);
return d[o] | (d[o + 1] << 8);
}
export function readU16BE(d, o) {
assertRange(d, o, 2);
return (d[o] << 8) | d[o + 1];
}
export function readU32LE(d, o) {
assertRange(d, o, 4);
return (d[o] | (d[o + 1] << 8) | (d[o + 2] << 16) | (d[o + 3] << 24)) >>> 0;
}
export function readU32BE(d, o) {
assertRange(d, o, 4);
return ((d[o] << 24) | (d[o + 1] << 16) | (d[o + 2] << 8) | d[o + 3]) >>> 0;
}
export function writeU16LE(d, o, v) {
assertRange(d, o, 2);
d[o] = v & 255;
d[o + 1] = (v >>> 8) & 255;
}
export function writeU16BE(d, o, v) {
assertRange(d, o, 2);
d[o] = (v >>> 8) & 255;
d[o + 1] = v & 255;
}
export function writeU32LE(d, o, v) {
assertRange(d, o, 4);
for (let i = 0; i < 4; i++) d[o + i] = (v >>> (8 * i)) & 255;
}
export function writeU32BE(d, o, v) {
assertRange(d, o, 4);
for (let i = 0; i < 4; i++) d[o + i] = (v >>> (8 * (3 - i))) & 255;
}
export function findPattern(data, pattern, start = 0) {
requireBytes(data);
requireBytes(pattern);
const hits = [];
for (let i = start; i <= data.length - pattern.length; i++) {
let ok = true;
for (let j = 0; j < pattern.length; j++)
if (data[i + j] !== pattern[j]) {
ok = false;
break;
}
if (ok) hits.push(i);
}
return hits;
}
export function countByte(data, value) {
requireBytes(data);
let n = 0;
for (const b of data) if (b === (value & 255)) n++;
return n;
}
export function isBlank(
data,
offset = 0,
length = data.length - offset,
blank = 0xff,
) {
assertRange(data, offset, length);
for (let i = offset; i < offset + length; i++)
if (data[i] !== blank) return false;
return true;
}
export function diffRanges(a, b) {
requireBytes(a);
requireBytes(b);
if (a.length !== b.length) throw new Error("Length mismatch");
const out = [];
let s = -1;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i] && s < 0) s = i;
if ((a[i] === b[i] || i === a.length - 1) && s >= 0) {
const e = a[i] === b[i] ? i - 1 : i;
out.push({ start: s, end: e, length: e - s + 1 });
s = -1;
}
}
return out;
}
export function changedByteCount(a, b) {
return diffRanges(a, b).reduce((n, r) => n + r.length, 0);
}
export function sum8(data) {
requireBytes(data);
let s = 0;
for (const b of data) s = (s + b) & 255;
return s;
}
export function xor8(data) {
requireBytes(data);
let x = 0;
for (const b of data) x ^= b;
return x;
}
export function crc16CcittFalse(data) {
requireBytes(data);
let crc = 0xffff;
for (const b of data) {
crc ^= b << 8;
for (let i = 0; i < 8; i++)
crc = (crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1) & 0xffff;
}
return crc;
}
export function crc32(data) {
requireBytes(data);
let crc = 0xffffffff;
for (const b of data) {
crc ^= b;
for (let i = 0; i < 8; i++) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
}
return (crc ^ 0xffffffff) >>> 0;
}
export function adler32(data) {
requireBytes(data);
let a = 1,
b = 0;
for (const v of data) {
a = (a + v) % 65521;
b = (b + a) % 65521;
}
return ((b << 16) | a) >>> 0;
}
export function bcdToInt(byte) {
const hi = (byte >>> 4) & 15,
lo = byte & 15;
if (hi > 9 || lo > 9) throw new Error("Invalid BCD");
return hi * 10 + lo;
}
export function intToBcd(value) {
if (!Number.isInteger(value) || value < 0 || value > 99)
throw new RangeError("BCD value must be 0..99");
return (Math.floor(value / 10) << 4) | (value % 10);
}
export function constantTimeEqual(a, b) {
requireBytes(a);
requireBytes(b);
if (a.length !== b.length) return false;
let d = 0;
for (let i = 0; i < a.length; i++) d |= a[i] ^ b[i];
return d === 0;
}
export function allowListedWrite(copy, offset, newBytes, allowedRanges) {
requireBytes(copy);
requireBytes(newBytes);
assertRange(copy, offset, newBytes.length);
const ok = allowedRanges.some(
(r) => offset >= r.start && offset + newBytes.length - 1 <= r.end,
);
if (!ok) throw new Error("Write is outside allow-list");
copy.set(newBytes, offset);
return copy;
}
export function asciiPreview(data,replacement="."){ requireBytes(data); return Array.from(data,b=>b>=32&&b<=126?String.fromCharCode(b):replacement).join(""); }
export function readU16LE(d,o){assertRange(d,o,2);return d[o]|(d[o+1]<<8);}
export function readU16BE(d,o){assertRange(d,o,2);return (d[o]<<8)|d[o+1];}
export function readU32LE(d,o){assertRange(d,o,4);return (d[o]|d[o+1]<<8|d[o+2]<<16|d[o+3]<<24)>>>0;}
export function readU32BE(d,o){assertRange(d,o,4);return ((d[o]<<24)|(d[o+1]<<16)|(d[o+2]<<8)|d[o+3])>>>0;}
export function writeU16LE(d,o,v){assertRange(d,o,2);d[o]=v&255;d[o+1]=(v>>>8)&255;}
export function writeU16BE(d,o,v){assertRange(d,o,2);d[o]=(v>>>8)&255;d[o+1]=v&255;}
export function writeU32LE(d,o,v){assertRange(d,o,4);for(let i=0;i<4;i++)d[o+i]=(v>>>(8*i))&255;}
export function writeU32BE(d,o,v){assertRange(d,o,4);for(let i=0;i<4;i++)d[o+i]=(v>>>(8*(3-i)))&255;}
export function findPattern(data,pattern,start=0){requireBytes(data);requireBytes(pattern);const hits=[];for(let i=start;i<=data.length-pattern.length;i++){let ok=true;for(let j=0;j<pattern.length;j++)if(data[i+j]!==pattern[j]){ok=false;break;}if(ok)hits.push(i);}return hits;}
export function countByte(data,value){requireBytes(data);let n=0;for(const b of data)if(b===(value&255))n++;return n;}
export function isBlank(data,offset=0,length=data.length-offset,blank=0xFF){assertRange(data,offset,length);for(let i=offset;i<offset+length;i++)if(data[i]!==blank)return false;return true;}
export function diffRanges(a,b){requireBytes(a);requireBytes(b);if(a.length!==b.length)throw new Error("Length mismatch");const out=[];let s=-1;for(let i=0;i<a.length;i++){if(a[i]!==b[i]&&s<0)s=i;if((a[i]===b[i]||i===a.length-1)&&s>=0){const e=a[i]===b[i]?i-1:i;out.push({start:s,end:e,length:e-s+1});s=-1;}}return out;}
export function changedByteCount(a,b){return diffRanges(a,b).reduce((n,r)=>n+r.length,0);}
export function sum8(data){requireBytes(data);let s=0;for(const b of data)s=(s+b)&255;return s;}
export function xor8(data){requireBytes(data);let x=0;for(const b of data)x^=b;return x;}
export function crc16CcittFalse(data){requireBytes(data);let crc=0xFFFF;for(const b of data){crc^=b<<8;for(let i=0;i<8;i++)crc=((crc&0x8000)?(crc<<1)^0x1021:crc<<1)&0xFFFF;}return crc;}
export function crc32(data){requireBytes(data);let crc=0xFFFFFFFF;for(const b of data){crc^=b;for(let i=0;i<8;i++)crc=(crc>>>1)^((crc&1)?0xEDB88320:0);}return (crc^0xFFFFFFFF)>>>0;}
export function adler32(data){requireBytes(data);let a=1,b=0;for(const v of data){a=(a+v)%65521;b=(b+a)%65521;}return ((b<<16)|a)>>>0;}
export function bcdToInt(byte){const hi=(byte>>>4)&15,lo=byte&15;if(hi>9||lo>9)throw new Error("Invalid BCD");return hi*10+lo;}
export function intToBcd(value){if(!Number.isInteger(value)||value<0||value>99)throw new RangeError("BCD value must be 0..99");return ((Math.floor(value/10)<<4)|(value%10));}
export function constantTimeEqual(a,b){requireBytes(a);requireBytes(b);if(a.length!==b.length)return false;let d=0;for(let i=0;i<a.length;i++)d|=a[i]^b[i];return d===0;}
export function allowListedWrite(copy,offset,newBytes,allowedRanges){requireBytes(copy);requireBytes(newBytes);assertRange(copy,offset,newBytes.length);const ok=allowedRanges.some(r=>offset>=r.start&&offset+newBytes.length-1<=r.end);if(!ok)throw new Error("Write is outside allow-list");copy.set(newBytes,offset);return copy;}
@@ -1,2 +1,15 @@
import { bytesToHex, asciiPreview, countByte, crc32 } from "../binary_utils.mjs";
export function inspect(data){return {length:data.length,first32:bytesToHex(data.slice(0,32)),ascii:asciiPreview(data.slice(0,64)),ffBytes:countByte(data,0xFF),crc32:crc32(data).toString(16).padStart(8,"0").toUpperCase()};}
import {
bytesToHex,
asciiPreview,
countByte,
crc32,
} from "../binary_utils.mjs";
export function inspect(data) {
return {
length: data.length,
first32: bytesToHex(data.slice(0, 32)),
ascii: asciiPreview(data.slice(0, 64)),
ffBytes: countByte(data, 0xff),
crc32: crc32(data).toString(16).padStart(8, "0").toUpperCase(),
};
}
@@ -1,2 +1,9 @@
import { constantTimeEqual, crc32 } from "../binary_utils.mjs";
export function verifyTwoReads(a,b){return {sameLength:a.length===b.length,identical:constantTimeEqual(a,b),crcA:crc32(a),crcB:crc32(b)};}
export function verifyTwoReads(a, b) {
return {
sameLength: a.length === b.length,
identical: constantTimeEqual(a, b),
crcA: crc32(a),
crcB: crc32(b),
};
}
@@ -1,2 +1,7 @@
import { findPattern, hexToBytes } from "../binary_utils.mjs";
export function inventory(data,patterns){return patterns.map(p=>({label:p.label,offsets:findPattern(data,hexToBytes(p.hex))}));}
export function inventory(data, patterns) {
return patterns.map((p) => ({
label: p.label,
offsets: findPattern(data, hexToBytes(p.hex)),
}));
}
@@ -1,3 +1,9 @@
import { cloneBytes, allowListedWrite, diffRanges } from "../binary_utils.mjs";
// Synthetic demonstration only. No vehicle-security data or real module offsets.
export function demo(input){const output=cloneBytes(input);allowListedWrite(output,4,new Uint8Array([0xAA,0x55]),[{start:4,end:5}]);return {output,changes:diffRanges(input,output)};}
export function demo(input) {
const output = cloneBytes(input);
allowListedWrite(output, 4, new Uint8Array([0xaa, 0x55]), [
{ start: 4, end: 5 },
]);
return { output, changes: diffRanges(input, output) };
}
@@ -1,2 +1,14 @@
import { hexToBytes,crc16CcittFalse,crc32,adler32 } from "../binary_utils.mjs";
export function selfTest(){const d=new TextEncoder().encode("123456789");return {crc16Ok:crc16CcittFalse(d)===0x29B1,crc32Ok:crc32(d)===0xCBF43926,adler32:adler32(d)};}
import {
hexToBytes,
crc16CcittFalse,
crc32,
adler32,
} from "../binary_utils.mjs";
export function selfTest() {
const d = new TextEncoder().encode("123456789");
return {
crc16Ok: crc16CcittFalse(d) === 0x29b1,
crc32Ok: crc32(d) === 0xcbf43926,
adler32: adler32(d),
};
}
@@ -1,2 +1,17 @@
const REQUIRED=["caseId","customerName","vehicleVin","proofOfOwnershipRef","authorizationRef","technician","timestampUtc","requestedService"];
export function validateAuthorizedJob(job){const missing=REQUIRED.filter(k=>!String(job[k]??"").trim());const vinOk=/^[A-HJ-NPR-Z0-9]{17}$/.test(String(job.vehicleVin||"").toUpperCase());return {valid:missing.length===0&&vinOk,missing,vinOk};}
const REQUIRED = [
"caseId",
"customerName",
"vehicleVin",
"proofOfOwnershipRef",
"authorizationRef",
"technician",
"timestampUtc",
"requestedService",
];
export function validateAuthorizedJob(job) {
const missing = REQUIRED.filter((k) => !String(job[k] ?? "").trim());
const vinOk = /^[A-HJ-NPR-Z0-9]{17}$/.test(
String(job.vehicleVin || "").toUpperCase(),
);
return { valid: missing.length === 0 && vinOk, missing, vinOk };
}
@@ -1,2 +1,12 @@
// Administrative inventory only. Does not calculate, extract, clone, generate or program credentials.
export function auditKeyInventory(entries){return entries.map((e,i)=>({row:i+1,assetTag:String(e.assetTag||""),type:String(e.type||""),source:String(e.source||""),sealed:Boolean(e.sealed),assigned:Boolean(e.assigned),notes:String(e.notes||"")}));}
export function auditKeyInventory(entries) {
return entries.map((e, i) => ({
row: i + 1,
assetTag: String(e.assetTag || ""),
type: String(e.type || ""),
source: String(e.source || ""),
sealed: Boolean(e.sealed),
assigned: Boolean(e.assigned),
notes: String(e.notes || ""),
}));
}
@@ -1,2 +1,16 @@
export function redact(value,keepLast=4){const s=String(value??"");return s.length<=keepLast?"*".repeat(s.length):"*".repeat(s.length-keepLast)+s.slice(-keepLast);}
export function securityReport(job){return {caseId:job.caseId,vin:redact(job.vehicleVin,6),authorizationRef:redact(job.authorizationRef,4),technician:job.technician,timestampUtc:job.timestampUtc,status:job.status};}
export function redact(value, keepLast = 4) {
const s = String(value ?? "");
return s.length <= keepLast
? "*".repeat(s.length)
: "*".repeat(s.length - keepLast) + s.slice(-keepLast);
}
export function securityReport(job) {
return {
caseId: job.caseId,
vin: redact(job.vehicleVin, 6),
authorizationRef: redact(job.authorizationRef, 4),
technician: job.technician,
timestampUtc: job.timestampUtc,
status: job.status,
};
}
@@ -6,8 +6,8 @@ export function fingerprintDump(data, sourceName = "synthetic-buffer") {
length: data.length,
crc32: crc32(data).toString(16).padStart(8, "0").toUpperCase(),
adler32: adler32(data).toString(16).padStart(8, "0").toUpperCase(),
ffBytes: countByte(data, 0xFF),
ffBytes: countByte(data, 0xff),
first16: bytesToHex(data.slice(0, 16)),
last16: bytesToHex(data.slice(-16))
last16: bytesToHex(data.slice(-16)),
};
}
@@ -10,10 +10,16 @@ export function buildChangeReport(original, candidate, allowedRanges = []) {
changedBytes: changedByteCount(original, candidate),
changedRanges: ranges.map((range) => ({
...range,
allowed: allowedRanges.some((allowed) => rangeContains(allowed, range.start, range.end)),
allowed: allowedRanges.some((allowed) =>
rangeContains(allowed, range.start, range.end),
),
before: bytesToHex(original.slice(range.start, range.end + 1)),
after: bytesToHex(candidate.slice(range.start, range.end + 1))
after: bytesToHex(candidate.slice(range.start, range.end + 1)),
})),
approved: ranges.every((range) => allowedRanges.some((allowed) => rangeContains(allowed, range.start, range.end)))
approved: ranges.every((range) =>
allowedRanges.some((allowed) =>
rangeContains(allowed, range.start, range.end),
),
),
};
}
@@ -2,7 +2,10 @@
Demonstrates the generic CRC engine pattern (see 17_MultiPROG_SDK/utilities/crc_engine.mjs)
so a script can pick a named checksum model at runtime instead of hard-coding one algorithm.
Read-only: computes and reports checksums, never writes to a module. */
import { crcNamed, CRC_MODELS } from "../../17_MultiPROG_SDK/utilities/crc_engine.mjs";
import {
crcNamed,
CRC_MODELS,
} from "../../17_MultiPROG_SDK/utilities/crc_engine.mjs";
import { fileURLToPath } from "node:url";
export function checksumReport(data, modelNames = Object.keys(CRC_MODELS)) {
@@ -3,14 +3,22 @@
Sector boundaries are platform/part-specific and must be supplied by the caller
(from the exact part's datasheet or tool vendor documentation) — never assumed.
See 04_Workflows/MCU_READ_WRITE_WORKFLOW.md and 01_Knowledge_Base/AUTOMOTIVE_MEMORY_CONCEPTS.md. */
import { requireBytes, assertRange, sliceBytes, isBlank, crc32 } from "../binary_utils.mjs";
import {
requireBytes,
assertRange,
sliceBytes,
isBlank,
crc32,
} from "../binary_utils.mjs";
export function parseSectorMap(data, sectors, blankValue = 0xFF) {
export function parseSectorMap(data, sectors, blankValue = 0xff) {
requireBytes(data);
if (!Array.isArray(sectors) || sectors.length === 0) throw new Error("sectors must be a non-empty array");
if (!Array.isArray(sectors) || sectors.length === 0)
throw new Error("sectors must be a non-empty array");
return sectors.map((sector) => {
const { name, start, length } = sector;
if (typeof name !== "string" || !name) throw new Error("sector.name must be a non-empty string");
if (typeof name !== "string" || !name)
throw new Error("sector.name must be a non-empty string");
assertRange(data, start, length);
const region = sliceBytes(data, start, length);
return {
@@ -23,8 +31,13 @@ export function parseSectorMap(data, sectors, blankValue = 0xFF) {
});
}
export function summarizeSectorMap(data, sectors, blankValue = 0xFF) {
export function summarizeSectorMap(data, sectors, blankValue = 0xff) {
const parsed = parseSectorMap(data, sectors, blankValue);
const blankCount = parsed.filter((s) => s.blank).length;
return { totalSectors: parsed.length, blankSectors: blankCount, usedSectors: parsed.length - blankCount, sectors: parsed };
return {
totalSectors: parsed.length,
blankSectors: blankCount,
usedSectors: parsed.length - blankCount,
sectors: parsed,
};
}
@@ -6,9 +6,29 @@
import { requireBytes, assertRange } from "../binary_utils.mjs";
const TRANSLITERATION = {
A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8,
J: 1, K: 2, L: 3, M: 4, N: 5, P: 7, R: 9,
S: 2, T: 3, U: 4, V: 5, W: 6, X: 7, Y: 8, Z: 9,
A: 1,
B: 2,
C: 3,
D: 4,
E: 5,
F: 6,
G: 7,
H: 8,
J: 1,
K: 2,
L: 3,
M: 4,
N: 5,
P: 7,
R: 9,
S: 2,
T: 3,
U: 4,
V: 5,
W: 6,
X: 7,
Y: 8,
Z: 9,
};
const WEIGHTS = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];
@@ -22,12 +42,15 @@ function transliterate(ch) {
export function extractVin(data, offset) {
requireBytes(data);
assertRange(data, offset, 17);
const text = Array.from(data.slice(offset, offset + 17), (b) => String.fromCharCode(b)).join("");
const text = Array.from(data.slice(offset, offset + 17), (b) =>
String.fromCharCode(b),
).join("");
return text.toUpperCase();
}
export function computeVinCheckDigit(vin) {
if (typeof vin !== "string" || vin.length !== 17) throw new Error("VIN must be exactly 17 characters");
if (typeof vin !== "string" || vin.length !== 17)
throw new Error("VIN must be exactly 17 characters");
if (/[IOQ]/i.test(vin)) throw new Error("VIN must not contain I, O, or Q");
let sum = 0;
for (let i = 0; i < 17; i++) sum += transliterate(vin[i]) * WEIGHTS[i];
@@ -38,7 +61,11 @@ export function computeVinCheckDigit(vin) {
export function validateVin(vin) {
const expected = computeVinCheckDigit(vin);
const actual = vin[8];
return { valid: expected === actual, expectedCheckDigit: expected, actualCheckDigit: actual };
return {
valid: expected === actual,
expectedCheckDigit: expected,
actualCheckDigit: actual,
};
}
export function selfTest() {
@@ -2,8 +2,10 @@
O(log n) alternative to linear findPattern() scans, e.g. for looking up a
sector containing a given address inside a large, sorted sector map. */
export function binarySearchOffset(sortedOffsets, target) {
if (!Array.isArray(sortedOffsets)) throw new Error("sortedOffsets must be an array");
let lo = 0, hi = sortedOffsets.length - 1;
if (!Array.isArray(sortedOffsets))
throw new Error("sortedOffsets must be an array");
let lo = 0,
hi = sortedOffsets.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >>> 1;
if (sortedOffsets[mid] === target) return mid;
@@ -15,8 +17,10 @@ export function binarySearchOffset(sortedOffsets, target) {
/* sortedSectors: array of {name, start, length}, sorted ascending by start, non-overlapping. */
export function findSectorForOffset(sortedSectors, offset) {
if (!Array.isArray(sortedSectors) || sortedSectors.length === 0) throw new Error("sortedSectors must be a non-empty array");
let lo = 0, hi = sortedSectors.length - 1;
if (!Array.isArray(sortedSectors) || sortedSectors.length === 0)
throw new Error("sortedSectors must be a non-empty array");
let lo = 0,
hi = sortedSectors.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >>> 1;
const sector = sortedSectors[mid];
@@ -19,26 +19,50 @@ export function readI16BE(data, offset) {
}
export function readI32LE(data, offset) {
assertRange(data, offset, 4);
return (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)) | 0;
return (
data[offset] |
(data[offset + 1] << 8) |
(data[offset + 2] << 16) |
(data[offset + 3] << 24) |
0
);
}
export function readI32BE(data, offset) {
assertRange(data, offset, 4);
return ((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]) | 0;
return (
(data[offset] << 24) |
(data[offset + 1] << 16) |
(data[offset + 2] << 8) |
data[offset + 3] |
0
);
}
/* Bit numbering: bit 0 = least significant bit. */
export function getBits(value, startBit, numBits) {
if (!Number.isInteger(startBit) || !Number.isInteger(numBits) || startBit < 0 || numBits <= 0 || startBit + numBits > 32) {
if (
!Number.isInteger(startBit) ||
!Number.isInteger(numBits) ||
startBit < 0 ||
numBits <= 0 ||
startBit + numBits > 32
) {
throw new RangeError("Invalid bit range");
}
const mask = (numBits === 32 ? 0xFFFFFFFF : (1 << numBits) - 1) >>> 0;
const mask = (numBits === 32 ? 0xffffffff : (1 << numBits) - 1) >>> 0;
return (value >>> startBit) & mask;
}
export function setBits(value, startBit, numBits, newValue) {
if (!Number.isInteger(startBit) || !Number.isInteger(numBits) || startBit < 0 || numBits <= 0 || startBit + numBits > 32) {
if (
!Number.isInteger(startBit) ||
!Number.isInteger(numBits) ||
startBit < 0 ||
numBits <= 0 ||
startBit + numBits > 32
) {
throw new RangeError("Invalid bit range");
}
const mask = ((numBits === 32 ? 0xFFFFFFFF : (1 << numBits) - 1) >>> 0);
const mask = (numBits === 32 ? 0xffffffff : (1 << numBits) - 1) >>> 0;
const cleared = value & ~(mask << startBit);
return (cleared | ((newValue & mask) << startBit)) >>> 0;
}
@@ -3,22 +3,42 @@
document any security algorithm, PIN/seed value, or bypass technique.
See 04_Workflows/AUTHORIZED_KEY_LEARNING_WORKFLOW.md. */
export function buildKeyLearningReport(session) {
const required = ["caseId", "vin", "authorizedBy", "existingKeyCountBefore", "keysAddedCount", "keysRemovedCount"];
const required = [
"caseId",
"vin",
"authorizedBy",
"existingKeyCountBefore",
"keysAddedCount",
"keysRemovedCount",
];
for (const field of required) {
if (session[field] === undefined || session[field] === null || session[field] === "") {
if (
session[field] === undefined ||
session[field] === null ||
session[field] === ""
) {
throw new Error(`Missing required field: ${field}`);
}
}
if (!Number.isInteger(session.existingKeyCountBefore) || session.existingKeyCountBefore < 0) {
if (
!Number.isInteger(session.existingKeyCountBefore) ||
session.existingKeyCountBefore < 0
) {
throw new Error("existingKeyCountBefore must be a non-negative integer");
}
if (!Number.isInteger(session.keysAddedCount) || session.keysAddedCount < 0) {
throw new Error("keysAddedCount must be a non-negative integer");
}
if (!Number.isInteger(session.keysRemovedCount) || session.keysRemovedCount < 0) {
if (
!Number.isInteger(session.keysRemovedCount) ||
session.keysRemovedCount < 0
) {
throw new Error("keysRemovedCount must be a non-negative integer");
}
const expectedKeyCountAfter = session.existingKeyCountBefore + session.keysAddedCount - session.keysRemovedCount;
const expectedKeyCountAfter =
session.existingKeyCountBefore +
session.keysAddedCount -
session.keysRemovedCount;
return {
caseId: session.caseId,
vin: session.vin,
@@ -1,19 +1,29 @@
/* Authorized-shop administrative example.
Does not read, alter, or conceal crash/deployment data. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService"
"caseId",
"timestampUtc",
"technician",
"customerName",
"vehicleVin",
"proofOfOwnershipRef",
"authorizationRef",
"requestedService",
];
export function validateAirbagCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const missing = REQUIRED.filter((k) => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "")
.trim()
.toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
const authorizationValid = Boolean(
String(caseRecord.authorizationRef ?? "").trim(),
);
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
authorizationValid,
};
}
@@ -6,6 +6,6 @@ export function verifyAirbagReadConsistency(readA, readB) {
sameLength: readA.length === readB.length,
identical: constantTimeEqual(readA, readB),
crcA: crc32(readA),
crcB: crc32(readB)
crcB: crc32(readB),
};
}
@@ -14,6 +14,6 @@ export function airbagStatusReport(job) {
technician: job.technician,
timestampUtc: job.timestampUtc,
deploymentStatusAsFound: job.deploymentStatusAsFound,
status: job.status
status: job.status,
};
}
@@ -1,12 +1,21 @@
/* Produces a consistent, sanitized filename for authorized airbag/SRS module backups. */
function safe(value) {
return String(value ?? "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 50);
return String(value ?? "unknown")
.trim()
.replace(/[^A-Za-z0-9._-]+/g, "_")
.slice(0, 50);
}
export function buildAirbagBackupName(meta) {
const stamp = safe(meta.timestampUtc).replace(/[:]/g, "-");
return [
safe(meta.caseId), safe(meta.vehicleVin), safe(meta.module),
safe(meta.memoryRegion), safe(meta.readNumber), stamp
].join("_") + ".bin";
return (
[
safe(meta.caseId),
safe(meta.vehicleVin),
safe(meta.module),
safe(meta.memoryRegion),
safe(meta.readNumber),
stamp,
].join("_") + ".bin"
);
}
@@ -7,7 +7,7 @@ export function auditAirbagCompletion(job) {
duplicateReadsMatched: job.duplicateReadsMatched === true,
crashDataUnaltered: job.crashDataUnaltered === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
technicianSigned: Boolean(job.technicianSignatureRef)
technicianSigned: Boolean(job.technicianSignatureRef),
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -1,18 +1,28 @@
/* Authorized-shop administrative example. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService"
"caseId",
"timestampUtc",
"technician",
"customerName",
"vehicleVin",
"proofOfOwnershipRef",
"authorizationRef",
"requestedService",
];
export function validateEcuCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const missing = REQUIRED.filter((k) => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "")
.trim()
.toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
const authorizationValid = Boolean(
String(caseRecord.authorizationRef ?? "").trim(),
);
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
authorizationValid,
};
}
@@ -6,6 +6,6 @@ export function verifyEcuReadConsistency(readA, readB) {
sameLength: readA.length === readB.length,
identical: constantTimeEqual(readA, readB),
crcA: crc32(readA),
crcB: crc32(readB)
crcB: crc32(readB),
};
}
@@ -8,6 +8,6 @@ export function crosscheckPartNumber(data, field, expectedPartNumber) {
return {
found,
expected: String(expectedPartNumber ?? ""),
matches: found === String(expectedPartNumber ?? "")
matches: found === String(expectedPartNumber ?? ""),
};
}
@@ -6,13 +6,19 @@ function rangeInside(change, allowed) {
return change.start >= allowed.start && change.end <= allowed.end;
}
export function validateAuthorizedEcuChangeScope(original, modified, allowedRanges) {
export function validateAuthorizedEcuChangeScope(
original,
modified,
allowedRanges,
) {
const changes = diffRanges(original, modified);
const violations = changes.filter(c => !allowedRanges.some(a => rangeInside(c, a)));
const violations = changes.filter(
(c) => !allowedRanges.some((a) => rangeInside(c, a)),
);
return {
valid: violations.length === 0,
changes,
violations,
changedBytes: changes.reduce((n, r) => n + r.length, 0)
changedBytes: changes.reduce((n, r) => n + r.length, 0),
};
}
@@ -7,7 +7,7 @@ export function auditEcuCompletion(job) {
duplicateReadsMatched: job.duplicateReadsMatched === true,
partNumberVerified: job.partNumberVerified === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
technicianSigned: Boolean(job.technicianSignatureRef)
technicianSigned: Boolean(job.technicianSignatureRef),
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -1,19 +1,29 @@
/* Authorized-shop administrative example.
Does not read, derive, calculate, program, clone, or bypass immobilizer credentials. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService"
"caseId",
"timestampUtc",
"technician",
"customerName",
"vehicleVin",
"proofOfOwnershipRef",
"authorizationRef",
"requestedService",
];
export function validateImmoCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const missing = REQUIRED.filter((k) => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "")
.trim()
.toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
const authorizationValid = Boolean(
String(caseRecord.authorizationRef ?? "").trim(),
);
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
authorizationValid,
};
}
@@ -4,7 +4,12 @@ import { constantTimeEqual, crc32, diffRanges } from "../../binary_utils.mjs";
export function compareAuthorizedReads(readA, readB) {
if (readA.length !== readB.length) {
return { valid: false, reason: "Different file lengths", lengthA: readA.length, lengthB: readB.length };
return {
valid: false,
reason: "Different file lengths",
lengthA: readA.length,
lengthB: readB.length,
};
}
const identical = constantTimeEqual(readA, readB);
return {
@@ -13,6 +18,6 @@ export function compareAuthorizedReads(readA, readB) {
length: readA.length,
crc32A: crc32(readA).toString(16).padStart(8, "0").toUpperCase(),
crc32B: crc32(readB).toString(16).padStart(8, "0").toUpperCase(),
changedRanges: identical ? [] : diffRanges(readA, readB)
changedRanges: identical ? [] : diffRanges(readA, readB),
};
}
@@ -4,15 +4,18 @@ import { countByte, crc32, asciiPreview } from "../../binary_utils.mjs";
export function buildDumpQualityReport(data) {
if (!data.length) return { valid: false, reason: "Empty input" };
const ff = countByte(data, 0xFF);
const ff = countByte(data, 0xff);
const zero = countByte(data, 0x00);
return {
valid: true,
length: data.length,
crc32: crc32(data).toString(16).padStart(8, "0").toUpperCase(),
ffPercent: Number((ff * 100 / data.length).toFixed(2)),
zeroPercent: Number((zero * 100 / data.length).toFixed(2)),
ffPercent: Number(((ff * 100) / data.length).toFixed(2)),
zeroPercent: Number(((zero * 100) / data.length).toFixed(2)),
printablePreview: asciiPreview(data.slice(0, 64)),
warning: ff === data.length || zero === data.length ? "Input appears blank or erased" : ""
warning:
ff === data.length || zero === data.length
? "Input appears blank or erased"
: "",
};
}
@@ -7,10 +7,17 @@ export function reconcileKeys(record) {
const retained = Number(record.keysRetainedByShop ?? 0);
const expectedReturn = received + added - retained;
return {
received, returned, added, retained, expectedReturn,
balanced: Number.isInteger(received) && Number.isInteger(returned) &&
Number.isInteger(added) && Number.isInteger(retained) &&
[received, returned, added, retained].every(v => v >= 0) &&
returned === expectedReturn
received,
returned,
added,
retained,
expectedReturn,
balanced:
Number.isInteger(received) &&
Number.isInteger(returned) &&
Number.isInteger(added) &&
Number.isInteger(retained) &&
[received, returned, added, retained].every((v) => v >= 0) &&
returned === expectedReturn,
};
}
@@ -1,12 +1,21 @@
/* Produces a consistent, sanitized filename for authorized backups. */
function safe(value) {
return String(value ?? "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 50);
return String(value ?? "unknown")
.trim()
.replace(/[^A-Za-z0-9._-]+/g, "_")
.slice(0, 50);
}
export function buildBackupName(meta) {
const stamp = safe(meta.timestampUtc).replace(/[:]/g, "-");
return [
safe(meta.caseId), safe(meta.vehicleVin), safe(meta.module),
safe(meta.memoryRegion), safe(meta.readNumber), stamp
].join("_") + ".bin";
return (
[
safe(meta.caseId),
safe(meta.vehicleVin),
safe(meta.module),
safe(meta.memoryRegion),
safe(meta.readNumber),
stamp,
].join("_") + ".bin"
);
}
@@ -6,13 +6,19 @@ function rangeInside(change, allowed) {
return change.start >= allowed.start && change.end <= allowed.end;
}
export function validateAuthorizedChangeScope(original, modified, allowedRanges) {
export function validateAuthorizedChangeScope(
original,
modified,
allowedRanges,
) {
const changes = diffRanges(original, modified);
const violations = changes.filter(c => !allowedRanges.some(a => rangeInside(c, a)));
const violations = changes.filter(
(c) => !allowedRanges.some((a) => rangeInside(c, a)),
);
return {
valid: violations.length === 0,
changes,
violations,
changedBytes: changes.reduce((n, r) => n + r.length, 0)
changedBytes: changes.reduce((n, r) => n + r.length, 0),
};
}
@@ -8,7 +8,7 @@ export function auditCompletion(job) {
physicalKeysReconciled: job.physicalKeysReconciled === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
securityDataRedacted: job.securityDataRedacted === true,
technicianSigned: Boolean(job.technicianSignatureRef)
technicianSigned: Boolean(job.technicianSignatureRef),
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -1,18 +1,29 @@
/* Authorized-shop administrative example for BCM/instrument cluster/TCU work. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService", "module"
"caseId",
"timestampUtc",
"technician",
"customerName",
"vehicleVin",
"proofOfOwnershipRef",
"authorizationRef",
"requestedService",
"module",
];
export function validateModuleCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const missing = REQUIRED.filter((k) => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "")
.trim()
.toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
const authorizationValid = Boolean(
String(caseRecord.authorizationRef ?? "").trim(),
);
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
authorizationValid,
};
}
@@ -6,6 +6,6 @@ export function verifyModuleReadConsistency(readA, readB) {
sameLength: readA.length === readB.length,
identical: constantTimeEqual(readA, readB),
crcA: crc32(readA),
crcB: crc32(readB)
crcB: crc32(readB),
};
}
@@ -1,12 +1,21 @@
/* Produces a consistent, sanitized filename for authorized module backups. */
function safe(value) {
return String(value ?? "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 50);
return String(value ?? "unknown")
.trim()
.replace(/[^A-Za-z0-9._-]+/g, "_")
.slice(0, 50);
}
export function buildModuleBackupName(meta) {
const stamp = safe(meta.timestampUtc).replace(/[:]/g, "-");
return [
safe(meta.caseId), safe(meta.vehicleVin), safe(meta.module),
safe(meta.memoryRegion), safe(meta.readNumber), stamp
].join("_") + ".bin";
return (
[
safe(meta.caseId),
safe(meta.vehicleVin),
safe(meta.module),
safe(meta.memoryRegion),
safe(meta.readNumber),
stamp,
].join("_") + ".bin"
);
}
@@ -8,13 +8,19 @@ function rangeInside(change, allowed) {
return change.start >= allowed.start && change.end <= allowed.end;
}
export function validateAuthorizedModuleChangeScope(original, modified, allowedRanges) {
export function validateAuthorizedModuleChangeScope(
original,
modified,
allowedRanges,
) {
const changes = diffRanges(original, modified);
const violations = changes.filter(c => !allowedRanges.some(a => rangeInside(c, a)));
const violations = changes.filter(
(c) => !allowedRanges.some((a) => rangeInside(c, a)),
);
return {
valid: violations.length === 0,
changes,
violations,
changedBytes: changes.reduce((n, r) => n + r.length, 0)
changedBytes: changes.reduce((n, r) => n + r.length, 0),
};
}
@@ -7,7 +7,7 @@ export function auditModuleCompletion(job) {
duplicateReadsMatched: job.duplicateReadsMatched === true,
mileageFieldUnchanged: job.mileageFieldUnchanged === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
technicianSigned: Boolean(job.technicianSignatureRef)
technicianSigned: Boolean(job.technicianSignatureRef),
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -2,24 +2,30 @@ import { assertRange, requireBytes } from "../binary_utils.mjs";
export function verifyChecksumRegion(data, region, checksum, algorithm) {
requireBytes(data);
if (!region || !checksum || typeof algorithm !== "function") throw new TypeError("Region, checksum, and algorithm are required");
if (!region || !checksum || typeof algorithm !== "function")
throw new TypeError("Region, checksum, and algorithm are required");
assertRange(data, region.start, region.length);
assertRange(data, checksum.offset, checksum.length);
if (![1, 2, 4].includes(checksum.length)) throw new RangeError("Checksum length must be 1, 2, or 4 bytes");
if (![1, 2, 4].includes(checksum.length))
throw new RangeError("Checksum length must be 1, 2, or 4 bytes");
const endian = checksum.endian === undefined ? "LE" : checksum.endian;
if (endian !== "LE" && endian !== "BE") throw new RangeError("Checksum endian must be LE or BE");
const computed = algorithm(data.slice(region.start, region.start + region.length)) >>> 0;
if (endian !== "LE" && endian !== "BE")
throw new RangeError("Checksum endian must be LE or BE");
const computed =
algorithm(data.slice(region.start, region.start + region.length)) >>> 0;
let stored = 0;
for (let index = 0; index < checksum.length; index++) {
const shift = endian === "BE" ? 8 * (checksum.length - index - 1) : 8 * index;
const shift =
endian === "BE" ? 8 * (checksum.length - index - 1) : 8 * index;
stored = (stored | (data[checksum.offset + index] << shift)) >>> 0;
}
const mask = checksum.length === 4 ? 0xFFFFFFFF : (2 ** (checksum.length * 8)) - 1;
const mask =
checksum.length === 4 ? 0xffffffff : 2 ** (checksum.length * 8) - 1;
return {
region: { start: region.start, length: region.length },
checksum: { offset: checksum.offset, length: checksum.length, endian },
stored: stored & mask,
computed: computed & mask,
matches: (stored & mask) === (computed & mask)
matches: (stored & mask) === (computed & mask),
};
}
@@ -8,23 +8,59 @@ import { checksumReport } from "../examples/34_checksum_model_selector.mjs";
const payload = new Uint8Array([1, 2, 3, 4]);
const withChecksum = new Uint8Array([1, 2, 3, 4, 0, 0, 0, 0]);
new DataView(withChecksum.buffer).setUint32(4, adler32(payload), false);
assert.equal(verifyChecksumRegion(withChecksum, { start: 0, length: 4 }, { offset: 4, length: 4, endian: "BE" }, adler32).matches, true);
assert.equal(
verifyChecksumRegion(
withChecksum,
{ start: 0, length: 4 },
{ offset: 4, length: 4, endian: "BE" },
adler32,
).matches,
true,
);
withChecksum[0] = 9;
assert.equal(verifyChecksumRegion(withChecksum, { start: 0, length: 4 }, { offset: 4, length: 4, endian: "BE" }, adler32).matches, false);
assert.equal(
verifyChecksumRegion(
withChecksum,
{ start: 0, length: 4 },
{ offset: 4, length: 4, endian: "BE" },
adler32,
).matches,
false,
);
assert.deepEqual(hexToBytes("00:0f ff"), new Uint8Array([0, 15, 255]));
assert.throws(() => hexToBytes("12zz34"), /Invalid character/);
assert.throws(() => verifyChecksumRegion(withChecksum, { start: 0, length: 4 }, { offset: 4, length: 4, endian: "BIG" }, adler32), /endian must be LE or BE/);
assert.throws(
() =>
verifyChecksumRegion(
withChecksum,
{ start: 0, length: 4 },
{ offset: 4, length: 4, endian: "BIG" },
adler32,
),
/endian must be LE or BE/,
);
const fingerprint = fingerprintDump(payload, "fixture.bin");
assert.equal(fingerprint.length, 4);
assert.equal(fingerprint.ffBytes, 0);
const report = buildChangeReport(new Uint8Array([1, 2, 3]), new Uint8Array([1, 9, 3]), [{ start: 1, end: 1 }]);
const report = buildChangeReport(
new Uint8Array([1, 2, 3]),
new Uint8Array([1, 9, 3]),
[{ start: 1, end: 1 }],
);
assert.equal(report.changedBytes, 1);
assert.equal(report.approved, true);
assert.equal(buildChangeReport(new Uint8Array([1, 2, 3]), new Uint8Array([1, 9, 3])).approved, false);
assert.equal(
buildChangeReport(new Uint8Array([1, 2, 3]), new Uint8Array([1, 9, 3]))
.approved,
false,
);
const checksums = checksumReport(new TextEncoder().encode("123456789"), ["crc32", "crc16_arc"]);
const checksums = checksumReport(new TextEncoder().encode("123456789"), [
"crc32",
"crc16_arc",
]);
assert.equal(checksums.crc32, "cbf43926");
assert.equal(checksums.crc16_arc, "bb3d");
@@ -1,7 +1,10 @@
import { strict as assert } from "node:assert";
import { crc32, diffRanges, bytesToHex } from "../binary_utils.mjs";
const v=new TextEncoder().encode("123456789");
assert.equal(crc32(v),0xCBF43926);
assert.equal(bytesToHex(new Uint8Array([0,15,255])),"00 0F FF");
assert.deepEqual(diffRanges(new Uint8Array([1,2,3,4]),new Uint8Array([1,9,8,4])),[{start:1,end:2,length:2}]);
const v = new TextEncoder().encode("123456789");
assert.equal(crc32(v), 0xcbf43926);
assert.equal(bytesToHex(new Uint8Array([0, 15, 255])), "00 0F FF");
assert.deepEqual(
diffRanges(new Uint8Array([1, 2, 3, 4]), new Uint8Array([1, 9, 8, 4])),
[{ start: 1, end: 2, length: 2 }],
);
console.log("All tests passed");
@@ -1,14 +1,36 @@
import { strict as assert } from "node:assert";
import { crc32 } from "../binary_utils.mjs";
import { parseSectorMap, summarizeSectorMap } from "../examples/35_flash_sector_map_parser.mjs";
import { extractVin, computeVinCheckDigit, validateVin, selfTest as vinSelfTest } from "../examples/36_vin_extractor_validator.mjs";
import { binarySearchOffset, findSectorForOffset } from "../examples/37_binary_search_pattern_locator.mjs";
import { readI8, readI16LE, readI16BE, readI32LE, readI32BE, getBits, setBits } from "../examples/38_bitfield_and_signed_int_utils.mjs";
import { buildKeyLearningReport, verifyKeyCountReconciliation } from "../examples/39_key_learning_session_report.mjs";
import {
parseSectorMap,
summarizeSectorMap,
} from "../examples/35_flash_sector_map_parser.mjs";
import {
extractVin,
computeVinCheckDigit,
validateVin,
selfTest as vinSelfTest,
} from "../examples/36_vin_extractor_validator.mjs";
import {
binarySearchOffset,
findSectorForOffset,
} from "../examples/37_binary_search_pattern_locator.mjs";
import {
readI8,
readI16LE,
readI16BE,
readI32LE,
readI32BE,
getBits,
setBits,
} from "../examples/38_bitfield_and_signed_int_utils.mjs";
import {
buildKeyLearningReport,
verifyKeyCountReconciliation,
} from "../examples/39_key_learning_session_report.mjs";
// 35_flash_sector_map_parser
{
const data = new Uint8Array(16).fill(0xFF);
const data = new Uint8Array(16).fill(0xff);
data.set([1, 2, 3, 4], 8);
const sectors = [
{ name: "boot", start: 0, length: 8 },
@@ -17,11 +39,16 @@ import { buildKeyLearningReport, verifyKeyCountReconciliation } from "../example
const parsed = parseSectorMap(data, sectors);
assert.equal(parsed[0].blank, true);
assert.equal(parsed[1].blank, false);
assert.equal(parsed[1].crc32, crc32(data.slice(8, 16)).toString(16).padStart(8, "0").toUpperCase());
assert.equal(
parsed[1].crc32,
crc32(data.slice(8, 16)).toString(16).padStart(8, "0").toUpperCase(),
);
const summary = summarizeSectorMap(data, sectors);
assert.equal(summary.blankSectors, 1);
assert.equal(summary.usedSectors, 1);
assert.throws(() => parseSectorMap(data, [{ name: "oob", start: 10, length: 100 }]));
assert.throws(() =>
parseSectorMap(data, [{ name: "oob", start: 10, length: 100 }]),
);
}
// 36_vin_extractor_validator
@@ -54,16 +81,19 @@ import { buildKeyLearningReport, verifyKeyCountReconciliation } from "../example
// 38_bitfield_and_signed_int_utils
{
assert.equal(readI8(new Uint8Array([0xFF]), 0), -1);
assert.equal(readI8(new Uint8Array([0x7F]), 0), 127);
assert.equal(readI8(new Uint8Array([0xff]), 0), -1);
assert.equal(readI8(new Uint8Array([0x7f]), 0), 127);
assert.equal(readI16LE(new Uint8Array([0x00, 0x01]), 0), 256);
assert.equal(readI16BE(new Uint8Array([0x00, 0x01]), 0), 1);
assert.equal(readI16LE(new Uint8Array([0xFF, 0xFF]), 0), -1);
assert.equal(readI16BE(new Uint8Array([0xFF, 0xFF]), 0), -1);
assert.equal(readI32LE(new Uint8Array([0x00, 0x00, 0x00, 0x01]), 0), 16777216);
assert.equal(readI16LE(new Uint8Array([0xff, 0xff]), 0), -1);
assert.equal(readI16BE(new Uint8Array([0xff, 0xff]), 0), -1);
assert.equal(
readI32LE(new Uint8Array([0x00, 0x00, 0x00, 0x01]), 0),
16777216,
);
assert.equal(readI32BE(new Uint8Array([0x00, 0x00, 0x00, 0x01]), 0), 1);
assert.equal(readI32LE(new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]), 0), -1);
assert.equal(readI32BE(new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]), 0), -1);
assert.equal(readI32LE(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0), -1);
assert.equal(readI32BE(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0), -1);
assert.equal(getBits(0b10110100, 2, 3), 0b101);
assert.equal(setBits(0b10110100, 2, 3, 0b011), 172);
@@ -84,8 +114,14 @@ import { buildKeyLearningReport, verifyKeyCountReconciliation } from "../example
assert.equal(report.expectedKeyCountAfter, 3);
assert.equal(verifyKeyCountReconciliation(report, 3).matches, true);
assert.equal(verifyKeyCountReconciliation(report, 2).matches, false);
assert.throws(() => buildKeyLearningReport({ ...session, caseId: "" }), /Missing required field/);
assert.throws(() => buildKeyLearningReport({ ...session, keysAddedCount: -1 }), /non-negative integer/);
assert.throws(
() => buildKeyLearningReport({ ...session, caseId: "" }),
/Missing required field/,
);
assert.throws(
() => buildKeyLearningReport({ ...session, keysAddedCount: -1 }),
/non-negative integer/,
);
}
console.log("Expansion examples passed");
@@ -1,2 +1,15 @@
import { bytesToHex, asciiPreview, countByte, crc32 } from "../binary_utils.mjs";
export function inspect(data){return {length:data.length,first32:bytesToHex(data.slice(0,32)),ascii:asciiPreview(data.slice(0,64)),ffBytes:countByte(data,0xFF),crc32:crc32(data).toString(16).padStart(8,"0").toUpperCase()};}
import {
bytesToHex,
asciiPreview,
countByte,
crc32,
} from "../binary_utils.mjs";
export function inspect(data) {
return {
length: data.length,
first32: bytesToHex(data.slice(0, 32)),
ascii: asciiPreview(data.slice(0, 64)),
ffBytes: countByte(data, 0xff),
crc32: crc32(data).toString(16).padStart(8, "0").toUpperCase(),
};
}
@@ -1,2 +1,9 @@
import { constantTimeEqual, crc32 } from "../binary_utils.mjs";
export function verifyTwoReads(a,b){return {sameLength:a.length===b.length,identical:constantTimeEqual(a,b),crcA:crc32(a),crcB:crc32(b)};}
export function verifyTwoReads(a, b) {
return {
sameLength: a.length === b.length,
identical: constantTimeEqual(a, b),
crcA: crc32(a),
crcB: crc32(b),
};
}
@@ -1,2 +1,7 @@
import { findPattern, hexToBytes } from "../binary_utils.mjs";
export function inventory(data,patterns){return patterns.map(p=>({label:p.label,offsets:findPattern(data,hexToBytes(p.hex))}));}
export function inventory(data, patterns) {
return patterns.map((p) => ({
label: p.label,
offsets: findPattern(data, hexToBytes(p.hex)),
}));
}
@@ -1,3 +1,9 @@
import { cloneBytes, allowListedWrite, diffRanges } from "../binary_utils.mjs";
// Synthetic demonstration only. No vehicle-security data or real module offsets.
export function demo(input){const output=cloneBytes(input);allowListedWrite(output,4,new Uint8Array([0xAA,0x55]),[{start:4,end:5}]);return {output,changes:diffRanges(input,output)};}
export function demo(input) {
const output = cloneBytes(input);
allowListedWrite(output, 4, new Uint8Array([0xaa, 0x55]), [
{ start: 4, end: 5 },
]);
return { output, changes: diffRanges(input, output) };
}
@@ -1,2 +1,14 @@
import { hexToBytes,crc16CcittFalse,crc32,adler32 } from "../binary_utils.mjs";
export function selfTest(){const d=new TextEncoder().encode("123456789");return {crc16Ok:crc16CcittFalse(d)===0x29B1,crc32Ok:crc32(d)===0xCBF43926,adler32:adler32(d)};}
import {
hexToBytes,
crc16CcittFalse,
crc32,
adler32,
} from "../binary_utils.mjs";
export function selfTest() {
const d = new TextEncoder().encode("123456789");
return {
crc16Ok: crc16CcittFalse(d) === 0x29b1,
crc32Ok: crc32(d) === 0xcbf43926,
adler32: adler32(d),
};
}
@@ -1,2 +1,17 @@
const REQUIRED=["caseId","customerName","vehicleVin","proofOfOwnershipRef","authorizationRef","technician","timestampUtc","requestedService"];
export function validateAuthorizedJob(job){const missing=REQUIRED.filter(k=>!String(job[k]??"").trim());const vinOk=/^[A-HJ-NPR-Z0-9]{17}$/.test(String(job.vehicleVin||"").toUpperCase());return {valid:missing.length===0&&vinOk,missing,vinOk};}
const REQUIRED = [
"caseId",
"customerName",
"vehicleVin",
"proofOfOwnershipRef",
"authorizationRef",
"technician",
"timestampUtc",
"requestedService",
];
export function validateAuthorizedJob(job) {
const missing = REQUIRED.filter((k) => !String(job[k] ?? "").trim());
const vinOk = /^[A-HJ-NPR-Z0-9]{17}$/.test(
String(job.vehicleVin || "").toUpperCase(),
);
return { valid: missing.length === 0 && vinOk, missing, vinOk };
}
@@ -1,2 +1,12 @@
// Administrative inventory only. Does not calculate, extract, clone, generate or program credentials.
export function auditKeyInventory(entries){return entries.map((e,i)=>({row:i+1,assetTag:String(e.assetTag||""),type:String(e.type||""),source:String(e.source||""),sealed:Boolean(e.sealed),assigned:Boolean(e.assigned),notes:String(e.notes||"")}));}
export function auditKeyInventory(entries) {
return entries.map((e, i) => ({
row: i + 1,
assetTag: String(e.assetTag || ""),
type: String(e.type || ""),
source: String(e.source || ""),
sealed: Boolean(e.sealed),
assigned: Boolean(e.assigned),
notes: String(e.notes || ""),
}));
}
@@ -1,2 +1,16 @@
export function redact(value,keepLast=4){const s=String(value??"");return s.length<=keepLast?"*".repeat(s.length):"*".repeat(s.length-keepLast)+s.slice(-keepLast);}
export function securityReport(job){return {caseId:job.caseId,vin:redact(job.vehicleVin,6),authorizationRef:redact(job.authorizationRef,4),technician:job.technician,timestampUtc:job.timestampUtc,status:job.status};}
export function redact(value, keepLast = 4) {
const s = String(value ?? "");
return s.length <= keepLast
? "*".repeat(s.length)
: "*".repeat(s.length - keepLast) + s.slice(-keepLast);
}
export function securityReport(job) {
return {
caseId: job.caseId,
vin: redact(job.vehicleVin, 6),
authorizationRef: redact(job.authorizationRef, 4),
technician: job.technician,
timestampUtc: job.timestampUtc,
status: job.status,
};
}
@@ -1,19 +1,29 @@
/* Authorized-shop administrative example.
Does not read, derive, calculate, program, clone, or bypass immobilizer credentials. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService"
"caseId",
"timestampUtc",
"technician",
"customerName",
"vehicleVin",
"proofOfOwnershipRef",
"authorizationRef",
"requestedService",
];
export function validateImmoCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const missing = REQUIRED.filter((k) => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "")
.trim()
.toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
const authorizationValid = Boolean(
String(caseRecord.authorizationRef ?? "").trim(),
);
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
authorizationValid,
};
}
@@ -4,7 +4,12 @@ import { constantTimeEqual, crc32, diffRanges } from "../../binary_utils.mjs";
export function compareAuthorizedReads(readA, readB) {
if (readA.length !== readB.length) {
return { valid: false, reason: "Different file lengths", lengthA: readA.length, lengthB: readB.length };
return {
valid: false,
reason: "Different file lengths",
lengthA: readA.length,
lengthB: readB.length,
};
}
const identical = constantTimeEqual(readA, readB);
return {
@@ -13,6 +18,6 @@ export function compareAuthorizedReads(readA, readB) {
length: readA.length,
crc32A: crc32(readA).toString(16).padStart(8, "0").toUpperCase(),
crc32B: crc32(readB).toString(16).padStart(8, "0").toUpperCase(),
changedRanges: identical ? [] : diffRanges(readA, readB)
changedRanges: identical ? [] : diffRanges(readA, readB),
};
}
@@ -4,15 +4,18 @@ import { countByte, crc32, asciiPreview } from "../../binary_utils.mjs";
export function buildDumpQualityReport(data) {
if (!data.length) return { valid: false, reason: "Empty input" };
const ff = countByte(data, 0xFF);
const ff = countByte(data, 0xff);
const zero = countByte(data, 0x00);
return {
valid: true,
length: data.length,
crc32: crc32(data).toString(16).padStart(8, "0").toUpperCase(),
ffPercent: Number((ff * 100 / data.length).toFixed(2)),
zeroPercent: Number((zero * 100 / data.length).toFixed(2)),
ffPercent: Number(((ff * 100) / data.length).toFixed(2)),
zeroPercent: Number(((zero * 100) / data.length).toFixed(2)),
printablePreview: asciiPreview(data.slice(0, 64)),
warning: ff === data.length || zero === data.length ? "Input appears blank or erased" : ""
warning:
ff === data.length || zero === data.length
? "Input appears blank or erased"
: "",
};
}
@@ -7,10 +7,17 @@ export function reconcileKeys(record) {
const retained = Number(record.keysRetainedByShop ?? 0);
const expectedReturn = received + added - retained;
return {
received, returned, added, retained, expectedReturn,
balanced: Number.isInteger(received) && Number.isInteger(returned) &&
Number.isInteger(added) && Number.isInteger(retained) &&
[received, returned, added, retained].every(v => v >= 0) &&
returned === expectedReturn
received,
returned,
added,
retained,
expectedReturn,
balanced:
Number.isInteger(received) &&
Number.isInteger(returned) &&
Number.isInteger(added) &&
Number.isInteger(retained) &&
[received, returned, added, retained].every((v) => v >= 0) &&
returned === expectedReturn,
};
}
@@ -1,12 +1,21 @@
/* Produces a consistent, sanitized filename for authorized backups. */
function safe(value) {
return String(value ?? "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 50);
return String(value ?? "unknown")
.trim()
.replace(/[^A-Za-z0-9._-]+/g, "_")
.slice(0, 50);
}
export function buildBackupName(meta) {
const stamp = safe(meta.timestampUtc).replace(/[:]/g, "-");
return [
safe(meta.caseId), safe(meta.vehicleVin), safe(meta.module),
safe(meta.memoryRegion), safe(meta.readNumber), stamp
].join("_") + ".bin";
return (
[
safe(meta.caseId),
safe(meta.vehicleVin),
safe(meta.module),
safe(meta.memoryRegion),
safe(meta.readNumber),
stamp,
].join("_") + ".bin"
);
}
@@ -6,13 +6,19 @@ function rangeInside(change, allowed) {
return change.start >= allowed.start && change.end <= allowed.end;
}
export function validateAuthorizedChangeScope(original, modified, allowedRanges) {
export function validateAuthorizedChangeScope(
original,
modified,
allowedRanges,
) {
const changes = diffRanges(original, modified);
const violations = changes.filter(c => !allowedRanges.some(a => rangeInside(c, a)));
const violations = changes.filter(
(c) => !allowedRanges.some((a) => rangeInside(c, a)),
);
return {
valid: violations.length === 0,
changes,
violations,
changedBytes: changes.reduce((n, r) => n + r.length, 0)
changedBytes: changes.reduce((n, r) => n + r.length, 0),
};
}
@@ -8,7 +8,7 @@ export function auditCompletion(job) {
physicalKeysReconciled: job.physicalKeysReconciled === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
securityDataRedacted: job.securityDataRedacted === true,
technicianSigned: Boolean(job.technicianSignatureRef)
technicianSigned: Boolean(job.technicianSignatureRef),
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -6,36 +6,196 @@ export function requireBytes(data) {
}
export function assertRange(data, offset, length) {
requireBytes(data);
if (!Number.isInteger(offset)||!Number.isInteger(length)||offset<0||length<0||offset+length>data.length) throw new RangeError("Range outside buffer");
if (
!Number.isInteger(offset) ||
!Number.isInteger(length) ||
offset < 0 ||
length < 0 ||
offset + length > data.length
)
throw new RangeError("Range outside buffer");
}
export function cloneBytes(data){ requireBytes(data); return new Uint8Array(data); }
export function sliceBytes(data,offset,length){ assertRange(data,offset,length); return data.slice(offset,offset+length); }
export function bytesToHex(data,separator=" "){ requireBytes(data); return Array.from(data,b=>b.toString(16).padStart(2,"0")).join(separator).toUpperCase(); }
export function hexToBytes(text){
const s=String(text).replace(/[^0-9a-f]/gi,"");
if(s.length%2) throw new Error("Hex text must contain complete bytes");
const out=new Uint8Array(s.length/2); for(let i=0;i<out.length;i++) out[i]=parseInt(s.slice(i*2,i*2+2),16); return out;
export function cloneBytes(data) {
requireBytes(data);
return new Uint8Array(data);
}
export function sliceBytes(data, offset, length) {
assertRange(data, offset, length);
return data.slice(offset, offset + length);
}
export function bytesToHex(data, separator = " ") {
requireBytes(data);
return Array.from(data, (b) => b.toString(16).padStart(2, "0"))
.join(separator)
.toUpperCase();
}
export function hexToBytes(text) {
const s = String(text).replace(/[^0-9a-f]/gi, "");
if (s.length % 2) throw new Error("Hex text must contain complete bytes");
const out = new Uint8Array(s.length / 2);
for (let i = 0; i < out.length; i++)
out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
return out;
}
export function asciiPreview(data, replacement = ".") {
requireBytes(data);
return Array.from(data, (b) =>
b >= 32 && b <= 126 ? String.fromCharCode(b) : replacement,
).join("");
}
export function readU16LE(d, o) {
assertRange(d, o, 2);
return d[o] | (d[o + 1] << 8);
}
export function readU16BE(d, o) {
assertRange(d, o, 2);
return (d[o] << 8) | d[o + 1];
}
export function readU32LE(d, o) {
assertRange(d, o, 4);
return (d[o] | (d[o + 1] << 8) | (d[o + 2] << 16) | (d[o + 3] << 24)) >>> 0;
}
export function readU32BE(d, o) {
assertRange(d, o, 4);
return ((d[o] << 24) | (d[o + 1] << 16) | (d[o + 2] << 8) | d[o + 3]) >>> 0;
}
export function writeU16LE(d, o, v) {
assertRange(d, o, 2);
d[o] = v & 255;
d[o + 1] = (v >>> 8) & 255;
}
export function writeU16BE(d, o, v) {
assertRange(d, o, 2);
d[o] = (v >>> 8) & 255;
d[o + 1] = v & 255;
}
export function writeU32LE(d, o, v) {
assertRange(d, o, 4);
for (let i = 0; i < 4; i++) d[o + i] = (v >>> (8 * i)) & 255;
}
export function writeU32BE(d, o, v) {
assertRange(d, o, 4);
for (let i = 0; i < 4; i++) d[o + i] = (v >>> (8 * (3 - i))) & 255;
}
export function findPattern(data, pattern, start = 0) {
requireBytes(data);
requireBytes(pattern);
const hits = [];
for (let i = start; i <= data.length - pattern.length; i++) {
let ok = true;
for (let j = 0; j < pattern.length; j++)
if (data[i + j] !== pattern[j]) {
ok = false;
break;
}
if (ok) hits.push(i);
}
return hits;
}
export function countByte(data, value) {
requireBytes(data);
let n = 0;
for (const b of data) if (b === (value & 255)) n++;
return n;
}
export function isBlank(
data,
offset = 0,
length = data.length - offset,
blank = 0xff,
) {
assertRange(data, offset, length);
for (let i = offset; i < offset + length; i++)
if (data[i] !== blank) return false;
return true;
}
export function diffRanges(a, b) {
requireBytes(a);
requireBytes(b);
if (a.length !== b.length) throw new Error("Length mismatch");
const out = [];
let s = -1;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i] && s < 0) s = i;
if ((a[i] === b[i] || i === a.length - 1) && s >= 0) {
const e = a[i] === b[i] ? i - 1 : i;
out.push({ start: s, end: e, length: e - s + 1 });
s = -1;
}
}
return out;
}
export function changedByteCount(a, b) {
return diffRanges(a, b).reduce((n, r) => n + r.length, 0);
}
export function sum8(data) {
requireBytes(data);
let s = 0;
for (const b of data) s = (s + b) & 255;
return s;
}
export function xor8(data) {
requireBytes(data);
let x = 0;
for (const b of data) x ^= b;
return x;
}
export function crc16CcittFalse(data) {
requireBytes(data);
let crc = 0xffff;
for (const b of data) {
crc ^= b << 8;
for (let i = 0; i < 8; i++)
crc = (crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1) & 0xffff;
}
return crc;
}
export function crc32(data) {
requireBytes(data);
let crc = 0xffffffff;
for (const b of data) {
crc ^= b;
for (let i = 0; i < 8; i++) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
}
return (crc ^ 0xffffffff) >>> 0;
}
export function adler32(data) {
requireBytes(data);
let a = 1,
b = 0;
for (const v of data) {
a = (a + v) % 65521;
b = (b + a) % 65521;
}
return ((b << 16) | a) >>> 0;
}
export function bcdToInt(byte) {
const hi = (byte >>> 4) & 15,
lo = byte & 15;
if (hi > 9 || lo > 9) throw new Error("Invalid BCD");
return hi * 10 + lo;
}
export function intToBcd(value) {
if (!Number.isInteger(value) || value < 0 || value > 99)
throw new RangeError("BCD value must be 0..99");
return (Math.floor(value / 10) << 4) | (value % 10);
}
export function constantTimeEqual(a, b) {
requireBytes(a);
requireBytes(b);
if (a.length !== b.length) return false;
let d = 0;
for (let i = 0; i < a.length; i++) d |= a[i] ^ b[i];
return d === 0;
}
export function allowListedWrite(copy, offset, newBytes, allowedRanges) {
requireBytes(copy);
requireBytes(newBytes);
assertRange(copy, offset, newBytes.length);
const ok = allowedRanges.some(
(r) => offset >= r.start && offset + newBytes.length - 1 <= r.end,
);
if (!ok) throw new Error("Write is outside allow-list");
copy.set(newBytes, offset);
return copy;
}
export function asciiPreview(data,replacement="."){ requireBytes(data); return Array.from(data,b=>b>=32&&b<=126?String.fromCharCode(b):replacement).join(""); }
export function readU16LE(d,o){assertRange(d,o,2);return d[o]|(d[o+1]<<8);}
export function readU16BE(d,o){assertRange(d,o,2);return (d[o]<<8)|d[o+1];}
export function readU32LE(d,o){assertRange(d,o,4);return (d[o]|d[o+1]<<8|d[o+2]<<16|d[o+3]<<24)>>>0;}
export function readU32BE(d,o){assertRange(d,o,4);return ((d[o]<<24)|(d[o+1]<<16)|(d[o+2]<<8)|d[o+3])>>>0;}
export function writeU16LE(d,o,v){assertRange(d,o,2);d[o]=v&255;d[o+1]=(v>>>8)&255;}
export function writeU16BE(d,o,v){assertRange(d,o,2);d[o]=(v>>>8)&255;d[o+1]=v&255;}
export function writeU32LE(d,o,v){assertRange(d,o,4);for(let i=0;i<4;i++)d[o+i]=(v>>>(8*i))&255;}
export function writeU32BE(d,o,v){assertRange(d,o,4);for(let i=0;i<4;i++)d[o+i]=(v>>>(8*(3-i)))&255;}
export function findPattern(data,pattern,start=0){requireBytes(data);requireBytes(pattern);const hits=[];for(let i=start;i<=data.length-pattern.length;i++){let ok=true;for(let j=0;j<pattern.length;j++)if(data[i+j]!==pattern[j]){ok=false;break;}if(ok)hits.push(i);}return hits;}
export function countByte(data,value){requireBytes(data);let n=0;for(const b of data)if(b===(value&255))n++;return n;}
export function isBlank(data,offset=0,length=data.length-offset,blank=0xFF){assertRange(data,offset,length);for(let i=offset;i<offset+length;i++)if(data[i]!==blank)return false;return true;}
export function diffRanges(a,b){requireBytes(a);requireBytes(b);if(a.length!==b.length)throw new Error("Length mismatch");const out=[];let s=-1;for(let i=0;i<a.length;i++){if(a[i]!==b[i]&&s<0)s=i;if((a[i]===b[i]||i===a.length-1)&&s>=0){const e=a[i]===b[i]?i-1:i;out.push({start:s,end:e,length:e-s+1});s=-1;}}return out;}
export function changedByteCount(a,b){return diffRanges(a,b).reduce((n,r)=>n+r.length,0);}
export function sum8(data){requireBytes(data);let s=0;for(const b of data)s=(s+b)&255;return s;}
export function xor8(data){requireBytes(data);let x=0;for(const b of data)x^=b;return x;}
export function crc16CcittFalse(data){requireBytes(data);let crc=0xFFFF;for(const b of data){crc^=b<<8;for(let i=0;i<8;i++)crc=((crc&0x8000)?(crc<<1)^0x1021:crc<<1)&0xFFFF;}return crc;}
export function crc32(data){requireBytes(data);let crc=0xFFFFFFFF;for(const b of data){crc^=b;for(let i=0;i<8;i++)crc=(crc>>>1)^((crc&1)?0xEDB88320:0);}return (crc^0xFFFFFFFF)>>>0;}
export function adler32(data){requireBytes(data);let a=1,b=0;for(const v of data){a=(a+v)%65521;b=(b+a)%65521;}return ((b<<16)|a)>>>0;}
export function bcdToInt(byte){const hi=(byte>>>4)&15,lo=byte&15;if(hi>9||lo>9)throw new Error("Invalid BCD");return hi*10+lo;}
export function intToBcd(value){if(!Number.isInteger(value)||value<0||value>99)throw new RangeError("BCD value must be 0..99");return ((Math.floor(value/10)<<4)|(value%10));}
export function constantTimeEqual(a,b){requireBytes(a);requireBytes(b);if(a.length!==b.length)return false;let d=0;for(let i=0;i<a.length;i++)d|=a[i]^b[i];return d===0;}
export function allowListedWrite(copy,offset,newBytes,allowedRanges){requireBytes(copy);requireBytes(newBytes);assertRange(copy,offset,newBytes.length);const ok=allowedRanges.some(r=>offset>=r.start&&offset+newBytes.length-1<=r.end);if(!ok)throw new Error("Write is outside allow-list");copy.set(newBytes,offset);return copy;}
@@ -1,7 +1,10 @@
import { strict as assert } from "node:assert";
import { crc32, diffRanges, bytesToHex } from "./binary_utils.mjs";
const v=new TextEncoder().encode("123456789");
assert.equal(crc32(v),0xCBF43926);
assert.equal(bytesToHex(new Uint8Array([0,15,255])),"00 0F FF");
assert.deepEqual(diffRanges(new Uint8Array([1,2,3,4]),new Uint8Array([1,9,8,4])),[{start:1,end:2,length:2}]);
const v = new TextEncoder().encode("123456789");
assert.equal(crc32(v), 0xcbf43926);
assert.equal(bytesToHex(new Uint8Array([0, 15, 255])), "00 0F FF");
assert.deepEqual(
diffRanges(new Uint8Array([1, 2, 3, 4]), new Uint8Array([1, 9, 8, 4])),
[{ start: 1, end: 2, length: 2 }],
);
console.log("All tests passed");
@@ -11,19 +11,29 @@ export function buildScriptIndex(sourceText, options = {}) {
const source = toText(sourceText);
const path = toText(options.path || "");
const headerMatch = source.match(/^\/\*([\s\S]*?)\*\//m);
const functions = [...source.matchAll(/(?:export\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/g)].map((match) => ({
const functions = [
...source.matchAll(/(?:export\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/g),
].map((match) => ({
name: match[1],
line: lineNumberAtIndex(source, match.index ?? 0)
line: lineNumberAtIndex(source, match.index ?? 0),
}));
const classes = [...source.matchAll(/class\s+([A-Za-z_$][\w$]*)/g)].map((match) => ({
const classes = [...source.matchAll(/class\s+([A-Za-z_$][\w$]*)/g)].map(
(match) => ({
name: match[1],
line: lineNumberAtIndex(source, match.index ?? 0),
}),
);
const imports = [
...source.matchAll(/import\s+(?:[^"']+?\s+from\s+)?["']([^"']+)["']/g),
].map((match) => ({
specifier: match[1],
}));
const exports = [
...source.matchAll(
/export\s+(?:function|class|const|let|var)\s+([A-Za-z_$][\w$]*)/g,
),
].map((match) => ({
name: match[1],
line: lineNumberAtIndex(source, match.index ?? 0)
}));
const imports = [...source.matchAll(/import\s+(?:[^"']+?\s+from\s+)?["']([^"']+)["']/g)].map((match) => ({
specifier: match[1]
}));
const exports = [...source.matchAll(/export\s+(?:function|class|const|let|var)\s+([A-Za-z_$][\w$]*)/g)].map((match) => ({
name: match[1]
}));
return {
path,
@@ -32,7 +42,7 @@ export function buildScriptIndex(sourceText, options = {}) {
functions,
classes,
imports,
exports
exports,
};
}
+48 -11
View File
@@ -1,21 +1,58 @@
/* Minimal stateful binary reader over a Uint8Array, similar in spirit to jBinary/binary-parser readers. */
export class BinaryReader {
constructor(data) {
if (!(data instanceof Uint8Array)) throw new TypeError("Uint8Array required");
if (!(data instanceof Uint8Array))
throw new TypeError("Uint8Array required");
this.data = data;
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
this.pos = 0;
}
get remaining() { return this.data.length - this.pos; }
seek(pos) { this.pos = pos; return this; }
skip(n) { this.pos += n; return this; }
readUint8() { const v = this.view.getUint8(this.pos); this.pos += 1; return v; }
readInt8() { const v = this.view.getInt8(this.pos); this.pos += 1; return v; }
readUint16LE() { const v = this.view.getUint16(this.pos, true); this.pos += 2; return v; }
readUint16BE() { const v = this.view.getUint16(this.pos, false); this.pos += 2; return v; }
readUint32LE() { const v = this.view.getUint32(this.pos, true); this.pos += 4; return v; }
readUint32BE() { const v = this.view.getUint32(this.pos, false); this.pos += 4; return v; }
readBytes(n) { const v = this.data.slice(this.pos, this.pos + n); this.pos += n; return v; }
get remaining() {
return this.data.length - this.pos;
}
seek(pos) {
this.pos = pos;
return this;
}
skip(n) {
this.pos += n;
return this;
}
readUint8() {
const v = this.view.getUint8(this.pos);
this.pos += 1;
return v;
}
readInt8() {
const v = this.view.getInt8(this.pos);
this.pos += 1;
return v;
}
readUint16LE() {
const v = this.view.getUint16(this.pos, true);
this.pos += 2;
return v;
}
readUint16BE() {
const v = this.view.getUint16(this.pos, false);
this.pos += 2;
return v;
}
readUint32LE() {
const v = this.view.getUint32(this.pos, true);
this.pos += 4;
return v;
}
readUint32BE() {
const v = this.view.getUint32(this.pos, false);
this.pos += 4;
return v;
}
readBytes(n) {
const v = this.data.slice(this.pos, this.pos + n);
this.pos += n;
return v;
}
readCString(maxLen = this.remaining) {
const start = this.pos;
let end = start;
+10 -8
View File
@@ -8,14 +8,14 @@ import { BinaryReader } from "./dataview_helpers.mjs";
])
*/
const READERS = {
u8: r => r.readUint8(),
i8: r => r.readInt8(),
u16le: r => r.readUint16LE(),
u16be: r => r.readUint16BE(),
u32le: r => r.readUint32LE(),
u32be: r => r.readUint32BE(),
u8: (r) => r.readUint8(),
i8: (r) => r.readInt8(),
u16le: (r) => r.readUint16LE(),
u16be: (r) => r.readUint16BE(),
u32le: (r) => r.readUint32LE(),
u32be: (r) => r.readUint32BE(),
cstring: (r, field) => r.readCString(field.maxLen),
bytes: (r, field) => r.readBytes(field.length)
bytes: (r, field) => r.readBytes(field.length),
};
export function parseStruct(data, fields) {
@@ -25,7 +25,9 @@ export function parseStruct(data, fields) {
const readOne = READERS[field.type];
if (!readOne) throw new Error(`Unknown field type: ${field.type}`);
if (field.count) {
out[field.name] = Array.from({ length: field.count }, () => readOne(reader, field));
out[field.name] = Array.from({ length: field.count }, () =>
readOne(reader, field),
);
} else {
out[field.name] = readOne(reader, field);
}
+2 -1
View File
@@ -8,7 +8,8 @@ const MOD_ADLER = 65521;
/* Adler-32 (zlib). Check value for "123456789" is 0x091E01DE. */
export function adler32(data) {
requireBytes(data);
let a = 1, b = 0;
let a = 1,
b = 0;
for (const byte of data) {
a = (a + byte) % MOD_ADLER;
b = (b + a) % MOD_ADLER;
+5 -5
View File
@@ -6,11 +6,11 @@ function requireBytes(data) {
/* CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, no reflect). Check value for "123456789" is 0x29B1. */
export function crc16CcittFalse(data) {
requireBytes(data);
let crc = 0xFFFF;
let crc = 0xffff;
for (const b of data) {
crc ^= b << 8;
for (let i = 0; i < 8; i++) {
crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xFFFF : (crc << 1) & 0xFFFF;
crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;
}
}
return crc;
@@ -19,12 +19,12 @@ export function crc16CcittFalse(data) {
/* CRC-16/MODBUS (poly 0x8005 reflected as 0xA001, init 0xFFFF, reflected). Check value for "123456789" is 0x4B37. */
export function crc16Modbus(data) {
requireBytes(data);
let crc = 0xFFFF;
let crc = 0xffff;
for (const byte of data) {
crc ^= byte;
for (let i = 0; i < 8; i++) {
crc = (crc & 1) ? (crc >>> 1) ^ 0xA001 : crc >>> 1;
crc = crc & 1 ? (crc >>> 1) ^ 0xa001 : crc >>> 1;
}
}
return crc & 0xFFFF;
return crc & 0xffff;
}
+11 -8
View File
@@ -1,9 +1,12 @@
export function normalizeBytes(data) {
if (data instanceof Uint8Array) return data;
if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
if (ArrayBuffer.isView(data))
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
if (data instanceof ArrayBuffer) return new Uint8Array(data);
if (typeof data === "string") return new TextEncoder().encode(data);
throw new TypeError("Uint8Array, ArrayBuffer, typed array, or string required");
throw new TypeError(
"Uint8Array, ArrayBuffer, typed array, or string required",
);
}
function requireBytes(data) {
@@ -13,25 +16,25 @@ function requireBytes(data) {
/* CRC-32 (IEEE 802.3, poly 0xEDB88320 reflected, init 0xFFFFFFFF). Check value for "123456789" is 0xCBF43926. */
export function crc32(data) {
const bytes = requireBytes(data);
let crc = 0xFFFFFFFF;
let crc = 0xffffffff;
for (const b of bytes) {
crc ^= b;
for (let i = 0; i < 8; i++) {
crc = (crc >>> 1) ^ ((crc & 1) ? 0xEDB88320 : 0);
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
}
}
return (crc ^ 0xFFFFFFFF) >>> 0;
return (crc ^ 0xffffffff) >>> 0;
}
/* CRC-32C (Castagnoli, poly 0x82F63B78 reflected, init 0xFFFFFFFF). Check value for "123456789" is 0xE3069283. */
export function crc32c(data) {
const bytes = requireBytes(data);
let crc = 0xFFFFFFFF;
let crc = 0xffffffff;
for (const b of bytes) {
crc ^= b;
for (let i = 0; i < 8; i++) {
crc = (crc & 1) ? (crc >>> 1) ^ 0x82F63B78 : crc >>> 1;
crc = crc & 1 ? (crc >>> 1) ^ 0x82f63b78 : crc >>> 1;
}
}
return (crc ^ 0xFFFFFFFF) >>> 0;
return (crc ^ 0xffffffff) >>> 0;
}
+3 -3
View File
@@ -3,8 +3,8 @@ function requireBytes(data) {
return data;
}
const POLY = 0xC96C5795D7870F42n;
const MASK64 = 0xFFFFFFFFFFFFFFFFn;
const POLY = 0xc96c5795d7870f42n;
const MASK64 = 0xffffffffffffffffn;
/* CRC-64/XZ (poly 0x42F0E1EBA9EA3693 reflected, init all-ones, reflected). Check value for "123456789" is 0x995DC9BBDF1939FA.
Uses BigInt; returns a zero-padded 16-hex-digit string since the value exceeds safe integer range. */
@@ -14,7 +14,7 @@ export function crc64Xz(data) {
for (const b of data) {
crc ^= BigInt(b);
for (let i = 0; i < 8; i++) {
crc = (crc & 1n) ? ((crc >> 1n) ^ POLY) & MASK64 : (crc >> 1n) & MASK64;
crc = crc & 1n ? ((crc >> 1n) ^ POLY) & MASK64 : (crc >> 1n) & MASK64;
}
}
return ((crc ^ MASK64) & MASK64).toString(16).padStart(16, "0");
+2 -2
View File
@@ -6,11 +6,11 @@ function requireBytes(data) {
/* CRC-8 (poly 0x07, init 0x00, no reflect, no xorout). Check value for ASCII "123456789" is 0xF4. */
export function crc8(data, poly = 0x07, init = 0x00) {
requireBytes(data);
let crc = init & 0xFF;
let crc = init & 0xff;
for (const b of data) {
crc ^= b;
for (let i = 0; i < 8; i++) {
crc = (crc & 0x80) ? ((crc << 1) ^ poly) & 0xFF : (crc << 1) & 0xFF;
crc = crc & 0x80 ? ((crc << 1) ^ poly) & 0xff : (crc << 1) & 0xff;
}
}
return crc;
+6 -3
View File
@@ -6,7 +6,8 @@ function requireBytes(data) {
/* Fletcher-16 over bytes (modulus 255). */
export function fletcher16(data) {
requireBytes(data);
let sum1 = 0, sum2 = 0;
let sum1 = 0,
sum2 = 0;
for (const b of data) {
sum1 = (sum1 + b) % 255;
sum2 = (sum2 + sum1) % 255;
@@ -18,9 +19,11 @@ export function fletcher16(data) {
export function fletcher32(data) {
requireBytes(data);
const words = [];
for (let i = 0; i + 1 < data.length; i += 2) words.push(data[i] | (data[i + 1] << 8));
for (let i = 0; i + 1 < data.length; i += 2)
words.push(data[i] | (data[i + 1] << 8));
if (data.length % 2 === 1) words.push(data[data.length - 1]);
let sum1 = 0, sum2 = 0;
let sum1 = 0,
sum2 = 0;
for (const w of words) {
sum1 = (sum1 + w) % 65535;
sum2 = (sum2 + sum1) % 65535;
+4 -2
View File
@@ -9,14 +9,16 @@ export function isPrintableAscii(byte) {
export function asciiPreview(data, replacement = ".") {
requireBytes(data);
return Array.from(data, b => (isPrintableAscii(b) ? String.fromCharCode(b) : replacement)).join("");
return Array.from(data, (b) =>
isPrintableAscii(b) ? String.fromCharCode(b) : replacement,
).join("");
}
export function encodeAscii(text) {
const out = new Uint8Array(text.length);
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i);
out[i] = code <= 127 ? code : 0x3F; // '?' for non-ASCII input
out[i] = code <= 127 ? code : 0x3f; // '?' for non-ASCII input
}
return out;
}
+5 -3
View File
@@ -4,14 +4,16 @@ function requireBytes(data) {
}
export function bcdByteToInt(byte) {
const hi = (byte >>> 4) & 0xF, lo = byte & 0xF;
const hi = (byte >>> 4) & 0xf,
lo = byte & 0xf;
if (hi > 9 || lo > 9) throw new Error("Invalid BCD nibble");
return hi * 10 + lo;
}
export function intToBcdByte(value) {
if (!Number.isInteger(value) || value < 0 || value > 99) throw new RangeError("BCD value must be 0..99");
return ((Math.floor(value / 10) << 4) | (value % 10)) & 0xFF;
if (!Number.isInteger(value) || value < 0 || value > 99)
throw new RangeError("BCD value must be 0..99");
return ((Math.floor(value / 10) << 4) | (value % 10)) & 0xff;
}
/* Decodes a run of packed-BCD bytes into a decimal digit string, e.g. [0x20,0x26] -> "2026". */
+5 -2
View File
@@ -5,13 +5,16 @@ function requireBytes(data) {
export function encodeHex(data, separator = "") {
requireBytes(data);
return Array.from(data, b => b.toString(16).padStart(2, "0")).join(separator).toUpperCase();
return Array.from(data, (b) => b.toString(16).padStart(2, "0"))
.join(separator)
.toUpperCase();
}
export function decodeHex(text) {
const s = String(text).replace(/[^0-9a-f]/gi, "");
if (s.length % 2) throw new Error("Hex text must contain complete bytes");
const out = new Uint8Array(s.length / 2);
for (let i = 0; i < out.length; i++) out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
for (let i = 0; i < out.length; i++)
out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
return out;
}
+39 -15
View File
@@ -3,16 +3,19 @@ function requireBytes(data) {
return data;
}
function rotl(x, n) { return (x << n) | (x >>> (32 - n)); }
function rotl(x, n) {
return (x << n) | (x >>> (32 - n));
}
const S = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5,
9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11,
16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15,
21,
];
const K = new Int32Array(64);
for (let i = 0; i < 64; i++) K[i] = (Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000)) | 0;
for (let i = 0; i < 64; i++)
K[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000) | 0;
function padMessageLE(data) {
let k = data.length + 1;
@@ -29,7 +32,8 @@ function padMessageLE(data) {
function toHexLE(v) {
let s = "";
for (let i = 0; i < 4; i++) s += ((v >>> (i * 8)) & 0xFF).toString(16).padStart(2, "0");
for (let i = 0; i < 4; i++)
s += ((v >>> (i * 8)) & 0xff).toString(16).padStart(2, "0");
return s;
}
@@ -38,22 +42,42 @@ export function md5(data) {
requireBytes(data);
const msg = padMessageLE(data);
const dv = new DataView(msg.buffer);
let a0 = 0x67452301, b0 = 0xefcdab89, c0 = 0x98badcfe, d0 = 0x10325476;
let a0 = 0x67452301,
b0 = 0xefcdab89,
c0 = 0x98badcfe,
d0 = 0x10325476;
const M = new Uint32Array(16);
for (let off = 0; off < msg.length; off += 64) {
for (let j = 0; j < 16; j++) M[j] = dv.getUint32(off + j * 4, true);
let A = a0, B = b0, C = c0, D = d0;
let A = a0,
B = b0,
C = c0,
D = d0;
for (let i = 0; i < 64; i++) {
let F, g;
if (i < 16) { F = (B & C) | ((~B) & D); g = i; }
else if (i < 32) { F = (D & B) | ((~D) & C); g = (5 * i + 1) % 16; }
else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; }
else { F = C ^ (B | (~D)); g = (7 * i) % 16; }
if (i < 16) {
F = (B & C) | (~B & D);
g = i;
} else if (i < 32) {
F = (D & B) | (~D & C);
g = (5 * i + 1) % 16;
} else if (i < 48) {
F = B ^ C ^ D;
g = (3 * i + 5) % 16;
} else {
F = C ^ (B | ~D);
g = (7 * i) % 16;
}
F = (F + A + K[i] + M[g]) >>> 0;
A = D; D = C; C = B;
A = D;
D = C;
C = B;
B = (B + rotl(F, S[i])) >>> 0;
}
a0 = (a0 + A) >>> 0; b0 = (b0 + B) >>> 0; c0 = (c0 + C) >>> 0; d0 = (d0 + D) >>> 0;
a0 = (a0 + A) >>> 0;
b0 = (b0 + B) >>> 0;
c0 = (c0 + C) >>> 0;
d0 = (d0 + D) >>> 0;
}
return toHexLE(a0) + toHexLE(b0) + toHexLE(c0) + toHexLE(d0);
}
+39 -10
View File
@@ -5,14 +5,20 @@ function requireBytes(data) {
return data;
}
function rotl(x, n) { return (x << n) | (x >>> (32 - n)); }
function rotl(x, n) {
return (x << n) | (x >>> (32 - n));
}
/* SHA-1 reference implementation (FIPS 180-4). Check value for "abc" is a9993e364706816aba3e25717850c26c9cd0d89. */
export function sha1(data) {
requireBytes(data);
const msg = padMessageBE(data);
const dv = new DataView(msg.buffer);
let h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0;
let h0 = 0x67452301,
h1 = 0xefcdab89,
h2 = 0x98badcfe,
h3 = 0x10325476,
h4 = 0xc3d2e1f0;
const w = new Uint32Array(80);
for (let off = 0; off < msg.length; off += 64) {
for (let t = 0; t < 16; t++) w[t] = dv.getUint32(off + t * 4);
@@ -20,17 +26,40 @@ export function sha1(data) {
const v = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
w[t] = rotl(v, 1);
}
let a = h0, b = h1, c = h2, d = h3, e = h4;
let a = h0,
b = h1,
c = h2,
d = h3,
e = h4;
for (let t = 0; t < 80; t++) {
let f, k;
if (t < 20) { f = (b & c) | ((~b) & d); k = 0x5A827999; }
else if (t < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; }
else if (t < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; }
else { f = b ^ c ^ d; k = 0xCA62C1D6; }
if (t < 20) {
f = (b & c) | (~b & d);
k = 0x5a827999;
} else if (t < 40) {
f = b ^ c ^ d;
k = 0x6ed9eba1;
} else if (t < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8f1bbcdc;
} else {
f = b ^ c ^ d;
k = 0xca62c1d6;
}
const temp = (rotl(a, 5) + f + e + k + w[t]) >>> 0;
e = d; d = c; c = rotl(b, 30); b = a; a = temp;
e = d;
d = c;
c = rotl(b, 30);
b = a;
a = temp;
}
h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0; h2 = (h2 + c) >>> 0; h3 = (h3 + d) >>> 0; h4 = (h4 + e) >>> 0;
h0 = (h0 + a) >>> 0;
h1 = (h1 + b) >>> 0;
h2 = (h2 + c) >>> 0;
h3 = (h3 + d) >>> 0;
h4 = (h4 + e) >>> 0;
}
return [h0, h1, h2, h3, h4].map(v => v.toString(16).padStart(8, "0")).join("");
return [h0, h1, h2, h3, h4]
.map((v) => v.toString(16).padStart(8, "0"))
.join("");
}
+34 -17
View File
@@ -6,24 +6,29 @@ function requireBytes(data) {
}
const K = new Uint32Array([
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
]);
function rotr(x, n) { return (x >>> n) | (x << (32 - n)); }
function rotr(x, n) {
return (x >>> n) | (x << (32 - n));
}
/* SHA-256 reference implementation (FIPS 180-4). Check value for "abc" is ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad. */
export function sha256(data) {
requireBytes(data);
const H = new Uint32Array([
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,
0x1f83d9ab, 0x5be0cd19,
]);
const msg = padMessageBE(data);
const dv = new DataView(msg.buffer);
@@ -38,16 +43,28 @@ export function sha256(data) {
let [a, b, c, d, e, f, g, h] = H;
for (let t = 0; t < 64; t++) {
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
const ch = (e & f) ^ ((~e) & g);
const ch = (e & f) ^ (~e & g);
const temp1 = (h + S1 + ch + K[t] + W[t]) >>> 0;
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
const maj = (a & b) ^ (a & c) ^ (b & c);
const temp2 = (S0 + maj) >>> 0;
h = g; g = f; f = e; e = (d + temp1) >>> 0;
d = c; c = b; b = a; a = (temp1 + temp2) >>> 0;
h = g;
g = f;
f = e;
e = (d + temp1) >>> 0;
d = c;
c = b;
b = a;
a = (temp1 + temp2) >>> 0;
}
H[0] = (H[0] + a) >>> 0; H[1] = (H[1] + b) >>> 0; H[2] = (H[2] + c) >>> 0; H[3] = (H[3] + d) >>> 0;
H[4] = (H[4] + e) >>> 0; H[5] = (H[5] + f) >>> 0; H[6] = (H[6] + g) >>> 0; H[7] = (H[7] + h) >>> 0;
H[0] = (H[0] + a) >>> 0;
H[1] = (H[1] + b) >>> 0;
H[2] = (H[2] + c) >>> 0;
H[3] = (H[3] + d) >>> 0;
H[4] = (H[4] + e) >>> 0;
H[5] = (H[5] + f) >>> 0;
H[6] = (H[6] + g) >>> 0;
H[7] = (H[7] + h) >>> 0;
}
return Array.from(H, v => v.toString(16).padStart(8, "0")).join("");
return Array.from(H, (v) => v.toString(16).padStart(8, "0")).join("");
}
+11 -4
View File
@@ -1,11 +1,18 @@
import assert from "node:assert/strict";
import { crc32, crc32c, normalizeBytes } from "../checksums/crc32.mjs";
import { buildScriptIndex, extractFunctionNames, summarizeScriptIndex } from "../administrative/script_catalog.mjs";
import {
buildScriptIndex,
extractFunctionNames,
summarizeScriptIndex,
} from "../administrative/script_catalog.mjs";
const bytes = normalizeBytes("123456789");
assert.deepEqual(Array.from(bytes), [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39]);
assert.equal(crc32(bytes), 0xCBF43926);
assert.equal(crc32c(bytes), 0xE3069283);
assert.deepEqual(
Array.from(bytes),
[0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39],
);
assert.equal(crc32(bytes), 0xcbf43926);
assert.equal(crc32c(bytes), 0xe3069283);
const source = `/* Safe administrative note */
export function openCase() {}
+10 -8
View File
@@ -13,16 +13,18 @@ export function runSelfTest() {
const check = new TextEncoder().encode("123456789");
const abc = new TextEncoder().encode("abc");
const results = {
crc8: crc8(check) === 0xF4,
crc16CcittFalse: crc16CcittFalse(check) === 0x29B1,
crc16Modbus: crc16Modbus(check) === 0x4B37,
crc32: crc32(check) === 0xCBF43926,
crc32c: crc32c(check) === 0xE3069283,
crc8: crc8(check) === 0xf4,
crc16CcittFalse: crc16CcittFalse(check) === 0x29b1,
crc16Modbus: crc16Modbus(check) === 0x4b37,
crc32: crc32(check) === 0xcbf43926,
crc32c: crc32c(check) === 0xe3069283,
crc64Xz: crc64Xz(check) === "995dc9bbdf1939fa",
adler32: adler32(check) === 0x091E01DE,
adler32: adler32(check) === 0x091e01de,
sha1: sha1(abc) === "a9993e364706816aba3e25717850c26c9cd0d89d",
sha256: sha256(abc) === "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
md5: md5(abc) === "900150983cd24fb0d6963f7d28e17f72"
sha256:
sha256(abc) ===
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
md5: md5(abc) === "900150983cd24fb0d6963f7d28e17f72",
};
results.allPassed = Object.values(results).every(Boolean);
return results;
+8 -4
View File
@@ -1,10 +1,14 @@
import { strict as assert } from 'node:assert';
import { cloneBytes, diffRanges, bytesToHex } from '../../03_Script_Starter_Kit/binary_utils.mjs';
import { strict as assert } from "node:assert";
import {
cloneBytes,
diffRanges,
bytesToHex,
} from "../../03_Script_Starter_Kit/binary_utils.mjs";
const sample = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
const clone = cloneBytes(sample);
assert.deepStrictEqual(clone, sample);
assert.strictEqual(bytesToHex(sample), '01 02 03 04');
assert.strictEqual(bytesToHex(sample), "01 02 03 04");
assert.deepStrictEqual(diffRanges(sample, clone), []);
console.log('buffer utils tests passed');
console.log("buffer utils tests passed");
@@ -3,14 +3,15 @@
* Demonstrates the structure expected for SDK-based script authorship.
*/
import { bytesToHex } from '../../03_Script_Starter_Kit/binary_utils.mjs';
import { bytesToHex } from "../../03_Script_Starter_Kit/binary_utils.mjs";
function buildExampleReport(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError('Uint8Array required');
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
return {
length: buffer.length,
hex: bytesToHex(buffer),
preview: buffer.slice(0, 8)
preview: buffer.slice(0, 8),
};
}
@@ -9,16 +9,19 @@ const SCRIPT_METADATA = {
version: "0.1.0",
purpose: "Binary processing",
author: "Repository contributor",
validation: "Synthetic test data only"
validation: "Synthetic test data only",
};
function toHex(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError("Uint8Array required");
return Array.from(buffer).map(b => b.toString(16).padStart(2, "0")).join(" ");
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
return Array.from(buffer)
.map((b) => b.toString(16).padStart(2, "0"))
.join(" ");
}
function main() {
const sample = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]);
const sample = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
console.log(toHex(sample));
}
@@ -5,15 +5,16 @@
*/
const SCRIPT_METADATA = {
name: 'template_byteswap_workflow',
version: '0.1.0',
purpose: 'Byte-swap workflow',
author: 'Repository contributor',
validation: 'Synthetic test data only'
name: "template_byteswap_workflow",
version: "0.1.0",
purpose: "Byte-swap workflow",
author: "Repository contributor",
validation: "Synthetic test data only",
};
function byteswap(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError('Uint8Array required');
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
const swapped = new Uint8Array(buffer.length);
for (let index = 0; index < buffer.length; index += 1) {
swapped[index] = buffer[buffer.length - 1 - index];
@@ -5,18 +5,19 @@
*/
const SCRIPT_METADATA = {
name: 'template_checksum_workflow',
version: '0.1.0',
purpose: 'Checksum validation workflow',
author: 'Repository contributor',
validation: 'Synthetic test data only'
name: "template_checksum_workflow",
version: "0.1.0",
purpose: "Checksum validation workflow",
author: "Repository contributor",
validation: "Synthetic test data only",
};
function checksumWorkflow(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError('Uint8Array required');
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
let sum = 0;
for (const byte of buffer) sum += byte;
return { length: buffer.length, checksum: sum & 0xFF };
return { length: buffer.length, checksum: sum & 0xff };
}
function main() {
@@ -5,15 +5,16 @@
*/
const SCRIPT_METADATA = {
name: 'template_compare_workflow',
version: '0.1.0',
purpose: 'Buffer comparison workflow',
author: 'Repository contributor',
validation: 'Synthetic test data only'
name: "template_compare_workflow",
version: "0.1.0",
purpose: "Buffer comparison workflow",
author: "Repository contributor",
validation: "Synthetic test data only",
};
function compareBuffers(left, right) {
if (!(left instanceof Uint8Array) || !(right instanceof Uint8Array)) throw new TypeError('Uint8Array required');
if (!(left instanceof Uint8Array) || !(right instanceof Uint8Array))
throw new TypeError("Uint8Array required");
const maxLength = Math.max(left.length, right.length);
const differences = [];
for (let index = 0; index < maxLength; index += 1) {
@@ -5,19 +5,21 @@
*/
const SCRIPT_METADATA = {
name: 'template_diff_report',
version: '0.1.0',
purpose: 'Buffer diff reporting',
author: 'Repository contributor',
validation: 'Synthetic test data only'
name: "template_diff_report",
version: "0.1.0",
purpose: "Buffer diff reporting",
author: "Repository contributor",
validation: "Synthetic test data only",
};
function buildDiffSummary(before, after) {
if (!(before instanceof Uint8Array) || !(after instanceof Uint8Array)) throw new TypeError('Uint8Array required');
if (!(before instanceof Uint8Array) || !(after instanceof Uint8Array))
throw new TypeError("Uint8Array required");
const changes = [];
const limit = Math.max(before.length, after.length);
for (let i = 0; i < limit; i++) {
if (before[i] !== after[i]) changes.push({ index: i, before: before[i], after: after[i] });
if (before[i] !== after[i])
changes.push({ index: i, before: before[i], after: after[i] });
}
return { changedBytes: changes.length, changes };
}
@@ -9,14 +9,15 @@ const SCRIPT_METADATA = {
version: "0.1.0",
purpose: "EEPROM-like processing",
author: "Repository contributor",
validation: "Synthetic test data only"
validation: "Synthetic test data only",
};
function buildReport(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError("Uint8Array required");
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
return {
length: buffer.length,
checksum: buffer.reduce((sum, byte) => sum + byte, 0)
checksum: buffer.reduce((sum, byte) => sum + byte, 0),
};
}
@@ -9,19 +9,20 @@ const SCRIPT_METADATA = {
version: "0.1.0",
purpose: "Flash-like processing",
author: "Repository contributor",
validation: "Synthetic test data only"
validation: "Synthetic test data only",
};
function inspectFlash(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError("Uint8Array required");
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
return {
length: buffer.length,
blankBytes: buffer.filter(byte => byte === 0xFF).length
blankBytes: buffer.filter((byte) => byte === 0xff).length,
};
}
function main() {
const sample = new Uint8Array([0xFF, 0x01, 0xFF, 0x02]);
const sample = new Uint8Array([0xff, 0x01, 0xff, 0x02]);
console.log(JSON.stringify(inspectFlash(sample)));
// HOST_READ_BUFFER();
}
@@ -5,24 +5,24 @@
*/
const SCRIPT_METADATA = {
name: 'template_inventory_workflow',
version: '0.1.0',
purpose: 'Inventory reconciliation workflow',
author: 'Repository contributor',
validation: 'Synthetic test data only'
name: "template_inventory_workflow",
version: "0.1.0",
purpose: "Inventory reconciliation workflow",
author: "Repository contributor",
validation: "Synthetic test data only",
};
function inventoryWorkflow(items) {
if (!Array.isArray(items)) throw new TypeError('Expected an array');
if (!Array.isArray(items)) throw new TypeError("Expected an array");
return {
itemCount: items.length,
uniqueCount: new Set(items).size,
summary: items.map(item => ({ value: item }))
summary: items.map((item) => ({ value: item })),
};
}
function main() {
const sample = ['A', 'B', 'A'];
const sample = ["A", "B", "A"];
console.log(JSON.stringify(inventoryWorkflow(sample), null, 2));
}
@@ -5,11 +5,11 @@
*/
const SCRIPT_METADATA = {
name: 'template_merge_workflow',
version: '0.1.0',
purpose: 'Buffer merge workflow',
author: 'Repository contributor',
validation: 'Synthetic test data only'
name: "template_merge_workflow",
version: "0.1.0",
purpose: "Buffer merge workflow",
author: "Repository contributor",
validation: "Synthetic test data only",
};
function mergeBuffers(...parts) {
@@ -17,7 +17,8 @@ function mergeBuffers(...parts) {
const merged = new Uint8Array(totalLength);
let offset = 0;
for (const part of parts) {
if (!(part instanceof Uint8Array)) throw new TypeError('Uint8Array required');
if (!(part instanceof Uint8Array))
throw new TypeError("Uint8Array required");
merged.set(part, offset);
offset += part.length;
}
@@ -25,7 +26,10 @@ function mergeBuffers(...parts) {
}
function main() {
const merged = mergeBuffers(new Uint8Array([0x01, 0x02]), new Uint8Array([0x03, 0x04]));
const merged = mergeBuffers(
new Uint8Array([0x01, 0x02]),
new Uint8Array([0x03, 0x04]),
);
console.log(JSON.stringify(Array.from(merged)));
}
@@ -5,23 +5,30 @@
*/
const SCRIPT_METADATA = {
name: 'template_patch_guard',
version: '0.1.0',
purpose: 'Allow-listed patch validation',
author: 'Repository contributor',
validation: 'Synthetic test data only'
name: "template_patch_guard",
version: "0.1.0",
purpose: "Allow-listed patch validation",
author: "Repository contributor",
validation: "Synthetic test data only",
};
function validatePatchPlan(offset, patchBytes, allowedRanges) {
if (!Number.isInteger(offset) || offset < 0) throw new RangeError('offset must be a non-negative integer');
if (!(patchBytes instanceof Uint8Array)) throw new TypeError('Uint8Array required');
const allowed = allowedRanges.some(range => offset >= range.start && offset + patchBytes.length - 1 <= range.end);
if (!allowed) throw new Error('Patch falls outside allow-list');
if (!Number.isInteger(offset) || offset < 0)
throw new RangeError("offset must be a non-negative integer");
if (!(patchBytes instanceof Uint8Array))
throw new TypeError("Uint8Array required");
const allowed = allowedRanges.some(
(range) =>
offset >= range.start && offset + patchBytes.length - 1 <= range.end,
);
if (!allowed) throw new Error("Patch falls outside allow-list");
return { ok: true, offset, length: patchBytes.length };
}
function main() {
const plan = validatePatchPlan(16, new Uint8Array([0xAA]), [{ start: 0, end: 31 }]);
const plan = validatePatchPlan(16, new Uint8Array([0xaa]), [
{ start: 0, end: 31 },
]);
console.log(JSON.stringify(plan));
}
@@ -9,7 +9,7 @@ const SCRIPT_METADATA = {
version: "0.1.0",
purpose: "Read-only inspection",
author: "Repository contributor",
validation: "Synthetic test data only"
validation: "Synthetic test data only",
};
function log(message) {
@@ -17,9 +17,15 @@ function log(message) {
}
function inspectBuffer(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError("Uint8Array required");
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
log(`Buffer length: ${buffer.length}`);
return { length: buffer.length, preview: Array.from(buffer.slice(0, 16)).map(b => b.toString(16).padStart(2, "0")).join(" ") };
return {
length: buffer.length,
preview: Array.from(buffer.slice(0, 16))
.map((b) => b.toString(16).padStart(2, "0"))
.join(" "),
};
}
function main() {
@@ -9,14 +9,17 @@ const SCRIPT_METADATA = {
version: "0.1.0",
purpose: "Reporting workflow",
author: "Repository contributor",
validation: "Synthetic test data only"
validation: "Synthetic test data only",
};
function buildReport(buffer) {
if (!(buffer instanceof Uint8Array)) throw new TypeError("Uint8Array required");
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
return {
length: buffer.length,
hex: Array.from(buffer).map(b => b.toString(16).padStart(2, "0")).join(" ")
hex: Array.from(buffer)
.map((b) => b.toString(16).padStart(2, "0"))
.join(" "),
};
}
@@ -9,17 +9,21 @@ const SCRIPT_METADATA = {
version: "0.1.0",
purpose: "Validation workflow",
author: "Repository contributor",
validation: "Synthetic test data only"
validation: "Synthetic test data only",
};
function validateBuffer(buffer, expectedLength) {
if (!(buffer instanceof Uint8Array)) throw new TypeError("Uint8Array required");
if (buffer.length !== expectedLength) throw new RangeError(`Expected ${expectedLength} bytes but received ${buffer.length}`);
if (!(buffer instanceof Uint8Array))
throw new TypeError("Uint8Array required");
if (buffer.length !== expectedLength)
throw new RangeError(
`Expected ${expectedLength} bytes but received ${buffer.length}`,
);
return { ok: true, length: buffer.length };
}
function main() {
const sample = new Uint8Array([0xAA, 0xBB, 0xCC]);
const sample = new Uint8Array([0xaa, 0xbb, 0xcc]);
console.log(JSON.stringify(validateBuffer(sample, 3)));
}
+16 -8
View File
@@ -1,8 +1,14 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { cloneBytes, diffRanges, bytesToHex, readU16LE, writeU16LE } from '../../03_Script_Starter_Kit/binary_utils.mjs';
import test from "node:test";
import assert from "node:assert/strict";
import {
cloneBytes,
diffRanges,
bytesToHex,
readU16LE,
writeU16LE,
} from "../../03_Script_Starter_Kit/binary_utils.mjs";
test('cloneBytes preserves data and does not alias', () => {
test("cloneBytes preserves data and does not alias", () => {
const source = new Uint8Array([1, 2, 3, 4]);
const clone = cloneBytes(source);
clone[0] = 9;
@@ -10,15 +16,17 @@ test('cloneBytes preserves data and does not alias', () => {
assert.equal(clone[0], 9);
});
test('diffRanges reports changes correctly', () => {
test("diffRanges reports changes correctly", () => {
const before = new Uint8Array([0, 1, 2, 3]);
const after = new Uint8Array([0, 9, 2, 3]);
assert.deepEqual(diffRanges(before, after), [{ start: 1, end: 1, length: 1 }]);
assert.deepEqual(diffRanges(before, after), [
{ start: 1, end: 1, length: 1 },
]);
});
test('hex and numeric helpers work', () => {
test("hex and numeric helpers work", () => {
const buffer = new Uint8Array([0x34, 0x12]);
assert.equal(bytesToHex(buffer), '34 12');
assert.equal(bytesToHex(buffer), "34 12");
const out = new Uint8Array(2);
writeU16LE(out, 0, 0x1234);
assert.equal(readU16LE(out, 0), 0x1234);
+29 -22
View File
@@ -1,39 +1,46 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { crc, crcNamed, createCrcModel, CRC_MODELS } from '../utilities/crc_engine.mjs';
import test from "node:test";
import assert from "node:assert/strict";
import {
crc,
crcNamed,
createCrcModel,
CRC_MODELS,
} from "../utilities/crc_engine.mjs";
// Standard CRC "check" vectors for the ASCII string "123456789", as published by
// the CRC RevEng catalogue (https://reveng.sourceforge.io/crc-catalogue/).
const CHECK_MESSAGE = new TextEncoder().encode('123456789');
const CHECK_MESSAGE = new TextEncoder().encode("123456789");
test('named CRC models match published check values', () => {
assert.equal(crcNamed(CHECK_MESSAGE, 'crc8'), 0xF4);
assert.equal(crcNamed(CHECK_MESSAGE, 'crc8_maxim'), 0xA1);
assert.equal(crcNamed(CHECK_MESSAGE, 'crc16_ccitt_false'), 0x29B1);
assert.equal(crcNamed(CHECK_MESSAGE, 'crc16_arc'), 0xBB3D);
assert.equal(crcNamed(CHECK_MESSAGE, 'crc16_modbus'), 0x4B37);
assert.equal(crcNamed(CHECK_MESSAGE, 'crc32'), 0xCBF43926);
assert.equal(crcNamed(CHECK_MESSAGE, 'crc32_mpeg2'), 0x0376E6E7);
test("named CRC models match published check values", () => {
assert.equal(crcNamed(CHECK_MESSAGE, "crc8"), 0xf4);
assert.equal(crcNamed(CHECK_MESSAGE, "crc8_maxim"), 0xa1);
assert.equal(crcNamed(CHECK_MESSAGE, "crc16_ccitt_false"), 0x29b1);
assert.equal(crcNamed(CHECK_MESSAGE, "crc16_arc"), 0xbb3d);
assert.equal(crcNamed(CHECK_MESSAGE, "crc16_modbus"), 0x4b37);
assert.equal(crcNamed(CHECK_MESSAGE, "crc32"), 0xcbf43926);
assert.equal(crcNamed(CHECK_MESSAGE, "crc32_mpeg2"), 0x0376e6e7);
});
test('crcNamed rejects unknown model names', () => {
assert.throws(() => crcNamed(CHECK_MESSAGE, 'not_a_model'));
test("crcNamed rejects unknown model names", () => {
assert.throws(() => crcNamed(CHECK_MESSAGE, "not_a_model"));
});
test('crc rejects unsupported widths', () => {
assert.throws(() => crc(CHECK_MESSAGE, { ...CRC_MODELS.crc16_arc, width: 12 }));
test("crc rejects unsupported widths", () => {
assert.throws(() =>
crc(CHECK_MESSAGE, { ...CRC_MODELS.crc16_arc, width: 12 }),
);
});
test('crc requires a Uint8Array', () => {
test("crc requires a Uint8Array", () => {
assert.throws(() => crc([1, 2, 3], CRC_MODELS.crc32));
});
test('createCrcModel produces a reusable bound calculator', () => {
test("createCrcModel produces a reusable bound calculator", () => {
const calcCrc32 = createCrcModel(CRC_MODELS.crc32);
assert.equal(calcCrc32(CHECK_MESSAGE), 0xCBF43926);
assert.equal(calcCrc32(CHECK_MESSAGE), 0xcbf43926);
});
test('empty buffer returns the model init/xorout combination', () => {
assert.equal(crcNamed(new Uint8Array(0), 'crc32'), 0x00000000);
assert.equal(crcNamed(new Uint8Array(0), 'crc16_ccitt_false'), 0xFFFF);
test("empty buffer returns the model init/xorout combination", () => {
assert.equal(crcNamed(new Uint8Array(0), "crc32"), 0x00000000);
assert.equal(crcNamed(new Uint8Array(0), "crc16_ccitt_false"), 0xffff);
});
@@ -1,15 +1,18 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { encodeHex, decodeHex } from '../../09_Collected_JS/encoding/hex.mjs';
import { bcdByteToInt, intToBcdByte } from '../../09_Collected_JS/encoding/bcd.mjs';
import test from "node:test";
import assert from "node:assert/strict";
import { encodeHex, decodeHex } from "../../09_Collected_JS/encoding/hex.mjs";
import {
bcdByteToInt,
intToBcdByte,
} from "../../09_Collected_JS/encoding/bcd.mjs";
test('hex helpers round-trip bytes', () => {
const data = new Uint8Array([0xAA, 0xBB, 0x0F]);
assert.equal(encodeHex(data), 'AABB0F');
assert.deepEqual(Array.from(decodeHex('AABB0F')), [0xAA, 0xBB, 0x0F]);
test("hex helpers round-trip bytes", () => {
const data = new Uint8Array([0xaa, 0xbb, 0x0f]);
assert.equal(encodeHex(data), "AABB0F");
assert.deepEqual(Array.from(decodeHex("AABB0F")), [0xaa, 0xbb, 0x0f]);
});
test('BCD helpers convert values correctly', () => {
test("BCD helpers convert values correctly", () => {
assert.equal(bcdByteToInt(0x23), 23);
assert.equal(intToBcdByte(23), 0x23);
});
@@ -1,40 +1,51 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readdirSync, readFileSync } from 'node:fs';
import path from 'node:path';
import test from "node:test";
import assert from "node:assert/strict";
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
const templateDir = path.resolve('17_MultiPROG_SDK/templates');
const templates = readdirSync(templateDir).filter(name => name.endsWith('.mjs')).sort();
const templateDir = path.resolve("17_MultiPROG_SDK/templates");
const templates = readdirSync(templateDir)
.filter((name) => name.endsWith(".mjs"))
.sort();
const expectedTemplates = [
'template_binary_tool.mjs',
'template_byteswap_workflow.mjs',
'template_checksum_workflow.mjs',
'template_compare_workflow.mjs',
'template_diff_report.mjs',
'template_eeprom.mjs',
'template_flash.mjs',
'template_inventory_workflow.mjs',
'template_merge_workflow.mjs',
'template_patch_guard.mjs',
'template_readonly.mjs',
'template_reporting.mjs',
'template_validator.mjs'
"template_binary_tool.mjs",
"template_byteswap_workflow.mjs",
"template_checksum_workflow.mjs",
"template_compare_workflow.mjs",
"template_diff_report.mjs",
"template_eeprom.mjs",
"template_flash.mjs",
"template_inventory_workflow.mjs",
"template_merge_workflow.mjs",
"template_patch_guard.mjs",
"template_readonly.mjs",
"template_reporting.mjs",
"template_validator.mjs",
];
test('SDK templates catalog contains the expected starter set', () => {
test("SDK templates catalog contains the expected starter set", () => {
for (const template of expectedTemplates) {
assert.ok(templates.includes(template), `${template} is missing from the template catalog`);
assert.ok(
templates.includes(template),
`${template} is missing from the template catalog`,
);
}
});
test('compare, merge, and byte-swap templates include their workflow metadata', () => {
test("compare, merge, and byte-swap templates include their workflow metadata", () => {
const combined = templates
.filter(name => ['template_compare_workflow.mjs', 'template_merge_workflow.mjs', 'template_byteswap_workflow.mjs'].includes(name))
.map(name => readFileSync(path.join(templateDir, name), 'utf8'))
.join('\n');
.filter((name) =>
[
"template_compare_workflow.mjs",
"template_merge_workflow.mjs",
"template_byteswap_workflow.mjs",
].includes(name),
)
.map((name) => readFileSync(path.join(templateDir, name), "utf8"))
.join("\n");
assert.ok(combined.includes('Buffer comparison workflow'));
assert.ok(combined.includes('Buffer merge workflow'));
assert.ok(combined.includes('Byte-swap workflow'));
assert.ok(combined.includes("Buffer comparison workflow"));
assert.ok(combined.includes("Buffer merge workflow"));
assert.ok(combined.includes("Byte-swap workflow"));
});
@@ -1,35 +1,50 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
const templateDir = path.resolve('17_MultiPROG_SDK/templates');
const templateDir = path.resolve("17_MultiPROG_SDK/templates");
const checksumTemplate = readFileSync(path.join(templateDir, 'template_checksum_workflow.mjs'), 'utf8');
const inventoryTemplate = readFileSync(path.join(templateDir, 'template_inventory_workflow.mjs'), 'utf8');
const checksumTemplate = readFileSync(
path.join(templateDir, "template_checksum_workflow.mjs"),
"utf8",
);
const inventoryTemplate = readFileSync(
path.join(templateDir, "template_inventory_workflow.mjs"),
"utf8",
);
const expectedSnippets = [
'template_checksum_workflow',
'Checksum validation workflow',
'template_inventory_workflow',
'Inventory reconciliation workflow'
"template_checksum_workflow",
"Checksum validation workflow",
"template_inventory_workflow",
"Inventory reconciliation workflow",
];
test('checksum workflow template contains expected metadata', () => {
for (const snippet of ['template_checksum_workflow', 'Checksum validation workflow']) {
test("checksum workflow template contains expected metadata", () => {
for (const snippet of [
"template_checksum_workflow",
"Checksum validation workflow",
]) {
assert.ok(checksumTemplate.includes(snippet));
}
});
test('inventory workflow template contains expected metadata', () => {
for (const snippet of ['template_inventory_workflow', 'Inventory reconciliation workflow']) {
test("inventory workflow template contains expected metadata", () => {
for (const snippet of [
"template_inventory_workflow",
"Inventory reconciliation workflow",
]) {
assert.ok(inventoryTemplate.includes(snippet));
}
});
test('workflow templates are documented in the SDK structure', () => {
const readme = readFileSync(path.resolve('17_MultiPROG_SDK/README.md'), 'utf8');
test("workflow templates are documented in the SDK structure", () => {
const readme = readFileSync(
path.resolve("17_MultiPROG_SDK/README.md"),
"utf8",
);
for (const snippet of expectedSnippets) {
assert.ok(readme.includes(snippet) || readme.includes('templates/'));
assert.ok(readme.includes(snippet) || readme.includes("templates/"));
}
});
+13 -8
View File
@@ -1,19 +1,24 @@
import { crc, CRC_MODELS, crcNamed } from "./crc_engine.mjs";
const msg = new TextEncoder().encode("123456789");
const vectors = {
crc8: 0xF4,
crc8_maxim: 0xA1,
crc16_ccitt_false: 0x29B1,
crc16_arc: 0xBB3D,
crc16_modbus: 0x4B37,
crc32: 0xCBF43926,
crc32_mpeg2: 0x0376E6E7
crc8: 0xf4,
crc8_maxim: 0xa1,
crc16_ccitt_false: 0x29b1,
crc16_arc: 0xbb3d,
crc16_modbus: 0x4b37,
crc32: 0xcbf43926,
crc32_mpeg2: 0x0376e6e7,
};
let allOk = true;
for (const [name, expected] of Object.entries(vectors)) {
const got = crcNamed(msg, name);
const ok = got === expected;
if (!ok) allOk = false;
console.log(name, got.toString(16), expected.toString(16), ok ? "OK" : "MISMATCH");
console.log(
name,
got.toString(16),
expected.toString(16),
ok ? "OK" : "MISMATCH",
);
}
console.log(allOk ? "ALL_OK" : "FAILURES");
+62 -11
View File
@@ -29,32 +29,83 @@ function reflectBits(value, bits) {
export function crc(data, model) {
requireBytes(data);
const { width, poly, init, refin, refout, xorout } = model;
if (![8, 16, 24, 32].includes(width)) throw new RangeError("width must be one of 8, 16, 24, 32");
const mask = width === 32 ? 0xFFFFFFFF : ((1 << width) - 1) >>> 0;
if (![8, 16, 24, 32].includes(width))
throw new RangeError("width must be one of 8, 16, 24, 32");
const mask = width === 32 ? 0xffffffff : ((1 << width) - 1) >>> 0;
const topBit = 1 << (width - 1);
let reg = init >>> 0;
for (let byte of data) {
if (refin) byte = reflectBits(byte, 8);
reg = (reg ^ (byte << (width - 8))) >>> 0;
for (let i = 0; i < 8; i++) {
const msbSet = width === 32 ? (reg & 0x80000000) !== 0 : (reg & topBit) !== 0;
const msbSet =
width === 32 ? (reg & 0x80000000) !== 0 : (reg & topBit) !== 0;
reg = msbSet ? ((reg << 1) ^ poly) >>> 0 : (reg << 1) >>> 0;
}
reg &= mask;
}
if (refout) reg = reflectBits(reg, width);
return (((reg ^ xorout) & mask) >>> 0);
return ((reg ^ xorout) & mask) >>> 0;
}
/** Well-known named CRC models, ready to pass to {@link crc}. */
export const CRC_MODELS = {
crc8: { width: 8, poly: 0x07, init: 0x00, refin: false, refout: false, xorout: 0x00 },
crc8_maxim: { width: 8, poly: 0x31, init: 0x00, refin: true, refout: true, xorout: 0x00 },
crc16_ccitt_false: { width: 16, poly: 0x1021, init: 0xFFFF, refin: false, refout: false, xorout: 0x0000 },
crc16_arc: { width: 16, poly: 0x8005, init: 0x0000, refin: true, refout: true, xorout: 0x0000 },
crc16_modbus: { width: 16, poly: 0x8005, init: 0xFFFF, refin: true, refout: true, xorout: 0x0000 },
crc32: { width: 32, poly: 0x04C11DB7, init: 0xFFFFFFFF, refin: true, refout: true, xorout: 0xFFFFFFFF },
crc32_mpeg2: { width: 32, poly: 0x04C11DB7, init: 0xFFFFFFFF, refin: false, refout: false, xorout: 0x00000000 }
crc8: {
width: 8,
poly: 0x07,
init: 0x00,
refin: false,
refout: false,
xorout: 0x00,
},
crc8_maxim: {
width: 8,
poly: 0x31,
init: 0x00,
refin: true,
refout: true,
xorout: 0x00,
},
crc16_ccitt_false: {
width: 16,
poly: 0x1021,
init: 0xffff,
refin: false,
refout: false,
xorout: 0x0000,
},
crc16_arc: {
width: 16,
poly: 0x8005,
init: 0x0000,
refin: true,
refout: true,
xorout: 0x0000,
},
crc16_modbus: {
width: 16,
poly: 0x8005,
init: 0xffff,
refin: true,
refout: true,
xorout: 0x0000,
},
crc32: {
width: 32,
poly: 0x04c11db7,
init: 0xffffffff,
refin: true,
refout: true,
xorout: 0xffffffff,
},
crc32_mpeg2: {
width: 32,
poly: 0x04c11db7,
init: 0xffffffff,
refin: false,
refout: false,
xorout: 0x00000000,
},
};
/** Convenience helper: compute a named model's CRC. */
+222 -202
View File
@@ -51,113 +51,113 @@
},
{
"path": "03_Script_Starter_Kit/binary_utils.mjs",
"size": 4671,
"sha256": "ba08f2ac18998e6854d85786de5a9bc0e9c7ef9e8ad7924663d34c9487b0c341"
"size": 5748,
"sha256": "002cb1b91dbeb6882210ca9d5b06e044aa315f3b623920f4ba0d47d62c18ff63"
},
{
"path": "03_Script_Starter_Kit/examples/01_read_only_inspector.mjs",
"size": 306,
"sha256": "18156a9cc515306ea14a38b4ecc20401d6a8c2511542ea08df803d1e38132a0e"
"size": 358,
"sha256": "29b332da18c61ec65b8ca5d5db15f6aba34b16c0af7aef1b06d96a59c0a5750f"
},
{
"path": "03_Script_Starter_Kit/examples/02_double_read_verifier.mjs",
"size": 203,
"sha256": "526487814bb04cb955a9ac666d3b1e77390c4a8462f2422488d905c2334c12b6"
"size": 240,
"sha256": "099b6acdc56c1403967c8f8def819f0e59cb1ae7e472e7012cf466b355369973"
},
{
"path": "03_Script_Starter_Kit/examples/03_pattern_inventory.mjs",
"size": 192,
"sha256": "4e1d5e1ecd3f8f284be57c173ca481e7c55d6a719c3d72214d656aafde20735a"
"size": 219,
"sha256": "c81df6685529de1c9946c39f29893911fee8c59996527ae4c53bdbbf9d54760a"
},
{
"path": "03_Script_Starter_Kit/examples/04_allowlisted_patch_demo.mjs",
"size": 345,
"sha256": "fac79790cac46e6e9299a618769e355a9153fd63ce145664ced729517dd37943"
"size": 381,
"sha256": "a059293e1010f2b57801d02b7b0e97559adacafb68acd5210687bdee4dafccdf"
},
{
"path": "03_Script_Starter_Kit/examples/05_checksum_test_vectors.mjs",
"size": 249,
"sha256": "95dd4b2f4001c7f4f645fd972061805139cf5b41ed528f3013e4cdf9f30cb01d"
"size": 297,
"sha256": "2227f3999d736cb2ffdd8347bef39d00061790cbce4a4d9aeb7e7d807527aeda"
},
{
"path": "03_Script_Starter_Kit/examples/06_authorized_job_record_validator.mjs",
"size": 387,
"sha256": "28581b002a52398f2e14752490c0a3165c48b055bb3ed4505e6a4ca03f074942"
"size": 456,
"sha256": "ed082376bff56fd54b3e6589c18f2b50b077620b989a5bf4417ae4771035c0c3"
},
{
"path": "03_Script_Starter_Kit/examples/07_key_inventory_audit.mjs",
"size": 351,
"sha256": "e86e045e48f7cb2d5d8f760e745b5f5434dd8e30b20be34f511e03b487d92d96"
"size": 415,
"sha256": "e03f7c0372a77e83cdb8b4be26c1ca7c2305f99bb921fc583f5741c5a477c6e8"
},
{
"path": "03_Script_Starter_Kit/examples/08_redacted_security_report.mjs",
"size": 381,
"sha256": "24882fd08420d906c6c87e6d8cf6fa30e3351d986787d7a6c3c2611d8f471399"
"size": 461,
"sha256": "3774504b4c55f60d73989c06531ff594a1a352fd703d3956a7b515e1c871d5c0"
},
{
"path": "03_Script_Starter_Kit/examples/32_dump_fingerprint.mjs",
"size": 483,
"sha256": "c6fa0fa4c3c2e3be275ef99ffb2cf4a972f205c5d79f76748d54794419f84ae0"
"size": 471,
"sha256": "14c6c16bd9b156f950e3147d7f7d415d7d5b7196acb6932e78659bc5fa703a62"
},
{
"path": "03_Script_Starter_Kit/examples/33_structured_change_report.mjs",
"size": 823,
"sha256": "06c13810f940911ecc840d65cd254e1b16ff698e79d06f7e8b54d3f56b710de8"
"size": 850,
"sha256": "b265866a04a4c8956f7c0e954bae245f5614a322e6295ebfc60cca8bac910ecf"
},
{
"path": "03_Script_Starter_Kit/examples/34_checksum_model_selector.mjs",
"size": 983,
"sha256": "fdb47c13d34a6182ff4dfbfdb804556cd57945370871c292ca0f603f4cb5d4ae"
"size": 966,
"sha256": "28a26a9f0175db2622817832566e0e93f04356aa93aec1ff7c49cea03b9e1b85"
},
{
"path": "03_Script_Starter_Kit/examples/35_flash_sector_map_parser.mjs",
"size": 1545,
"sha256": "c2c05312356d67f1f0b106b3a4f1a88fa37f00750c8be4c24b2f67c89e4eccb2"
"size": 1555,
"sha256": "30e6c08bf52d8f5126550d0a81ec3c96b2955f6162c99bafdfcdc2e1c43e9e7b"
},
{
"path": "03_Script_Starter_Kit/examples/36_vin_extractor_validator.mjs",
"size": 1970,
"sha256": "87b497606501561c03df7096aed7d0052d6c881dda705282e9bdfe2bf79183fd"
"size": 1989,
"sha256": "0c804c7f143ad5e9ac6183c0530ea4623cb36a597121026df019a0962746c5c5"
},
{
"path": "03_Script_Starter_Kit/examples/37_binary_search_pattern_locator.mjs",
"size": 1232,
"sha256": "bd3768307edabd9356498064d0595b64765d4e53b78a238ad0c738b3e30f9f99"
"size": 1220,
"sha256": "6e2c2c6ad5513d47cbe3f3ff18472fd7e24a4337493b1e210c7c92b0776798fa"
},
{
"path": "03_Script_Starter_Kit/examples/38_bitfield_and_signed_int_utils.mjs",
"size": 1901,
"sha256": "7c0333bc36781c7d9d2d3cda69307dbd7fd0f8a8513181ccaca352e8e759af0d"
"size": 1951,
"sha256": "784ec2a6e02d63146a071aabb7260b1b829822cb300beffc1c50769a373318b5"
},
{
"path": "03_Script_Starter_Kit/examples/39_key_learning_session_report.mjs",
"size": 1905,
"sha256": "8075711e347cd30b992ac856ac082b44a188d420d8a7de1d5530c659bb6aee32"
"size": 1954,
"sha256": "3f986a7945696b32cbf0117e2689b02ed19272f510ef61ac13d77fc309d4cc45"
},
{
"path": "03_Script_Starter_Kit/examples/airbag_authorized_safe/17_airbag_case_intake_validator.mjs",
"size": 758,
"sha256": "78c0557df83822e3a731079935a2824bc48a446a5cd0d417fa801cb0ac13267f"
"size": 774,
"sha256": "d7a8110cb32ba7765d2dd8cac769b3675b21126e1faafc2bf9a19e19925db75e"
},
{
"path": "03_Script_Starter_Kit/examples/airbag_authorized_safe/18_airbag_module_read_consistency.mjs",
"size": 397,
"sha256": "b9679d96d432dbc7a3ebe1234222d89e9d8bd62e74a731260feec4435b48cf2c"
"size": 387,
"sha256": "f20721742dae16dd87190be85346a9b973ce1cdc5b1b179359120b5c65ca729b"
},
{
"path": "03_Script_Starter_Kit/examples/airbag_authorized_safe/19_airbag_crash_data_disclosure_redactor.mjs",
"size": 775,
"sha256": "6c308586475129d12479b7a86100fe6572d31e67a6e40ae9034e6e5e91a85e4c"
"size": 757,
"sha256": "3b0f0f9a066f04f1bc3c8e039333bfa2dcbf0b159db7508086cd0d8ac5357f5e"
},
{
"path": "03_Script_Starter_Kit/examples/airbag_authorized_safe/20_airbag_backup_naming_policy.mjs",
"size": 487,
"sha256": "cd91e20f4ea0af4058f1e471d9538d585ea8ef1d64378bd2f4746aaa122e0fb3"
"size": 531,
"sha256": "57c4d42136f9967d99d9aa013000d82184ec1074e867d5de33b1ac9dd80e6929"
},
{
"path": "03_Script_Starter_Kit/examples/airbag_authorized_safe/21_airbag_service_completion_audit.mjs",
"size": 644,
"sha256": "bcef13df3f2ab4ecc14ffd3b36195d321742d77867521c23fa09fa2214c0a6f6"
"size": 632,
"sha256": "6415ec3dccba1b173c7c401a72fcf4546e62815a47f7591d3d891dfb9be91150"
},
{
"path": "03_Script_Starter_Kit/examples/airbag_authorized_safe/README.md",
@@ -166,28 +166,28 @@
},
{
"path": "03_Script_Starter_Kit/examples/ecu_authorized_safe/22_ecu_case_intake_validator.mjs",
"size": 695,
"sha256": "daeb1e4c4f288fcf38dd81df7ae3cd5aecb6896fe83e9cabdba6962b20b511bf"
"size": 712,
"sha256": "84f93b17985eb9a6b2970b7bf5d6ed0c23ce8a5871c0e006ab97f061311be38b"
},
{
"path": "03_Script_Starter_Kit/examples/ecu_authorized_safe/23_ecu_calibration_read_consistency.mjs",
"size": 404,
"sha256": "f765c2f93a702884f5dc17afa7d9a21767987cbe8966103ad2c6faea4e5c0536"
"size": 394,
"sha256": "365991287a980790b955f58e9bce3426bd5e98a919616f60c1b2a5cfd26d16e0"
},
{
"path": "03_Script_Starter_Kit/examples/ecu_authorized_safe/24_ecu_partnumber_crosscheck.mjs",
"size": 558,
"sha256": "6d9740863777954e79750ac0193345a97447339dd5cd809507d7f382c863863c"
"size": 546,
"sha256": "ac50cf432a8451369252883b0a80f3f7b94aad14631e4e633ee783fe4c243f8c"
},
{
"path": "03_Script_Starter_Kit/examples/ecu_authorized_safe/25_ecu_change_scope_guard.mjs",
"size": 713,
"sha256": "4f14ccc0d5ced13bfd1f82553abc7d31172f3bdc560b69fb4117c0ddb3dd4cad"
"size": 718,
"sha256": "f58e42c4c799321deed0a15d1f836f14361cc05a64359ec9d0ee128efd32448d"
},
{
"path": "03_Script_Starter_Kit/examples/ecu_authorized_safe/26_ecu_service_completion_audit.mjs",
"size": 641,
"sha256": "273a901c6d56996e2581198a6ab165147601acbf7a93b893673a96839682e0f0"
"size": 629,
"sha256": "1bae9c170207e9a848a7245c5284e3c789a7bc190e063a5e5bc9f8682e5b7e2d"
},
{
"path": "03_Script_Starter_Kit/examples/ecu_authorized_safe/README.md",
@@ -196,23 +196,23 @@
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/09_immo_case_intake_validator.mjs",
"size": 766,
"sha256": "db363224eebfdbb1cb27bca692db9d25364a6ee9077770384b954a411332f605"
"size": 801,
"sha256": "01d015ccdcf1b4edda8952055e55d6a0904325624b357731659205eafd0e8195"
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/10_immo_module_read_consistency.mjs",
"size": 734,
"sha256": "166915eb000e26031ba34607a371d182124780029010249dd1ba95b6db3a3d93"
"size": 764,
"sha256": "8f8f0aaec12d9345da64f42fe10e1b799a5b378a44573e92a5d8d2bed103b52d"
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/11_immo_dump_quality_report.mjs",
"size": 790,
"sha256": "b8d93fcd3208ca38c4376173d8d5d850d244b8cc572cf2e552fbba1f2749ad90"
"size": 817,
"sha256": "7a8faa47bc7da683005f48296577d4f57ed22f732e6b1175ed74ee2266f2f1bc"
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/12_authorized_key_count_reconciliation.mjs",
"size": 737,
"sha256": "fbc6e47c850f4a80f4c6390a8c9c0207ebfe785f93c897de7f6b40f4728faa06"
"size": 774,
"sha256": "21dfc385ba67092f158b949c754303336fef840ea03cb3ab3197d6ad2eaf0e9b"
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/13_security_value_redactor.mjs",
@@ -221,18 +221,18 @@
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/14_immo_backup_naming_policy.mjs",
"size": 451,
"sha256": "eb73cc5cae0fb5b0ef9e6a8d569958f4a9504506b6b93b1511e6698f45220439"
"size": 507,
"sha256": "b30ffe482b1b5d0a81488032f976a564913f6a325b8f69c1b978deb3c6949da1"
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/15_immo_change_scope_guard.mjs",
"size": 683,
"sha256": "9700dd36c27863e795665d3b4e615832f958feaf117f201b6f157065817089b3"
"size": 706,
"sha256": "b116ceb7fcabf79c3c24f76bd7e9b091585ccc933e6aace6818a27809f00a020"
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/16_immo_service_completion_audit.mjs",
"size": 694,
"sha256": "dd29d920ff57fb8d5696f72361f083b0c4e851f245f062bf9e001388fd9af9a4"
"size": 695,
"sha256": "db9dd131a0e8f899132b639f1d06dc2b3eea2bda080de2462a0747c57a8c8a80"
},
{
"path": "03_Script_Starter_Kit/examples/immobilizer_authorized_safe/README.md",
@@ -241,28 +241,28 @@
},
{
"path": "03_Script_Starter_Kit/examples/other_modules_authorized_safe/27_module_case_intake_validator.mjs",
"size": 744,
"sha256": "18d9c2c567a9da532fd05f888fc2953af58c03ad1de9dc9303e689c445fe00eb"
"size": 763,
"sha256": "8fab0cbc95487a57453e1fa54337a260615858c83d2a9db8eb20e20bd3a9502e"
},
{
"path": "03_Script_Starter_Kit/examples/other_modules_authorized_safe/28_module_read_consistency.mjs",
"size": 397,
"sha256": "394afc2e58884869a7c0376b8256716cbce70d1dc011aa20b1ab0ee521685416"
"size": 387,
"sha256": "85bb7d89ca284e431a4df7e92e20b7dfd1e2f5d02579edf19e4866d7f84a53d0"
},
{
"path": "03_Script_Starter_Kit/examples/other_modules_authorized_safe/29_module_backup_naming_policy.mjs",
"size": 476,
"sha256": "c38ba3e4e11f0d758e4ac996ccd6434794225a2ead2ba5f643809999eed03db2"
"size": 520,
"sha256": "7e84684f7bb172fef867789f7913e11946af81045f44894680201267f8133fd6"
},
{
"path": "03_Script_Starter_Kit/examples/other_modules_authorized_safe/30_module_change_scope_guard.mjs",
"size": 863,
"sha256": "8a4e421e9d2aee8a444a49d58112fc4c8ad5a1d8e4998b3076dbcd24cdfdd684"
"size": 866,
"sha256": "487c4f8897b4a34f9c3f9d6112be82be9d96dec76bbb085651622dab88a34b4f"
},
{
"path": "03_Script_Starter_Kit/examples/other_modules_authorized_safe/31_module_service_completion_audit.mjs",
"size": 650,
"sha256": "8e7078215db0db5ed4aad25443c2534f9603a31ec95a66dee449e78829351982"
"size": 638,
"sha256": "e5abd3d67dcd193abe9ba973477e9e81deecf749a01bcac846c4e8c213251610"
},
{
"path": "03_Script_Starter_Kit/examples/other_modules_authorized_safe/README.md",
@@ -272,7 +272,7 @@
{
"path": "03_Script_Starter_Kit/templates/checksum_region_verifier.mjs",
"size": 1383,
"sha256": "5f4567383be8569e2e57f18c2eef10798b69ff2416e8a9f95797335a8d09a18f"
"sha256": "df20899f32d0521b44d4498e07858e2e2d223082e097db67a4523bd759c224e1"
},
{
"path": "03_Script_Starter_Kit/templates/multiprog_safe_template.mjs",
@@ -281,18 +281,18 @@
},
{
"path": "03_Script_Starter_Kit/tests/test_additional_examples.mjs",
"size": 1881,
"sha256": "5f59f2bdb63c7fa0e7859e6ab4fa12a3d24c3277b240220b668ec2fefb0a6261"
"size": 1976,
"sha256": "ff8ccf7e22ee9aeab25f8b2efb5ac9d5df18bb7c7100420b9bf2cc01878e690d"
},
{
"path": "03_Script_Starter_Kit/tests/test_binary_utils.mjs",
"size": 407,
"sha256": "f067c3b4f7d6782808e738d4b4eacf14b0321b82571b67994f618452d0d6e0aa"
"size": 435,
"sha256": "d2081786819f0fbde6e9f91cb09e72fade6894a7bd3882922ce4739667536ef0"
},
{
"path": "03_Script_Starter_Kit/tests/test_expansion_examples.mjs",
"size": 4082,
"sha256": "72731f254473b99ac5a8c2bcb0354c993b3f3fa215a4ea3b685a1c68bcc23245"
"size": 4090,
"sha256": "50400058d90833fce8994e7b1582ced9b4fb038af12251abb5b6412025f43ea8"
},
{
"path": "04_Workflows/AUTHORIZED_KEY_LEARNING_WORKFLOW.md",
@@ -464,6 +464,26 @@
"size": 2326,
"sha256": "b1f305f2ec3e985c910afd0ae6e978865212c1625393cfe6780597cfd40d8fca"
},
{
"path": "08_Collected_MultiPROG_Scripts/Archived/README.md",
"size": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "08_Collected_MultiPROG_Scripts/Community/README.md",
"size": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "08_Collected_MultiPROG_Scripts/Examples/README.md",
"size": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "08_Collected_MultiPROG_Scripts/GitHub/binary-parser/README.md",
"size": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "08_Collected_MultiPROG_Scripts/GitHub/CarKeyGuyNL_Multi-Prog-Scripts/EWS_Code_generator.mjs",
"size": 5866,
@@ -511,63 +531,63 @@
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/01_read_only_inspector.mjs",
"size": 306,
"sha256": "18156a9cc515306ea14a38b4ecc20401d6a8c2511542ea08df803d1e38132a0e"
"size": 358,
"sha256": "29b332da18c61ec65b8ca5d5db15f6aba34b16c0af7aef1b06d96a59c0a5750f"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/02_double_read_verifier.mjs",
"size": 203,
"sha256": "526487814bb04cb955a9ac666d3b1e77390c4a8462f2422488d905c2334c12b6"
"size": 240,
"sha256": "099b6acdc56c1403967c8f8def819f0e59cb1ae7e472e7012cf466b355369973"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/03_pattern_inventory.mjs",
"size": 192,
"sha256": "4e1d5e1ecd3f8f284be57c173ca481e7c55d6a719c3d72214d656aafde20735a"
"size": 219,
"sha256": "c81df6685529de1c9946c39f29893911fee8c59996527ae4c53bdbbf9d54760a"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/04_allowlisted_patch_demo.mjs",
"size": 345,
"sha256": "fac79790cac46e6e9299a618769e355a9153fd63ce145664ced729517dd37943"
"size": 381,
"sha256": "a059293e1010f2b57801d02b7b0e97559adacafb68acd5210687bdee4dafccdf"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/05_checksum_test_vectors.mjs",
"size": 249,
"sha256": "95dd4b2f4001c7f4f645fd972061805139cf5b41ed528f3013e4cdf9f30cb01d"
"size": 297,
"sha256": "2227f3999d736cb2ffdd8347bef39d00061790cbce4a4d9aeb7e7d807527aeda"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/06_authorized_job_record_validator.mjs",
"size": 387,
"sha256": "28581b002a52398f2e14752490c0a3165c48b055bb3ed4505e6a4ca03f074942"
"size": 456,
"sha256": "ed082376bff56fd54b3e6589c18f2b50b077620b989a5bf4417ae4771035c0c3"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/07_key_inventory_audit.mjs",
"size": 351,
"sha256": "e86e045e48f7cb2d5d8f760e745b5f5434dd8e30b20be34f511e03b487d92d96"
"size": 415,
"sha256": "e03f7c0372a77e83cdb8b4be26c1ca7c2305f99bb921fc583f5741c5a477c6e8"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/08_redacted_security_report.mjs",
"size": 381,
"sha256": "24882fd08420d906c6c87e6d8cf6fa30e3351d986787d7a6c3c2611d8f471399"
"size": 461,
"sha256": "3774504b4c55f60d73989c06531ff594a1a352fd703d3956a7b515e1c871d5c0"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/09_immo_case_intake_validator.mjs",
"size": 766,
"sha256": "db363224eebfdbb1cb27bca692db9d25364a6ee9077770384b954a411332f605"
"size": 801,
"sha256": "01d015ccdcf1b4edda8952055e55d6a0904325624b357731659205eafd0e8195"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/10_immo_module_read_consistency.mjs",
"size": 734,
"sha256": "166915eb000e26031ba34607a371d182124780029010249dd1ba95b6db3a3d93"
"size": 764,
"sha256": "8f8f0aaec12d9345da64f42fe10e1b799a5b378a44573e92a5d8d2bed103b52d"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/11_immo_dump_quality_report.mjs",
"size": 790,
"sha256": "b8d93fcd3208ca38c4376173d8d5d850d244b8cc572cf2e552fbba1f2749ad90"
"size": 817,
"sha256": "7a8faa47bc7da683005f48296577d4f57ed22f732e6b1175ed74ee2266f2f1bc"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/12_authorized_key_count_reconciliation.mjs",
"size": 737,
"sha256": "fbc6e47c850f4a80f4c6390a8c9c0207ebfe785f93c897de7f6b40f4728faa06"
"size": 774,
"sha256": "21dfc385ba67092f158b949c754303336fef840ea03cb3ab3197d6ad2eaf0e9b"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/13_security_value_redactor.mjs",
@@ -576,23 +596,23 @@
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/14_immo_backup_naming_policy.mjs",
"size": 451,
"sha256": "eb73cc5cae0fb5b0ef9e6a8d569958f4a9504506b6b93b1511e6698f45220439"
"size": 507,
"sha256": "b30ffe482b1b5d0a81488032f976a564913f6a325b8f69c1b978deb3c6949da1"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/15_immo_change_scope_guard.mjs",
"size": 683,
"sha256": "9700dd36c27863e795665d3b4e615832f958feaf117f201b6f157065817089b3"
"size": 706,
"sha256": "b116ceb7fcabf79c3c24f76bd7e9b091585ccc933e6aace6818a27809f00a020"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/16_immo_service_completion_audit.mjs",
"size": 694,
"sha256": "dd29d920ff57fb8d5696f72361f083b0c4e851f245f062bf9e001388fd9af9a4"
"size": 695,
"sha256": "db9dd131a0e8f899132b639f1d06dc2b3eea2bda080de2462a0747c57a8c8a80"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/binary_utils.mjs",
"size": 4567,
"sha256": "b5645c1560828f97acfa0578e214754cb21f233f514dea5f133529346654fdd0"
"size": 5637,
"sha256": "ae4947054a93e6dbdd3da6f1db05703b6541ddd5546c87f1f737df76adf95f6a"
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/multiprog_safe_template.mjs",
@@ -601,8 +621,8 @@
},
{
"path": "08_Collected_MultiPROG_Scripts/Original/test_binary_utils.mjs",
"size": 406,
"sha256": "b424eaba7c78efcde0458bafa4a74a1424e1ccfd52823d9f55d328b342d35b59"
"size": 434,
"sha256": "66bab858b5d530e754bffd59c80ece8aa5dc37b38f0b532e71f9cff9adbf7a84"
},
{
"path": "08_Collected_MultiPROG_Scripts/README.md",
@@ -621,88 +641,88 @@
},
{
"path": "09_Collected_JS/administrative/script_catalog.mjs",
"size": 1728,
"sha256": "f11a93c24b031ecfc367bc8fe88c23d023e87cf23a21ecab3297d2c63a7c11ae"
"size": 1738,
"sha256": "f81651ce744ee1634d5024879b847841d2c52634dba6a97b3649437a0e672756"
},
{
"path": "09_Collected_JS/binary/dataview_helpers.mjs",
"size": 1468,
"sha256": "04ef48405f8c9d578fedffe3cf0ae29cb2bef79ea1cf73d1537d51e5f13d4216"
"size": 1571,
"sha256": "0d504c75818513e01745d856cab09a6b76942054ae30e37f4daf11d1170bbb3f"
},
{
"path": "09_Collected_JS/binary/struct_reader.mjs",
"size": 1088,
"sha256": "470a26697b5a5d4a0be52021a1604591536f96ce0a649e49c9ff8d6f1fdf9aed"
"size": 1082,
"sha256": "626a49e4d7d929f24523c93fd30d8ff0f9b2a0364faf4c00ee2862f91fabfce2"
},
{
"path": "09_Collected_JS/checksums/adler32.mjs",
"size": 440,
"sha256": "08bba9ae975765bc288e70135c7aea4f7063b0e8c5c9149c8bcbd27104c02e03"
"size": 427,
"sha256": "c07cdbbc4184cc03dec011b0054f9cf59ee4bd22c92be9f71e31f0a71e24952d"
},
{
"path": "09_Collected_JS/checksums/crc16.mjs",
"size": 903,
"sha256": "27b87f99de40ce6481f8b02dd6532b65e5b68b079e35c85dea4f80b7f8e7eef2"
"size": 869,
"sha256": "5048d939ab64760d4b8771b46e58c33cc9bf38cff305e02f0747c474ce8b5f95"
},
{
"path": "09_Collected_JS/checksums/crc32.mjs",
"size": 1283,
"sha256": "8f75655eb06d3a533f7458b1778bf36937a39122d47990877b8d7a2acc8584d4"
"size": 1255,
"sha256": "6d516a055bbe91cbf187357e020bd108608f19195f4fac5f1021d6cda7b7832c"
},
{
"path": "09_Collected_JS/checksums/crc64.mjs",
"size": 767,
"sha256": "a9a9e4d7ee26a7e69c7ca5e9013d6db2a9721341e2ddf48ac7d22d455a4e6b50"
"size": 744,
"sha256": "f57edf320db079e3289aa39cbc79a86d42016e393feda27e1816c3f77dbf1d76"
},
{
"path": "09_Collected_JS/checksums/crc8.mjs",
"size": 525,
"sha256": "7cbbb3e9df49e12732bac2d866acd89863122fa8ccc12bac37715c68af7e0dac"
"size": 506,
"sha256": "fc4318146c8b2ffedf5e5ebe00ae20bed04a66785c61e59a83ac14ce0e2fd5ef"
},
{
"path": "09_Collected_JS/checksums/fletcher.mjs",
"size": 875,
"sha256": "096557f7eb0746b84de31e8d392c5e6ababd0fa8648a868830272993928e6ab3"
"size": 858,
"sha256": "e296b7237c81798ff487b263d6d4a84505b4b5f93fb3441e4b1b5f2ac801cd8b"
},
{
"path": "09_Collected_JS/encoding/ascii.mjs",
"size": 660,
"sha256": "789cff1a8daef369530137d79dc016a3d1bcb196ff60d3d5c275302db6c5eacc"
"size": 646,
"sha256": "d935384bc42a4cc15acfa0a8dfcfa130d18962c21786c52eafbe874bbb42ebbf"
},
{
"path": "09_Collected_JS/encoding/bcd.mjs",
"size": 811,
"sha256": "645205a14510dd1c67b0ca4061de9fe1b409e64d178f1ea1fab633ab0ca91cf3"
"size": 796,
"sha256": "09db57f972c44d2ae4016a526b13a1940d5b3571f42cef0e0124e8f8aea9fec9"
},
{
"path": "09_Collected_JS/encoding/hex.mjs",
"size": 629,
"sha256": "fca861f1e3103bfbe4c0b5d8a7de89b504a559fec5224239086f82da67b6aba1"
"size": 628,
"sha256": "bf4a6d08674f60e7408110118730a65c9a11282c72d9cd263d21d9be1fe03f1a"
},
{
"path": "09_Collected_JS/encoding/utf8.mjs",
"size": 378,
"sha256": "c24e24dff1c08e6b689d32ec2de034a35874a7a2a121521f3496e6144de687c8"
"size": 369,
"sha256": "29e453b146d708d369a167cc7c42423f18bbeefb7ce6bc21791595113b15de8f"
},
{
"path": "09_Collected_JS/hashing/_pad.mjs",
"size": 576,
"sha256": "5fb75f11b31806ca10942af87820dda98f71cb6d6eeac2e2b79a9a20e065aeac"
"size": 562,
"sha256": "a74fae5efc879d1db8ef1e6676fef854a9b6379765a60b644c3ee260a7135c9b"
},
{
"path": "09_Collected_JS/hashing/md5.mjs",
"size": 2180,
"sha256": "d16657cf6ee9765fd1b7ef0be4009551c57afe40ab279f6675ec5a91398be899"
"size": 2246,
"sha256": "a228f3f98fd0fae02d1f2171536b92aa4a274e595a138530b995510fb01a1188"
},
{
"path": "09_Collected_JS/hashing/sha1.mjs",
"size": 1539,
"sha256": "81030b7e4ed8e5e87a88edaa61983496457a4d120e3d870d6cf909e0d0a40254"
"size": 1665,
"sha256": "5946d9d94782e9a7309397f6149b5cbbfdc7d74f07a0da0a702a6096cbe4d7b9"
},
{
"path": "09_Collected_JS/hashing/sha256.mjs",
"size": 2676,
"sha256": "2fab620737487841ac3dc44f1b39677e11374d58209b1a272f763c235118507e"
"size": 2693,
"sha256": "91d12d2837609fc38b8cb20365222d8f1c4ad958a7cf543645c12b49905f05ae"
},
{
"path": "09_Collected_JS/README.md",
@@ -711,13 +731,13 @@
},
{
"path": "09_Collected_JS/tests/test_admin_utils.mjs",
"size": 962,
"sha256": "18db775821614eea74793fbf3d187c5e8a268d3279347571dc4a44125664f336"
"size": 955,
"sha256": "13a6c83047837a47e4475b80b32b1daea3e4e344aa9dc97f920cfc5fec43b3ab"
},
{
"path": "09_Collected_JS/tests/test_vectors.mjs",
"size": 1522,
"sha256": "81a955abb172697066c73fcbf64cfaf68cc792d71295561b548492898bf5bcc8"
"size": 1500,
"sha256": "3827815a8c48bdb0fc78476511b271733587cec8008b5662696fa73fa4fc031b"
},
{
"path": "10_Source_Repositories/README.md",
@@ -951,8 +971,8 @@
},
{
"path": "16_Tests/MultiPROG/test_buffer_utils.mjs",
"size": 441,
"sha256": "531d025c253c6e8bbc7d3c5899cc95ece73e628b2076f4d9cd92cb4391592a27"
"size": 438,
"sha256": "6b87a381fac35bd9097941b832c81ac1dee16bed041a14446805fdab8ed06b47"
},
{
"path": "16_Tests/MultiPROG/TEST_GUIDE.md",
@@ -1011,8 +1031,8 @@
},
{
"path": "17_MultiPROG_SDK/examples/example_readonly_report.mjs",
"size": 568,
"sha256": "9531f8ca37ed566137e008a5e0b73cdc281d51e712e5947bd59dbb28bfed395e"
"size": 555,
"sha256": "2774c0d4405c1f0bb5ddccda253381281ae5cde59d8df1792ccce344411005d4"
},
{
"path": "17_MultiPROG_SDK/package.json",
@@ -1066,103 +1086,103 @@
},
{
"path": "17_MultiPROG_SDK/templates/template_binary_tool.mjs",
"size": 713,
"sha256": "44ad6a153d1d1d3832616a698ed2ac48da7dbee0e3ee20b12f318a23ff72b13f"
"size": 705,
"sha256": "de577c5d09e17f60258111fb3f34b99813f2dcad2eca2101df1f7cc6d6c7d638"
},
{
"path": "17_MultiPROG_SDK/templates/template_byteswap_workflow.mjs",
"size": 860,
"sha256": "0408a174c7c23600260e17b5e8dba9e3eef2987054eb19e30af33aa4a86386ae"
"size": 836,
"sha256": "4ebf34ec8c3563d1df4bb6d77f64c14f6fbdc757d6749cc47c8de30d3199c07a"
},
{
"path": "17_MultiPROG_SDK/templates/template_checksum_workflow.mjs",
"size": 798,
"sha256": "5e6a5a3093780c067a8b3efd394b717922c5601b5bb38c311ea9482a0d8a3b23"
"size": 776,
"sha256": "ce7da9fb532f0fafc3a3cb359c9937b2d5e471f231438075da6bbb7aa142605a"
},
{
"path": "17_MultiPROG_SDK/templates/template_compare_workflow.mjs",
"size": 1103,
"sha256": "ad3af18143646dc1e5920900e352dee99aeda6ea74307686ad48f98ad193df84"
"size": 1075,
"sha256": "12c3275b4f272793a206d50b5ceb87f2f2d71199c5e870066bab8e21326731d9"
},
{
"path": "17_MultiPROG_SDK/templates/template_diff_report.mjs",
"size": 1036,
"sha256": "cbede11acd30fe0b7f74feef44a31ecfec9389c3d0c1278fa517c8bc5694f044"
"size": 1016,
"sha256": "97f2991ad52ce345acf6cfe5c232450f2c1071ce6e7235d2b7de5aabc47f78ff"
},
{
"path": "17_MultiPROG_SDK/templates/template_eeprom.mjs",
"size": 880,
"sha256": "e6912b099c9ecbe650ddacb32787e322e7a98d313d2de428bf3cbd26b46157ff"
"size": 855,
"sha256": "2966f2cf7ae8af2e67f88521952abbe51d5a0d4de26ffa6ac193112f376233da"
},
{
"path": "17_MultiPROG_SDK/templates/template_flash.mjs",
"size": 791,
"sha256": "72d321eb39bce162bc9578a2cde0208b243170260e55741c8663ade0d8ca92df"
"size": 770,
"sha256": "2fc231b219cf07de620df705adde523687dfef10e9166de51714708b2faf2e6d"
},
{
"path": "17_MultiPROG_SDK/templates/template_inventory_workflow.mjs",
"size": 809,
"sha256": "734be92c2b3bb30316a45c597020b48f1fa613a74d493b37e6e83bc8614a6804"
"size": 784,
"sha256": "ea5ed02ccdbca44abffbfdad75010422dd489fae971ea950dbabd66ce64d659a"
},
{
"path": "17_MultiPROG_SDK/templates/template_merge_workflow.mjs",
"size": 938,
"sha256": "7d7732562ccd1721dd0ed35db216955737001c69b334c1f5b25eac82bb4432dc"
"size": 926,
"sha256": "a0e3e9475c58ecdf843f376902a41fe6b8867d4aa1b95dc6ee8335e5b0e66d28"
},
{
"path": "17_MultiPROG_SDK/templates/template_patch_guard.mjs",
"size": 1065,
"sha256": "32c62ce6a6cc2b80b8cb39b7f22bdb7835d0e3777d49bfbe174c37eaa4d983af"
"size": 1072,
"sha256": "65571fd9a4f1b6a1d7bdd40ff9c12ca3f66a366aff5a638134357208b844c671"
},
{
"path": "17_MultiPROG_SDK/templates/template_readonly.mjs",
"size": 985,
"sha256": "8586522c73c807ee55f05bd4609b5ecd69f19e37263fd379d313227376ff2e59"
"sha256": "2be6e8e0f111e95af1c5ee59c5dca7bd9bc02d24d9a811ef9d13c4a78de72026"
},
{
"path": "17_MultiPROG_SDK/templates/template_reporting.mjs",
"size": 771,
"sha256": "21d4219f2f03b86a034e8cf4869b6ad39f889b025b5fbb150e95caa2246fee1e"
"size": 765,
"sha256": "3035bcec7de67f023c8139bfe773eeccf0cb253896c0f03c44248a8f813d0915"
},
{
"path": "17_MultiPROG_SDK/templates/template_validator.mjs",
"size": 843,
"sha256": "1b3d5d225ad3e2393b1b8345465bc3ccfd972d410b337382b8748a9739c09773"
"size": 839,
"sha256": "066789a687a8eb55357ac7b0a32303ac3c104ba5f4c89f910fe761dabb26b66e"
},
{
"path": "17_MultiPROG_SDK/tests/buffer_helpers.test.mjs",
"size": 923,
"sha256": "4006821515db6eabc4ec7114720df1283e32843038bf0a2584b1d210b29c49fc"
"size": 918,
"sha256": "4acb0f503aa662b7d270cbc882e0012b7612a2f7621ae65d3f27046edd934301"
},
{
"path": "17_MultiPROG_SDK/tests/crc_engine.test.mjs",
"size": 1686,
"sha256": "a5e5f4379599d5bef8207c415802cfa0ed0d2ff2f1f8493480d4271d23ed1aa6"
"size": 1664,
"sha256": "602c96f31845995c9905772703fae3ae80055dfe04c931a2dad2fdeaad601053"
},
{
"path": "17_MultiPROG_SDK/tests/encoding_helpers.test.mjs",
"size": 605,
"sha256": "e8c417f64846c0cf5663ed172a313039acbcc1db6dc0fe61eca7c5808957c89c"
"size": 595,
"sha256": "5b709995a22e8c167754ce2e07e85aeff686e3045866e24076cb8e00eaf96e5c"
},
{
"path": "17_MultiPROG_SDK/tests/template_catalog.test.mjs",
"size": 1519,
"sha256": "92c72a14c4a3075f12afc0ee45ede4b35d83ae9a65d5802a9824538d80324c1d"
"size": 1556,
"sha256": "420e2f9f53fe58871de762d61fc8b3e24cc3ef38ba695cb926ebd7381e6b7899"
},
{
"path": "17_MultiPROG_SDK/tests/workflow_templates.test.mjs",
"size": 1351,
"sha256": "a2ccc4fc13d02d75d73b38134e8bc409d8a9d529c882ae4f3156b540cdfb8d0d"
"size": 1370,
"sha256": "c4755f971257e7b5d86ec0084cfa89df0f5bdb9016946ad6e0be467b90996107"
},
{
"path": "17_MultiPROG_SDK/utilities/_verify_crc.mjs",
"size": 608,
"sha256": "2c314eb8ac8a5779edf3468b343f8f2a33aae25a382e6026b43e55ca1ef1ab13"
"size": 611,
"sha256": "0c891863ca23517868c0d52cc63fc45bb85670a61cefb898e53c13a87e71b45c"
},
{
"path": "17_MultiPROG_SDK/utilities/crc_engine.mjs",
"size": 3248,
"sha256": "9b4510b713931cbdf89c60914f23a5aa496a7a65c2ceed564bbd3bbcf0a55c26"
"size": 3378,
"sha256": "beb3ad564baf0d9e804d7313432fae49cc6b410b88a1705f200dd844941cdb50"
},
{
"path": "18_Documentation/BEGINNER_GUIDE.md",
@@ -1276,8 +1296,8 @@
},
{
"path": "MANIFEST.json",
"size": 53145,
"sha256": "4d134c1f95221c71fc607c76eb333c75b6139f4f28e52af7c80720f6867d404a"
"size": 52781,
"sha256": "79274991c0597ef13296e79fb95a4d2c01c7b14f47733c57d70a8ffd904a7956"
},
{
"path": "README.md",