429 lines
16 KiB
Go
429 lines
16 KiB
Go
package rdpdr
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/binary"
|
||
"testing"
|
||
)
|
||
|
||
// fakeSender 捕获发往通道的消息。
|
||
type fakeSender struct{ msgs [][]byte }
|
||
|
||
func (f *fakeSender) SendToChannel(ch string, s []byte) (int, error) {
|
||
f.msgs = append(f.msgs, append([]byte(nil), s...))
|
||
return len(s), nil
|
||
}
|
||
|
||
// fakeFS 记录派发并支持手动完成。
|
||
type fakeFS struct {
|
||
h *Handler
|
||
opens []string
|
||
lists int
|
||
complet []uint32
|
||
}
|
||
|
||
func (f *fakeFS) Open(completionID, fileID uint32, path string) {
|
||
f.opens = append(f.opens, path)
|
||
}
|
||
func (f *fakeFS) Read(completionID, fileID uint32, offset uint64, length uint32) {}
|
||
func (f *fakeFS) Close(completionID, fileID uint32) {}
|
||
func (f *fakeFS) List(completionID, fileID uint32, initial bool) {
|
||
f.complet = append(f.complet, completionID)
|
||
f.lists++
|
||
}
|
||
func (f *fakeFS) Stat(completionID, fileID uint32) {}
|
||
func (f *fakeFS) Volume(completionID uint32, infoClass uint32) {}
|
||
|
||
func newTestHandler() (*Handler, *fakeSender, *fakeFS) {
|
||
h := NewHandler("webrdp", "local")
|
||
s := &fakeSender{}
|
||
h.Sender(s)
|
||
fs := &fakeFS{h: h}
|
||
h.SetFilesystem(fs)
|
||
return h, s, fs
|
||
}
|
||
|
||
func TestServerAnnounceFlow(t *testing.T) {
|
||
h, s, _ := newTestHandler()
|
||
// Server Announce:major 1 minor 5 clientID 0x1234
|
||
req := make([]byte, 12)
|
||
binary.LittleEndian.PutUint16(req[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req[2:], PAKID_CORE_SERVER_ANNOUNCE)
|
||
binary.LittleEndian.PutUint16(req[4:], 1)
|
||
binary.LittleEndian.PutUint16(req[6:], 5)
|
||
binary.LittleEndian.PutUint32(req[8:], 0x1234)
|
||
h.Process(req)
|
||
|
||
if len(s.msgs) != 2 {
|
||
t.Fatalf("应发 2 条消息(announce reply/name),实发 %d", len(s.msgs))
|
||
}
|
||
// 1) Client Announce Reply:v1.5 + 原 ClientId
|
||
if got := binary.LittleEndian.Uint16(s.msgs[0][2:]); got != PAKID_CORE_CLIENTID_CONFIRM {
|
||
t.Fatalf("msg0 packetID=%#x", got)
|
||
}
|
||
if binary.LittleEndian.Uint16(s.msgs[0][4:]) != 1 || binary.LittleEndian.Uint16(s.msgs[0][6:]) != 5 {
|
||
t.Fatal("announce reply 版本应为 1.5")
|
||
}
|
||
if binary.LittleEndian.Uint32(s.msgs[0][8:]) != 0x1234 {
|
||
t.Fatal("announce reply 应原样回 ClientId")
|
||
}
|
||
// 2) Client Name Request:UnicodeFlag=1
|
||
if got := binary.LittleEndian.Uint16(s.msgs[1][2:]); got != PAKID_CORE_CLIENT_NAME {
|
||
t.Fatalf("msg1 packetID=%#x", got)
|
||
}
|
||
if binary.LittleEndian.Uint32(s.msgs[1][4:]) != 1 {
|
||
t.Fatal("UnicodeFlag 应为 1")
|
||
}
|
||
|
||
// 服务端 CLIENTID_CONFIRM:仅记录状态,设备列表还不发
|
||
req2 := make([]byte, 8)
|
||
binary.LittleEndian.PutUint16(req2[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req2[2:], PAKID_CORE_CLIENTID_CONFIRM)
|
||
h.Process(req2)
|
||
if len(s.msgs) != 2 {
|
||
t.Fatalf("CLIENTID_CONFIRM 后不应发消息,实发 %d", len(s.msgs))
|
||
}
|
||
// 服务端 USER_LOGGEDON → 此刻才宣告设备列表(MS-RDPEFS 3.2.5.1.3)
|
||
req3 := make([]byte, 8)
|
||
binary.LittleEndian.PutUint16(req3[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req3[2:], PAKID_CORE_USER_LOGGEDON)
|
||
h.Process(req3)
|
||
if len(s.msgs) != 3 {
|
||
t.Fatalf("USER_LOGGEDON 后应发设备列表,实发 %d", len(s.msgs))
|
||
}
|
||
// 3) Device List:count=1, type=FILESYSTEM, DosName "webrdp"
|
||
if got := binary.LittleEndian.Uint16(s.msgs[2][2:]); got != PAKID_CORE_DEVICELIST_ANNOUNCE {
|
||
t.Fatalf("msg2 packetID=%#x", got)
|
||
}
|
||
if binary.LittleEndian.Uint32(s.msgs[2][4:]) != 1 {
|
||
t.Fatal("DeviceCount 应为 1")
|
||
}
|
||
if binary.LittleEndian.Uint32(s.msgs[2][8:]) != RDPDR_DTYP_FILESYSTEM {
|
||
t.Fatal("DeviceType 应为 FILESYSTEM(8)")
|
||
}
|
||
if got := binary.LittleEndian.Uint32(s.msgs[2][12:]); got != 1 {
|
||
t.Fatalf("DeviceId 应为 1(非 0),实 %d", got)
|
||
}
|
||
if name := string(bytes.TrimRight(s.msgs[2][16:24], "\x00")); name != "webrdp" {
|
||
t.Fatalf("DosName=%q", name)
|
||
}
|
||
// V02 协商下 DeviceData = ASCII 全名 + 1 字节 NUL(FreeRDP drive_main.c
|
||
// 同款;服务端 UNC 映射键取 DosName,DeviceData 内容须非空否则拒装)
|
||
if got := len(s.msgs[2]); got != 35 { // 28 + "webrdp" ASCII 6 + NUL 1
|
||
t.Fatalf("设备列表报文应为 35 字节,实 %d", got)
|
||
}
|
||
if got := binary.LittleEndian.Uint32(s.msgs[2][24:]); got != 7 {
|
||
t.Fatalf("DeviceDataLength 应为 7,实 %d", got)
|
||
}
|
||
if !bytes.Equal(s.msgs[2][28:35], []byte("webrdp\x00")) {
|
||
t.Fatalf("DeviceData 应为 ASCII 全名+NUL,实 % x", s.msgs[2][28:35])
|
||
}
|
||
}
|
||
|
||
func TestAnnounceVersionNegotiation(t *testing.T) {
|
||
h, s, _ := newTestHandler()
|
||
// 服务端 1.13(Win10)→ 客户端应回 1.MIN(0x000D, 0x000D)=1.13
|
||
req := make([]byte, 12)
|
||
binary.LittleEndian.PutUint16(req[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req[2:], PAKID_CORE_SERVER_ANNOUNCE)
|
||
binary.LittleEndian.PutUint16(req[4:], 1)
|
||
binary.LittleEndian.PutUint16(req[6:], 0x000D)
|
||
binary.LittleEndian.PutUint32(req[8:], 0x1234)
|
||
h.Process(req)
|
||
if binary.LittleEndian.Uint16(s.msgs[0][4:]) != 1 ||
|
||
binary.LittleEndian.Uint16(s.msgs[0][6:]) != 0x000D {
|
||
t.Fatalf("服务端 1.13 时应回 1.13,实 %d.%d",
|
||
binary.LittleEndian.Uint16(s.msgs[0][4:]),
|
||
binary.LittleEndian.Uint16(s.msgs[0][6:]))
|
||
}
|
||
// 服务端 1.5(老语义)→ 回 1.5
|
||
h2, s2, _ := newTestHandler()
|
||
binary.LittleEndian.PutUint16(req[6:], 5)
|
||
h2.Process(req)
|
||
if binary.LittleEndian.Uint16(s2.msgs[0][6:]) != 5 {
|
||
t.Fatal("服务端 1.5 时应回 1.5")
|
||
}
|
||
}
|
||
|
||
func TestSingleAnnounceAtLogon(t *testing.T) {
|
||
h, s, _ := newTestHandler()
|
||
// Server Announce → Server Caps → ClientID Confirm:1.0x 语义下
|
||
// 均不应发设备列表(FreeRDP:非登录阶段跳过 FS 设备,count=0 不发)
|
||
ann := make([]byte, 12)
|
||
binary.LittleEndian.PutUint16(ann[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(ann[2:], PAKID_CORE_SERVER_ANNOUNCE)
|
||
binary.LittleEndian.PutUint16(ann[4:], 1)
|
||
binary.LittleEndian.PutUint16(ann[6:], 0x000D)
|
||
binary.LittleEndian.PutUint32(ann[8:], 0x1234)
|
||
h.Process(ann)
|
||
caps := make([]byte, 8)
|
||
binary.LittleEndian.PutUint16(caps[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(caps[2:], PAKID_CORE_SERVER_CAPABILITY)
|
||
caps[4] = 0
|
||
h.Process(caps)
|
||
conf := make([]byte, 12)
|
||
binary.LittleEndian.PutUint16(conf[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(conf[2:], PAKID_CORE_CLIENTID_CONFIRM)
|
||
binary.LittleEndian.PutUint16(conf[4:], 1)
|
||
binary.LittleEndian.PutUint16(conf[6:], 0x000D)
|
||
binary.LittleEndian.PutUint32(conf[8:], 0x99) // 服务端回显采纳
|
||
h.Process(conf)
|
||
// msgs: announce reply, name request, caps response——无设备列表
|
||
if n := len(s.msgs); n != 3 {
|
||
t.Fatalf("登录前不应发设备列表(3 条),实 %d", n)
|
||
}
|
||
if h.clientID != 0x99 {
|
||
t.Fatalf("应采纳 Confirm 回显的 ClientId,实 %#x", h.clientID)
|
||
}
|
||
// USER_LOGGEDON:唯一一次设备列表宣告
|
||
logon := make([]byte, 8)
|
||
binary.LittleEndian.PutUint16(logon[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(logon[2:], PAKID_CORE_USER_LOGGEDON)
|
||
h.Process(logon)
|
||
if n := len(s.msgs); n != 4 {
|
||
t.Fatalf("USER_LOGGEDON 后应发设备列表(4 条),实 %d", n)
|
||
}
|
||
if got := binary.LittleEndian.Uint16(s.msgs[3][2:]); got != PAKID_CORE_DEVICELIST_ANNOUNCE {
|
||
t.Fatalf("msg3 应为设备列表,实 %#x", got)
|
||
}
|
||
// 重复 USER_LOGGEDON 不重发
|
||
h.Process(logon)
|
||
if n := len(s.msgs); n != 4 {
|
||
t.Fatalf("重复 USER_LOGGEDON 不应重发,实 %d", n)
|
||
}
|
||
}
|
||
|
||
func TestSanitizeShareName(t *testing.T) {
|
||
if got := sanitizeShareName(`a:b<c>d"e/f\g|h i,j`); got != "a_b_c_d_e_f_g_h_i_j" {
|
||
t.Fatalf("sanitizeShareName=%q", got)
|
||
}
|
||
if got := sanitizeShareName("rdpdrive-test"); got != "rdpdrive-test" {
|
||
t.Fatalf("合法名不应改动,实 %q", got)
|
||
}
|
||
}
|
||
|
||
func TestClientCapabilityResponse(t *testing.T) {
|
||
h, s, _ := newTestHandler()
|
||
// 先握手(服务端 1.13 → 客户端版本 1.13),再发带 GENERAL capset 的
|
||
// 服务端能力请求(ioCode1 = 全部常见 IRP 位)
|
||
ann := make([]byte, 12)
|
||
binary.LittleEndian.PutUint16(ann[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(ann[2:], PAKID_CORE_SERVER_ANNOUNCE)
|
||
binary.LittleEndian.PutUint16(ann[4:], 1)
|
||
binary.LittleEndian.PutUint16(ann[6:], 0x000D)
|
||
binary.LittleEndian.PutUint32(ann[8:], 0x1234)
|
||
h.Process(ann)
|
||
|
||
req := make([]byte, 8+44)
|
||
binary.LittleEndian.PutUint16(req[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req[2:], PAKID_CORE_SERVER_CAPABILITY)
|
||
binary.LittleEndian.PutUint16(req[4:], 1) // numCapabilities
|
||
binary.LittleEndian.PutUint16(req[8:], CAP_GENERAL_TYPE)
|
||
binary.LittleEndian.PutUint16(req[10:], 44)
|
||
binary.LittleEndian.PutUint32(req[12:], 2)
|
||
binary.LittleEndian.PutUint32(req[28:], 0xFFFFFFFF) // ioCode1
|
||
h.Process(req)
|
||
|
||
if len(s.msgs) != 3 || len(s.msgs[2]) != 8+44+8 {
|
||
t.Fatalf("能力响应长度应 60,实 %v", len(s.msgs[2]))
|
||
}
|
||
m := s.msgs[2]
|
||
if binary.LittleEndian.Uint16(m[2:]) != PAKID_CORE_CLIENT_CAPABILITY {
|
||
t.Fatal("packetID 应为 CLIENT_CAPABILITY")
|
||
}
|
||
if binary.LittleEndian.Uint16(m[4:]) != 2 {
|
||
t.Fatal("numCapabilities 应为 2")
|
||
}
|
||
// GENERAL:版本字段各 2 字节(旧实现误写 4 字节,服务端解析错位)
|
||
if binary.LittleEndian.Uint16(m[8:]) != CAP_GENERAL_TYPE || binary.LittleEndian.Uint16(m[10:]) != 44 {
|
||
t.Fatal("GENERAL capset 头错误")
|
||
}
|
||
if binary.LittleEndian.Uint16(m[24:]) != 1 || binary.LittleEndian.Uint16(m[26:]) != 0x000D {
|
||
t.Fatal("GENERAL 协议版本应为 1.13(各 2 字节)")
|
||
}
|
||
// ioCode1 = 客户端掩码 ∩ 服务端掩码
|
||
if got := binary.LittleEndian.Uint32(m[28:]); got != clientIOCode1 {
|
||
t.Fatalf("ioCode1 应为求交结果 %#x,实 %#x", clientIOCode1, got)
|
||
}
|
||
// extendedPDU = REMOVE|DISPLAY_NAME|USER_LOGGEDON;extraFlags1 = ENABLE_ASYNCIO
|
||
if got := binary.LittleEndian.Uint32(m[36:]); got != 7 {
|
||
t.Fatalf("extendedPDU 应为 7,实 %#x", got)
|
||
}
|
||
if got := binary.LittleEndian.Uint32(m[40:]); got != RDPDR_ENABLE_ASYNCIO {
|
||
t.Fatalf("extraFlags1 应为 ENABLE_ASYNCIO,实 %#x", got)
|
||
}
|
||
// DRIVE:仅 8 字节头 {type=4, len=8, version=2}(FreeRDP 同款)
|
||
if binary.LittleEndian.Uint16(m[52:]) != CAP_DRIVE_TYPE || binary.LittleEndian.Uint16(m[54:]) != 8 {
|
||
t.Fatal("DRIVE capset 头错误(FreeRDP 对齐:仅 8 字节头,len=8)")
|
||
}
|
||
if binary.LittleEndian.Uint32(m[56:]) != 2 {
|
||
t.Fatal("DRIVE capset Version 应为 2")
|
||
}
|
||
}
|
||
|
||
func TestCreateDispatchAndComplete(t *testing.T) {
|
||
h, s, fs := newTestHandler()
|
||
|
||
// 构造 CREATE 请求:path "\hello.txt"(FileId 由 handler 分配,预期为 1)
|
||
path := utf16Bytes("\\hello.txt")
|
||
req := make([]byte, 56+len(path))
|
||
binary.LittleEndian.PutUint16(req[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req[2:], PAKID_CORE_DEVICE_IOREQUEST)
|
||
binary.LittleEndian.PutUint32(req[4:], h.deviceID)
|
||
binary.LittleEndian.PutUint32(req[12:], 77) // CompletionId
|
||
binary.LittleEndian.PutUint32(req[16:], IRP_MJ_CREATE)
|
||
binary.LittleEndian.PutUint32(req[52:], uint32(len(path)))
|
||
copy(req[56:], path)
|
||
h.Process(req)
|
||
|
||
if len(fs.opens) != 1 || fs.opens[0] != "\\hello.txt" {
|
||
t.Fatalf("Open 未正确派发: %v", fs.opens)
|
||
}
|
||
|
||
// 完成路径 A:失败 → 纯状态响应
|
||
h.CompleteJSON(77, STATUS_NO_SUCH_FILE, "create", "file")
|
||
if len(s.msgs) != 1 {
|
||
t.Fatalf("失败完成应发 1 条响应,实 %d", len(s.msgs))
|
||
}
|
||
resp := s.msgs[0]
|
||
if binary.LittleEndian.Uint16(resp[2:]) != PAKID_CORE_DEVICE_IOCOMPLETION ||
|
||
binary.LittleEndian.Uint32(resp[8:]) != 77 ||
|
||
binary.LittleEndian.Uint32(resp[12:]) != STATUS_NO_SUCH_FILE {
|
||
t.Fatal("失败 CREATE 响应头错误")
|
||
}
|
||
|
||
// 完成路径 B:成功 → 16 字节头 + FileId(4) + Information(1)
|
||
h.track(78, &pending{deviceID: h.deviceID, fileID: 1, major: IRP_MJ_CREATE})
|
||
h.CompleteJSON(78, STATUS_SUCCESS, "create", "file")
|
||
resp = s.msgs[1]
|
||
if len(resp) != 21 {
|
||
t.Fatalf("成功 CREATE 响应应 21 字节,实 %d", len(resp))
|
||
}
|
||
if binary.LittleEndian.Uint32(resp[16:]) != 1 || resp[20] != 1 {
|
||
t.Fatal("CREATE 响应 FileId/Information 错误")
|
||
}
|
||
}
|
||
|
||
func TestDirectoryListPagination(t *testing.T) {
|
||
h, s, fs := newTestHandler()
|
||
dirID := uint32(1)
|
||
|
||
req := make([]byte, 56)
|
||
binary.LittleEndian.PutUint16(req[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req[2:], PAKID_CORE_DEVICE_IOREQUEST)
|
||
binary.LittleEndian.PutUint32(req[4:], h.deviceID)
|
||
binary.LittleEndian.PutUint32(req[8:], dirID)
|
||
binary.LittleEndian.PutUint32(req[12:], 500)
|
||
binary.LittleEndian.PutUint32(req[16:], IRP_MJ_DIRECTORY_CONTROL)
|
||
binary.LittleEndian.PutUint32(req[20:], IRP_MN_QUERY_DIRECTORY)
|
||
binary.LittleEndian.PutUint32(req[24:], FileBothDirectoryInformation)
|
||
req[28] = 1 // InitialQuery
|
||
h.Process(req)
|
||
|
||
if len(fs.complet) != 1 {
|
||
t.Fatal("List 未派发")
|
||
}
|
||
// 桥接返回 2 条目
|
||
h.CompleteJSON(fs.complet[0], STATUS_SUCCESS, "list",
|
||
`[{"name":"a.txt","size":5,"mtime":1700000000000},{"name":"sub","dir":true,"mtime":1700000000000}]`)
|
||
resp := s.msgs[0]
|
||
if binary.LittleEndian.Uint32(resp[12:]) != STATUS_SUCCESS {
|
||
t.Fatal("首次枚举应成功")
|
||
}
|
||
dataLen := binary.LittleEndian.Uint32(resp[16:])
|
||
data := resp[20:]
|
||
if int(dataLen) != len(data)-0 || dataLen == 0 {
|
||
t.Fatalf("数据长度字段不一致: %d", dataLen)
|
||
}
|
||
// 条目链校验:第一条 NextEntryOffset 指向第二条,第二条为 0
|
||
first := binary.LittleEndian.Uint32(data[0:])
|
||
if first == 0 || int(first)+4 > len(data) {
|
||
t.Fatalf("NextEntryOffset 链错误: %d", first)
|
||
}
|
||
// a.txt 名字长度(UTF-16 字节数)在 class3: FileNameLength @60
|
||
nameLen := binary.LittleEndian.Uint32(data[60:])
|
||
if nameLen != 10 { // "a.txt" 5 chars × 2
|
||
t.Fatalf("FileNameLength=%d", nameLen)
|
||
}
|
||
second := binary.LittleEndian.Uint32(data[first:])
|
||
if second != 0 {
|
||
t.Fatalf("末条 NextEntryOffset 应为 0,实 %d", second)
|
||
}
|
||
|
||
// 续枚举(InitialQuery=0):桥接再次返回全量,游标已到末尾 → NO_MORE_FILES
|
||
req[28] = 0
|
||
h.Process(req)
|
||
if len(fs.complet) != 2 {
|
||
t.Fatal("续枚举 List 未派发")
|
||
}
|
||
h.CompleteJSON(fs.complet[1], STATUS_SUCCESS, "list",
|
||
`[{"name":"a.txt","size":5,"mtime":1700000000000},{"name":"sub","dir":true,"mtime":1700000000000}]`)
|
||
resp = s.msgs[1]
|
||
if binary.LittleEndian.Uint32(resp[12:]) != STATUS_NO_MORE_FILES {
|
||
t.Fatalf("续枚举应返回 NO_MORE_FILES,实 %#x", binary.LittleEndian.Uint32(resp[12:]))
|
||
}
|
||
}
|
||
|
||
func TestReadCompletionEncoding(t *testing.T) {
|
||
h, s, _ := newTestHandler()
|
||
h.track(9, &pending{deviceID: h.deviceID, fileID: 3, major: IRP_MJ_READ})
|
||
h.CompleteBytes(9, STATUS_SUCCESS, []byte("hello"))
|
||
resp := s.msgs[0]
|
||
if len(resp) != 16+4+5 {
|
||
t.Fatalf("READ 响应应 25 字节,实 %d", len(resp))
|
||
}
|
||
if binary.LittleEndian.Uint32(resp[16:]) != 5 {
|
||
t.Fatal("Length 前缀应为 5")
|
||
}
|
||
if !bytes.Equal(resp[20:], []byte("hello")) {
|
||
t.Fatal("数据内容不一致")
|
||
}
|
||
}
|
||
|
||
func TestWriteDeniedInReadOnlyMode(t *testing.T) {
|
||
h, s, _ := newTestHandler()
|
||
req := make([]byte, 24)
|
||
binary.LittleEndian.PutUint16(req[0:], RDPDR_CTYP_CORE)
|
||
binary.LittleEndian.PutUint16(req[2:], PAKID_CORE_DEVICE_IOREQUEST)
|
||
binary.LittleEndian.PutUint32(req[4:], h.deviceID)
|
||
binary.LittleEndian.PutUint32(req[12:], 42)
|
||
binary.LittleEndian.PutUint32(req[16:], IRP_MJ_WRITE)
|
||
h.Process(req)
|
||
if len(s.msgs) != 1 {
|
||
t.Fatal("WRITE 应直接拒绝")
|
||
}
|
||
if binary.LittleEndian.Uint32(s.msgs[0][12:]) != STATUS_ACCESS_DENIED {
|
||
t.Fatal("WRITE 应回 ACCESS_DENIED")
|
||
}
|
||
}
|
||
|
||
func TestDirEntryEncodingLayouts(t *testing.T) {
|
||
entries := []DirEntry{{Name: "x", Dir: true, Size: 0, Mtime: 1700000000000}}
|
||
// 单条即末条:NextEntryOffset=0,无对齐填充
|
||
if d := encodeDirEntries(FileDirectoryInformation, entries); len(d) != 64+2 {
|
||
t.Fatalf("class1 单条应为 66,实 %d", len(d))
|
||
}
|
||
if d := encodeDirEntries(FileBothDirectoryInformation, entries); len(d) != 93+2 {
|
||
t.Fatalf("class3 单条应为 95,实 %d", len(d))
|
||
}
|
||
if d := encodeDirEntries(FileNamesInformation, entries); len(d) != 12+2 {
|
||
t.Fatalf("names 单条应为 14,实 %d", len(d))
|
||
}
|
||
if d := encodeDirEntries(0x99, entries); d != nil {
|
||
t.Fatal("未知信息类应返回 nil")
|
||
}
|
||
// 非末条需 4 字节对齐:两条 class1("x"=2 字节名)→ 68 + 66
|
||
two := []DirEntry{{Name: "x", Mtime: 1}, {Name: "y", Mtime: 1}}
|
||
if d := encodeDirEntries(FileDirectoryInformation, two); len(d) != 68+66 {
|
||
t.Fatalf("class1 两条应 68+66,实 %d", len(d))
|
||
}
|
||
// FileBasicInformation 40 字节
|
||
if d := encodeFileInfo(FileBasicInformation, entries[0]); len(d) != 40 {
|
||
t.Fatalf("FileBasicInformation 应 40 字节,实 %d", len(d))
|
||
}
|
||
// FileFsSizeInformation 24 字节
|
||
if d := encodeVolumeInfo(FileFsSizeInformation, "local"); len(d) != 24 {
|
||
t.Fatalf("FileFsSizeInformation 应 24 字节,实 %d", len(d))
|
||
}
|
||
}
|