// Package rdpdr implements the client side of the RDP File System Virtual // Channel Extension (MS-RDPEFS, channel name "rdpdr") for drive redirection: // the server sees a file-system device backed by an asynchronous bridge // (browser-picked folder), and enumerates/reads it through device I/O // requests. // // The bridge is asynchronous by necessity (browser filesystem operations are // promise-based): each Device I/O Request is dispatched to the Filesystem // implementation together with its completionId, and the result — or failure — // is fed back via CompleteStatus/CompleteBytes/CompleteJSON, which emit the // matching Device I/O Response with the proper wire encoding. package rdpdr import ( "encoding/binary" "log/slog" "strings" "sync" "git.zeroonesoft.cn/golib/rdplib/core" ) // ── 常量(MS-RDPEFS 2.2,与 FreeRDP channels/rdpdr.h 对齐)──────────────── const ( RDPDR_CTYP_CORE = 0x4472 RDPDR_CTYP_PRN = 0x5052 ) const ( PAKID_CORE_SERVER_ANNOUNCE = 0x496E PAKID_CORE_CLIENTID_CONFIRM = 0x4343 // 双向:客户端 Announce Reply / 服务端 Confirm PAKID_CORE_CLIENT_NAME = 0x434E PAKID_CORE_DEVICELIST_ANNOUNCE = 0x4441 PAKID_CORE_DEVICE_REPLY = 0x6472 PAKID_CORE_DEVICE_IOREQUEST = 0x4952 PAKID_CORE_DEVICE_IOCOMPLETION = 0x4943 PAKID_CORE_SERVER_CAPABILITY = 0x5350 PAKID_CORE_CLIENT_CAPABILITY = 0x4350 PAKID_CORE_DEVICELIST_REMOVE = 0x444D PAKID_CORE_USER_LOGGEDON = 0x554C ) const ( CAP_GENERAL_TYPE = 0x0001 CAP_DRIVE_TYPE = 0x0004 ) // 协议版本与能力位(与 FreeRDP channels/rdpdr.h / rdpdr_capabilities.c 对齐)。 const ( RDPDR_VERSION_MINOR_RDP51 = 0x0005 RDPDR_VERSION_MINOR_RDP10X = 0x000D // 客户端响应版本上限(FreeRDP rdpdr_main.c MIN 上限) // GENERAL capset 的 ExtendedPDU 能力位(MS-RDPEFS 2.2.2.1) RDPDR_DEVICE_REMOVE_PDUS = 0x00000001 RDPDR_CLIENT_DISPLAY_NAME_PDU = 0x00000002 RDPDR_USER_LOGGEDON_PDU = 0x00000004 // GENERAL capset 的 extraFlags1 RDPDR_ENABLE_ASYNCIO = 0x00000001 ) // clientIOCode1 是能力响应里宣告的 IRP major 码位掩码——取 FreeRDP 同款 // 全集(含本实现未细分的 CLEANUP/FLUSH/SHUTDOWN/LOCK/SECURITY 等,这些 // 会以 STATUS_NOT_IMPLEMENTED 兜底完成),与服务端 ioCode1 求交后回给服务端。 const clientIOCode1 = 1< 的 RDPNP 共享映射——访问报"无法访问"且零通道 IO, // 见 doc/history/stage6-plan.md(RDPDR-1/2 章节)。 clientIDConfirmed bool deviceListSent bool fs Filesystem nextFileID uint32 pendingMu sync.Mutex pending map[uint32]*pending // completionId → pending enumMu sync.Mutex enumPos map[uint32]int // FileId → 已返回条目数(目录枚举分页游标) } // clientComputerName 是 CLIENT_NAME_REQUEST 里上报的客户端机器名。取值 // "tsclient" 与 M1 端到端验收通过(2026-09-12 08:25,10.0.0.3)时的取值 // 一致;后改为 "grdpclient" 的会话全部失败。因当时 CLOSE 探测错误会独立 // 导致映射被撤销,两个变量未分离验证,先回退到已知良好值(注释勿 // overclaim:名字的独立影响待 CLOSE 修复验证后再做 A/B)。 const clientComputerName = "tsclient" // NewHandler 创建处理器。shareName 是共享名:DEVICE_ANNOUNCE 的 DosName 取 // 其前 8 字符(FreeRDP 同款),DeviceData 为 ASCII 全名 + NUL——服务端按 // DosName 建 \\tsclient\ 的 UNC 映射。label 是卷标(可含中文)。 func NewHandler(shareName, label string) *Handler { shareName = sanitizeShareName(shareName) return &Handler{ devName: shareName, label: label, versionMinor: RDPDR_VERSION_MINOR_RDP10X, deviceID: 1, // DeviceId 从 1 起(FreeRDP 同款;0 可能与服务端内部路由冲突) nextFileID: 1, pending: make(map[uint32]*pending), enumPos: make(map[uint32]int), } } // SetFilesystem 挂接异步文件系统桥(连接前调用)。 func (h *Handler) SetFilesystem(fs Filesystem) { h.fs = fs } // min16 返回较小者(Go 1.21 前无泛型 min,wasm 目标锁定旧工具链时需要)。 func min16(a, b uint16) uint16 { if a < b { return a } return b } // sanitizeShareName 把共享名中 DEVICE_ANNOUNCE 不允许的字符替换为 '_' //(MS-RDPEFS 2.2.1.3;FreeRDP drive_main.c 同款过滤表,含空格与逗号)。 func sanitizeShareName(s string) string { const forbidden = `\/:*?"<>|, ` + "\t" r := []rune(s) for i, c := range r { if strings.ContainsRune(forbidden, c) { r[i] = '_' } } return string(r) } // SetDeviceID 指定宣告用的 DeviceId(默认 1)。 func (h *Handler) SetDeviceID(id uint32) { h.deviceID = id } // GetType 实现 plugin.ChannelTransport。 func (h *Handler) GetType() (string, uint32) { return "rdpdr", 0x80000000 | 0x40000000 | 0x00800000 // INITIALIZED|ENCRYPT_RDP|COMPRESS_RDP } // Sender 实现 plugin.ChannelTransport。 func (h *Handler) Sender(s core.ChannelSender) { h.channelSender = s } func (h *Handler) send(b []byte) { if h.channelSender == nil { return } if _, err := h.channelSender.SendToChannel("rdpdr", b); err != nil { slog.Warn("rdpdr send", "err", err) } } // Process 实现 plugin.ChannelTransport:分发服务端消息。 func (h *Handler) Process(data []byte) { if len(data) < 4 { return } component := binary.LittleEndian.Uint16(data[0:]) packetID := binary.LittleEndian.Uint16(data[2:]) if component != RDPDR_CTYP_CORE { slog.Debug("rdpdr: non-core component", "component", component, "packetID", packetID) return } switch packetID { case PAKID_CORE_SERVER_ANNOUNCE: h.processServerAnnounce(data) case PAKID_CORE_CLIENTID_CONFIRM: // 服务端回显其接受的版本与 ClientId(FreeRDP 采纳该版本), // 1.0x 语义下设备列表等 USER_LOGGEDON 再发 if len(data) >= 12 { h.versionMajor = binary.LittleEndian.Uint16(data[4:]) h.versionMinor = binary.LittleEndian.Uint16(data[6:]) h.clientID = binary.LittleEndian.Uint32(data[8:]) } slog.Debug("rdpdr: server clientid confirm", "major", h.versionMajor, "minor", h.versionMinor, "clientID", h.clientID) h.clientIDConfirmed = true case PAKID_CORE_USER_LOGGEDON: slog.Debug("rdpdr: user loggedon") if h.clientIDConfirmed && !h.deviceListSent { h.sendDeviceList() } case PAKID_CORE_DEVICE_REPLY: if len(data) >= 12 { slog.Debug("rdpdr: device reply", "deviceID", binary.LittleEndian.Uint32(data[4:]), "result", binary.LittleEndian.Uint32(data[8:])) } case PAKID_CORE_SERVER_CAPABILITY: h.processServerCapability(data) case PAKID_CORE_DEVICE_IOREQUEST: h.processIORequest(data) default: slog.Debug("rdpdr: unhandled", "packetID", packetID, "len", len(data)) } } // ── 握手:announce reply → name request → 设备列表 ─────────────────────── func (h *Handler) processServerAnnounce(data []byte) { if len(data) < 12 { return } h.versionMajor = binary.LittleEndian.Uint16(data[4:]) h.versionMinor = binary.LittleEndian.Uint16(data[6:]) h.clientID = binary.LittleEndian.Uint32(data[8:]) slog.Debug("rdpdr: server announce", "major", h.versionMajor, "minor", h.versionMinor, "clientID", h.clientID) // 客户端响应版本取 min(自身上限, 服务端)(FreeRDP rdpdr_main.c 同款): // major 上限 1,minor 上限 0x000D。旧实现回 1.5(RDP5.1 时代语义)与 // GENERAL 能力集布局错位,均已按 FreeRDP 源码修正。 h.versionMajor = min16(1, h.versionMajor) h.versionMinor = min16(RDPDR_VERSION_MINOR_RDP10X, h.versionMinor) // Client Announce Reply:VersionMajor(2) VersionMinor(2) ClientId(4) b := make([]byte, 12) binary.LittleEndian.PutUint16(b[0:], RDPDR_CTYP_CORE) binary.LittleEndian.PutUint16(b[2:], PAKID_CORE_CLIENTID_CONFIRM) binary.LittleEndian.PutUint16(b[4:], h.versionMajor) binary.LittleEndian.PutUint16(b[6:], h.versionMinor) binary.LittleEndian.PutUint32(b[8:], h.clientID) h.send(b) // Client Name Request:UnicodeFlag(4)=1 CodePage(4)=0 // ComputerNameLen(4,含 NUL) ComputerName(UTF-16LE, NUL 结尾) // 机器名取值依据见 clientComputerName 注释(M1 已知良好值 "tsclient"; // "grdpclient" 变体失败与 CLOSE 探测错误混在一起,未分离归因)。 uname := append(utf16Bytes(clientComputerName), 0, 0) b2 := make([]byte, 16+len(uname)) binary.LittleEndian.PutUint16(b2[0:], RDPDR_CTYP_CORE) binary.LittleEndian.PutUint16(b2[2:], PAKID_CORE_CLIENT_NAME) binary.LittleEndian.PutUint32(b2[4:], 1) // UnicodeFlag binary.LittleEndian.PutUint32(b2[8:], 0) // CodePage binary.LittleEndian.PutUint32(b2[12:], uint32(len(uname))) copy(b2[16:], uname) h.send(b2) // 设备列表在 USER_LOGGEDON(且 CLIENTID_CONFIRM 已到)后发 // (MS-RDPEFS 3.2.5.1.3;提前宣告服务端报 0xC0000001 拒装设备) } func (h *Handler) sendDeviceList() { h.deviceListSent = true // DEVICE_ANNOUNCE{DeviceType(4) DeviceId(4) DosName(8) DeviceDataLength(4) // DeviceData},与 FreeRDP drive_main.c/rdpdr_main.c 逐字节对齐: // DosName = 共享名前 8 字节(短则 NUL 填充,高位字节替换 '_'); // DeviceData = ASCII 全名 + 1 字节 NUL(FreeRDP 同款,Win10 接受; // V02 协商下 DeviceDataLength 为 0 会遭服务端 0xC0000001 拒装)。 dos := []byte(h.devName) if len(dos) > 8 { dos = dos[:8] } for i := range dos { if dos[i] > 0x7F { dos[i] = '_' } } var dosName [8]byte copy(dosName[:], dos) devData := append([]byte(h.devName), 0) // 头 4 + DeviceCount 4 + DeviceType 4 + DeviceId 4 + DosName 8 + // DeviceDataLength 4 = 28,随后 DeviceData b := make([]byte, 28+len(devData)) binary.LittleEndian.PutUint16(b[0:], RDPDR_CTYP_CORE) binary.LittleEndian.PutUint16(b[2:], PAKID_CORE_DEVICELIST_ANNOUNCE) binary.LittleEndian.PutUint32(b[4:], 1) // DeviceCount binary.LittleEndian.PutUint32(b[8:], RDPDR_DTYP_FILESYSTEM) binary.LittleEndian.PutUint32(b[12:], h.deviceID) copy(b[16:], dosName[:]) binary.LittleEndian.PutUint32(b[24:], uint32(len(devData))) copy(b[28:], devData) h.send(b) slog.Debug("rdpdr: device list announced", "name", h.devName, "deviceID", h.deviceID, "devDataLen", len(devData)) } // ── 能力协商 ───────────────────────────────────────────────────────────── func (h *Handler) processServerCapability(data []byte) { // 解析服务端 GENERAL capset,取 ioCode1(能力响应须与之求交) if len(data) >= 12 { // Server Capability:头 8 + numCapabilities(2) + Padding(2),随后 capset 列表 off := 8 for off+8 <= len(data) { typ := binary.LittleEndian.Uint16(data[off:]) length := int(binary.LittleEndian.Uint16(data[off+2:])) if length < 8 || off+length > len(data) { break } if typ == CAP_GENERAL_TYPE && length >= 44 { // capset 内:osType(4) osVersion(4) protoMajor(2) protoMinor(2) // ioCode1(4) ioCode2(4) extendedPDU(4) ... h.serverIOCode1 = binary.LittleEndian.Uint32(data[off+20:]) slog.Debug("rdpdr: server caps", "num", binary.LittleEndian.Uint16(data[4:]), "ioCode1", h.serverIOCode1, "extendedPDU", binary.LittleEndian.Uint32(data[off+28:])) } off += length } } // Client Core Capability Response(FreeRDP rdpdr_capabilities.c 逐字节对齐): // 头 8 + GENERAL 44 + DRIVE 8 = 60。 // 旧实现三处错误会使服务端拒建共享映射:GENERAL 的协议版本误写 4 字节 //(服务端解析成 major=5 minor=0 ioCode1=0)、DRIVE capset 声明 10 写 12、 // ioCode1=0/extendedPDU=4/extraFlags1=0。 b := make([]byte, 8+44+8) binary.LittleEndian.PutUint16(b[0:], RDPDR_CTYP_CORE) binary.LittleEndian.PutUint16(b[2:], PAKID_CORE_CLIENT_CAPABILITY) binary.LittleEndian.PutUint16(b[4:], 2) // numCapabilities binary.LittleEndian.PutUint16(b[6:], 0) // Padding // GENERAL:header(2+2+4) + osType(4) osVersion(4) protoMajor(2) protoMinor(2) // ioCode1(4) ioCode2(4) extendedPDU(4) extraFlags1(4) extraFlags2(4) // specialTypeDeviceCap(4) = 44 binary.LittleEndian.PutUint16(b[8:], CAP_GENERAL_TYPE) binary.LittleEndian.PutUint16(b[10:], 44) binary.LittleEndian.PutUint32(b[12:], 2) // Version = GENERAL_CAPABILITY_VERSION_02 // OsType @16 = 0, OsVersion @20 = 0 binary.LittleEndian.PutUint16(b[24:], h.versionMajor) binary.LittleEndian.PutUint16(b[26:], h.versionMinor) binary.LittleEndian.PutUint32(b[28:], clientIOCode1&h.serverIOCode1) // IoCode2 @32 = 0 binary.LittleEndian.PutUint32(b[36:], RDPDR_DEVICE_REMOVE_PDUS| RDPDR_CLIENT_DISPLAY_NAME_PDU|RDPDR_USER_LOGGEDON_PDU) binary.LittleEndian.PutUint32(b[40:], RDPDR_ENABLE_ASYNCIO) // ExtraFlags2 @44 = 0, SpecialTypeDeviceCap @48 = 0 // DRIVE:仅 8 字节头 {type, CapabilityLength=8, Version=2},无额外字段 binary.LittleEndian.PutUint16(b[52:], CAP_DRIVE_TYPE) binary.LittleEndian.PutUint16(b[54:], 8) binary.LittleEndian.PutUint32(b[56:], 2) h.send(b) slog.Debug("rdpdr: client caps sent") } // ── Device I/O Request 分发 ────────────────────────────────────────────── func (h *Handler) processIORequest(data []byte) { if len(data) < 24 { return } deviceID := binary.LittleEndian.Uint32(data[4:]) fileID := binary.LittleEndian.Uint32(data[8:]) completionID := binary.LittleEndian.Uint32(data[12:]) major := binary.LittleEndian.Uint32(data[16:]) minor := binary.LittleEndian.Uint32(data[20:]) if deviceID != h.deviceID { slog.Warn("rdpdr: io for unknown device", "deviceID", deviceID) return } p := &pending{deviceID: deviceID, fileID: fileID, major: major} slog.Debug("rdpdr: io", "fileID", fileID, "completion", completionID, "major", major, "minor", minor, "len", len(data)) switch major { case IRP_MJ_CREATE: // 头 24 + DesiredAccess(4) AllocationSize(8) FileAttributes(4) // SharedAccess(4) CreateDisposition(4) CreateOptions(4) PathLength(4) = 56 if len(data) < 56 { h.completeStatus(completionID, p, STATUS_INVALID_PARAMETER, nil) return } pathLen := int(binary.LittleEndian.Uint32(data[52:])) path := "" if pathLen > 0 && 56+pathLen <= len(data) { path = utf16ToString(data[56 : 56+pathLen]) } // FileId 由客户端在 CREATE 时分配(服务端请求里的 FileId 为 0), // 后续所有 IO 携带该号,桥接侧以此为句柄键。 p.fileID = h.allocFileID() h.track(completionID, p) if h.fs != nil { h.fs.Open(completionID, p.fileID, path) } else { h.takePending(completionID) h.completeStatus(completionID, p, STATUS_DEVICE_NOT_READY, nil) } case IRP_MJ_READ: if len(data) < 24+4+8 { h.completeStatus(completionID, p, STATUS_INVALID_PARAMETER, nil) return } length := binary.LittleEndian.Uint32(data[24:]) offset := binary.LittleEndian.Uint64(data[28:]) h.track(completionID, p) if h.fs != nil { h.fs.Read(completionID, fileID, offset, length) } case IRP_MJ_CLOSE: h.track(completionID, p) if h.fs != nil { h.fs.Close(completionID, fileID) } case IRP_MJ_DIRECTORY_CONTROL: if minor == IRP_MN_QUERY_DIRECTORY && len(data) >= 56 { p.info = binary.LittleEndian.Uint32(data[24:]) initial := data[28] != 0 if initial { h.enumMu.Lock() h.enumPos[fileID] = 0 h.enumMu.Unlock() } h.track(completionID, p) if h.fs != nil { h.fs.List(completionID, fileID, initial) } } else { // 变更通知:声明不支持,服务端退化为轮询 h.completeStatus(completionID, p, STATUS_NOT_IMPLEMENTED, nil) } case IRP_MJ_QUERY_VOLUME_INFORMATION: if len(data) < 24+4 { h.completeStatus(completionID, p, STATUS_INVALID_PARAMETER, nil) return } p.info = binary.LittleEndian.Uint32(data[24:]) h.track(completionID, p) if h.fs != nil { h.fs.Volume(completionID, p.info) } case IRP_MJ_QUERY_INFORMATION: if len(data) < 24+4 { h.completeStatus(completionID, p, STATUS_INVALID_PARAMETER, nil) return } p.info = binary.LittleEndian.Uint32(data[24:]) h.track(completionID, p) if h.fs != nil { h.fs.Stat(completionID, fileID) } case IRP_MJ_WRITE, IRP_MJ_SET_INFORMATION: // 只读阶段(M1):明确拒绝写路径 h.completeStatus(completionID, p, STATUS_ACCESS_DENIED, nil) case IRP_MJ_DEVICE_CONTROL, IRP_MJ_LOCK_CONTROL: // FreeRDP 的 Discard 语义:未实现的 FSCTL/锁请求以 SUCCESS 空数据 // 完成——NOT_IMPLEMENTED 会让服务端把整个设备标记为"不支持"。 h.completeStatus(completionID, p, STATUS_SUCCESS, nil) default: slog.Debug("rdpdr: unhandled IRP", "major", major) h.completeStatus(completionID, p, STATUS_NOT_IMPLEMENTED, nil) } } func (h *Handler) track(completionID uint32, p *pending) { h.pendingMu.Lock() h.pending[completionID] = p h.pendingMu.Unlock() } func (h *Handler) takePending(completionID uint32) *pending { h.pendingMu.Lock() p := h.pending[completionID] delete(h.pending, completionID) h.pendingMu.Unlock() return p } // ── 完成回调(由异步桥在 wasm 侧调用)──────────────────────────────────── func (h *Handler) completeStatus(completionID uint32, p *pending, status uint32, extra []byte) { if p == nil { p = &pending{} } if p.major == IRP_MJ_CLOSE { h.enumMu.Lock() delete(h.enumPos, p.fileID) h.enumMu.Unlock() // Device Close Response 带固定 5 字节零 Padding(MS-RDPEFS // 2.2.1.4.4;FreeRDP drive_process_irp_close 同款 Stream_Zero(5))。 // 缺 padding 的 16 字节短响应虽在浏览期被服务端容忍,但为与服务端 // 探测解析器逐字节一致(bb998f3 只修正了状态码,长度仍与 FreeRDP // 不同),这里统一补齐。 extra = append(extra, 0, 0, 0, 0, 0) } slog.Debug("rdpdr: complete", "completion", completionID, "major", p.major, "info", p.info, "status", status, "extra", len(extra)) b := make([]byte, 16, 16+len(extra)) binary.LittleEndian.PutUint16(b[0:], RDPDR_CTYP_CORE) binary.LittleEndian.PutUint16(b[2:], PAKID_CORE_DEVICE_IOCOMPLETION) binary.LittleEndian.PutUint32(b[4:], p.deviceID) binary.LittleEndian.PutUint32(b[8:], completionID) binary.LittleEndian.PutUint32(b[12:], status) b = append(b, extra...) h.send(b) } // CompleteStatus 完成一个纯状态响应(CLOSE/错误路径等)。 func (h *Handler) CompleteStatus(completionID uint32, status uint32) { h.completeStatus(completionID, h.takePending(completionID), status, nil) } // CompleteBytes 完成带数据块的响应(READ:Length(4) 前缀 + 数据)。 func (h *Handler) CompleteBytes(completionID uint32, status uint32, data []byte) { p := h.takePending(completionID) extra := make([]byte, 4+len(data)) binary.LittleEndian.PutUint32(extra, uint32(len(data))) copy(extra[4:], data) h.completeStatus(completionID, p, status, extra) } // CompleteJSON 完成结构化响应;kind 决定编码: // - "create" json="dir"|"file":CREATE 响应(FileId+Information(FILE_OPENED)) // - "list" json=[{name,dir,size,mtime,created,accessed}...]:按信息类编码目录条目 // - "stat" json={size,mtime,created,accessed,dir}:QUERY_INFORMATION 响应 // - "volume" json 忽略:QUERY_VOLUME_INFORMATION 响应(卷标取 Handler 配置) // - "status" json 忽略:纯状态响应(CLOSE 等无载荷完成)——CLOSE 必须 // 以 STATUS_SUCCESS 完成,返回错误状态(如 NOT_IMPLEMENTED)会让服务端 // 判定设备异常、撤销 \\tsclient\<名> 映射(表现为"试图访问无效的地址" // 且后续零 IRP,2026-09-12 10.0.0.3 服务端重启后探测序列新增 CLOSE 步骤 // 时暴露)。 // // status != STATUS_SUCCESS 时一律回纯状态响应。 func (h *Handler) CompleteJSON(completionID uint32, status uint32, kind, json string) { p := h.takePending(completionID) if p == nil { // 未知完成号(重复回调/通道重置):回空 pending 的纯状态响应兜底 h.completeStatus(completionID, &pending{}, status, nil) return } if status != STATUS_SUCCESS { h.completeStatus(completionID, p, status, nil) return } switch kind { case "status": h.completeStatus(completionID, p, STATUS_SUCCESS, nil) case "create": h.enumMu.Lock() h.enumPos[p.fileID] = 0 h.enumMu.Unlock() extra := make([]byte, 5) binary.LittleEndian.PutUint32(extra, p.fileID) extra[4] = 1 // Information = FILE_OPENED h.completeStatus(completionID, p, STATUS_SUCCESS, extra) case "list": entries, err := decodeEntries(json) if err != nil { h.completeStatus(completionID, p, STATUS_INVALID_PARAMETER, nil) return } h.enumMu.Lock() pos := h.enumPos[p.fileID] if pos > len(entries) || pos < 0 { pos = 0 // 目录内容变化的兜底 } if pos >= len(entries) { h.enumPos[p.fileID] = 0 h.enumMu.Unlock() h.completeStatus(completionID, p, STATUS_NO_MORE_FILES, nil) return } batch := entries[pos:] h.enumPos[p.fileID] = len(entries) h.enumMu.Unlock() data := encodeDirEntries(p.info, batch) if data == nil { h.completeStatus(completionID, p, STATUS_NOT_IMPLEMENTED, nil) return } extra := make([]byte, 4+len(data)) binary.LittleEndian.PutUint32(extra, uint32(len(data))) copy(extra[4:], data) h.completeStatus(completionID, p, STATUS_SUCCESS, extra) case "stat": e, err := decodeEntry(json) if err != nil { h.completeStatus(completionID, p, STATUS_INVALID_PARAMETER, nil) return } data := encodeFileInfo(p.info, e) if data == nil { h.completeStatus(completionID, p, STATUS_NOT_IMPLEMENTED, nil) return } extra := make([]byte, 4+len(data)) binary.LittleEndian.PutUint32(extra, uint32(len(data))) copy(extra[4:], data) h.completeStatus(completionID, p, STATUS_SUCCESS, extra) case "volume": data := encodeVolumeInfo(p.info, h.label) if data == nil { h.completeStatus(completionID, p, STATUS_NOT_IMPLEMENTED, nil) return } extra := make([]byte, 4+len(data)) binary.LittleEndian.PutUint32(extra, uint32(len(data))) copy(extra[4:], data) h.completeStatus(completionID, p, STATUS_SUCCESS, extra) default: h.completeStatus(completionID, p, STATUS_NOT_IMPLEMENTED, nil) } } // allocFileID 为新 CREATE 分配 FileId(服务端请求中 FileId=0,响应才带号)。 func (h *Handler) allocFileID() uint32 { id := h.nextFileID h.nextFileID++ return id }