138 lines
4.5 KiB
Go
138 lines
4.5 KiB
Go
|
|
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
|
||
|
|
}
|