Files
aiops/backend/pkg/rdp/mcs_probe.go
T
LoganandClaude 76ec9387f0 feat(rdp): 新增 RDP 远程桌面/SMB/账单同步能力 + 清理编译产物
主要变更:
- RDP 远程桌面:新增 backend/pkg/rdp(RDP 引擎 + credssp/display/rle/trace)、
  backend/controller/rdp*(连接/凭证/文件/会话/实时)、frontend RDP 组件与 API
- Windows SMB 文件传输:backend/pkg/winsmb
- 云账单同步:backend/pkg/billingsync + billing_sync/aliyun_billing,前端账单详情
- Agent CLI Kubernetes 工具:kubernetes_action/get、pod_execute/logs
- 服务器导入助手:server_import_helpers、ImportCredentialSettings
- 国际化与组件重构(cloud/deploy/monitor/server/system 等多模块)
- 清理编译产物二进制:移除 backend/server、backend/agent/bin/aiops-agent-*
- .gitignore:新增 backend/agent/ 与 .cache/(避免 334MB go-build 缓存误入库)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 01:50:36 +08:00

1665 lines
47 KiB
Go

package rdp
import (
"bytes"
"context"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"time"
"unicode/utf16"
"ezops/pkg/rdp/credssp"
"ezops/pkg/rdp/transport"
)
const (
mcsConnectInitial = 0x65
mcsConnectResponse = 0x66
mcsErectDomainRequest = 1
mcsAttachUserRequest = 10
mcsAttachUserConfirm = 11
mcsChannelJoinRequest = 14
mcsChannelJoinConfirm = 15
mcsSendDataRequest = 25
mcsSendDataIndication = 26
mcsGlobalChannelID = 1003
mcsUserChannelIDOffset = 1001
gccCSCore = 0xC001
gccCSSecurity = 0xC002
gccCSNet = 0xC003
gccSCCore = 0x0C01
gccSCSecurity = 0x0C02
gccSCNet = 0x0C03
secFlagInfoPkt = 0x0040
secFlagLicensePkt = 0x0080
licenseErrorAlert = 0xff
licenseStatusValid = 0x00000007
licenseStateNoTransit = 0x00000002
pduTypeDemandActive = 0x0011
pduTypeConfirmActive = 0x0013
pduTypeDeactivateAll = 0x0016
pduTypeData = 0x0017
pduTypeServerRedirect = 0x001a
pduType2Synchronize = 0x1f
pduType2Control = 0x14
pduType2FontList = 0x27
pduType2FontMap = 0x28
pduType2RefreshRect = 0x21
pduType2SetErrorInfo = 0x2f
pduType2SuppressOutput = 0x23
pduType2SaveSessionInfo = 0x26
pduType2FrameAcknowledge = 0x38
ctrlActionGrantedControl = 0x0002
)
var errFinalizeActive = errors.New("RDP 会话已进入图形更新流")
// MCSProbeInfo 记录 NLA 后 MCS/GCC 连接期响应,用于确认图形会话层入口是否跑通 // Logan.Li
type MCSProbeInfo struct {
HandshakeInfo
UserDataBytes int
ServerCoreVersion uint32
ServerChannelID uint16
ServerChannelCount uint16
ServerChannelIDs []uint16
ServerEncryption uint32
ServerEncryptionLvl uint32
UserChannelID uint16
MessageChannelID uint16
JoinedChannels []uint16
FirstDataChannelID uint16
FirstSecurityFlags uint16
FirstPayloadBytes int
LicenseMessageType uint8
LicenseErrorCode uint32
LicenseState uint32
DemandPDUType uint16
DemandSharedID uint32
DemandCapabilities uint16
DemandSource string
PostConfirmTypes []uint8
}
// ProbeMCSConnect 复用同一条 CredSSP TLS 连接发送 MCS Connect Initial 并解析 Connect Response // Logan.Li
func ProbeMCSConnect(ctx context.Context, cfg Config) (MCSProbeInfo, error) {
port := cfg.Port
if port == 0 {
port = 3389
}
address := fmt.Sprintf("%s:%d", cfg.Host, port)
start := time.Now()
rdpDebugf(cfg, "probe start address=%s mode=%q", address, cfg.MCSMode)
strategies := []credssp.Options{
{Version: 6, PubKeyStyle: "binding"},
{Version: 6, PubKeyStyle: "binding", UseCBT: true},
{Version: 6, PubKeyStyle: "binding", ForceMIC: true},
{Version: 2, PubKeyStyle: "raw"},
}
var lastErr error
for _, opts := range strategies {
rdpDebugf(cfg, "strategy start version=%d pubkey=%s cbt=%v mic=%v", opts.Version, opts.PubKeyStyle, opts.UseCBT, opts.ForceMIC)
info, err := probeMCSConnectOnce(ctx, cfg, address, opts)
if err == nil {
info.Duration = time.Since(start)
rdpDebugf(cfg, "probe ok duration=%s joined=%v post=%v", info.Duration.Round(time.Millisecond), info.JoinedChannels, info.PostConfirmTypes)
return info, nil
}
rdpDebugf(cfg, "strategy failed version=%d pubkey=%s err=%v", opts.Version, opts.PubKeyStyle, err)
lastErr = err
}
if lastErr == nil {
lastErr = fmt.Errorf("MCS Connect Initial 失败")
}
return MCSProbeInfo{HandshakeInfo: HandshakeInfo{Address: address, Duration: time.Since(start)}}, lastErr
}
func probeMCSConnectOnce(ctx context.Context, cfg Config, address string, opts credssp.Options) (MCSProbeInfo, error) {
start := time.Now()
tlsConn, result, err := dialCredSSPOnce(ctx, cfg, address, opts)
if err != nil {
return MCSProbeInfo{}, err
}
defer tlsConn.Close()
rdpDebugf(cfg, "credssp ok protocol=%s elapsed=%s", result.ProtocolName(), time.Since(start).Round(time.Millisecond))
_ = tlsConn.SetDeadline(time.Now().Add(30 * time.Second))
rdpDebugf(cfg, "send MCS ConnectInitial")
if err := transport.WriteX224Data(tlsConn, buildMCSConnectInitial(cfg)); err != nil {
return MCSProbeInfo{}, err
}
payload, err := transport.ReadX224Data(tlsConn)
if err != nil {
return MCSProbeInfo{}, err
}
if cfg.Debug {
debugGCCServerBlocks(cfg, payload)
}
mcsInfo, err := parseMCSConnectResponse(payload)
if err != nil {
return MCSProbeInfo{}, err
}
rdpDebugf(cfg, "connect response userData=%d channel=%d channels=%v enc=%d/%d", mcsInfo.UserDataBytes, mcsInfo.ServerChannelID, mcsInfo.ServerChannelIDs, mcsInfo.ServerEncryption, mcsInfo.ServerEncryptionLvl)
userID, err := attachMCSUser(tlsConn)
if err != nil {
return MCSProbeInfo{}, err
}
mcsInfo.UserChannelID = userID
rdpDebugf(cfg, "attach user ok userID=%d", userID)
joinIDs := append([]uint16{mcsGlobalChannelID, userID}, mcsInfo.ServerChannelIDs...)
if mcsInfo.MessageChannelID != 0 {
joinIDs = append(joinIDs, mcsInfo.MessageChannelID)
}
for _, channelID := range joinIDs {
joined, err := joinMCSChannel(tlsConn, userID, channelID)
if err != nil {
return MCSProbeInfo{}, err
}
mcsInfo.JoinedChannels = append(mcsInfo.JoinedChannels, joined)
rdpDebugf(cfg, "join channel ok channel=%d", joined)
}
first, demand, err := sendClientInfoAndReadDemand(tlsConn, cfg, userID)
if err != nil {
return MCSProbeInfo{}, err
}
mcsInfo.FirstDataChannelID = first.channelID
mcsInfo.FirstSecurityFlags = first.securityFlags
mcsInfo.FirstPayloadBytes = len(first.payload)
mcsInfo.LicenseMessageType = first.licenseMsgType
mcsInfo.LicenseErrorCode = first.licenseErrorCode
mcsInfo.LicenseState = first.licenseState
mcsInfo.DemandPDUType = demand.pduType
mcsInfo.DemandSharedID = demand.sharedID
mcsInfo.DemandCapabilities = demand.capabilityCount
mcsInfo.DemandSource = string(demand.sourceDescriptor)
rdpDebugf(cfg, "demand active shared=0x%08x caps=%d source=%q firstSecurity=0x%04x firstPayload=%d", demand.sharedID, demand.capabilityCount, string(demand.sourceDescriptor), first.securityFlags, len(first.payload))
postTypes, err := sendConfirmActiveAndFinalize(tlsConn, cfg, userID, demand)
if err != nil {
return MCSProbeInfo{}, fmt.Errorf("Client Finalize 失败: shared=0x%08x caps=%d source=%q: %w", demand.sharedID, demand.capabilityCount, string(demand.sourceDescriptor), err)
}
mcsInfo.PostConfirmTypes = postTypes
state := tlsConn.ConnectionState()
certSubject := ""
if len(state.PeerCertificates) > 0 {
certSubject = state.PeerCertificates[0].Subject.String()
}
mcsInfo.HandshakeInfo = HandshakeInfo{
Address: address,
SelectedProtocol: result.ProtocolName(),
TLSCipher: transport.TLSCipherSuiteName(state.CipherSuite),
CertSubject: certSubject,
}
return mcsInfo, nil
}
func debugGCCServerBlocks(cfg Config, mcsPayload []byte) {
info, err := parseMCSConnectResponse(mcsPayload)
if err != nil {
rdpDebugf(cfg, "debug gcc parse failed err=%v", err)
return
}
rdpDebugf(cfg, "debug gcc summary userData=%d core=0x%08x scNet=%d msg=%d", info.UserDataBytes, info.ServerCoreVersion, info.ServerChannelID, info.MessageChannelID)
}
type mcsDataIndication struct {
channelID uint16
securityFlags uint16
payload []byte
licenseMsgType uint8
licenseErrorCode uint32
licenseState uint32
}
type demandActiveProbe struct {
pduType uint16
sharedID uint32
capabilityCount uint16
sourceDescriptor []byte
}
func sendClientInfoAndReadDemand(rw io.ReadWriter, cfg Config, userID uint16) (mcsDataIndication, demandActiveProbe, error) {
infoPDU := buildClientInfoPDU(cfg)
rdpDebugf(cfg, "send ClientInfo userID=%d bytes=%d", userID, len(infoPDU))
if err := transport.WriteX224Data(rw, buildMCSSendDataRequest(userID, mcsGlobalChannelID, infoPDU)); err != nil {
return mcsDataIndication{}, demandActiveProbe{}, err
}
payload, err := transport.ReadX224Data(rw)
if err != nil {
return mcsDataIndication{}, demandActiveProbe{}, err
}
first, err := parseMCSDataIndication(payload)
if err != nil {
return mcsDataIndication{}, demandActiveProbe{}, err
}
if first.securityFlags&secFlagLicensePkt != 0 {
rdpDebugf(cfg, "license packet security=0x%04x payload=%d", first.securityFlags, len(first.payload))
if err := parseLicensePacket(&first); err != nil {
return first, demandActiveProbe{}, err
}
payload, err = transport.ReadX224Data(rw)
if err != nil {
return first, demandActiveProbe{}, err
}
}
next, err := parseMCSDataIndication(payload)
if err != nil {
return first, demandActiveProbe{}, err
}
demand, err := parseDemandActiveProbe(next.payload)
if err != nil {
return first, demandActiveProbe{}, err
}
return first, demand, nil
}
func sendConfirmActiveAndFinalize(rw io.ReadWriter, cfg Config, userID uint16, demand demandActiveProbe) ([]uint8, error) {
rdpDebugf(cfg, "finalize start userID=%d shared=0x%08x", userID, demand.sharedID)
pdus := []struct {
name string
data []byte
}{
{"ConfirmActive", buildConfirmActivePDU(cfg, userID, demand)},
{"Synchronize", buildDataPDU(userID, demand.sharedID, pduType2Synchronize, buildSynchronizeData(userID))},
{"ControlCooperate", buildDataPDU(userID, demand.sharedID, pduType2Control, buildControlData(0x0004))},
{"ControlRequest", buildDataPDU(userID, demand.sharedID, pduType2Control, buildControlData(0x0001))},
{"FontList", buildDataPDU(userID, demand.sharedID, pduType2FontList, buildFontListData())},
}
var types []uint8
for _, pdu := range pdus {
rdpDebugf(cfg, "finalize send %s bytes=%d", pdu.name, len(pdu.data))
if err := transport.WriteX224Data(rw, buildMCSSendDataRequest(userID, mcsGlobalChannelID, pdu.data)); err != nil {
return nil, fmt.Errorf("发送 %s 失败: %w", pdu.name, err)
}
if isRDPStepMode(cfg) {
if stepTypes, err := readFinalizeStep(rw, pdu.name, 500*time.Millisecond); err != nil {
return append(types, stepTypes...), err
} else if len(stepTypes) > 0 {
types = append(types, stepTypes...)
}
}
}
readTypes, err := waitFinalizeResponses(rw, cfg, 5*time.Second)
if err != nil {
return types, err
}
types = append(types, readTypes...)
return types, nil
}
func isRDPStepMode(cfg Config) bool {
return cfg.MCSMode == "step" || cfg.MCSMode == "step-legacy" || cfg.MCSMode == "step-bpp32" || cfg.MCSMode == "step-minimal" || cfg.MCSMode == "step-plain-info" || cfg.MCSMode == "step-grdp-comment" || cfg.MCSMode == "step-grdp-modern"
}
func readFinalizeStep(rw io.ReadWriter, name string, timeout time.Duration) ([]uint8, error) {
conn, ok := rw.(interface{ SetReadDeadline(time.Time) error })
if !ok {
return nil, nil
}
_ = conn.SetReadDeadline(time.Now().Add(timeout))
defer conn.SetReadDeadline(time.Time{})
payload, err := transport.ReadX224Data(rw)
if err != nil {
return nil, nil
}
data, err := parseMCSDataIndication(payload)
if err != nil {
return nil, fmt.Errorf("%s 后响应解析失败: %w", name, err)
}
got, err := parseShareDataPDUInfo(data.payload)
if err != nil {
return nil, fmt.Errorf("%s 后 Share Data 解析失败: %w", name, err)
}
if got.type2 == pduType2SetErrorInfo {
return []uint8{got.type2}, fmt.Errorf("%s 后服务端返回 ErrorInfo: code=0x%08x(%s) raw=%s", name, got.errorInfo, rdpErrorInfoName(got.errorInfo), got.rawPrefix)
}
return []uint8{got.type2}, nil
}
func waitFinalizeResponses(rw io.ReadWriter, cfg Config, timeout time.Duration) ([]uint8, error) {
if conn, ok := rw.(interface{ SetReadDeadline(time.Time) error }); ok {
_ = conn.SetReadDeadline(time.Now().Add(timeout))
defer conn.SetReadDeadline(time.Time{})
}
expected := []finalizeExpectation{
{type2: pduType2Synchronize},
{type2: pduType2Control, action: 0x0004},
{type2: pduType2Control, action: ctrlActionGrantedControl},
{type2: pduType2FontMap},
}
var types []uint8
for _, want := range expected {
for {
payload, err := readNextSlowPathPayload(rw, cfg, timeout)
if err != nil {
if errors.Is(err, errFinalizeActive) || len(types) > 0 {
rdpDebugf(cfg, "finalize accept active session types=%v err=%v", types, err)
return types, nil
}
return types, fmt.Errorf("读取 Client Finalize 响应失败: %w", err)
}
data, err := parseMCSDataIndication(payload)
if err != nil {
return types, err
}
got, err := parseShareDataPDUInfo(data.payload)
if err != nil {
return types, err
}
types = append(types, got.type2)
rdpDebugf(cfg, "finalize recv type2=0x%02x action=0x%04x raw=%s", got.type2, got.controlAction, got.rawPrefix)
if got.type2 == pduType2SetErrorInfo {
return types, fmt.Errorf("服务端返回 ErrorInfo: code=0x%08x(%s) seen=%v raw=%s", got.errorInfo, rdpErrorInfoName(got.errorInfo), types, got.rawPrefix)
}
if got.type2 == pduType2SaveSessionInfo {
continue
}
if got.type2 != want.type2 {
continue
}
if want.type2 == pduType2Control && got.controlAction != want.action {
continue
}
break
}
}
return types, nil
}
func readNextSlowPathPayload(r io.Reader, cfg Config, timeout time.Duration) ([]byte, error) {
deadline := time.Now().Add(timeout)
seenFastPath := false
for {
pkt, err := ReadWirePacket(r)
if err != nil {
if seenFastPath {
return nil, errFinalizeActive
}
return nil, err
}
if !pkt.FastPath {
return pkt.Payload, nil
}
seenFastPath = true
rdpDebugf(cfg, "finalize skip fastpath sec=0x%02x payload=%d", pkt.SecFlag, len(pkt.Payload))
if time.Now().After(deadline) {
return nil, errFinalizeActive
}
}
}
type finalizeExpectation struct {
type2 uint8
action uint16
}
func buildConfirmActivePDU(cfg Config, userID uint16, demand demandActiveProbe) []byte {
source := demand.sourceDescriptor
if len(source) == 0 {
source = []byte("rdpy")
}
body := bytes.Buffer{}
writeLE32(&body, demand.sharedID)
writeLE16(&body, 0x03ea)
writeLE16(&body, uint16(len(source)))
caps, capCount := buildConfirmCapabilities(cfg)
rdpDebugf(cfg, "build ConfirmActive sourceLen=%d caps=%d capsBytes=%d", len(source), capCount, len(caps))
debugCapabilityList(cfg, caps)
writeLE16(&body, uint16(len(caps)+4))
body.Write(source)
writeLE16(&body, capCount)
writeLE16(&body, 0)
body.Write(caps)
pdu := buildShareControlPDU(userID, pduTypeConfirmActive, body.Bytes())
prefixLen := len(pdu)
if prefixLen > 96 {
prefixLen = 96
}
rdpDebugf(cfg, "ConfirmActive pduLen=%d prefix=%s", len(pdu), hex.EncodeToString(pdu[:prefixLen]))
return pdu
}
func rdpDebugf(cfg Config, format string, args ...interface{}) {
if !cfg.Debug {
return
}
_, _ = fmt.Fprintf(os.Stderr, "[rdp] "+format+"\n", args...)
}
func rdpErrorInfoName(code uint32) string {
switch code {
case 0x000010EA:
return "ERRINFO_BAD_CAPABILITIES"
case 0x000010E9:
return "ERRINFO_NO_CURSOR_CACHE"
case 0x000010E8:
return "ERRINFO_CAPABILITY_SET_TOO_LARGE"
case 0x000010E7:
return "ERRINFO_CAPABILITY_SET_TOO_SMALL"
case 0x000010D4:
return "ERRINFO_CONFIRM_ACTIVE_HAS_WRONG_SHAREID"
case 0x000010D5:
return "ERRINFO_CONFIRM_ACTIVE_HAS_WRONG_ORIGINATOR"
default:
return "UNKNOWN"
}
}
func debugCapabilityList(cfg Config, caps []byte) {
if !cfg.Debug {
return
}
offset := 0
index := 0
for offset+4 <= len(caps) {
capType := binary.LittleEndian.Uint16(caps[offset : offset+2])
capLen := int(binary.LittleEndian.Uint16(caps[offset+2 : offset+4]))
if capLen < 4 || offset+capLen > len(caps) {
rdpDebugf(cfg, "cap[%02d] invalid offset=%d type=0x%04x len=%d total=%d", index, offset, capType, capLen, len(caps))
return
}
body := caps[offset+4 : offset+capLen]
prefixLen := len(body)
if prefixLen > 24 {
prefixLen = 24
}
rdpDebugf(cfg, "cap[%02d] type=0x%04x len=%d body=%s", index, capType, capLen, hex.EncodeToString(body[:prefixLen]))
offset += capLen
index++
}
if offset != len(caps) {
rdpDebugf(cfg, "cap trailing bytes=%d offset=%d total=%d", len(caps)-offset, offset, len(caps))
}
}
func buildConfirmCapabilities(cfg Config) ([]byte, uint16) {
width := cfg.Width
if width <= 0 {
width = 1280
}
height := cfg.Height
if height <= 0 {
height = 800
}
out := bytes.Buffer{}
count := uint16(0)
addCapability := func(capType uint16, body []byte) {
writeCapability(&out, capType, body)
count++
}
if cfg.MCSMode == "grdp-comment" || cfg.MCSMode == "step-grdp-comment" {
return buildGRDPCommentCapabilities(cfg, width, height)
}
addCapability(0x0001, buildGeneralCapability())
bitmap := bytes.Buffer{}
preferredBPP := uint16(24)
if cfg.MCSMode == "bpp32" || cfg.MCSMode == "step-bpp32" || cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" || cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
preferredBPP = 32
}
writeLE16(&bitmap, preferredBPP)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, uint16(width))
writeLE16(&bitmap, uint16(height))
writeLE16(&bitmap, 0)
writeLE16(&bitmap, 1)
if cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
writeLE16(&bitmap, 0)
} else {
writeLE16(&bitmap, 1)
}
writeLE16(&bitmap, 0)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 0)
addCapability(0x0002, bitmap.Bytes())
if cfg.MCSMode == "basic" {
addCapability(0x000d, buildInputCapability())
return out.Bytes(), count
}
if cfg.MCSMode == "legacy" || cfg.MCSMode == "step-legacy" {
addCapability(0x0003, mustHex("00000000000000000000000000000000010014000000010000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008403000000000000000000"))
addCapability(0x0005, mustHex("0000000002000200"))
addCapability(0x0007, mustHex("0000000000000000"))
addCapability(0x0008, buildPointerCapability(128))
addCapability(0x0009, mustHex("00000000"))
addCapability(0x000a, mustHex("06000000"))
addCapability(0x000c, mustHex("01000000"))
addCapability(0x000d, buildInputCapability())
addCapability(0x000e, mustHex("01000000"))
addCapability(0x000f, mustHex("01000000"))
addCapability(0x0010, make([]byte, 48))
addCapability(0x0014, mustHex("0000000040060000"))
return out.Bytes(), count
}
if cfg.MCSMode == "minimal" || cfg.MCSMode == "step-minimal" {
addCapability(0x0003, mustHex("00000000000000000000000000000000010014000000010000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008403000000000000000000"))
addCapability(0x0008, mustHex("00001400"))
addCapability(0x000d, buildInputCapability())
addCapability(0x000e, mustHex("01000000"))
return out.Bytes(), count
}
if cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" || cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
addCapability(0x0003, buildGRDPOrderCapability())
addCapability(0x0005, mustHex("0000000002000200"))
addCapability(0x0007, mustHex("0000000000000000"))
addCapability(0x0008, buildPointerCapability(20))
addCapability(0x0009, mustHex("00000000"))
addCapability(0x000a, mustHex("06000000"))
addCapability(0x000c, mustHex("01000000"))
addCapability(0x000d, buildInputCapability())
addCapability(0x000e, mustHex("01000000"))
addCapability(0x000f, mustHex("01000000"))
addCapability(0x0010, buildGlyphNoneCapability())
addCapability(0x001d, buildBitmapCodecsCapability())
addCapability(0x0013, buildBitmapCacheRev2CapabilityWithPersist(2))
addCapability(0x0014, mustHex("0000000040060000"))
addCapability(0x001a, mustHex("00003f00"))
addCapability(0x0017, mustHex("ff000000"))
addCapability(0x001b, mustHex("0100"))
addCapability(0x0019, mustHex("0100"))
addCapability(0x001c, mustHex("5200000000000000"))
addCapability(0x001e, mustHex("02000000"))
return out.Bytes(), count
}
addCapability(0x0003, mustHex("00000000000000000000000000000000010014000000010000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008403000000000000000000"))
addCapability(0x0008, buildPointerCapability(128))
addCapability(0x000c, mustHex("00000000"))
addCapability(0x000d, buildInputCapability())
addCapability(0x000f, mustHex("00000000"))
addCapability(0x0010, make([]byte, 48))
addCapability(0x0013, buildBitmapCacheRev2Capability())
addCapability(0x0011, mustHex("0000000000000000"))
addCapability(0x0014, mustHex("0000000000000000"))
addCapability(0x001a, mustHex("00000000"))
return out.Bytes(), count
}
func buildGRDPCommentCapabilities(cfg Config, width int, height int) ([]byte, uint16) {
out := bytes.Buffer{}
count := uint16(0)
addCapability := func(capType uint16, body []byte) {
writeCapability(&out, capType, body)
count++
}
addCapability(0x0001, buildGeneralCapability())
bitmap := bytes.Buffer{}
writeLE16(&bitmap, 24)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, uint16(width))
writeLE16(&bitmap, uint16(height))
writeLE16(&bitmap, 0)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 0)
writeLE16(&bitmap, 1)
writeLE16(&bitmap, 0)
addCapability(0x0002, bitmap.Bytes())
addCapability(0x0003, mustHex("000000000000000000000000000000000000000000000000010014000000010000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000008403000000000000000000"))
addCapability(0x0004, make([]byte, 36))
addCapability(0x0008, mustHex("00001400"))
addCapability(0x000c, mustHex("00000000"))
addCapability(0x000d, buildInputCapability())
addCapability(0x000f, mustHex("00000000"))
addCapability(0x0010, buildGlyphNoneCapability())
addCapability(0x0011, mustHex("0000000000000000"))
addCapability(0x0014, mustHex("0000000000000000"))
addCapability(0x001a, mustHex("00000000"))
rdpDebugf(cfg, "grdp-comment caps=%d bytes=%d", count, out.Len())
return out.Bytes(), count
}
func buildGRDPOrderCapability() []byte {
out := bytes.Buffer{}
out.Write(make([]byte, 16))
writeLE32(&out, 0)
writeLE16(&out, 1)
writeLE16(&out, 20)
writeLE16(&out, 0)
writeLE16(&out, 1)
writeLE16(&out, 0)
writeLE16(&out, 0x00aa)
orderSupport := make([]byte, 32)
// native 暂不处理 Orders,能力集必须全关,避免服务端只发订单导致桌面黑屏 // Logan.Li
out.Write(orderSupport)
writeLE16(&out, 0)
writeLE16(&out, 0x0004)
writeLE32(&out, 0)
writeLE32(&out, 480*480)
writeLE16(&out, 0)
writeLE16(&out, 0)
writeLE16(&out, 0x04e4)
writeLE16(&out, 0)
return out.Bytes()
}
func buildGeneralCapability() []byte {
out := bytes.Buffer{}
writeLE16(&out, 0x0001)
writeLE16(&out, 0x0003)
writeLE16(&out, 0x0200)
writeLE16(&out, 0)
writeLE16(&out, 0x0002)
writeLE16(&out, 0x041d)
writeLE16(&out, 0)
writeLE16(&out, 0)
writeLE16(&out, 0)
out.WriteByte(1)
out.WriteByte(1)
return out.Bytes()
}
func buildPointerCapability(cacheSize uint16) []byte {
out := bytes.Buffer{}
writeLE16(&out, 1)
writeLE16(&out, cacheSize)
writeLE16(&out, cacheSize)
return out.Bytes()
}
func buildBitmapCacheRev2Capability() []byte {
return buildBitmapCacheRev2CapabilityWithPersist(0)
}
func buildBitmapCacheRev2CapabilityWithPersist(persist uint16) []byte {
out := bytes.Buffer{}
writeLE16(&out, persist)
out.WriteByte(0)
out.WriteByte(5)
for _, cells := range []uint32{0x258, 0x258, 0x800, 0x1000, 0x800} {
writeLE32(&out, cells)
}
out.Write(make([]byte, 12))
return out.Bytes()
}
func buildGlyphNoneCapability() []byte {
out := bytes.Buffer{}
out.Write(make([]byte, 40))
writeLE32(&out, 0)
writeLE16(&out, 0)
writeLE16(&out, 0)
return out.Bytes()
}
func buildBitmapCodecsCapability() []byte {
out := bytes.Buffer{}
out.WriteByte(2)
out.Write(mustHex("b91b8dca0f004f15589fae2d1a87e2d6"))
out.WriteByte(1)
writeLE16(&out, 3)
out.Write([]byte{1, 1, 3})
out.Write(mustHex("122f777672bd6344afb3b73c9c6f7886"))
out.WriteByte(3)
rfx := buildRemoteFXProperties()
writeLE16(&out, uint16(len(rfx)))
out.Write(rfx)
return out.Bytes()
}
func buildRemoteFXProperties() []byte {
return mustHex("29000000000000001d000000c0cb080000000100c1cb1500000001c0cf010008000001400001010104")
}
func buildInputCapability() []byte {
out := bytes.Buffer{}
writeLE16(&out, 0x003d)
writeLE16(&out, 0)
writeLE32(&out, 0x00000409)
writeLE32(&out, 4)
writeLE32(&out, 0)
writeLE32(&out, 12)
out.Write(make([]byte, 64))
return out.Bytes()
}
func writeCapability(w io.Writer, capType uint16, body []byte) {
writeLE16(w, capType)
writeLE16(w, uint16(len(body)+4))
_, _ = w.Write(body)
}
func buildShareControlPDU(userID uint16, pduType uint16, body []byte) []byte {
out := bytes.Buffer{}
writeLE16(&out, uint16(len(body)+6))
writeLE16(&out, pduType)
writeLE16(&out, userID)
out.Write(body)
return out.Bytes()
}
func buildDataPDU(userID uint16, sharedID uint32, type2 uint8, body []byte) []byte {
data := bytes.Buffer{}
writeLE32(&data, sharedID)
data.WriteByte(0)
data.WriteByte(1)
writeLE16(&data, uint16(len(body)+4))
data.WriteByte(type2)
data.WriteByte(0)
writeLE16(&data, 0)
data.Write(body)
return buildShareControlPDU(userID, pduTypeData, data.Bytes())
}
func buildSynchronizeData(userID uint16) []byte {
out := bytes.Buffer{}
writeLE16(&out, 1)
writeLE16(&out, userID)
return out.Bytes()
}
func buildControlData(action uint16) []byte {
out := bytes.Buffer{}
writeLE16(&out, action)
writeLE16(&out, 0)
writeLE32(&out, 0)
return out.Bytes()
}
func buildFontListData() []byte {
out := bytes.Buffer{}
writeLE16(&out, 0)
writeLE16(&out, 0)
writeLE16(&out, 0x0003)
writeLE16(&out, 0x0032)
return out.Bytes()
}
func attachMCSUser(rw io.ReadWriter) (uint16, error) {
if err := transport.WriteX224Data(rw, buildMCSErectDomainRequest()); err != nil {
return 0, err
}
if err := transport.WriteX224Data(rw, buildMCSAttachUserRequest()); err != nil {
return 0, err
}
payload, err := transport.ReadX224Data(rw)
if err != nil {
return 0, err
}
return parseMCSAttachUserConfirm(payload)
}
func joinMCSChannel(rw io.ReadWriter, userID uint16, channelID uint16) (uint16, error) {
if err := transport.WriteX224Data(rw, buildMCSChannelJoinRequest(userID, channelID)); err != nil {
return 0, err
}
payload, err := transport.ReadX224Data(rw)
if err != nil {
return 0, err
}
return parseMCSChannelJoinConfirm(payload, userID, channelID)
}
func buildMCSErectDomainRequest() []byte {
out := bytes.Buffer{}
writeMCSPDUHeader(&out, mcsErectDomainRequest)
writePERInteger(&out, 0)
writePERInteger(&out, 0)
return out.Bytes()
}
func buildMCSAttachUserRequest() []byte {
out := bytes.Buffer{}
writeMCSPDUHeader(&out, mcsAttachUserRequest)
return out.Bytes()
}
func buildMCSChannelJoinRequest(userID uint16, channelID uint16) []byte {
out := bytes.Buffer{}
writeMCSPDUHeader(&out, mcsChannelJoinRequest)
writeBE16Value(&out, userID-mcsUserChannelIDOffset)
writeBE16Value(&out, channelID)
return out.Bytes()
}
func buildMCSSendDataRequest(userID uint16, channelID uint16, data []byte) []byte {
out := bytes.Buffer{}
writeMCSPDUHeader(&out, mcsSendDataRequest)
writeBE16Value(&out, userID-mcsUserChannelIDOffset)
writeBE16Value(&out, channelID)
out.WriteByte(0x70)
writePERLength(&out, len(data))
out.Write(data)
return out.Bytes()
}
func parseMCSAttachUserConfirm(data []byte) (uint16, error) {
r := bytes.NewReader(data)
pdu, err := readMCSPDUHeader(r)
if err != nil {
return 0, err
}
if pdu != mcsAttachUserConfirm {
return 0, fmt.Errorf("MCS AttachUserConfirm 类型错误: %d", pdu)
}
result, err := r.ReadByte()
if err != nil {
return 0, err
}
if result != 0 {
return 0, fmt.Errorf("MCS AttachUser 被拒绝: %d", result)
}
userID, err := readBE16Value(r)
if err != nil {
return 0, err
}
return userID + mcsUserChannelIDOffset, nil
}
func parseMCSChannelJoinConfirm(data []byte, expectedUserID uint16, expectedChannelID uint16) (uint16, error) {
r := bytes.NewReader(data)
pdu, err := readMCSPDUHeader(r)
if err != nil {
return 0, err
}
if pdu != mcsChannelJoinConfirm {
return 0, fmt.Errorf("MCS ChannelJoinConfirm 类型错误: %d", pdu)
}
result, err := r.ReadByte()
if err != nil {
return 0, err
}
userID, err := readBE16Value(r)
if err != nil {
return 0, err
}
userID += mcsUserChannelIDOffset
channelID, err := readBE16Value(r)
if err != nil {
return 0, err
}
if userID != expectedUserID {
return 0, fmt.Errorf("MCS ChannelJoin 用户不匹配: got=%d want=%d", userID, expectedUserID)
}
if channelID != expectedChannelID {
return 0, fmt.Errorf("MCS ChannelJoin 通道不匹配: got=%d want=%d", channelID, expectedChannelID)
}
if result != 0 && (channelID == mcsGlobalChannelID || channelID == expectedUserID) {
return 0, fmt.Errorf("MCS 必需通道加入失败: channel=%d result=%d", channelID, result)
}
return channelID, nil
}
func parseMCSDataIndication(data []byte) (mcsDataIndication, error) {
r := bytes.NewReader(data)
pdu, err := readMCSPDUHeader(r)
if err != nil {
return mcsDataIndication{}, err
}
if pdu != mcsSendDataIndication {
return mcsDataIndication{}, fmt.Errorf("MCS DataIndication 类型错误: %d", pdu)
}
if _, err := readBE16Value(r); err != nil {
return mcsDataIndication{}, err
}
channelID, err := readBE16Value(r)
if err != nil {
return mcsDataIndication{}, err
}
if _, err := r.ReadByte(); err != nil {
return mcsDataIndication{}, err
}
size, err := readPERLength(r)
if err != nil {
return mcsDataIndication{}, err
}
if int(size) > r.Len() {
return mcsDataIndication{}, io.ErrUnexpectedEOF
}
payload := make([]byte, int(size))
_, _ = io.ReadFull(r, payload)
flags := uint16(0)
if len(payload) >= 4 {
flags = binary.LittleEndian.Uint16(payload[0:2])
}
return mcsDataIndication{channelID: channelID, securityFlags: flags, payload: payload}, nil
}
func parseLicensePacket(data *mcsDataIndication) error {
if len(data.payload) < 8 {
return fmt.Errorf("License PDU 太短: %d", len(data.payload))
}
body := data.payload[4:]
data.licenseMsgType = body[0]
if len(body) >= 12 && data.licenseMsgType == licenseErrorAlert {
data.licenseErrorCode = binary.LittleEndian.Uint32(body[4:8])
data.licenseState = binary.LittleEndian.Uint32(body[8:12])
if data.licenseErrorCode == licenseStatusValid && data.licenseState == licenseStateNoTransit {
return nil
}
return fmt.Errorf("License ErrorAlert: code=0x%08x state=0x%08x", data.licenseErrorCode, data.licenseState)
}
return fmt.Errorf("暂未实现 License msgType=0x%02x 的响应", data.licenseMsgType)
}
func parseDemandActiveProbe(payload []byte) (demandActiveProbe, error) {
r := bytes.NewReader(payload)
totalLength, err := readLE16(r)
if err != nil {
return demandActiveProbe{}, err
}
pduType, err := readLE16(r)
if err != nil {
return demandActiveProbe{}, err
}
if int(totalLength) > len(payload) {
return demandActiveProbe{}, fmt.Errorf("Share Control 长度超出数据: total=%d payload=%d", totalLength, len(payload))
}
if pduType != pduTypeDemandActive {
return demandActiveProbe{pduType: pduType}, fmt.Errorf("等待 DemandActive,收到 PDU type=0x%04x", pduType)
}
if _, err := readLE16(r); err != nil {
return demandActiveProbe{}, err
}
sharedID, err := readLE32(r)
if err != nil {
return demandActiveProbe{}, err
}
sourceLen, err := readLE16(r)
if err != nil {
return demandActiveProbe{}, err
}
if _, err := readLE16(r); err != nil {
return demandActiveProbe{}, err
}
if int(sourceLen) > r.Len() {
return demandActiveProbe{}, io.ErrUnexpectedEOF
}
source := make([]byte, int(sourceLen))
_, _ = io.ReadFull(r, source)
capabilityCount, err := readLE16(r)
if err != nil {
return demandActiveProbe{}, err
}
return demandActiveProbe{pduType: pduType, sharedID: sharedID, capabilityCount: capabilityCount, sourceDescriptor: source}, nil
}
func parseShareDataPDUType(payload []byte) (uint8, error) {
info, err := parseShareDataPDUInfo(payload)
if err != nil {
return 0, err
}
return info.type2, nil
}
type shareDataPDUInfo struct {
type2 uint8
controlAction uint16
errorInfo uint32
rawPrefix string
}
func parseShareDataPDUInfo(payload []byte) (shareDataPDUInfo, error) {
if len(payload) < 18 {
return shareDataPDUInfo{}, fmt.Errorf("Share Data PDU 太短: %d", len(payload))
}
pduType := binary.LittleEndian.Uint16(payload[2:4])
if pduType != pduTypeData {
return shareDataPDUInfo{}, fmt.Errorf("等待 Data PDU,收到 type=0x%04x", pduType)
}
prefixLen := len(payload)
if prefixLen > 32 {
prefixLen = 32
}
info := shareDataPDUInfo{type2: payload[14], rawPrefix: hex.EncodeToString(payload[:prefixLen])}
if info.type2 == pduType2Control {
if len(payload) < 20 {
return shareDataPDUInfo{}, fmt.Errorf("Control Data PDU 太短: %d", len(payload))
}
info.controlAction = binary.LittleEndian.Uint16(payload[18:20])
}
if info.type2 == pduType2SetErrorInfo {
if len(payload) < 22 {
return shareDataPDUInfo{}, fmt.Errorf("ErrorInfo Data PDU 太短: %d", len(payload))
}
info.errorInfo = binary.LittleEndian.Uint32(payload[18:22])
}
return info, nil
}
func mustHex(value string) []byte {
data, err := hex.DecodeString(value)
if err != nil {
panic(err)
}
return data
}
func buildClientInfoPDU(cfg Config) []byte {
info := buildRDPInfo(cfg)
out := bytes.Buffer{}
writeLE16(&out, secFlagInfoPkt)
writeLE16(&out, 0)
out.Write(info)
return out.Bytes()
}
func buildRDPInfo(cfg Config) []byte {
domain := utf16LENull(cfg.Domain)
user := utf16LENull(cfg.Username)
password := utf16LENull(cfg.Password)
alternateShell := []byte{0, 0}
workingDir := []byte{0, 0}
flags := uint32(0x00000001 | 0x00000002 | 0x00000008 | 0x00000010 | 0x00000020 | 0x00000040 | 0x00000100 | 0x00004000 | 0x00010000 | 0x00020000)
if cfg.MCSMode == "plain-info" || cfg.MCSMode == "step-plain-info" {
flags = 0x00000001 | 0x00000002 | 0x00000008 | 0x00004000
}
out := bytes.Buffer{}
writeLE32(&out, 0)
writeLE32(&out, flags)
writeLE16(&out, uint16(len(domain)-2))
writeLE16(&out, uint16(len(user)-2))
writeLE16(&out, uint16(len(password)-2))
writeLE16(&out, uint16(len(alternateShell)-2))
writeLE16(&out, uint16(len(workingDir)-2))
out.Write(domain)
out.Write(user)
out.Write(password)
out.Write(alternateShell)
out.Write(workingDir)
out.Write(buildExtendedInfo())
return out.Bytes()
}
func buildExtendedInfo() []byte {
clientAddress := []byte{0, 0}
clientDir := []byte{0, 0}
out := bytes.Buffer{}
writeLE16(&out, 0x0002)
writeLE16(&out, uint16(len(clientAddress)))
out.Write(clientAddress)
writeLE16(&out, uint16(len(clientDir)))
out.Write(clientDir)
out.Write(make([]byte, 172))
writeLE32(&out, 0)
writeLE32(&out, 0x00000001|0x00000002|0x00000004|0x00000080|0x00000100)
return out.Bytes()
}
func buildMCSConnectInitial(cfg Config) []byte {
userData := bytes.Buffer{}
userData.Write(buildGCCClientCore(cfg))
userData.Write(buildGCCClientNetwork())
userData.Write(buildGCCClientSecurity())
userData.Write(buildGCCClientMessageChannel())
conference := buildGCCConferenceCreate(userData.Bytes())
body := bytes.Buffer{}
writeBEROctetString(bodyWriter(&body), []byte{0x01})
writeBEROctetString(bodyWriter(&body), []byte{0x01})
writeBERBoolean(bodyWriter(&body), true)
writeBERDomainParameters(bodyWriter(&body), 34, 2, 0, 0xffff)
writeBERDomainParameters(bodyWriter(&body), 1, 1, 1, 0x0420)
writeBERDomainParameters(bodyWriter(&body), 0xffff, 0xfc17, 0xffff, 0xffff)
writeBEROctetString(bodyWriter(&body), conference)
out := bytes.Buffer{}
writeBERApplicationTag(bodyWriter(&out), mcsConnectInitial, body.Len())
out.Write(body.Bytes())
return out.Bytes()
}
func buildGCCClientCore(cfg Config) []byte {
width := cfg.Width
if width <= 0 {
width = 1280
}
height := cfg.Height
if height <= 0 {
height = 800
}
highColorDepth := uint16(24)
supportedColorDepths := uint16(0x000f)
if cfg.MCSMode == "bpp32" || cfg.MCSMode == "step-bpp32" || cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" {
highColorDepth = 32
supportedColorDepths = 0x001f
}
body := bytes.Buffer{}
writeLE32(&body, 0x00080007)
writeLE16(&body, uint16(width))
writeLE16(&body, uint16(height))
writeLE16(&body, 0xCA01)
writeLE16(&body, 0xAA03)
writeLE32(&body, 0x00000409)
writeLE32(&body, 22621)
body.Write(fixedUTF16LE("AIOPS", 32))
writeLE32(&body, 4)
writeLE32(&body, 0)
writeLE32(&body, 12)
body.Write(make([]byte, 64))
writeLE16(&body, 0xCA01)
writeLE16(&body, 1)
writeLE32(&body, 0)
writeLE16(&body, highColorDepth)
writeLE16(&body, supportedColorDepths)
writeLE16(&body, 0x00a3)
body.Write(make([]byte, 64))
body.WriteByte(0x06)
body.WriteByte(0)
writeLE32(&body, transport.ProtocolCredSSP)
return withGCCBlock(gccCSCore, body.Bytes())
}
func buildGCCClientNetwork() []byte {
body := bytes.Buffer{}
writeLE32(&body, 0)
return withGCCBlock(gccCSNet, body.Bytes())
}
func buildGCCClientSecurity() []byte {
body := bytes.Buffer{}
writeLE32(&body, 0x0000000b)
writeLE32(&body, 0)
return withGCCBlock(gccCSSecurity, body.Bytes())
}
func buildGCCClientMessageChannel() []byte {
body := bytes.Buffer{}
writeLE32(&body, 0)
return withGCCBlock(0xC006, body.Bytes())
}
func withGCCBlock(blockType uint16, body []byte) []byte {
out := bytes.Buffer{}
writeLE16(&out, blockType)
writeLE16(&out, uint16(len(body)+4))
out.Write(body)
return out.Bytes()
}
func buildGCCConferenceCreate(userData []byte) []byte {
out := bytes.Buffer{}
writePERChoice(&out, 0)
writePERObjectIdentifier(&out, []byte{0, 0, 20, 124, 0, 1})
writePERLength(&out, len(userData)+14)
writePERChoice(&out, 0)
out.WriteByte(0x08)
writePERNumericString(&out, "1", 1)
out.WriteByte(0)
out.WriteByte(1)
writePERChoice(&out, 0xc0)
writePEROctetStream(&out, []byte("Duca"), 4)
writePEROctetStream(&out, userData, 0)
return out.Bytes()
}
func parseMCSConnectResponse(data []byte) (MCSProbeInfo, error) {
r := bytes.NewReader(data)
if err := readBERApplicationTag(r, mcsConnectResponse); err != nil {
return MCSProbeInfo{}, err
}
if _, err := readBEREnumerated(r); err != nil {
return MCSProbeInfo{}, err
}
if _, err := readBERInteger(r); err != nil {
return MCSProbeInfo{}, err
}
if err := skipBERDomainParameters(r); err != nil {
return MCSProbeInfo{}, err
}
userData, err := readBEROctetString(r)
if err != nil {
return MCSProbeInfo{}, err
}
info := MCSProbeInfo{UserDataBytes: len(userData)}
parseGCCConferenceResponse(userData, &info)
return info, nil
}
func parseGCCConferenceResponse(data []byte, info *MCSProbeInfo) {
r := bytes.NewReader(data)
_, _ = r.ReadByte()
_ = readPERObjectIdentifier(r, []byte{0, 0, 20, 124, 0, 1})
_, _ = readPERLength(r)
_, _ = r.ReadByte()
_, _ = readBE16(r)
_, _ = readPERInteger(r)
_, _ = r.ReadByte()
_, _ = r.ReadByte()
_, _ = r.ReadByte()
if !readPEROctetStreamMatch(r, []byte("McDn"), 4) {
return
}
total, _ := readPERLength(r)
for total >= 4 && r.Len() >= 4 {
blockType, _ := readLE16(r)
blockLen, _ := readLE16(r)
if blockLen < 4 || int(blockLen-4) > r.Len() {
return
}
body := make([]byte, int(blockLen-4))
_, _ = io.ReadFull(r, body)
total -= blockLen
parseGCCServerBlock(blockType, body, info)
}
}
func parseGCCServerBlock(blockType uint16, body []byte, info *MCSProbeInfo) {
r := bytes.NewReader(body)
switch blockType {
case gccSCCore:
info.ServerCoreVersion, _ = readLE32(r)
case gccSCSecurity:
info.ServerEncryption, _ = readLE32(r)
info.ServerEncryptionLvl, _ = readLE32(r)
case gccSCNet:
info.ServerChannelID, _ = readLE16(r)
info.ServerChannelCount, _ = readLE16(r)
for i := 0; i < int(info.ServerChannelCount) && r.Len() >= 2; i++ {
channelID, _ := readLE16(r)
info.ServerChannelIDs = append(info.ServerChannelIDs, channelID)
}
case 0x0C04:
info.MessageChannelID, _ = readLE16(r)
}
}
type bodyWriter interface {
Write([]byte) (int, error)
WriteByte(byte) error
}
func writeBERApplicationTag(w bodyWriter, tag int, size int) {
w.WriteByte(0x7f)
w.WriteByte(byte(tag))
writeBERLength(w, size)
}
func readBERApplicationTag(r *bytes.Reader, tag int) error {
first, err := r.ReadByte()
if err != nil {
return err
}
if first != 0x7f {
return fmt.Errorf("MCS BER application tag 头错误: 0x%x", first)
}
got, err := r.ReadByte()
if err != nil {
return err
}
if int(got) != tag {
return fmt.Errorf("MCS BER application tag 不匹配: 0x%x", got)
}
_, err = readBERLength(r)
return err
}
func writeBERDomainParameters(w bodyWriter, maxChannelIDs, maxUserIDs, maxTokenIDs, maxPDUSize int) {
body := bytes.Buffer{}
writeBERInteger(&body, maxChannelIDs)
writeBERInteger(&body, maxUserIDs)
writeBERInteger(&body, maxTokenIDs)
writeBERInteger(&body, 1)
writeBERInteger(&body, 0)
writeBERInteger(&body, 1)
writeBERInteger(&body, maxPDUSize)
writeBERInteger(&body, 2)
w.WriteByte(0x30)
writeBERLength(w, body.Len())
w.Write(body.Bytes())
}
func skipBERDomainParameters(r *bytes.Reader) error {
tag, err := r.ReadByte()
if err != nil {
return err
}
if tag != 0x30 {
return fmt.Errorf("MCS DomainParameters tag 错误: 0x%x", tag)
}
size, err := readBERLength(r)
if err != nil {
return err
}
if size > r.Len() {
return io.ErrUnexpectedEOF
}
_, err = r.Seek(int64(size), io.SeekCurrent)
return err
}
func writeBEROctetString(w bodyWriter, data []byte) {
w.WriteByte(0x04)
writeBERLength(w, len(data))
w.Write(data)
}
func readBEROctetString(r *bytes.Reader) ([]byte, error) {
tag, err := r.ReadByte()
if err != nil {
return nil, err
}
if tag != 0x04 {
return nil, fmt.Errorf("MCS octet string tag 错误: 0x%x", tag)
}
size, err := readBERLength(r)
if err != nil {
return nil, err
}
if size > r.Len() {
return nil, io.ErrUnexpectedEOF
}
out := make([]byte, size)
_, err = io.ReadFull(r, out)
return out, err
}
func writeBERBoolean(w bodyWriter, value bool) {
w.WriteByte(0x01)
w.WriteByte(1)
if value {
w.WriteByte(0xff)
return
}
w.WriteByte(0)
}
func writeBERInteger(w bodyWriter, value int) {
w.WriteByte(0x02)
switch {
case value <= 0xff:
w.WriteByte(1)
w.WriteByte(byte(value))
case value <= 0xffff:
w.WriteByte(2)
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(value))
w.Write(b[:])
default:
w.WriteByte(4)
var b [4]byte
binary.BigEndian.PutUint32(b[:], uint32(value))
w.Write(b[:])
}
}
func readBERInteger(r *bytes.Reader) (int, error) {
tag, err := r.ReadByte()
if err != nil {
return 0, err
}
if tag != 0x02 {
return 0, fmt.Errorf("MCS integer tag 错误: 0x%x", tag)
}
size, err := readBERLength(r)
if err != nil {
return 0, err
}
if size > 4 || size > r.Len() {
return 0, io.ErrUnexpectedEOF
}
raw := make([]byte, size)
_, _ = io.ReadFull(r, raw)
value := 0
for _, b := range raw {
value = value<<8 | int(b)
}
return value, nil
}
func readBEREnumerated(r *bytes.Reader) (byte, error) {
tag, err := r.ReadByte()
if err != nil {
return 0, err
}
if tag != 0x0a {
return 0, fmt.Errorf("MCS enumerated tag 错误: 0x%x", tag)
}
size, err := readBERLength(r)
if err != nil {
return 0, err
}
if size != 1 {
return 0, fmt.Errorf("MCS enumerated 长度错误: %d", size)
}
return r.ReadByte()
}
func writeBERLength(w bodyWriter, size int) {
if size > 0x7f {
w.WriteByte(0x82)
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(size))
w.Write(b[:])
return
}
w.WriteByte(byte(size))
}
func readBERLength(r *bytes.Reader) (int, error) {
first, err := r.ReadByte()
if err != nil {
return 0, err
}
if first&0x80 == 0 {
return int(first), nil
}
count := int(first & 0x7f)
if count == 0 || count > 2 || count > r.Len() {
return 0, fmt.Errorf("BER length 字节数非法: %d", count)
}
value := 0
for i := 0; i < count; i++ {
b, _ := r.ReadByte()
value = value<<8 | int(b)
}
return value, nil
}
func writePERChoice(w bodyWriter, choice byte) {
w.WriteByte(choice)
}
func writePERInteger(w bodyWriter, value int) {
if value <= 0xff {
writePERLength(w, 1)
w.WriteByte(byte(value))
return
}
if value <= 0xffff {
writePERLength(w, 2)
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(value))
w.Write(b[:])
return
}
writePERLength(w, 4)
var b [4]byte
binary.BigEndian.PutUint32(b[:], uint32(value))
w.Write(b[:])
}
func writePERObjectIdentifier(w bodyWriter, oid []byte) {
w.WriteByte(5)
w.WriteByte((oid[0] << 4) | (oid[1] & 0x0f))
w.Write(oid[2:6])
}
func readPERObjectIdentifier(r *bytes.Reader, oid []byte) bool {
size, err := r.ReadByte()
if err != nil || size != 5 || r.Len() < 5 {
return false
}
raw := make([]byte, 5)
_, _ = io.ReadFull(r, raw)
got := []byte{raw[0] >> 4, raw[0] & 0x0f, raw[1], raw[2], raw[3], raw[4]}
return bytes.Equal(got, oid)
}
func writePERLength(w bodyWriter, size int) {
if size > 0x7f {
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(size|0x8000))
w.Write(b[:])
return
}
w.WriteByte(byte(size))
}
func readPERLength(r *bytes.Reader) (uint16, error) {
first, err := r.ReadByte()
if err != nil {
return 0, err
}
if first&0x80 == 0 {
return uint16(first), nil
}
next, err := r.ReadByte()
if err != nil {
return 0, err
}
return uint16(first&0x7f)<<8 | uint16(next), nil
}
func writePERNumericString(w bodyWriter, s string, minValue int) {
length := len(s)
encodedLength := minValue
if length >= minValue {
encodedLength = length - minValue
}
writePERLength(w, encodedLength)
for i := 0; i < length; i += 2 {
left := (s[i] - '0') % 10
right := byte(0)
if i+1 < length {
right = (s[i+1] - '0') % 10
}
w.WriteByte((left << 4) | right)
}
}
func writePEROctetStream(w bodyWriter, data []byte, minValue int) {
encodedLength := minValue
if len(data) >= minValue {
encodedLength = len(data) - minValue
}
writePERLength(w, encodedLength)
w.Write(data)
}
func readPEROctetStreamMatch(r *bytes.Reader, expected []byte, minValue int) bool {
encodedLength, err := readPERLength(r)
if err != nil {
return false
}
size := int(encodedLength) + minValue
if size != len(expected) || size > r.Len() {
return false
}
raw := make([]byte, size)
_, _ = io.ReadFull(r, raw)
return bytes.Equal(raw, expected)
}
func readPERInteger(r *bytes.Reader) (uint32, error) {
size, err := readPERLength(r)
if err != nil {
return 0, err
}
if int(size) > r.Len() {
return 0, io.ErrUnexpectedEOF
}
switch size {
case 1:
b, _ := r.ReadByte()
return uint32(b), nil
case 2:
return readBE16(r)
case 4:
return readBE32(r)
default:
_, _ = r.Seek(int64(size), io.SeekCurrent)
return 0, nil
}
}
func fixedUTF16LE(value string, size int) []byte {
out := make([]byte, size)
encoded := utf16.Encode([]rune(value))
for i, r := range encoded {
offset := i * 2
if offset+1 >= len(out) {
break
}
binary.LittleEndian.PutUint16(out[offset:offset+2], r)
}
return out
}
func utf16LENull(value string) []byte {
out := bytes.Buffer{}
for _, r := range utf16.Encode([]rune(value)) {
writeLE16(&out, r)
}
writeLE16(&out, 0)
return out.Bytes()
}
func writeLE16(w io.Writer, value uint16) {
var b [2]byte
binary.LittleEndian.PutUint16(b[:], value)
_, _ = w.Write(b[:])
}
func writeLE32(w io.Writer, value uint32) {
var b [4]byte
binary.LittleEndian.PutUint32(b[:], value)
_, _ = w.Write(b[:])
}
func readLE16(r io.Reader) (uint16, error) {
var b [2]byte
_, err := io.ReadFull(r, b[:])
return binary.LittleEndian.Uint16(b[:]), err
}
func readLE32(r io.Reader) (uint32, error) {
var b [4]byte
_, err := io.ReadFull(r, b[:])
return binary.LittleEndian.Uint32(b[:]), err
}
func readBE16(r io.Reader) (uint32, error) {
var b [2]byte
_, err := io.ReadFull(r, b[:])
return uint32(binary.BigEndian.Uint16(b[:])), err
}
func readBE32(r io.Reader) (uint32, error) {
var b [4]byte
_, err := io.ReadFull(r, b[:])
return binary.BigEndian.Uint32(b[:]), err
}
func writeMCSPDUHeader(w bodyWriter, pduType byte) {
w.WriteByte(pduType << 2)
}
func readMCSPDUHeader(r *bytes.Reader) (byte, error) {
header, err := r.ReadByte()
if err != nil {
return 0, err
}
return header >> 2, nil
}
func writeBE16Value(w bodyWriter, value uint16) {
var b [2]byte
binary.BigEndian.PutUint16(b[:], value)
w.Write(b[:])
}
func readBE16Value(r io.Reader) (uint16, error) {
var b [2]byte
_, err := io.ReadFull(r, b[:])
return binary.BigEndian.Uint16(b[:]), err
}