package mft import ( "encoding/binary" "testing" "time" "unicode/utf16" ) func TestConvertFileTime(t *testing.T) { tests := []struct { name string value uint64 want time.Time }{ { name: "epoch", value: 0, want: time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC), }, { name: "one second", value: 10000000, want: time.Date(1601, time.January, 1, 0, 0, 1, 0, time.UTC), }, } for _, tt := range tests { got := ConvertFileTime(tt.value) if !got.Equal(tt.want) { t.Fatalf("%s: ConvertFileTime(%d) = %v, want %v", tt.name, tt.value, got, tt.want) } } } func TestFileAttributeConstants(t *testing.T) { if FileAttributeCompressed == FileAttributeOffline { t.Fatal("FileAttributeCompressed and FileAttributeOffline should differ") } } func TestParseAttributeListParsesNamedEntry(t *testing.T) { baseRef := FileReference{RecordNumber: 33, SequenceNumber: 2} entries, err := ParseAttributeList(buildAttributeListEntry(AttributeTypeData, "alt", 7, baseRef, 9)) if err != nil { t.Fatalf("ParseAttributeList returned error: %v", err) } if len(entries) != 1 { t.Fatalf("len(entries) = %d, want 1", len(entries)) } if entries[0].Name != "alt" { t.Fatalf("entries[0].Name = %q, want %q", entries[0].Name, "alt") } if entries[0].BaseRecordReference != baseRef { t.Fatalf("entries[0].BaseRecordReference = %+v, want %+v", entries[0].BaseRecordReference, baseRef) } if entries[0].StartingVCN != 7 { t.Fatalf("entries[0].StartingVCN = %d, want 7", entries[0].StartingVCN) } } func TestParseIndexRootParsesSingleEntry(t *testing.T) { fileRef := FileReference{RecordNumber: 51, SequenceNumber: 4} fileNameData := testFileNameData("hello.txt", FileReference{RecordNumber: 9, SequenceNumber: 1}.ToUint64(), FileNameNamespaceWin32) root, err := ParseIndexRoot(buildIndexRoot(buildIndexEntry(fileRef, fileNameData, 0, 0))) if err != nil { t.Fatalf("ParseIndexRoot returned error: %v", err) } if len(root.Entries) != 1 { t.Fatalf("len(root.Entries) = %d, want 1", len(root.Entries)) } if root.Entries[0].FileReference != fileRef { t.Fatalf("root.Entries[0].FileReference = %+v, want %+v", root.Entries[0].FileReference, fileRef) } if root.Entries[0].FileName.Name != "hello.txt" { t.Fatalf("root.Entries[0].FileName.Name = %q, want %q", root.Entries[0].FileName.Name, "hello.txt") } } func buildAttributeListEntry(attrType AttributeType, name string, startingVCN uint64, baseRef FileReference, attrID uint16) []byte { encodedName := utf16.Encode([]rune(name)) entryLength := minAttributeListEntryLength + len(encodedName)*2 buf := make([]byte, entryLength) binary.LittleEndian.PutUint32(buf[0x00:], uint32(attrType)) binary.LittleEndian.PutUint16(buf[0x04:], uint16(entryLength)) buf[0x06] = byte(len(encodedName)) if len(encodedName) > 0 { buf[0x07] = 0x1A for i, v := range encodedName { binary.LittleEndian.PutUint16(buf[0x1A+i*2:], v) } } binary.LittleEndian.PutUint64(buf[0x08:], startingVCN) copy(buf[0x10:], encodeRawFileReference(baseRef)) binary.LittleEndian.PutUint16(buf[0x18:], attrID) return buf } func buildIndexEntry(fileRef FileReference, fileNameData []byte, flags uint32, subNodeVCN uint64) []byte { entryLength := 0x10 + len(fileNameData) if flags&0b1 != 0 { entryLength += 8 } buf := make([]byte, entryLength) copy(buf[0x00:], encodeRawFileReference(fileRef)) binary.LittleEndian.PutUint16(buf[0x08:], uint16(entryLength)) binary.LittleEndian.PutUint16(buf[0x0A:], uint16(len(fileNameData))) binary.LittleEndian.PutUint32(buf[0x0C:], flags) copy(buf[0x10:], fileNameData) if flags&0b1 != 0 { binary.LittleEndian.PutUint64(buf[entryLength-8:], subNodeVCN) } return buf } func buildIndexRoot(entry []byte) []byte { totalSize := indexRootHeaderLength + len(entry) buf := make([]byte, indexRootEntryOffset+len(entry)) binary.LittleEndian.PutUint32(buf[0x00:], uint32(AttributeTypeFileName)) binary.LittleEndian.PutUint32(buf[0x04:], uint32(CollationTypeFileName)) binary.LittleEndian.PutUint32(buf[0x08:], 4096) binary.LittleEndian.PutUint32(buf[0x0C:], 1) binary.LittleEndian.PutUint32(buf[0x10:], 0x10) binary.LittleEndian.PutUint32(buf[0x14:], uint32(totalSize)) binary.LittleEndian.PutUint32(buf[0x18:], uint32(totalSize)) copy(buf[indexRootEntryOffset:], entry) return buf } func encodeRawFileReference(ref FileReference) []byte { buf := make([]byte, 8) rawRecord := make([]byte, 8) binary.LittleEndian.PutUint64(rawRecord, ref.RecordNumber) copy(buf[:6], rawRecord[:6]) binary.LittleEndian.PutUint16(buf[6:], ref.SequenceNumber) return buf }