diff --git a/worker/src/archive/ranged/range-reader.ts b/worker/src/archive/ranged/range-reader.ts new file mode 100644 index 0000000..3026026 --- /dev/null +++ b/worker/src/archive/ranged/range-reader.ts @@ -0,0 +1,14 @@ +import type { Client } from "tdl"; +import { downloadFileRange } from "../../tdlib/range-download.js"; + +export type RangeReader = ( + fileId: string, + offset: number, + length: number, + partSize: bigint, +) => Promise; + +export function tdlibRangeReader(client: Client): RangeReader { + return (fileId, offset, length, partSize) => + downloadFileRange(client, fileId, offset, length, partSize); +} diff --git a/worker/src/archive/ranged/sevenz-ranged.test.ts b/worker/src/archive/ranged/sevenz-ranged.test.ts index 3109ba7..1a3cc5a 100644 --- a/worker/src/archive/ranged/sevenz-ranged.test.ts +++ b/worker/src/archive/ranged/sevenz-ranged.test.ts @@ -28,3 +28,38 @@ describe("parseSevenZSignatureHeader", () => { expect(parseSevenZSignatureHeader(MAGIC)).toBeNull(); }); }); + +import { readSevenZListingRanged } from "./sevenz-ranged.js"; +import type { RangeReader } from "./range-reader.js"; + +describe("readSevenZListingRanged", () => { + it("reads the signature + end-header regions and reconstructs for 7z l", async () => { + const size = 5_000_000; + const endHeaderOffset = 4_900_000; // absolute + const nextHeaderOffset = endHeaderOffset - 32; + const sig = Buffer.alloc(32); + Buffer.from([0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c]).copy(sig, 0); + sig.writeBigUInt64LE(BigInt(nextHeaderOffset), 12); + sig.writeBigUInt64LE(100n, 20); + + const reads: { offset: number; length: number }[] = []; + const read: RangeReader = async (_id, offset, length) => { + reads.push({ offset, length }); + if (offset === 0) return sig.subarray(0, length); + return Buffer.alloc(length, 0xAB); // stand-in end-header bytes + }; + + // Inject a fake lister via the module boundary: readSevenZListingRanged + // calls listFromSparse(parts, read7zContents). We assert the ranged reads + // it issued; the sparse file + real 7z is covered by live verification. + const entries = await readSevenZListingRanged( + [{ fileId: "1", fileSize: BigInt(size), fileName: "a.7z" }], + read, + ); + // entries may be null here because the stand-in bytes aren't a real 7z; + // the contract under test is the ranged-read offsets: + expect(reads[0]).toEqual({ offset: 0, length: 32 }); + expect(reads[1]).toEqual({ offset: endHeaderOffset, length: 100 }); + expect(entries === null || Array.isArray(entries)).toBe(true); + }); +}); diff --git a/worker/src/archive/ranged/sevenz-ranged.ts b/worker/src/archive/ranged/sevenz-ranged.ts index 79e0448..0892f00 100644 --- a/worker/src/archive/ranged/sevenz-ranged.ts +++ b/worker/src/archive/ranged/sevenz-ranged.ts @@ -1,3 +1,11 @@ +import type { FileEntry } from "../zip-reader.js"; +import { read7zContents } from "../sevenz-reader.js"; +import { listFromSparse } from "./sparse-list.js"; +import type { RangeReader } from "./range-reader.js"; +import { childLogger } from "../../util/logger.js"; + +const log = childLogger("sevenz-ranged"); + const SEVENZ_MAGIC = Buffer.from([0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c]); export function parseSevenZSignatureHeader( @@ -10,3 +18,36 @@ export function parseSevenZSignatureHeader( nextHeaderSize: Number(buf.readBigUInt64LE(20)), }; } + +export interface RangedPart { fileId: string; fileSize: bigint; fileName: string } + +export async function readSevenZListingRanged( + parts: RangedPart[], + read: RangeReader, +): Promise { + const part = parts[0]; + if (!part) return null; + const size = Number(part.fileSize); + try { + const sig = await read(part.fileId, 0, 32, part.fileSize); + const parsed = parseSevenZSignatureHeader(sig); + if (!parsed) return null; + const endStart = 32 + parsed.nextHeaderOffset; + if (endStart < 0 || endStart + parsed.nextHeaderSize > size) return null; + const endHeader = await read(part.fileId, endStart, parsed.nextHeaderSize, part.fileSize); + return listFromSparse( + [{ + fileName: part.fileName, + size, + regions: [ + { offset: 0, bytes: sig }, + { offset: endStart, bytes: endHeader }, + ], + }], + read7zContents, + ); + } catch (err) { + log.warn({ err, fileId: part.fileId }, "ranged 7z listing failed"); + return null; + } +}