init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ReadBytesComplete func(result []byte, err error)
|
||||
|
||||
func StartReadBytes(len int, r io.Reader, cb ReadBytesComplete) {
|
||||
b := make([]byte, len)
|
||||
go func() {
|
||||
_, err := io.ReadFull(r, b)
|
||||
cb(b, err)
|
||||
}()
|
||||
}
|
||||
|
||||
func ReadBytes(len int, r io.Reader) ([]byte, error) {
|
||||
b := make([]byte, len)
|
||||
length, err := io.ReadFull(r, b)
|
||||
return b[:length], err
|
||||
}
|
||||
|
||||
func ReadByte(r io.Reader) (byte, error) {
|
||||
var buf [1]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
return buf[0], err
|
||||
}
|
||||
|
||||
func ReadUInt8(r io.Reader) (uint8, error) {
|
||||
var buf [1]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
return buf[0], err
|
||||
}
|
||||
|
||||
func ReadUint16LE(r io.Reader) (uint16, error) {
|
||||
var buf [2]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.LittleEndian.Uint16(buf[:]), nil
|
||||
}
|
||||
|
||||
func ReadUint16BE(r io.Reader) (uint16, error) {
|
||||
var buf [2]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.BigEndian.Uint16(buf[:]), nil
|
||||
}
|
||||
|
||||
func ReadUInt32LE(r io.Reader) (uint32, error) {
|
||||
var buf [4]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.LittleEndian.Uint32(buf[:]), nil
|
||||
}
|
||||
|
||||
func ReadUInt32BE(r io.Reader) (uint32, error) {
|
||||
var buf [4]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.BigEndian.Uint32(buf[:]), nil
|
||||
}
|
||||
|
||||
func WriteByte(data byte, w io.Writer) (int, error) {
|
||||
buf := [1]byte{data}
|
||||
return w.Write(buf[:])
|
||||
}
|
||||
|
||||
func WriteBytes(data []byte, w io.Writer) (int, error) {
|
||||
return w.Write(data)
|
||||
}
|
||||
|
||||
func WriteUInt8(data uint8, w io.Writer) (int, error) {
|
||||
buf := [1]byte{data}
|
||||
return w.Write(buf[:])
|
||||
}
|
||||
|
||||
func WriteUInt16BE(data uint16, w io.Writer) (int, error) {
|
||||
var buf [2]byte
|
||||
binary.BigEndian.PutUint16(buf[:], data)
|
||||
return w.Write(buf[:])
|
||||
}
|
||||
|
||||
func WriteUInt16LE(data uint16, w io.Writer) (int, error) {
|
||||
var buf [2]byte
|
||||
binary.LittleEndian.PutUint16(buf[:], data)
|
||||
return w.Write(buf[:])
|
||||
}
|
||||
|
||||
func WriteUInt32LE(data uint32, w io.Writer) (int, error) {
|
||||
var buf [4]byte
|
||||
binary.LittleEndian.PutUint32(buf[:], data)
|
||||
return w.Write(buf[:])
|
||||
}
|
||||
|
||||
func WriteUInt32BE(data uint32, w io.Writer) (int, error) {
|
||||
var buf [4]byte
|
||||
binary.BigEndian.PutUint32(buf[:], data)
|
||||
return w.Write(buf[:])
|
||||
}
|
||||
|
||||
func PutUint16BE(data uint16) (uint8, uint8) {
|
||||
return uint8(data >> 8), uint8(data)
|
||||
}
|
||||
|
||||
func Uint16BE(d0, d1 uint8) uint16 {
|
||||
return uint16(d0)<<8 | uint16(d1)
|
||||
}
|
||||
|
||||
func RGB565ToRGB(data uint16) (r, g, b uint8) {
|
||||
r = uint8((data & 0xF800) >> 8)
|
||||
g = uint8((data & 0x07E0) >> 3)
|
||||
b = uint8((data & 0x001F) << 3)
|
||||
|
||||
return
|
||||
}
|
||||
func RGB555ToRGB(data uint16) (r, g, b uint8) {
|
||||
r = uint8((data & 0x7C00) >> 7)
|
||||
g = uint8((data & 0x03E0) >> 2)
|
||||
b = uint8((data & 0x001F) << 3)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteUInt16LE(t *testing.T) {
|
||||
buff := &bytes.Buffer{}
|
||||
WriteUInt32LE(66538, buff)
|
||||
result := hex.EncodeToString(buff.Bytes())
|
||||
expected := "ea030100"
|
||||
if result != expected {
|
||||
t.Error(result, "not equals to", expected)
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Hex wraps a byte slice as a slog.LogValuer that lazily encodes the bytes
|
||||
// as a hexadecimal string only when the slog handler actually formats it.
|
||||
//
|
||||
// Use Hex(buf) instead of hex.EncodeToString(buf) inside slog.Debug calls on
|
||||
// hot paths: when the logger's level filter discards the record (the common
|
||||
// case in production), the encode and the per-call string allocation are
|
||||
// skipped entirely.
|
||||
type Hex []byte
|
||||
|
||||
// LogValue implements slog.LogValuer.
|
||||
func (h Hex) LogValue() slog.Value {
|
||||
return slog.StringValue(hex.EncodeToString(h))
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// MppcDecompressor maintains per-connection state for RDP bulk data
|
||||
// decompression (MS-RDPBCGR §3.1.8.4, RFC 2118 MPPC token grammar). A single
|
||||
// instance is shared between the fast-path and slow-path receivers of one RDP
|
||||
// connection. The token grammar follows the MS-RDPBCGR pseudo-code as
|
||||
// implemented in FreeRDP's libfreerdp/codec/mppc.c (rdesktop's variant uses a
|
||||
// different literal/length encoding that does not interop with Win10 RDP5
|
||||
// streams).
|
||||
//
|
||||
// Token grammar (bit-level, MSB first):
|
||||
//
|
||||
// literal 0x00-0x7F : "0" + 7 bits (8-bit token)
|
||||
// literal 0x80-0xFF : "10" + 7 bits (9-bit token)
|
||||
// copy tuple : "11" + offset prefix + length prefix
|
||||
//
|
||||
// Offset prefix (RDP5, compressionType nibble 0x1, 64K dictionary):
|
||||
//
|
||||
// "11111" + 6 bits → offset 0-63
|
||||
// "11110" + 8 bits → offset 64-319
|
||||
// "1110" + 11 bits → offset 320-2367
|
||||
// "110" + 16 bits → offset 2368-67903
|
||||
//
|
||||
// (RDP4 / 8K dictionary: "1111"+6, "1110"+8, "110"+13.)
|
||||
//
|
||||
// Length prefix ("0" → 3; otherwise n 1-bits, a 0 terminator, then n+1 value
|
||||
// bits with the (1<<m) high bit implied, m = n+1; RDP5 allows m up to 15).
|
||||
//
|
||||
// History: uncompressed segments do NOT touch the shared history; FLUSH
|
||||
// (0x80) zeroes it, RESET/AT_FRONT (0x40) rewinds the write cursor to 0.
|
||||
type MppcDecompressor struct {
|
||||
history [mppcHistorySize]byte
|
||||
offset int
|
||||
}
|
||||
|
||||
const mppcHistorySize = 65536
|
||||
|
||||
// mppc flag bits(fast-path compressionFlags 字节,MS-RDPBCGR 2.2.9.1.1.4.1
|
||||
// + FreeRDP bulk.c):
|
||||
//
|
||||
// 低半字节 0x01 – 压缩类型选择:1 = RDP5(64K 字典)
|
||||
// 0x20 PACKET_COMPRESSED – 块为 MPPC 压缩数据
|
||||
// 0x40 PACKET_AT_FRONT – 解压数据写到历史前端(游标归零)
|
||||
// 0x80 PACKET_FLUSHED – 清零历史,游标归零
|
||||
//
|
||||
// 注意:FreeRDP mppc.c 不处理 0x01 位(它只是类型选择),本解码器固定
|
||||
// 使用 64K 字典,因此该位可忽略。
|
||||
const (
|
||||
mppcType64K = 0x01
|
||||
mppcCompressed = 0x20
|
||||
mppcAtFront = 0x40
|
||||
mppcFlushed = 0x80
|
||||
)
|
||||
|
||||
func NewMppcDecompressor() *MppcDecompressor {
|
||||
return &MppcDecompressor{}
|
||||
}
|
||||
|
||||
// Decompress processes one MPPC segment.
|
||||
//
|
||||
// flags is the compressionFlags byte (fast-path) or the slow-path bulk flags;
|
||||
// it carries the PACKET_* bits. Segments without PACKET_COMPRESSED are
|
||||
// returned unchanged and do not affect the history.
|
||||
func (d *MppcDecompressor) Decompress(flags byte, data []byte) ([]byte, error) {
|
||||
if flags&mppcFlushed != 0 {
|
||||
d.history = [mppcHistorySize]byte{}
|
||||
d.offset = 0
|
||||
}
|
||||
if flags&mppcAtFront != 0 {
|
||||
d.offset = 0
|
||||
}
|
||||
|
||||
if flags&mppcCompressed == 0 {
|
||||
out := make([]byte, len(data))
|
||||
copy(out, data)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// RDP5 64K 字典(协商固定,见常量注释)。
|
||||
const mask = 65535
|
||||
start := d.offset
|
||||
br := newMppcBitReader(data)
|
||||
|
||||
// abort 与 FreeRDP mppc.c 失败路径语义一致:HistoryPtr 只在成功返回时
|
||||
// 提交,解码中途失败必须回滚写游标,否则后续分段的 match 引用的
|
||||
// 历史从此错位,整条流持续产出垃圾。
|
||||
abort := func(msg string) ([]byte, error) {
|
||||
d.offset = start
|
||||
return nil, errors.New("mppc: " + msg)
|
||||
}
|
||||
|
||||
for br.bitsLeft >= 8 {
|
||||
if br.readBit() == 0 {
|
||||
// Literal 0x00-0x7F ("0" + 7 bits).
|
||||
if d.offset >= mppcHistorySize {
|
||||
return abort("history full")
|
||||
}
|
||||
d.history[d.offset] = byte(br.readBits(7))
|
||||
d.offset++
|
||||
continue
|
||||
}
|
||||
if br.readBit() == 0 {
|
||||
// Literal 0x80-0xFF ("10" + 7 bits, 9-bit token).
|
||||
if d.offset >= mppcHistorySize {
|
||||
return abort("history full")
|
||||
}
|
||||
d.history[d.offset] = byte(0x80 | br.readBits(7))
|
||||
d.offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// Copy tuple: decode CopyOffset (distance back from the write
|
||||
// cursor, masked into the dictionary)。前缀位必须逐位惰性读取,
|
||||
// 不能在 switch 初始化里预先消耗。
|
||||
var copyOffset int
|
||||
if br.readBit() == 0 {
|
||||
copyOffset = br.readBits(16) + 2368
|
||||
} else if br.readBit() == 0 {
|
||||
copyOffset = br.readBits(11) + 320
|
||||
} else if br.readBit() == 0 {
|
||||
copyOffset = br.readBits(8) + 64
|
||||
} else {
|
||||
copyOffset = br.readBits(6)
|
||||
}
|
||||
|
||||
// Decode LengthOfMatch: n leading 1-bits + 0 terminator, then n+1
|
||||
// value bits; length = (1<<(n+1)) | bits. "0" alone means 3.
|
||||
n := 0
|
||||
for br.readBit() == 1 {
|
||||
n++
|
||||
const maxBits = 15
|
||||
if n > maxBits-1 {
|
||||
return abort("length code overflow")
|
||||
}
|
||||
}
|
||||
var copyLength int
|
||||
if n == 0 {
|
||||
copyLength = 3
|
||||
} else {
|
||||
m := n + 1
|
||||
copyLength = (1 << uint(m)) | br.readBits(m)
|
||||
}
|
||||
|
||||
if d.offset+copyLength > mppcHistorySize {
|
||||
return abort(fmt.Sprintf("copy overflows history (%d+%d)", d.offset, copyLength))
|
||||
}
|
||||
src := (d.offset - copyOffset) & mask
|
||||
for i := 0; i < copyLength; i++ {
|
||||
d.history[d.offset] = d.history[src]
|
||||
d.offset++
|
||||
src = (src + 1) & mask
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]byte, d.offset-start)
|
||||
copy(out, d.history[start:d.offset])
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// mppcBitReader reads bits MSB-first from a byte slice. Reading past the
|
||||
// end yields zero bits (matching FreeRDP's padded bit stream behaviour).
|
||||
type mppcBitReader struct {
|
||||
data []byte
|
||||
byteIdx int
|
||||
mask byte // bit mask within current byte; starts at 0x80
|
||||
bitsLeft int // total bits remaining
|
||||
}
|
||||
|
||||
func newMppcBitReader(data []byte) *mppcBitReader {
|
||||
return &mppcBitReader{
|
||||
data: data,
|
||||
mask: 0x80,
|
||||
bitsLeft: len(data) * 8,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *mppcBitReader) readBit() int {
|
||||
if r.bitsLeft <= 0 {
|
||||
return 0
|
||||
}
|
||||
r.bitsLeft--
|
||||
var bit int
|
||||
if r.data[r.byteIdx]&r.mask != 0 {
|
||||
bit = 1
|
||||
}
|
||||
r.mask >>= 1
|
||||
if r.mask == 0 {
|
||||
r.mask = 0x80
|
||||
r.byteIdx++
|
||||
}
|
||||
return bit
|
||||
}
|
||||
|
||||
func (r *mppcBitReader) readBits(n int) int {
|
||||
result := 0
|
||||
for i := 0; i < n; i++ {
|
||||
result = (result << 1) | r.readBit()
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Token grammar reference (MS-RDPBCGR §3.1.8.4 as implemented by FreeRDP's
|
||||
// libfreerdp/codec/mppc.c):
|
||||
//
|
||||
// literal 0x00-0x7F : "0" + 7 bits (8-bit token)
|
||||
// literal 0x80-0xFF : "10" + 7 bits (9-bit token)
|
||||
// copy tuple : "11" + offset prefix + length prefix
|
||||
|
||||
// compressedABC is the MPPC-64K encoding of "abc" (three literal tokens;
|
||||
// bytes < 0x80 are transmitted as 8-bit "0"+7bits tokens).
|
||||
var compressedABC = []byte{0x61, 0x62, 0x63}
|
||||
|
||||
// compressedABCABC is "abc" followed by a copy tuple that repeats it:
|
||||
//
|
||||
// "11" copy tuple, "111" offset group 1, offset 000011 (=3),
|
||||
// length "0" (=3). Bits: 01100001 01100010 01100011 11111000 0110…
|
||||
// → 0x61 0x62 0x63 0xF8 0x60
|
||||
//
|
||||
// Decoded: "abcabc".
|
||||
var compressedABCABC = []byte{0x61, 0x62, 0x63, 0xF8, 0x60}
|
||||
|
||||
func TestMppcDecompressLiterals(t *testing.T) {
|
||||
d := NewMppcDecompressor()
|
||||
got, err := d.Decompress(mppcType64K|mppcCompressed, compressedABC)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, []byte("abc")) {
|
||||
t.Errorf("got %q, want %q", got, "abc")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcDecompressCopyTuple(t *testing.T) {
|
||||
d := NewMppcDecompressor()
|
||||
got, err := d.Decompress(mppcType64K|mppcCompressed, compressedABCABC)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, []byte("abcabc")) {
|
||||
t.Errorf("got %q, want %q", got, "abcabc")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcDecompressFlush(t *testing.T) {
|
||||
d := NewMppcDecompressor()
|
||||
// First call: populate history.
|
||||
_, _ = d.Decompress(mppcType64K|mppcCompressed, compressedABC)
|
||||
if d.history[0] != 'a' {
|
||||
t.Fatal("history not populated after first call")
|
||||
}
|
||||
// Second call with FLUSH: history must be zeroed, offset restarts at 0.
|
||||
_, _ = d.Decompress(mppcAtFront|mppcFlushed|mppcCompressed, compressedABC)
|
||||
if d.history[0] != 'a' || d.history[1] != 'b' || d.history[2] != 'c' {
|
||||
t.Errorf("unexpected history after flush: %q %q %q",
|
||||
d.history[0], d.history[1], d.history[2])
|
||||
}
|
||||
if d.offset != 3 {
|
||||
t.Errorf("offset after flush: got %d, want 3", d.offset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcDecompressReset(t *testing.T) {
|
||||
d := NewMppcDecompressor()
|
||||
_, _ = d.Decompress(mppcType64K|mppcCompressed, compressedABC)
|
||||
if d.offset != 3 {
|
||||
t.Fatalf("offset after first call: got %d, want 3", d.offset)
|
||||
}
|
||||
// RESET (PACKET_AT_FRONT): cursor returns to the front, history contents
|
||||
// preserved; the fresh tokens overwrite from position 0.
|
||||
_, _ = d.Decompress(mppcAtFront|mppcType64K|mppcCompressed, compressedABC)
|
||||
if d.offset != 3 {
|
||||
t.Fatalf("offset after reset+decompress: got %d, want 3", d.offset)
|
||||
}
|
||||
if d.history[0] != 'a' {
|
||||
t.Errorf("history[0] after reset: got %q", d.history[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcDecompressUncompressed(t *testing.T) {
|
||||
d := NewMppcDecompressor()
|
||||
plain := []byte("hello")
|
||||
got, err := d.Decompress(mppcType64K, plain) // no COMPRESSED flag
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, plain) {
|
||||
t.Errorf("got %q, want %q", got, plain)
|
||||
}
|
||||
// Uncompressed segments do NOT advance the shared history (matching
|
||||
// FreeRDP): only compressed tokens feed it.
|
||||
if d.offset != 0 {
|
||||
t.Errorf("offset: got %d, want 0 (history untouched)", d.offset)
|
||||
}
|
||||
}
|
||||
|
||||
// mppcTestEncoder is a minimal MPPC compressor used to generate reference
|
||||
// streams. It mirrors the decoder's grammar and history arithmetic exactly.
|
||||
type mppcTestEncoder struct {
|
||||
buf bitWriter
|
||||
hist []byte
|
||||
mask int
|
||||
big bool
|
||||
}
|
||||
|
||||
type bitWriter struct {
|
||||
out []byte
|
||||
cur byte
|
||||
nbit uint
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeBit(b int) {
|
||||
if b != 0 {
|
||||
w.cur |= 0x80 >> w.nbit
|
||||
}
|
||||
w.nbit++
|
||||
if w.nbit == 8 {
|
||||
w.out = append(w.out, w.cur)
|
||||
w.cur = 0
|
||||
w.nbit = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeBits(v, n int) {
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
w.writeBit((v >> uint(i)) & 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) flush() []byte {
|
||||
if w.nbit > 0 {
|
||||
w.out = append(w.out, w.cur)
|
||||
w.cur = 0
|
||||
w.nbit = 0
|
||||
}
|
||||
return w.out
|
||||
}
|
||||
|
||||
func newMppcTestEncoder(big bool) *mppcTestEncoder {
|
||||
mask := 8191
|
||||
if big {
|
||||
mask = 65535
|
||||
}
|
||||
return &mppcTestEncoder{big: big, mask: mask}
|
||||
}
|
||||
|
||||
func (e *mppcTestEncoder) emitLiteral(b byte) {
|
||||
if b < 0x80 {
|
||||
e.buf.writeBits(int(b), 8)
|
||||
} else {
|
||||
e.buf.writeBits(2, 2) // "10"
|
||||
e.buf.writeBits(int(b)&0x7F, 7)
|
||||
}
|
||||
e.hist = append(e.hist, b)
|
||||
}
|
||||
|
||||
func (e *mppcTestEncoder) emitCopy(off, length int) {
|
||||
e.buf.writeBits(3, 2) // "11"
|
||||
if e.big {
|
||||
switch {
|
||||
case off <= 63:
|
||||
e.buf.writeBits(7, 3) // "111"
|
||||
e.buf.writeBits(off, 6)
|
||||
case off <= 319:
|
||||
e.buf.writeBits(6, 3) // "110"
|
||||
e.buf.writeBits(off-64, 8)
|
||||
case off <= 2367:
|
||||
e.buf.writeBits(2, 2) // "10"
|
||||
e.buf.writeBits(off-320, 11)
|
||||
default:
|
||||
e.buf.writeBit(0)
|
||||
e.buf.writeBits(off-2368, 16)
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case off <= 63:
|
||||
e.buf.writeBits(3, 2) // "11"
|
||||
e.buf.writeBits(off, 6)
|
||||
case off <= 319:
|
||||
e.buf.writeBits(2, 2) // "10"
|
||||
e.buf.writeBits(off-64, 8)
|
||||
default:
|
||||
e.buf.writeBit(0)
|
||||
e.buf.writeBits(off-320, 13)
|
||||
}
|
||||
}
|
||||
// Length: "0" → 3; otherwise n leading 1-bits, a 0 terminator, then n+1
|
||||
// value bits with the (1<<m) high bit implied (m = n+1).
|
||||
if length == 3 {
|
||||
e.buf.writeBit(0)
|
||||
} else {
|
||||
m := 0
|
||||
for (1 << uint(m+1)) <= length {
|
||||
m++
|
||||
}
|
||||
k := m - 1
|
||||
for i := 0; i < k; i++ {
|
||||
e.buf.writeBit(1)
|
||||
}
|
||||
e.buf.writeBit(0)
|
||||
e.buf.writeBits(length-(1<<uint(m)), m)
|
||||
}
|
||||
// Apply the copy to the encoder's own history view (distance = off).
|
||||
pos := len(e.hist)
|
||||
src := (pos - off) & e.mask
|
||||
for i := 0; i < length; i++ {
|
||||
e.hist = append(e.hist, e.hist[src])
|
||||
src = (src + 1) & e.mask
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcLiteralRoundTrip(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
for _, big := range []bool{false, true} {
|
||||
enc := newMppcTestEncoder(big)
|
||||
var want []byte
|
||||
for i := 0; i < 500; i++ {
|
||||
b := byte(rng.Intn(256))
|
||||
enc.emitLiteral(b)
|
||||
want = append(want, b)
|
||||
}
|
||||
// 字面量语法与字典大小无关,两种编码器的字面量流都能被
|
||||
// 64K 解码器还原。
|
||||
got, err := NewMppcDecompressor().Decompress(mppcCompressed, enc.buf.flush())
|
||||
if err != nil {
|
||||
t.Fatalf("big=%v: %v", big, err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("big=%v: mismatch got %d bytes want %d", big, len(got), len(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcCopyRoundTrip(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(2))
|
||||
// (offset, length) pairs covering every offset group and several length
|
||||
// groups (3, 4-7, 8-15, 16-31, longer binary forms).
|
||||
cases := [][2]int{
|
||||
{5, 3}, {63, 4}, {64, 7}, {100, 3}, {319, 8},
|
||||
{320, 16}, {1000, 31}, {2367, 9}, {2368, 32},
|
||||
{5000, 100}, {30000, 300}, {60000, 500},
|
||||
}
|
||||
// 解码器固定使用 RDP5 64K 字典(协商决定),8K 语法不再可解。
|
||||
big := true
|
||||
enc := newMppcTestEncoder(big)
|
||||
var want []byte
|
||||
for _, c := range cases {
|
||||
off, length := c[0], c[1]
|
||||
for off > len(enc.hist) {
|
||||
b := byte(rng.Intn(256))
|
||||
enc.emitLiteral(b)
|
||||
want = append(want, b)
|
||||
}
|
||||
enc.emitCopy(off, length)
|
||||
pos := len(want)
|
||||
src := (pos - off) & enc.mask
|
||||
for i := 0; i < length; i++ {
|
||||
want = append(want, want[src])
|
||||
src = (src + 1) & enc.mask
|
||||
}
|
||||
}
|
||||
got, err := NewMppcDecompressor().Decompress(mppcType64K|mppcCompressed, enc.buf.flush())
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("copy round-trip mismatch (%d vs %d bytes)", len(got), len(want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcHistoryAcrossSegments(t *testing.T) {
|
||||
enc := newMppcTestEncoder(true)
|
||||
d := NewMppcDecompressor()
|
||||
flags := byte(mppcCompressed) // 无 RESET/AT_FRONT:历史跨段连续
|
||||
|
||||
// Segment 1: seed literals.
|
||||
var want []byte
|
||||
for i := 0; i < 400; i++ {
|
||||
b := byte('A' + i%26)
|
||||
enc.emitLiteral(b)
|
||||
want = append(want, b)
|
||||
}
|
||||
got, err := d.Decompress(flags, enc.buf.flush())
|
||||
if err != nil || !bytes.Equal(got, want[:len(got)]) {
|
||||
t.Fatalf("seg1: err=%v len=%d", err, len(got))
|
||||
}
|
||||
enc.buf.out = nil
|
||||
|
||||
// Segment 2: copy referencing segment 1 content.
|
||||
enc.emitCopy(200, 50)
|
||||
got2, err := d.Decompress(flags, enc.buf.flush())
|
||||
if err != nil {
|
||||
t.Fatalf("seg2: %v", err)
|
||||
}
|
||||
want2 := want[len(want)-200:]
|
||||
for i := 0; i < 50; i++ {
|
||||
if got2[i] != want2[i] {
|
||||
t.Fatalf("seg2 mismatch at %d: got %q want %q", i, got2[i], want2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcFlushAndReset(t *testing.T) {
|
||||
enc := newMppcTestEncoder(true)
|
||||
d := NewMppcDecompressor()
|
||||
flags := byte(mppcCompressed) // 无 RESET/AT_FRONT:历史跨段连续
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
enc.emitLiteral(byte(i % 256))
|
||||
}
|
||||
if _, err := d.Decompress(flags, enc.buf.flush()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.offset == 0 {
|
||||
t.Fatal("history should have advanced")
|
||||
}
|
||||
|
||||
// RESET moves the write cursor to the front without clearing content.
|
||||
enc2 := newMppcTestEncoder(true)
|
||||
enc2.emitLiteral('x')
|
||||
enc2.emitLiteral('y')
|
||||
got, err := d.Decompress(mppcCompressed|mppcAtFront|mppcType64K, enc2.buf.flush())
|
||||
if err != nil || string(got) != "xy" {
|
||||
t.Fatalf("reset: err=%v got=%q", err, got)
|
||||
}
|
||||
if d.offset != 2 {
|
||||
t.Fatalf("reset should restart the cursor, got %d", d.offset)
|
||||
}
|
||||
|
||||
// FLUSH clears the whole dictionary.
|
||||
got, err = d.Decompress(mppcCompressed|mppcAtFront|mppcFlushed, enc2.buf.flush())
|
||||
if err != nil || string(got) != "xy" {
|
||||
t.Fatalf("flush: err=%v got=%q", err, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMppcRejectsGarbage(t *testing.T) {
|
||||
d := NewMppcDecompressor()
|
||||
// A long run of 1-bits overflows the LengthOfMatch prefix code.
|
||||
data := make([]byte, 16)
|
||||
for i := range data {
|
||||
data[i] = 0xFF
|
||||
}
|
||||
if _, err := d.Decompress(mppcCompressed|mppcType64K, data); err == nil {
|
||||
t.Fatal("expected error for length code overflow")
|
||||
}
|
||||
}
|
||||
+997
@@ -0,0 +1,997 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func CVAL(p *[]uint8) int {
|
||||
a := int((*p)[0])
|
||||
*p = (*p)[1:]
|
||||
return a
|
||||
}
|
||||
|
||||
func CVAL2(p *[]uint8, v *uint16) {
|
||||
*v = *((*uint16)(unsafe.Pointer(&(*p)[0])))
|
||||
*p = (*p)[2:]
|
||||
}
|
||||
|
||||
func CVAL3(p *[]uint8, v *[3]uint8) {
|
||||
(*v)[0] = (*p)[0]
|
||||
(*v)[1] = (*p)[1]
|
||||
(*v)[2] = (*p)[2]
|
||||
*p = (*p)[3:]
|
||||
}
|
||||
|
||||
func REPEAT(f func(), count *int, x *int, width int) {
|
||||
for *count > 0 && *x < width {
|
||||
f()
|
||||
*count--
|
||||
*x++
|
||||
}
|
||||
}
|
||||
|
||||
// rleFailLog 记录 RLE 解码失败(限次),此前失败被静默吞掉:
|
||||
// 半解码的池化缓冲被直接画上画布形成噪块,且无任何日志可查。
|
||||
func rleFailLog(reason string, width, height, inputLen int, consumed int) {
|
||||
slog.Warn("bitmap RLE decode failed", "reason", reason,
|
||||
"w", width, "h", height, "inputLen", inputLen, "consumed", consumed)
|
||||
}
|
||||
|
||||
/* 1 byte bitmap decompress */
|
||||
func decompress1(output *[]uint8, width, height int, input []uint8, size int) bool {
|
||||
var (
|
||||
prevline, line, count int
|
||||
offset, code int
|
||||
x int = width
|
||||
opcode int
|
||||
lastopcode int8 = -1
|
||||
insertmix, bicolour, isfillormix bool
|
||||
mixmask, mask uint8
|
||||
colour1, colour2 uint8
|
||||
mix uint8 = 0xff
|
||||
fom_mask uint8
|
||||
)
|
||||
out := *output
|
||||
for len(input) != 0 {
|
||||
fom_mask = 0
|
||||
code = CVAL(&input)
|
||||
opcode = code >> 4
|
||||
/* Handle different opcode forms */
|
||||
switch opcode {
|
||||
case 0xc, 0xd, 0xe:
|
||||
opcode -= 6
|
||||
count = int(code & 0xf)
|
||||
offset = 16
|
||||
break
|
||||
case 0xf:
|
||||
opcode = code & 0xf
|
||||
if opcode < 9 {
|
||||
count = int(CVAL(&input))
|
||||
count |= int(CVAL(&input) << 8)
|
||||
} else {
|
||||
count = 1
|
||||
if opcode < 0xb {
|
||||
count = 8
|
||||
}
|
||||
}
|
||||
offset = 0
|
||||
break
|
||||
default:
|
||||
opcode >>= 1
|
||||
count = int(code & 0x1f)
|
||||
offset = 32
|
||||
break
|
||||
}
|
||||
/* Handle strange cases for counts */
|
||||
if offset != 0 {
|
||||
isfillormix = ((opcode == 2) || (opcode == 7))
|
||||
if count == 0 {
|
||||
if isfillormix {
|
||||
count = int(CVAL(&input)) + 1
|
||||
} else {
|
||||
count = int(CVAL(&input) + offset)
|
||||
}
|
||||
} else if isfillormix {
|
||||
count <<= 3
|
||||
}
|
||||
}
|
||||
/* Read preliminary data */
|
||||
switch opcode {
|
||||
case 0: /* Fill */
|
||||
if (lastopcode == int8(opcode)) && !((x == width) && (prevline == 0)) {
|
||||
insertmix = true
|
||||
}
|
||||
break
|
||||
case 8: /* Bicolour */
|
||||
colour1 = uint8(CVAL(&input))
|
||||
colour2 = uint8(CVAL(&input))
|
||||
break
|
||||
case 3: /* Colour */
|
||||
colour2 = uint8(CVAL(&input))
|
||||
break
|
||||
case 6: /* SetMix/Mix */
|
||||
fallthrough
|
||||
case 7: /* SetMix/FillOrMix */
|
||||
mix = uint8(CVAL(&input))
|
||||
opcode -= 5
|
||||
break
|
||||
case 9: /* FillOrMix_1 */
|
||||
mask = 0x03
|
||||
opcode = 0x02
|
||||
fom_mask = 3
|
||||
break
|
||||
case 0x0a: /* FillOrMix_2 */
|
||||
mask = 0x05
|
||||
opcode = 0x02
|
||||
fom_mask = 5
|
||||
break
|
||||
}
|
||||
lastopcode = int8(opcode)
|
||||
mixmask = 0
|
||||
/* Output body */
|
||||
for count > 0 {
|
||||
if x >= width {
|
||||
if height <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
x = 0
|
||||
height--
|
||||
prevline = line
|
||||
line = height * width
|
||||
}
|
||||
switch opcode {
|
||||
case 0: /* Fill */
|
||||
if insertmix {
|
||||
if prevline == 0 {
|
||||
out[x+line] = mix
|
||||
} else {
|
||||
out[x+line] = out[prevline+x] ^ mix
|
||||
}
|
||||
insertmix = false
|
||||
count--
|
||||
x++
|
||||
}
|
||||
n := min(count, width-x)
|
||||
if prevline == 0 {
|
||||
clear(out[x+line : x+line+n])
|
||||
} else {
|
||||
copy(out[x+line:x+line+n], out[prevline+x:prevline+x+n])
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 1: /* Mix */
|
||||
n := min(count, width-x)
|
||||
if prevline == 0 {
|
||||
seg := out[x+line : x+line+n]
|
||||
for i := range seg {
|
||||
seg[i] = mix
|
||||
}
|
||||
} else {
|
||||
src := out[prevline+x : prevline+x+n]
|
||||
dst := out[x+line : x+line+n]
|
||||
for i := range dst {
|
||||
dst[i] = src[i] ^ mix
|
||||
}
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 2: /* Fill or Mix */
|
||||
if prevline == 0 {
|
||||
for count > 0 && x < width {
|
||||
mixmask <<= 1
|
||||
if mixmask == 0 {
|
||||
mask = fom_mask
|
||||
if fom_mask == 0 {
|
||||
mask = uint8(CVAL(&input))
|
||||
mixmask = 1
|
||||
}
|
||||
}
|
||||
if mask&mixmask != 0 {
|
||||
out[x+line] = mix
|
||||
} else {
|
||||
out[x+line] = 0
|
||||
}
|
||||
|
||||
count--
|
||||
x++
|
||||
}
|
||||
} else {
|
||||
for count > 0 && x < width {
|
||||
mixmask = mixmask << 1
|
||||
if mixmask == 0 {
|
||||
mask = fom_mask
|
||||
if fom_mask == 0 {
|
||||
mask = uint8(CVAL(&input))
|
||||
mixmask = 1
|
||||
}
|
||||
}
|
||||
if mask&mixmask != 0 {
|
||||
out[x+line] = out[prevline+x] ^ mix
|
||||
} else {
|
||||
out[x+line] = out[prevline+x]
|
||||
}
|
||||
|
||||
count--
|
||||
x++
|
||||
}
|
||||
}
|
||||
break
|
||||
case 3: /* Colour */
|
||||
n := min(count, width-x)
|
||||
seg := out[x+line : x+line+n]
|
||||
for i := range seg {
|
||||
seg[i] = colour2
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 4: /* Copy */
|
||||
for count > 0 && x < width {
|
||||
n := min(count, width-x)
|
||||
if len(input) < n {
|
||||
return false
|
||||
}
|
||||
copy(out[x+line:x+line+n], input[:n])
|
||||
input = input[n:]
|
||||
count -= n
|
||||
x += n
|
||||
}
|
||||
break
|
||||
case 8: /* Bicolour */
|
||||
for count > 0 && x < width {
|
||||
if bicolour {
|
||||
out[x+line] = colour2
|
||||
bicolour = false
|
||||
} else {
|
||||
out[x+line] = colour1
|
||||
bicolour = true
|
||||
count++
|
||||
}
|
||||
|
||||
count--
|
||||
x++
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case 0xd: /* White */
|
||||
n := min(count, width-x)
|
||||
seg := out[x+line : x+line+n]
|
||||
for i := range seg {
|
||||
seg[i] = 0xff
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 0xe: /* Black */
|
||||
n := min(count, width-x)
|
||||
clear(out[x+line : x+line+n])
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
default:
|
||||
fmt.Printf("bitmap opcode 0x%x\n", opcode)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// decompress2Pool reuses the intermediate []uint16 buffer across calls to
|
||||
// decompress2, avoiding a large per-frame allocation.
|
||||
var decompress2Pool sync.Pool
|
||||
|
||||
/* 2 byte bitmap decompress */
|
||||
func decompress2(output *[]uint8, width, height int, input []uint8, size int) bool {
|
||||
needed := width * height
|
||||
|
||||
var out []uint16
|
||||
if v := decompress2Pool.Get(); v != nil {
|
||||
out = v.([]uint16)
|
||||
if cap(out) < needed {
|
||||
out = make([]uint16, needed)
|
||||
} else {
|
||||
out = out[:needed]
|
||||
}
|
||||
} else {
|
||||
out = make([]uint16, needed)
|
||||
}
|
||||
defer func() { decompress2Pool.Put(out[:cap(out)]) }()
|
||||
|
||||
var (
|
||||
prevline, line, count int
|
||||
offset, code int
|
||||
x int = width
|
||||
opcode int
|
||||
lastopcode int = -1
|
||||
insertmix, bicolour, isfillormix bool
|
||||
mixmask, mask uint8
|
||||
colour1, colour2 uint16
|
||||
mix uint16 = 0xffff
|
||||
fom_mask uint8
|
||||
)
|
||||
inputLen0 := len(input)
|
||||
|
||||
for len(input) != 0 {
|
||||
fom_mask = 0
|
||||
code = CVAL(&input)
|
||||
opcode = code >> 4
|
||||
/* Handle different opcode forms */
|
||||
switch opcode {
|
||||
case 0xc, 0xd, 0xe:
|
||||
opcode -= 6
|
||||
count = code & 0xf
|
||||
offset = 16
|
||||
break
|
||||
case 0xf:
|
||||
opcode = code & 0xf
|
||||
if opcode < 9 {
|
||||
count = CVAL(&input)
|
||||
count |= CVAL(&input) << 8
|
||||
} else {
|
||||
count = 1
|
||||
if opcode < 0xb {
|
||||
count = 8
|
||||
}
|
||||
}
|
||||
offset = 0
|
||||
break
|
||||
default:
|
||||
opcode >>= 1
|
||||
count = code & 0x1f
|
||||
offset = 32
|
||||
break
|
||||
}
|
||||
|
||||
/* Handle strange cases for counts */
|
||||
if offset != 0 {
|
||||
isfillormix = ((opcode == 2) || (opcode == 7))
|
||||
if count == 0 {
|
||||
if isfillormix {
|
||||
count = CVAL(&input) + 1
|
||||
} else {
|
||||
count = CVAL(&input) + offset
|
||||
}
|
||||
} else if isfillormix {
|
||||
count <<= 3
|
||||
}
|
||||
}
|
||||
/* Read preliminary data */
|
||||
switch opcode {
|
||||
case 0: /* Fill */
|
||||
if (lastopcode == opcode) && !((x == width) && (prevline == 0)) {
|
||||
insertmix = true
|
||||
}
|
||||
break
|
||||
case 8: /* Bicolour */
|
||||
CVAL2(&input, &colour1)
|
||||
CVAL2(&input, &colour2)
|
||||
break
|
||||
case 3: /* Colour */
|
||||
CVAL2(&input, &colour2)
|
||||
break
|
||||
case 6: /* SetMix/Mix */
|
||||
fallthrough
|
||||
case 7: /* SetMix/FillOrMix */
|
||||
CVAL2(&input, &mix)
|
||||
opcode -= 5
|
||||
break
|
||||
case 9: /* FillOrMix_1 */
|
||||
mask = 0x03
|
||||
opcode = 0x02
|
||||
fom_mask = 3
|
||||
break
|
||||
case 0x0a: /* FillOrMix_2 */
|
||||
mask = 0x05
|
||||
opcode = 0x02
|
||||
fom_mask = 5
|
||||
break
|
||||
}
|
||||
lastopcode = opcode
|
||||
mixmask = 0
|
||||
/* Output body */
|
||||
for count > 0 {
|
||||
if x >= width {
|
||||
if height <= 0 {
|
||||
rleFailLog("lines exhausted", width, height, inputLen0, inputLen0-len(input))
|
||||
return false
|
||||
}
|
||||
|
||||
x = 0
|
||||
height--
|
||||
prevline = line
|
||||
line = height * width
|
||||
}
|
||||
switch opcode {
|
||||
case 0: /* Fill */
|
||||
if insertmix {
|
||||
if prevline == 0 {
|
||||
out[x+line] = mix
|
||||
} else {
|
||||
out[x+line] = out[prevline+x] ^ mix
|
||||
}
|
||||
insertmix = false
|
||||
count--
|
||||
x++
|
||||
}
|
||||
n := min(count, width-x)
|
||||
if prevline == 0 {
|
||||
clear(out[x+line : x+line+n])
|
||||
} else {
|
||||
copy(out[x+line:x+line+n], out[prevline+x:prevline+x+n])
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 1: /* Mix */
|
||||
n := min(count, width-x)
|
||||
if prevline == 0 {
|
||||
seg := out[x+line : x+line+n]
|
||||
for i := range seg {
|
||||
seg[i] = mix
|
||||
}
|
||||
} else {
|
||||
src := out[prevline+x : prevline+x+n]
|
||||
dst := out[x+line : x+line+n]
|
||||
for i := range dst {
|
||||
dst[i] = src[i] ^ mix
|
||||
}
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 2: /* Fill or Mix */
|
||||
if prevline == 0 {
|
||||
for count > 0 && x < width {
|
||||
mixmask <<= 1
|
||||
if mixmask == 0 {
|
||||
mask = fom_mask
|
||||
if fom_mask == 0 {
|
||||
mask = uint8(CVAL(&input))
|
||||
mixmask = 1
|
||||
}
|
||||
}
|
||||
if mask&mixmask != 0 {
|
||||
out[x+line] = mix
|
||||
} else {
|
||||
out[x+line] = 0
|
||||
}
|
||||
|
||||
count--
|
||||
x++
|
||||
}
|
||||
} else {
|
||||
for count > 0 && x < width {
|
||||
mixmask = mixmask << 1
|
||||
if mixmask == 0 {
|
||||
mask = fom_mask
|
||||
if fom_mask == 0 {
|
||||
mask = uint8(CVAL(&input))
|
||||
mixmask = 1
|
||||
}
|
||||
}
|
||||
if mask&mixmask != 0 {
|
||||
out[x+line] = out[prevline+x] ^ mix
|
||||
} else {
|
||||
out[x+line] = out[prevline+x]
|
||||
}
|
||||
|
||||
count--
|
||||
x++
|
||||
}
|
||||
}
|
||||
break
|
||||
case 3: /* Colour */
|
||||
n := min(count, width-x)
|
||||
seg := out[x+line : x+line+n]
|
||||
for i := range seg {
|
||||
seg[i] = colour2
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 4: /* Copy */
|
||||
for count > 0 && x < width {
|
||||
n := min(count, width-x)
|
||||
if len(input) < n*2 {
|
||||
rleFailLog("copy input exhausted", width, height, inputLen0, inputLen0-len(input))
|
||||
return false
|
||||
}
|
||||
copy(out[x+line:x+line+n], unsafe.Slice((*uint16)(unsafe.Pointer(&input[0])), n))
|
||||
input = input[n*2:]
|
||||
count -= n
|
||||
x += n
|
||||
}
|
||||
|
||||
break
|
||||
case 8: /* Bicolour */
|
||||
for count > 0 && x < width {
|
||||
if bicolour {
|
||||
out[x+line] = colour2
|
||||
bicolour = false
|
||||
} else {
|
||||
out[x+line] = colour1
|
||||
bicolour = true
|
||||
count++
|
||||
}
|
||||
|
||||
count--
|
||||
x++
|
||||
}
|
||||
|
||||
break
|
||||
case 0xd: /* White */
|
||||
n2 := min(count, width-x)
|
||||
seg2 := out[x+line : x+line+n2]
|
||||
for i := range seg2 {
|
||||
seg2[i] = 0xffff
|
||||
}
|
||||
count -= n2
|
||||
x += n2
|
||||
break
|
||||
case 0xe: /* Black */
|
||||
n3 := min(count, width-x)
|
||||
clear(out[x+line : x+line+n3])
|
||||
count -= n3
|
||||
x += n3
|
||||
break
|
||||
default:
|
||||
rleFailLog(fmt.Sprintf("bad opcode 0x%x", opcode), width, height, inputLen0, inputLen0-len(input))
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
outBytes := *output
|
||||
for i, v := range out {
|
||||
outBytes[i*2] = byte(v >> 8)
|
||||
outBytes[i*2+1] = byte(v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// /* 3 byte bitmap decompress */
|
||||
func decompress3(output *[]uint8, width, height int, input []uint8, size int) bool {
|
||||
var (
|
||||
prevline, line, count int
|
||||
opcode, offset, code int
|
||||
x int = width
|
||||
lastopcode int = -1
|
||||
insertmix, bicolour, isfillormix bool
|
||||
mixmask, mask uint8
|
||||
colour1 = [3]uint8{0, 0, 0}
|
||||
colour2 = [3]uint8{0, 0, 0}
|
||||
mix = [3]uint8{0xff, 0xff, 0xff}
|
||||
fom_mask uint8
|
||||
)
|
||||
out := *output
|
||||
for len(input) != 0 {
|
||||
fom_mask = 0
|
||||
code = CVAL(&input)
|
||||
opcode = code >> 4
|
||||
/* Handle different opcode forms */
|
||||
switch opcode {
|
||||
case 0xc, 0xd, 0xe:
|
||||
opcode -= 6
|
||||
count = code & 0xf
|
||||
offset = 16
|
||||
break
|
||||
case 0xf:
|
||||
opcode = code & 0xf
|
||||
if opcode < 9 {
|
||||
count = CVAL(&input)
|
||||
count |= CVAL(&input) << 8
|
||||
} else {
|
||||
count = 1
|
||||
if opcode < 0xb {
|
||||
count = 8
|
||||
}
|
||||
}
|
||||
offset = 0
|
||||
break
|
||||
default:
|
||||
opcode >>= 1
|
||||
count = code & 0x1f
|
||||
offset = 32
|
||||
break
|
||||
}
|
||||
|
||||
/* Handle strange cases for counts */
|
||||
if offset != 0 {
|
||||
isfillormix = ((opcode == 2) || (opcode == 7))
|
||||
if count == 0 {
|
||||
if isfillormix {
|
||||
count = CVAL(&input) + 1
|
||||
} else {
|
||||
count = CVAL(&input) + offset
|
||||
}
|
||||
} else if isfillormix {
|
||||
count <<= 3
|
||||
}
|
||||
}
|
||||
/* Read preliminary data */
|
||||
switch opcode {
|
||||
case 0: /* Fill */
|
||||
if (lastopcode == opcode) && !((x == width) && (prevline == 0)) {
|
||||
insertmix = true
|
||||
}
|
||||
break
|
||||
case 8: /* Bicolour */
|
||||
CVAL3(&input, &colour1)
|
||||
CVAL3(&input, &colour2)
|
||||
break
|
||||
case 3: /* Colour */
|
||||
CVAL3(&input, &colour2)
|
||||
break
|
||||
case 6: /* SetMix/Mix */
|
||||
fallthrough
|
||||
case 7: /* SetMix/FillOrMix */
|
||||
CVAL3(&input, &mix)
|
||||
opcode -= 5
|
||||
break
|
||||
case 9: /* FillOrMix_1 */
|
||||
mask = 0x03
|
||||
opcode = 0x02
|
||||
fom_mask = 3
|
||||
break
|
||||
case 0x0a: /* FillOrMix_2 */
|
||||
mask = 0x05
|
||||
opcode = 0x02
|
||||
fom_mask = 5
|
||||
break
|
||||
}
|
||||
|
||||
lastopcode = opcode
|
||||
mixmask = 0
|
||||
/* Output body */
|
||||
for count > 0 {
|
||||
if x >= width {
|
||||
if height <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
x = 0
|
||||
height--
|
||||
prevline = line
|
||||
line = height * width * 3
|
||||
}
|
||||
switch opcode {
|
||||
case 0: /* Fill */
|
||||
if insertmix {
|
||||
if prevline == 0 {
|
||||
out[3*x+line] = mix[0]
|
||||
out[3*x+line+1] = mix[1]
|
||||
out[3*x+line+2] = mix[2]
|
||||
} else {
|
||||
out[3*x+line] = out[prevline+3*x] ^ mix[0]
|
||||
out[3*x+line+1] = out[prevline+3*x+1] ^ mix[1]
|
||||
out[3*x+line+2] = out[prevline+3*x+2] ^ mix[2]
|
||||
}
|
||||
insertmix = false
|
||||
count--
|
||||
x++
|
||||
}
|
||||
n := min(count, width-x)
|
||||
if prevline == 0 {
|
||||
clear(out[3*x+line : 3*x+line+3*n])
|
||||
} else {
|
||||
dstBase := 3*x + line
|
||||
srcBase := prevline + 3*x
|
||||
copy(out[dstBase:dstBase+3*n], out[srcBase:srcBase+3*n])
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 1: /* Mix */
|
||||
n := min(count, width-x)
|
||||
dst1 := out[3*x+line : 3*x+line+3*n]
|
||||
if prevline == 0 {
|
||||
// Exponential-doubling copy: O(log n) memcpy calls
|
||||
dst1[0], dst1[1], dst1[2] = mix[0], mix[1], mix[2]
|
||||
for wrote := 3; wrote < len(dst1); {
|
||||
wrote += copy(dst1[wrote:], dst1[:wrote])
|
||||
}
|
||||
} else {
|
||||
src1 := out[prevline+3*x : prevline+3*x+3*n]
|
||||
for i := 0; i+2 < len(dst1); i += 3 {
|
||||
dst1[i] = src1[i] ^ mix[0]
|
||||
dst1[i+1] = src1[i+1] ^ mix[1]
|
||||
dst1[i+2] = src1[i+2] ^ mix[2]
|
||||
}
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 2: /* Fill or Mix */
|
||||
if prevline == 0 {
|
||||
base := 3*x + line
|
||||
for count > 0 && x < width {
|
||||
mixmask = mixmask << 1
|
||||
if mixmask == 0 {
|
||||
mask = fom_mask
|
||||
if fom_mask == 0 {
|
||||
mask = uint8(CVAL(&input))
|
||||
mixmask = 1
|
||||
}
|
||||
}
|
||||
if mask&mixmask != 0 {
|
||||
out[base] = mix[0]
|
||||
out[base+1] = mix[1]
|
||||
out[base+2] = mix[2]
|
||||
} else {
|
||||
out[base] = 0
|
||||
out[base+1] = 0
|
||||
out[base+2] = 0
|
||||
}
|
||||
base += 3
|
||||
count--
|
||||
x++
|
||||
}
|
||||
} else {
|
||||
base := 3*x + line
|
||||
prev := prevline + 3*x
|
||||
for count > 0 && x < width {
|
||||
mixmask = mixmask << 1
|
||||
if mixmask == 0 {
|
||||
mask = fom_mask
|
||||
if fom_mask == 0 {
|
||||
mask = uint8(CVAL(&input))
|
||||
mixmask = 1
|
||||
}
|
||||
}
|
||||
if mask&mixmask != 0 {
|
||||
out[base] = out[prev] ^ mix[0]
|
||||
out[base+1] = out[prev+1] ^ mix[1]
|
||||
out[base+2] = out[prev+2] ^ mix[2]
|
||||
} else {
|
||||
out[base] = out[prev]
|
||||
out[base+1] = out[prev+1]
|
||||
out[base+2] = out[prev+2]
|
||||
}
|
||||
base += 3
|
||||
prev += 3
|
||||
count--
|
||||
x++
|
||||
}
|
||||
}
|
||||
break
|
||||
case 3: /* Colour */
|
||||
n := min(count, width-x)
|
||||
seg3 := out[3*x+line : 3*x+line+3*n]
|
||||
seg3[0], seg3[1], seg3[2] = colour2[0], colour2[1], colour2[2]
|
||||
for wrote := 3; wrote < len(seg3); {
|
||||
wrote += copy(seg3[wrote:], seg3[:wrote])
|
||||
}
|
||||
count -= n
|
||||
x += n
|
||||
break
|
||||
case 4: /* Copy */
|
||||
for count > 0 && x < width {
|
||||
n := min(count, width-x)
|
||||
if len(input) < n*3 {
|
||||
return false
|
||||
}
|
||||
copy(out[3*x+line:3*x+line+n*3], input[:n*3])
|
||||
input = input[n*3:]
|
||||
count -= n
|
||||
x += n
|
||||
}
|
||||
break
|
||||
case 8: /* Bicolour */
|
||||
base8 := 3*x + line
|
||||
for count > 0 && x < width {
|
||||
if bicolour {
|
||||
out[base8] = colour2[0]
|
||||
out[base8+1] = colour2[1]
|
||||
out[base8+2] = colour2[2]
|
||||
bicolour = false
|
||||
} else {
|
||||
out[base8] = colour1[0]
|
||||
out[base8+1] = colour1[1]
|
||||
out[base8+2] = colour1[2]
|
||||
bicolour = true
|
||||
count++
|
||||
}
|
||||
base8 += 3
|
||||
count--
|
||||
x++
|
||||
}
|
||||
break
|
||||
case 0xd: /* White */
|
||||
n3 := min(count, width-x)
|
||||
seg3 := out[3*x+line : 3*x+line+3*n3]
|
||||
for i := range seg3 {
|
||||
seg3[i] = 0xff
|
||||
}
|
||||
count -= n3
|
||||
x += n3
|
||||
break
|
||||
case 0xe: /* Black */
|
||||
n2 := min(count, width-x)
|
||||
clear(out[3*x+line : 3*x+line+3*n2])
|
||||
count -= n2
|
||||
x += n2
|
||||
break
|
||||
default:
|
||||
fmt.Printf("bitmap opcode 0x%x\n", opcode)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/* decompress a colour plane */
|
||||
func processPlane(in *[]uint8, width, height int, output *[]uint8, j int) int {
|
||||
var (
|
||||
indexw int
|
||||
indexh int
|
||||
code int
|
||||
collen int
|
||||
replen int
|
||||
color uint8
|
||||
x uint8
|
||||
revcode int
|
||||
lastline int
|
||||
thisline int
|
||||
)
|
||||
ln := len(*in)
|
||||
out := *output // hoist pointer dereference; writes to out[i] affect the underlying array
|
||||
|
||||
lastline = 0
|
||||
indexh = 0
|
||||
i := 0
|
||||
for indexh < height {
|
||||
thisline = j + (width * height * 4) - ((indexh + 1) * width * 4)
|
||||
color = 0
|
||||
indexw = 0
|
||||
i = thisline
|
||||
|
||||
if lastline == 0 {
|
||||
for indexw < width {
|
||||
code = CVAL(in)
|
||||
replen = int(code & 0xf)
|
||||
collen = int((code >> 4) & 0xf)
|
||||
revcode = (replen << 4) | collen
|
||||
if (revcode <= 47) && (revcode >= 16) {
|
||||
replen = revcode
|
||||
collen = 0
|
||||
}
|
||||
for collen > 0 {
|
||||
color = uint8(CVAL(in))
|
||||
out[i] = color
|
||||
i += 4
|
||||
|
||||
indexw++
|
||||
collen--
|
||||
}
|
||||
for replen > 0 {
|
||||
out[i] = color
|
||||
i += 4
|
||||
indexw++
|
||||
replen--
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// prevOffset is constant per row: indexw*4+lastline == i+(lastline-thisline)
|
||||
// because i == thisline+indexw*4. Pre-computing it eliminates a multiply
|
||||
// per pixel in both inner loops.
|
||||
prevOffset := lastline - thisline
|
||||
for indexw < width {
|
||||
code = CVAL(in)
|
||||
replen = int(code & 0xf)
|
||||
collen = int((code >> 4) & 0xf)
|
||||
revcode = (replen << 4) | collen
|
||||
if (revcode <= 47) && (revcode >= 16) {
|
||||
replen = revcode
|
||||
collen = 0
|
||||
}
|
||||
for collen > 0 {
|
||||
x = uint8(CVAL(in))
|
||||
if x&1 != 0 {
|
||||
x = x >> 1
|
||||
x = x + 1
|
||||
color = -x
|
||||
} else {
|
||||
x = x >> 1
|
||||
color = x
|
||||
}
|
||||
x = out[i+prevOffset] + color
|
||||
out[i] = x
|
||||
i += 4
|
||||
indexw++
|
||||
collen--
|
||||
}
|
||||
for replen > 0 {
|
||||
x = out[i+prevOffset] + color
|
||||
out[i] = x
|
||||
i += 4
|
||||
indexw++
|
||||
replen--
|
||||
}
|
||||
}
|
||||
}
|
||||
indexh++
|
||||
lastline = thisline
|
||||
}
|
||||
return ln - len(*in)
|
||||
}
|
||||
|
||||
/* 4 byte bitmap decompress */
|
||||
func decompress4(output *[]uint8, width, height int, input []uint8, size int) bool {
|
||||
var (
|
||||
code int
|
||||
onceBytes, total int
|
||||
)
|
||||
|
||||
code = CVAL(&input)
|
||||
rle := code&0x10 != 0
|
||||
noAlpha := code&0x20 != 0
|
||||
|
||||
if !rle {
|
||||
return false
|
||||
}
|
||||
|
||||
total = 1
|
||||
out := *output
|
||||
|
||||
if noAlpha {
|
||||
// No alpha plane in the stream; fill alpha channel with 0xFF.
|
||||
for i := 3; i < len(out); i += 4 {
|
||||
out[i] = 0xFF
|
||||
}
|
||||
} else {
|
||||
onceBytes = processPlane(&input, width, height, output, 3)
|
||||
total += onceBytes
|
||||
}
|
||||
|
||||
onceBytes = processPlane(&input, width, height, output, 2)
|
||||
total += onceBytes
|
||||
|
||||
onceBytes = processPlane(&input, width, height, output, 1)
|
||||
total += onceBytes
|
||||
|
||||
onceBytes = processPlane(&input, width, height, output, 0)
|
||||
total += onceBytes
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DecompressInto decompresses bitmap data into dst, reusing dst if it has
|
||||
// sufficient capacity (size = width*height*bpp). If dst is nil or too small
|
||||
// a new slice is allocated. Returns the (re)used output slice.
|
||||
func DecompressInto(input []uint8, dst []uint8, width, height int, bpp int) ([]uint8, bool) {
|
||||
size := width * height * bpp
|
||||
if cap(dst) >= size {
|
||||
dst = dst[:size]
|
||||
} else {
|
||||
dst = make([]uint8, size)
|
||||
}
|
||||
ok := false
|
||||
switch bpp {
|
||||
case 1:
|
||||
ok = decompress1(&dst, width, height, input, size)
|
||||
case 2:
|
||||
ok = decompress2(&dst, width, height, input, size)
|
||||
case 3:
|
||||
ok = decompress3(&dst, width, height, input, size)
|
||||
case 4:
|
||||
ok = decompress4(&dst, width, height, input, size)
|
||||
default:
|
||||
fmt.Printf("bpp %d\n", bpp)
|
||||
}
|
||||
return dst, ok
|
||||
}
|
||||
|
||||
/* main decompress function */
|
||||
func Decompress(input []uint8, width, height int, bpp int) []uint8 {
|
||||
out, _ := DecompressInto(input, nil, width, height, bpp)
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// rle_test.go
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkDecompress3(b *testing.B) {
|
||||
input := []byte{
|
||||
192, 44, 200, 8, 132, 200, 8, 200, 8, 200, 8, 200, 8, 0, 19, 132, 232, 8, 12, 50, 142, 66, 77, 58, 208, 59, 225, 25, 1, 0, 0, 0, 0, 0, 0, 0, 132, 139, 33, 142, 66, 142, 66, 142, 66, 208, 59, 4, 43, 1, 0, 0, 0, 0, 0, 0, 0, 132, 203, 41, 142, 66, 142, 66, 142, 66, 208, 59, 96, 0, 1, 0, 0, 0, 0, 0, 0, 0, 132, 9, 17, 142, 66, 142, 66, 142, 66, 208, 59, 230, 27, 1, 0, 0, 0, 0, 0, 0, 0, 132, 200, 8, 9, 17, 139, 33, 74, 25, 243, 133, 14, 200, 8, 132, 200, 8, 200, 8, 200, 8, 200, 8,
|
||||
}
|
||||
dst := make([]uint8, 64*64*3)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
DecompressInto(input, dst, 64, 64, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
input := []byte{
|
||||
192, 44, 200, 8, 132, 200, 8, 200, 8, 200, 8, 200, 8, 0, 19, 132, 232, 8, 12, 50, 142, 66, 77, 58, 208, 59, 225, 25, 1, 0, 0, 0, 0, 0, 0, 0, 132, 139, 33, 142, 66, 142, 66, 142, 66, 208, 59, 4, 43, 1, 0, 0, 0, 0, 0, 0, 0, 132, 203, 41, 142, 66, 142, 66, 142, 66, 208, 59, 96, 0, 1, 0, 0, 0, 0, 0, 0, 0, 132, 9, 17, 142, 66, 142, 66, 142, 66, 208, 59, 230, 27, 1, 0, 0, 0, 0, 0, 0, 0, 132, 200, 8, 9, 17, 139, 33, 74, 25, 243, 133, 14, 200, 8, 132, 200, 8, 200, 8, 200, 8, 200, 8,
|
||||
}
|
||||
out := Decompress(input, 64, 64, 3)
|
||||
fmt.Println(out)
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// readBufSize is the size of the buffered reader used for socket reads.
|
||||
// RDP packets can be large (bitmap updates, channel data); a 64 KiB buffer
|
||||
// keeps the number of read(2) syscalls low without wasting memory.
|
||||
const readBufSize = 65536
|
||||
|
||||
// tcpRecvBufSize is the OS-level TCP receive socket buffer size.
|
||||
// The default on most systems (~87 KiB on Linux, ~128 KiB on macOS) is too
|
||||
// small for high-resolution RDP sessions where the server can burst several
|
||||
// MiB of bitmap/H.264 data per frame. 512 KiB allows the kernel to buffer
|
||||
// more in-flight data, reducing stalls when the application goroutine is
|
||||
// briefly busy decoding a previous frame.
|
||||
const tcpRecvBufSize = 512 * 1024
|
||||
|
||||
type SocketLayer struct {
|
||||
conn net.Conn
|
||||
tlsConn *tls.Conn
|
||||
reader *bufio.Reader // buffers reads regardless of TLS state
|
||||
serverName string
|
||||
// certVerifier 非空时在 TLS 握手完成后以服务器叶子证书的 SHA-256 指纹调用,
|
||||
// 返回错误则中断连接(TOFU 指纹校验用)
|
||||
certVerifier func(sha256Fp []byte) error
|
||||
}
|
||||
|
||||
func NewSocketLayer(conn net.Conn, serverName string) *SocketLayer {
|
||||
// Disable Nagle's algorithm so small DVC responses are sent immediately.
|
||||
if tc, ok := conn.(*net.TCPConn); ok {
|
||||
tc.SetNoDelay(true)
|
||||
// Increase the OS receive buffer so the kernel can absorb large bitmap
|
||||
// or H.264 bursts without dropping bytes while the decoder is busy.
|
||||
// SetReadBuffer is a best-effort hint; ignore errors (e.g. restricted
|
||||
// by the OS cap in /proc/sys/net/core/rmem_max on Linux).
|
||||
_ = tc.SetReadBuffer(tcpRecvBufSize)
|
||||
}
|
||||
l := &SocketLayer{
|
||||
conn: conn,
|
||||
tlsConn: nil,
|
||||
serverName: serverName,
|
||||
}
|
||||
l.reader = bufio.NewReaderSize(conn, readBufSize)
|
||||
return l
|
||||
}
|
||||
|
||||
func (s *SocketLayer) SetDeadline(t time.Time) error {
|
||||
return s.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (s *SocketLayer) Read(b []byte) (n int, err error) {
|
||||
return s.reader.Read(b)
|
||||
}
|
||||
|
||||
func (s *SocketLayer) Write(b []byte) (n int, err error) {
|
||||
if s.tlsConn != nil {
|
||||
return s.tlsConn.Write(b)
|
||||
}
|
||||
return s.conn.Write(b)
|
||||
}
|
||||
|
||||
func (s *SocketLayer) Close() error {
|
||||
if s.tlsConn != nil {
|
||||
s.tlsConn.Close() // best-effort; always close the underlying TCP socket
|
||||
}
|
||||
return s.conn.Close()
|
||||
}
|
||||
|
||||
// SetCertVerifier 注册服务器证书指纹校验回调(须在 StartTLS 前调用)
|
||||
func (s *SocketLayer) SetCertVerifier(fn func(sha256Fp []byte) error) {
|
||||
s.certVerifier = fn
|
||||
}
|
||||
|
||||
func (s *SocketLayer) StartTLS() error {
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: s.serverName,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
// MaxVersion: tls.VersionTLS13,
|
||||
}
|
||||
tlsConn := tls.Client(s.conn, config)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
// RDP 服务器普遍使用自签证书,链校验关闭;改为 TOFU 指纹校验:
|
||||
// 应用层比对叶子证书 SHA-256,不匹配(如中间人)则拒绝继续
|
||||
if s.certVerifier != nil {
|
||||
certs := tlsConn.ConnectionState().PeerCertificates
|
||||
if len(certs) > 0 {
|
||||
summary := sha256.Sum256(certs[0].Raw)
|
||||
if err := s.certVerifier(summary[:]); err != nil {
|
||||
s.tlsConn = tlsConn
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
s.tlsConn = tlsConn
|
||||
// Reset the buffered reader to read from the TLS connection.
|
||||
// Reset discards any unconsumed buffered bytes from the plain-text phase,
|
||||
// which is correct because the TLS handshake has already consumed them.
|
||||
s.reader.Reset(tlsConn)
|
||||
return nil
|
||||
}
|
||||
|
||||
type PublicKey struct {
|
||||
N *big.Int `asn1:"explicit,tag:0"` // modulus
|
||||
E int `asn1:"explicit,tag:1"` // public exponent
|
||||
}
|
||||
|
||||
func (s *SocketLayer) TlsPubKey() ([]byte, error) {
|
||||
if s.tlsConn == nil {
|
||||
return nil, errors.New("TLS conn does not exist")
|
||||
}
|
||||
pub := s.tlsConn.ConnectionState().PeerCertificates[0].PublicKey.(*rsa.PublicKey)
|
||||
return asn1.Marshal(*pub)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package core
|
||||
|
||||
import "git.zeroonesoft.cn/golib/rdplib/emission"
|
||||
|
||||
type Transport interface {
|
||||
Read(b []byte) (n int, err error)
|
||||
Write(b []byte) (n int, err error)
|
||||
Close() error
|
||||
|
||||
On(event, listener any) *emission.Emitter
|
||||
Once(event, listener any) *emission.Emitter
|
||||
Off(event, listener any) *emission.Emitter
|
||||
Emit(event any, arguments ...any) *emission.Emitter
|
||||
}
|
||||
|
||||
type FastPathListener interface {
|
||||
RecvFastPath(secFlag byte, s []byte)
|
||||
}
|
||||
|
||||
type FastPathSender interface {
|
||||
SendFastPath(secFlag byte, s []byte) (int, error)
|
||||
}
|
||||
|
||||
type ChannelSender interface {
|
||||
SendToChannel(channel string, s []byte) (int, error)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
func Reverse(s []byte) []byte {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func Random(n int) []byte {
|
||||
const alpha = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alpha[b%byte(len(alpha))]
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func UTF16ToLittleEndianBytes(u []uint16) []byte {
|
||||
b := make([]byte, 2*len(u))
|
||||
for index, value := range u {
|
||||
binary.LittleEndian.PutUint16(b[index*2:], value)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func LittleEndianBytesToUTF16(u []byte) []uint16 {
|
||||
b := make([]uint16, len(u)/2)
|
||||
for i := range b {
|
||||
b[i] = binary.LittleEndian.Uint16(u[i*2:])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// s.encode('utf-16le')
|
||||
func UnicodeEncode(p string) []byte {
|
||||
return UTF16ToLittleEndianBytes(utf16.Encode([]rune(p)))
|
||||
}
|
||||
|
||||
func UnicodeDecode(p []byte) string {
|
||||
return string(utf16.Decode(LittleEndianBytesToUTF16(p)))
|
||||
}
|
||||
|
||||
func BytesToUint64(b []byte) uint64 {
|
||||
return binary.LittleEndian.Uint64(b)
|
||||
}
|
||||
Reference in New Issue
Block a user