mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 16:12:38 +00:00
Keep the graceful final-ACK TCP close and optional FEM control guards from pixitha's browser compatibility fixes. Replace Brotli negotiation and duplicate page assets with one gzip representation compressed by Google Zopfli 0.4.3 using 1,000 iterations. Compress the bootstrap, captive success, compact OTA, and static WiFi setup pages with the same validated build cache. Honor gzip quality values and wildcard precedence. Validate compressed OTA page evidence in the firmware capability gate, and add native and Python regressions plus CI coverage. Remove the unused Brotli build dependency. Validation: 54 native tests, 56 Python/browser checks, full V4.3 build and verified application flash. Actual V4 WiFi responses matched the generated gzip assets; 30 repeat downloads completed without reboot. Chromium loaded the setup and settings pages without script or transfer errors. Physical Android testing is pending because the Pi detects no phone over USB or mDNS.
71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
"""Pinned Google Zopfli helpers for static and host-generated DEFLATE data.
|
|
|
|
Zopfli writes standards-compliant DEFLATE streams, so the existing device
|
|
decoders need no additional firmware code or RAM. It is deliberately used
|
|
only where compression happens on a development machine or host controller:
|
|
firmware assets, test vectors, and BLE mOTA transport blocks.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
ZOPFLI_VERSION = "0.4.3"
|
|
|
|
try:
|
|
import zopfli
|
|
from zopfli import gzip as _gzip
|
|
from zopfli import zlib as _zlib
|
|
except ImportError as error:
|
|
raise RuntimeError(
|
|
"Google Zopfli 0.4.3 is required to generate MeshCore compressed "
|
|
"assets. Install it with 'python -m pip install -r "
|
|
"requirements-build.txt'."
|
|
) from error
|
|
|
|
if zopfli.__version__ != ZOPFLI_VERSION:
|
|
raise RuntimeError(
|
|
"MeshCore requires Google Zopfli %s, but %s is installed. Run "
|
|
"'python -m pip install -r requirements-build.txt'."
|
|
% (ZOPFLI_VERSION, zopfli.__version__)
|
|
)
|
|
|
|
# Above zlib's maximum level 9: Zopfli refines the LZ77 model repeatedly.
|
|
# Fifteen iterations remains the practical default for raw OTA streams.
|
|
# Firmware HTML gzip assets use the 1,000-iteration setting below.
|
|
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(numiterations: int = ITERATIONS) -> dict[str, int | bool]:
|
|
if numiterations < 1:
|
|
raise ValueError("Zopfli requires at least one iteration")
|
|
return {
|
|
"numiterations": numiterations,
|
|
"blocksplitting": True,
|
|
"blocksplittinglast": False,
|
|
"blocksplittingmax": 15,
|
|
}
|
|
|
|
|
|
def gzip_compress(data: bytes, *, numiterations: int = MAXIMUM_ITERATIONS) -> bytes:
|
|
"""Compress *data* as deterministic RFC 1952 gzip using Google Zopfli."""
|
|
encoded = bytearray(_gzip.compress(data, **_options(numiterations)))
|
|
# The gzip OS byte is informational. Normalize it to RFC 1952's unknown
|
|
# value so a checked-in asset is identical across operating systems.
|
|
if len(encoded) < 10 or encoded[:3] != b"\x1f\x8b\x08":
|
|
raise RuntimeError("Zopfli returned an invalid gzip stream")
|
|
encoded[9] = 0xFF
|
|
return bytes(encoded)
|
|
|
|
|
|
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:
|
|
raise RuntimeError("Zopfli returned an invalid zlib stream")
|
|
return encoded[2:-4]
|