init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
package pdu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// buildSurfCmd 构造一条 SET/STREAM_SURFACE_BITS 命令。
|
||||
// win10=true 用 Win10 实测 22 字节头(codecID@13),否则用
|
||||
// FreeRDP 经典 21 字节头(codecID@12)。
|
||||
func buildSurfCmd(cmdType uint16, codecID, bpp byte, w, h uint16, payload []byte, win10 bool) []byte {
|
||||
hdrLen := 21
|
||||
extra := 0
|
||||
if win10 {
|
||||
hdrLen = 22
|
||||
extra = 1
|
||||
}
|
||||
buf := make([]byte, hdrLen+len(payload))
|
||||
binary.LittleEndian.PutUint16(buf[0:], cmdType)
|
||||
binary.LittleEndian.PutUint16(buf[2:], 10) // destLeft
|
||||
binary.LittleEndian.PutUint16(buf[4:], 20) // destTop
|
||||
binary.LittleEndian.PutUint16(buf[6:], 10+w-1) // destRight
|
||||
binary.LittleEndian.PutUint16(buf[8:], 20+h-1) // destBottom
|
||||
buf[10] = bpp // bpp
|
||||
buf[11] = 0 // reserved
|
||||
buf[12+extra] = codecID
|
||||
binary.LittleEndian.PutUint16(buf[13+extra:], w)
|
||||
binary.LittleEndian.PutUint16(buf[15+extra:], h)
|
||||
binary.LittleEndian.PutUint32(buf[17+extra:], uint32(len(payload)))
|
||||
copy(buf[hdrLen:], payload)
|
||||
return buf
|
||||
}
|
||||
|
||||
func buildFrameMarker(action uint16, frameID uint32) []byte {
|
||||
buf := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint16(buf[0:], CMDTYPE_FRAME_MARKER)
|
||||
binary.LittleEndian.PutUint16(buf[2:], action)
|
||||
binary.LittleEndian.PutUint32(buf[4:], frameID)
|
||||
return buf
|
||||
}
|
||||
|
||||
// Win10 实测 22 字节头布局回归:cmdType(2)+dest(8)+bpp(1)+?@11+?@12+
|
||||
// codecID@13+width@14+height@16+len u32@18。偏移若错位,
|
||||
// codecID/width/length 断言即失败。
|
||||
func TestParseSurfaceCommandsWin10Layout(t *testing.T) {
|
||||
payload := make([]byte, 2*2*4) // 2x2 32bpp 原始像素
|
||||
buf := buildSurfCmd(CMDTYPE_SET_SURFACE_BITS, 0, 32, 2, 2, payload, true)
|
||||
|
||||
result, consumed, needMore, valid, _, _ := parseSurfaceCommandsIncremental(buf, 0)
|
||||
if !valid || needMore {
|
||||
t.Fatalf("valid=%v needMore=%v", valid, needMore)
|
||||
}
|
||||
if consumed != len(buf) {
|
||||
t.Fatalf("consumed=%d, want %d", consumed, len(buf))
|
||||
}
|
||||
if len(result.Rects) != 1 {
|
||||
t.Fatalf("rects=%d, want 1", len(result.Rects))
|
||||
}
|
||||
r := result.Rects[0]
|
||||
if r.Width != 2 || r.Height != 2 || r.BitsPerPixel != 32 {
|
||||
t.Fatalf("size=%dx%d bpp=%d, want 2x2/32", r.Width, r.Height, r.BitsPerPixel)
|
||||
}
|
||||
if r.DestLeft != 10 || r.DestTop != 20 {
|
||||
t.Fatalf("dest=(%d,%d), want (10,20)", r.DestLeft, r.DestTop)
|
||||
}
|
||||
}
|
||||
|
||||
// FreeRDP 经典 21 字节头(codecID@12)的自适应回退。
|
||||
func TestParseSurfaceCommandsClassicLayout(t *testing.T) {
|
||||
buf := buildSurfCmd(CMDTYPE_SET_SURFACE_BITS, 0, 32, 2, 2,
|
||||
make([]byte, 2*2*4), false)
|
||||
result, consumed, needMore, valid, _, _ := parseSurfaceCommandsIncremental(buf, 0)
|
||||
if !valid || needMore || consumed != len(buf) {
|
||||
t.Fatalf("valid=%v needMore=%v consumed=%d/%d", valid, needMore, consumed, len(buf))
|
||||
}
|
||||
if len(result.Rects) != 1 {
|
||||
t.Fatalf("rects=%d, want 1", len(result.Rects))
|
||||
}
|
||||
if result.Rects[0].Width != 2 || result.Rects[0].Height != 2 {
|
||||
t.Fatalf("size=%dx%d, want 2x2", result.Rects[0].Width, result.Rects[0].Height)
|
||||
}
|
||||
}
|
||||
|
||||
// 真实抓包(Win10 19041, 24bpp 会话, 64x64 NSCodec 瓦片):
|
||||
// 平面长度 4096+448+339+0=4883, bitmapDataLength=4903=20+4883。
|
||||
func TestParseSurfaceCommandsRealCapture(t *testing.T) {
|
||||
// 真实命令前 48 字节(SURFCMD_DUMP 捕获),payload 截取自转储
|
||||
head := []byte{
|
||||
0x01, 0x00, 0x40, 0x04, 0x40, 0x02, 0x80, 0x04, 0x80, 0x02,
|
||||
0x20, 0x00, 0x00, 0x01, 0x40, 0x00, 0x40, 0x00, 0x27, 0x13,
|
||||
0x00, 0x00,
|
||||
}
|
||||
nsHdr := []byte{ // NSCodec 流头:四平面长度+颜色参数
|
||||
0x00, 0x10, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00,
|
||||
0x53, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0x01, 0x00, 0x00,
|
||||
}
|
||||
// bitmapData = NSCodec 流头(20) + 四平面数据(4883) = 4903
|
||||
buf := append(head, nsHdr...)
|
||||
buf = append(buf, make([]byte, 4883)...)
|
||||
if len(buf) != 22+4903 {
|
||||
t.Fatalf("capture length %d, want %d", len(buf), 22+4903)
|
||||
}
|
||||
result, consumed, needMore, valid, _, _ := parseSurfaceCommandsIncremental(buf, 0)
|
||||
if !valid || needMore {
|
||||
t.Fatalf("valid=%v needMore=%v", valid, needMore)
|
||||
}
|
||||
if consumed != len(buf) {
|
||||
t.Fatalf("consumed=%d, want %d", consumed, len(buf))
|
||||
}
|
||||
if len(result.Rects) != 1 {
|
||||
t.Fatalf("rects=%d, want 1 (codecID=1 NSCodec)", len(result.Rects))
|
||||
}
|
||||
r := result.Rects[0]
|
||||
if r.DestLeft != 1088 || r.DestTop != 576 || r.Width != 64 || r.Height != 64 {
|
||||
t.Fatalf("rect=(%d,%d %dx%d), want (1088,576 64x64)",
|
||||
r.DestLeft, r.DestTop, r.Width, r.Height)
|
||||
}
|
||||
if r.BitsPerPixel != 32 {
|
||||
t.Fatalf("bpp=%d, want 32 (NSCodec 输出)", r.BitsPerPixel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSurfaceCommandsRoundtrip(t *testing.T) {
|
||||
payload := make([]byte, 2*2*4) // 2x2 32bpp 原始像素
|
||||
buf := append(buildFrameMarker(SURFCMD_FRAMEACTION_BEGIN, 7),
|
||||
buildSurfCmd(CMDTYPE_SET_SURFACE_BITS, 0, 32, 2, 2, payload, true)...)
|
||||
buf = append(buf, buildFrameMarker(SURFCMD_FRAMEACTION_END, 7)...)
|
||||
|
||||
result, consumed, needMore, valid, _, _ := parseSurfaceCommandsIncremental(buf, 0)
|
||||
if !valid {
|
||||
t.Fatal("buffer should parse as valid command stream")
|
||||
}
|
||||
if needMore {
|
||||
t.Fatal("complete stream should not need more data")
|
||||
}
|
||||
if consumed != len(buf) {
|
||||
t.Fatalf("consumed=%d, want %d", consumed, len(buf))
|
||||
}
|
||||
if len(result.Rects) != 1 {
|
||||
t.Fatalf("rects=%d, want 1", len(result.Rects))
|
||||
}
|
||||
r := result.Rects[0]
|
||||
if r.DestLeft != 10 || r.DestTop != 20 {
|
||||
t.Fatalf("dest=(%d,%d), want (10,20)", r.DestLeft, r.DestTop)
|
||||
}
|
||||
if r.Width != 2 || r.Height != 2 {
|
||||
t.Fatalf("size=%dx%d, want 2x2", r.Width, r.Height)
|
||||
}
|
||||
if r.BitsPerPixel != 32 {
|
||||
t.Fatalf("bpp=%d, want 32", r.BitsPerPixel)
|
||||
}
|
||||
if len(result.FrameIDs) != 1 || result.FrameIDs[0] != 7 {
|
||||
t.Fatalf("frameIDs=%v, want [7]", result.FrameIDs)
|
||||
}
|
||||
}
|
||||
|
||||
// Win10 会把一条命令拆到多个 fast-path PDU:前半段必须 needMore(且
|
||||
// 缓冲不得被调用方丢弃),后半段到齐后完整解析且 consumed 精确。
|
||||
func TestParseSurfaceCommandsSplitAcrossSegments(t *testing.T) {
|
||||
cmd := buildSurfCmd(CMDTYPE_STREAM_SURFACE_BITS, 0, 32, 4, 2,
|
||||
make([]byte, 4*2*4), true)
|
||||
cut := 24 // 落在 payload 中间
|
||||
|
||||
r1, c1, more1, valid1, _, _ := parseSurfaceCommandsIncremental(cmd[:cut], 0)
|
||||
if !valid1 || !more1 || c1 != 0 || len(r1.Rects) != 0 {
|
||||
t.Fatalf("seg1: valid=%v more=%v consumed=%d rects=%d",
|
||||
valid1, more1, c1, len(r1.Rects))
|
||||
}
|
||||
|
||||
result, consumed, needMore, valid, _, _ := parseSurfaceCommandsIncremental(cmd, 0)
|
||||
if !valid || needMore {
|
||||
t.Fatalf("seg2: valid=%v needMore=%v", valid, needMore)
|
||||
}
|
||||
if consumed != len(cmd) || len(result.Rects) != 1 {
|
||||
t.Fatalf("seg2: consumed=%d/%d rects=%d",
|
||||
consumed, len(cmd), len(result.Rects))
|
||||
}
|
||||
}
|
||||
|
||||
// codecID=1 (NSCodec) 的 payload 即使解码失败,分帧也必须精确消费,
|
||||
// 否则累积缓冲错位后整条流报废(黑屏的伴随症状)。
|
||||
func TestParseSurfaceCommandsNSCodecFraming(t *testing.T) {
|
||||
cmd := buildSurfCmd(CMDTYPE_SET_SURFACE_BITS, 1, 32, 800, 480,
|
||||
make([]byte, 100), true) // 非 NSCodec 合法数据 → 解码可能失败
|
||||
result, consumed, needMore, valid, _, _ := parseSurfaceCommandsIncremental(cmd, 0)
|
||||
if !valid || needMore {
|
||||
t.Fatalf("valid=%v needMore=%v", valid, needMore)
|
||||
}
|
||||
if consumed != len(cmd) {
|
||||
t.Fatalf("consumed=%d, want %d", consumed, len(cmd))
|
||||
}
|
||||
if len(result.Rects) > 1 {
|
||||
t.Fatalf("rects=%d, want at most 1", len(result.Rects))
|
||||
}
|
||||
}
|
||||
|
||||
// 长度字段读到离谱值时应判无效并丢弃缓冲,而不是无限累积。
|
||||
func TestParseSurfaceCommandsRejectsBadLength(t *testing.T) {
|
||||
cmd := buildSurfCmd(CMDTYPE_SET_SURFACE_BITS, 0, 32, 2, 2,
|
||||
make([]byte, 16), true)
|
||||
binary.LittleEndian.PutUint32(cmd[18:], 1<<30) // 1GB,非法
|
||||
_, _, _, valid, _, _ := parseSurfaceCommandsIncremental(cmd, 22)
|
||||
if valid {
|
||||
t.Fatal("absurd bitmapDataLength should invalidate the buffer")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFastPathFragmentConstants 锁定分片字段的线路值:MS-RDPBCGR
|
||||
// 2.2.9.1.1.3.1 / FreeRDP fastpath.h 枚举(SINGLE=0, LAST=1, FIRST=2,
|
||||
// NEXT=3)左移 4 位。此前 FIRST/NEXT/LAST 三个值轮换错位——首片被当
|
||||
// 孤儿丢弃、续片提前冲刷、尾片被缓存到下一条更新,是 24bpp 花屏与
|
||||
// surface cmd resync 的总根源。
|
||||
func TestFastPathFragmentConstants(t *testing.T) {
|
||||
if FASTPATH_FRAGMENT_SINGLE != 0x00 {
|
||||
t.Fatalf("SINGLE = %#x, want 0x00", FASTPATH_FRAGMENT_SINGLE)
|
||||
}
|
||||
if FASTPATH_FRAGMENT_LAST != 0x10 {
|
||||
t.Fatalf("LAST = %#x, want 0x10", FASTPATH_FRAGMENT_LAST)
|
||||
}
|
||||
if FASTPATH_FRAGMENT_FIRST != 0x20 {
|
||||
t.Fatalf("FIRST = %#x, want 0x20", FASTPATH_FRAGMENT_FIRST)
|
||||
}
|
||||
if FASTPATH_FRAGMENT_NEXT != 0x30 {
|
||||
t.Fatalf("NEXT = %#x, want 0x30", FASTPATH_FRAGMENT_NEXT)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFastPathPointerNewLayout 用 Win10 实测指针字节锁定 TS_POINTER_NEW
|
||||
// 布局:xorBpp u16 首字段 + 全 u16 颜色指针属性(FreeRDP
|
||||
// update_read_pointer_new → s_update_read_pointer_color)。此前漏读
|
||||
// xorBpp 导致整体后移 2 字节,指针宽高/掩码长度全错位、光标无法显示。
|
||||
func TestFastPathPointerNewLayout(t *testing.T) {
|
||||
raw := []byte{
|
||||
0x20, 0x00, // xorBpp = 32
|
||||
0x00, 0x00, // cacheIndex = 0
|
||||
0x03, 0x00, // hotSpot.xPos = 3
|
||||
0x03, 0x00, // hotSpot.yPos = 3
|
||||
0x29, 0x00, // width = 41
|
||||
0x27, 0x00, // height = 39
|
||||
0xEA, 0x00, // lengthAndMask = 234 = ((41+15)/16)*2 * 39
|
||||
0xFC, 0x18, // lengthXorMask = 6396 = ((41*32+15)/16)*2 * 39
|
||||
}
|
||||
raw = append(raw, make([]byte, 234+6396)...)
|
||||
p := &FastPathUpdatePointerPDU{}
|
||||
if err := p.Unpack(bytes.NewReader(raw)); err != nil {
|
||||
t.Fatalf("Unpack: %v", err)
|
||||
}
|
||||
if p.XorBpp != 32 || p.CacheIdx != 0 || p.HotX != 3 || p.HotY != 3 ||
|
||||
p.Width != 41 || p.Height != 39 {
|
||||
t.Fatalf("header fields wrong: %+v", p)
|
||||
}
|
||||
if p.MaskLen != 234 || len(p.Mask) != 234 {
|
||||
t.Fatalf("AND mask: len=%d MaskLen=%d, want 234", len(p.Mask), p.MaskLen)
|
||||
}
|
||||
if p.XorLen != 6396 || len(p.Data) != 6396 {
|
||||
t.Fatalf("XOR mask: len=%d XorLen=%d, want 6396", len(p.Data), p.XorLen)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseSurfaceCommandsDropsUnknownTail 验证尾部未知结构(实测 Win10
|
||||
// 整屏重绘批次末尾的 11 字节尾巴)不再拖垮整批:前缀命令照常解析上屏。
|
||||
func TestParseSurfaceCommandsDropsUnknownTail(t *testing.T) {
|
||||
cmd := buildSurfCmd(CMDTYPE_SET_SURFACE_BITS, 0, 32, 2, 2,
|
||||
make([]byte, 16), true)
|
||||
bad := []byte{0x80, 0x00, 0x05, 0x00, 0x01, 0x00, 0x36, 0x01, 0x00, 0x00, 0x00}
|
||||
buf := append(append([]byte{}, cmd...), bad...)
|
||||
result, consumed, needMore, valid, dropped, _ := parseSurfaceCommandsIncremental(buf, 22)
|
||||
if !valid {
|
||||
t.Fatal("合法前缀不应判无效")
|
||||
}
|
||||
if !dropped {
|
||||
t.Fatal("未知尾巴应标记 dropped")
|
||||
}
|
||||
if needMore {
|
||||
t.Fatal("丢弃尾巴后不应再等待更多数据")
|
||||
}
|
||||
if consumed != len(cmd) {
|
||||
t.Fatalf("consumed=%d,期望 %d", consumed, len(cmd))
|
||||
}
|
||||
if len(result.Rects) != 1 {
|
||||
t.Fatalf("前缀命令应产出 1 个矩形,实得 %d", len(result.Rects))
|
||||
}
|
||||
}
|
||||
|
||||
// TestPersistentKeyListPDU:键按单元容量分配、头 20 字节布局、
|
||||
// bBitMask=FIRST|LAST、键条目 uint64 小端,超 2042 键在发送层截断
|
||||
// (此处仅验证 PDU 自身,截断在 maybeSendPersistentKeyList)。
|
||||
func TestPersistentKeyListPDU(t *testing.T) {
|
||||
// 2000 键、单元容量 600/1024/4096/0/0 → 600+1024+376=2000
|
||||
keys := make([]uint64, 2000)
|
||||
for i := range keys {
|
||||
keys[i] = uint64(i) + 0x10000
|
||||
}
|
||||
cells := [5]uint16{600, 1024, 4096, 0, 0}
|
||||
m := NewPersistentKeyListPDU(0x103EA, keys, cells)
|
||||
if m.numEntries != [5]uint16{600, 1024, 376, 0, 0} {
|
||||
t.Fatalf("numEntries=%v", m.numEntries)
|
||||
}
|
||||
b := m.Serialize()
|
||||
// ShareDataHeader(12) + 头 24(num/total×10+bBitMask+pad)+ 2000×8
|
||||
if len(b) != 12+24+2000*8 {
|
||||
t.Fatalf("len=%d", len(b))
|
||||
}
|
||||
if b[12+20] != 0x03 || b[12+21] != 0 || b[12+22] != 0 {
|
||||
t.Fatalf("bBitMask/pad wrong: %v", b[12+20:12+23])
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(b[12:]); got != 600 {
|
||||
t.Fatalf("numEntriesCache0=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(b[12+10:]); got != 600 {
|
||||
t.Fatalf("totalEntriesCache0=%d", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(b[12+4:]); got != 376 {
|
||||
t.Fatalf("numEntriesCache2=%d", got)
|
||||
}
|
||||
// 第一条键
|
||||
if got := binary.LittleEndian.Uint64(b[12+24:]); got != 0x10000 {
|
||||
t.Fatalf("first key=%x", got)
|
||||
}
|
||||
// 空键列表也合法(totalEntries 全 0)
|
||||
empty := NewPersistentKeyListPDU(0x103EA, nil, cells).Serialize()
|
||||
if len(empty) != 12+24 {
|
||||
t.Fatalf("empty len=%d", len(empty))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user