package pdu import ( "bytes" "encoding/binary" "errors" "fmt" "io" "log/slog" "sync" "github.com/lunixbochs/struc" "git.zeroonesoft.cn/golib/rdplib/core" ) // capBuffPool pools bytes.Buffer instances used to serialize individual // capability structures inside DemandActivePDU and ConfirmActivePDU. // Each Serialize call borrows one buffer and returns it when done. var capBuffPool = sync.Pool{ New: func() any { return &bytes.Buffer{} }, } // nscPlaneBufPool reuses byte slices for the intermediate YCoCg planes in // decodeNSCodec. The planes are local to the decode call and released before // the function returns, so pool re-use is safe. var nscPlaneBufPool = sync.Pool{ New: func() any { return []byte(nil) }, } func acquireNSCPlaneBuf(size int) []byte { b := nscPlaneBufPool.Get().([]byte) if cap(b) >= size { return b[:size] } return make([]byte, size) } func releaseNSCPlaneBuf(b []byte) { if b != nil { nscPlaneBufPool.Put(b[:cap(b)]) } } // DecodeRemoteFX is a pluggable decoder for RemoteFX (MS-RDPRFX) surface codec // data. It is set at init time by the main client package to avoid a circular // import between protocol/pdu and plugin/rdpgfx. // The function receives raw RFX data and returns top-down BGRA pixels. var DecodeRemoteFX func(data []byte, width, height int) []byte const ( PDUTYPE_DEMANDACTIVEPDU = 0x11 PDUTYPE_CONFIRMACTIVEPDU = 0x13 PDUTYPE_DEACTIVATEALLPDU = 0x16 PDUTYPE_DATAPDU = 0x17 PDUTYPE_SERVER_REDIR_PKT = 0x1A ) type PduType2 uint8 const ( PDUTYPE2_UPDATE = 0x02 PDUTYPE2_CONTROL = 0x14 PDUTYPE2_POINTER = 0x1B PDUTYPE2_INPUT = 0x1C PDUTYPE2_SYNCHRONIZE = 0x1F PDUTYPE2_REFRESH_RECT = 0x21 PDUTYPE2_PLAY_SOUND = 0x22 PDUTYPE2_SUPPRESS_OUTPUT = 0x23 PDUTYPE2_SHUTDOWN_REQUEST = 0x24 PDUTYPE2_SHUTDOWN_DENIED = 0x25 PDUTYPE2_SAVE_SESSION_INFO = 0x26 PDUTYPE2_FONTLIST = 0x27 PDUTYPE2_FONTMAP = 0x28 PDUTYPE2_SET_KEYBOARD_INDICATORS = 0x29 PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST = 0x2B PDUTYPE2_BITMAPCACHE_ERROR_PDU = 0x2C PDUTYPE2_SET_KEYBOARD_IME_STATUS = 0x2D PDUTYPE2_OFFSCRCACHE_ERROR_PDU = 0x2E PDUTYPE2_SET_ERROR_INFO_PDU = 0x2F PDUTYPE2_DRAWNINEGRID_ERROR_PDU = 0x30 PDUTYPE2_DRAWGDIPLUS_ERROR_PDU = 0x31 PDUTYPE2_ARC_STATUS_PDU = 0x32 PDUTYPE2_STATUS_INFO_PDU = 0x36 PDUTYPE2_MONITOR_LAYOUT_PDU = 0x37 PDUTYPE2_FRAME_ACKNOWLEDGE = 0x38 ) // Slow-Path Pointer Update types (MS-RDPBCGR 2.2.9.1.1.4) const ( TS_PTRUPDATE_TYPE_SYSTEM = 0x0001 TS_PTRUPDATE_TYPE_POSITION = 0x0003 TS_PTRUPDATE_TYPE_COLOR = 0x0006 TS_PTRUPDATE_TYPE_CACHED = 0x0007 TS_PTRUPDATE_TYPE_POINTER = 0x0008 ) func (p PduType2) String() string { switch p { case PDUTYPE2_UPDATE: return "PDUTYPE2_UPDATE" case PDUTYPE2_CONTROL: return "PDUTYPE2_CONTROL" case PDUTYPE2_POINTER: return "PDUTYPE2_POINTER" case PDUTYPE2_INPUT: return "PDUTYPE2_INPUT" case PDUTYPE2_SYNCHRONIZE: return "PDUTYPE2_SYNCHRONIZE" case PDUTYPE2_REFRESH_RECT: return "PDUTYPE2_REFRESH_RECT" case PDUTYPE2_PLAY_SOUND: return "PDUTYPE2_PLAY_SOUND" case PDUTYPE2_SUPPRESS_OUTPUT: return "PDUTYPE2_SUPPRESS_OUTPUT" case PDUTYPE2_SHUTDOWN_REQUEST: return "PDUTYPE2_SHUTDOWN_REQUEST" case PDUTYPE2_SHUTDOWN_DENIED: return "PDUTYPE2_SHUTDOWN_DENIED" case PDUTYPE2_SAVE_SESSION_INFO: return "PDUTYPE2_SAVE_SESSION_INFO" case PDUTYPE2_FONTLIST: return "PDUTYPE2_FONTLIST" case PDUTYPE2_FONTMAP: return "PDUTYPE2_FONTMAP" case PDUTYPE2_SET_KEYBOARD_INDICATORS: return "PDUTYPE2_SET_KEYBOARD_INDICATORS" case PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST: return "PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST" case PDUTYPE2_BITMAPCACHE_ERROR_PDU: return "PDUTYPE2_BITMAPCACHE_ERROR_PDU" case PDUTYPE2_SET_KEYBOARD_IME_STATUS: return "PDUTYPE2_SET_KEYBOARD_IME_STATUS" case PDUTYPE2_OFFSCRCACHE_ERROR_PDU: return "PDUTYPE2_OFFSCRCACHE_ERROR_PDU" case PDUTYPE2_SET_ERROR_INFO_PDU: return "PDUTYPE2_SET_ERROR_INFO_PDU" case PDUTYPE2_DRAWNINEGRID_ERROR_PDU: return "PDUTYPE2_DRAWNINEGRID_ERROR_PDU" case PDUTYPE2_DRAWGDIPLUS_ERROR_PDU: return "PDUTYPE2_DRAWGDIPLUS_ERROR_PDU" case PDUTYPE2_ARC_STATUS_PDU: return "PDUTYPE2_ARC_STATUS_PDU" case PDUTYPE2_STATUS_INFO_PDU: return "PDUTYPE2_STATUS_INFO_PDU" case PDUTYPE2_MONITOR_LAYOUT_PDU: return "PDUTYPE2_MONITOR_LAYOUT_PDU" } return "Unknown" } const ( CTRLACTION_REQUEST_CONTROL = 0x0001 CTRLACTION_GRANTED_CONTROL = 0x0002 CTRLACTION_DETACH = 0x0003 CTRLACTION_COOPERATE = 0x0004 ) const ( STREAM_UNDEFINED = 0x00 STREAM_LOW = 0x01 STREAM_MED = 0x02 STREAM_HI = 0x04 ) type FastPathUpdateType uint8 const ( FASTPATH_UPDATETYPE_ORDERS = 0x0 FASTPATH_UPDATETYPE_BITMAP = 0x1 FASTPATH_UPDATETYPE_PALETTE = 0x2 FASTPATH_UPDATETYPE_SYNCHRONIZE = 0x3 FASTPATH_UPDATETYPE_SURFCMDS = 0x4 FASTPATH_UPDATETYPE_PTR_NULL = 0x5 FASTPATH_UPDATETYPE_PTR_DEFAULT = 0x6 FASTPATH_UPDATETYPE_PTR_POSITION = 0x8 FASTPATH_UPDATETYPE_COLOR = 0x9 FASTPATH_UPDATETYPE_CACHED = 0xA FASTPATH_UPDATETYPE_POINTER = 0xB FASTPATH_UPDATETYPE_LARGE_POINTER = 0xC ) func (t FastPathUpdateType) String() string { switch t { case FASTPATH_UPDATETYPE_ORDERS: return "FASTPATH_UPDATETYPE_ORDERS" case FASTPATH_UPDATETYPE_BITMAP: return "FASTPATH_UPDATETYPE_BITMAP" case FASTPATH_UPDATETYPE_PALETTE: return "FASTPATH_UPDATETYPE_PALETTE" case FASTPATH_UPDATETYPE_SYNCHRONIZE: return "FASTPATH_UPDATETYPE_SYNCHRONIZE" case FASTPATH_UPDATETYPE_SURFCMDS: return "FASTPATH_UPDATETYPE_SURFCMDS" case FASTPATH_UPDATETYPE_PTR_NULL: return "FASTPATH_UPDATETYPE_PTR_NULL" case FASTPATH_UPDATETYPE_PTR_DEFAULT: return "FASTPATH_UPDATETYPE_PTR_DEFAULT" case FASTPATH_UPDATETYPE_PTR_POSITION: return "FASTPATH_UPDATETYPE_PTR_POSITION" case FASTPATH_UPDATETYPE_COLOR: return "FASTPATH_UPDATETYPE_COLOR" case FASTPATH_UPDATETYPE_CACHED: return "FASTPATH_UPDATETYPE_CACHED" case FASTPATH_UPDATETYPE_POINTER: return "FASTPATH_UPDATETYPE_POINTER" case FASTPATH_UPDATETYPE_LARGE_POINTER: return "FASTPATH_UPDATETYPE_LARGE_POINTER" } return "Unknown" } const ( BITMAP_COMPRESSION = 0x0001 //NO_BITMAP_COMPRESSION_HDR = 0x0400 BITMAP_NO_PROCESSING = 0x8000 // Surface command: data is already decoded top-down BGRA ) // Surface Command types (MS-RDPBCGR 2.2.9.1.2.1) const ( CMDTYPE_SET_SURFACE_BITS = 0x0001 CMDTYPE_FRAME_MARKER = 0x0004 CMDTYPE_STREAM_SURFACE_BITS = 0x0006 ) const ( SURFCMD_FRAMEACTION_BEGIN = 0x0000 SURFCMD_FRAMEACTION_END = 0x0001 ) /* compression types */ const ( RDP_MPPC_BIG = 0x01 RDP_MPPC_COMPRESSED = 0x20 RDP_MPPC_RESET = 0x40 RDP_MPPC_FLUSH = 0x80 RDP_MPPC_DICT_SIZE = 65536 ) type ShareDataHeader struct { SharedId uint32 `struc:"little"` Padding1 uint8 `struc:"little"` StreamId uint8 `struc:"little"` UncompressedLength uint16 `struc:"little"` PDUType2 uint8 `struc:"little"` CompressedType uint8 `struc:"little"` CompressedLength uint16 `struc:"little"` } func NewShareDataHeader(size int, type2 uint8, shareId uint32) *ShareDataHeader { return &ShareDataHeader{ SharedId: shareId, PDUType2: type2, StreamId: STREAM_LOW, UncompressedLength: uint16(size + 4), } } type PDUMessage interface { Type() uint16 Serialize() []byte } type DemandActivePDU struct { SharedId uint32 `struc:"little"` LengthSourceDescriptor uint16 `struc:"little,sizeof=SourceDescriptor"` LengthCombinedCapabilities uint16 `struc:"little"` SourceDescriptor []byte `struc:"sizefrom=LengthSourceDescriptor"` NumberCapabilities uint16 `struc:"little,sizeof=CapabilitySets"` Pad2Octets uint16 `struc:"little"` CapabilitySets []Capability `struc:"sizefrom=NumberCapabilities"` SessionId uint32 `struc:"little"` } func (d *DemandActivePDU) Type() uint16 { return PDUTYPE_DEMANDACTIVEPDU } func (d *DemandActivePDU) Serialize() []byte { buff := &bytes.Buffer{} core.WriteUInt32LE(d.SharedId, buff) core.WriteUInt16LE(d.LengthSourceDescriptor, buff) core.WriteUInt16LE(d.LengthCombinedCapabilities, buff) core.WriteBytes([]byte(d.SourceDescriptor), buff) core.WriteUInt16LE(uint16(len(d.CapabilitySets)), buff) core.WriteUInt16LE(d.Pad2Octets, buff) capBuff := capBuffPool.Get().(*bytes.Buffer) for _, cap := range d.CapabilitySets { core.WriteUInt16LE(uint16(cap.Type()), buff) capBuff.Reset() struc.Pack(capBuff, cap) capBytes := capBuff.Bytes() core.WriteUInt16LE(uint16(len(capBytes)+4), buff) core.WriteBytes(capBytes, buff) } capBuffPool.Put(capBuff) core.WriteUInt32LE(d.SessionId, buff) return buff.Bytes() } func readDemandActivePDU(r io.Reader) (*DemandActivePDU, error) { d := &DemandActivePDU{} var err error d.SharedId, err = core.ReadUInt32LE(r) if err != nil { return nil, err } d.LengthSourceDescriptor, err = core.ReadUint16LE(r) d.LengthCombinedCapabilities, err = core.ReadUint16LE(r) sourceDescriptorBytes, err := core.ReadBytes(int(d.LengthSourceDescriptor), r) if err != nil { return nil, err } d.SourceDescriptor = sourceDescriptorBytes d.NumberCapabilities, err = core.ReadUint16LE(r) d.Pad2Octets, err = core.ReadUint16LE(r) d.CapabilitySets = make([]Capability, 0, d.NumberCapabilities) for i := 0; i < int(d.NumberCapabilities); i++ { c, err := readCapability(r) if err != nil { //return nil, err continue } d.CapabilitySets = append(d.CapabilitySets, c) } d.NumberCapabilities = uint16(len(d.CapabilitySets)) d.SessionId, err = core.ReadUInt32LE(r) if err != nil { return nil, err } return d, nil } type ConfirmActivePDU struct { SharedId uint32 `struc:"little"` OriginatorId uint16 `struc:"little"` LengthSourceDescriptor uint16 `struc:"little,sizeof=SourceDescriptor"` LengthCombinedCapabilities uint16 `struc:"little"` SourceDescriptor []byte `struc:"sizefrom=LengthSourceDescriptor"` NumberCapabilities uint16 `struc:"little,sizeof=CapabilitySets"` Pad2Octets uint16 `struc:"little"` CapabilitySets []Capability `struc:"sizefrom=NumberCapabilities"` } func (*ConfirmActivePDU) Type() uint16 { return PDUTYPE_CONFIRMACTIVEPDU } func (c *ConfirmActivePDU) Serialize() []byte { buff := &bytes.Buffer{} core.WriteUInt32LE(c.SharedId, buff) core.WriteUInt16LE(c.OriginatorId, buff) core.WriteUInt16LE(uint16(len(c.SourceDescriptor)), buff) capsBuff := &bytes.Buffer{} capBuff := capBuffPool.Get().(*bytes.Buffer) for _, capa := range c.CapabilitySets { core.WriteUInt16LE(uint16(capa.Type()), capsBuff) capBuff.Reset() struc.Pack(capBuff, capa) capBytes := capBuff.Bytes() core.WriteUInt16LE(uint16(len(capBytes)+4), capsBuff) core.WriteBytes(capBytes, capsBuff) } capBuffPool.Put(capBuff) capsBytes := capsBuff.Bytes() core.WriteUInt16LE(uint16(2+2+len(capsBytes)), buff) core.WriteBytes(c.SourceDescriptor, buff) core.WriteUInt16LE(c.NumberCapabilities, buff) core.WriteUInt16LE(c.Pad2Octets, buff) core.WriteBytes(capsBytes, buff) return buff.Bytes() } // 9401 => share control header // 1300 => share control header // ec03 => share control header // ea030100 => shareId 66538 // ea03 => OriginatorId // 0400 // 8001 => LengthCombinedCapabilities // 72647079 // 0c00 => NumberCapabilities 12 // 0000 // caps below // 010018000100030000020000000015040000000000000000 // 02001c00180001000100010000052003000000000100000001000000 // 030058000000000000000000000000000000000000000000010014000000010000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000008403000000000000000000 // 04002800000000000000000000000000000000000000000000000000000000000000000000000000 // 0800080000001400 // 0c00080000000000 // 0d005c001500000009040000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000 // 0f00080000000000 // 10003400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 // 11000c000000000000000000 // 14000c000000000000000000 // 1a00080000000000 func NewConfirmActivePDU() *ConfirmActivePDU { return &ConfirmActivePDU{ OriginatorId: 0x03EA, CapabilitySets: make([]Capability, 0), SourceDescriptor: []byte("rdpy"), } } func readConfirmActivePDU(r io.Reader) (*ConfirmActivePDU, error) { p := &ConfirmActivePDU{} var err error p.SharedId, err = core.ReadUInt32LE(r) if err != nil { return nil, err } p.OriginatorId, err = core.ReadUint16LE(r) p.LengthSourceDescriptor, err = core.ReadUint16LE(r) p.LengthCombinedCapabilities, err = core.ReadUint16LE(r) sourceDescriptorBytes, err := core.ReadBytes(int(p.LengthSourceDescriptor), r) if err != nil { return nil, err } p.SourceDescriptor = sourceDescriptorBytes p.NumberCapabilities, err = core.ReadUint16LE(r) p.Pad2Octets, err = core.ReadUint16LE(r) p.CapabilitySets = make([]Capability, 0, p.NumberCapabilities) for i := 0; i < int(p.NumberCapabilities); i++ { c, err := readCapability(r) if err != nil { return nil, err } p.CapabilitySets = append(p.CapabilitySets, c) } s, _ := core.ReadUInt32LE(r) slog.Debug("readConfirmActivePDU", "sessionid", s) return p, nil } type DeactiveAllPDU struct { ShareId uint32 `struc:"little"` LengthSourceDescriptor uint16 `struc:"little,sizeof=SourceDescriptor"` SourceDescriptor []byte } func (*DeactiveAllPDU) Type() uint16 { return PDUTYPE_DEACTIVATEALLPDU } func (d *DeactiveAllPDU) Serialize() []byte { buff := &bytes.Buffer{} struc.Pack(buff, d) return buff.Bytes() } func readDeactiveAllPDU(r io.Reader) (*DeactiveAllPDU, error) { p := &DeactiveAllPDU{} err := struc.Unpack(r, p) return p, err } // ServerRedirectionPDU represents the RDP Server Redirection PDU // (MS-RDPBCGR 2.2.13.2.1). Only the LoadBalanceInfo field (routing // token) is extracted; other optional fields are skipped. type ServerRedirectionPDU struct { Flags uint16 Length uint16 SessionID uint32 RedirFlags uint32 LoadBalanceInfo []byte } const ( LB_TARGET_NET_ADDRESS = 0x00000001 LB_LOAD_BALANCE_INFO = 0x00000002 LB_USERNAME = 0x00000004 ) func (*ServerRedirectionPDU) Type() uint16 { return PDUTYPE_SERVER_REDIR_PKT } func (d *ServerRedirectionPDU) Serialize() []byte { return nil } func readServerRedirectionPDU(r io.Reader) (*ServerRedirectionPDU, error) { // Enhanced Security variant has a 2-byte pad before the PDU body if _, err := core.ReadUint16LE(r); err != nil { return nil, fmt.Errorf("redir: read pad: %w", err) } redir := &ServerRedirectionPDU{} var err error if redir.Flags, err = core.ReadUint16LE(r); err != nil { return nil, fmt.Errorf("redir: read flags: %w", err) } if redir.Length, err = core.ReadUint16LE(r); err != nil { return nil, fmt.Errorf("redir: read length: %w", err) } if redir.SessionID, err = core.ReadUInt32LE(r); err != nil { return nil, fmt.Errorf("redir: read sessionID: %w", err) } if redir.RedirFlags, err = core.ReadUInt32LE(r); err != nil { return nil, fmt.Errorf("redir: read redirFlags: %w", err) } // Parse variable-length fields in flag order. // We only need LoadBalanceInfo (routing token) for reconnection. if redir.RedirFlags&LB_TARGET_NET_ADDRESS != 0 { cbLen, err := core.ReadUInt32LE(r) if err != nil { return nil, fmt.Errorf("redir: read targetNetAddr len: %w", err) } if _, err := core.ReadBytes(int(cbLen), r); err != nil { return nil, fmt.Errorf("redir: read targetNetAddr: %w", err) } } if redir.RedirFlags&LB_LOAD_BALANCE_INFO != 0 { cbLen, err := core.ReadUInt32LE(r) if err != nil { return nil, fmt.Errorf("redir: read loadBalanceInfo len: %w", err) } redir.LoadBalanceInfo, err = core.ReadBytes(int(cbLen), r) if err != nil { return nil, fmt.Errorf("redir: read loadBalanceInfo: %w", err) } } slog.Debug("Server Redirection PDU", "flags", redir.Flags, "sessionID", redir.SessionID, "redirFlags", redir.RedirFlags, "loadBalanceInfo", string(redir.LoadBalanceInfo)) return redir, nil } type DataPDU struct { Header *ShareDataHeader Data DataPDUData } func (*DataPDU) Type() uint16 { return PDUTYPE_DATAPDU } func (d *DataPDU) Serialize() []byte { buff := &bytes.Buffer{} struc.Pack(buff, d.Header) struc.Pack(buff, d.Data) return buff.Bytes() } func NewDataPDU(data DataPDUData, shareId uint32) *DataPDU { dataLen, err := struc.Sizeof(data) if err != nil { // Fallback: pack to measure length dataBuff := &bytes.Buffer{} struc.Pack(dataBuff, data) dataLen = dataBuff.Len() } return &DataPDU{ Header: NewShareDataHeader(dataLen, data.Type2(), shareId), Data: data, } } func readDataPDU(r io.Reader, mppc *core.MppcDecompressor) (*DataPDU, error) { header := &ShareDataHeader{} err := struc.Unpack(r, header) if err != nil { slog.Error("readDataPDU", "err", err) return nil, err } // Decompress the payload when the server has compressed it. if header.CompressedType != 0 && mppc != nil { if header.CompressedType&RDP_MPPC_COMPRESSED != 0 { compressed, err := core.ReadBytes(int(header.CompressedLength), r) if err != nil { slog.Error("readDataPDU: reading compressed payload", "err", err) return nil, err } decompressed, err := mppc.Decompress(header.CompressedType, compressed) if err != nil { slog.Error("readDataPDU: MPPC decompression failed", "err", err) return nil, err } r = bytes.NewReader(decompressed) } else { // CompressedType set but not COMPRESSED: update history only. plain, _ := core.ReadBytes(int(header.CompressedLength), r) _, _ = mppc.Decompress(header.CompressedType, plain) r = bytes.NewReader(plain) } } var d DataPDUData slog.Debug("readDataPDU", "PDUTYPE2", header.PDUType2) switch header.PDUType2 { case PDUTYPE2_UPDATE: d = &UpdateDataPDU{} case PDUTYPE2_SYNCHRONIZE: d = &SynchronizeDataPDU{} case PDUTYPE2_CONTROL: d = &ControlDataPDU{} case PDUTYPE2_FONTLIST: d = &FontListDataPDU{} case PDUTYPE2_SET_ERROR_INFO_PDU: d = &ErrorInfoDataPDU{} case PDUTYPE2_FONTMAP: d = &FontMapDataPDU{} case PDUTYPE2_SAVE_SESSION_INFO: d = &SaveSessionInfo{} case PDUTYPE2_POINTER: d = &PointerDataPDU{} case PDUTYPE2_SET_KEYBOARD_INDICATORS: d = &SetKeyboardIndicatorsDataPDU{} default: err = fmt.Errorf("Unknown data pdu type2 0x%02x", header.PDUType2) slog.Error("readDataPDU", "err", err) return nil, err } err = d.Unpack(r) if err != nil { slog.Error("readDataPDU", "err", err) return nil, err } p := &DataPDU{ Header: header, Data: d, } return p, nil } type DataPDUData interface { Type2() uint8 Unpack(io.Reader) error } type UpdateDataPDU struct { UpdateType uint16 Udata UpdateData } func (*UpdateDataPDU) Type2() uint8 { return PDUTYPE2_UPDATE } func (d *UpdateDataPDU) Unpack(r io.Reader) (err error) { //slow path update d.UpdateType, err = core.ReadUint16LE(r) slog.Debug("FastPathUpdate", "type", d.UpdateType) var p UpdateData switch d.UpdateType { case FASTPATH_UPDATETYPE_ORDERS: case FASTPATH_UPDATETYPE_BITMAP: p = &BitmapUpdateDataPDU{} case FASTPATH_UPDATETYPE_PALETTE: case FASTPATH_UPDATETYPE_SYNCHRONIZE: } if p != nil { err = p.Unpack(r) if err != nil { //slog.Error("Unpack:", err) return err } } else { return fmt.Errorf("Unsupport slow update type 0x%x", d.UpdateType) } d.Udata = p return nil } // PointerDataPDU handles slow-path pointer updates (MS-RDPBCGR 2.2.9.1.1.4) type PointerDataPDU struct { MessageType uint16 Pad2Octets uint16 Pdata UpdateData } func (*PointerDataPDU) Type2() uint8 { return PDUTYPE2_POINTER } func (d *PointerDataPDU) Unpack(r io.Reader) error { var err error d.MessageType, err = core.ReadUint16LE(r) if err != nil { return err } d.Pad2Octets, err = core.ReadUint16LE(r) if err != nil { return err } slog.Debug("PointerDataPDU", "messageType", d.MessageType) var p UpdateData switch d.MessageType { case TS_PTRUPDATE_TYPE_CACHED: p = &FastPathUpdateCachedPDU{} case TS_PTRUPDATE_TYPE_POINTER: p = &FastPathUpdatePointerPDU{} case TS_PTRUPDATE_TYPE_SYSTEM, TS_PTRUPDATE_TYPE_POSITION, TS_PTRUPDATE_TYPE_COLOR: // not yet parsed; remaining data is discarded by the caller default: slog.Debug("PointerDataPDU: unhandled", "messageType", d.MessageType) } if p != nil { if err = p.Unpack(r); err != nil { return err } } d.Pdata = p return nil } func (d *PointerDataPDU) Serialize() []byte { return nil } type BitmapUpdateDataPDU struct { NumberRectangles uint16 `struc:"little,sizeof=Rectangles"` Rectangles []BitmapData } func (*BitmapUpdateDataPDU) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_BITMAP } func (f *BitmapUpdateDataPDU) Unpack(r io.Reader) error { var err error f.NumberRectangles, err = core.ReadUint16LE(r) if err != nil { return err } if f.NumberRectangles > 4096 { return fmt.Errorf("implausible rectangle count %d", f.NumberRectangles) } f.Rectangles = make([]BitmapData, 0, f.NumberRectangles) for i := 0; i < int(f.NumberRectangles); i++ { rect := BitmapData{} rect.DestLeft, err = core.ReadUint16LE(r) if err != nil { return err } rect.DestTop, err = core.ReadUint16LE(r) if err != nil { return err } rect.DestRight, err = core.ReadUint16LE(r) if err != nil { return err } rect.DestBottom, err = core.ReadUint16LE(r) if err != nil { return err } rect.Width, err = core.ReadUint16LE(r) if err != nil { return err } rect.Height, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitsPerPixel, err = core.ReadUint16LE(r) if err != nil { return err } rect.Flags, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapLength, err = core.ReadUint16LE(r) if err != nil { return err } ln := rect.BitmapLength if rect.Flags&BITMAP_COMPRESSION != 0 && (rect.Flags&NO_BITMAP_COMPRESSION_HDR == 0) { rect.BitmapComprHdr = new(BitmapCompressedDataHeader) rect.BitmapComprHdr.CbCompFirstRowSize, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapComprHdr.CbCompMainBodySize, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapComprHdr.CbScanWidth, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapComprHdr.CbUncompressedSize, err = core.ReadUint16LE(r) if err != nil { return err } ln = rect.BitmapComprHdr.CbCompMainBodySize } rect.BitmapDataStream, err = core.ReadBytes(int(ln), r) if err != nil { return err } f.Rectangles = append(f.Rectangles, rect) } return nil } type SynchronizeDataPDU struct { MessageType uint16 `struc:"little"` TargetUser uint16 `struc:"little"` } func (*SynchronizeDataPDU) Type2() uint8 { return PDUTYPE2_SYNCHRONIZE } func NewSynchronizeDataPDU(targetUser uint16) *SynchronizeDataPDU { return &SynchronizeDataPDU{ MessageType: 1, TargetUser: targetUser, } } func (d *SynchronizeDataPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } type ControlDataPDU struct { Action uint16 `struc:"little"` GrantId uint16 `struc:"little"` ControlId uint32 `struc:"little"` } func (*ControlDataPDU) Type2() uint8 { return PDUTYPE2_CONTROL } func (d *ControlDataPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } type FontListDataPDU struct { NumberFonts uint16 `struc:"little"` TotalNumFonts uint16 `struc:"little"` ListFlags uint16 `struc:"little"` EntrySize uint16 `struc:"little"` } func (*FontListDataPDU) Type2() uint8 { return PDUTYPE2_FONTLIST } func (d *FontListDataPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } type ErrorInfoDataPDU struct { ErrorInfo uint32 `struc:"little"` } func (*ErrorInfoDataPDU) Type2() uint8 { return PDUTYPE2_SET_ERROR_INFO_PDU } func (d *ErrorInfoDataPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } type FontMapDataPDU struct { NumberEntries uint16 `struc:"little"` TotalNumEntries uint16 `struc:"little"` MapFlags uint16 `struc:"little"` EntrySize uint16 `struc:"little"` } func (*FontMapDataPDU) Type2() uint8 { return PDUTYPE2_FONTMAP } func (d *FontMapDataPDU) Unpack(r io.Reader) error { err := struc.Unpack(r, d) // MS-RDPBCGR 2.2.1.22.1: Font Map payload fields are optional. // VirtualBox sends a short FontMap PDU with no payload data. if err == io.EOF || err == io.ErrUnexpectedEOF { return nil } return err } // SetKeyboardIndicatorsDataPDU sets the state of keyboard indicator LEDs. // MS-RDPBCGR 2.2.8.2.1.3.3.1 type SetKeyboardIndicatorsDataPDU struct { UnitId uint16 `struc:"little"` LedFlags uint16 `struc:"little"` } func (*SetKeyboardIndicatorsDataPDU) Type2() uint8 { return PDUTYPE2_SET_KEYBOARD_INDICATORS } func (d *SetKeyboardIndicatorsDataPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } // SuppressOutputPDU tells the server to start/stop sending display updates. // MS-RDPBCGR 2.2.11.3.1 type SuppressOutputPDU struct { AllowDisplayUpdates uint8 `struc:"little"` Pad3Octets [3]byte `struc:"little"` Left uint16 `struc:"little"` Top uint16 `struc:"little"` Right uint16 `struc:"little"` Bottom uint16 `struc:"little"` } func (*SuppressOutputPDU) Type2() uint8 { return PDUTYPE2_SUPPRESS_OUTPUT } func (d *SuppressOutputPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } // FrameAcknowledgeDataPDU acknowledges receipt of a frame (MS-RDPBCGR 2.2.11.3.2). type FrameAcknowledgeDataPDU struct { FrameID uint32 `struc:"little"` } func (*FrameAcknowledgeDataPDU) Type2() uint8 { return PDUTYPE2_FRAME_ACKNOWLEDGE } func (d *FrameAcknowledgeDataPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } // RefreshRectPDU requests the server to redraw one or more screen regions. // MS-RDPBCGR 2.2.11.2 type RefreshRectPDU struct { NumberOfAreas uint8 `struc:"little"` Pad3Octets [3]byte `struc:"little"` Left uint16 `struc:"little"` Top uint16 `struc:"little"` Right uint16 `struc:"little"` Bottom uint16 `struc:"little"` } func (*RefreshRectPDU) Type2() uint8 { return PDUTYPE2_REFRESH_RECT } func (d *RefreshRectPDU) Unpack(r io.Reader) error { return struc.Unpack(r, d) } type InfoType uint32 const ( INFOTYPE_LOGON = 0x00000000 INFOTYPE_LOGON_LONG = 0x00000001 INFOTYPE_LOGON_PLAINNOTIFY = 0x00000002 INFOTYPE_LOGON_EXTENDED_INFO = 0x00000003 ) const ( LOGON_EX_AUTORECONNECTCOOKIE = 0x00000001 LOGON_EX_LOGONERRORS = 0x00000002 ) type LogonFields struct { CbFileData uint32 `struc:"little"` Len uint32 //28 `struc:"little"` Version uint32 // 1 `struc:"little"` LogonId uint32 `struc:"little"` random [16]byte //16 `struc:"little"` } type SaveSessionInfo struct { InfoType uint32 Length uint16 FieldsPresent uint32 LogonId uint32 Random []byte } func (s *SaveSessionInfo) logonInfoV1(r io.Reader) (err error) { core.ReadUInt32LE(r) // cbDomain b, _ := core.ReadBytes(52, r) domain := core.UnicodeDecode(b) core.ReadUInt32LE(r) // cbUserName b, _ = core.ReadBytes(512, r) userName := core.UnicodeDecode(b) sessionId, _ := core.ReadUInt32LE(r) s.LogonId = sessionId slog.Debug("logonInfo", "sessionId", s.LogonId, "userName", userName, "domain", domain) return err } func (s *SaveSessionInfo) logonInfoV2(r io.Reader) (err error) { core.ReadUint16LE(r) core.ReadUInt32LE(r) sessionId, _ := core.ReadUInt32LE(r) s.LogonId = sessionId cbDomain, _ := core.ReadUInt32LE(r) cbUserName, _ := core.ReadUInt32LE(r) core.ReadBytes(558, r) b, _ := core.ReadBytes(int(cbDomain), r) domain := core.UnicodeDecode(b) b, _ = core.ReadBytes(int(cbUserName), r) userName := core.UnicodeDecode(b) slog.Debug("logonInfoV2", "sessionId", s.LogonId, "userName", userName, "domain", domain) return err } func (s *SaveSessionInfo) logonPlainNotify(r io.Reader) (err error) { core.ReadBytes(576, r) /* pad (576 bytes) */ return err } func (s *SaveSessionInfo) logonInfoExtended(r io.Reader) (err error) { s.Length, err = core.ReadUint16LE(r) s.FieldsPresent, err = core.ReadUInt32LE(r) //slog.Debug("FieldsPresent:", s.FieldsPresent) // auto reconnect cookie if s.FieldsPresent&LOGON_EX_AUTORECONNECTCOOKIE != 0 { core.ReadUInt32LE(r) b, _ := core.ReadUInt32LE(r) if b != 28 { return errors.New("invalid length in Auto-Reconnect packet") } b, _ = core.ReadUInt32LE(r) if b != 1 { return errors.New("unsupported version of Auto-Reconnect packet") } b, _ = core.ReadUInt32LE(r) s.LogonId = b s.Random, _ = core.ReadBytes(16, r) } else { // logon error info core.ReadUInt32LE(r) b, _ := core.ReadUInt32LE(r) b, _ = core.ReadUInt32LE(r) s.LogonId = b } core.ReadBytes(570, r) return err } func (s *SaveSessionInfo) Unpack(r io.Reader) (err error) { s.InfoType, err = core.ReadUInt32LE(r) switch s.InfoType { case INFOTYPE_LOGON: err = s.logonInfoV1(r) case INFOTYPE_LOGON_LONG: err = s.logonInfoV2(r) case INFOTYPE_LOGON_PLAINNOTIFY: err = s.logonPlainNotify(r) case INFOTYPE_LOGON_EXTENDED_INFO: err = s.logonInfoExtended(r) default: return fmt.Errorf("Unhandled saveSessionInfo type 0x%x", s.InfoType) } return err } func (*SaveSessionInfo) Type2() uint8 { return PDUTYPE2_SAVE_SESSION_INFO } type PersistKeyPDU struct { NumEntriesCache0 uint16 `struc:"little"` NumEntriesCache1 uint16 `struc:"little"` NumEntriesCache2 uint16 `struc:"little"` NumEntriesCache3 uint16 `struc:"little"` NumEntriesCache4 uint16 `struc:"little"` TotalEntriesCache0 uint16 `struc:"little"` TotalEntriesCache1 uint16 `struc:"little"` TotalEntriesCache2 uint16 `struc:"little"` TotalEntriesCache3 uint16 `struc:"little"` TotalEntriesCache4 uint16 `struc:"little"` BBitMask uint8 `struc:"little"` Pad1 uint8 `struc:"little"` Ppad3 uint16 `struc:"little"` } func (*PersistKeyPDU) Type2() uint8 { return PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST } type UpdateData interface { FastPathUpdateType() uint8 Unpack(io.Reader) error } type BitmapCompressedDataHeader struct { CbCompFirstRowSize uint16 `struc:"little"` CbCompMainBodySize uint16 `struc:"little"` CbScanWidth uint16 `struc:"little"` CbUncompressedSize uint16 `struc:"little"` } type BitmapData struct { DestLeft uint16 `struc:"little"` DestTop uint16 `struc:"little"` DestRight uint16 `struc:"little"` DestBottom uint16 `struc:"little"` Width uint16 `struc:"little"` Height uint16 `struc:"little"` BitsPerPixel uint16 `struc:"little"` Flags uint16 `struc:"little"` BitmapLength uint16 `struc:"little,sizeof=BitmapDataStream"` BitmapComprHdr *BitmapCompressedDataHeader BitmapDataStream []byte } func (b *BitmapData) IsCompress() bool { return b.Flags&BITMAP_COMPRESSION != 0 } type FastPathBitmapUpdateDataPDU struct { Header uint16 `struc:"little"` NumberRectangles uint16 `struc:"little,sizeof=Rectangles"` Rectangles []BitmapData } func (f *FastPathBitmapUpdateDataPDU) Unpack(r io.Reader) error { var err error f.Header, err = core.ReadUint16LE(r) if err != nil { return err } f.NumberRectangles, err = core.ReadUint16LE(r) if err != nil { return err } // 矩形数受载荷物理限制(每矩形至少 18 字节);超限即流已错位, // 直接拒绝而不是按垃圾矩形继续解析。 if f.NumberRectangles > 4096 { return fmt.Errorf("implausible rectangle count %d", f.NumberRectangles) } f.Rectangles = make([]BitmapData, 0, f.NumberRectangles) for i := 0; i < int(f.NumberRectangles); i++ { rect := BitmapData{} rect.DestLeft, err = core.ReadUint16LE(r) if err != nil { return err } rect.DestTop, err = core.ReadUint16LE(r) if err != nil { return err } rect.DestRight, err = core.ReadUint16LE(r) if err != nil { return err } rect.DestBottom, err = core.ReadUint16LE(r) if err != nil { return err } rect.Width, err = core.ReadUint16LE(r) if err != nil { return err } rect.Height, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitsPerPixel, err = core.ReadUint16LE(r) if err != nil { return err } rect.Flags, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapLength, err = core.ReadUint16LE(r) if err != nil { return err } ln := rect.BitmapLength if rect.Flags&BITMAP_COMPRESSION != 0 && (rect.Flags&NO_BITMAP_COMPRESSION_HDR == 0) { rect.BitmapComprHdr = new(BitmapCompressedDataHeader) rect.BitmapComprHdr.CbCompFirstRowSize, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapComprHdr.CbCompMainBodySize, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapComprHdr.CbScanWidth, err = core.ReadUint16LE(r) if err != nil { return err } rect.BitmapComprHdr.CbUncompressedSize, err = core.ReadUint16LE(r) if err != nil { return err } ln = rect.BitmapComprHdr.CbCompMainBodySize } rect.BitmapDataStream, err = core.ReadBytes(int(ln), r) if err != nil { return err } f.Rectangles = append(f.Rectangles, rect) } return nil } func (*FastPathBitmapUpdateDataPDU) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_BITMAP } type FastPathColorPdu struct { CacheIdx uint16 X uint16 Y uint16 Width uint16 Height uint16 MaskLen uint16 `struc:"little,sizeof=Mask"` DataLen uint16 `struc:"little,sizeof=Data"` Mask []byte Data []byte } func (*FastPathColorPdu) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_COLOR } func (f *FastPathColorPdu) Unpack(r io.Reader) error { return struc.Unpack(r, f) } type FastPathSurfaceCmds struct { Rects []BitmapData } func (*FastPathSurfaceCmds) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_SURFCMDS } func (f *FastPathSurfaceCmds) Unpack(r io.Reader) error { // This won't be called; Surface Commands are handled directly in RecvFastPath. return nil } // SurfaceCommandsResult holds parsed bitmap data and frame IDs to acknowledge. type SurfaceCommandsResult struct { Rects []BitmapData FrameIDs []uint32 } // ParseSurfaceCommands parses one or more surface commands from raw data // and returns decoded BitmapData rectangles and frame IDs that need acknowledgment. func ParseSurfaceCommands(data []byte) SurfaceCommandsResult { r := bytes.NewReader(data) var result SurfaceCommandsResult for r.Len() > 0 { cmdType, err := core.ReadUint16LE(r) if err != nil { break } switch cmdType { case CMDTYPE_SET_SURFACE_BITS, CMDTYPE_STREAM_SURFACE_BITS: rect, err := decodeSurfaceBitsCmd(r, true) if err != nil { slog.Warn("decodeSurfaceBitsCmd", "err", err) return result } if rect != nil { result.Rects = append(result.Rects, *rect) } case CMDTYPE_FRAME_MARKER: frameAction, _ := core.ReadUint16LE(r) frameId, _ := core.ReadUInt32LE(r) if frameAction == SURFCMD_FRAMEACTION_END { result.FrameIDs = append(result.FrameIDs, frameId) } default: slog.Warn("Unknown surface command type", "cmdType", cmdType) return result } } return result } // decodeSurfaceBitsCmd parses a SET_SURFACE_BITS or STREAM_SURFACE_BITS command. // win10Layout=true 时按 Win10 实测 22 字节头解析(codecID 前多一个保留字节), // 否则按 FreeRDP 经典 21 字节头解析。命令不含开头的 cmdType(2)。 func decodeSurfaceBitsCmd(r io.Reader, win10Layout bool) (*BitmapData, error) { destLeft, err := core.ReadUint16LE(r) if err != nil { return nil, err } destTop, _ := core.ReadUint16LE(r) destRight, _ := core.ReadUint16LE(r) destBottom, _ := core.ReadUint16LE(r) // 位图头尾部有两种实测布局(均从命令起始计偏移): // Win10 19041 实测(头长 22):bpp@10 ?@11 ?@12 codecID@13 width@14 // height@16 bitmapDataLength u32@18,数据@22; // FreeRDP update_recv_surface_bits(头长 21):bpp@10 reserved@11 // codecID@12 width@13 height@15 bitmapDataLength u32@17,数据@21。 // win10Layout 由调用方依据 width/height 与 dest 矩形的一致性判别, // 相对经典布局在 codecID 前多 1 个保留字节。 bpp, _ := core.ReadUInt8(r) _, _ = core.ReadUInt8(r) // reserved if win10Layout { _, _ = core.ReadUInt8(r) } codecID, _ := core.ReadUInt8(r) width, _ := core.ReadUint16LE(r) height, _ := core.ReadUint16LE(r) bitmapDataLength, _ := core.ReadUInt32LE(r) bitmapData, err := core.ReadBytes(int(bitmapDataLength), r) if err != nil { return nil, fmt.Errorf("failed to read bitmap data: %v", err) } slog.Debug("decodeSurfaceBitsCmd", "destLeft", destLeft, "destTop", destTop, "destRight", destRight, "destBottom", destBottom, "width", width, "height", height, "bpp", bpp, "codecID", codecID, "dataLen", bitmapDataLength) var pixels []byte outBpp := uint16(bpp) switch codecID { case 0: // Uncompressed pixels = bitmapData case 1: // NSCodec pixels = decodeNSCodec(bitmapData, int(width), int(height)) outBpp = 32 // NSCodec always decodes to BGRA (4 bytes/pixel) case 3: // RemoteFX (MS-RDPRFX) if DecodeRemoteFX != nil { pixels = DecodeRemoteFX(bitmapData, int(width), int(height)) outBpp = 32 } else { slog.Warn("RemoteFX surface codec not available", "codecID", codecID) return nil, nil } default: slog.Warn("Unsupported surface codec", "codecID", codecID) return nil, nil // skip unsupported codecs } if pixels == nil { return nil, nil } // Flip vertically for bottom-up codecs. NSCodec decodes top-down but the // bitmap coordinate system expects bottom-up. RFX (codecID=3) is already // in the correct top-down orientation and must NOT be flipped. if codecID != 3 { stride := int(width) * int(outBpp) / 8 h := int(height) if stride > 0 && len(pixels) < stride*h { // 解码产物不完整时翻转必然越界 panic;丢弃该帧而非崩溃。 slog.Warn("surface bits: short pixel buffer, drop frame", "codecID", codecID, "len", len(pixels), "want", stride*h) return nil, nil } for y := 0; y < h/2; y++ { top := y * stride bot := (h - 1 - y) * stride for i := range stride { pixels[top+i], pixels[bot+i] = pixels[bot+i], pixels[top+i] } } } return &BitmapData{ DestLeft: destLeft, DestTop: destTop, DestRight: destRight, DestBottom: destBottom, Width: width, Height: height, BitsPerPixel: outBpp, Flags: BITMAP_NO_PROCESSING, BitmapLength: 0, BitmapDataStream: pixels, }, nil } // decodeNSCodec decodes NSCodec (MS-RDPNSC) encoded bitmap data into BGRA pixels. // Implements the decoder exactly as FreeRDP does (libfreerdp/codec/nsc.c). func decodeNSCodec(data []byte, width, height int) []byte { if len(data) < 20 { slog.Warn("NSCodec data too short", "len", len(data)) return nil } r := bytes.NewReader(data) lumaLen, _ := core.ReadUInt32LE(r) orangeLen, _ := core.ReadUInt32LE(r) greenLen, _ := core.ReadUInt32LE(r) alphaLen, _ := core.ReadUInt32LE(r) colorLossLevel, _ := core.ReadUInt8(r) chromaSubsamplingLevel, _ := core.ReadUInt8(r) _, _ = core.ReadUint16LE(r) // reserved if colorLossLevel < 1 { colorLossLevel = 1 } shift := colorLossLevel - 1 slog.Debug("NSCodec", "lumaLen", lumaLen, "orangeLen", orangeLen, "greenLen", greenLen, "alphaLen", alphaLen, "colorLossLevel", colorLossLevel, "chromaSub", chromaSubsamplingLevel) remaining := data[20:] // Bounds check totalPlaneLen := int(lumaLen + orangeLen + greenLen + alphaLen) if totalPlaneLen > len(remaining) { slog.Warn("NSCodec plane lengths exceed data", "planeLens", totalPlaneLen, "available", len(remaining)) return nil } // Compute plane original (decompressed) sizes, matching FreeRDP: // Y and A: tempWidth * height (Y uses rounded width for row stride) // Co and Cg: (tempWidth>>1) * (tempHeight>>1) when chroma subsampled tempWidth := (width + 7) &^ 7 // ROUND_UP_TO(width, 8) tempHeight := (height + 1) &^ 1 // ROUND_UP_TO(height, 2) var yOrigSize, coOrigSize, cgOrigSize, aOrigSize int if chromaSubsamplingLevel > 0 { yOrigSize = tempWidth * height coOrigSize = (tempWidth >> 1) * (tempHeight >> 1) cgOrigSize = coOrigSize } else { yOrigSize = width * height coOrigSize = yOrigSize cgOrigSize = yOrigSize } aOrigSize = width * height // Acquire pooled plane buffers; released before returning so the pool is // reused across decode calls without escaping to the caller. yPlane := acquireNSCPlaneBuf(yOrigSize) defer releaseNSCPlaneBuf(yPlane) coPlane := acquireNSCPlaneBuf(coOrigSize) defer releaseNSCPlaneBuf(coPlane) cgPlane := acquireNSCPlaneBuf(cgOrigSize) defer releaseNSCPlaneBuf(cgPlane) // Decompress each plane: if planeSize < originalSize → NRLE decode, // if planeSize == 0 → fill with 0xFF, otherwise raw copy. nscDecompressPlaneInto(remaining[:lumaLen], int(lumaLen), yPlane) remaining = remaining[lumaLen:] nscDecompressPlaneInto(remaining[:orangeLen], int(orangeLen), coPlane) remaining = remaining[orangeLen:] nscDecompressPlaneInto(remaining[:greenLen], int(greenLen), cgPlane) remaining = remaining[greenLen:] var aPlane []byte if alphaLen > 0 { aPlane = acquireNSCPlaneBuf(aOrigSize) defer releaseNSCPlaneBuf(aPlane) nscDecompressPlaneInto(remaining[:alphaLen], int(alphaLen), aPlane) } // YCoCg to BGRA conversion (matches FreeRDP nsc_decode exactly). // FreeRDP formula: // co_val = (INT16)(INT8)(((INT16)*coplane) << shift) // cg_val = (INT16)(INT8)(((INT16)*cgplane) << shift) // R = Y + co - cg // G = Y + cg // B = Y - co - cg totalPixels := width * height pixels := make([]byte, totalPixels*4) // Row widths for plane indexing (FreeRDP uses rw for Y, rw>>1 for chroma) yRowWidth := width coRowWidth := width if chromaSubsamplingLevel > 0 { yRowWidth = tempWidth coRowWidth = tempWidth >> 1 } if chromaSubsamplingLevel == 0 && aPlane == nil { // Fast path: no chroma subsampling, no alpha override. // ycoCgToBGRANoSub has SIMD implementations on amd64/arm64. ycoCgToBGRANoSub(pixels, yPlane, coPlane, cgPlane, width*height, shift) return pixels } if chromaSubsamplingLevel > 0 { // 2:1 horizontal chroma subsampling: each Co/Cg sample covers 2 pixels. // Process 2 pixels per iteration to eliminate the px%2 modulo. for py := range height { yRowOff := py * yRowWidth coIdx := (py >> 1) * coRowWidth cgIdx := coIdx outBase := py * width px := 0 for ; px+1 < width; px += 2 { coVal, cgVal := int16(0), int16(0) if coIdx < len(coPlane) { coVal = int16(int8(byte(int16(coPlane[coIdx]) << shift))) } if cgIdx < len(cgPlane) { cgVal = int16(int8(byte(int16(cgPlane[cgIdx]) << shift))) } coIdx++ cgIdx++ // Pixel px off0 := (outBase + px) * 4 yVal := int16(0) if yIdx := yRowOff + px; yIdx < len(yPlane) { yVal = int16(yPlane[yIdx]) } pixels[off0] = clampByte(yVal - coVal - cgVal) pixels[off0+1] = clampByte(yVal + cgVal) pixels[off0+2] = clampByte(yVal + coVal - cgVal) if aPlane != nil && outBase+px < len(aPlane) { pixels[off0+3] = aPlane[outBase+px] } else { pixels[off0+3] = 0xFF } // Pixel px+1 (shares same Co/Cg sample) off1 := off0 + 4 yVal = int16(0) if yIdx := yRowOff + px + 1; yIdx < len(yPlane) { yVal = int16(yPlane[yIdx]) } pixels[off1] = clampByte(yVal - coVal - cgVal) pixels[off1+1] = clampByte(yVal + cgVal) pixels[off1+2] = clampByte(yVal + coVal - cgVal) if aPlane != nil && outBase+px+1 < len(aPlane) { pixels[off1+3] = aPlane[outBase+px+1] } else { pixels[off1+3] = 0xFF } } // Handle odd width remainder if px < width { off := (outBase + px) * 4 coVal, cgVal := int16(0), int16(0) if coIdx < len(coPlane) { coVal = int16(int8(byte(int16(coPlane[coIdx]) << shift))) } if cgIdx < len(cgPlane) { cgVal = int16(int8(byte(int16(cgPlane[cgIdx]) << shift))) } yVal := int16(0) if yIdx := yRowOff + px; yIdx < len(yPlane) { yVal = int16(yPlane[yIdx]) } pixels[off] = clampByte(yVal - coVal - cgVal) pixels[off+1] = clampByte(yVal + cgVal) pixels[off+2] = clampByte(yVal + coVal - cgVal) if aPlane != nil && outBase+px < len(aPlane) { pixels[off+3] = aPlane[outBase+px] } else { pixels[off+3] = 0xFF } } } } else { // No subsampling, but with alpha plane. for py := range height { yRowOff := py * yRowWidth coIdx := py * coRowWidth cgIdx := coIdx outBase := py * width for px := range width { yVal, coVal, cgVal := int16(0), int16(0), int16(0) if yIdx := yRowOff + px; yIdx < len(yPlane) { yVal = int16(yPlane[yIdx]) } if coIdx < len(coPlane) { coVal = int16(int8(byte(int16(coPlane[coIdx]) << shift))) } if cgIdx < len(cgPlane) { cgVal = int16(int8(byte(int16(cgPlane[cgIdx]) << shift))) } coIdx++ cgIdx++ off := (outBase + px) * 4 pixels[off] = clampByte(yVal - coVal - cgVal) pixels[off+1] = clampByte(yVal + cgVal) pixels[off+2] = clampByte(yVal + coVal - cgVal) if outBase+px < len(aPlane) { pixels[off+3] = aPlane[outBase+px] } else { pixels[off+3] = 0xFF } } } } return pixels } func clampByte(v int16) uint8 { if v < 0 { return 0 } if v > 255 { return 255 } return uint8(v) } // nscDecompressPlane decompresses a single NSCodec plane. // If planeSize == 0, fills with 0xFF. If planeSize >= originalSize, raw copy. // Otherwise, uses the NRLE format (matching FreeRDP's nsc_rle_decode). func nscDecompressPlane(input []byte, planeSize, originalSize int) []byte { out := make([]byte, originalSize) nscDecompressPlaneInto(input, planeSize, out) return out } // nscDecompressPlaneInto is the zero-allocation variant of nscDecompressPlane. // out must be pre-allocated to exactly originalSize bytes. func nscDecompressPlaneInto(input []byte, planeSize int, out []byte) { originalSize := len(out) if planeSize == 0 { for i := range out { out[i] = 0xFF } return } if planeSize >= originalSize { copy(out, input[:originalSize]) return } nrleDecodeInto(input[:planeSize], out) } // nrleDecode decompresses NRLE (NSCodec Run-Length Encoding) data. // Matches FreeRDP's nsc_rle_decode exactly: // - 2 consecutive equal bytes trigger a run // - If 3rd byte < 0xFF: run length = byte + 2 // - If 3rd byte == 0xFF: run length = next 4 bytes as uint32 LE // - Last 4 bytes of output are copied raw from input func nrleDecode(input []byte, originalSize int) []byte { output := make([]byte, originalSize) nrleDecodeInto(input, output) return output } // nrleDecodeInto is the zero-allocation variant of nrleDecode. // output must be pre-allocated to exactly originalSize bytes. func nrleDecodeInto(input []byte, output []byte) { originalSize := len(output) left := originalSize inPos := 0 outPos := 0 for left > 4 && inPos < len(input) { value := input[inPos] inPos++ if left == 5 { output[outPos] = value outPos++ left-- } else if inPos < len(input) && value == input[inPos] { // Run detected inPos++ // skip the second occurrence runLen := 0 if inPos < len(input) { if input[inPos] < 0xFF { runLen = int(input[inPos]) + 2 inPos++ } else { // Long run: skip 0xFF marker, read uint32 LE inPos++ if inPos+4 <= len(input) { runLen = int(input[inPos]) | int(input[inPos+1])<<8 | int(input[inPos+2])<<16 | int(input[inPos+3])<<24 inPos += 4 } } } if runLen > left { runLen = left } // Exponential-doubling copy for large runs is O(log n) instead of O(n). n := min(runLen, originalSize-outPos) output[outPos] = value wrote := 1 for wrote < n { step := wrote if wrote+step > n { step = n - wrote } copy(output[outPos+wrote:outPos+wrote+step], output[outPos:outPos+wrote]) wrote += step } outPos += n left -= runLen } else { // Single byte output[outPos] = value outPos++ left-- } } // Copy last 4 bytes raw if left >= 4 && inPos+4 <= len(input) { copy(output[outPos:outPos+4], input[inPos:inPos+4]) } } // TS_POINTER_NEW(慢路径 PTR_MSG_TYPE_POINTER 0x0008,FreeRDP // update_read_pointer_new)与快速路径 FASTPATH_UPDATETYPE_POINTER 0x0B // 共用同一布局:首个字段是 2 字节 xorBpp,其后才是 cacheIndex 等颜色 // 指针属性(FreeRDP s_update_read_pointer_color:全部 u16 字段)。 // TS_POINTER_NEW(慢路径 PTR_MSG_TYPE_POINTER 0x0008 与快速路径 // FASTPATH_UPDATETYPE_POINTER 0x0B 共用布局,MS-RDPBCGR 2.2.9.1.1.4.4/4.5、 // FreeRDP update_read_pointer_new):xorBpp 首字段,其后为颜色指针属性。 // 可变长 blob 顺序按规范:xorMaskData(lengthXorMask 字节)在前, // andMaskData(lengthAndMask 字节)在后——即 Data 先消费、Mask 后消费。 // 注意 struc 按字段声明顺序消费,故 Data 必须声明在 Mask 之前; // 此前 Mask 在前导致两个掩码整体互换,图像旋转错位产生花屏。 type FastPathUpdatePointerPDU struct { XorBpp uint16 `struc:"little"` CacheIdx uint16 `struc:"little"` HotX uint16 `struc:"little"` HotY uint16 `struc:"little"` Width uint16 `struc:"little"` Height uint16 `struc:"little"` MaskLen uint16 `struc:"little,sizeof=Mask"` // lengthAndMask XorLen uint16 `struc:"little,sizeof=Data"` // lengthXorMask Data []byte // xorMaskData(XOR 在前) Mask []byte // andMaskData(AND 在后) } func (*FastPathUpdatePointerPDU) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_POINTER } func (f *FastPathUpdatePointerPDU) Unpack(r io.Reader) error { return struc.Unpack(r, f) } type FastPathPointerPositionPDU struct { X uint16 `struc:"little"` Y uint16 `struc:"little"` } func (*FastPathPointerPositionPDU) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_PTR_POSITION } func (f *FastPathPointerPositionPDU) Unpack(r io.Reader) error { return struc.Unpack(r, f) } type FastPathUpdatePointerNullPDU struct { } func (*FastPathUpdatePointerNullPDU) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_PTR_NULL } func (f *FastPathUpdatePointerNullPDU) Unpack(r io.Reader) error { return nil } // FastPathUpdatePointerDefaultPDU:FASTPATH_UPDATETYPE_PTR_DEFAULT(0x6), // 无载荷——服务器要求客户端恢复默认系统箭头。此前该类型没有对应的 // PDU 结构,分发时落入 PTR_POSITION 解析而报错丢弃,指针无法从 // 隐藏/自定义形状切回箭头。 type FastPathUpdatePointerDefaultPDU struct { } func (*FastPathUpdatePointerDefaultPDU) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_PTR_DEFAULT } func (f *FastPathUpdatePointerDefaultPDU) Unpack(r io.Reader) error { return nil } type FastPathUpdateCachedPDU struct { CacheIdx uint16 `struc:"little"` } func (*FastPathUpdateCachedPDU) FastPathUpdateType() uint8 { return FASTPATH_UPDATETYPE_CACHED } func (f *FastPathUpdateCachedPDU) Unpack(r io.Reader) error { return struc.Unpack(r, f) } type FastPathUpdatePDU struct { UpdateHeader uint8 Fragmentation uint8 CompressionFlags uint8 Size uint16 Data UpdateData } const ( FASTPATH_OUTPUT_COMPRESSION_USED = 0x2 ) // Fast-path fragmentation bits (MS-RDPBCGR 2.2.9.1.1.3.1): SINGLE=0, // FIRST=1, NEXT=2, LAST=3, left-shifted into bits 4-5 of the update header. // 续片的 updateCode 无意义,重组后必须用首片记住的 code 解析。 const ( // FASTPATH_FRAGMENT_*:updateHeader 位 5-4 的分片字段(MS-RDPBCGR // 2.2.9.1.1.3.1、FreeRDP fastpath.h 枚举左移 4 位后的线路值)。 // SINGLE=0x0、LAST=0x1、FIRST=0x2、NEXT=0x3。此前 FIRST/NEXT/LAST // 三个值轮换错位,导致首片被当孤儿丢弃、续片提前冲刷、尾片被缓存 // 到下一条更新——SURFCMDS 流从命令中间开始,花屏与 resync 的总根源。 FASTPATH_FRAGMENT_SINGLE = (0x0 << 4) FASTPATH_FRAGMENT_LAST = (0x1 << 4) FASTPATH_FRAGMENT_FIRST = (0x2 << 4) FASTPATH_FRAGMENT_NEXT = (0x3 << 4) ) func readFastPathUpdatePDU(r io.Reader, code uint8) (*FastPathUpdatePDU, error) { f := &FastPathUpdatePDU{} var err error var d UpdateData switch code { case FASTPATH_UPDATETYPE_ORDERS: d = &FastPathOrdersPDU{} case FASTPATH_UPDATETYPE_BITMAP: d = &FastPathBitmapUpdateDataPDU{} case FASTPATH_UPDATETYPE_PALETTE: case FASTPATH_UPDATETYPE_SYNCHRONIZE: case FASTPATH_UPDATETYPE_SURFCMDS: //d = &FastPathSurfaceCmds{} case FASTPATH_UPDATETYPE_PTR_NULL: d = &FastPathUpdatePointerNullPDU{} case FASTPATH_UPDATETYPE_PTR_DEFAULT: d = &FastPathUpdatePointerDefaultPDU{} case FASTPATH_UPDATETYPE_PTR_POSITION: d = &FastPathPointerPositionPDU{} case FASTPATH_UPDATETYPE_COLOR: //d = &FastPathColorPdu{} case FASTPATH_UPDATETYPE_CACHED: d = &FastPathUpdateCachedPDU{} case FASTPATH_UPDATETYPE_POINTER: d = &FastPathUpdatePointerPDU{} case FASTPATH_UPDATETYPE_LARGE_POINTER: default: return f, fmt.Errorf("Unknown FastPathPDU type 0x%x", code) } if d != nil { err = d.Unpack(r) if err != nil { //slog.Error("Unpack:", err) return nil, err } } else { return nil, fmt.Errorf("Unsupport FastPathPDU type 0x%x", code) } f.Data = d return f, nil } type ShareControlHeader struct { TotalLength uint16 `struc:"little"` PDUType uint16 `struc:"little"` PDUSource uint16 `struc:"little"` } type PDU struct { ShareCtrlHeader *ShareControlHeader Message PDUMessage } func NewPDU(userId uint16, message PDUMessage) *PDU { pdu := &PDU{} pdu.ShareCtrlHeader = &ShareControlHeader{ TotalLength: uint16(len(message.Serialize()) + 6), PDUType: message.Type(), PDUSource: userId, } pdu.Message = message return pdu } func readPDU(r io.Reader, mppc *core.MppcDecompressor) (*PDU, error) { pdu := &PDU{} var err error header := &ShareControlHeader{} err = struc.Unpack(r, header) if err != nil { return nil, err } pdu.ShareCtrlHeader = header var d PDUMessage switch pdu.ShareCtrlHeader.PDUType { case PDUTYPE_DEMANDACTIVEPDU: slog.Debug("readPDU:PDUTYPE_DEMANDACTIVEPDU") d, err = readDemandActivePDU(r) case PDUTYPE_DATAPDU: slog.Debug("readPDU:PDUTYPE_DATAPDU") d, err = readDataPDU(r, mppc) case PDUTYPE_CONFIRMACTIVEPDU: slog.Debug("readPDU:PDUTYPE_CONFIRMACTIVEPDU") d, err = readConfirmActivePDU(r) case PDUTYPE_DEACTIVATEALLPDU: slog.Debug("readPDU:PDUTYPE_DEACTIVATEALLPDU") d, err = readDeactiveAllPDU(r) case PDUTYPE_SERVER_REDIR_PKT: slog.Debug("readPDU:PDUTYPE_SERVER_REDIR_PKT") d, err = readServerRedirectionPDU(r) default: slog.Error("PDU invalid pdu type", "type", fmt.Sprintf("0x%02x", pdu.ShareCtrlHeader.PDUType)) } if err != nil { return nil, err } pdu.Message = d return pdu, err } func (p *PDU) serialize() []byte { buff := &bytes.Buffer{} struc.Pack(buff, p.ShareCtrlHeader) core.WriteBytes(p.Message.Serialize(), buff) return buff.Bytes() } type SlowPathInputEvent struct { EventTime uint32 `struc:"little"` MessageType uint16 `struc:"little"` Size int `struc:"skip"` SlowPathInputData []byte `struc:"sizefrom=Size"` } type PointerEvent struct { PointerFlags uint16 `struc:"little"` XPos uint16 `struc:"little"` YPos uint16 `struc:"little"` } func (p *PointerEvent) Serialize() []byte { return []byte{ byte(p.PointerFlags), byte(p.PointerFlags >> 8), byte(p.XPos), byte(p.XPos >> 8), byte(p.YPos), byte(p.YPos >> 8), } } // FastPathEncode appends this mouse event in the Fast-Path Input wire format // (MS-RDPBCGR §2.2.8.1.2.2.3) to buf and returns the new slice. func (p *PointerEvent) FastPathEncode(buf []byte) []byte { buf = append(buf, byte(FASTPATH_INPUT_EVENT_MOUSE<<5)) buf = append(buf, byte(p.PointerFlags), byte(p.PointerFlags>>8), byte(p.XPos), byte(p.XPos>>8), byte(p.YPos), byte(p.YPos>>8)) return buf } type SynchronizeEvent struct { Pad2Octets uint16 `struc:"little"` ToggleFlags uint32 `struc:"little"` } func (p *SynchronizeEvent) Serialize() []byte { return []byte{ byte(p.Pad2Octets), byte(p.Pad2Octets >> 8), byte(p.ToggleFlags), byte(p.ToggleFlags >> 8), byte(p.ToggleFlags >> 16), byte(p.ToggleFlags >> 24), } } type ScancodeKeyEvent struct { KeyboardFlags uint16 `struc:"little"` KeyCode uint16 `struc:"little"` Pad2Octets uint16 `struc:"little"` } func (p *ScancodeKeyEvent) Serialize() []byte { return []byte{ byte(p.KeyboardFlags), byte(p.KeyboardFlags >> 8), byte(p.KeyCode), byte(p.KeyCode >> 8), byte(p.Pad2Octets), byte(p.Pad2Octets >> 8), } } // FastPathEncode appends this scancode event in the Fast-Path Input wire // format (MS-RDPBCGR §2.2.8.1.2.2.1) to buf and returns the new slice. // // Slow-path callers in this codebase historically encoded extended keys by // stuffing the 0xE0 prefix into the high byte of KeyCode (e.g. 0xE048 for // the up-arrow) and leaving KBDFLAGS_EXTENDED unset. Fast-path can only // carry an 8-bit make code, so we promote any 0xE0XX encoding to the proper // EXTENDED flag here. func (p *ScancodeKeyEvent) FastPathEncode(buf []byte) []byte { flags := byte(0) if p.KeyboardFlags&KBDFLAGS_RELEASE != 0 { flags |= FASTPATH_INPUT_KBDFLAGS_RELEASE } if p.KeyboardFlags&KBDFLAGS_EXTENDED != 0 || p.KeyCode&0xFF00 == 0xE000 { flags |= FASTPATH_INPUT_KBDFLAGS_EXTENDED } if p.KeyboardFlags&KBDFLAGS_EXTENDED1 != 0 { flags |= FASTPATH_INPUT_KBDFLAGS_EXTENDED1 } buf = append(buf, byte(FASTPATH_INPUT_EVENT_SCANCODE<<5)|flags) buf = append(buf, byte(p.KeyCode)) return buf } type UnicodeKeyEvent struct { KeyboardFlags uint16 `struc:"little"` Unicode uint16 `struc:"little"` Pad2Octets uint16 `struc:"little"` } func (p *UnicodeKeyEvent) Serialize() []byte { return []byte{ byte(p.KeyboardFlags), byte(p.KeyboardFlags >> 8), byte(p.Unicode), byte(p.Unicode >> 8), byte(p.Pad2Octets), byte(p.Pad2Octets >> 8), } } // FastPathEncode appends this unicode key event in the Fast-Path Input wire // format (MS-RDPBCGR §2.2.8.1.2.2.5) to buf and returns the new slice. func (p *UnicodeKeyEvent) FastPathEncode(buf []byte) []byte { flags := byte(0) if p.KeyboardFlags&KBDFLAGS_RELEASE != 0 { flags |= FASTPATH_INPUT_KBDFLAGS_RELEASE } buf = append(buf, byte(FASTPATH_INPUT_EVENT_UNICODE<<5)|flags) buf = append(buf, byte(p.Unicode), byte(p.Unicode>>8)) return buf } type ClientInputEventPDU struct { NumEvents uint16 `struc:"little,sizeof=SlowPathInputEvents"` Pad2Octets uint16 `struc:"little"` SlowPathInputEvents []SlowPathInputEvent `struc:"little"` } func (*ClientInputEventPDU) Type2() uint8 { return PDUTYPE2_INPUT } func (*ClientInputEventPDU) Unpack(io.Reader) error { return nil } // findSurfaceResync 从 from 开始扫描下一个合法的 SET/STREAM_SURFACE_BITS // 命令头(cmdType 匹配 + width/height 与 dest 矩形一致 + bitmapDataLength // 不越界,22/21 两种布局任一通过即认)。找不到返回 -1。 func findSurfaceResync(buf []byte, from int) int { for i := from; i+22 <= len(buf); i++ { if !validSurfaceHeader(buf, i) { continue } // 链式验证:候选头部之后必须紧跟缓冲结束、帧标记或另一个合法 // 命令头。随机像素数据偶发形成单个伪头部可以,但连续两环全合 // 法的概率实际为零——以此排除误命中画出的花屏瓦片。 if total, ok := surfaceCmdTotal(buf, i); ok { next := i + total if next+22 > len(buf) || validSurfaceHeader(buf, next) || isFrameMarkerAt(buf, next) || validSurfaceHeader(buf, next+8) { return i } } } return -1 } // isFrameMarkerAt 判断 pos 处是否为帧标记命令(cmdType=4,action 0/1)。 func isFrameMarkerAt(buf []byte, pos int) bool { return pos+8 <= len(buf) && binary.LittleEndian.Uint16(buf[pos:]) == CMDTYPE_FRAME_MARKER && binary.LittleEndian.Uint16(buf[pos+2:]) <= 1 } // validSurfaceHeader 判断 pos 处是否为一个自洽的 SET/STREAM_SURFACE_BITS // 命令头(cmdType 匹配 + codecID 已通告 + width/height 与 dest 一致 + // bitmapDataLength 不越界,22/21 两种布局任一通过)。 func validSurfaceHeader(buf []byte, pos int) bool { if pos+22 > len(buf) { return false } ct := binary.LittleEndian.Uint16(buf[pos:]) if ct != CMDTYPE_SET_SURFACE_BITS && ct != CMDTYPE_STREAM_SURFACE_BITS { return false } dl := int(binary.LittleEndian.Uint16(buf[pos+2:])) dt := int(binary.LittleEndian.Uint16(buf[pos+4:])) dr := int(binary.LittleEndian.Uint16(buf[pos+6:])) db := int(binary.LittleEndian.Uint16(buf[pos+8:])) remain := len(buf) - pos fit := func(w, h int) bool { return (w == dr-dl || w == dr-dl+1) && (h == db-dt || h == db-dt+1) } codec := func(b byte) bool { return b <= 3 } // 通告过的编解码族(0/1/3) w22 := int(binary.LittleEndian.Uint16(buf[pos+14:])) h22 := int(binary.LittleEndian.Uint16(buf[pos+16:])) l22 := int(binary.LittleEndian.Uint32(buf[pos+18:])) if l22 >= 0 && l22 <= 64<<20 && 22+l22 <= remain && fit(w22, h22) && codec(buf[pos+13]) { return true } w21 := int(binary.LittleEndian.Uint16(buf[pos+13:])) h21 := int(binary.LittleEndian.Uint16(buf[pos+15:])) l21 := int(binary.LittleEndian.Uint32(buf[pos+17:])) if l21 >= 0 && l21 <= 64<<20 && 21+l21 <= remain && fit(w21, h21) && codec(buf[pos+12]) { return true } return false } // surfaceCmdTotal 返回 pos 处合法命令的总长度(字节)。 func surfaceCmdTotal(buf []byte, pos int) (int, bool) { if pos+22 > len(buf) { return 0, false } w22 := int(binary.LittleEndian.Uint16(buf[pos+14:])) h22 := int(binary.LittleEndian.Uint16(buf[pos+16:])) l22 := int(binary.LittleEndian.Uint32(buf[pos+18:])) dl := int(binary.LittleEndian.Uint16(buf[pos+2:])) dt := int(binary.LittleEndian.Uint16(buf[pos+4:])) dr := int(binary.LittleEndian.Uint16(buf[pos+6:])) db := int(binary.LittleEndian.Uint16(buf[pos+8:])) fit := func(w, h int) bool { return (w == dr-dl || w == dr-dl+1) && (h == db-dt || h == db-dt+1) } if l22 >= 0 && l22 <= 64<<20 && 22+l22 <= len(buf)-pos && fit(w22, h22) { return 22 + l22, true } w21 := int(binary.LittleEndian.Uint16(buf[pos+13:])) h21 := int(binary.LittleEndian.Uint16(buf[pos+15:])) l21 := int(binary.LittleEndian.Uint32(buf[pos+17:])) if l21 >= 0 && l21 <= 64<<20 && 21+l21 <= len(buf)-pos && fit(w21, h21) { return 21 + l21, true } return 0, false } // parseSurfaceCommandsIncremental 从缓冲解析完整的 surface 命令(可多条)。 // 返回 consumed=完整消费的字节数;needMore=true 表示尾部命令不完整、 // 需继续累积(此时 valid 恒为 true,缓冲必须保留);valid=false 表示 // 缓冲开头不是合法命令流(调用方应整体丢弃)。dropped=true 表示尾部 // 携带无法解析的未知结构(实测 Win10 整屏重绘批次末尾有 11 字节未文档 // 化尾巴),已成功解析的前缀命令必须照常上屏、仅尾巴丢弃。 // sticky:已锁定的 SET_SURFACE_BITS 头部长度(21/22;0=自动判定)。 // 服务器布局会话内恒定,首条命令判定后必须锁定——逐帧启发式在 // inclusive/exclusive 双兼容下会偶发选错 ±1 字节,错位累积到批次末尾 // 即"未知命令类型→整段重置"(表现为周期性花屏+断流)。返回的 chosen // 为本批实际判定的头部长度(sticky=0 时供调用方锁定)。 func parseSurfaceCommandsIncremental(buf []byte, sticky int) (result SurfaceCommandsResult, consumed int, needMore bool, valid bool, dropped bool, chosen int) { pos := 0 chosen = sticky for pos < len(buf) { if len(buf)-pos < 2 { return result, pos, true, true, false, chosen } cmdType := binary.LittleEndian.Uint16(buf[pos:]) switch cmdType { case CMDTYPE_SET_SURFACE_BITS, CMDTYPE_STREAM_SURFACE_BITS: // 头部布局(字段偏移均从命令起始计): // Win10 实测 22 字节:codecID@13、width@14、height@16、len u32@18、数据@22 // FreeRDP 经典 21 字节:codecID@12、width@13、height@15、len u32@17、数据@21 // 自动判定仅用于首条命令;锁定后按已知布局硬性校验。 saneLen := func(l int) bool { return l >= 0 && l <= 64<<20 } fitDim := func(w, h, dl, dt, dr, db int) bool { // 兼容 inclusive/exclusive 两种 destRight/destBottom 约定 return (w == dr-dl || w == dr-dl+1) && (h == db-dt || h == db-dt+1) } remain := len(buf) - pos var hdrLen, rawLen int switch sticky { case 22: if remain < 22 { return result, pos, true, true, false, chosen } l := int(binary.LittleEndian.Uint32(buf[pos+18:])) if !saneLen(l) { return result, 0, false, false, false, chosen } if 22+l > remain { return result, pos, true, true, false, chosen } hdrLen, rawLen = 22, l case 21: if remain < 21 { return result, pos, true, true, false, chosen } l := int(binary.LittleEndian.Uint32(buf[pos+17:])) if !saneLen(l) { return result, 0, false, false, false, chosen } if 21+l > remain { return result, pos, true, true, false, chosen } hdrLen, rawLen = 21, l default: if remain < 22 { return result, pos, true, true, false, chosen } dl := int(binary.LittleEndian.Uint16(buf[pos+2:])) dt := int(binary.LittleEndian.Uint16(buf[pos+4:])) dr := int(binary.LittleEndian.Uint16(buf[pos+6:])) db := int(binary.LittleEndian.Uint16(buf[pos+8:])) w22 := int(binary.LittleEndian.Uint16(buf[pos+14:])) h22 := int(binary.LittleEndian.Uint16(buf[pos+16:])) l22 := int(binary.LittleEndian.Uint32(buf[pos+18:])) w21 := int(binary.LittleEndian.Uint16(buf[pos+13:])) h21 := int(binary.LittleEndian.Uint16(buf[pos+15:])) l21 := int(binary.LittleEndian.Uint32(buf[pos+17:])) switch { case saneLen(l22) && 22+l22 <= remain && fitDim(w22, h22, dl, dt, dr, db): hdrLen, rawLen, chosen = 22, l22, 22 case saneLen(l21) && 21+l21 <= remain && fitDim(w21, h21, dl, dt, dr, db): hdrLen, rawLen, chosen = 21, l21, 21 case saneLen(l22) && 22+l22 <= remain: hdrLen, rawLen, chosen = 22, l22, 22 case saneLen(l21) && 21+l21 <= remain: hdrLen, rawLen, chosen = 21, l21, 21 case saneLen(l22) || saneLen(l21): return result, pos, true, true, false, chosen // 数据未到齐,继续累积 default: return result, 0, false, false, false, chosen } } total := hdrLen + rawLen rect, err := decodeSurfaceBitsCmd(bytes.NewReader(buf[pos+2:pos+total]), hdrLen == 22) if err != nil { slog.Warn("decodeSurfaceBitsCmd", "err", err) return result, 0, false, false, false, chosen } if rect != nil { result.Rects = append(result.Rects, *rect) } pos += total case CMDTYPE_FRAME_MARKER: if len(buf)-pos < 2+2+4 { return result, pos, true, true, false, chosen } if binary.LittleEndian.Uint16(buf[pos+2:]) == SURFCMD_FRAMEACTION_END { result.FrameIDs = append(result.FrameIDs, binary.LittleEndian.Uint32(buf[pos+4:])) } pos += 8 default: // 重同步:未知命令类型说明流已错位(MPPC 历史或分片边界残留 // 问题)。向前扫描下一个能通过 dest 一致性+长度双重校验的 // SET/STREAM 命令头,只丢弃其之前的字节,避免整段缓冲报废。 if next := findSurfaceResync(buf, pos+1); next > pos { slog.Warn("surface cmd resync", "drop", next-pos, "pos", pos, "next", next, "len", len(buf), "at", fmt.Sprintf("% X", buf[pos:min(pos+24, len(buf))]), "nextAt", fmt.Sprintf("% X", buf[next:min(next+24, len(buf))])) pos = next continue } slog.Warn("surface cmd unknown type", "cmdType", fmt.Sprintf("0x%04X", cmdType), "pos", pos, "len", len(buf), "prefix", fmt.Sprintf("% X", buf[pos:min(pos+16, len(buf))])) // 尾部未知结构(实测 Win10 整屏重绘批次末尾带 11 字节未文档 // 化尾巴):已成功解析的前缀命令照常上屏,仅尾巴丢弃。 // 若整批报废,每秒一次的悬浮部件更新会把整屏重绘全部丢掉 //(表现为周期性花屏+断流)。 return result, pos, false, true, true, chosen } } return result, pos, false, true, false, chosen } // PersistentKeyListPDU 是 TS_BITMAPCACHE_PERSISTENT_LIST_PDU // (MS-RDPBCGR 2.2.2.3):连接 finalize 阶段声明客户端持久位图缓存 // 持有的条目键,服务器重连后可据此免重传这些位图。 // 头部固定 20 字节(numEntriesCache×5、totalEntriesCache×5、 // bBitMask、pad1、pad3),随后为 numKeys×8 字节的键(key1 低 32 位、 // key2 高 32 位,即 uint64 小端)。 // // 该 PDU 的键条目是变长且长度由 numEntriesCacheX 的分配决定,struc // 反射无法直接表达,故手写序列化(与 PDUMessage 接口对接)。 type PersistentKeyListPDU struct { shareId uint32 numEntries [5]uint16 keys []uint64 } func NewPersistentKeyListPDU(shareId uint32, keys []uint64, cellCounts [5]uint16) *PersistentKeyListPDU { m := &PersistentKeyListPDU{shareId: shareId, keys: keys} // 键按各缓存单元的广告容量依次分配(cache0 用完才进 cache1……), // totalEntriesCacheX 回填为本 PDU 实际条数(FreeRDP 同款语义)。 rest := uint16(len(keys)) for i := 0; i < 5; i++ { if rest < cellCounts[i] { m.numEntries[i] = rest } else { m.numEntries[i] = cellCounts[i] } rest -= m.numEntries[i] } return m } func (*PersistentKeyListPDU) Type() uint16 { return PDUTYPE_DATAPDU } func (m *PersistentKeyListPDU) Serialize() []byte { payload := make([]byte, 24+len(m.keys)*8) for i := 0; i < 5; i++ { binary.LittleEndian.PutUint16(payload[i*2:], m.numEntries[i]) // totalEntriesCacheX 写为实际条数(与 numEntries 一致) binary.LittleEndian.PutUint16(payload[10+i*2:], m.numEntries[i]) } payload[20] = 0x03 // bBitMask = PERSIST_FIRST_PDU | PERSIST_LAST_PDU // payload[21] pad1、payload[22:24] pad3 保持 0 off := 24 for _, k := range m.keys { binary.LittleEndian.PutUint64(payload[off:], k) off += 8 } hdr := NewShareDataHeader(len(payload), PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST, m.shareId) out := &bytes.Buffer{} struc.Pack(out, hdr) out.Write(payload) return out.Bytes() }