Files
rdplib/protocol/t125/mcs.go
T

754 lines
23 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package t125
import (
"bytes"
"errors"
"fmt"
"io"
"log/slog"
"reflect"
"time"
// "git.zeroonesoft.cn/golib/rdplib/plugin/cliprdr"
"git.zeroonesoft.cn/golib/rdplib/plugin/drdynvc"
"git.zeroonesoft.cn/golib/rdplib/plugin/rail"
"git.zeroonesoft.cn/golib/rdplib/plugin/rdpsnd"
"git.zeroonesoft.cn/golib/rdplib/core"
"git.zeroonesoft.cn/golib/rdplib/emission"
"git.zeroonesoft.cn/golib/rdplib/protocol/t125/ber"
"git.zeroonesoft.cn/golib/rdplib/protocol/t125/gcc"
"git.zeroonesoft.cn/golib/rdplib/protocol/t125/per"
)
// take idea from https://github.com/Madnikulin50/gordp
// Multiple Channel Service layer
type MCSMessage uint8
const (
MCS_TYPE_CONNECT_INITIAL MCSMessage = 0x65
MCS_TYPE_CONNECT_RESPONSE = 0x66
)
type MCSDomainPDU uint16
const (
ERECT_DOMAIN_REQUEST MCSDomainPDU = 1
DISCONNECT_PROVIDER_ULTIMATUM = 8
ATTACH_USER_REQUEST = 10
ATTACH_USER_CONFIRM = 11
CHANNEL_JOIN_REQUEST = 14
CHANNEL_JOIN_CONFIRM = 15
SEND_DATA_REQUEST = 25
SEND_DATA_INDICATION = 26
)
const (
MCS_GLOBAL_CHANNEL_ID uint16 = 1003
MCS_USERCHANNEL_BASE = 1001
)
const (
GLOBAL_CHANNEL_NAME = "global"
)
/**
* Format MCS PDULayer header packet
* @param mcsPdu {integer}
* @param options {integer}
* @returns {type.UInt8} headers
*/
func writeMCSPDUHeader(mcsPdu MCSDomainPDU, options uint8, w io.Writer) {
core.WriteUInt8((uint8(mcsPdu)<<2)|options, w)
}
func readMCSPDUHeader(options uint8, mcsPdu MCSDomainPDU) bool {
return (options >> 2) == uint8(mcsPdu)
}
type DomainParameters struct {
MaxChannelIds int
MaxUserIds int
MaxTokenIds int
NumPriorities int
MinThoughput int
MaxHeight int
MaxMCSPDUsize int
ProtocolVersion int
}
/**
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
* @returns {asn1.univ.Sequence}
*/
func NewDomainParameters(
maxChannelIds int,
maxUserIds int,
maxTokenIds int,
numPriorities int,
minThoughput int,
maxHeight int,
maxMCSPDUsize int,
protocolVersion int) *DomainParameters {
return &DomainParameters{maxChannelIds, maxUserIds, maxTokenIds,
numPriorities, minThoughput, maxHeight, maxMCSPDUsize, protocolVersion}
}
func (d *DomainParameters) BER() []byte {
buff := &bytes.Buffer{}
ber.WriteInteger(d.MaxChannelIds, buff)
ber.WriteInteger(d.MaxUserIds, buff)
ber.WriteInteger(d.MaxTokenIds, buff)
ber.WriteInteger(1, buff)
ber.WriteInteger(0, buff)
ber.WriteInteger(1, buff)
ber.WriteInteger(d.MaxMCSPDUsize, buff)
ber.WriteInteger(2, buff)
return buff.Bytes()
}
func ReadDomainParameters(r io.Reader) (*DomainParameters, error) {
if !ber.ReadUniversalTag(ber.TAG_SEQUENCE, true, r) {
return nil, errors.New("bad BER tags")
}
d := &DomainParameters{}
ber.ReadLength(r)
d.MaxChannelIds, _ = ber.ReadInteger(r)
d.MaxUserIds, _ = ber.ReadInteger(r)
d.MaxTokenIds, _ = ber.ReadInteger(r)
ber.ReadInteger(r)
ber.ReadInteger(r)
ber.ReadInteger(r)
d.MaxMCSPDUsize, _ = ber.ReadInteger(r)
ber.ReadInteger(r)
return d, nil
}
/**
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
* @param userData {Buffer}
* @returns {asn1.univ.Sequence}
*/
type ConnectInitial struct {
CallingDomainSelector []byte
CalledDomainSelector []byte
UpwardFlag bool
TargetParameters DomainParameters
MinimumParameters DomainParameters
MaximumParameters DomainParameters
UserData []byte
}
func NewConnectInitial(userData []byte) ConnectInitial {
return ConnectInitial{[]byte{0x1},
[]byte{0x1},
true,
*NewDomainParameters(34, 2, 0, 1, 0, 1, 0xffff, 2),
*NewDomainParameters(1, 1, 1, 1, 0, 1, 0x420, 2),
*NewDomainParameters(0xffff, 0xfc17, 0xffff, 1, 0, 1, 0xffff, 2),
userData}
}
func (c *ConnectInitial) BER() []byte {
buff := &bytes.Buffer{}
ber.WriteOctetstring(string(c.CallingDomainSelector), buff)
ber.WriteOctetstring(string(c.CalledDomainSelector), buff)
ber.WriteBoolean(c.UpwardFlag, buff)
ber.WriteEncodedDomainParams(c.TargetParameters.BER(), buff)
ber.WriteEncodedDomainParams(c.MinimumParameters.BER(), buff)
ber.WriteEncodedDomainParams(c.MaximumParameters.BER(), buff)
ber.WriteOctetstring(string(c.UserData), buff)
return buff.Bytes()
}
/**
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
* @returns {asn1.univ.Sequence}
*/
type ConnectResponse struct {
result uint8
calledConnectId int
domainParameters *DomainParameters
userData []byte
}
func NewConnectResponse(userData []byte) *ConnectResponse {
return &ConnectResponse{0,
0,
NewDomainParameters(22, 3, 0, 1, 0, 1, 0xfff8, 2),
userData}
}
func ReadConnectResponse(r io.Reader) (*ConnectResponse, error) {
c := &ConnectResponse{}
var err error
_, err = ber.ReadApplicationTag(MCS_TYPE_CONNECT_RESPONSE, r)
if err != nil {
return nil, err
}
c.result, err = ber.ReadEnumerated(r)
if err != nil {
return nil, err
}
c.calledConnectId, err = ber.ReadInteger(r)
c.domainParameters, err = ReadDomainParameters(r)
if err != nil {
return nil, err
}
if !ber.ReadUniversalTag(ber.TAG_OCTET_STRING, false, r) {
return nil, errors.New("invalid expected BER tag")
}
dataLen, _ := ber.ReadLength(r)
c.userData, err = core.ReadBytes(dataLen, r)
return c, err
}
type MCSChannelInfo struct {
ID uint16
Name string
}
type MCS struct {
emission.Emitter
transport core.Transport
recvOpCode MCSDomainPDU
sendOpCode MCSDomainPDU
channels []MCSChannelInfo
}
func NewMCS(t core.Transport, recvOpCode MCSDomainPDU, sendOpCode MCSDomainPDU) *MCS {
m := &MCS{
*emission.NewEmitter(),
t,
recvOpCode,
sendOpCode,
[]MCSChannelInfo{{MCS_GLOBAL_CHANNEL_ID, GLOBAL_CHANNEL_NAME}},
}
m.transport.On("close", func() {
m.Emit("close")
}).On("error", func(err error) {
m.Emit("error", err)
})
return m
}
func (x *MCS) Read(b []byte) (n int, err error) {
return x.transport.Read(b)
}
func (x *MCS) Write(b []byte) (n int, err error) {
return x.transport.Write(b)
}
func (m *MCS) Close() error {
return m.transport.Close()
}
type MCSClient struct {
*MCS
clientCoreData *gcc.ClientCoreData
clientNetworkData *gcc.ClientNetworkData
clientSecurityData *gcc.ClientSecurityData
// lowColorDepth:SetSessionColorDepth(16/24) 置位, Dynvc-GFX 能力位
// 需要跳过(该端点要求 32bpp 会话)
lowColorDepth bool
serverCoreData *gcc.ServerCoreData
serverNetworkData *gcc.ServerNetworkData
serverSecurityData *gcc.ServerSecurityData
channelsConnected int
userId uint16
nbChannelRequested int
pendingJoins int // 并行突发加入后尚未收到确认的 SVC 通道数
messageChannelId uint16 // from SC_MCS_MSGCHANNEL; 0 = not negotiated
messageChannelJoined bool
bwStartTime time.Time // timestamp of last RDP_BW_START for timeDelta calculation
}
func NewMCSClient(t core.Transport, kbdLayout uint32, keyboardType uint32, keyboardSubType uint32) *MCSClient {
c := &MCSClient{
MCS: NewMCS(t, SEND_DATA_INDICATION, SEND_DATA_REQUEST),
clientCoreData: gcc.NewClientCoreData(kbdLayout, keyboardType, keyboardSubType),
clientNetworkData: gcc.NewClientNetworkData(),
clientSecurityData: gcc.NewClientSecurityData(),
userId: 1 + MCS_USERCHANNEL_BASE,
}
c.transport.On("connect", c.connect)
return c
}
func (c *MCSClient) SetClientDesktop(width, height uint16) {
c.clientCoreData.DesktopWidth = width
c.clientCoreData.DesktopHeight = height
}
// SetClientName 覆盖客户端计算机名(ClientCoreData.ClientName,UTF-16LE,
// 字段共 32 字节,超长截断)。服务端按该名字管理 \\tsclient 重定向映射,
// 同名客户端残留(非正常断开)会让后续同名会话的映射失效——Explorer
// 打开 \\tsclient\<名> 报“试图访问无效的地址”且 rdpdr 通道零 IRP
// (RDPDR-2)。wasm 下 os.Hostname 回退值恒为 "js",必须每次连接随机化。
func (c *MCSClient) SetClientName(name string) {
var buf [32]byte
copy(buf[:], core.UnicodeEncode(name))
c.clientCoreData.ClientName = buf
}
// SetSessionColorDepth 请求会话颜色位数(16/24/32,其它值按 32 处理)。
// 32bpp = highColorDepth 24BPP + WANT_32BPP_SESSION(mstsc 默认);
// 16/24bpp 清除 WANT_32BPP_SESSION 并收紧 supportedColorDepths。
// 注意 RDPGFX 会话(RemoteFX/H264 模式)表面恒为 32bpp,此选项只在
// 传统位图管线生效。
// SetSessionColorDepth 请求会话颜色位数(16/24/32,其它值按 32 处理)。
// 实测(Win10 19041)三个坑:
// 1. supportedColorDepths 若不含 24/32bpp 支持位,服务器在 MCS 握手
// 阶段直接断连(无 ERRINFO)——支持位恒为全量(与 FreeRDP 一致)。
// 2. 低色深时必须同时清除 SUPPORT_DYNVC_GFX_PROTOCOL(0x200):
// 该位声明 32bpp 的 Dynvc-GFX 端点,与 16bpp 会话矛盾,服务器会
// 进入不一致状态(fastpath SURFCMDS 泛滥、音频 DVC 不初始化)。
// SetClientDynvcProtocol 会在低色深时跳过该位(drdynvc 通道保留)。
// 3. 色深协商只通过 highColorDepth + postBeta2ColorDepth +
// WANT_32BPP_SESSION 位表达。
// 注意 RDPGFX 会话(RemoteFX/H264 模式)表面恒为 32bpp,此选项只在
// 传统位图管线生效。
func (c *MCSClient) SetSessionColorDepth(bpp int) {
d := c.clientCoreData
switch bpp {
case 16:
d.HighColorDepth = gcc.HIGH_COLOR_16BPP
d.PostBeta2ColorDepth = gcc.RNS_UD_COLOR_16BPP_565
d.EarlyCapabilityFlags &^= gcc.RNS_UD_CS_WANT_32BPP_SESSION |
gcc.RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL
c.lowColorDepth = true
case 24:
d.HighColorDepth = gcc.HIGH_COLOR_24BPP
d.PostBeta2ColorDepth = gcc.RNS_UD_COLOR_24BPP
d.EarlyCapabilityFlags &^= gcc.RNS_UD_CS_WANT_32BPP_SESSION |
gcc.RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL
c.lowColorDepth = true
default:
// 32bpp:保持 NewClientCoreData 的默认(WANT_32BPP_SESSION)
}
}
func (c *MCSClient) SetClientDynvcProtocol() {
// 低色深会话不支持 Dynvc-GFX 端点(见 SetSessionColorDepth),
// drdynvc 通道本身保留(剪贴板图片/音频 DVC 仍需要)。
if !c.lowColorDepth {
c.clientCoreData.EarlyCapabilityFlags |= gcc.RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL
}
c.clientNetworkData.AddVirtualChannel(drdynvc.ChannelName, drdynvc.ChannelOption)
}
func (c *MCSClient) SetClientRemoteProgram() {
c.clientNetworkData.AddVirtualChannel(rail.ChannelName, rail.ChannelOption)
}
func (c *MCSClient) SetClientSoundProtocol() {
c.clientNetworkData.AddVirtualChannel(rdpsnd.ChannelName, rdpsnd.ChannelOption)
}
func (c *MCSClient) SetClientDeviceRedirection() {
c.clientNetworkData.AddVirtualChannel("rdpdr",
uint32(gcc.CHANNEL_OPTION_INITIALIZED|gcc.CHANNEL_OPTION_ENCRYPT_RDP|gcc.CHANNEL_OPTION_COMPRESS_RDP))
}
func (c *MCSClient) SetClientClipboard() {
c.clientNetworkData.AddVirtualChannel("cliprdr",
uint32(gcc.CHANNEL_OPTION_INITIALIZED|gcc.CHANNEL_OPTION_ENCRYPT_RDP|gcc.CHANNEL_OPTION_COMPRESS_RDP))
}
func (c *MCSClient) connect(selectedProtocol uint32) {
slog.Debug("connect", "selectedProtocol", selectedProtocol)
c.clientCoreData.ServerSelectedProtocol = selectedProtocol
slog.Debug("connnect", "clientCoreData", c.clientCoreData)
slog.Debug("connect", "clientNetworkData", c.clientNetworkData)
slog.Debug("connect", "clientSecurityData", c.clientSecurityData)
// sendConnectclientCoreDataInitial
userDataBuff := bytes.Buffer{}
userDataBuff.Write(c.clientCoreData.Pack())
userDataBuff.Write(c.clientNetworkData.Pack())
userDataBuff.Write(c.clientSecurityData.Pack())
userDataBuff.Write(gcc.PackClientMsgChannelData())
slog.Debug("userData", "data", core.Hex(userDataBuff.Bytes()), "len", len(userDataBuff.Bytes()))
ccReq := gcc.MakeConferenceCreateRequest(userDataBuff.Bytes())
slog.Debug("ccReq", "data", core.Hex(ccReq), "len", len(ccReq))
connectInitial := NewConnectInitial(ccReq)
connectInitialBerEncoded := connectInitial.BER()
dataBuff := &bytes.Buffer{}
ber.WriteApplicationTag(uint8(MCS_TYPE_CONNECT_INITIAL), len(connectInitialBerEncoded), dataBuff)
dataBuff.Write(connectInitialBerEncoded)
slog.Debug("send connet initial", "data", core.Hex(dataBuff.Bytes()), "len", len(dataBuff.Bytes()))
_, err := c.transport.Write(dataBuff.Bytes())
if err != nil {
c.Emit("error", errors.New(fmt.Sprintf("mcs sendConnectInitial write error %v", err)))
return
}
slog.Debug("mcs wait for data event")
c.transport.Once("data", c.recvConnectResponse)
}
func (c *MCSClient) recvConnectResponse(s []byte) {
slog.Debug("mcs recvConnectResponse", "s", core.Hex(s))
cResp, err := ReadConnectResponse(bytes.NewReader(s))
if err != nil {
c.Emit("error", errors.New(fmt.Sprintf("ReadConnectResponse %v", err)))
return
}
// record server gcc block
serverSettings := gcc.ReadConferenceCreateResponse(cResp.userData)
for _, v := range serverSettings {
switch v.(type) {
case *gcc.ServerSecurityData:
c.serverSecurityData = v.(*gcc.ServerSecurityData)
case *gcc.ServerCoreData:
c.serverCoreData = v.(*gcc.ServerCoreData)
case *gcc.ServerNetworkData:
c.serverNetworkData = v.(*gcc.ServerNetworkData)
case *gcc.ServerMsgChannelData:
c.messageChannelId = v.(*gcc.ServerMsgChannelData).MCSChannelId
slog.Debug("SC_MCS_MSGCHANNEL", "messageChannelId", c.messageChannelId)
default:
slog.Warn("recvConnectResponse: unhandled server gcc block", "type", reflect.TypeOf(v))
}
}
c.sendErectDomainRequest()
c.sendAttachUserRequest()
c.transport.Once("data", c.recvAttachUserConfirm)
}
func (c *MCSClient) sendErectDomainRequest() {
buff := &bytes.Buffer{}
writeMCSPDUHeader(ERECT_DOMAIN_REQUEST, 0, buff)
per.WriteInteger(0, buff)
per.WriteInteger(0, buff)
c.transport.Write(buff.Bytes())
}
func (c *MCSClient) sendAttachUserRequest() {
buff := &bytes.Buffer{}
writeMCSPDUHeader(ATTACH_USER_REQUEST, 0, buff)
c.transport.Write(buff.Bytes())
}
func (c *MCSClient) recvAttachUserConfirm(s []byte) {
slog.Debug("mcs recvAttachUserConfirm", "s", core.Hex(s))
r := bytes.NewReader(s)
option, err := core.ReadUInt8(r)
if err != nil {
c.Emit("error", err)
return
}
if !readMCSPDUHeader(option, ATTACH_USER_CONFIRM) {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER"))
return
}
e, err := per.ReadEnumerates(r)
if err != nil {
c.Emit("error", err)
return
}
if e != 0 {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_SERVER_REJECT_USER'"))
return
}
userId, _ := per.ReadInteger16(r)
userId += MCS_USERCHANNEL_BASE
c.userId = userId
c.channels = append(c.channels, MCSChannelInfo{userId, "user"})
c.connectChannels()
}
func (c *MCSClient) connectChannels() {
slog.Debug("connectChannels", "channelsConnected", c.channelsConnected, "channels", c.channels)
if c.channelsConnected < len(c.channels) {
// sendChannelJoinRequest
c.sendChannelJoinRequest(c.channels[c.channelsConnected].ID)
c.transport.Once("data", c.recvChannelJoinConfirm)
return
}
// Join the message channel (for connect-time auto-detection) before SVCs.
if c.messageChannelId != 0 && !c.messageChannelJoined {
c.messageChannelJoined = true
c.sendChannelJoinRequest(c.messageChannelId)
c.transport.Once("data", c.recvChannelJoinConfirm)
return
}
if c.nbChannelRequested == 0 && int(c.serverNetworkData.ChannelCount) > 0 {
// 并行突发加入全部 SVC 静态通道(mstsc 同款):原先串行逐个等
// 确认,rdpdr 排在队尾,其 join 在服务端 LogonNotify 之后 ~100ms
// 才完成——登录期驱动映射(winlogon→drprov 按"当时的设备表"创建
// \\TSCLIENT\<名> 连接)因此永远看不到我们的设备,`dir
// \\tsclient\<名>` 恒为"设备没有连接"(2250)。并行发送让 rdpdr
// 与其余通道同时就绪,抢在 LogonNotify 之前。
for i := 0; i < int(c.serverNetworkData.ChannelCount); i++ {
c.sendChannelJoinRequest(c.serverNetworkData.ChannelIdArray[i])
}
c.nbChannelRequested = int(c.serverNetworkData.ChannelCount)
c.pendingJoins = int(c.serverNetworkData.ChannelCount)
// 单个持久监听 + 计数:emission 对同一条数据事件会触发全部
// 监听器,N×Once 会在首个确认上重复消费(实测导致通道表重复
// 追加与 MCS opcode 错误),故用 Off 在计数归零后摘除。
c.transport.On("data", c.recvChannelJoinConfirm)
return
}
if c.pendingJoins > 0 {
// 并行突发确认进行中,由 recvChannelJoinConfirm 计数收尾。
return
}
c.transport.On("data", c.recvData)
// send client and sever gcc informations callback to sec
clientData := make([]any, 0)
clientData = append(clientData, c.clientCoreData)
clientData = append(clientData, c.clientSecurityData)
clientData = append(clientData, c.clientNetworkData)
serverData := make([]any, 0)
serverData = append(serverData, c.serverCoreData)
serverData = append(serverData, c.serverSecurityData)
c.Emit("connect", clientData, serverData, c.userId, c.channels)
}
func (c *MCSClient) sendChannelJoinRequest(channelId uint16) {
slog.Debug("sendChannelJoinRequest", "channelId", channelId)
buff := &bytes.Buffer{}
writeMCSPDUHeader(CHANNEL_JOIN_REQUEST, 0, buff)
per.WriteInteger16(c.userId-MCS_USERCHANNEL_BASE, buff)
per.WriteInteger16(channelId, buff)
c.transport.Write(buff.Bytes())
}
func (c *MCSClient) recvData(s []byte) {
r := bytes.NewReader(s)
option, err := core.ReadUInt8(r)
if err != nil {
c.Emit("error", err)
return
}
if readMCSPDUHeader(option, DISCONNECT_PROVIDER_ULTIMATUM) {
c.Emit("error", errors.New("MCS DISCONNECT_PROVIDER_ULTIMATUM"))
c.transport.Close()
return
} else if !readMCSPDUHeader(option, c.recvOpCode) {
c.Emit("error", errors.New("Invalid expected MCS opcode receive data"))
return
}
userId, _ := per.ReadInteger16(r)
userId += MCS_USERCHANNEL_BASE
channelId, _ := per.ReadInteger16(r)
per.ReadEnumerates(r)
size, _ := per.ReadLength(r)
// channel ID doesn't match a requested layer
found := false
channelName := ""
for _, channel := range c.channels {
if channel.ID == channelId {
found = true
channelName = channel.Name
break
}
}
if !found {
if c.messageChannelId != 0 && channelId == c.messageChannelId {
data, _ := core.ReadBytes(int(size), r)
c.handleAutoDetect(data)
return
}
slog.Error("mcs receive data for an unconnected layer")
return
}
left, err := core.ReadBytes(int(size), r)
if err != nil {
c.Emit("error", errors.New(fmt.Sprintf("mcs recvData get data error %v", err)))
return
}
c.Emit("sec", channelName, left)
}
func (c *MCSClient) recvChannelJoinConfirm(s []byte) {
slog.Debug("recvChannelJoinConfirm", "s", core.Hex(s))
r := bytes.NewReader(s)
option, err := core.ReadUInt8(r)
if err != nil {
return
}
if !readMCSPDUHeader(option, CHANNEL_JOIN_CONFIRM) {
// 并行突发窗口内同监听器会看到数据 PDU:静默忽略(由 recvData
// 在突发完成后接管处理)。
return
}
confirm, _ := per.ReadEnumerates(r)
userId, _ := per.ReadInteger16(r)
userId += MCS_USERCHANNEL_BASE
if c.userId != userId {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID"))
return
}
channelId, _ := per.ReadInteger16(r)
if (confirm != 0) && (channelId == uint16(MCS_GLOBAL_CHANNEL_ID) || channelId == c.userId) {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNEL"))
return
}
if confirm == 0 {
for i := 0; i < int(c.serverNetworkData.ChannelCount); i++ {
if channelId == c.serverNetworkData.ChannelIdArray[i] {
var t MCSChannelInfo
t.ID = channelId
t.Name = string(c.clientNetworkData.ChannelDefArray[i].Name[:])
c.channels = append(c.channels, t)
}
}
}
c.channelsConnected++
if c.pendingJoins > 0 {
c.pendingJoins--
if c.pendingJoins > 0 {
return
}
// 全部 SVC 确认到齐:摘除突发期监听器,交还数据泵。
c.transport.Off("data", c.recvChannelJoinConfirm)
}
c.connectChannels()
}
// Connect-time auto-detection constants (MS-RDPBCGR 2.2.14).
const (
secAutoDetectReq = uint16(0x1000)
secAutoDetectRsp = uint16(0x2000)
rdpRttRequestConnecttime = uint16(0x1001)
rdpBwStartConnecttime = uint16(0x1014)
rdpBwPayload = uint16(0x0002)
rdpBwStopConnecttime = uint16(0x002B)
rdpRttRequest = uint16(0x0001) // continuous RTT request
rdpBwStart = uint16(0x0014) // continuous BW start (no response)
rdpBwStop = uint16(0x0429) // continuous BW stop
typeIDAutodetectResponse = uint8(0x01)
rdpRttResponseType = uint16(0x0000)
rdpBwResultsConnecttime = uint16(0x0003)
rdpBwResults = uint16(0x000B) // continuous BW results
)
// handleAutoDetect processes a connect-time auto-detect request from the server
// on the message channel. It responds to RTT and BW measurement requests so
// that gnome-remote-desktop proceeds to open the audio DVC channels.
func (c *MCSClient) handleAutoDetect(data []byte) {
r := bytes.NewReader(data)
secFlag, _ := core.ReadUint16LE(r)
core.ReadUint16LE(r) // secFlagHi
if secFlag&secAutoDetectReq == 0 {
return
}
_, _ = core.ReadUInt8(r) // headerLength
core.ReadUInt8(r) // headerTypeId
seqNum, _ := core.ReadUint16LE(r)
reqType, _ := core.ReadUint16LE(r)
switch reqType {
case rdpRttRequestConnecttime, rdpRttRequest:
c.sendAutoDetectResponse(seqNum, rdpRttResponseType, 0)
case rdpBwStartConnecttime, rdpBwStart:
c.bwStartTime = time.Now()
case rdpBwStopConnecttime:
elapsed := uint32(time.Since(c.bwStartTime).Milliseconds())
if elapsed == 0 {
elapsed = 1
}
c.sendAutoDetectResponse(seqNum, rdpBwResultsConnecttime, elapsed)
case rdpBwStop:
elapsed := uint32(time.Since(c.bwStartTime).Milliseconds())
if elapsed == 0 {
elapsed = 1
}
c.sendAutoDetectResponse(seqNum, rdpBwResults, elapsed)
// rdpBwPayload requires no response
}
}
// sendAutoDetectResponse sends an auto-detect response on the message channel.
// timeDelta is 0 for RTT responses (no BW fields); non-zero for BW responses
// (timeDelta in milliseconds since the corresponding BW_START was received).
func (c *MCSClient) sendAutoDetectResponse(sequenceNumber uint16, responseType uint16, timeDelta uint32) {
includeBW := responseType == rdpBwResultsConnecttime || responseType == rdpBwResults
headerLength := uint8(6)
if includeBW {
headerLength = 14
}
payload := &bytes.Buffer{}
core.WriteUInt16LE(secAutoDetectRsp, payload)
core.WriteUInt16LE(0, payload)
core.WriteUInt8(headerLength, payload)
core.WriteUInt8(typeIDAutodetectResponse, payload)
core.WriteUInt16LE(sequenceNumber, payload)
core.WriteUInt16LE(responseType, payload)
if includeBW {
core.WriteUInt32LE(timeDelta, payload) // timeDelta in milliseconds
core.WriteUInt32LE(0, payload) // byteCount (no BW_PAYLOAD was sent)
}
c.transport.Write(c.Pack(payload.Bytes(), c.messageChannelId))
}
func (c *MCSClient) Pack(data []byte, channelId uint16) []byte {
buff := &bytes.Buffer{}
writeMCSPDUHeader(c.sendOpCode, 0, buff)
per.WriteInteger16(c.userId-MCS_USERCHANNEL_BASE, buff)
per.WriteInteger16(channelId, buff)
core.WriteUInt8(0x70, buff)
per.WriteLength(len(data), buff)
core.WriteBytes(data, buff)
return buff.Bytes()
}
func (c *MCSClient) Write(data []byte) (n int, err error) {
data = c.Pack(data, c.channels[0].ID)
return c.transport.Write(data)
}
func (c *MCSClient) SendToChannel(channel string, data []byte) (n int, err error) {
channelId := c.channels[0].ID
for _, ch := range c.channels {
if channel == ch.Name {
channelId = ch.ID
break
}
}
data = c.Pack(data, channelId)
return c.transport.Write(data)
}