init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
package rdpgfx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// fakeStore 记录 Persist 调用并回放固定条目,用于验证持久缓存桥。
|
||||
type fakeStore struct {
|
||||
persisted []GfxCacheEntry
|
||||
export []GfxCacheEntry
|
||||
}
|
||||
|
||||
func (s *fakeStore) Persist(key uint64, w, h int, bpp uint16, data []byte) {
|
||||
cp := make([]byte, len(data))
|
||||
copy(cp, data)
|
||||
s.persisted = append(s.persisted, GfxCacheEntry{Key: key, Width: w, Height: h, Bpp: bpp, Data: cp})
|
||||
}
|
||||
|
||||
func (s *fakeStore) Export() []GfxCacheEntry { return s.export }
|
||||
|
||||
func (s *fakeStore) Get(key uint64) (GfxCacheEntry, bool) {
|
||||
for _, e := range s.export {
|
||||
if e.Key == key {
|
||||
return e, true
|
||||
}
|
||||
}
|
||||
return GfxCacheEntry{}, false
|
||||
}
|
||||
|
||||
func (s *fakeStore) Keys() []uint64 {
|
||||
out := make([]uint64, 0, len(s.export))
|
||||
for _, e := range s.export {
|
||||
out = append(out, e.Key)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// newCacheTestHandler 返回带捕获 sendFn 的最小处理器(不经 NewGfxHandler,
|
||||
// 避免拉起解码/写循环 goroutine)。
|
||||
func newCacheTestHandler() (*GfxHandler, *[][]byte) {
|
||||
sent := &[][]byte{}
|
||||
g := &GfxHandler{
|
||||
surfaces: make(map[uint16]*surface),
|
||||
cacheEntries: make(map[uint16]cacheEntry),
|
||||
sendFn: func(b []byte) {
|
||||
cp := make([]byte, len(b))
|
||||
copy(cp, b)
|
||||
*sent = append(*sent, cp)
|
||||
},
|
||||
}
|
||||
return g, sent
|
||||
}
|
||||
|
||||
func TestSendCacheImportOffer(t *testing.T) {
|
||||
g, sent := newCacheTestHandler()
|
||||
st := &fakeStore{export: []GfxCacheEntry{
|
||||
{Key: 0x1122334455667788, Width: 8, Height: 2, Data: bytes.Repeat([]byte{0xAB}, 8*2*4)},
|
||||
{Key: 0x0102030405060708 >> 0, Width: 4, Height: 4, Data: bytes.Repeat([]byte{0xCD}, 4*4*4)},
|
||||
}}
|
||||
g.cacheStore = st
|
||||
|
||||
// 空上报守卫:未调用 Export 前直接发送应只发一次,重复调用被闩住
|
||||
g.sendCacheImportOffer()
|
||||
if len(*sent) != 1 {
|
||||
t.Fatalf("期望 1 条 PDU,实得 %d", len(*sent))
|
||||
}
|
||||
g.sendCacheImportOffer()
|
||||
if len(*sent) != 1 {
|
||||
t.Fatalf("importOfferSent 闩失效:实得 %d 条", len(*sent))
|
||||
}
|
||||
|
||||
pdu := (*sent)[0]
|
||||
if got := binary.LittleEndian.Uint16(pdu[0:]); got != cmdidCacheImportOffer {
|
||||
t.Fatalf("cmdId=0x%X,期望 0x%X", got, cmdidCacheImportOffer)
|
||||
}
|
||||
wantLen := 8 + 2 + 12*2
|
||||
if got := binary.LittleEndian.Uint32(pdu[4:]); int(got) != wantLen {
|
||||
t.Fatalf("pduLength=%d,期望 %d", got, wantLen)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(pdu[8:]); got != 2 {
|
||||
t.Fatalf("cacheEntriesCount=%d,期望 2", got)
|
||||
}
|
||||
// 条目 1:key u64 + bitmapLength u32
|
||||
if got := binary.LittleEndian.Uint64(pdu[10:]); got != 0x1122334455667788 {
|
||||
t.Fatalf("entry0 key=0x%X", got)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(pdu[18:]); got != 8*2*4 {
|
||||
t.Fatalf("entry0 bitmapLength=%d,期望 %d", got, 8*2*4)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint64(pdu[22:]); got != 0x0102030405060708 {
|
||||
t.Fatalf("entry1 key=0x%X", got)
|
||||
}
|
||||
if len(g.offeredCache) != 2 {
|
||||
t.Fatalf("offeredCache 应保留 2 条待映射,实得 %d", len(g.offeredCache))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendCacheImportOfferEmpty(t *testing.T) {
|
||||
g, sent := newCacheTestHandler()
|
||||
g.cacheStore = &fakeStore{export: nil}
|
||||
g.sendCacheImportOffer()
|
||||
if len(*sent) != 0 {
|
||||
t.Fatalf("空存储不应发送 PDU")
|
||||
}
|
||||
g.cacheStore = nil
|
||||
g.importOfferSent = false
|
||||
g.sendCacheImportOffer()
|
||||
if len(*sent) != 0 {
|
||||
t.Fatalf("无 store 不应发送 PDU")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnCacheImportReply(t *testing.T) {
|
||||
g, _ := newCacheTestHandler()
|
||||
e0 := GfxCacheEntry{Key: 0xA, Width: 4, Height: 2, Data: bytes.Repeat([]byte{1}, 4*2*4)}
|
||||
e1 := GfxCacheEntry{Key: 0xB, Width: 2, Height: 2, Data: bytes.Repeat([]byte{2}, 2*2*4)}
|
||||
g.offeredCache = []GfxCacheEntry{e0, e1}
|
||||
|
||||
// 前缀语义:前 2 条导入,槽位 7 与 9
|
||||
data := make([]byte, 2, 2+4)
|
||||
binary.LittleEndian.PutUint16(data, 2)
|
||||
data = binary.LittleEndian.AppendUint16(data, 7)
|
||||
data = binary.LittleEndian.AppendUint16(data, 9)
|
||||
g.onCacheImportReply(data)
|
||||
|
||||
ce, ok := g.cacheEntries[7]
|
||||
if !ok || ce.key != 0xA || ce.width != 4 || ce.height != 2 || !bytes.Equal(ce.data, e0.Data) {
|
||||
t.Fatalf("槽位 7 条目不符: %+v", ce)
|
||||
}
|
||||
ce, ok = g.cacheEntries[9]
|
||||
if !ok || ce.key != 0xB || !bytes.Equal(ce.data, e1.Data) {
|
||||
t.Fatalf("槽位 9 条目不符: %+v", ce)
|
||||
}
|
||||
if g.offeredCache != nil {
|
||||
t.Fatalf("Reply 后 offeredCache 应清空")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnCacheImportReplyClamp(t *testing.T) {
|
||||
g, _ := newCacheTestHandler()
|
||||
bad := GfxCacheEntry{Key: 0xC, Width: 4, Height: 2, Data: []byte{1, 2, 3}} // 长度不符
|
||||
ok1 := GfxCacheEntry{Key: 0xD, Width: 2, Height: 2, Data: bytes.Repeat([]byte{3}, 2*2*4)}
|
||||
g.offeredCache = []GfxCacheEntry{bad, ok1}
|
||||
|
||||
// n=5 超过上报数(截到 2);条目 0 长度不符必须被拒(防花屏)
|
||||
data := make([]byte, 2, 2+10)
|
||||
binary.LittleEndian.PutUint16(data, 5)
|
||||
for _, s := range []uint16{3, 4} {
|
||||
data = binary.LittleEndian.AppendUint16(data, s)
|
||||
}
|
||||
g.onCacheImportReply(data)
|
||||
|
||||
if _, hit := g.cacheEntries[3]; hit {
|
||||
t.Fatalf("长度不符的条目不应入缓存")
|
||||
}
|
||||
if ce, hit := g.cacheEntries[4]; !hit || ce.key != 0xD {
|
||||
t.Fatalf("槽位 4 应为有效条目")
|
||||
}
|
||||
|
||||
// 槽位数组截断:只有 1 个完整槽位
|
||||
g2, _ := newCacheTestHandler()
|
||||
g2.offeredCache = []GfxCacheEntry{ok1, ok1}
|
||||
short := []byte{2, 0, 6, 0} // n=2 但只有 1 个槽位
|
||||
g2.onCacheImportReply(short)
|
||||
if _, hit := g2.cacheEntries[6]; !hit {
|
||||
t.Fatalf("截断时应导入完整部分")
|
||||
}
|
||||
if len(g2.cacheEntries) != 1 {
|
||||
t.Fatalf("截断时不应导入缺失槽位,实得 %d 条", len(g2.cacheEntries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurfaceToCachePersists(t *testing.T) {
|
||||
g, _ := newCacheTestHandler()
|
||||
st := &fakeStore{}
|
||||
g.cacheStore = st
|
||||
|
||||
// 8×4 表面,每行像素值 = 行号(BGRA 同值)
|
||||
sw, sh := 8, 4
|
||||
sdata := make([]byte, sw*sh*4)
|
||||
for row := range sh {
|
||||
for col := 0; col < sw; col++ {
|
||||
o := (row*sw + col) * 4
|
||||
sdata[o], sdata[o+1], sdata[o+2], sdata[o+3] = byte(row), byte(row), byte(row), 0xFF
|
||||
}
|
||||
}
|
||||
g.surfaces[1] = &surface{width: uint16(sw), height: uint16(sh), data: sdata}
|
||||
|
||||
key := uint64(0x1122334455667788)
|
||||
p := make([]byte, 0, 20)
|
||||
p = binary.LittleEndian.AppendUint16(p, 1) // surfId
|
||||
p = binary.LittleEndian.AppendUint64(p, key) // cacheKey
|
||||
p = binary.LittleEndian.AppendUint16(p, 3) // slot
|
||||
p = binary.LittleEndian.AppendUint16(p, 2) // left
|
||||
p = binary.LittleEndian.AppendUint16(p, 1) // top
|
||||
p = binary.LittleEndian.AppendUint16(p, 6) // right
|
||||
p = binary.LittleEndian.AppendUint16(p, 3) // bottom
|
||||
g.onSurfaceToCache(p)
|
||||
|
||||
ce := g.cacheEntries[3]
|
||||
if ce.width != 4 || ce.height != 2 {
|
||||
t.Fatalf("缓存条目尺寸 %dx%d,期望 4x2", ce.width, ce.height)
|
||||
}
|
||||
if ce.key != key {
|
||||
t.Fatalf("缓存条目 key=0x%X,期望 0x%X", ce.key, key)
|
||||
}
|
||||
// 第一行来自表面第 1 行(值为 1),第二行来自第 2 行(值为 2)
|
||||
if ce.data[0] != 1 || ce.data[(4*1)*4] != 2 {
|
||||
t.Fatalf("缓存像素内容不符: [0]=%d [row1]=%d", ce.data[0], ce.data[(4*1)*4])
|
||||
}
|
||||
if len(st.persisted) != 1 {
|
||||
t.Fatalf("Persist 应被调用 1 次,实得 %d", len(st.persisted))
|
||||
}
|
||||
pv := st.persisted[0]
|
||||
if pv.Key != key || pv.Width != 4 || pv.Height != 2 || !bytes.Equal(pv.Data, ce.data) {
|
||||
t.Fatalf("持久化条目不符: %+v", pv)
|
||||
}
|
||||
|
||||
// key=0 不持久化(视为无效键)
|
||||
p0 := append([]byte(nil), p...)
|
||||
binary.LittleEndian.PutUint64(p0[2:], 0)
|
||||
g.onSurfaceToCache(p0)
|
||||
if len(st.persisted) != 1 {
|
||||
t.Fatalf("key=0 不应触发 Persist,实得 %d", len(st.persisted))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxCacheImportEntries(t *testing.T) {
|
||||
if maxCacheImportEntries >= 0x1556 {
|
||||
t.Fatalf("maxCacheImportEntries=%d 必须小于规范上限 0x1556", maxCacheImportEntries)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user