Skip to content
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cdb-converter",
"version": "0.2.0",
"version": "0.3.0",
"description": "Convert Pro Cycling Manager CDB files to/from SQLite and other formats. TypeScript library with zero configuration.",
"license": "MIT",
"author": "mpicciolli",
Expand Down
21 changes: 18 additions & 3 deletions src/reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,24 @@ export class CDBReader {
break;

default:
throw new Error(
`Unknown chunk type: 0x${(header.chunkType as number).toString(16)}`,
);
{
if (typeof console !== "undefined") {
console.warn(
`Skipping unknown chunk type: 0x${(header.chunkType as number).toString(16)} at position ${chunkStartPos}`,
);
}
const skippedBytes = chunkEndPos - this.pos - 4;
if (skippedBytes < 0) {
throw new Error(
`Invalid chunk size for unknown chunk type 0x${(header.chunkType as number).toString(16)} at position ${chunkStartPos}`,
);
}
result = {
type: header.chunkType,
value: this.readBytes(skippedBytes),
};
}
break;
}

this.readPadding();
Expand Down
51 changes: 50 additions & 1 deletion test/reader.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { CDBReader } from "../src/reader";
import { CHUNK_TYPE, DATA_TYPE, MAGIC } from "../src/tableMetadata";
import { CDBWriter } from "../src/writer";
Expand Down Expand Up @@ -59,6 +59,22 @@ function createWrapperWithMissingColumnDescription(): Uint8Array {
return writer.getData();
}

function createUnknownChunkBuffer(payloadLength: number): Uint8Array {
const chunkSize = 28 + payloadLength;
const buffer = new ArrayBuffer(chunkSize);
const view = new DataView(buffer);

view.setUint32(0, MAGIC.CHUNK_BEGIN, true);
view.setUint32(4, chunkSize, true);
view.setUint32(8, 0x99, true); // unknown chunk type
view.setUint32(12, 0, true);
view.setUint32(16, 0, true);
view.setUint32(20, MAGIC.CHUNK_SEPARATOR, true);
view.setUint32(24 + payloadLength, MAGIC.CHUNK_END, true);

return new Uint8Array(buffer);
}

function createFloatListTable(rows: string[]): Uint8Array {
const writer = new CDBWriter();

Expand Down Expand Up @@ -134,6 +150,39 @@ describe("CDBReader", () => {
);
});

it("skips an unknown chunk type instead of throwing", () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
try {
const reader = new CDBReader(createUnknownChunkBuffer(8));

const chunk = reader.readChunk();

expect(chunk.type).toBe(0x99);
expect(chunk.value).toBeInstanceOf(Uint8Array);
expect((chunk.value as Uint8Array).length).toBe(8);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining("Skipping unknown chunk type: 0x99"),
);
} finally {
warnSpy.mockRestore();
}
});

it("throws when an unknown chunk declares an impossibly small size", () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
try {
const buffer = createUnknownChunkBuffer(0);
new DataView(buffer.buffer).setUint32(4, 4, true);
const reader = new CDBReader(buffer);

expect(() => reader.readChunk()).toThrowError(
"Invalid chunk size for unknown chunk type 0x99 at position 0",
);
} finally {
warnSpy.mockRestore();
}
});

describe("FLOAT_LIST formatting", () => {
it("round-trips small magnitude values without losing precision", () => {
const original = Math.fround(1e-8);
Expand Down