365 lines
9.8 KiB
Go
365 lines
9.8 KiB
Go
package drdynvc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"io"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"git.zeroonesoft.cn/golib/rdplib/core"
|
|
"git.zeroonesoft.cn/golib/rdplib/plugin"
|
|
)
|
|
|
|
const (
|
|
ChannelName = plugin.DRDYNVC_SVC_CHANNEL_NAME
|
|
ChannelOption = plugin.CHANNEL_OPTION_INITIALIZED |
|
|
plugin.CHANNEL_OPTION_ENCRYPT_RDP
|
|
)
|
|
|
|
const (
|
|
MAX_DVC_CHANNELS = 20
|
|
)
|
|
|
|
const (
|
|
DYNVC_CREATE_REQ = 0x01
|
|
DYNVC_DATA_FIRST = 0x02
|
|
DYNVC_DATA = 0x03
|
|
DYNVC_CLOSE = 0x04
|
|
DYNVC_CAPABILITIES = 0x05
|
|
DYNVC_DATA_FIRST_COMPRESSED = 0x06
|
|
DYNVC_DATA_COMPRESSED = 0x07
|
|
DYNVC_SOFT_SYNC_REQUEST = 0x08
|
|
DYNVC_SOFT_SYNC_RESPONSE = 0x09
|
|
)
|
|
|
|
// DvcChannelHandler processes data for a specific dynamic virtual channel.
|
|
type DvcChannelHandler interface {
|
|
Process(data []byte)
|
|
}
|
|
|
|
type ChannelClient struct {
|
|
name string
|
|
id uint32
|
|
channelSender core.ChannelSender
|
|
}
|
|
|
|
type dvcChannelInfo struct {
|
|
name string
|
|
id uint32
|
|
cbChId uint8
|
|
handler DvcChannelHandler
|
|
}
|
|
|
|
type dvcReassembly struct {
|
|
buf bytes.Buffer
|
|
totalLen uint32
|
|
}
|
|
|
|
type DvcClient struct {
|
|
w core.ChannelSender
|
|
channels map[string]ChannelClient
|
|
handlers map[string]DvcChannelHandler // channelName → handler
|
|
rejectedChannels map[string]bool // channelName → explicitly rejected
|
|
channelById map[uint32]*dvcChannelInfo // channelId → info
|
|
reassembly map[uint32]*dvcReassembly // channelId → reassembly state
|
|
negotiatedVersion uint16
|
|
}
|
|
|
|
func NewDvcClient() *DvcClient {
|
|
return &DvcClient{
|
|
channels: make(map[string]ChannelClient, 100),
|
|
handlers: make(map[string]DvcChannelHandler),
|
|
rejectedChannels: make(map[string]bool),
|
|
channelById: make(map[uint32]*dvcChannelInfo),
|
|
reassembly: make(map[uint32]*dvcReassembly),
|
|
}
|
|
}
|
|
|
|
// RegisterHandler registers a handler for a named DVC channel.
|
|
func (c *DvcClient) RegisterHandler(name string, handler DvcChannelHandler) {
|
|
c.handlers[name] = handler
|
|
}
|
|
|
|
// RegisterRejectedChannel marks a DVC channel to be explicitly rejected
|
|
// (non-zero CreationStatus) so the server does not use it.
|
|
// Use this to steer servers toward a fallback channel; for example,
|
|
// rejecting AUDIO_PLAYBACK_LOSSY_DVC forces gnome-remote-desktop to
|
|
// fall back to lossless AUDIO_PLAYBACK_DVC (PCM).
|
|
func (c *DvcClient) RegisterRejectedChannel(name string) {
|
|
c.rejectedChannels[name] = true
|
|
}
|
|
|
|
func (c *DvcClient) LoadAddin(f core.ChannelSender) {
|
|
|
|
}
|
|
|
|
type DvcHeader struct {
|
|
cmd uint8
|
|
sp uint8
|
|
cbChId uint8
|
|
}
|
|
|
|
func readHeader(r io.Reader) *DvcHeader {
|
|
value, _ := core.ReadUInt8(r)
|
|
cmd := (value & 0xf0) >> 4
|
|
sp := (value & 0x0c) >> 2
|
|
cbChId := (value & 0x03) >> 0
|
|
return &DvcHeader{cmd, sp, cbChId}
|
|
}
|
|
|
|
func (h *DvcHeader) serialize(channelId uint32) []byte {
|
|
b := &bytes.Buffer{}
|
|
core.WriteUInt8((h.cmd<<4)|(h.sp<<2)|h.cbChId, b)
|
|
if h.cbChId == 0 {
|
|
core.WriteUInt8(uint8(channelId), b)
|
|
} else if h.cbChId == 1 {
|
|
core.WriteUInt16LE(uint16(channelId), b)
|
|
} else {
|
|
core.WriteUInt32LE(channelId, b)
|
|
}
|
|
|
|
return b.Bytes()
|
|
}
|
|
|
|
func (c *DvcClient) Send(s []byte) (int, error) {
|
|
slog.Debug("dvc Send", "len", len(s), "data", hex.EncodeToString(s))
|
|
name, _ := c.GetType()
|
|
return c.w.SendToChannel(name, s)
|
|
}
|
|
|
|
// SendDvcData sends data on a DVC channel wrapped in a DYNVC_DATA PDU.
|
|
func (c *DvcClient) SendDvcData(channelId uint32, data []byte) {
|
|
ch, ok := c.channelById[channelId]
|
|
if !ok {
|
|
return
|
|
}
|
|
hdr := &DvcHeader{cmd: DYNVC_DATA, sp: 0, cbChId: ch.cbChId}
|
|
b := &bytes.Buffer{}
|
|
b.Write(hdr.serialize(channelId))
|
|
b.Write(data)
|
|
c.Send(b.Bytes())
|
|
}
|
|
func (c *DvcClient) Sender(f core.ChannelSender) {
|
|
c.w = f
|
|
}
|
|
func (c *DvcClient) GetType() (string, uint32) {
|
|
return ChannelName, ChannelOption
|
|
}
|
|
|
|
func (c *DvcClient) Process(s []byte) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
slog.Error("dvc: panic in Process", "err", r)
|
|
}
|
|
}()
|
|
r := bytes.NewReader(s)
|
|
hdr := readHeader(r)
|
|
b, _ := core.ReadBytes(r.Len(), r)
|
|
|
|
switch hdr.cmd {
|
|
case DYNVC_CAPABILITIES:
|
|
slog.Debug("DYNVC_CAPABILITIES")
|
|
c.processCapsPdu(hdr, b)
|
|
case DYNVC_CREATE_REQ:
|
|
slog.Debug("DYNVC_CREATE_REQ")
|
|
c.processCreateReq(hdr, b)
|
|
case DYNVC_DATA_FIRST:
|
|
c.processDataFirst(hdr, b)
|
|
case DYNVC_DATA:
|
|
c.processData(hdr, b)
|
|
case DYNVC_CLOSE:
|
|
c.processClose(hdr, b)
|
|
case DYNVC_SOFT_SYNC_REQUEST:
|
|
slog.Debug("DYNVC_SOFT_SYNC_REQUEST")
|
|
c.processSoftSyncRequest(hdr, b)
|
|
default:
|
|
slog.Warn("dvc: unhandled cmd", "cmd", hdr.cmd)
|
|
}
|
|
}
|
|
func (c *DvcClient) processClose(hdr *DvcHeader, s []byte) {
|
|
r := bytes.NewReader(s)
|
|
channelId := readDvcId(r, hdr.cbChId)
|
|
ch, ok := c.channelById[channelId]
|
|
name := "(unknown)"
|
|
if ok {
|
|
name = ch.name
|
|
delete(c.channelById, channelId)
|
|
delete(c.reassembly, channelId)
|
|
}
|
|
slog.Debug("dvc: CLOSE", "channelId", channelId, "name", name)
|
|
}
|
|
|
|
func (c *DvcClient) processCreateReq(hdr *DvcHeader, s []byte) {
|
|
r := bytes.NewReader(s)
|
|
channelId := readDvcId(r, hdr.cbChId)
|
|
nameBytes, _ := core.ReadBytes(r.Len(), r)
|
|
channelName := strings.TrimRight(string(nameBytes), "\x00")
|
|
slog.Debug("dvc: create request", "channelId", channelId, "name", channelName)
|
|
|
|
// Associate handler if registered
|
|
var handler DvcChannelHandler
|
|
if h, ok := c.handlers[channelName]; ok {
|
|
handler = h
|
|
info := &dvcChannelInfo{
|
|
name: channelName,
|
|
id: channelId,
|
|
cbChId: hdr.cbChId,
|
|
handler: handler,
|
|
}
|
|
c.channelById[channelId] = info
|
|
|
|
// Provide send callback if handler supports it
|
|
if setter, ok := handler.(interface{ SetSendFunc(func([]byte)) }); ok {
|
|
chId := channelId
|
|
setter.SetSendFunc(func(data []byte) {
|
|
c.SendDvcData(chId, data)
|
|
})
|
|
}
|
|
slog.Debug("dvc: handler registered", "channel", channelName, "id", channelId)
|
|
}
|
|
|
|
// If explicitly rejected, send a non-zero CreationStatus so the server
|
|
// does not use this channel (e.g. AUDIO_PLAYBACK_LOSSY_DVC → fallback to PCM).
|
|
if c.rejectedChannels[channelName] {
|
|
slog.Debug("dvc: rejecting channel", "channel", channelName, "id", channelId)
|
|
rspHdr := &DvcHeader{cmd: DYNVC_CREATE_REQ, sp: 0, cbChId: hdr.cbChId}
|
|
b := &bytes.Buffer{}
|
|
b.Write(rspHdr.serialize(channelId))
|
|
core.WriteUInt32LE(0x80004005, b) // E_FAIL
|
|
c.Send(b.Bytes())
|
|
return
|
|
}
|
|
|
|
// Send success response (Sp SHOULD be 0 per MS-RDPEDYC 2.2.2.2).
|
|
// Always accept: some Windows servers stop sending data on static
|
|
// virtual channels (e.g. cliprdr) when DVC creation requests are
|
|
// rejected, even for unrelated channels.
|
|
rspHdr := &DvcHeader{cmd: DYNVC_CREATE_REQ, sp: 0, cbChId: hdr.cbChId}
|
|
b := &bytes.Buffer{}
|
|
b.Write(rspHdr.serialize(channelId))
|
|
core.WriteUInt32LE(0, b)
|
|
c.Send(b.Bytes())
|
|
|
|
// Notify handler that channel is ready (CREATE_RSP has been sent)
|
|
if handler != nil {
|
|
if ch, ok := handler.(interface{ OnChannelCreated() }); ok {
|
|
ch.OnChannelCreated()
|
|
}
|
|
}
|
|
}
|
|
|
|
func readDvcId(r io.Reader, cbLen uint8) (id uint32) {
|
|
switch cbLen {
|
|
case 0:
|
|
i, _ := core.ReadUInt8(r)
|
|
id = uint32(i)
|
|
case 1:
|
|
i, _ := core.ReadUint16LE(r)
|
|
id = uint32(i)
|
|
default:
|
|
id, _ = core.ReadUInt32LE(r)
|
|
}
|
|
return
|
|
}
|
|
func (c *DvcClient) processDataFirst(hdr *DvcHeader, s []byte) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
slog.Error("dvc: panic in processDataFirst", "err", r)
|
|
}
|
|
}()
|
|
r := bytes.NewReader(s)
|
|
channelId := readDvcId(r, hdr.cbChId)
|
|
|
|
// Read total length (encoding based on sp/Len field)
|
|
var totalLen uint32
|
|
switch hdr.sp {
|
|
case 0:
|
|
l, _ := core.ReadUInt8(r)
|
|
totalLen = uint32(l)
|
|
case 1:
|
|
l, _ := core.ReadUint16LE(r)
|
|
totalLen = uint32(l)
|
|
default:
|
|
totalLen, _ = core.ReadUInt32LE(r)
|
|
}
|
|
|
|
data, _ := core.ReadBytes(r.Len(), r)
|
|
ch, ok := c.channelById[channelId]
|
|
if !ok || ch.handler == nil {
|
|
return
|
|
}
|
|
|
|
if uint32(len(data)) >= totalLen {
|
|
ch.handler.Process(data[:totalLen])
|
|
} else {
|
|
ra := &dvcReassembly{totalLen: totalLen}
|
|
ra.buf.Write(data)
|
|
c.reassembly[channelId] = ra
|
|
}
|
|
}
|
|
|
|
func (c *DvcClient) processData(hdr *DvcHeader, s []byte) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
slog.Error("dvc: panic in processData", "err", r)
|
|
}
|
|
}()
|
|
r := bytes.NewReader(s)
|
|
channelId := readDvcId(r, hdr.cbChId)
|
|
data, _ := core.ReadBytes(r.Len(), r)
|
|
|
|
ch, ok := c.channelById[channelId]
|
|
if !ok || ch.handler == nil {
|
|
return
|
|
}
|
|
|
|
ra, hasReassembly := c.reassembly[channelId]
|
|
if hasReassembly {
|
|
ra.buf.Write(data)
|
|
if uint32(ra.buf.Len()) >= ra.totalLen {
|
|
ch.handler.Process(ra.buf.Bytes()[:ra.totalLen])
|
|
delete(c.reassembly, channelId)
|
|
}
|
|
} else {
|
|
ch.handler.Process(data)
|
|
}
|
|
}
|
|
|
|
func (c *DvcClient) processCapsPdu(hdr *DvcHeader, s []byte) {
|
|
r := bytes.NewReader(s)
|
|
core.ReadUInt8(r)
|
|
ver, _ := core.ReadUint16LE(r)
|
|
slog.Debug("Server supports dvc", "version", ver)
|
|
|
|
// Respond with the server's version (up to 3).
|
|
// Version 3 is required for some servers to activate RDPGFX.
|
|
ver = min(ver, 3)
|
|
|
|
// Client CAPS response: header(1) + pad(1) + version(2) = 4 bytes
|
|
// Priority charges are only in the server's CAPS request, not the client response.
|
|
b := &bytes.Buffer{}
|
|
core.WriteUInt8(0x50, b) // header: Cmd=5(CAPS), Sp=0, CbChId=0
|
|
core.WriteUInt8(0x00, b) // pad
|
|
core.WriteUInt16LE(ver, b)
|
|
slog.Debug("dvc: CAPS response", "version", ver, "len", b.Len())
|
|
c.Send(b.Bytes())
|
|
c.negotiatedVersion = ver
|
|
}
|
|
|
|
func (c *DvcClient) processSoftSyncRequest(hdr *DvcHeader, s []byte) {
|
|
r := bytes.NewReader(s)
|
|
core.ReadUInt8(r) // Pad
|
|
length, _ := core.ReadUInt32LE(r) // Length
|
|
flags, _ := core.ReadUint16LE(r) // Flags
|
|
numTunnels, _ := core.ReadUint16LE(r)
|
|
slog.Debug("DYNVC_SOFT_SYNC_REQUEST", "length", length, "flags", flags, "numTunnels", numTunnels)
|
|
|
|
// Send SOFT_SYNC_RESPONSE: header + pad + length(4)
|
|
b := &bytes.Buffer{}
|
|
core.WriteUInt8((DYNVC_SOFT_SYNC_RESPONSE<<4)|0x00, b) // cmd=9, sp=0, cbChId=0
|
|
core.WriteUInt8(0, b) // Pad
|
|
core.WriteUInt32LE(0x04, b) // Length = 4 (just the length field)
|
|
c.Send(b.Bytes())
|
|
}
|