const codeToType = { 0: Int8Array, 1: Uint8Array, 2: Int16Array, 3: Uint16Array, 4: Int32Array, 5: Uint32Array, 7: Float32Array, 8: Float64Array, 9: BigInt64Array, 10: BigUint64Array }; const typeToBytes = { Int8Array: 1, Uint8Array: 1, Int16Array: 2, Uint16Array: 2, Int32Array: 4, Uint32Array: 4, Float32Array: 4, Float64Array: 8, BigInt64Array: 8, BigUint64Array: 8 }; function sequential(binary) { if (!(binary instanceof Uint8Array) || binary.length < 4) { throw new Error('Invalid binary input'); } const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength); let offset = 0; // Initialize result (assuming single i value for simplicity; extend for multiple i values if needed) const result = { i: null, j: [], Δelems: [], elems: [] }; // Process bundles while (offset < binary.length) { // Read bundle header if (offset + 4 > binary.length) throw new Error('Incomplete bundle header'); const bundleHeader = view.getUint32(offset, true); if ((bundleHeader & 0xFF) !== 0x1C) throw new Error('Invalid bundle marker'); const bundleLength = bundleHeader >> 8; offset += 4; const bundleEnd = offset + bundleLength; if (bundleEnd > binary.length) throw new Error('Bundle length exceeds input size'); // Process chunks in bundle while (offset < bundleEnd) { // Read chunk header if (offset + 12 > bundleEnd) throw new Error('Incomplete chunk header'); const chunkType = view.getUint8(offset); if (chunkType !== 0x11) throw new Error(`Unsupported chunk type: ${chunkType}`); offset += 1; // Skip udv offset += 1; const count = view.getUint16(offset, true); offset += 2; if (count > 65535) throw new Error('Chunk count exceeds 65535'); const iValue = view.getUint16(offset, true); offset += 2; const j0 = view.getUint16(offset, true); offset += 2; const Δj = view.getInt16(offset, true); offset += 2; const ΔelemCount = view.getUint8(offset++); // Δelem_count const elemCount = view.getUint8(offset++); // elem_count // Set i value (assuming all chunks share the same i) if (result.i === null) result.i = iValue; else if (result.i !== iValue) throw new Error('Multiple i values not supported'); // Read preface (element types) const ΔelemTypes = []; for (let i = 0; i < ΔelemCount; i++) { if (offset >= bundleEnd) throw new Error('Incomplete Δelem types'); const typeByte = view.getUint8(offset++); const baseCode = typeByte & 0x0F; const incrCode = typeByte >> 4; if (!codeToType[baseCode] || !codeToType[incrCode]) { throw new Error(`Invalid type code in Δelem: ${typeByte}`); } ΔelemTypes.push({ baseType: codeToType[baseCode], incrType: codeToType[incrCode] }); } const elemTypes = []; for (let i = 0; i < elemCount; i++) { if (offset >= bundleEnd) throw new Error('Incomplete elem types'); const typeCode = view.getUint8(offset++); if (!codeToType[typeCode]) throw new Error(`Invalid type code in elem: ${typeCode}`); elemTypes.push(codeToType[typeCode]); } // Initialize Δelems and elems arrays if first chunk if (!result.Δelems.length && ΔelemCount > 0) { result.Δelems = Array(ΔelemCount).fill().map(() => []); } if (!result.elems.length && elemCount > 0) { result.elems = Array(elemCount).fill().map(() => []); } // Read initial values for Δelems const initialValues = []; for (const { baseType } of ΔelemTypes) { if (offset + typeToBytes[baseType.name] > bundleEnd) { throw new Error('Incomplete initial values'); } initialValues.push(readTypedValue(view, offset, baseType)); offset += typeToBytes[baseType.name]; } // Skip padding while (offset % 4 !== 0) { if (offset >= bundleEnd) throw new Error('Incomplete padding after initial values'); offset++; } // Reconstruct j values for (let idx = 0; idx < count; idx++) { result.j.push(j0 + idx * Δj); } // Read record data (non-interleaved) for (let i = 0; i < ΔelemCount; i++) { let current = initialValues[i]; const values = result.Δelems[i]; const incrType = ΔelemTypes[i].incrType; const isBigInt = typeof current === 'bigint'; for (let idx = 0; idx < count; idx++) { if (offset + typeToBytes[incrType.name] > bundleEnd) { throw new Error('Incomplete Δelem data'); } let delta = readTypedValue(view, offset, incrType); if (idx === 0) { values.push(isBigInt ? Number(current) : current); } else { if (isBigInt) { delta = BigInt(delta); current += delta; values.push(Number(current)); } else { current += delta; values.push(current); } } offset += typeToBytes[incrType.name]; } } for (let i = 0; i < elemCount; i++) { const values = result.elems[i]; const type = elemTypes[i]; const isBigInt = type === BigInt64Array || type === BigUint64Array; for (let idx = 0; idx < count; idx++) { if (offset + typeToBytes[type.name] > bundleEnd) { throw new Error('Incomplete elem data'); } let value = readTypedValue(view, offset, type); values.push(isBigInt ? Number(value) : value); offset += typeToBytes[type.name]; } } // Skip padding while (offset % 4 !== 0) { if (offset >= bundleEnd) throw new Error('Incomplete padding after record data'); offset++; } } } return result; } function interleaved(binary) { if (!(binary instanceof Uint8Array) || binary.length < 4) { throw new Error('Invalid binary input'); } const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength); let offset = 0; // Initialize result (assuming single i value for simplicity; extend for multiple i values if needed) const result = { i: null, j: [], Δelems: [], elems: [] }; // Process bundles while (offset < binary.length) { // Read bundle header if (offset + 4 > binary.length) throw new Error('Incomplete bundle header'); const bundleHeader = view.getUint32(offset, true); if ((bundleHeader & 0xFF) !== 0x1C) throw new Error('Invalid bundle marker'); const bundleLength = bundleHeader >> 8; offset += 4; const bundleEnd = offset + bundleLength; if (bundleEnd > binary.length) throw new Error('Bundle length exceeds input size'); // Process chunks in bundle while (offset < bundleEnd) { // Read chunk header if (offset + 12 > bundleEnd) throw new Error('Incomplete chunk header'); const chunkType = view.getUint8(offset); if (chunkType !== 0x12) throw new Error(`Unsupported chunk type: ${chunkType}`); offset += 1; // Skip udv offset += 1; const count = view.getUint16(offset, true); offset += 2; if (count > 65535) throw new Error('Chunk count exceeds 65535'); const iValue = view.getUint16(offset, true); offset += 2; const j0 = view.getUint16(offset, true); offset += 2; const Δj = view.getInt16(offset, true); offset += 2; const ΔelemCount = view.getUint8(offset++); // Δelem_count const elemCount = view.getUint8(offset++); // elem_count // Set i value (assuming all chunks share the same i) if (result.i === null) result.i = iValue; else if (result.i !== iValue) throw new Error('Multiple i values not supported'); // Read preface (element types) const ΔelemTypes = []; for (let i = 0; i < ΔelemCount; i++) { if (offset >= bundleEnd) throw new Error('Incomplete Δelem types'); const typeByte = view.getUint8(offset++); const baseCode = typeByte & 0x0F; const incrCode = typeByte >> 4; if (!codeToType[baseCode] || !codeToType[incrCode]) { throw new Error(`Invalid type code in Δelem: ${typeByte}`); } ΔelemTypes.push({ baseType: codeToType[baseCode], incrType: codeToType[incrCode] }); } const elemTypes = []; for (let i = 0; i < elemCount; i++) { if (offset >= bundleEnd) throw new Error('Incomplete elem types'); const typeCode = view.getUint8(offset++); if (!codeToType[typeCode]) throw new Error(`Invalid type code in elem: ${typeCode}`); elemTypes.push(codeToType[typeCode]); } // Initialize Δelems and elems arrays if first chunk if (!result.Δelems.length && ΔelemCount > 0) { result.Δelems = Array(ΔelemCount).fill().map(() => []); } if (!result.elems.length && elemCount > 0) { result.elems = Array(elemCount).fill().map(() => []); } // Read initial values for Δelems const initialValues = []; for (const { baseType } of ΔelemTypes) { if (offset + typeToBytes[baseType.name] > bundleEnd) { throw new Error('Incomplete initial values'); } initialValues.push(readTypedValue(view, offset, baseType)); offset += typeToBytes[baseType.name]; } // Skip padding while (offset % 4 !== 0) { if (offset >= bundleEnd) throw new Error('Incomplete padding after initial values'); offset++; } // Reconstruct j values for (let idx = 0; idx < count; idx++) { result.j.push(j0 + idx * Δj); } // Read interleaved record data for (let idx = 0; idx < count; idx++) { // Read Δelems for (let i = 0; i < ΔelemCount; i++) { const values = result.Δelems[i]; const incrType = ΔelemTypes[i].incrType; const isBigInt = typeof initialValues[i] === 'bigint'; if (offset + typeToBytes[incrType.name] > bundleEnd) { throw new Error('Incomplete Δelem data'); } let delta = readTypedValue(view, offset, incrType); offset += typeToBytes[incrType.name]; if (idx === 0) { values.push(isBigInt ? Number(initialValues[i]) : initialValues[i]); } else { if (isBigInt) { delta = BigInt(delta); initialValues[i] += delta; values.push(Number(initialValues[i])); } else { initialValues[i] += delta; values.push(initialValues[i]); } } } // Read elems for (let i = 0; i < elemCount; i++) { const values = result.elems[i]; const type = elemTypes[i]; const isBigInt = type === BigInt64Array || type === BigUint64Array; if (offset + typeToBytes[type.name] > bundleEnd) { throw new Error('Incomplete elem data'); } let value = readTypedValue(view, offset, type); values.push(isBigInt ? Number(value) : value); offset += typeToBytes[type.name]; } } // Skip padding while (offset % 4 !== 0) { if (offset >= bundleEnd) throw new Error('Incomplete padding after record data'); offset++; } } } return result; } function readTypedValue(view, offset, type) { switch (type) { case Int8Array: return view.getInt8(offset); case Uint8Array: return view.getUint8(offset); case Int16Array: return view.getInt16(offset, true); case Uint16Array: return view.getUint16(offset, true); case Int32Array: return view.getInt32(offset, true); case Uint32Array: return view.getUint32(offset, true); case Float32Array: return view.getFloat32(offset, true); case Float64Array: return view.getFloat64(offset, true); case BigInt64Array: return view.getBigInt64(offset, true); case BigUint64Array: return view.getBigUint64(offset, true); default: throw new Error(`Unsupported type: ${type.name}`); } } module.exports = { sequential, interleaved };