mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 11:02:21 +00:00
Maximize Zopfli mOTA transport compression
This commit is contained in:
1 parent
e5768b12d0
commit
2d6e546ba4
3 files changed
+30
-7
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user