Files
MeshCore/scripts/esp32_ble_lifecycle_fix.py
T

107 lines
4.8 KiB
Python

"""Restore pinned Arduino 3.3.11 BLE service ownership for reversible shutdown.
BLEDevice owns the new wrapper's server, but its implicit destructor leaves
raw service pointers behind. BLEService's destructor is private; delete it
from BLEDevice's existing friend context. NUS characteristics/descriptors stay
application-owned, as on the older wrappers. Never modify the shared SDK.
"""
from pathlib import Path
import hashlib
import re
FRAMEWORKS_UNCHANGED = {(2, 0, 17), (3, 1, 3)}
FRAMEWORK_PATCHED = (3, 3, 11)
PINNED_SHA256 = "ec3735051f3263fc0101986fe33602a2593c294c6ccbd9f7b1b416c15529c3c9"
PATCHED_SHA256 = "bf8fecfd6367eb0a2c23dd610336fefa17687d1f7bbc38e9d8f8642835763e87"
SOURCE_DIRECTORY = ("libraries", "BLE", "src")
ORIGINAL = ''' if (m_pServer != nullptr) {
delete m_pServer;
m_pServer = nullptr;
}
'''
REPAIRED = ''' if (m_pServer != nullptr) {
// MeshCore: the server map owns raw service pointers. BLEDevice is a
// friend of BLEServer/BLEService, whose destructor is private here.
// The application has already stopped host callback dispatch.
BLEService* service = m_pServer->m_serviceMap.getFirst();
while (service != nullptr) {
delete service;
service = m_pServer->m_serviceMap.getNext();
}
delete m_pServer;
m_pServer = nullptr;
}
'''
SERVICE_ORIGINAL = 'BLEService::~BLEService() {\n#if defined(CONFIG_NIMBLE_ENABLED)\n if (m_pSvcDef != nullptr) {\n if (m_pSvcDef->characteristics != nullptr) {\n for (int i = 0; m_pSvcDef->characteristics[i].uuid != NULL; ++i) {\n if (m_pSvcDef->characteristics[i].descriptors) {\n delete (m_pSvcDef->characteristics[i].descriptors);\n }\n }\n delete (m_pSvcDef->characteristics);\n }\n\n delete (m_pSvcDef);\n }\n#endif\n}'
# Each definition is allocated with new[] by BLEService::start().
SERVICE_REPAIRED = SERVICE_ORIGINAL.replace("delete (", "delete[] (")
SOURCE_PINS = {
"BLEDevice.cpp": (PINNED_SHA256, PATCHED_SHA256, ORIGINAL, REPAIRED),
"BLEService.cpp": (
"2b71699e1962cdc37a54ace429ebdd408b0e40b379d621d427add5bb5a93b252",
"8c4d8917ffb768a9eab468b1be06be209b4b8fc065b125c821682b6579a30267", SERVICE_ORIGINAL, SERVICE_REPAIRED),
}
def patched_ble_source(source, framework_version, name="BLEDevice.cpp"):
if framework_version in FRAMEWORKS_UNCHANGED:
return source
if framework_version != FRAMEWORK_PATCHED:
raise RuntimeError("ESP32 BLE lifecycle fix: unreviewed framework version")
source = source.replace("\r\n", "\n")
try:
original_digest, patched_digest, original, repaired = SOURCE_PINS[name]
except KeyError as error:
raise RuntimeError("ESP32 BLE lifecycle fix: unreviewed source") from error
digest = hashlib.sha256(source.encode("utf-8")).hexdigest()
if digest == patched_digest:
return source
if digest != original_digest or source.count(original) != 1:
raise RuntimeError("ESP32 BLE lifecycle fix: changed pinned source; review SDK update")
patched = source.replace(original, repaired, 1)
if hashlib.sha256(patched.encode("utf-8")).hexdigest() != patched_digest:
raise RuntimeError("ESP32 BLE lifecycle fix: unexpected transform result")
return patched
def framework_version(source):
header = source.parents[3] / "cores" / "esp32" / "esp_arduino_version.h"
try:
text = header.read_text(encoding="utf-8")
except OSError as error:
raise RuntimeError("ESP32 BLE lifecycle fix: missing framework version header") from error
entries = re.findall(r"^#define ESP_ARDUINO_VERSION_(MAJOR|MINOR|PATCH)\s+(\d+)\s*$",
text, re.MULTILINE)
values = dict(entries)
if len(entries) != 3 or set(values) != {"MAJOR", "MINOR", "PATCH"}:
raise RuntimeError("ESP32 BLE lifecycle fix: malformed framework version")
return tuple(int(values[key]) for key in ("MAJOR", "MINOR", "PATCH"))
def replace_ble_source(build_env, node):
source = Path(node.srcnode().get_abspath())
if source.parts[-4:-1] != SOURCE_DIRECTORY or source.name not in SOURCE_PINS:
return node
version = framework_version(source)
original = source.read_text(encoding="utf-8")
patched = patched_ble_source(original, version, source.name)
if patched == original:
return node
destination = Path(build_env.subst("$BUILD_DIR")) / "patched-esp32-ble" / source.name
destination.parent.mkdir(parents=True, exist_ok=True)
if not destination.exists() or destination.read_text(encoding="utf-8") != patched:
destination.write_text(patched, encoding="utf-8")
build_env.AppendUnique(CPPPATH=[str(source.parent)])
return build_env.File(str(destination))
def install(build_env):
build_env.AddBuildMiddleware(replace_ble_source, "*libraries*BLE*src*BLE*.cpp")
if "Import" in globals():
Import("env")
install(env)