Maximize Zopfli mOTA transport compression

This commit is contained in:
mikecarper committed 2026-09-19 20:12:37 -07:00
1 parent e5768b12d0
commit 2d6e546ba4
3 files changed
+30 -7

No files matched your search

+11 -5
View File
@@ -32,11 +32,17 @@ if zopfli.__version__ != ZOPFLI_VERSION:
# Fifteen iterations is the upstream binding's practical default for assets
# of this size and keeps CI/build times reasonable.
ITERATIONS = 15
# Zopfli has no finite algorithmic iteration ceiling. Its ``--i1000``
# convention is the maximum-effort setting used for host-cached mOTA transfer
# blocks, where the extra host CPU time is preferable to wasting radio airtime.
MAXIMUM_ITERATIONS = 1000
def _options() -> dict[str, int | bool]:
def _options(numiterations: int = ITERATIONS) -> dict[str, int | bool]:
if numiterations < 1:
raise ValueError("Zopfli requires at least one iteration")
return {
"numiterations": ITERATIONS,
"numiterations": numiterations,
"blocksplitting": True,
"blocksplittinglast": False,
"blocksplittingmax": 15,
@@ -54,9 +60,9 @@ def gzip_compress(data: bytes) -> bytes:
return bytes(encoded)
def raw_deflate_compress(data: bytes) -> bytes:
"""Compress *data* to an RFC 1951 raw DEFLATE stream using Google Zopfli."""
encoded = _zlib.compress(data, **_options())
def raw_deflate_compress(data: bytes, *, numiterations: int = ITERATIONS) -> bytes:
"""Compress *data* to an RFC 1951 raw-DEFLATE stream with Zopfli."""
encoded = _zlib.compress(data, **_options(numiterations))
# A zlib wrapper is exactly a two-byte header and four-byte Adler-32
# trailer. Removing it preserves the RFC 1951 body expected by tinf.
if len(encoded) < 6 or (encoded[0] & 0x0F) != 8:
+5 -2
View File
@@ -21,7 +21,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "scripts"))
from zopfli_compress import raw_deflate_compress # noqa: E402
from zopfli_compress import MAXIMUM_ITERATIONS, raw_deflate_compress # noqa: E402
BLEAK_IMPORT_ERROR: ImportError | None = None
try:
@@ -62,6 +62,9 @@ STATUS_ERR = 1
MOTA_READ_MAX = 192
MOTA_DEFLATE_CHUNK_MAX = 190
MOTA_DEFLATE_BLOCK_MAX = 2048
# This work occurs on the host and each result is cached while its chunks are
# served. Use Zopfli's maximum-effort convention to reduce LoRa airtime.
MOTA_ZOPFLI_ITERATIONS = MAXIMUM_ITERATIONS
MOTA_DESC_WIRE = 38
MOTA_SOURCE_CAP_DEFLATE_BLOCK = 0x01
MOTA_HEADER_LEN = 8
@@ -101,7 +104,7 @@ class MotaFile:
raw = stream.read(raw_length)
if len(raw) != raw_length:
return None
encoded = raw_deflate_compress(raw)
encoded = raw_deflate_compress(raw, numiterations=MOTA_ZOPFLI_ITERATIONS)
return encoded if 0 < len(encoded) < len(raw) else None
@staticmethod
+14
View File
@@ -6,6 +6,7 @@ import tempfile
import unittest
import zlib
from pathlib import Path
from unittest import mock
import ble_mota_seeder as seeder
@@ -78,6 +79,19 @@ class TransportDeflateTests(unittest.TestCase):
encoded_1k = compressor_1k.compress(self.raw) + compressor_1k.flush()
self.assertLess(len(encoded) + 400, len(encoded_1k))
def test_uses_maximum_zopfli_iterations_for_mota_transport(self):
compressor = zlib.compressobj(
level=9, method=zlib.DEFLATED, wbits=-11
)
expected = compressor.compress(self.raw) + compressor.flush()
with mock.patch.object(
seeder, "raw_deflate_compress", return_value=expected
) as compress:
self.assertEqual(self.catalog.files[0].deflated_block(0), expected)
compress.assert_called_once_with(
self.raw, numiterations=seeder.MOTA_ZOPFLI_ITERATIONS
)
def test_rejects_bad_block_range_and_oversized_chunk(self):
self.assertEqual(self.exchange(1, 0, 0)[0], seeder.STATUS_ERR)
self.assertEqual(