Formatted.
This commit is contained in:
@@ -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,
|
||||
|
||||
+16
-6
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,6 +14,6 @@ export function airbagStatusReport(job) {
|
||||
technician: job.technician,
|
||||
timestampUtc: job.timestampUtc,
|
||||
deploymentStatusAsFound: job.deploymentStatusAsFound,
|
||||
status: job.status
|
||||
status: job.status,
|
||||
};
|
||||
}
|
||||
|
||||
+14
-5
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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 };
|
||||
}
|
||||
|
||||
+16
-6
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -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 };
|
||||
}
|
||||
|
||||
+16
-6
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
+7
-2
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
+7
-4
@@ -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"
|
||||
: "",
|
||||
};
|
||||
}
|
||||
|
||||
+12
-5
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
+14
-5
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
+9
-3
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -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 };
|
||||
}
|
||||
|
||||
+17
-6
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
+14
-5
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
+9
-3
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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");
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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("");
|
||||
}
|
||||
|
||||
@@ -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("");
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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/"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user