internal/screen/codec.go
Rama main · réplica de lectura del repositorio original.
// SPDX-FileCopyrightText: 2026 Rashik Adhikari
// SPDX-License-Identifier: Apache-2.0
package screen
import (
"bytes"
"compress/flate"
"encoding/binary"
"fmt"
"io"
)
// Encoding a frame, and the three decisions that make it small.
//
// First: which tiles differ from the last frame. Typing changes one, and
// everything downstream then works on one tile rather than five hundred.
//
// Second: within a changed tile, how many distinct colours there are. One is
// a header; two is a bit a pixel and is what text looks like; sixteen is four
// bits; more than that is a photograph and goes out as pixels.
//
// Third: deflate over the whole payload, from the standard library. The tile
// encodings above remove the structure deflate is bad at — long runs of
// four-byte colours — and deflate then removes the repetition they leave.
// Either alone is much worse than both.
// MaxFrame bounds an encoded frame a decoder will accept.
//
// Thirty-two megabytes. A 4K frame is 33 million bytes raw and this codec is
// only ever larger than raw by a header, so anything past this is a frame
// claiming to be a size it cannot be — and a decoder that allocated whatever
// it was told to would be a way to exhaust memory from the network.
const MaxFrame = 32 << 20
// Encoder holds the previous frame, which is what makes a delta possible.
//
// Not safe for concurrent use: a screen has one encoder and the previous
// frame is state.
type Encoder struct {
previous Frame
// force makes the next frame a key frame.
force bool
}
// NewEncoder starts one. The first frame it sees is a key frame.
func NewEncoder() *Encoder { return &Encoder{force: true} }
// Keyframe asks for the next frame to carry every tile.
//
// Needed whenever a receiver joins, and after any loss: this codec has no
// error concealment and a decoder that missed a tile is wrong until it is
// sent again. Saying so is better than pretending a lost packet is
// survivable.
func (e *Encoder) Keyframe() { e.force = true }
// Encode produces a frame, against whatever the encoder last saw.
func (e *Encoder) Encode(f Frame) ([]byte, Stats, error) {
var s Stats
if err := f.Valid(); err != nil {
return nil, s, err
}
key := e.force || e.previous.Width != f.Width ||
e.previous.Height != f.Height
across, down := f.tiles()
s.Tiles, s.Key = across*down, key
s.Source = len(f.Pix)
// The changed-tile bitmap, one bit a tile, and then the tiles.
bitmap := make([]byte, (s.Tiles+7)/8)
var body bytes.Buffer
for ty := range down {
for tx := range across {
pix, w, h := f.tile(tx, ty)
if !key {
was, _, _ := e.previous.tile(tx, ty)
if bytes.Equal(was, pix) {
continue
}
}
index := ty*across + tx
bitmap[index/8] |= 1 << (index % 8)
s.Changed++
writeTile(&body, pix, w, h, &s)
}
}
var out bytes.Buffer
out.WriteString(Magic)
out.WriteByte(Version)
var flags byte
if key {
flags |= 1
}
out.WriteByte(flags)
var dims [8]byte
binary.BigEndian.PutUint32(dims[0:4], uint32(f.Width))
binary.BigEndian.PutUint32(dims[4:8], uint32(f.Height))
out.Write(dims[:])
// Deflate over the bitmap and the tiles together. The tile encodings
// removed the structure deflate handles badly; what is left is
// repetition, which is what it is for.
payload := append(bitmap, body.Bytes()...)
packed, err := deflate(payload)
if err != nil {
return nil, s, err
}
var size [4]byte
binary.BigEndian.PutUint32(size[:], uint32(len(payload)))
out.Write(size[:])
out.Write(packed)
e.previous = Frame{Width: f.Width, Height: f.Height,
Pix: append([]byte(nil), f.Pix...)}
e.force = false
s.Bytes = out.Len()
return out.Bytes(), s, nil
}
// writeTile encodes one tile by whatever fits it.
func writeTile(out *bytes.Buffer, pix []byte, w, h int, s *Stats) {
palette, ok := paletteOf(pix)
switch {
case ok && len(palette) == 1:
s.Solid++
out.WriteByte(encSolid)
out.Write(palette[0][:])
case ok && len(palette) == 2:
s.Mono++
out.WriteByte(encMono)
out.Write(palette[0][:])
out.Write(palette[1][:])
out.Write(pack(pix, palette, 1, w, h))
case ok:
s.Palette++
out.WriteByte(encPalette)
out.WriteByte(byte(len(palette)))
for _, c := range palette {
out.Write(c[:])
}
out.Write(pack(pix, palette, 4, w, h))
default:
s.Raw++
out.WriteByte(encRaw)
out.Write(pix)
}
}
// colour is one pixel.
type colour [4]byte
// paletteOf returns the distinct colours in a tile, in first-seen order, and
// whether there are few enough to be worth a palette.
//
// First-seen order rather than sorted, because the order only has to match
// between the two halves of pack and unpack and sorting would be work for
// nothing.
func paletteOf(pix []byte) ([]colour, bool) {
var out []colour
seen := map[colour]bool{}
for i := 0; i+4 <= len(pix); i += 4 {
c := colour{pix[i], pix[i+1], pix[i+2], pix[i+3]}
if seen[c] {
continue
}
if len(out) == MaxPalette {
return nil, false
}
seen[c] = true
out = append(out, c)
}
return out, true
}
// pack writes palette indices at the given bit width, row by row.
//
// Rows are byte-aligned. That wastes up to seven bits a row and makes the
// decoder's arithmetic obvious, which on a routine that runs five hundred
// times a frame at thirty frames a second is the trade worth taking: the
// wasted bits are under one per cent and an off-by-one here corrupts a
// picture in a way nobody can read back from the symptom.
func pack(pix []byte, palette []colour, bits, w, h int) []byte {
index := map[colour]byte{}
for i, c := range palette {
index[c] = byte(i)
}
perRow := (w*bits + 7) / 8
out := make([]byte, perRow*h)
for y := range h {
row := out[y*perRow : (y+1)*perRow]
for x := range w {
at := (y*w + x) * 4
v := index[colour{pix[at], pix[at+1], pix[at+2], pix[at+3]}]
bit := x * bits
row[bit/8] |= v << (8 - bits - bit%8)
}
}
return out
}
// unpack reverses it.
func unpack(in []byte, palette []colour, bits, w, h int) ([]byte, error) {
perRow := (w*bits + 7) / 8
if len(in) < perRow*h {
return nil, fmt.Errorf(
"a %dx%d tile at %d bit(s) a pixel needs %d bytes and has %d",
w, h, bits, perRow*h, len(in))
}
mask := byte(1<<bits - 1)
out := make([]byte, w*h*4)
for y := range h {
row := in[y*perRow : (y+1)*perRow]
for x := range w {
bit := x * bits
v := (row[bit/8] >> (8 - bits - bit%8)) & mask
if int(v) >= len(palette) {
return nil, fmt.Errorf(
"a pixel names palette entry %d of %d", v, len(palette))
}
copy(out[(y*w+x)*4:], palette[v][:])
}
}
return out, nil
}
// Decoder holds the previous frame, which is what a delta is against.
type Decoder struct{ previous Frame }
// NewDecoder starts one.
func NewDecoder() *Decoder { return &Decoder{} }
// Decode applies a frame to whatever the decoder last held.
func (d *Decoder) Decode(in []byte) (Frame, error) {
if len(in) < 14 {
return Frame{}, fmt.Errorf("this is too short to be a frame")
}
if string(in[:4]) != Magic {
return Frame{}, fmt.Errorf("this is not a screen frame")
}
if in[4] != Version {
return Frame{}, fmt.Errorf(
"this frame is version %d and this build speaks %d", in[4],
Version)
}
key := in[5]&1 != 0
width := int(binary.BigEndian.Uint32(in[6:10]))
height := int(binary.BigEndian.Uint32(in[10:14]))
size := int(binary.BigEndian.Uint32(in[14:18]))
out := Frame{Width: width, Height: height}
if err := checkDimensions(width, height); err != nil {
return Frame{}, err
}
if size < 0 || size > MaxFrame {
return Frame{}, fmt.Errorf(
"this frame says its payload is %d bytes, which is past the "+
"%d-byte ceiling. A decoder that allocated whatever it was "+
"told to would be a way to exhaust memory from the network",
size, MaxFrame)
}
payload, err := inflate(in[18:], size)
if err != nil {
return Frame{}, err
}
switch {
case key:
out.Pix = make([]byte, width*height*4)
case d.previous.Width != width || d.previous.Height != height:
return Frame{}, fmt.Errorf(
"this is a delta against a %dx%d frame and the last one was "+
"%dx%d. A receiver that has missed a key frame is wrong "+
"until it is sent another", width, height,
d.previous.Width, d.previous.Height)
default:
out.Pix = append([]byte(nil), d.previous.Pix...)
}
across, down := out.tiles()
count := across * down
bits := (count + 7) / 8
if len(payload) < bits {
return Frame{}, fmt.Errorf("the frame has no tile map")
}
bitmap, body := payload[:bits], payload[bits:]
at := 0
for ty := range down {
for tx := range across {
index := ty*across + tx
if bitmap[index/8]&(1<<(index%8)) == 0 {
continue
}
w, h := TileSize, TileSize
if tx*TileSize+w > width {
w = width - tx*TileSize
}
if ty*TileSize+h > height {
h = height - ty*TileSize
}
pix, used, rerr := readTile(body[at:], w, h)
if rerr != nil {
return Frame{}, fmt.Errorf("tile %d,%d: %w", tx, ty, rerr)
}
at += used
out.putTile(tx, ty, pix, w, h)
}
}
d.previous = Frame{Width: width, Height: height,
Pix: append([]byte(nil), out.Pix...)}
return out, nil
}
// checkDimensions refuses a frame that claims a size nothing can hold.
//
// Before any allocation. The numbers came off the wire, and width times
// height times four is the size of the buffer a decoder is about to make.
func checkDimensions(width, height int) error {
if width <= 0 || height <= 0 {
return fmt.Errorf("this frame claims to be %dx%d", width, height)
}
if width > MaxDimension || height > MaxDimension {
return fmt.Errorf(
"this frame claims to be %dx%d, which is larger than any "+
"display and is the shape of a number chosen to make a "+
"decoder allocate", width, height)
}
return nil
}
// readTile decodes one tile and says how many bytes it took.
func readTile(in []byte, w, h int) ([]byte, int, error) {
if len(in) == 0 {
return nil, 0, fmt.Errorf("the frame ends where a tile should start")
}
switch in[0] {
case encSolid:
if len(in) < 5 {
return nil, 0, fmt.Errorf("a solid tile with no colour")
}
pix := make([]byte, w*h*4)
for i := 0; i < len(pix); i += 4 {
copy(pix[i:], in[1:5])
}
return pix, 5, nil
case encMono:
if len(in) < 9 {
return nil, 0, fmt.Errorf("a two-colour tile with one colour")
}
palette := []colour{
{in[1], in[2], in[3], in[4]},
{in[5], in[6], in[7], in[8]},
}
n := ((w*1+7)/8)*h + 9
if len(in) < n {
return nil, 0, fmt.Errorf("a two-colour tile is short")
}
pix, err := unpack(in[9:n], palette, 1, w, h)
return pix, n, err
case encPalette:
if len(in) < 2 {
return nil, 0, fmt.Errorf("a palette tile with no palette")
}
size := int(in[1])
if size < 1 || size > MaxPalette {
return nil, 0, fmt.Errorf(
"a tile claims a palette of %d, and the ceiling is %d",
size, MaxPalette)
}
head := 2 + size*4
if len(in) < head {
return nil, 0, fmt.Errorf("a palette tile is short")
}
palette := make([]colour, size)
for i := range palette {
copy(palette[i][:], in[2+i*4:6+i*4])
}
n := head + ((w*4+7)/8)*h
if len(in) < n {
return nil, 0, fmt.Errorf("a palette tile is short")
}
pix, err := unpack(in[head:n], palette, 4, w, h)
return pix, n, err
case encRaw:
n := 1 + w*h*4
if len(in) < n {
return nil, 0, fmt.Errorf("a raw tile is short")
}
return append([]byte(nil), in[1:n]...), n, nil
}
return nil, 0, fmt.Errorf("%d is not a tile encoding", in[0])
}
func deflate(in []byte) ([]byte, error) {
var out bytes.Buffer
w, err := flate.NewWriter(&out, flate.BestSpeed)
if err != nil {
return nil, err
}
if _, err := w.Write(in); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return out.Bytes(), nil
}
// inflate expands a payload, refusing one that does not stop where it said.
//
// The declared size is the ceiling as well as the expectation: a compressed
// stream that expands to a hundred times what it claims is the oldest attack
// on a decompressor, and reading into a bounded buffer is the whole defence.
func inflate(in []byte, size int) ([]byte, error) {
r := flate.NewReader(bytes.NewReader(in))
defer r.Close()
out := make([]byte, size)
if _, err := io.ReadFull(r, out); err != nil {
return nil, fmt.Errorf(
"this frame said it holds %d bytes and does not: %w", size, err)
}
// One more byte would mean it holds more than it declared.
var extra [1]byte
if n, _ := r.Read(extra[:]); n > 0 {
return nil, fmt.Errorf(
"this frame expands past the %d bytes it declared", size)
}
return out, nil
}