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) } } } }