2338 lines
70 KiB
Go
2338 lines
70 KiB
Go
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()
|
||
}
|