compound-binary-file-js
Version:
This is an implementation of [Compound Binary File v.3](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b) \ Allows reading existing files, creation of the/write operation
296 lines (265 loc) • 15.1 kB
text/typescript
import {Sectors} from "../src/Sectors";
import {instance, mock, verify, when} from "ts-mockito";
import {Header} from "../src/Header";
import {
ENDOFCHAIN_MARK,
ENDOFCHAIN_MARK_INT,
FATSECT_MARK,
FREESECT_MARK_OR_NOSTREAM,
initializedWidth
} from "../src/utils";
import * as Long from "long";
import "../src/Long";
import {SimpleSector} from "../src/dataview/SimpleSector";
import {SimpleDataview} from "../src/dataview/SimpleDataview";
import {AllocationTable} from "../src/alloc/AllocationTable";
import { expect } from "chai";
import {FixedSizeChunkedDataview} from "../src/dataview/FixedSizeChunkedDataview";
import {FATtoDIFATFacade} from "../src/alloc/FATtoDIFATFacade";
import {FAT} from "../src/alloc/FAT";
import {MiniFAT} from "../src/alloc/MiniFAT";
import {DIFAT} from "../src/alloc/DIFAT";
import {DIFATSector} from "../src/dataview/DIFATSector";
describe('allocation table test', () => {
let sectorsMock: Sectors;
let header: Header;
beforeEach(()=> {
sectorsMock = mock(Sectors);
const headerMock = mock(Header);
when(headerMock.getSectorShift()).thenReturn(Header.HEADER_LENGTH);
header = instance(headerMock);
});
it('test building a chain of sectors', () => {
const firstSector = [];
firstSector.push(...Long.fromValue(1).to4BytesLE());
firstSector.push(...Long.fromValue(2).to4BytesLE());
firstSector.push(...Long.fromValue(5).to4BytesLE());
firstSector.push(...Long.fromValue(4).to4BytesLE());
const secondSector = [];
secondSector.push(...Long.fromValue(6).to4BytesLE());
secondSector.push(...ENDOFCHAIN_MARK);
secondSector.push(...ENDOFCHAIN_MARK);
secondSector.push(...ENDOFCHAIN_MARK);
when(sectorsMock.sector(0)).thenReturn(SimpleSector.from(new SimpleDataview(firstSector), 0));
when(sectorsMock.sector(1)).thenReturn(SimpleSector.from(new SimpleDataview(secondSector), 1));
const allocationTable = new AllocationTable(
instance(sectorsMock),
[0, 1],
16);
expect(allocationTable.buildChain(0).length).eq(4);
expect(allocationTable.buildChain(0)).to.deep.eq([0,1,2,5]);
expect(allocationTable.buildChain(3).length).eq(3);
expect(allocationTable.buildChain(3)).to.deep.eq([3,4,6]);
});
it('allocate more than 128 FAT sectors (129 Sectors must be allocated in fact -- 1 additional is for storing 129th sector position)', () => {
const sectors = new Sectors(new FixedSizeChunkedDataview(512), header);
const allocationTable = new AllocationTable(sectors, [], header.getSectorShift());
let previousSectorPosition = null;
let firstSectorPosition = null;
for (let i = 0; i < 129; i++) {
const sectorPosition = sectors.allocate().getPosition();
if(firstSectorPosition == null) {
firstSectorPosition = sectorPosition;
}
allocationTable.registerSector(sectorPosition, previousSectorPosition);
previousSectorPosition = sectorPosition;
}
// verify(fatToDIFATFacade, times(2)).registerFatSectorInDIFAT(anyInt());
const chain = allocationTable.buildChain(firstSectorPosition);
expect(chain.length).eq(129);
});
it('sector chain is empty if the parameter sectorPosition (position of the first sector in chain) is ENDOFCHAIN', () => {
const sectors = new Sectors(new FixedSizeChunkedDataview(512), header);
const allocationTable = new AllocationTable(sectors, [], header.getSectorShift());
expect(allocationTable.buildChain(ENDOFCHAIN_MARK_INT).length).eq(0);
});
});
describe('FAT test', () => {
let faTtoDIFATFacade: FATtoDIFATFacade;
beforeEach(() => {
faTtoDIFATFacade = mock(FATtoDIFATFacade);
when(faTtoDIFATFacade.getFatSectorChain()).thenReturn([]);
});
it('If first FAT sector is registered it\'s position should be written to Header also', () => {
const rootView = new FixedSizeChunkedDataview(512);
const header = Header.empty(rootView.allocate(Header.HEADER_LENGTH));
const sectors = new Sectors(rootView, header);
const fat = new FAT(sectors, header, instance(faTtoDIFATFacade));
fat.registerSector(0, null);
fat.registerSector(1, 0);
expect(header.getNumberOfFatSectors()).eq(1);
for (let i = 0; i < 128; i++) {
fat.registerSector(i + 2, i + 1);
}
expect(header.getNumberOfFatSectors()).eq(2);
});
it('should properly register more than 128 sectors -- new Sector should be created to store next 128 positions', () => {
const rootView = new FixedSizeChunkedDataview(512);
const header = Header.empty(rootView.allocate(Header.HEADER_LENGTH));
const sectors = new Sectors(new FixedSizeChunkedDataview(512), header);
const sectorPositions: number[] = [];
for (let i = 0; i < 129; i++) {
sectorPositions.push(sectors.allocate().getPosition());
}
const allocationTable = new FAT(sectors, header, instance(faTtoDIFATFacade));
allocationTable.registerSector(sectorPositions[0], null);
for (let i = 1; i <sectorPositions.length; i++) {
allocationTable.registerSector(sectorPositions[i], sectorPositions[i - 1]);
}
const fatSector = sectors.sector(129);
for (let i = 0; i < sectorPositions.length - 1; i++) {
expect(Long.fromBytesLE(fatSector.subView(i * 4, (i + 1) * 4).getData()).toNumber()).eq(sectorPositions[i + 1]);
}
expect(Long.fromBytesLE(fatSector.subView(508).getData()).toNumber()).eq(128);
const secondFatSector = sectors.sector(130);
expect(secondFatSector.subView(0, 4).getData()).to.deep.eq(ENDOFCHAIN_MARK);
expect(secondFatSector.subView(4, 8).getData()).to.deep.eq(FATSECT_MARK);
expect(secondFatSector.subView(8, 12).getData()).to.deep.eq(FATSECT_MARK);
});
it('test case when all sector positions (128) fit in one FAT sector', () => {
const rootView = new FixedSizeChunkedDataview(512);
const header = Header.empty(rootView.allocate(Header.HEADER_LENGTH));
const sectors = new Sectors(new FixedSizeChunkedDataview(512), header);
const sectorPositions: number[] = [];
for (let i = 0; i < 128; i++) {
sectorPositions.push(sectors.allocate().getPosition());
}
const allocationTable = new FAT(sectors, header, instance(faTtoDIFATFacade));
allocationTable.registerSector(sectorPositions[0], null);
for (let i = 1; i <sectorPositions.length; i++) {
allocationTable.registerSector(sectorPositions[i], sectorPositions[i - 1]);
}
const fatSector = sectors.sector(128);
for (let i = 0; i < sectorPositions.length - 2; i++) {
expect(Long.fromBytesLE(fatSector.subView(i * 4, (i + 1) * 4).getData()).toNumber()).eq(sectorPositions[i + 1]);
}
expect(fatSector.subView(508).getData()).to.deep.eq(ENDOFCHAIN_MARK);
const secondFATSector = sectors.sector(129);
expect(secondFATSector.subView(0, 4).getData()).to.deep.eq(FATSECT_MARK);
expect(secondFATSector.subView(4, 8).getData()).to.deep.eq(FATSECT_MARK);
});
});
describe('DIFAT test', () => {
let faTtoDIFATFacadeMock: FATtoDIFATFacade;
let sectorsMock: Sectors;
let headerMock: Header;
beforeEach(() => {
faTtoDIFATFacadeMock = mock(FATtoDIFATFacade);
sectorsMock = mock(Sectors);
headerMock = mock(Header);
when(headerMock.getSectorShift()).thenReturn(Header.HEADER_LENGTH);
});
it('Read FAT sector chain when only DIFAT entries in Header exist', () => {
const difatEntriesInHeader: number[] = [];
for (let i = 0; i < 100; i++) {
difatEntriesInHeader.push(i);
}
when(headerMock.getDifatEntries()).thenReturn(difatEntriesInHeader);
when(headerMock.getFirstDifatSectorLocation()).thenReturn(Long.fromBytesLE(ENDOFCHAIN_MARK).toNumber());
expect(new DIFAT(instance(sectorsMock), instance(headerMock), instance(faTtoDIFATFacadeMock)).getFatSectorChain().length).eq(100);
});
it('Read FAT sector chain when two DIFAT sectors exist + DIFAT entries in Header ', () => {
const difatEntriesInHeader: number[] = [];
for (let i = 0; i < Header.DIFAT_ENTRIES_LIMIT_IN_HEADER; i++) {
difatEntriesInHeader.push(i);
}
when(headerMock.getDifatEntries()).thenReturn(difatEntriesInHeader);
when(headerMock.getFirstDifatSectorLocation()).thenReturn(0);
const sectors = new Sectors(new FixedSizeChunkedDataview(512), instance(headerMock));
const firstSector = sectors.allocateDIFAT();
for (let i = 0; i < 127; i++) {
firstSector.subView(i * 4, (i + 1) * 4).writeAt(0, Long.fromValue(i).to4BytesLE())
}
firstSector.subView(508).writeAt(0, Long.fromValue(1).to4BytesLE());
const secondSector = sectors.allocateDIFAT();
secondSector.subView(0, 4).writeAt(0, Long.fromValue(0).to4BytesLE());
secondSector.subView(508).writeAt(0, ENDOFCHAIN_MARK);
expect(new DIFAT(sectors, instance(headerMock), instance(faTtoDIFATFacadeMock)).getFatSectorChain().length).eq(237);
});
it('register first FAT sector', () => {
when(headerMock.canFitMoreDifatEntries()).thenReturn(true);
when(headerMock.getFirstDifatSectorLocation()).thenReturn(Long.fromBytesLE(ENDOFCHAIN_MARK).toNumber());
new DIFAT(instance(sectorsMock), instance(headerMock), instance(faTtoDIFATFacadeMock)).registerFATSector(1);
verify(headerMock.registerFatSector(1)).times(1);
});
it('register FAT sector at position 436 (last DIFAT entry in Header)', () => {
when(headerMock.canFitMoreDifatEntries()).thenReturn(true);
when(headerMock.getFirstDifatSectorLocation()).thenReturn(Long.fromBytesLE(ENDOFCHAIN_MARK).toNumber());
new DIFAT(instance(sectorsMock), instance(headerMock), instance(faTtoDIFATFacadeMock)).registerFATSector(1);
verify(headerMock.registerFatSector(1)).times(1);
});
it('register FAT sector in first DIFAT sector (sector to store DIFAT entries following beyond those in Header)', () => {
when(headerMock.canFitMoreDifatEntries()).thenReturn(false);
when(headerMock.getFirstDifatSectorLocation()).thenReturn(Long.fromBytesLE(ENDOFCHAIN_MARK).toNumber());
const sectorMock = mock(DIFATSector);
when(sectorMock.getPosition()).thenReturn(1);
when(sectorsMock.allocateDIFAT()).thenReturn(instance(sectorMock));
new DIFAT(instance(sectorsMock), instance(headerMock), instance(faTtoDIFATFacadeMock)).registerFATSector(0);
verify(sectorsMock.allocateDIFAT()).times(1);
verify(faTtoDIFATFacadeMock.registerDifatSectorInFAT(1)).times(1);
verify(sectorMock.registerFatSector(0)).times(1);
});
it('register 2 FAT sectors in the first DIFAT sector', () => {
when(headerMock.getFirstDifatSectorLocation()).thenReturn(Long.fromBytesLE(ENDOFCHAIN_MARK).toNumber());
when(headerMock.canFitMoreDifatEntries()).thenReturn(false);
const sector = new DIFATSector(SimpleSector.from(new SimpleDataview(initializedWidth(512, 0)), 1, FREESECT_MARK_OR_NOSTREAM));
when(sectorsMock.allocateDIFAT()).thenReturn(sector);
const difat = new DIFAT(instance(sectorsMock), instance(headerMock), instance(faTtoDIFATFacadeMock));
difat.registerFATSector(0);
difat.registerFATSector(1);
verify(sectorsMock.allocateDIFAT()).times(1);
verify(faTtoDIFATFacadeMock.registerDifatSectorInFAT(1)).times(1);
expect(Long.fromBytesLE(sector.subView(0, 4).getData()).toNumber()).eq(0);
expect(Long.fromBytesLE(sector.subView(4, 8).getData()).toNumber()).eq(1);
expect(sector.subView(8, 12).getData()).to.deep.eq(FREESECT_MARK_OR_NOSTREAM);
});
it('register 2 FAT sectors -- first going to DIFAT sector #1 and second to DIFAT sector #2 (check that additional DIFAT sector is created on overflow)', () => {
when(headerMock.canFitMoreDifatEntries()).thenReturn(false);
when(headerMock.getFirstDifatSectorLocation()).thenReturn(0);
const sectors = new Sectors(new FixedSizeChunkedDataview(512), instance(headerMock));
const firstSector = sectors.allocateDIFAT();
for (let i = 0; i < 126; i++) {
firstSector.subView(i * 4, (i + 1) * 4).writeAt(0, Long.fromValue(i).to4BytesLE());
}
const difat = new DIFAT(sectors, instance(headerMock), instance(faTtoDIFATFacadeMock));
difat.registerFATSector(126);
difat.registerFATSector(127);
verify(faTtoDIFATFacadeMock.registerDifatSectorInFAT(1)).times(1);
expect(Long.fromBytesLE(firstSector.subView(504, 508).getData()).toNumber()).eq(126);
expect(Long.fromBytesLE(firstSector.subView(508).getData()).toNumber()).eq(1);
expect(Long.fromBytesLE(sectors.sector(1).subView(0, 4).getData()).toNumber()).eq(127);
});
it('number of DIFAT sectors has to be stored in Header', () => {
const rootView = new FixedSizeChunkedDataview(512);
const header = Header.empty(rootView.allocate(Header.HEADER_LENGTH));
const sectors = new Sectors(rootView, header);
const difat = new DIFAT(sectors, header, instance(faTtoDIFATFacadeMock));
for (let i = 0; i < Header.DIFAT_ENTRIES_LIMIT_IN_HEADER + 2; i++) {
difat.registerFATSector(i);
}
expect(header.getFirstDifatSectorLocation()).gte(0);
expect(header.getNumberOfDifatSectors()).eq(1);
});
});
describe('Mini FAT test', () => {
let faTtoDIFATFacadeMock: FATtoDIFATFacade;
beforeEach(() => {
faTtoDIFATFacadeMock = mock(FATtoDIFATFacade);
when(faTtoDIFATFacadeMock.getFatSectorChain()).thenReturn([]);
});
it('Number of Mini FAT sectors should be written to Header', () => {
const rootView = new FixedSizeChunkedDataview(512);
const header = Header.empty(rootView.allocate(Header.HEADER_LENGTH));
const sectors = new Sectors(rootView, header);
const fat = new FAT(sectors, header, instance(faTtoDIFATFacadeMock));
const miniFAT = new MiniFAT(sectors, header, fat);
miniFAT.registerSector(0, null);
miniFAT.registerSector(1, 0);
expect(header.getNumberOfMiniFatSectors()).eq(1);
for (let i = 0; i < 128; i++) {
miniFAT.registerSector(i+2, i+1);
}
expect(header.getNumberOfMiniFatSectors()).eq(2);
expect(header.getFirstMinifatSectorLocation()).gte(0);
})
});