主要变更: - 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>
1665 lines
47 KiB
Go
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
|
|
}
|