init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user