199 lines
5.9 KiB
Go
199 lines
5.9 KiB
Go
// cliprdr_image_test.go — 剪贴板图片链路的协议层单元测试(native 可跑)
|
|
package cliprdr_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"image/png"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.zeroonesoft.cn/golib/rdplib/core"
|
|
"git.zeroonesoft.cn/golib/rdplib/plugin/cliprdr"
|
|
)
|
|
|
|
// captureSender 捕获 handler 发出的 PDU,供断言
|
|
type captureSender struct {
|
|
mu sync.Mutex
|
|
pdus [][]byte
|
|
}
|
|
|
|
func (s *captureSender) GetType() (string, uint32) { return "cliprdr", 0 }
|
|
func (s *captureSender) Sender(f core.ChannelSender) {}
|
|
func (s *captureSender) Process(data []byte) {}
|
|
|
|
func (s *captureSender) SendToChannel(name string, data []byte) (int, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
pdu := make([]byte, len(data))
|
|
copy(pdu, data)
|
|
s.pdus = append(s.pdus, pdu)
|
|
return len(data), nil
|
|
}
|
|
|
|
func (s *captureSender) findByType(msgType uint16) [][]byte {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
var out [][]byte
|
|
for _, p := range s.pdus {
|
|
if len(p) >= 8 && binary.LittleEndian.Uint16(p[0:]) == msgType {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// fakeDIB 构造一张 2x2 的 32bpp BI_RGB 底行优先 DIB
|
|
func fakeDIB() []byte {
|
|
const w, h = 2, 2
|
|
dib := make([]byte, 40+w*4*h)
|
|
binary.LittleEndian.PutUint32(dib[0:4], 40) // biSize
|
|
binary.LittleEndian.PutUint32(dib[4:8], w) // width
|
|
binary.LittleEndian.PutUint32(dib[8:12], h) // height(正 = 底行优先)
|
|
binary.LittleEndian.PutUint16(dib[12:14], 1) // planes
|
|
binary.LittleEndian.PutUint16(dib[14:16], 32) // bpp
|
|
pix := dib[40:]
|
|
// 视觉顶部 = DIB 最后一行:红、绿
|
|
off := (h - 1) * w * 4
|
|
binary.LittleEndian.PutUint32(pix[off:off+4], 0x00FF0000) // BGRA → 红
|
|
binary.LittleEndian.PutUint32(pix[off+4:off+8], 0x0000FF00) // 绿
|
|
// 视觉底部 = DIB 首行:蓝、白
|
|
binary.LittleEndian.PutUint32(pix[0:4], 0x000000FF)
|
|
binary.LittleEndian.PutUint32(pix[4:8], 0x00FFFFFF)
|
|
return dib
|
|
}
|
|
|
|
// capsPDU 构造 CB_CLIP_CAPS(声明长格式名),handler 需先收到它才会按长名解析
|
|
func capsPDU() []byte {
|
|
body := make([]byte, 0, 20)
|
|
body = binary.LittleEndian.AppendUint16(body, 1) // cCapSets
|
|
body = binary.LittleEndian.AppendUint16(body, 0) // pad
|
|
body = binary.LittleEndian.AppendUint16(body, cliprdr.CB_CAPSTYPE_GENERAL)
|
|
body = binary.LittleEndian.AppendUint16(body, cliprdr.CB_CAPSTYPE_GENERAL_LEN)
|
|
body = binary.LittleEndian.AppendUint32(body, cliprdr.CB_CAPS_VERSION_2)
|
|
body = binary.LittleEndian.AppendUint32(body, cliprdr.CB_USE_LONG_FORMAT_NAMES)
|
|
p := pduHeader(cliprdr.CB_CLIP_CAPS, 0, len(body))
|
|
return append(p, body...)
|
|
}
|
|
|
|
func TestDIBToPNG(t *testing.T) {
|
|
pngBytes, err := cliprdr.DIBToPNG(fakeDIB())
|
|
if err != nil {
|
|
t.Fatalf("DIBToPNG: %v", err)
|
|
}
|
|
img, err := png.Decode(bytes.NewReader(pngBytes))
|
|
if err != nil {
|
|
t.Fatalf("decoded PNG invalid: %v", err)
|
|
}
|
|
if img.Bounds().Dx() != 2 || img.Bounds().Dy() != 2 {
|
|
t.Fatalf("unexpected size %v", img.Bounds())
|
|
}
|
|
r, g, b, _ := img.At(0, 0).RGBA() // RGBA() 分量为 16 位
|
|
if r != 0xFFFF || g != 0 || b != 0 {
|
|
t.Fatalf("pixel(0,0) want red, got r=%x g=%x b=%x", r, g, b)
|
|
}
|
|
}
|
|
|
|
func TestFormatDataResponseDIBConvertedToPNG(t *testing.T) {
|
|
h := cliprdr.NewHandler(nil, nil)
|
|
sender := &captureSender{}
|
|
h.Sender(sender)
|
|
|
|
var gotPNG []byte
|
|
h.SetImageCallbacks(func(p []byte) { gotPNG = p }, nil)
|
|
|
|
// 先声明能力(长格式名),再让服务端提供仅含 CF_DIB 的 FormatList
|
|
h.Process(capsPDU())
|
|
h.Process(mkFormatListLong([]cliprdr.CliprdrFormat{{FormatId: cliprdr.CF_DIB}}))
|
|
h.Process(responsePDU(cliprdr.CF_DIB, fakeDIB()))
|
|
|
|
if len(gotPNG) == 0 {
|
|
t.Fatal("expected PNG bytes via onRemoteClipboardImage")
|
|
}
|
|
if _, err := png.Decode(bytes.NewReader(gotPNG)); err != nil {
|
|
t.Fatalf("converted PNG invalid: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFormatListPNGTriggersRequest(t *testing.T) {
|
|
h := cliprdr.NewHandler(nil, nil)
|
|
sender := &captureSender{}
|
|
h.Sender(sender)
|
|
|
|
h.Process(capsPDU())
|
|
h.Process(mkFormatListLong([]cliprdr.CliprdrFormat{
|
|
{FormatId: 0xC3F0, FormatName: "PNG"},
|
|
{FormatId: cliprdr.CF_UNICODETEXT, FormatName: ""},
|
|
}))
|
|
|
|
requests := sender.findByType(0x0004) // CB_FORMAT_DATA_REQUEST
|
|
for _, p := range requests {
|
|
id := binary.LittleEndian.Uint32(p[8:12])
|
|
if id == 0xC3F0 { // 应按服务端给出的 PNG 注册 ID 发起请求
|
|
return
|
|
}
|
|
}
|
|
t.Fatal("expected PNG format data request for server-offered PNG format")
|
|
}
|
|
|
|
func TestFormatDataRequestPNGServedFromProvider(t *testing.T) {
|
|
h := cliprdr.NewHandler(nil, nil)
|
|
sender := &captureSender{}
|
|
h.Sender(sender)
|
|
h.SetImageCallbacks(nil, func() []byte { return []byte("FAKEPNG") })
|
|
|
|
h.Process(requestPDU(cliprdr.CF_PNG))
|
|
|
|
responses := sender.findByType(0x0005) // CB_FORMAT_DATA_RESPONSE
|
|
for _, p := range responses {
|
|
if binary.LittleEndian.Uint16(p[2:]) == 1 && bytes.Equal(p[8:15], []byte("FAKEPNG")) {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("expected PNG data served from provider, got %d responses", len(responses))
|
|
}
|
|
|
|
// --- helpers: 构造标准 CLIPRDR PDU ---
|
|
|
|
func pduHeader(msgType, msgFlags uint16, bodyLen int) []byte {
|
|
h := make([]byte, 8)
|
|
binary.LittleEndian.PutUint16(h[0:], msgType)
|
|
binary.LittleEndian.PutUint16(h[2:], msgFlags)
|
|
binary.LittleEndian.PutUint32(h[4:], uint32(bodyLen))
|
|
return h
|
|
}
|
|
|
|
func requestPDU(formatId uint32) []byte {
|
|
p := pduHeader(0x0004, 0, 4)
|
|
return binary.LittleEndian.AppendUint32(p, formatId)
|
|
}
|
|
|
|
func responsePDU(formatId uint32, data []byte) []byte {
|
|
_ = formatId // 响应 PDU 本身不含格式 ID,格式由我方请求记录
|
|
p := pduHeader(0x0005, 1, len(data))
|
|
return append(p, data...)
|
|
}
|
|
|
|
func mkFormatListLong(formats []cliprdr.CliprdrFormat) []byte {
|
|
var body bytes.Buffer
|
|
for _, f := range formats {
|
|
binary.Write(&body, binary.LittleEndian, f.FormatId)
|
|
body.Write(encodeUTF16(f.FormatName))
|
|
body.Write([]byte{0, 0})
|
|
}
|
|
p := pduHeader(0x0002, 0, body.Len())
|
|
return append(p, body.Bytes()...)
|
|
}
|
|
|
|
func encodeUTF16(s string) []byte {
|
|
u := []uint16{}
|
|
for _, r := range s {
|
|
u = append(u, uint16(r))
|
|
}
|
|
out := make([]byte, len(u)*2)
|
|
for i, v := range u {
|
|
binary.LittleEndian.PutUint16(out[i*2:], v)
|
|
}
|
|
return out
|
|
}
|