init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib

This commit is contained in:
w11
2026-09-19 18:55:43 +08:00
commit 1096fbce9d
94 changed files with 36790 additions and 0 deletions
+295
View File
@@ -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)
}
}
}
}