From 973068e6d5899a291551f8a1ddd3e357c143c59d Mon Sep 17 00:00:00 2001 From: ktz03 <2484593937@qq.com> Date: Fri, 9 Oct 2026 10:54:53 +0800 Subject: [PATCH 1/2] fix: accept GGUF byte ranges ending at EOF --- .../fileReaders/GgufNetworkFetchFileReader.ts | 2 +- .../gguf/ggufNetworkFileBoundary.test.ts | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/standalone/gguf/ggufNetworkFileBoundary.test.ts diff --git a/src/gguf/fileReaders/GgufNetworkFetchFileReader.ts b/src/gguf/fileReaders/GgufNetworkFetchFileReader.ts index 0a6d0c12..5b5d9acf 100644 --- a/src/gguf/fileReaders/GgufNetworkFetchFileReader.ts +++ b/src/gguf/fileReaders/GgufNetworkFetchFileReader.ts @@ -59,7 +59,7 @@ export class GgufNetworkFetchFileReader extends GgufFileReader { if (endOffset > this._buffer.length) return this._fetchToExpandBufferUpToOffset(endOffset) .then(() => { - if (endOffset >= this._buffer.length) + if (endOffset > this._buffer.length) throw new Error("Expected buffer to be long enough for the requested byte range"); }); diff --git a/test/standalone/gguf/ggufNetworkFileBoundary.test.ts b/test/standalone/gguf/ggufNetworkFileBoundary.test.ts new file mode 100644 index 00000000..3d2dbd41 --- /dev/null +++ b/test/standalone/gguf/ggufNetworkFileBoundary.test.ts @@ -0,0 +1,93 @@ +import http from "node:http"; +import {AddressInfo} from "node:net"; +import {describe, expect, test} from "vitest"; +import {readGgufFileInfo} from "../../../src/gguf/readGgufFileInfo.js"; +import {defaultExtraAllocationSize} from "../../../src/gguf/consts.js"; + +describe("gguf network file boundaries", () => { + test("reads metadata ending exactly at EOF after expanding the buffer", async () => { + const name = "a".repeat(defaultExtraAllocationSize + 32); + await withRangeServer(createGguf(name), async (url) => { + const info = await readGgufFileInfo(url, { + readTensorInfo: false, + fetchRetryOptions: {retries: 0} + }); + + expect(info.metadata.general?.name).to.equal(name); + expect(info.tensorCount).to.equal(0); + }); + }); + + test("reads metadata when more bytes follow the requested range", async () => { + const name = "a".repeat(defaultExtraAllocationSize + 32); + const data = Buffer.concat([createGguf(name), Buffer.alloc(32)]); + await withRangeServer(data, async (url) => { + const info = await readGgufFileInfo(url, { + readTensorInfo: false, + fetchRetryOptions: {retries: 0} + }); + + expect(info.metadata.general?.name).to.equal(name); + }); + }); + + test("rejects metadata truncated before the end of the requested range", async () => { + const name = "a".repeat(defaultExtraAllocationSize + 32); + const data = createGguf(name).subarray(0, -1); + await withRangeServer(data, async (url) => { + await expect(readGgufFileInfo(url, { + readTensorInfo: false, + fetchRetryOptions: {retries: 0} + })).rejects.toThrow("Expected buffer to be long enough for the requested byte range"); + }); + }); +}); + +function createGguf(name: string) { + const key = Buffer.from("general.name"); + const value = Buffer.from(name); + const header = Buffer.alloc(24); + header.write("GGUF"); + header.writeUInt32LE(3, 4); + header.writeBigUInt64LE(0n, 8); + header.writeBigUInt64LE(1n, 16); + + const keyLength = Buffer.alloc(8); + keyLength.writeBigUInt64LE(BigInt(key.length)); + const valueType = Buffer.alloc(4); + valueType.writeUInt32LE(8); // GGUF string + const valueLength = Buffer.alloc(8); + valueLength.writeBigUInt64LE(BigInt(value.length)); + + return Buffer.concat([header, keyLength, key, valueType, valueLength, value]); +} + +async function withRangeServer(data: Buffer, callback: (url: string) => Promise) { + const server = http.createServer((request, response) => { + const range = /^bytes=(\d+)-(\d+)$/.exec(request.headers.range ?? ""); + if (range == null) { + response.writeHead(400).end(); + return; + } + + const start = Number(range[1]); + const end = Math.min(Number(range[2]), data.length - 1); + response.writeHead(206, { + "Content-Range": `bytes ${start}-${end}/${data.length}` + }); + response.end(data.subarray(start, end + 1)); + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + try { + const address = server.address() as AddressInfo; + await callback(`http://127.0.0.1:${address.port}/model.gguf`); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error == null ? resolve() : reject(error))); + }); + } +} From 5a2803870fb2c86ca477f03e2b8925b15207851a Mon Sep 17 00:00:00 2001 From: ktz03 <2484593937@qq.com> Date: Wed, 14 Oct 2026 10:17:09 +0800 Subject: [PATCH 2/2] test: use a real GGUF for network EOF coverage --- .../gguf/ggufNetworkFileBoundary.test.ts | 99 ++++--------------- 1 file changed, 17 insertions(+), 82 deletions(-) diff --git a/test/standalone/gguf/ggufNetworkFileBoundary.test.ts b/test/standalone/gguf/ggufNetworkFileBoundary.test.ts index 3d2dbd41..638e7ddc 100644 --- a/test/standalone/gguf/ggufNetworkFileBoundary.test.ts +++ b/test/standalone/gguf/ggufNetworkFileBoundary.test.ts @@ -1,93 +1,28 @@ -import http from "node:http"; -import {AddressInfo} from "node:net"; import {describe, expect, test} from "vitest"; -import {readGgufFileInfo} from "../../../src/gguf/readGgufFileInfo.js"; -import {defaultExtraAllocationSize} from "../../../src/gguf/consts.js"; +import {GgufNetworkFetchFileReader} from "../../../src/gguf/fileReaders/GgufNetworkFetchFileReader.js"; + +const remoteGGUFModel = "https://huggingface.co/ggml-org/models-moved/resolve/499bc8821c6b12b4e53c5bffcb21ec206f212d81/tinyllamas/stories260K.gguf"; +const remoteGGUFModelSize = 1185376; describe("gguf network file boundaries", () => { - test("reads metadata ending exactly at EOF after expanding the buffer", async () => { - const name = "a".repeat(defaultExtraAllocationSize + 32); - await withRangeServer(createGguf(name), async (url) => { - const info = await readGgufFileInfo(url, { - readTensorInfo: false, - fetchRetryOptions: {retries: 0} - }); + test("reads the final byte after expanding the buffer to EOF", {timeout: 1000 * 60 * 10}, async () => { + const fileReader = new GgufNetworkFetchFileReader({url: remoteGGUFModel, retryOptions: {retries: 0}}); - expect(info.metadata.general?.name).to.equal(name); - expect(info.tensorCount).to.equal(0); - }); + expect(await fileReader.readUint8(remoteGGUFModelSize - 1)) + .toBe(63); }); - test("reads metadata when more bytes follow the requested range", async () => { - const name = "a".repeat(defaultExtraAllocationSize + 32); - const data = Buffer.concat([createGguf(name), Buffer.alloc(32)]); - await withRangeServer(data, async (url) => { - const info = await readGgufFileInfo(url, { - readTensorInfo: false, - fetchRetryOptions: {retries: 0} - }); - - expect(info.metadata.general?.name).to.equal(name); - }); - }); + test("reads bytes before EOF", {timeout: 1000 * 60 * 10}, async () => { + const fileReader = new GgufNetworkFetchFileReader({url: remoteGGUFModel, retryOptions: {retries: 0}}); - test("rejects metadata truncated before the end of the requested range", async () => { - const name = "a".repeat(defaultExtraAllocationSize + 32); - const data = createGguf(name).subarray(0, -1); - await withRangeServer(data, async (url) => { - await expect(readGgufFileInfo(url, { - readTensorInfo: false, - fetchRetryOptions: {retries: 0} - })).rejects.toThrow("Expected buffer to be long enough for the requested byte range"); - }); + expect(await fileReader.readStringWithLength(0, 4)) + .toBe("GGUF"); }); -}); - -function createGguf(name: string) { - const key = Buffer.from("general.name"); - const value = Buffer.from(name); - const header = Buffer.alloc(24); - header.write("GGUF"); - header.writeUInt32LE(3, 4); - header.writeBigUInt64LE(0n, 8); - header.writeBigUInt64LE(1n, 16); - const keyLength = Buffer.alloc(8); - keyLength.writeBigUInt64LE(BigInt(key.length)); - const valueType = Buffer.alloc(4); - valueType.writeUInt32LE(8); // GGUF string - const valueLength = Buffer.alloc(8); - valueLength.writeBigUInt64LE(BigInt(value.length)); + test("rejects a byte beyond EOF", {timeout: 1000 * 60 * 10}, async () => { + const fileReader = new GgufNetworkFetchFileReader({url: remoteGGUFModel, retryOptions: {retries: 0}}); - return Buffer.concat([header, keyLength, key, valueType, valueLength, value]); -} - -async function withRangeServer(data: Buffer, callback: (url: string) => Promise) { - const server = http.createServer((request, response) => { - const range = /^bytes=(\d+)-(\d+)$/.exec(request.headers.range ?? ""); - if (range == null) { - response.writeHead(400).end(); - return; - } - - const start = Number(range[1]); - const end = Math.min(Number(range[2]), data.length - 1); - response.writeHead(206, { - "Content-Range": `bytes ${start}-${end}/${data.length}` - }); - response.end(data.subarray(start, end + 1)); + await expect(fileReader.readUint8(remoteGGUFModelSize)) + .rejects.toThrow("Expected buffer to be long enough for the requested byte range"); }); - - await new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", resolve); - }); - try { - const address = server.address() as AddressInfo; - await callback(`http://127.0.0.1:${address.port}/model.gguf`); - } finally { - await new Promise((resolve, reject) => { - server.close((error) => (error == null ? resolve() : reject(error))); - }); - } -} +});