init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user