feat(ntfs): define bounded attribute reference evidence

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
N0zoM1z0andgensokyo-dev authored and gensokyo-dev committed 2026-11-11 14:10:25 +08:00
1 parent 1e80a96094
commit 428590e9f2
3 files changed
+60 -1

No files matched your search

+2
View File
@@ -3,6 +3,7 @@ import {
ntfsFileNameSchema,
ntfsAttributeSchema,
ntfsAttributeIssueSchema,
ntfsAttributeListResolutionSchema,
} from "./windows/ntfsMftAttributes.js";
import { z } from "zod";
import { MFT_INLINE_HEADER_LIMIT } from "./windows/ntfsMft.js";
@@ -332,6 +333,7 @@ const ntfsRecordHeaderSchema = z.strictObject({
attributeCoverage: z.enum(["complete", "incomplete"]).exactOptional(),
attributeIssue: ntfsAttributeIssueSchema.nullable().exactOptional(),
fileNameCoverage: z.enum(["complete", "incomplete"]).exactOptional(),
attributeListResolution: ntfsAttributeListResolutionSchema.exactOptional(),
fixups: z.strictObject({
sequenceNumber: offset,
entries: offset,
+8 -1
View File
@@ -2,6 +2,7 @@ import {
decodeMftFileReference,
readMftAttributes,
type MftAttributeScan,
type MftAttributeListResolution,
type MftFileReference,
} from "./ntfsMftAttributes.js";
export type { MftFileReference, MftFileName } from "./ntfsMftAttributes.js";
@@ -80,6 +81,8 @@ export interface MftRecordFixups {
}
export interface MftRecord extends MftAttributeScan {
/** Derived links from a base record's attribute lists into this same acquisition. */
readonly attributeListResolution?: MftAttributeListResolution;
/** Zero-based ordinal of this record in the stream. */
readonly index: number;
/** File offset where the record starts. */
@@ -120,7 +123,10 @@ export interface MftInventory {
}
/** Journal joins need identity headers, without retaining every attribute payload. */
export type MftRecordHeader = Omit<MftRecord, keyof MftAttributeScan>;
export type MftRecordHeader = Omit<
MftRecord,
keyof MftAttributeScan | "attributeListResolution"
>;
export interface MftIndex extends MftInventory {
readonly records: readonly MftRecordHeader[];
}
@@ -228,6 +234,7 @@ export const parseMftIndex = (
attributes: _attributes,
attributeCoverage: _coverage,
attributeIssue: _issue,
attributeListResolution: _resolution,
...header
}) => records.push(header),
{ startRecord, recordLimit },
+50
View File
@@ -97,6 +97,56 @@ export const ntfsAttributeSchema = z.strictObject({
]),
});
export type MftAttribute = z.infer<typeof ntfsAttributeSchema>;
/** Per selected record; each additional decoded record is bounded to 64 KiB. */
export const MFT_INLINE_ATTRIBUTE_REFERENCE_LIMIT = 32;
export const ntfsAttributeListResolutionSchema = z.strictObject({
coverage: z.enum(["complete", "incomplete"]),
knownEntryCount: offset,
omittedKnownEntries: offset,
unreadListAttributeOffsets: z.array(offset),
incompleteRecordIndices: z.array(offset),
references: z
.array(
z.strictObject({
listEntryFileOffset: offset,
result: z.discriminatedUnion("kind", [
z.strictObject({
kind: z.literal("resolved"),
attributeFileOffset: offset,
}),
z.strictObject({
kind: z.literal("ambiguous"),
candidateCount: offset,
}),
z.strictObject({
kind: z.literal("unresolved"),
reason: z.enum([
"unverified-base",
"record-unavailable",
"unverified-record",
"sequence-mismatch",
"base-reference-mismatch",
"attribute-unavailable",
"capacity",
]),
}),
]),
}),
)
.max(MFT_INLINE_ATTRIBUTE_REFERENCE_LIMIT),
/** Additional facts only; local physical-record attributes remain unchanged. */
linkedAttributes: z
.array(ntfsAttributeSchema)
.max(MFT_INLINE_ATTRIBUTE_REFERENCE_LIMIT),
linkedFileNames: z
.array(ntfsFileNameSchema)
.max(MFT_INLINE_ATTRIBUTE_REFERENCE_LIMIT),
});
export type MftAttributeListResolution = z.infer<
typeof ntfsAttributeListResolutionSchema
>;
export interface MftAttributeScan {
readonly attributeIssue: AttributeIssue | null;
readonly fileNames: readonly MftFileName[];