完善 Windows 运维封装与 NTFS 索引解析
- 新增自启动幂等配置、统一错误语义、进程等待和进程树终止能力 - 增强服务生命周期管理,支持等待状态、重启、幂等创建和配置更新 - 新增 NTFS 卷索引、文件 ID 解析、文件遍历、USN 变更监听和 bookmark 持久化 - 修复 NTFS boot sector、fragment、MFT、USN 解析边界和路径重建问题 - 补充权限、进程、服务、NTFS 解析和工作流回归测试 - 增加 Windows 测试脚本和管理员 NTFS smoke 验证脚本 - 升级 Go 兼容版本到 1.18,并更新 stario、win32api 及相关间接依赖
This commit is contained in:
+279
-150
@@ -1,14 +1,15 @@
|
||||
/*
|
||||
Package mft provides functions to parse records and their attributes in an NTFS Master File Table ("MFT" for short).
|
||||
Package mft provides functions to parse records and their attributes in an NTFS Master File Table ("MFT" for short).
|
||||
|
||||
Basic usage
|
||||
# Basic usage
|
||||
|
||||
First parse a record using mft.ParseRecord(), which parses the record header and the attribute headers. Then parse
|
||||
each attribute's data individually using the various mft.Parse...() functions.
|
||||
// Error handling left out for brevity
|
||||
record, err := mft.ParseRecord()
|
||||
attrs, err := record.FindAttributes(mft.AttributeTypeFileName)
|
||||
fileName, err := mft.ParseFileName(attrs[0])
|
||||
First parse a record using mft.ParseRecord(), which parses the record header and the attribute headers. Then parse
|
||||
each attribute's data individually using the various mft.Parse...() functions.
|
||||
|
||||
// Error handling left out for brevity
|
||||
record, err := mft.ParseRecord()
|
||||
attrs, err := record.FindAttributes(mft.AttributeTypeFileName)
|
||||
fileName, err := mft.ParseFileName(attrs[0])
|
||||
*/
|
||||
package mft
|
||||
|
||||
@@ -26,7 +27,42 @@ var (
|
||||
fileSignature = []byte{0x46, 0x49, 0x4c, 0x45}
|
||||
)
|
||||
|
||||
const maxInt = int64(^uint(0) >> 1)
|
||||
const (
|
||||
maxInt = int64(^uint(0) >> 1)
|
||||
minRecordHeaderLength = 42
|
||||
minAttributeDataLength = 22
|
||||
minAttributeListHeader = 8
|
||||
minAttributeTypeLength = 4
|
||||
dataRunTerminatorLength = 1
|
||||
)
|
||||
|
||||
type recordHeader struct {
|
||||
signature []byte
|
||||
fileReference FileReference
|
||||
baseRecordReference FileReference
|
||||
logFileSequence uint64
|
||||
hardLinkCount int
|
||||
flags RecordFlag
|
||||
actualSize uint32
|
||||
allocatedSize uint32
|
||||
nextAttributeID int
|
||||
firstAttributeOffset int
|
||||
}
|
||||
|
||||
type attributeHeader struct {
|
||||
attrType AttributeType
|
||||
resident bool
|
||||
name string
|
||||
flags AttributeFlags
|
||||
attributeID int
|
||||
payloadOffset int
|
||||
}
|
||||
|
||||
type attributePayload struct {
|
||||
allocatedSize uint64
|
||||
actualSize uint64
|
||||
data []byte
|
||||
}
|
||||
|
||||
// A Record represents an MFT entry, excluding all technical data (such as "offset to first attribute"). The Attributes
|
||||
// list only contains the attribute headers and raw data; the attribute data has to be parsed separately. When this is a
|
||||
@@ -48,51 +84,68 @@ type Record struct {
|
||||
// ParseRecord parses bytes into a Record after applying fixup. The data is assumed to be in Little Endian order. Only
|
||||
// the attribute headers are parsed, not the actual attribute data.
|
||||
func ParseRecord(b []byte) (Record, error) {
|
||||
if len(b) < 42 {
|
||||
return Record{}, fmt.Errorf("record data length should be at least 42 but is %d", len(b))
|
||||
}
|
||||
sig := b[:4]
|
||||
if bytes.Compare(sig, fileSignature) != 0 {
|
||||
return Record{}, fmt.Errorf("unknown record signature: %# x", sig)
|
||||
}
|
||||
|
||||
b = binutil.Duplicate(b)
|
||||
r := binutil.NewLittleEndianReader(b)
|
||||
baseRecordRef, err := ParseFileReference(r.Read(0x20, 8))
|
||||
header, data, err := parseRecordHeader(b)
|
||||
if err != nil {
|
||||
return Record{}, fmt.Errorf("unable to parse base record reference: %v", err)
|
||||
return Record{}, err
|
||||
}
|
||||
|
||||
firstAttributeOffset := int(r.Uint16(0x14))
|
||||
if firstAttributeOffset < 0 || firstAttributeOffset >= len(b) {
|
||||
return Record{}, fmt.Errorf("invalid first attribute offset %d (data length: %d)", firstAttributeOffset, len(b))
|
||||
}
|
||||
|
||||
updateSequenceOffset := int(r.Uint16(0x04))
|
||||
updateSequenceSize := int(r.Uint16(0x06))
|
||||
b, err = applyFixUp(b, updateSequenceOffset, updateSequenceSize)
|
||||
if err != nil {
|
||||
return Record{}, fmt.Errorf("unable to apply fixup: %v", err)
|
||||
}
|
||||
|
||||
attributes, err := ParseAttributes(b[firstAttributeOffset:])
|
||||
attributes, err := ParseAttributes(data[header.firstAttributeOffset:])
|
||||
if err != nil {
|
||||
return Record{}, err
|
||||
}
|
||||
return Record{
|
||||
Signature: binutil.Duplicate(sig),
|
||||
FileReference: FileReference{RecordNumber: uint64(r.Uint32(0x2C)), SequenceNumber: r.Uint16(0x10)},
|
||||
BaseRecordReference: baseRecordRef,
|
||||
LogFileSequenceNumber: r.Uint64(0x08),
|
||||
HardLinkCount: int(r.Uint16(0x12)),
|
||||
Flags: RecordFlag(r.Uint16(0x16)),
|
||||
ActualSize: r.Uint32(0x18),
|
||||
AllocatedSize: r.Uint32(0x1C),
|
||||
NextAttributeId: int(r.Uint16(0x28)),
|
||||
Signature: header.signature,
|
||||
FileReference: header.fileReference,
|
||||
BaseRecordReference: header.baseRecordReference,
|
||||
LogFileSequenceNumber: header.logFileSequence,
|
||||
HardLinkCount: header.hardLinkCount,
|
||||
Flags: header.flags,
|
||||
ActualSize: header.actualSize,
|
||||
AllocatedSize: header.allocatedSize,
|
||||
NextAttributeId: header.nextAttributeID,
|
||||
Attributes: attributes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseRecordHeader(b []byte) (recordHeader, []byte, error) {
|
||||
if len(b) < minRecordHeaderLength {
|
||||
return recordHeader{}, nil, fmt.Errorf("record data length should be at least %d but is %d", minRecordHeaderLength, len(b))
|
||||
}
|
||||
if !bytes.Equal(b[:4], fileSignature) {
|
||||
return recordHeader{}, nil, fmt.Errorf("unknown record signature: %# x", b[:4])
|
||||
}
|
||||
|
||||
data := binutil.Duplicate(b)
|
||||
r := binutil.NewLittleEndianReader(data)
|
||||
|
||||
baseRecordRef, err := ParseFileReference(r.Read(0x20, 8))
|
||||
if err != nil {
|
||||
return recordHeader{}, nil, fmt.Errorf("unable to parse base record reference: %v", err)
|
||||
}
|
||||
|
||||
firstAttributeOffset := int(r.Uint16(0x14))
|
||||
if firstAttributeOffset < 0 || firstAttributeOffset >= len(data) {
|
||||
return recordHeader{}, nil, fmt.Errorf("invalid first attribute offset %d (data length: %d)", firstAttributeOffset, len(data))
|
||||
}
|
||||
|
||||
if _, err := applyFixUp(data, int(r.Uint16(0x04)), int(r.Uint16(0x06))); err != nil {
|
||||
return recordHeader{}, nil, fmt.Errorf("unable to apply fixup: %v", err)
|
||||
}
|
||||
|
||||
return recordHeader{
|
||||
signature: binutil.Duplicate(data[:4]),
|
||||
fileReference: FileReference{RecordNumber: uint64(r.Uint32(0x2C)), SequenceNumber: r.Uint16(0x10)},
|
||||
baseRecordReference: baseRecordRef,
|
||||
logFileSequence: r.Uint64(0x08),
|
||||
hardLinkCount: int(r.Uint16(0x12)),
|
||||
flags: RecordFlag(r.Uint16(0x16)),
|
||||
actualSize: r.Uint32(0x18),
|
||||
allocatedSize: r.Uint32(0x1C),
|
||||
nextAttributeID: int(r.Uint16(0x28)),
|
||||
firstAttributeOffset: firstAttributeOffset,
|
||||
}, data, nil
|
||||
}
|
||||
|
||||
// A FileReference represents a reference to an MFT record. Since the FileReference in a Record is only 4 bytes, the
|
||||
// RecordNumber will probably not exceed 32 bits.
|
||||
type FileReference struct {
|
||||
@@ -102,10 +155,8 @@ type FileReference struct {
|
||||
|
||||
func (f FileReference) ToUint64() uint64 {
|
||||
origin := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint16(origin, f.SequenceNumber)
|
||||
origin[6] = origin[0]
|
||||
origin[7] = origin[1]
|
||||
binary.LittleEndian.PutUint32(origin, uint32(f.RecordNumber))
|
||||
binary.LittleEndian.PutUint64(origin, f.RecordNumber)
|
||||
binary.LittleEndian.PutUint16(origin[6:], f.SequenceNumber)
|
||||
return binary.LittleEndian.Uint64(origin)
|
||||
}
|
||||
|
||||
@@ -117,7 +168,7 @@ func ParseFileReference(b []byte) (FileReference, error) {
|
||||
}
|
||||
|
||||
return FileReference{
|
||||
RecordNumber: binary.LittleEndian.Uint64(padTo(b[:6], 8)),
|
||||
RecordNumber: binary.LittleEndian.Uint64(padToUnsigned(b[:6], 8)),
|
||||
SequenceNumber: binary.LittleEndian.Uint16(b[6:]),
|
||||
}, nil
|
||||
}
|
||||
@@ -139,19 +190,45 @@ func (f *RecordFlag) Is(c RecordFlag) bool {
|
||||
}
|
||||
|
||||
func applyFixUp(b []byte, offset int, length int) ([]byte, error) {
|
||||
if offset < 0 {
|
||||
return nil, fmt.Errorf("update sequence offset %d is negative", offset)
|
||||
}
|
||||
if length < 2 {
|
||||
return nil, fmt.Errorf("update sequence length %d is too small", length)
|
||||
}
|
||||
updateSequenceLength := length * 2
|
||||
if offset > len(b) || updateSequenceLength > len(b)-offset {
|
||||
return nil, fmt.Errorf("update sequence range [%d:%d] exceeds record length %d", offset, offset+updateSequenceLength, len(b))
|
||||
}
|
||||
|
||||
r := binutil.NewLittleEndianReader(b)
|
||||
|
||||
updateSequence := r.Read(offset, length*2) // length is in pairs, not bytes
|
||||
updateSequence := r.Read(offset, updateSequenceLength) // length is in pairs, not bytes
|
||||
updateSequenceNumber := updateSequence[:2]
|
||||
updateSequenceArray := updateSequence[2:]
|
||||
if len(updateSequenceArray) == 0 || len(updateSequenceArray)%2 != 0 {
|
||||
return nil, fmt.Errorf("invalid update sequence array length %d", len(updateSequenceArray))
|
||||
}
|
||||
|
||||
sectorCount := len(updateSequenceArray) / 2
|
||||
if sectorCount == 0 {
|
||||
return nil, fmt.Errorf("update sequence does not contain any sector entries")
|
||||
}
|
||||
if len(b)%sectorCount != 0 {
|
||||
return nil, fmt.Errorf("record length %d is not divisible by sector count %d", len(b), sectorCount)
|
||||
}
|
||||
sectorSize := len(b) / sectorCount
|
||||
if sectorSize < 2 {
|
||||
return nil, fmt.Errorf("invalid sector size %d", sectorSize)
|
||||
}
|
||||
|
||||
for i := 1; i <= sectorCount; i++ {
|
||||
offset := sectorSize*i - 2
|
||||
if bytes.Compare(updateSequenceNumber, b[offset:offset+2]) != 0 {
|
||||
return nil, fmt.Errorf("update sequence mismatch at pos %d", offset)
|
||||
sectorOffset := sectorSize*i - 2
|
||||
if sectorOffset < 0 || sectorOffset+2 > len(b) {
|
||||
return nil, fmt.Errorf("invalid sector offset %d for record length %d", sectorOffset, len(b))
|
||||
}
|
||||
if !bytes.Equal(updateSequenceNumber, b[sectorOffset:sectorOffset+2]) {
|
||||
return nil, fmt.Errorf("update sequence mismatch at pos %d", sectorOffset)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,99 +314,129 @@ func ParseAttributes(b []byte) ([]Attribute, error) {
|
||||
}
|
||||
attributes := make([]Attribute, 0)
|
||||
for len(b) > 0 {
|
||||
if len(b) < 4 {
|
||||
return nil, fmt.Errorf("attribute header data should be at least 4 bytes but is %d", len(b))
|
||||
recordData, remaining, done, err := nextAttributeRecordData(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := binutil.NewLittleEndianReader(b)
|
||||
attrType := r.Uint32(0)
|
||||
if attrType == uint32(AttributeTypeTerminator) {
|
||||
if done {
|
||||
break
|
||||
}
|
||||
|
||||
if len(b) < 8 {
|
||||
return nil, fmt.Errorf("cannot read attribute header record length, data should be at least 8 bytes but is %d", len(b))
|
||||
}
|
||||
|
||||
uRecordLength := r.Uint32(0x04)
|
||||
if int64(uRecordLength) > maxInt {
|
||||
return nil, fmt.Errorf("record length %d overflows maximum int value %d", uRecordLength, maxInt)
|
||||
}
|
||||
recordLength := int(uRecordLength)
|
||||
if recordLength <= 0 {
|
||||
return nil, fmt.Errorf("cannot handle attribute with zero or negative record length %d", recordLength)
|
||||
}
|
||||
|
||||
if recordLength > len(b) {
|
||||
return nil, fmt.Errorf("attribute record length %d exceeds data length %d", recordLength, len(b))
|
||||
}
|
||||
|
||||
recordData := r.Read(0, recordLength)
|
||||
attribute, err := ParseAttribute(recordData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attributes = append(attributes, attribute)
|
||||
b = r.ReadFrom(recordLength)
|
||||
b = remaining
|
||||
}
|
||||
return attributes, nil
|
||||
}
|
||||
|
||||
func nextAttributeRecordData(b []byte) (recordData []byte, remaining []byte, done bool, err error) {
|
||||
if len(b) < minAttributeTypeLength {
|
||||
return nil, nil, false, fmt.Errorf("attribute header data should be at least %d bytes but is %d", minAttributeTypeLength, len(b))
|
||||
}
|
||||
|
||||
r := binutil.NewLittleEndianReader(b)
|
||||
if AttributeType(r.Uint32(0)) == AttributeTypeTerminator {
|
||||
return nil, nil, true, nil
|
||||
}
|
||||
|
||||
if len(b) < minAttributeListHeader {
|
||||
return nil, nil, false, fmt.Errorf("cannot read attribute header record length, data should be at least %d bytes but is %d", minAttributeListHeader, len(b))
|
||||
}
|
||||
|
||||
uRecordLength := r.Uint32(0x04)
|
||||
if int64(uRecordLength) > maxInt {
|
||||
return nil, nil, false, fmt.Errorf("record length %d overflows maximum int value %d", uRecordLength, maxInt)
|
||||
}
|
||||
recordLength := int(uRecordLength)
|
||||
if recordLength <= 0 {
|
||||
return nil, nil, false, fmt.Errorf("cannot handle attribute with zero or negative record length %d", recordLength)
|
||||
}
|
||||
if recordLength > len(b) {
|
||||
return nil, nil, false, fmt.Errorf("attribute record length %d exceeds data length %d", recordLength, len(b))
|
||||
}
|
||||
return r.Read(0, recordLength), r.ReadFrom(recordLength), false, nil
|
||||
}
|
||||
|
||||
// ParseAttribute parses bytes into an Attribute. The data is assumed to be in Little Endian order. Only the attribute
|
||||
// headers are parsed, not the actual attribute data.
|
||||
func ParseAttribute(b []byte) (Attribute, error) {
|
||||
if len(b) < 22 {
|
||||
return Attribute{}, fmt.Errorf("attribute data should be at least 22 bytes but is %d", len(b))
|
||||
if len(b) < minAttributeDataLength {
|
||||
return Attribute{}, fmt.Errorf("attribute data should be at least %d bytes but is %d", minAttributeDataLength, len(b))
|
||||
}
|
||||
|
||||
r := binutil.NewLittleEndianReader(b)
|
||||
|
||||
nameLength := r.Byte(0x09)
|
||||
nameOffset := r.Uint16(0x0A)
|
||||
|
||||
name := ""
|
||||
if nameLength != 0 {
|
||||
nameBytes := r.Read(int(nameOffset), int(nameLength)*2)
|
||||
name = utf16.DecodeString(nameBytes, binary.LittleEndian)
|
||||
header, err := parseAttributeHeader(r, b)
|
||||
if err != nil {
|
||||
return Attribute{}, err
|
||||
}
|
||||
|
||||
resident := r.Byte(0x08) == 0x00
|
||||
var attributeData []byte
|
||||
actualSize := uint64(0)
|
||||
allocatedSize := uint64(0)
|
||||
if resident {
|
||||
dataOffset := int(r.Uint16(0x14))
|
||||
uDataLength := r.Uint32(0x10)
|
||||
if int64(uDataLength) > maxInt {
|
||||
return Attribute{}, fmt.Errorf("attribute data length %d overflows maximum int value %d", uDataLength, maxInt)
|
||||
}
|
||||
dataLength := int(uDataLength)
|
||||
expectedDataLength := dataOffset + dataLength
|
||||
|
||||
if len(b) < expectedDataLength {
|
||||
return Attribute{}, fmt.Errorf("expected attribute data length to be at least %d but is %d", expectedDataLength, len(b))
|
||||
}
|
||||
|
||||
attributeData = r.Read(dataOffset, dataLength)
|
||||
} else {
|
||||
dataOffset := int(r.Uint16(0x20))
|
||||
if len(b) < dataOffset {
|
||||
return Attribute{}, fmt.Errorf("expected attribute data length to be at least %d but is %d", dataOffset, len(b))
|
||||
}
|
||||
allocatedSize = r.Uint64(0x28)
|
||||
actualSize = r.Uint64(0x30)
|
||||
attributeData = r.ReadFrom(int(dataOffset))
|
||||
payload, err := parseAttributePayload(r, b, header)
|
||||
if err != nil {
|
||||
return Attribute{}, err
|
||||
}
|
||||
|
||||
return Attribute{
|
||||
Type: AttributeType(r.Uint32(0)),
|
||||
Resident: resident,
|
||||
Name: name,
|
||||
Flags: AttributeFlags(r.Uint16(0x0C)),
|
||||
AttributeId: int(r.Uint16(0x0E)),
|
||||
AllocatedSize: allocatedSize,
|
||||
ActualSize: actualSize,
|
||||
Data: binutil.Duplicate(attributeData),
|
||||
Type: header.attrType,
|
||||
Resident: header.resident,
|
||||
Name: header.name,
|
||||
Flags: header.flags,
|
||||
AttributeId: header.attributeID,
|
||||
AllocatedSize: payload.allocatedSize,
|
||||
ActualSize: payload.actualSize,
|
||||
Data: binutil.Duplicate(payload.data),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseAttributeHeader(r *binutil.BinReader, b []byte) (attributeHeader, error) {
|
||||
nameLength := int(r.Byte(0x09))
|
||||
nameOffset := int(r.Uint16(0x0A))
|
||||
name := ""
|
||||
if nameLength != 0 {
|
||||
nameEnd := nameOffset + nameLength*2
|
||||
if len(b) < nameEnd {
|
||||
return attributeHeader{}, fmt.Errorf("expected attribute name length to be at least %d but is %d", nameEnd, len(b))
|
||||
}
|
||||
name = utf16.DecodeString(r.Read(nameOffset, nameLength*2), binary.LittleEndian)
|
||||
}
|
||||
|
||||
resident := r.Byte(0x08) == 0x00
|
||||
payloadOffset := int(r.Uint16(0x20))
|
||||
if resident {
|
||||
payloadOffset = int(r.Uint16(0x14))
|
||||
}
|
||||
|
||||
return attributeHeader{
|
||||
attrType: AttributeType(r.Uint32(0)),
|
||||
resident: resident,
|
||||
name: name,
|
||||
flags: AttributeFlags(r.Uint16(0x0C)),
|
||||
attributeID: int(r.Uint16(0x0E)),
|
||||
payloadOffset: payloadOffset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseAttributePayload(r *binutil.BinReader, b []byte, header attributeHeader) (attributePayload, error) {
|
||||
if header.resident {
|
||||
uDataLength := r.Uint32(0x10)
|
||||
if int64(uDataLength) > maxInt {
|
||||
return attributePayload{}, fmt.Errorf("attribute data length %d overflows maximum int value %d", uDataLength, maxInt)
|
||||
}
|
||||
dataLength := int(uDataLength)
|
||||
expectedDataLength := header.payloadOffset + dataLength
|
||||
if len(b) < expectedDataLength {
|
||||
return attributePayload{}, fmt.Errorf("expected attribute data length to be at least %d but is %d", expectedDataLength, len(b))
|
||||
}
|
||||
return attributePayload{data: r.Read(header.payloadOffset, dataLength)}, nil
|
||||
}
|
||||
|
||||
if len(b) < header.payloadOffset {
|
||||
return attributePayload{}, fmt.Errorf("expected attribute data length to be at least %d but is %d", header.payloadOffset, len(b))
|
||||
}
|
||||
return attributePayload{
|
||||
allocatedSize: r.Uint64(0x28),
|
||||
actualSize: r.Uint64(0x30),
|
||||
data: r.ReadFrom(header.payloadOffset),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -350,38 +457,45 @@ func ParseDataRuns(b []byte) ([]DataRun, error) {
|
||||
|
||||
runs := make([]DataRun, 0)
|
||||
for len(b) > 0 {
|
||||
r := binutil.NewLittleEndianReader(b)
|
||||
header := r.Byte(0)
|
||||
if header == 0 {
|
||||
run, consumed, done, err := parseDataRun(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if done {
|
||||
break
|
||||
}
|
||||
|
||||
lengthLength := int(header &^ 0xF0)
|
||||
offsetLength := int(header >> 4)
|
||||
|
||||
dataRunDataLength := offsetLength + lengthLength
|
||||
|
||||
headerAndDataLength := dataRunDataLength + 1
|
||||
if len(b) < headerAndDataLength {
|
||||
return nil, fmt.Errorf("expected at least %d bytes of datarun data but is %d", headerAndDataLength, len(b))
|
||||
}
|
||||
|
||||
dataRunData := r.Reader(1, dataRunDataLength)
|
||||
|
||||
lengthBytes := dataRunData.Read(0, lengthLength)
|
||||
dataLength := binary.LittleEndian.Uint64(padTo(lengthBytes, 8))
|
||||
|
||||
offsetBytes := dataRunData.Read(lengthLength, offsetLength)
|
||||
dataOffset := int64(binary.LittleEndian.Uint64(padTo(offsetBytes, 8)))
|
||||
|
||||
runs = append(runs, DataRun{OffsetCluster: dataOffset, LengthInClusters: dataLength})
|
||||
|
||||
b = r.ReadFrom(headerAndDataLength)
|
||||
runs = append(runs, run)
|
||||
b = b[consumed:]
|
||||
}
|
||||
|
||||
return runs, nil
|
||||
}
|
||||
|
||||
func parseDataRun(b []byte) (DataRun, int, bool, error) {
|
||||
r := binutil.NewLittleEndianReader(b)
|
||||
header := r.Byte(0)
|
||||
if header == 0 {
|
||||
return DataRun{}, dataRunTerminatorLength, true, nil
|
||||
}
|
||||
|
||||
lengthLength := int(header &^ 0xF0)
|
||||
offsetLength := int(header >> 4)
|
||||
dataRunDataLength := offsetLength + lengthLength
|
||||
headerAndDataLength := dataRunDataLength + dataRunTerminatorLength
|
||||
if len(b) < headerAndDataLength {
|
||||
return DataRun{}, 0, false, fmt.Errorf("expected at least %d bytes of datarun data but is %d", headerAndDataLength, len(b))
|
||||
}
|
||||
|
||||
dataRunData := r.Reader(1, dataRunDataLength)
|
||||
lengthBytes := dataRunData.Read(0, lengthLength)
|
||||
offsetBytes := dataRunData.Read(lengthLength, offsetLength)
|
||||
|
||||
return DataRun{
|
||||
OffsetCluster: int64(binary.LittleEndian.Uint64(padToSigned(offsetBytes, 8))),
|
||||
LengthInClusters: binary.LittleEndian.Uint64(padToUnsigned(lengthBytes, 8)),
|
||||
}, headerAndDataLength, false, nil
|
||||
}
|
||||
|
||||
// DataRunsToFragments transform a list of DataRuns with relative offsets and lengths specified in cluster into a list
|
||||
// of fragment.Fragment elements with absolute offsets and lengths specified in bytes (for example for use in a
|
||||
// fragment.Reader). Note that data will probably not align to a cluster exactly so there could be some padding at the
|
||||
@@ -401,7 +515,7 @@ func DataRunsToFragments(runs []DataRun, bytesPerCluster int) []fragment.Fragmen
|
||||
return frags
|
||||
}
|
||||
|
||||
func padTo(data []byte, length int) []byte {
|
||||
func padToUnsigned(data []byte, length int) []byte {
|
||||
if len(data) > length {
|
||||
return data
|
||||
}
|
||||
@@ -413,7 +527,22 @@ func padTo(data []byte, length int) []byte {
|
||||
return result
|
||||
}
|
||||
copy(result, data)
|
||||
if data[len(data)-1]&0b10000000 == 0b10000000 {
|
||||
return result
|
||||
}
|
||||
|
||||
func padToSigned(data []byte, length int) []byte {
|
||||
if len(data) > length {
|
||||
return data
|
||||
}
|
||||
if len(data) == length {
|
||||
return data
|
||||
}
|
||||
result := make([]byte, length)
|
||||
if len(data) == 0 {
|
||||
return result
|
||||
}
|
||||
copy(result, data)
|
||||
if data[len(data)-1]&0x80 != 0 {
|
||||
for i := len(data); i < length; i++ {
|
||||
result[i] = 0xFF
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user