From 99bcd7d7a83e4d47dbd3c321f799456239ebeea8 Mon Sep 17 00:00:00 2001 From: James Prevett Date: Wed, 2 Oct 2024 23:21:07 -0500 Subject: [PATCH] Refactored `DirectoryRecord` into a struct Refactored `SLComponentRecord` into a struct Added `ShortFormDate` struct --- src/DirectoryRecord.ts | 82 +++++++++++++++++++--------------------- src/SLComponentRecord.ts | 17 ++++----- src/VolumeDescriptor.ts | 17 +++++---- src/entries.ts | 28 +++++++------- src/utils.ts | 73 +++++++++++++++++++++-------------- 5 files changed, 115 insertions(+), 102 deletions(-) diff --git a/src/DirectoryRecord.ts b/src/DirectoryRecord.ts index 417dab6..fe29edc 100644 --- a/src/DirectoryRecord.ts +++ b/src/DirectoryRecord.ts @@ -1,23 +1,27 @@ +import { decode, Errno, ErrnoError } from '@zenfs/core'; +import { deserialize, member, struct, types as t } from 'utilium'; import { type Directory, ISODirectory, JolietDirectory } from './Directory.js'; import { SLComponentFlags } from './SLComponentRecord.js'; import { FileFlags, rockRidgeIdentifier } from './constants.js'; import { CLEntry, EREntry, NMEntry, NMFlags, RREntry, SLEntry, SPEntry, SystemUseEntry, constructSystemUseEntries } from './entries.js'; -import { TGetString, getASCIIString, getJolietString, getShortFormDate } from './utils.js'; +import { ShortFormDate, getJolietString } from './utils.js'; +@struct() export abstract class DirectoryRecord { protected _view: DataView; - // Offset at which system use entries begin. Set to -1 if not enabled. - protected _rockRidgeOffset: number; protected _suEntries?: SystemUseEntry[]; protected _file?: Uint8Array; protected _dir?: Directory; public constructor( protected data: Uint8Array, - rockRidgeOffset: number + /** + * Offset at which system use entries begin. Set to -1 if not enabled. + */ + protected _rockRidgeOffset: number ) { + deserialize(this, data); this._view = new DataView(data.buffer); - this._rockRidgeOffset = rockRidgeOffset; } public get hasRockRidge(): boolean { @@ -27,6 +31,7 @@ export abstract class DirectoryRecord { public get rockRidgeOffset(): number { return this._rockRidgeOffset; } + /** * !!ONLY VALID ON ROOT NODE!! * Checks if Rock Ridge is enabled, and sets the offset. @@ -40,44 +45,44 @@ export abstract class DirectoryRecord { } } - public get length(): number { - return this.data[0]; - } + @t.uint8 public length!: number; - public get extendedAttributeRecordLength(): number { - return this.data[1]; - } + @t.uint8 public extendedAttributeRecordLength!: number; + + @t.uint32 protected _lba!: number; public get lba(): number { - return this._view.getUint32(2, true) * 2048; + return this._lba * 2048; } - public get dataLength(): number { - return this._view.getUint32(10, true); + public set lba(value: number) { + if (!Number.isInteger(value / 2048)) { + throw new ErrnoError(Errno.EINVAL, 'Invalid LBA value'); + } + this._lba = value / 2048; } + @t.uint32 public dataLength!: number; + + @member(ShortFormDate) protected date: ShortFormDate = new ShortFormDate(); + public get recordingDate(): Date { - return getShortFormDate(this.data, 18); + return this.date.date; } - public get fileFlags(): number { - return this.data[25]; - } + @t.uint8 public fileFlags!: number; - public get fileUnitSize(): number { - return this.data[26]; - } + @t.uint8 public fileUnitSize!: number; - public get interleaveGapSize(): number { - return this.data[27]; - } + @t.uint8 public interleaveGapSize!: number; - public get volumeSequenceNumber(): number { - return this._view.getUint16(28, true); - } + @t.uint16 public volumeSequenceNumber!: number; + + @t.uint8 protected identifierLength!: number; public get identifier(): string { - return this._getString(this.data, 33, this.data[32]); + const stringData = this.data.slice(33, 33 + this.identifierLength); + return this._getString(stringData); } public fileName(isoData: Uint8Array): string { @@ -179,10 +184,10 @@ export abstract class DirectoryRecord { } return this._suEntries!; } - protected getString(i: number, len: number): string { - return this._getString(this.data, i, len); + protected getString(): string { + return this._getString(this.data); } - protected abstract _getString: TGetString; + protected abstract _getString: (data: Uint8Array) => string; protected abstract _constructDirectory(isoData: Uint8Array): Directory; protected _rockRidgeFilename(isoData: Uint8Array): string | null { const nmEntries = this.getSUEntries(isoData).filter(e => e instanceof NMEntry); @@ -237,25 +242,16 @@ export abstract class DirectoryRecord { } export class ISODirectoryRecord extends DirectoryRecord { - public constructor(data: Uint8Array, rockRidgeOffset: number) { - super(data, rockRidgeOffset); - } protected _constructDirectory(isoData: Uint8Array): Directory { return new ISODirectory(this, isoData); } - protected get _getString(): TGetString { - return getASCIIString; - } + + protected _getString = decode; } export class JolietDirectoryRecord extends DirectoryRecord { - public constructor(data: Uint8Array, rockRidgeOffset: number) { - super(data, rockRidgeOffset); - } protected _constructDirectory(isoData: Uint8Array): Directory { return new JolietDirectory(this, isoData); } - protected get _getString(): TGetString { - return getJolietString; - } + protected _getString = getJolietString; } diff --git a/src/SLComponentRecord.ts b/src/SLComponentRecord.ts index b421ba5..ba4f8d0 100644 --- a/src/SLComponentRecord.ts +++ b/src/SLComponentRecord.ts @@ -1,4 +1,4 @@ -import { TGetString } from './utils.js'; +import { struct, types as t } from 'utilium'; export const enum SLComponentFlags { CONTINUE = 1, @@ -7,22 +7,19 @@ export const enum SLComponentFlags { ROOT = 1 << 3, } +@struct() export class SLComponentRecord { public constructor(protected data: Uint8Array) {} - public get flags(): SLComponentFlags { - return this.data[0]; - } + @t.uint8 public flags!: SLComponentFlags; + + @t.uint8 public componentLength!: number; public get length(): number { return 2 + this.componentLength; } - public get componentLength(): number { - return this.data[1]; - } - - public content(getString: TGetString): string { - return getString(this.data, 2, this.componentLength); + public content(getString: (data: Uint8Array) => string): string { + return getString(this.data.slice(2, 2 + this.componentLength)); } } diff --git a/src/VolumeDescriptor.ts b/src/VolumeDescriptor.ts index 0c9543b..d25dea3 100644 --- a/src/VolumeDescriptor.ts +++ b/src/VolumeDescriptor.ts @@ -1,6 +1,7 @@ import { ErrnoError, Errno } from '@zenfs/core/error.js'; import { DirectoryRecord, ISODirectoryRecord, JolietDirectoryRecord } from './DirectoryRecord.js'; -import { getASCIIString, getDate, getJolietString } from './utils.js'; +import { getDate, getJolietString } from './utils.js'; +import { decode } from '@zenfs/core'; export const enum VolumeDescriptorType { BootRecord = 0, @@ -21,7 +22,7 @@ export class VolumeDescriptor { } public get standardIdentifier(): string { - return getASCIIString(this._data, 1, 5); + return decode(this._data.slice(1, 5)); } public get version(): number { @@ -117,19 +118,19 @@ export abstract class PrimaryOrSupplementaryVolumeDescriptor extends VolumeDescr } public get volumeCreationDate(): Date { - return getDate(this._data, 813); + return getDate(this._data.slice(813)); } public get volumeModificationDate(): Date { - return getDate(this._data, 830); + return getDate(this._data.slice(830)); } public get volumeExpirationDate(): Date { - return getDate(this._data, 847); + return getDate(this._data.slice(847)); } public get volumeEffectiveDate(): Date { - return getDate(this._data, 864); + return getDate(this._data.slice(864)); } public get fileStructureVersion(): number { @@ -193,7 +194,7 @@ export class SupplementaryVolumeDescriptor extends PrimaryOrSupplementaryVolumeD protected _constructRootDirectoryRecord(data: Uint8Array): DirectoryRecord { return new JolietDirectoryRecord(data, -1); } - protected _getString(idx: number, len: number): string { - return getJolietString(this._data, idx, len); + protected _getString(index: number, length: number): string { + return getJolietString(this._data.slice(index, length)); } } diff --git a/src/entries.ts b/src/entries.ts index 031ba6b..faa8e3c 100644 --- a/src/entries.ts +++ b/src/entries.ts @@ -1,6 +1,7 @@ import { deserialize, struct, types as t, type Tuple } from 'utilium'; import { SLComponentRecord } from './SLComponentRecord.js'; -import { TGetString, getASCIIString, getDate, getShortFormDate } from './utils.js'; +import { getDate, getShortFormDate } from './utils.js'; +import { decode } from '@zenfs/core'; export const enum EntrySignature { CE = 0x4345, @@ -39,7 +40,7 @@ class SystemUseEntry { @t.uint16 public signature!: EntrySignature; public get signatureString(): string { - return getASCIIString(this.data, 0, 2); + return decode(this.data.slice(0, 2)); } @t.uint8 public length!: number; @@ -110,15 +111,16 @@ export class EREntry extends SystemUseEntry { @t.uint8 public extensionVersion!: number; public get extensionIdentifier(): string { - return getASCIIString(this.data, 8, this.idLength); + return decode(this.data.slice(8, 8 + this.idLength)); } public get extensionDescriptor(): string { - return getASCIIString(this.data, 8 + this.idLength, this.descriptorLength); + return decode(this.data.slice(8 + this.idLength, 8 + this.idLength + this.descriptorLength)); } public get extensionSource(): string { - return getASCIIString(this.data, 8 + this.idLength + this.descriptorLength, this.sourceLength); + const start = 8 + this.idLength + this.descriptorLength; + return decode(this.data.slice(start, start + this.sourceLength)); } } @@ -260,8 +262,8 @@ export const enum NMFlags { export class NMEntry extends SystemUseEntry { @t.uint8 public flags!: NMFlags; - public name(getString: TGetString): string { - return getString(this.data, 5, this.length - 5); + public name(getString: (data: Uint8Array) => string): string { + return getString(this.data.slice(5, this.length)); } } @@ -309,7 +311,7 @@ export class TFEntry extends SystemUseEntry { return; } - return this._longFormDates() ? getDate(this.data, 5) : getShortFormDate(this.data, 5); + return this._longFormDates() ? getDate(this.data.slice(5)) : getShortFormDate(this.data.slice(5)); } public get modify(): Date | undefined { @@ -317,7 +319,7 @@ export class TFEntry extends SystemUseEntry { return; } const previousDates = this.flags & TFFlags.CREATION ? 1 : 0; - return this._longFormDates() ? getDate(this.data, 5 + previousDates * 17) : getShortFormDate(this.data, 5 + previousDates * 7); + return this._longFormDates() ? getDate(this.data.slice(5 + previousDates * 17)) : getShortFormDate(this.data.slice(5 + previousDates * 7)); } public get access(): Date | undefined { @@ -326,7 +328,7 @@ export class TFEntry extends SystemUseEntry { } let previousDates = this.flags & TFFlags.CREATION ? 1 : 0; previousDates += this.flags & TFFlags.MODIFY ? 1 : 0; - return this._longFormDates() ? getDate(this.data, 5 + previousDates * 17) : getShortFormDate(this.data, 5 + previousDates * 7); + return this._longFormDates() ? getDate(this.data.slice(5 + previousDates * 17)) : getShortFormDate(this.data.slice(5 + previousDates * 7)); } public get backup(): Date | undefined { @@ -336,7 +338,7 @@ export class TFEntry extends SystemUseEntry { let previousDates = this.flags & TFFlags.CREATION ? 1 : 0; previousDates += this.flags & TFFlags.MODIFY ? 1 : 0; previousDates += this.flags & TFFlags.ACCESS ? 1 : 0; - return this._longFormDates() ? getDate(this.data, 5 + previousDates * 17) : getShortFormDate(this.data, 5 + previousDates * 7); + return this._longFormDates() ? getDate(this.data.slice(5 + previousDates * 17)) : getShortFormDate(this.data.slice(5 + previousDates * 7)); } public get expiration(): Date | undefined { @@ -347,7 +349,7 @@ export class TFEntry extends SystemUseEntry { previousDates += this.flags & TFFlags.MODIFY ? 1 : 0; previousDates += this.flags & TFFlags.ACCESS ? 1 : 0; previousDates += this.flags & TFFlags.BACKUP ? 1 : 0; - return this._longFormDates() ? getDate(this.data, 5 + previousDates * 17) : getShortFormDate(this.data, 5 + previousDates * 7); + return this._longFormDates() ? getDate(this.data.slice(5 + previousDates * 17)) : getShortFormDate(this.data.slice(5 + previousDates * 7)); } public get effective(): Date | undefined { @@ -359,7 +361,7 @@ export class TFEntry extends SystemUseEntry { previousDates += this.flags & TFFlags.ACCESS ? 1 : 0; previousDates += this.flags & TFFlags.BACKUP ? 1 : 0; previousDates += this.flags & TFFlags.EXPIRATION ? 1 : 0; - return this._longFormDates() ? getDate(this.data, 5 + previousDates * 17) : getShortFormDate(this.data, 5 + previousDates * 7); + return this._longFormDates() ? getDate(this.data.slice(5 + previousDates * 17)) : getShortFormDate(this.data.slice(5 + previousDates * 7)); } private _longFormDates(): boolean { diff --git a/src/utils.ts b/src/utils.ts index 6dab747..a8993b0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,43 +1,60 @@ import { decode } from '@zenfs/core/utils.js'; +import { deserialize, struct, types as t } from 'utilium'; -export function getASCIIString(data: Uint8Array, startIndex: number, length: number) { - return decode(data.slice(startIndex, startIndex + length)); -} -export function getJolietString(data: Uint8Array, startIndex: number, length: number): string { - if (length === 1) { +export function getJolietString(data: Uint8Array): string { + if (data.length === 1) { // Special: Root, parent, current directory are still a single byte. - return String.fromCharCode(data[startIndex]); + return String.fromCharCode(data[0]); } // UTF16-BE, which isn't natively supported by Uint8Arrays. // Length should be even, but pessimistically floor just in case. - const pairs = Math.floor(length / 2); + const pairs = Math.floor(data.length / 2); const chars = new Array(pairs); for (let i = 0; i < pairs; i++) { - const pos = startIndex + (i << 1); + const pos = i << 1; chars[i] = String.fromCharCode(data[pos + 1] | (data[pos] << 8)); } return chars.join(''); } -export function getDate(data: Uint8Array, startIndex: number): Date { - const year = parseInt(getASCIIString(data, startIndex, 4), 10); - const mon = parseInt(getASCIIString(data, startIndex + 4, 2), 10); - const day = parseInt(getASCIIString(data, startIndex + 6, 2), 10); - const hour = parseInt(getASCIIString(data, startIndex + 8, 2), 10); - const min = parseInt(getASCIIString(data, startIndex + 10, 2), 10); - const sec = parseInt(getASCIIString(data, startIndex + 12, 2), 10); - const hundrethsSec = parseInt(getASCIIString(data, startIndex + 14, 2), 10); + +export function getDate(data: Uint8Array): Date { + const year = parseInt(decode(data.slice(0, 4))); + const month = parseInt(decode(data.slice(4, 6))); + const day = parseInt(decode(data.slice(6, 8))); + const hour = parseInt(decode(data.slice(8, 10))); + const min = parseInt(decode(data.slice(10, 12))); + const sec = parseInt(decode(data.slice(12, 14))); + const hundrethsSec = parseInt(decode(data.slice(14, 16))); // Last is a time-zone offset, but JavaScript dates don't support time zones well. - return new Date(year, mon, day, hour, min, sec, hundrethsSec * 100); + return new Date(year, month, day, hour, min, sec, hundrethsSec * 100); +} + +@struct() +export class ShortFormDate { + /** + * Years since 1990 + * @todo This may not be the correct size + * @see https://wiki.osdev.org/ISO_9660 + */ + @t.uint8 public year!: number; + @t.uint8 public month!: number; + @t.uint8 public day!: number; + @t.uint8 public hour!: number; + @t.uint8 public minute!: number; + @t.uint8 public second!: number; + + /** + * Note: Timezone is ignored + */ + @t.uint8 public offsetFromGMT!: number; + + public get date(): Date { + return new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second); + } } -export type TGetString = (d: Uint8Array, i: number, len: number) => string; -export function getShortFormDate(data: Uint8Array, startIndex: number): Date { - const yearsSince1900 = data[startIndex]; - const month = data[startIndex + 1]; - const day = data[startIndex + 2]; - const hour = data[startIndex + 3]; - const minute = data[startIndex + 4]; - const second = data[startIndex + 5]; - // JavaScript's Date support isn't so great; ignore timezone. - // const offsetFromGMT = this._data[24]; - return new Date(yearsSince1900, month - 1, day, hour, minute, second); + +export function getShortFormDate(data: Uint8Array): Date { + const date = new ShortFormDate(); + deserialize(date, data); + return date.date; }