Files
rdplib/protocol/pdu/data.go
T

2338 lines
70 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}