// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. import { describe, expect, it } from 'vitest'; import { BinaryReader } from '../src/onenote/binary/BinaryReader.js'; import { parseObjectPropSet } from '../src/onenote/onestore/property-set.js'; import { OneNoteParserError } from '../src/onenote/parser/error.js'; import { resolveParserLimits } from '../src/onenote/parser/limits.js'; describe('bounded OneStore primitives', () => { it('reports an absolute offset instead of reading beyond its view', () => { const reader = new BinaryReader( Uint8Array.from([0, 1, 2, 3, 4, 5]), 2, 3, 'test view' ); expect(reader.u16()).toBe(0x0302); try { reader.u16(); expect.unreachable('the read should fail'); } catch (error) { expect(error).toBeInstanceOf(OneNoteParserError); expect(error).toMatchObject({ code: 'BOUNDS_EXCEEDED', offset: 4, structure: 'test view', }); } }); it('decodes a bounded object property set without reading Boolean bytes', () => { const bytes = Uint8Array.from([ 0x00, 0x00, 0x00, 0x80, // OID stream: zero IDs, OSID stream absent 0x02, 0x00, // two properties 0x01, 0x00, 0x00, 0x88, // Boolean true 0x02, 0x00, 0x00, 0x1c, // byte vector 0x03, 0x00, 0x00, 0x00, 0xaa, 0xbb, 0xcc, ]); const parsed = parseObjectPropSet( bytes, { offset: 0, size: bytes.length, nil: false, zero: false }, resolveParserLimits() ); expect(parsed.properties.entries[0]?.value).toEqual({ kind: 'bool', value: true, }); expect(parsed.properties.entries[1]?.value).toMatchObject({ kind: 'bytes', value: Uint8Array.from([0xaa, 0xbb, 0xcc]), }); }); it('enforces one aggregate budget across nested PropertySet arrays', () => { const bytes = objectPropertySetWithNestedArrays(16); const reference = { offset: 0, size: bytes.length, nil: false, zero: false, }; expect(() => parseObjectPropSet( bytes, reference, // Every array contains one set, but the property set contains 16. resolveParserLimits({ maxObjectReferences: 15 }) ) ).toThrowError( expect.objectContaining({ code: 'LIMIT_EXCEEDED', structure: 'Nested PropertySets', }) ); const parsed = parseObjectPropSet( bytes, reference, resolveParserLimits({ maxObjectReferences: 16 }) ); expect(parsed.properties.entries).toHaveLength(16); }); }); function objectPropertySetWithNestedArrays(count: number): Uint8Array { const propertyIds = Array.from({ length: count }, (_, index) => u32((0x10 << 26) | index) ).flat(); const values = Array.from({ length: count }, (_, index) => [ ...u32(1), // One nested PropertySet in this array. ...u32(index), 0x00, 0x00, // Empty nested PropertySet. ]).flat(); return Uint8Array.from([ ...u32(0x8000_0000), // Zero OIDs; OSID stream absent. count & 0xff, count >>> 8, ...propertyIds, ...values, ]); } function u32(value: number): number[] { return [ value & 0xff, (value >>> 8) & 0xff, (value >>> 16) & 0xff, value >>> 24, ]; }