mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-27 06:49:07 +01:00
Clamp Tight length to 22 bits and fall back to Raw on overflow
This commit is contained in:
+37
-10
@@ -495,10 +495,17 @@ func zlibLevelFor(level int) int {
|
|||||||
return level
|
return level
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tightMaxLength is the maximum payload size representable in the Tight
|
||||||
|
// compact length prefix (RFB §7.7.6: 22 bits, three 7+7+8 bit groups).
|
||||||
|
// Exceeding this would silently truncate the high byte; callers must fall
|
||||||
|
// back to a different encoding when an attempt would overflow.
|
||||||
|
const tightMaxLength = (1 << 22) - 1
|
||||||
|
|
||||||
// encodeTightRect emits a single Tight-encoded rect. Picks Fill for uniform
|
// encodeTightRect emits a single Tight-encoded rect. Picks Fill for uniform
|
||||||
// content, JPEG for photo-like rects above a size and color-count threshold,
|
// content, JPEG for photo-like rects above a size and color-count threshold,
|
||||||
// and Basic+zlib otherwise. Returns the rect header + Tight body (no
|
// and Basic+zlib otherwise. When Tight's 22-bit length cap would be exceeded
|
||||||
// FramebufferUpdate header).
|
// (huge full-frame rects under bad compression), falls back to Raw. Returns
|
||||||
|
// the rect header + body (no FramebufferUpdate header).
|
||||||
func encodeTightRect(img *image.RGBA, pf clientPixelFormat, x, y, w, h int, t *tightState) []byte {
|
func encodeTightRect(img *image.RGBA, pf clientPixelFormat, x, y, w, h int, t *tightState) []byte {
|
||||||
if pixel, uniform := tileIsUniform(img, x, y, w, h); uniform {
|
if pixel, uniform := tileIsUniform(img, x, y, w, h); uniform {
|
||||||
return encodeTightFill(x, y, w, h, byte(pixel), byte(pixel>>8), byte(pixel>>16))
|
return encodeTightFill(x, y, w, h, byte(pixel), byte(pixel>>8), byte(pixel>>16))
|
||||||
@@ -508,7 +515,12 @@ func encodeTightRect(img *image.RGBA, pf clientPixelFormat, x, y, w, h int, t *t
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return encodeTightBasic(img, x, y, w, h, t)
|
if buf, ok := encodeTightBasic(img, x, y, w, h, t); ok {
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
// Fall back to Raw rect body (skip the 4-byte FU header that encodeRawRect
|
||||||
|
// prepends, since callers compose their own FU header).
|
||||||
|
return encodeRawRect(img, pf, x, y, w, h)[4:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeTightRectHeader(buf []byte, x, y, w, h int) {
|
func writeTightRectHeader(buf []byte, x, y, w, h int) {
|
||||||
@@ -520,8 +532,13 @@ func writeTightRectHeader(buf []byte, x, y, w, h int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// appendTightLength encodes a Tight compact length prefix (1, 2, or 3 bytes
|
// appendTightLength encodes a Tight compact length prefix (1, 2, or 3 bytes
|
||||||
// LE-ish, top bit of each byte signals continuation).
|
// LE-ish, top bit of each byte signals continuation). Lengths exceeding
|
||||||
|
// tightMaxLength would silently truncate the high byte; callers must clamp
|
||||||
|
// or fall back before reaching here.
|
||||||
func appendTightLength(buf []byte, n int) []byte {
|
func appendTightLength(buf []byte, n int) []byte {
|
||||||
|
if n < 0 || n > tightMaxLength {
|
||||||
|
panic(fmt.Sprintf("tight length out of range: %d", n))
|
||||||
|
}
|
||||||
b0 := byte(n & 0x7f)
|
b0 := byte(n & 0x7f)
|
||||||
if n <= 0x7f {
|
if n <= 0x7f {
|
||||||
return append(buf, b0)
|
return append(buf, b0)
|
||||||
@@ -532,6 +549,8 @@ func appendTightLength(buf []byte, n int) []byte {
|
|||||||
return append(buf, b0, b1)
|
return append(buf, b0, b1)
|
||||||
}
|
}
|
||||||
b1 |= 0x80
|
b1 |= 0x80
|
||||||
|
// High group is 8 bits per spec, but our cap guarantees the top 2 bits
|
||||||
|
// are zero; mask defensively.
|
||||||
b2 := byte((n >> 14) & 0xff)
|
b2 := byte((n >> 14) & 0xff)
|
||||||
return append(buf, b0, b1, b2)
|
return append(buf, b0, b1, b2)
|
||||||
}
|
}
|
||||||
@@ -562,6 +581,9 @@ func encodeTightJPEG(img *image.RGBA, x, y, w, h int, t *tightState) ([]byte, bo
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
jpegBytes := t.jpegBuf.Bytes()
|
jpegBytes := t.jpegBuf.Bytes()
|
||||||
|
if len(jpegBytes) > tightMaxLength {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
buf := make([]byte, 0, 12+1+3+len(jpegBytes))
|
buf := make([]byte, 0, 12+1+3+len(jpegBytes))
|
||||||
buf = buf[:12]
|
buf = buf[:12]
|
||||||
writeTightRectHeader(buf, x, y, w, h)
|
writeTightRectHeader(buf, x, y, w, h)
|
||||||
@@ -574,8 +596,10 @@ func encodeTightJPEG(img *image.RGBA, x, y, w, h int, t *tightState) ([]byte, bo
|
|||||||
// encodeTightBasic emits Basic+zlib with the no-op (CopyFilter) filter.
|
// encodeTightBasic emits Basic+zlib with the no-op (CopyFilter) filter.
|
||||||
// Pixels are sent as 24-bit RGB ("TPIXEL" format) which most clients
|
// Pixels are sent as 24-bit RGB ("TPIXEL" format) which most clients
|
||||||
// negotiate when the server advertises 32bpp true colour. Streams under
|
// negotiate when the server advertises 32bpp true colour. Streams under
|
||||||
// 12 bytes ship uncompressed per RFB Tight spec.
|
// 12 bytes ship uncompressed per RFB Tight spec. Returns ok=false when the
|
||||||
func encodeTightBasic(img *image.RGBA, x, y, w, h int, t *tightState) []byte {
|
// compressed payload would exceed Tight's 22-bit length cap or when zlib
|
||||||
|
// errors, signalling the caller to fall back to Raw.
|
||||||
|
func encodeTightBasic(img *image.RGBA, x, y, w, h int, t *tightState) ([]byte, bool) {
|
||||||
pixelStream := w * h * 3
|
pixelStream := w * h * 3
|
||||||
if cap(t.scratch) < pixelStream {
|
if cap(t.scratch) < pixelStream {
|
||||||
t.scratch = make([]byte, pixelStream)
|
t.scratch = make([]byte, pixelStream)
|
||||||
@@ -605,20 +629,23 @@ func encodeTightBasic(img *image.RGBA, x, y, w, h int, t *tightState) []byte {
|
|||||||
writeTightRectHeader(buf, x, y, w, h)
|
writeTightRectHeader(buf, x, y, w, h)
|
||||||
buf = append(buf, subenc, filter)
|
buf = append(buf, subenc, filter)
|
||||||
buf = append(buf, scratch...)
|
buf = append(buf, scratch...)
|
||||||
return buf
|
return buf, true
|
||||||
}
|
}
|
||||||
|
|
||||||
z := t.zlib
|
z := t.zlib
|
||||||
z.buf.Reset()
|
z.buf.Reset()
|
||||||
if _, err := z.w.Write(scratch); err != nil {
|
if _, err := z.w.Write(scratch); err != nil {
|
||||||
log.Debugf("tight zlib write: %v", err)
|
log.Debugf("tight zlib write: %v", err)
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
if err := z.w.Flush(); err != nil {
|
if err := z.w.Flush(); err != nil {
|
||||||
log.Debugf("tight zlib flush: %v", err)
|
log.Debugf("tight zlib flush: %v", err)
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
compressed := z.buf.Bytes()
|
compressed := z.buf.Bytes()
|
||||||
|
if len(compressed) > tightMaxLength {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
buf := make([]byte, 0, 12+2+5+len(compressed))
|
buf := make([]byte, 0, 12+2+5+len(compressed))
|
||||||
buf = buf[:12]
|
buf = buf[:12]
|
||||||
@@ -626,7 +653,7 @@ func encodeTightBasic(img *image.RGBA, x, y, w, h int, t *tightState) []byte {
|
|||||||
buf = append(buf, subenc, filter)
|
buf = append(buf, subenc, filter)
|
||||||
buf = appendTightLength(buf, len(compressed))
|
buf = appendTightLength(buf, len(compressed))
|
||||||
buf = append(buf, compressed...)
|
buf = append(buf, compressed...)
|
||||||
return buf
|
return buf, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func tightQualityFor(pixels int) int {
|
func tightQualityFor(pixels int) int {
|
||||||
|
|||||||
Reference in New Issue
Block a user