Files
rdplib/plugin/rdpgfx/rdpgfx_cache_test.go
T

235 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package 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)
}
}