Files
rdplib/plugin/rdpgfx/clear_test.go
T

296 lines
12 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
// ClearCodec 解码器单元测试:按 MS-RDPEGFX 2.2.4 逐段手工构造码流,
// 验证 residual 游程、bands 列带(含短 vBar 缓存命中)、RLEX 子编码与 glyph 缓存。
import (
"encoding/binary"
"testing"
)
// buildClearStream 组装一个完整 TS_CLEARCODEC_BITMAP_STREAM
func buildClearStream(glyphFlags, seqNumber byte, glyphIndex uint16,
residual, bands, subcodec []byte) []byte {
out := []byte{glyphFlags, seqNumber}
if glyphFlags&(clearFlagGlyphHit|clearFlagGlyphIndex) != 0 {
out = binary.LittleEndian.AppendUint16(out, glyphIndex)
}
out = binary.LittleEndian.AppendUint32(out, uint32(len(residual)))
out = binary.LittleEndian.AppendUint32(out, uint32(len(bands)))
out = binary.LittleEndian.AppendUint32(out, uint32(len(subcodec)))
out = append(out, residual...)
out = append(out, bands...)
out = append(out, subcodec...)
return out
}
// px 取出输出位图中 (x,y) 的 BGRA
func px(out []byte, w, x, y int) [4]byte {
i := (y*w + x) * 4
return [4]byte{out[i], out[i+1], out[i+2], out[i+3]}
}
func expectPixel(t *testing.T, out []byte, w, x, y int, want [4]byte, ctx string) {
t.Helper()
got := px(out, w, x, y)
if got != want {
t.Fatalf("%s: pixel(%d,%d) = %v, want %v", ctx, x, y, got, want)
}
}
// TestClearResidual:单条游程铺满整幅
func TestClearResidual(t *testing.T) {
w, h := 4, 2
// residual: b,g,r=0x10,0x20,0x30 runLen=8
residual := []byte{0x10, 0x20, 0x30, 8}
stream := buildClearStream(0, 0, 0, residual, nil, nil)
ctx := newClearCodecCtx()
out := ctx.decode(stream, w, h)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
expectPixel(t, out, w, x, y, [4]byte{0x10, 0x20, 0x30, 0xFF}, "residual")
}
}
}
// TestClearResidualRunEncoding:长游程 2 字节/4 字节变长编码
func TestClearResidualRunEncoding(t *testing.T) {
w, h := 16, 16 // 256 像素
// run=255(触发 2 字节编码 0x00FF)+ run=1 → 共 256
residual := []byte{1, 2, 3, 0xFF, 0xFF, 0x00, 4, 5, 6, 1}
stream := buildClearStream(0, 0, 0, residual, nil, nil)
ctx := newClearCodecCtx()
out := ctx.decode(stream, w, h)
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
want := [4]byte{1, 2, 3, 0xFF}
if y == 15 && x == 15 {
want = [4]byte{4, 5, 6, 0xFF}
}
expectPixel(t, out, w, x, y, want, "residual-run")
}
}
}
// TestClearBandsShortVBarCacheMiss:列带 + 短 vBar 存缓存 + 背景合成
func TestClearBandsShortVBarCacheMiss(t *testing.T) {
w, h := 2, 4
// band: x0..1, y0..3 → vBarCount=2, vBarHeight=4
bands := []byte{}
bands = binary.LittleEndian.AppendUint16(bands, 0) // xStart
bands = binary.LittleEndian.AppendUint16(bands, 1) // xEnd
bands = binary.LittleEndian.AppendUint16(bands, 0) // yStart
bands = binary.LittleEndian.AppendUint16(bands, 3) // yEnd
bands = append(bands, 0xAA, 0xBB, 0xCC) // cb,cg,cr 背景色
// 每列:SHORT_VBAR_CACHE_MISS,yOn=1,yOff=3 → 2 个像素,位于行 1-2
for col := 0; col < 2; col++ {
header := uint16(1) | uint16(3)<<8 // yOn=1, yOff=3
bands = binary.LittleEndian.AppendUint16(bands, header)
bands = append(bands, byte(col+1), 0x00, 0x10) // 行1: b,g,r
bands = append(bands, byte(col+2), 0x00, 0x20) // 行2
}
stream := buildClearStream(0, 0, 0, nil, bands, nil)
ctx := newClearCodecCtx()
out := ctx.decode(stream, w, h)
for col := 0; col < 2; col++ {
expectPixel(t, out, w, col, 0, [4]byte{0xAA, 0xBB, 0xCC, 0xFF}, "bands-bg-front")
expectPixel(t, out, w, col, 1, [4]byte{byte(col + 1), 0x00, 0x10, 0xFF}, "bands-short-1")
expectPixel(t, out, w, col, 2, [4]byte{byte(col + 2), 0x00, 0x20, 0xFF}, "bands-short-2")
expectPixel(t, out, w, col, 3, [4]byte{0xAA, 0xBB, 0xCC, 0xFF}, "bands-bg-back")
}
}
// TestClearBandsShortVBarCacheHit:第二帧复用第一帧的短 vBar 缓存
func TestClearBandsShortVBarCacheHit(t *testing.T) {
w, h := 1, 4
mkBands := func(hit bool) []byte {
bands := []byte{}
bands = binary.LittleEndian.AppendUint16(bands, 0)
bands = binary.LittleEndian.AppendUint16(bands, 0)
bands = binary.LittleEndian.AppendUint16(bands, 0)
bands = binary.LittleEndian.AppendUint16(bands, 3)
bands = append(bands, 0x01, 0x02, 0x03) // 背景
if hit {
// SHORT_VBAR_CACHE_HIT:idx=0 + 1 字节 yOn=2
bands = binary.LittleEndian.AppendUint16(bands, 0x4000|0)
bands = append(bands, 2)
} else {
// MISS:yOn=2,yOff=4 → 2 像素
bands = binary.LittleEndian.AppendUint16(bands, 2|4<<8)
bands = append(bands, 0x10, 0x20, 0x30)
bands = append(bands, 0x40, 0x50, 0x60)
}
return bands
}
ctx := newClearCodecCtx()
ctx.decode(buildClearStream(0, 0, 0, nil, mkBands(false), nil), w, h)
out := ctx.decode(buildClearStream(0, 1, 0, nil, mkBands(true), nil), w, h)
// 缓存命中:行 2-3 = 缓存的两像素,行 0-1 = 背景
expectPixel(t, out, w, 0, 0, [4]byte{0x01, 0x02, 0x03, 0xFF}, "hit-bg-0")
expectPixel(t, out, w, 0, 1, [4]byte{0x01, 0x02, 0x03, 0xFF}, "hit-bg-1")
expectPixel(t, out, w, 0, 2, [4]byte{0x10, 0x20, 0x30, 0xFF}, "hit-short-2")
expectPixel(t, out, w, 0, 3, [4]byte{0x40, 0x50, 0x60, 0xFF}, "hit-short-3")
}
// TestClearSubcodecBGR24:未压缩 BGR24 子编码
func TestClearSubcodecBGR24(t *testing.T) {
w, h := 2, 2
sub := []byte{}
sub = binary.LittleEndian.AppendUint16(sub, 1) // xStart
sub = binary.LittleEndian.AppendUint16(sub, 1) // yStart
sub = binary.LittleEndian.AppendUint16(sub, 1) // width
sub = binary.LittleEndian.AppendUint16(sub, 1) // height
sub = binary.LittleEndian.AppendUint32(sub, 3) // bitmapDataByteCount
sub = append(sub, 0) // subcodecId = BGR24
sub = append(sub, 0x77, 0x88, 0x99) // b,g,r
stream := buildClearStream(0, 0, 0, nil, nil, sub)
ctx := newClearCodecCtx()
out := ctx.decode(stream, w, h)
expectPixel(t, out, w, 0, 0, [4]byte{0, 0, 0, 0}, "bgr24-untouched")
expectPixel(t, out, w, 1, 1, [4]byte{0x77, 0x88, 0x99, 0xFF}, "bgr24-hit")
}
// TestClearRLEX:调色板游程子编码(套位打包索引)
func TestClearRLEX(t *testing.T) {
w, h := 4, 1
sub := []byte{}
sub = binary.LittleEndian.AppendUint16(sub, 0) // xStart
sub = binary.LittleEndian.AppendUint16(sub, 0) // yStart
sub = binary.LittleEndian.AppendUint16(sub, 4) // width
sub = binary.LittleEndian.AppendUint16(sub, 1) // height
sub = binary.LittleEndian.AppendUint32(sub, 15)
// RLEX: subcodecId=2 + paletteCount=4 + 4×(b,g,r)
sub = append(sub, 2)
sub = append(sub, 4)
sub = append(sub, 0x10, 0x00, 0x00)
sub = append(sub, 0x20, 0x00, 0x00)
sub = append(sub, 0x30, 0x00, 0x00)
sub = append(sub, 0x40, 0x00, 0x00)
// paletteCount=4 → numBits=2;suiteDepth=3, stopIndex=3 → tmp=(3<<2)|3=0x0F
// runLen=0(1 字节)→ 输出 4 像素 palette[0..3]
sub = append(sub, 0x0F, 0)
stream := buildClearStream(0, 0, 0, nil, nil, sub)
ctx := newClearCodecCtx()
out := ctx.decode(stream, w, h)
expectPixel(t, out, w, 0, 0, [4]byte{0x10, 0, 0, 0xFF}, "rlex-0")
expectPixel(t, out, w, 1, 0, [4]byte{0x20, 0, 0, 0xFF}, "rlex-1")
expectPixel(t, out, w, 2, 0, [4]byte{0x30, 0, 0, 0xFF}, "rlex-2")
expectPixel(t, out, w, 3, 0, [4]byte{0x40, 0, 0, 0xFF}, "rlex-3")
}
// TestClearGlyphIndexThenHit:GLYPH_INDEX 缓存输出,GLYPH_HIT 直接取缓存
func TestClearGlyphIndexThenHit(t *testing.T) {
w, h := 2, 2
residual := []byte{0x11, 0x22, 0x33, 4}
ctx := newClearCodecCtx()
// 第一帧:GLYPH_INDEX,缓存到 index=7
ctx.decode(buildClearStream(clearFlagGlyphIndex, 0, 7, residual, nil, nil), w, h)
// 第二帧:GLYPH_HIT|GLYPH_INDEX(合法命中组合),无载荷也应输出缓存
out := ctx.decode(buildClearStream(clearFlagGlyphHit|clearFlagGlyphIndex, 1, 7, nil, nil, nil), w, h)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
expectPixel(t, out, w, x, y, [4]byte{0x11, 0x22, 0x33, 0xFF}, "glyph-hit")
}
}
// 单独的 GLYPH_HIT 是非法组合 → 整帧拒绝(nil,调用方保留 surface)
out = ctx.decode(buildClearStream(clearFlagGlyphHit, 2, 7, nil, nil, nil), w, h)
if out != nil {
t.Fatalf("invalid glyph flags: want nil frame, got %d bytes", len(out))
}
// 空更新流(子编码矩形完全越界,服务器真实样本形态)→ 整帧拒绝
sub := []byte{}
sub = binary.LittleEndian.AppendUint16(sub, 32) // xStart == w(越界)
sub = binary.LittleEndian.AppendUint16(sub, 64) // yStart == h(越界)
sub = binary.LittleEndian.AppendUint16(sub, 8) // width
sub = binary.LittleEndian.AppendUint16(sub, 0) // height
sub = binary.LittleEndian.AppendUint32(sub, 15)
sub = append(sub, 2) // subcodecId = RLEX
sub = append(sub, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
out = ctx.decode(buildClearStream(0, 3, 0, nil, nil, sub), 32, 64)
if out != nil {
t.Fatalf("out-of-bounds subcodec rect: want nil frame, got %d bytes", len(out))
}
}
// TestClearVBarCacheHitAcrossFrames:整 vBar 缓存跨帧复用(CACHE_RESET 后哑填充不崩溃)
func TestClearVBarCacheHitAcrossFrames(t *testing.T) {
w, h := 1, 2
mkBands := func(hit bool) []byte {
bands := []byte{}
bands = binary.LittleEndian.AppendUint16(bands, 0)
bands = binary.LittleEndian.AppendUint16(bands, 0)
bands = binary.LittleEndian.AppendUint16(bands, 0)
bands = binary.LittleEndian.AppendUint16(bands, 1) // yEnd → vBarHeight=2
bands = append(bands, 0, 0, 0)
if hit {
bands = binary.LittleEndian.AppendUint16(bands, 0x8000|0) // VBAR_CACHE_HIT idx 0
} else {
// MISS(vBarHeader 最高两位为 00 且 0x8000 未置位):count=yOn..yOff 不可用,
// 直接用像素数:yOn=0, yOff=2 → header = 0|2<<8
bands = binary.LittleEndian.AppendUint16(bands, 0|2<<8)
bands = append(bands, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)
}
return bands
}
ctx := newClearCodecCtx()
ctx.decode(buildClearStream(0, 0, 0, nil, mkBands(false), nil), w, h)
// 带 CACHE_RESET 重置 vBar 存储,再用 idx0 命中 → 哑填充不 panic
out := ctx.decode(buildClearStream(clearFlagCacheReset, 1, 0, nil, mkBands(true), nil), w, h)
expectPixel(t, out, w, 0, 0, [4]byte{0, 0, 0, 0}, "vbar-reset-dummy")
}
// TestClearNSCodec:NSCodec 子编码(RLE 平面 + YCoCg 恢复 + 空 alpha 平面)
func TestClearNSCodec(t *testing.T) {
w, h := 8, 2 // org 每平面 16 字节
// 单平面 RLE:12 字节游程 [v,v,run-2] + 尾部 4 字节原样 = 7 字节
rle := func(v byte) []byte { return []byte{v, v, 10, v, v, v, v} }
yPlane := rle(0x80)
coPlane := rle(0x40)
cgPlane := rle(0x00)
sub := []byte{}
sub = binary.LittleEndian.AppendUint16(sub, 0) // xStart
sub = binary.LittleEndian.AppendUint16(sub, 0) // yStart
sub = binary.LittleEndian.AppendUint16(sub, uint16(w)) // width
sub = binary.LittleEndian.AppendUint16(sub, uint16(h)) // height
sub = binary.LittleEndian.AppendUint32(sub, uint32(20 + 7 + 7 + 7 + 0))
sub = append(sub, 1) // subcodecId = NSCodec
for _, n := range []int{7, 7, 7, 0} {
sub = binary.LittleEndian.AppendUint32(sub, uint32(n)) // PlaneByteCount
}
sub = append(sub, 1, 0, 0, 0) // ColorLossLevel=1, ChromaSubsampling=0, 保留
sub = append(sub, yPlane...)
sub = append(sub, coPlane...)
sub = append(sub, cgPlane...)
// A 平面 byteCount=0 → 全 0xFF
stream := buildClearStream(0, 0, 0, nil, nil, sub)
ctx := newClearCodecCtx()
out := ctx.decode(stream, w, h)
// shift=0:r=0x80+0x40-0x00=0xC0, g=0x80, b=0x80-0x40-0x00=0x40
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
expectPixel(t, out, w, x, y, [4]byte{0x40, 0x80, 0xC0, 0xFF}, "nsc")
}
}
}
// TestProgDWTExtrapolateConstant:常数 LL3 + 零高频 → IDWT 输出应为同一常数
func TestProgDWTExtrapolateConstant(t *testing.T) {
buf := make([]int16, 4096)
// extrapolate 布局 LL3 在 [4015,4096),9×9 行主序
for y := 0; y < 9; y++ {
for x := 0; x < 9; x++ {
buf[4015+y*9+x] = 128
}
}
progDWTExtrapolate(buf)
for y := 0; y < 64; y++ {
for x := 0; x < 64; x++ {
if got := buf[y*64+x]; got != 128 {
t.Fatalf("DWT(%d,%d) = %d, want 128", x, y, got)
}
}
}
}