mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-11-04 10:57:27 +00:00
Fix constrained firmware builds and CI checks
This commit is contained in:
1 parent
dbaf951359
commit
cf5dfeb87b
30 files changed
+839
-44
No files matched your search
@@ -52,12 +52,12 @@ jobs:
|
||||
--firmware-version v1.18.0.1-halo-keymind-cascade-dev
|
||||
--radio-preset usa-cascade-fixed --profile cascade --require-ota
|
||||
|
||||
- name: Verify linked Companion crypto, retained Reader and ARM stack use
|
||||
- name: Verify linked Companion crypto, Reader exclusion and ARM stack use
|
||||
run: |
|
||||
python3 -m pip install unicorn==2.1.4
|
||||
python3 -B test/test_cc310_shared_scratch.py -v
|
||||
python3 -B scripts/check_nrf52_companion_crypto.py .pio/build/RAK_4631_companion_radio_full/firmware.elf
|
||||
python3 -B scripts/check_nrf52_companion_reader.py .pio/build/RAK_4631_companion_radio_full/firmware.elf
|
||||
python3 -B scripts/check_nrf52_companion_reader.py --expect-disabled .pio/build/RAK_4631_companion_radio_full/firmware.elf
|
||||
python3 -B scripts/check_nrf52_companion_ota_workspace.py .pio/build/RAK_4631_companion_radio_full/firmware.elf
|
||||
|
||||
- name: Qualify XIAO Full Companion with Meshtext and direct OTA
|
||||
@@ -293,6 +293,14 @@ jobs:
|
||||
run: pio run -e muzi_base_repeater -e muzi_base_room_server -e muzi_base_companion_radio_usb -e muzi_base_companion_radio_ble
|
||||
- name: Qualify Muzi Full Companion
|
||||
run: bash build.sh build-firmware muzi_base_companion_radio_full --firmware-version v1.18.0.2-halo-keymind-cascade-dev --radio-preset usa-cascade-fixed --profile cascade
|
||||
- name: Upload universal build failure logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: universal-lora-build-logs
|
||||
path: out/build-logs/*.log
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
headless-kiss-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -1168,6 +1176,8 @@ jobs:
|
||||
- name: Verify WiFi, ESP-NOW and combined wireless controls
|
||||
run: |
|
||||
python3 -B test/test_wireless_control.py -v
|
||||
python3 -B test/test_stm32_wireless_specialization.py -v
|
||||
python3 -B test/test_companion_no_wireless.py -v
|
||||
python3 -B test/test_serial_wifi_sessions.py -v
|
||||
python3 -B test/test_companion_wifi_listener.py -v
|
||||
python3 -B test/test_wifi_compatibility.py -v
|
||||
@@ -1404,6 +1414,7 @@ jobs:
|
||||
python3 -B test/test_webconfig_ui.py
|
||||
python3 -B test/test_webconfig_regulators.py -v
|
||||
python3 -B test/test_webconfig_gzip.py
|
||||
python3 -B test/test_compact_elegant_ota.py -v
|
||||
python3 -B test/test_webconfig_secret_masking.py
|
||||
python3 -B test/test_webconfig_station_ownership.py -v
|
||||
python3 -B test/test_mqtt_canonical_wifi.py -v
|
||||
|
||||
@@ -21,7 +21,14 @@
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "FS.h"
|
||||
|
||||
#include "elegantWebpage.h"
|
||||
#if defined(ESP32) && defined(MESH_COMPACT_ELEGANT_OTA) && MESH_COMPACT_ELEGANT_OTA
|
||||
// Keep the full AsyncElegantOTA upload protocol; only its page is smaller.
|
||||
#include <helpers/esp32/StaticHtml.h>
|
||||
#define ELEGANT_HTML MESH_HTML_ELEGANT_OTA_COMPACT
|
||||
#define ELEGANT_HTML_SIZE MESH_HTML_ELEGANT_OTA_COMPACT_LEN
|
||||
#else
|
||||
#include "elegantWebpage.h"
|
||||
#endif
|
||||
|
||||
|
||||
class AsyncElegantOtaClass{
|
||||
|
||||
@@ -477,8 +477,10 @@ these are crypto function frames only, excluding callers, interrupts and other
|
||||
tasks. The loop stack stays at 8 KiB. A full firmware RAM check still applies.
|
||||
|
||||
CI runs `scripts/check_nrf52_companion_crypto.py` on the actual Full RAK ELF.
|
||||
It checks the 960-byte table and retained Reader corpus, executes two RFC 8032
|
||||
vectors, and rejects crypto function stack use above 2 KiB. Host tests compare
|
||||
It checks the 960-byte table, executes two RFC 8032 vectors, and rejects crypto
|
||||
function stack use above 2 KiB. Reader is now disabled by default in Companion
|
||||
builds; an explicit `COMPANION_FEATURE_READER=1` retains the implementation and
|
||||
corpus. Host tests compare
|
||||
1,791 scalars and 128 keypair/signature/shared-secret cases against the original
|
||||
implementation, plus scalar boundary/random and sanitizer tests. Clean Full
|
||||
builds also cover T1000-E and T096. These builds are synthetic coverage for
|
||||
|
||||
@@ -256,7 +256,9 @@ for the state machine and limitations.
|
||||
|
||||
## Commands
|
||||
|
||||
ESP32 and nRF52 Full Companions include an offline World English Bible lookup:
|
||||
Custom ESP32 and nRF52 Full Companion USB builds can opt into an offline World
|
||||
English Bible lookup with `COMPANION_FEATURE_READER=1`. Standard builds leave
|
||||
it disabled to preserve firmware space:
|
||||
|
||||
```text
|
||||
get reader 3:16
|
||||
|
||||
@@ -3672,6 +3672,16 @@ static bool companionNativeUsbLightSleepReady() {
|
||||
|
||||
bool handleCompanionWirelessCommand(const char* command, char* reply, size_t size,
|
||||
CompanionWirelessSource source) {
|
||||
#if defined(STM32_PLATFORM) && !defined(BLE_PIN_CODE) && !defined(WIFI_SSID) \
|
||||
&& !defined(WITH_BRIDGE) && !defined(WITH_MQTT_BRIDGE) \
|
||||
&& !defined(WITH_UDP_BRIDGE) && !defined(WITH_ESPNOW_BRIDGE) \
|
||||
&& !defined(WITH_WEBCONFIG) && !defined(ETHERNET_ENABLED) \
|
||||
&& !MESH_ENABLE_EMERGENCY_POWER \
|
||||
&& (!defined(MESH_PRIMARY_ESPNOW) || !MESH_PRIMARY_ESPNOW)
|
||||
// This build has no wireless transport whose state can change.
|
||||
(void)source;
|
||||
return mesh::wireless::handleNoServices(command, reply, size);
|
||||
#else
|
||||
#if defined(BLE_PIN_CODE)
|
||||
const auto wireless_command = mesh::wireless::parse(command);
|
||||
if (reply && size && companion_bluetooth_off_at != 0
|
||||
@@ -3700,6 +3710,7 @@ bool handleCompanionWirelessCommand(const char* command, char* reply, size_t siz
|
||||
}
|
||||
}
|
||||
return mesh::wireless::control().handle(command, reply, size, millis(), requester);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if COMPANION_RTC_TERMINAL_INPUT
|
||||
|
||||
@@ -1739,12 +1739,6 @@ public:
|
||||
movePage(-1);
|
||||
return true;
|
||||
}
|
||||
#if COMPANION_FEATURE_READER
|
||||
if (key == KEY_ENTER && isRadioPage()) {
|
||||
_task->showReader();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (key == KEY_NEXT || key == KEY_RIGHT) {
|
||||
movePage(1);
|
||||
if (_page == HomePage::RECENT) {
|
||||
@@ -3157,7 +3151,8 @@ char UITask::handleLongPress(char c) {
|
||||
// The radio page's reader gesture, including exit, takes precedence over
|
||||
// the global early-boot rescue gesture just like the WiFi setup page.
|
||||
if (_display != NULL && isReaderActive()) { closeReader(); return 0; }
|
||||
if (_display != NULL && curr == home && static_cast<HomeScreen*>(home)->isRadioPage()) {
|
||||
if (c == KEY_ENTER && _display != NULL && curr == home
|
||||
&& static_cast<HomeScreen*>(home)->isRadioPage()) {
|
||||
showReader();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -43,8 +43,6 @@ def check(path):
|
||||
if elf.symbols.get("base", (0, 0))[1] != 960:
|
||||
raise ValueError("Companion must retain the 960-byte Ed25519 base table")
|
||||
reader = "_ZN4mesh5bible9generatedL10readerDataE"
|
||||
if reader not in elf.symbols:
|
||||
raise ValueError("Companion Reader corpus is missing")
|
||||
path_cache = elf.symbols.get("_ZZN4mesh18contactPathStorageEvE4pool")
|
||||
if path_cache and elf.sections[path_cache[2]][1] != 8: # SHT_NOBITS
|
||||
raise ValueError("Contact path cache has a flash load image; expected zero-initialized storage")
|
||||
@@ -102,7 +100,7 @@ def check(path):
|
||||
return {
|
||||
"passed": True, "elf_sha256": hashlib.sha256(elf.data).hexdigest(),
|
||||
"base_table_bytes": elf.symbols["base"][1],
|
||||
"reader_data_bytes": elf.symbols[reader][1], "vectors": rows,
|
||||
"reader_data_bytes": elf.symbols.get(reader, (0, 0))[1], "vectors": rows,
|
||||
"zero_initialized_path_cache_bytes": path_cache[1] if path_cache else 0,
|
||||
"stack_limit_bytes": 2048,
|
||||
"scope": "Crypto function frames only; excludes caller/ISR/task stacks and physical timing.",
|
||||
|
||||
@@ -23,6 +23,19 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
TYPOGRAPHY = str.maketrans({'\u00a0':' ', '\u2013':'-', '\u2014':'-', '\u2018':"'", '\u2019':"'", '\u201c':'"', '\u201d':'"'})
|
||||
|
||||
|
||||
def check_disabled(path):
|
||||
elf = FirmwareElf(path)
|
||||
if elf.unpack('H', 18)[0] != 40:
|
||||
raise ValueError('Expected ARM ELF')
|
||||
retained = [name for name in elf.symbols
|
||||
if 'readerData' in name or 'replyVerse' in name
|
||||
or 'handleReaderCommand' in name]
|
||||
if retained:
|
||||
raise ValueError('Reader code or corpus unexpectedly linked: ' + repr(retained))
|
||||
return {'passed': True, 'reader': 'disabled',
|
||||
'elf_sha256': hashlib.sha256(elf.data).hexdigest()}
|
||||
|
||||
|
||||
def check(path, every_page=False):
|
||||
elf = FirmwareElf(path)
|
||||
if elf.unpack('H', 18)[0] != 40:
|
||||
@@ -132,8 +145,10 @@ if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('elf')
|
||||
parser.add_argument('--every-page', action='store_true')
|
||||
parser.add_argument('--expect-disabled', action='store_true')
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
print(json.dumps(check(args.elf, args.every_page), indent=2))
|
||||
print(json.dumps(check_disabled(args.elf) if args.expect_disabled
|
||||
else check(args.elf, args.every_page), indent=2))
|
||||
except (ValueError, OSError) as error:
|
||||
parser.exit(1, f'FAILED: {error}\n')
|
||||
@@ -118,7 +118,16 @@ def main():
|
||||
"#pragma once", "#include <stdint.h>", "#include <pgmspace.h>"]
|
||||
for page in sorted(Path("webui/pages").glob("*.html")):
|
||||
symbol = "MESH_HTML_" + page.stem.upper()
|
||||
compressed = compress_page(page.read_bytes())
|
||||
page_bytes = page.read_bytes()
|
||||
if page.name == "elegant_ota_compact.html":
|
||||
# Inline the pinned, locally vendored MD5 implementation: the OTA
|
||||
# page must work without internet and the server verifies its hash.
|
||||
marker = b"/* MESH_SPARK_MD5 */"
|
||||
if page_bytes.count(marker) != 1:
|
||||
raise ValueError("compact OTA page must contain one MD5 marker")
|
||||
vendor = Path("webui/vendor/spark-md5-3.0.2.min.js").read_bytes()
|
||||
page_bytes = page_bytes.replace(marker, vendor)
|
||||
compressed = compress_page(page_bytes)
|
||||
static_lines.append("const uint32_t %s_LEN = %d;" % (symbol, len(compressed)))
|
||||
append_byte_array(static_lines, symbol, compressed)
|
||||
static_header = Path("src/helpers/esp32/StaticHtml.h")
|
||||
|
||||
@@ -3750,8 +3750,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (handleBeaconCommand(command, reply, 157)) return;
|
||||
#endif
|
||||
#if defined(STM32_PLATFORM) && defined(STM32WLxx) && defined(USE_STM32WL) \
|
||||
&& !defined(WITH_WEBCONFIG) && !defined(WITH_MQTT_BRIDGE) \
|
||||
&& !defined(WITH_UDP_BRIDGE) && !defined(WITH_ESPNOW_BRIDGE) \
|
||||
&& !defined(WITH_BRIDGE) && !defined(ETHERNET_ENABLED) \
|
||||
&& !defined(BLE_PIN_CODE) && !defined(WIFI_SSID) \
|
||||
&& !MESH_ENABLE_EMERGENCY_POWER \
|
||||
&& (!defined(MESH_PRIMARY_ESPNOW) || !MESH_PRIMARY_ESPNOW)
|
||||
// The integrated STM32WL target has no 2.4 GHz services. Keep the same
|
||||
// unsupported-command replies without retaining the state machine.
|
||||
if (mesh::wireless::handleNoServices(command, reply, 160)) return;
|
||||
#else
|
||||
if (mesh::wireless::control().handle(command, reply, 160, millis(),
|
||||
_callbacks->wirelessCommandSource(sender_timestamp))) return;
|
||||
#endif
|
||||
// Infrastructure callers can already have reflected a three-byte CLI
|
||||
// request prefix into the same 160-byte reply. Reserve it for bulk pages.
|
||||
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef COMPANION_FEATURE_READER
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE) \
|
||||
&& (defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM))
|
||||
#define COMPANION_FEATURE_READER 1
|
||||
#else
|
||||
#define COMPANION_FEATURE_READER 0
|
||||
#endif
|
||||
#define COMPANION_FEATURE_READER 0
|
||||
#endif
|
||||
|
||||
#ifndef COMPANION_READER_COMPACT_STREAM
|
||||
|
||||
@@ -47,6 +47,24 @@ inline Command parse(const char* s) {
|
||||
return {scope, Action::Invalid};
|
||||
}
|
||||
|
||||
// Match Control::handle when the build has no wireless services, without
|
||||
// retaining the state-transition machinery on flash-constrained devices.
|
||||
inline bool handleNoServices(const char* text, char* reply, size_t size) {
|
||||
const auto command = parse(text);
|
||||
if (command.action == Action::None || !reply || !size) return false;
|
||||
if (command.action == Action::Invalid) {
|
||||
snprintf(reply, size, MESH_CLI_REPLY_TEXT("Error: use set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]", "Error: set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]"));
|
||||
} else if (command.action == Action::Get) {
|
||||
if (command.scope == All)
|
||||
snprintf(reply, size, "wifi=unavailable, bluetooth=unavailable, espnow=unavailable");
|
||||
else
|
||||
snprintf(reply, size, "%s unavailable", command.scope == WiFi ? "wifi" : "espnow");
|
||||
} else {
|
||||
snprintf(reply, size, MESH_CLI_REPLY_TEXT("Error: requested wireless service unavailable in this build/boot", "Error: wireless service unavailable this build/boot"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class Backend {
|
||||
public:
|
||||
virtual uint8_t available() const = 0; // Services usable in this boot.
|
||||
|
||||
@@ -3,6 +3,273 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <pgmspace.h>
|
||||
const uint32_t MESH_HTML_ELEGANT_OTA_COMPACT_LEN = 4211;
|
||||
const uint8_t MESH_HTML_ELEGANT_OTA_COMPACT[] PROGMEM = {
|
||||
0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x94,0x52,0x55,0xa2,0xac,0x36,
|
||||
0x18,0x7e,0x9f,0x55,0xa4,0xa9,0x0d,0xb7,0xc8,0xe8,0x91,0x19,0xa0,0xee,0xee,0x2e,
|
||||
0x21,0xf9,0x19,0xd2,0x03,0x84,0x9b,0x84,0x91,0x4b,0x8f,0x6c,0xa6,0xb6,0x8e,0xee,
|
||||
0xa4,0x2b,0xe9,0x8f,0x5d,0x7b,0xeb,0xc3,0x97,0xfc,0xee,0xe1,0x33,0x42,0x71,0x7b,
|
||||
0xaa,0x80,0x64,0xb6,0xc8,0xe3,0x49,0xd8,0x7d,0x61,0x06,0x4c,0xc4,0x61,0x01,0x96,
|
||||
0x11,0x9e,0x31,0x6d,0xc0,0x46,0xb4,0xb6,0xa9,0x77,0x41,0x07,0x69,0xc9,0x0a,0x88,
|
||||
0xe8,0x5e,0xc2,0xa1,0x52,0xda,0x52,0xc2,0x55,0x69,0xa1,0x44,0xab,0x83,0x14,0x36,
|
||||
0x8b,0x04,0xec,0x25,0x07,0xaf,0x63,0x5c,0x59,0x4a,0x2b,0x59,0xee,0x19,0xce,0x72,
|
||||
0x88,0xe6,0x14,0xb3,0x58,0x69,0x73,0x88,0x3f,0x04,0x93,0xbd,0xae,0x34,0x90,0x8f,
|
||||
0xbf,0x78,0x35,0x0c,0x7a,0x59,0x68,0xec,0x09,0xbf,0x49,0xa2,0xc4,0xa9,0x49,0x31,
|
||||
0xea,0x66,0x7e,0x56,0x1d,0x89,0x61,0xa5,0xf1,0x0c,0x68,0x99,0x6e,0x0b,0x76,0xec,
|
||||
0x03,0x6f,0x96,0x17,0x1a,0x0a,0xe4,0xf5,0x4e,0x96,0x9b,0x05,0xd2,0x84,0xd5,0x56,
|
||||
0x6d,0x2b,0x26,0x84,0x2c,0x77,0x9b,0x19,0x99,0xb7,0xfa,0x5c,0x96,0xe0,0x65,0x20,
|
||||
0x77,0x19,0xc6,0xf2,0x57,0xeb,0xeb,0x49,0x2a,0x21,0x17,0xd8,0x53,0x33,0xb8,0xb6,
|
||||
0x66,0x64,0x76,0x9d,0xd4,0xd6,0xaa,0x12,0xcb,0xad,0x6a,0xdb,0xa7,0x96,0x65,0x86,
|
||||
0x29,0xed,0xa0,0x69,0xc6,0xc0,0xfe,0x1a,0x1d,0xba,0xe0,0xd7,0x95,0x56,0x3b,0x0d,
|
||||
0xc6,0x34,0x7d,0x45,0xf3,0xd9,0xec,0xf9,0xeb,0xc9,0xb3,0xc6,0x32,0x5b,0xa3,0x2c,
|
||||
0x93,0x16,0x3c,0x53,0x31,0x0e,0x9b,0x4a,0xe3,0x38,0x34,0xab,0xb6,0x6a,0x0f,0x3a,
|
||||
0xcd,0xd5,0xa1,0xe3,0x36,0xac,0x3c,0x1d,0x30,0x07,0x5c,0x4f,0xc2,0xa0,0xef,0x3c,
|
||||
0x0c,0xfa,0xe1,0xb7,0x03,0x68,0xf7,0x31,0x7f,0x6a,0x4c,0x28,0x08,0x2b,0x22,0x45,
|
||||
0x44,0xa5,0xc0,0x89,0x4b,0x7b,0xa2,0xf1,0x67,0xe8,0x81,0x85,0x91,0x7e,0xec,0x64,
|
||||
0x54,0xfc,0x7b,0xf7,0x47,0x18,0x54,0x18,0x64,0x6c,0x38,0x0e,0x73,0xd8,0x41,0x29,
|
||||
0xe2,0x2f,0x2b,0xc1,0x2c,0x90,0x76,0xf1,0x61,0x30,0xc8,0x26,0x61,0xce,0x12,0xc0,
|
||||
0xe5,0x77,0x03,0xe8,0x74,0x11,0xd5,0x18,0x58,0xd1,0x61,0xdd,0xad,0x88,0x92,0x3d,
|
||||
0xcb,0x6b,0x64,0x52,0xa9,0x8b,0x03,0xd3,0x28,0xe0,0x19,0xf0,0x2b,0x10,0x31,0x79,
|
||||
0x6b,0x10,0x61,0xc8,0x36,0xd2,0xff,0x8f,0x98,0x83,0x39,0x19,0x0b,0x05,0x6d,0x63,
|
||||
0x8d,0xcc,0x18,0x2d,0x0c,0xc6,0x36,0xc6,0xc0,0x24,0x55,0xba,0x77,0xa3,0xf1,0xbb,
|
||||
0x05,0xdb,0x01,0x99,0xfa,0x89,0x2c,0x89,0xd2,0xa4,0xfd,0xfd,0xdd,0x03,0xe7,0xa1,
|
||||
0x73,0xa2,0xc7,0x32,0xa4,0x18,0x7c,0xba,0x82,0x46,0x9a,0x71,0x0e,0x15,0xde,0x6f,
|
||||
0xeb,0xe8,0x0e,0xde,0xb4,0x75,0xc3,0x64,0xfd,0xfa,0x07,0xf3,0x9e,0xa1,0x5d,0x98,
|
||||
0xba,0xca,0x15,0x13,0x14,0xc7,0xd9,0xfe,0x61,0xd0,0xeb,0xd0,0x63,0x3c,0x8b,0xce,
|
||||
0x6a,0x64,0x28,0xc1,0xd3,0x8d,0x28,0xde,0xc8,0xc3,0x96,0x91,0xca,0xa4,0xc0,0x75,
|
||||
0x61,0x77,0xa3,0xd9,0xb8,0xde,0xfe,0x88,0x28,0xd1,0x2a,0x87,0x87,0x5c,0xdc,0x2f,
|
||||
0xd4,0x70,0x2d,0x2b,0x1b,0x07,0xf7,0x08,0x5e,0x97,0xbe,0xf2,0x0a,0xb1,0x26,0x4b,
|
||||
0x7f,0xe6,0x2f,0x5c,0xf2,0x6a,0x29,0xf4,0x3f,0x7f,0x93,0xd7,0x75,0xfd,0xc0,0x25,
|
||||
0xd3,0x0f,0xdf,0xfd,0x82,0x7c,0xfc,0x19,0xf9,0xfa,0x8b,0xb7,0x3e,0xf9,0xc0,0xd9,
|
||||
0x12,0x03,0x40,0x0e,0x90,0xd4,0x32,0xd8,0xe3,0xca,0x95,0xf6,0xc9,0xbd,0x60,0x32,
|
||||
0x4d,0xeb,0x92,0x5b,0xa9,0xca,0x69,0xca,0xb8,0x55,0xfa,0xe4,0x34,0x32,0x9d,0xb6,
|
||||
0xdd,0xaa,0x94,0xc0,0xb1,0x52,0xda,0x9a,0x28,0x8a,0xa8,0x4a,0x7e,0x05,0x6e,0xa9,
|
||||
0xd3,0x14,0x4a,0xd4,0x39,0xf8,0xa3,0x6a,0xf0,0x9a,0x3a,0xd7,0x90,0x1b,0x20,0x8f,
|
||||
0x7c,0x05,0xa4,0xb2,0x84,0xd6,0x75,0xcc,0x40,0x5f,0x78,0xa1,0x17,0xfa,0xac,0x10,
|
||||
0x4e,0xd3,0xd3,0x63,0xda,0xde,0xbf,0xd9,0x33,0x4d,0x76,0xb9,0x4a,0xb6,0x56,0x9f,
|
||||
0x9a,0x96,0x88,0x0e,0x12,0x6b,0x3d,0x5c,0x73,0x66,0x79,0x36,0x05,0xa7,0x17,0x1a,
|
||||
0xc8,0xd3,0xeb,0x96,0xf2,0x3f,0x6f,0x47,0xf0,0xe1,0x1b,0xeb,0xc7,0x0a,0xb9,0x76,
|
||||
0x1e,0x35,0x55,0x97,0x7d,0x1a,0xcc,0x47,0x6b,0xac,0xcf,0x58,0x2d,0xb1,0x8d,0x6d,
|
||||
0x9b,0x87,0x09,0xb1,0x5c,0x44,0x0f,0x4d,0x99,0x9b,0x38,0x8d,0x06,0x5b,0xeb,0x92,
|
||||
0xb0,0x97,0x92,0x17,0x56,0x8b,0xcb,0xd5,0xe5,0xd9,0xf9,0xe2,0x72,0x7d,0xed,0x66,
|
||||
0x70,0xfc,0x99,0x67,0x3a,0xfa,0x1e,0x97,0xe6,0xd2,0x39,0x62,0x81,0x58,0x22,0x56,
|
||||
0x88,0x35,0xe2,0x0c,0x71,0x8e,0xb8,0x40,0x5c,0x22,0x18,0x22,0x41,0x70,0x84,0x40,
|
||||
0x00,0x22,0xa5,0x3f,0x6e,0xc7,0x74,0x84,0x17,0xe5,0xf4,0xbe,0x8b,0x49,0xdd,0xa3,
|
||||
0x6b,0x5c,0xeb,0x34,0x2c,0xea,0x0a,0x9a,0x0e,0xaf,0x7b,0xdf,0x71,0x7b,0xf2,0x88,
|
||||
0x5a,0x67,0x3b,0x54,0x36,0x68,0xc3,0xd0,0xfc,0xc6,0xe2,0x38,0x5e,0x2e,0x3c,0x83,
|
||||
0x75,0x5f,0x3f,0x0c,0x8b,0xc7,0xc0,0x4f,0x3c,0x07,0xf4,0xba,0x72,0xba,0x71,0xb2,
|
||||
0xe8,0xf8,0xfd,0xec,0x47,0x37,0xc1,0x6f,0xfe,0xa3,0xcb,0xf1,0x5b,0xfc,0xc7,0xe1,
|
||||
0x54,0x18,0x47,0x10,0xc3,0xc0,0x6e,0xf4,0x7f,0x6c,0x86,0x83,0x4e,0x42,0x86,0xc1,
|
||||
0xb4,0xa0,0x71,0xed,0x59,0x65,0xc0,0xb0,0xfc,0xb9,0x76,0x5c,0xf6,0xf3,0x2a,0xcb,
|
||||
0xf3,0xae,0xd4,0x78,0x54,0xea,0xd3,0xf2,0x0b,0xdd,0x16,0x92,0x4a,0x31,0x64,0x1b,
|
||||
0x58,0x5d,0xe5,0x91,0xa6,0xf8,0xdf,0x64,0xfc,0xb4,0x54,0x60,0x7d,0x01,0x48,0x95,
|
||||
0x47,0xa1,0x06,0x0b,0x32,0x37,0x9b,0xb2,0x0f,0xce,0xa7,0x20,0xf4,0xf3,0xee,0xf7,
|
||||
0xad,0x0d,0x77,0xf1,0xa8,0x69,0x29,0x00,0xdb,0x02,0x94,0x0a,0x8f,0x4e,0x15,0x1e,
|
||||
0x0c,0x58,0x82,0x0a,0x5a,0xf9,0xa4,0xb3,0xd0,0xcf,0xbb,0xc1,0x13,0xb9,0xc1,0xa3,
|
||||
0xd1,0xd3,0x01,0xd6,0x05,0x28,0x75,0x1e,0x8d,0x8a,0x78,0xec,0xe7,0xa6,0x95,0x73,
|
||||
0xde,0x78,0x6b,0x95,0xf0,0x58,0x7e,0xdf,0xc6,0x70,0x15,0x13,0x8a,0x9a,0x0c,0xfe,
|
||||
0x63,0xa4,0x4e,0x4c,0x9b,0x59,0x63,0x28,0x0c,0x77,0x13,0x02,0xc3,0x07,0x3a,0xda,
|
||||
0x55,0x8c,0x06,0xbc,0x94,0x61,0x52,0xfb,0x35,0x9e,0xc4,0x36,0x97,0xfc,0x41,0x25,
|
||||
0x3c,0xe2,0xbc,0xfa,0x9f,0x47,0x7b,0x21,0x5c,0x91,0x59,0x31,0xf4,0x58,0x6f,0x60,
|
||||
0x22,0x4a,0x52,0x1e,0x83,0xbc,0x17,0x34,0x84,0x05,0xa2,0x98,0x8a,0xa2,0x97,0x5a,
|
||||
0x90,0x55,0xca,0xd4,0x93,0xbd,0x21,0x82,0x48,0x4c,0xe1,0x43,0x50,0xf5,0x42,0x59,
|
||||
0x2a,0x54,0x11,0x53,0x10,0xa8,0x97,0x32,0xb9,0x4c,0x31,0x40,0x2f,0x54,0x91,0x92,
|
||||
0xc2,0x79,0xea,0x01,0xdf,0x41,0x49,0xea,0x24,0x9e,0x3c,0x04,0x41,0x7a,0x29,0x89,
|
||||
0x02,0x84,0xb1,0x47,0x7b,0xc1,0x88,0x89,0x98,0x8b,0xc6,0xa8,0xc7,0x18,0xc4,0x4d,
|
||||
0x4c,0xb8,0xfe,0x46,0x5d,0x6f,0x97,0x8f,0xaf,0x03,0xd5,0x0b,0x6e,0x51,0x6e,0xa0,
|
||||
0x27,0xc9,0x0e,0x52,0xbc,0x93,0x2e,0xb7,0xf3,0xc7,0xd7,0xe5,0x67,0x3f,0xe4,0x65,
|
||||
0x04,0x17,0x7e,0x9a,0xea,0x20,0xc9,0x3b,0xe9,0x7c,0x3b,0x7d,0x7c,0x1d,0x24,0xf4,
|
||||
0xe6,0x2a,0x81,0x08,0xc8,0x4b,0xa4,0x87,0x28,0xdf,0x45,0xa7,0xdb,0xf5,0xe3,0xeb,
|
||||
0x21,0xa2,0x5e,0x12,0x92,0x15,0x42,0xfc,0xf2,0xd0,0xe1,0xe1,0xdf,0x3d,0xd6,0x2b,
|
||||
0x08,0x66,0xe9,0x85,0xa1,0x07,0xd4,0x9b,0x24,0xc1,0x29,0x65,0xca,0xb1,0x5e,0xee,
|
||||
0xa4,0x91,0x22,0x36,0xe5,0x68,0x2f,0x25,0x33,0x89,0xd4,0x9c,0x72,0xaa,0x37,0xf3,
|
||||
0x54,0x75,0x95,0x9c,0x72,0xb4,0x17,0x08,0x95,0x24,0x5e,0x34,0x04,0x49,0x2f,0x64,
|
||||
0x88,0x4b,0x39,0x66,0x9e,0xa3,0x6f,0xb8,0x98,0xc0,0x08,0x53,0x10,0xa4,0x17,0x54,
|
||||
0xd5,0x13,0xea,0x31,0x24,0x71,0x2f,0x83,0x92,0x44,0xea,0xd0,0x13,0xbd,0x21,0xc4,
|
||||
0x84,0x53,0x43,0xa6,0x20,0x70,0x2f,0x14,0xbb,0x53,0x84,0xe8,0x9f,0xa2,0xfd,0xb2,
|
||||
0xff,0xcc,0x4d,0x22,0xcd,0xf2,0x29,0xd1,0x43,0x92,0x6f,0x92,0xfd,0xbc,0x3f,0x18,
|
||||
0xd9,0x8b,0x89,0xd9,0x42,0xd5,0xe5,0x09,0x01,0x0e,0x09,0xde,0x24,0xfb,0x69,0x7f,
|
||||
0x66,0x83,0x94,0x22,0x21,0x73,0x7e,0x39,0xfc,0x70,0xf8,0x9b,0x63,0xbf,0xee,0x07,
|
||||
0x42,0x7b,0x89,0x09,0x95,0x99,0xbf,0x08,0xf2,0x20,0xd4,0x2f,0x02,0xf4,0x82,0x09,
|
||||
0x55,0x31,0x39,0x4d,0x14,0xda,0x1b,0x38,0x38,0x4b,0xc4,0x66,0x8a,0xe8,0x05,0x33,
|
||||
0xad,0x70,0x19,0x1a,0xa8,0x17,0xa8,0x34,0x84,0x5c,0x69,0xa4,0x90,0xde,0x3c,0xc1,
|
||||
0x51,0x08,0x9d,0x28,0xa8,0x97,0x58,0x9a,0x04,0x33,0x8f,0x10,0xd2,0x2b,0x98,0x8d,
|
||||
0x51,0x51,0x23,0x84,0xf7,0x16,0x4e,0x5c,0xc8,0x9a,0x08,0xaa,0x97,0x2b,0x89,0xab,
|
||||
0x66,0x4c,0x00,0xe0,0x5e,0xca,0x48,0x58,0x8a,0x8d,0x04,0xb0,0xde,0x4c,0x28,0x94,
|
||||
0x8d,0x69,0x44,0xe0,0x5e,0x55,0x26,0x92,0x6e,0xf8,0xc3,0x70,0xd9,0x3f,0xcf,0xb7,
|
||||
0x7b,0xde,0xdf,0x77,0x45,0xa5,0x0b,0xa5,0xea,0x93,0xe1,0x07,0xc3,0x5f,0x8c,0xf3,
|
||||
0xfe,0x79,0xba,0xdd,0xe3,0xfe,0x69,0x15,0xec,0x59,0x50,0xbc,0x49,0xe8,0x90,0xf0,
|
||||
0x4b,0x72,0xda,0x3f,0xaf,0xb7,0x7b,0xdf,0xcf,0x2f,0xa7,0x70,0x31,0x2d,0xb2,0x97,
|
||||
0xc6,0x0e,0x4d,0xbc,0x34,0xd7,0xfd,0xf3,0x72,0xbb,0x07,0xfe,0x9d,0xab,0x85,0x8a,
|
||||
0x92,0xd9,0xcb,0x83,0x87,0x07,0xf8,0x1d,0x04,0xee,0x0d,0x41,0xa4,0x69,0x16,0x18,
|
||||
0x92,0xa4,0x17,0xb2,0xb4,0xd2,0x9d,0x7c,0x4a,0x3a,0x56,0x6f,0x30,0x96,0xa9,0x07,
|
||||
0xbd,0x98,0x4c,0x8b,0x39,0xaa,0xa6,0xa2,0xbc,0x83,0x32,0x44,0x20,0x62,0x35,0x04,
|
||||
0xc1,0x7a,0x09,0x39,0x10,0xaa,0x53,0x8f,0xf7,0x82,0x39,0xa1,0x52,0x92,0xc6,0x22,
|
||||
0xe9,0x0d,0x42,0x05,0x83,0xeb,0x98,0xf4,0x18,0x83,0x19,0x0b,0x05,0x4d,0x45,0xe8,
|
||||
0x05,0x30,0x31,0x48,0xa2,0xa6,0x26,0xee,0x2d,0x90,0x91,0xc1,0x56,0x53,0x52,0xf5,
|
||||
0x12,0x15,0x4d,0x33,0xc3,0xbf,0x40,0xff,0xb1,0x63,0x1d,0xbc,0x8d,0x83,0x50,0xf8,
|
||||
0xaf,0x5c,0x37,0x14,0xea,0x7a,0x65,0x9d,0x4d,0xa7,0xc6,0x4d,0x8d,0x1b,0x5a,0x91,
|
||||
0x75,0xca,0x6c,0xc8,0x39,0xb8,0x8a,0x5d,0x35,0x55,0xea,0xff,0x7e,0xef,0x61,0xec,
|
||||
0xac,0xdb,0x7b,0x75,0x04,0xf8,0x00,0xbd,0xef,0xc1,0x07,0x0a,0x1f,0xbe,0x7a,0x44,
|
||||
0x87,0x61,0xa1,0x5b,0x4e,0x24,0xba,0x0c,0x0b,0xdd,0x72,0x23,0xd1,0x63,0x58,0xe8,
|
||||
0x96,0x17,0x89,0x3e,0x83,0x02,0x5a,0x2b,0x4f,0xa9,0x6e,0xfc,0x96,0xa4,0xfa,0x19,
|
||||
0x65,0x5a,0xa9,0x68,0x47,0x5c,0x06,0xe0,0x3d,0x10,0x29,0xec,0x40,0x86,0x75,0x3f,
|
||||
0x90,0x4c,0xf8,0x74,0x6e,0xfa,0xdb,0x12,0x32,0x8d,0x44,0x6a,0xa1,0x75,0x76,0x99,
|
||||
0xf4,0x07,0xe7,0x19,0x91,0x94,0x91,0x55,0x80,0x39,0x34,0x0c,0x9b,0x9b,0xb0,0x4b,
|
||||
0xf1,0xf6,0xd8,0xc4,0x3d,0xc0,0x5d,0x9f,0xe6,0xe6,0x11,0x68,0x62,0xad,0x53,0x7d,
|
||||
0xd3,0x99,0x4e,0x3b,0x77,0xa4,0xf3,0x35,0x84,0x3b,0x6d,0x19,0x31,0x02,0x9f,0xcc,
|
||||
0x89,0x34,0x35,0x5d,0x77,0x23,0xc3,0x07,0x1b,0x5e,0xf4,0x49,0x12,0x4e,0xb9,0x5a,
|
||||
0x4a,0xa4,0x56,0x3c,0x50,0x57,0xe0,0x00,0xa2,0x79,0x31,0x10,0x6d,0xa7,0xe1,0xb9,
|
||||
0xf8,0x0a,0x69,0x79,0xfc,0xc8,0x6d,0x40,0xcb,0x6b,0x36,0x5a,0xfc,0xa8,0x82,0x7d,
|
||||
0x5e,0xa2,0x4d,0x20,0xcc,0xcd,0xe4,0xac,0x23,0x63,0x9e,0x4d,0xae,0x79,0x9c,0xf0,
|
||||
0x51,0x99,0x06,0xa6,0x10,0x0a,0x85,0x79,0xd4,0x75,0x22,0xe6,0xc9,0xab,0x23,0xf1,
|
||||
0x72,0xdb,0xac,0xf4,0xa6,0x8b,0xcf,0x7d,0x75,0x45,0xe4,0x51,0xdd,0xe7,0x92,0x52,
|
||||
0x9a,0xa7,0x62,0x1d,0xa7,0x41,0x11,0xaa,0x22,0x1c,0x60,0x4c,0xd1,0xb6,0xf9,0x47,
|
||||
0x7f,0xa3,0xa5,0x25,0x35,0xf3,0x80,0x8e,0x43,0xe7,0x38,0xbb,0x58,0xd3,0xfb,0x75,
|
||||
0x15,0x84,0x21,0x91,0x7b,0x7e,0x18,0x7a,0x34,0x5f,0x1e,0xe5,0xb8,0xcd,0x45,0x4f,
|
||||
0x20,0x87,0x44,0x9e,0xd4,0x6a,0x1b,0x69,0xe1,0x0c,0xba,0x14,0xd4,0xa9,0xaf,0x04,
|
||||
0x8c,0x84,0x9d,0xe7,0xb0,0x50,0x42,0x1d,0x36,0x03,0x2c,0xe1,0xdf,0xca,0x92,0x97,
|
||||
0x45,0xa2,0xb0,0x87,0xd6,0x44,0x3b,0x28,0xc7,0xc4,0x3a,0x3c,0xa5,0xc4,0x9a,0xdb,
|
||||
0xbc,0x99,0xd3,0xdd,0x63,0x48,0x3f,0x11,0xd7,0xe8,0xf2,0x3e,0x56,0x19,0x81,0x49,
|
||||
0xe8,0x0e,0xc0,0xf0,0x60,0x24,0x57,0x61,0x47,0xc3,0xf7,0x70,0x5c,0x74,0x40,0xc7,
|
||||
0x8f,0x44,0x9c,0x98,0x7a,0x2d,0x12,0xb0,0x39,0xef,0x25,0x6c,0xc4,0xa2,0xa1,0x55,
|
||||
0xa9,0xac,0xaa,0x55,0x89,0xce,0xaf,0x15,0x4c,0x49,0x07,0xe5,0xa1,0xab,0x4b,0xaa,
|
||||
0xe9,0x08,0xac,0x87,0xea,0x74,0xad,0x97,0x3e,0x54,0x83,0xdb,0x07,0xaf,0xa5,0xca,
|
||||
0x9a,0xe7,0x1a,0xb4,0x2b,0x35,0x75,0xbe,0xbf,0x9a,0xf0,0x88,0xfe,0x97,0x50,0x29,
|
||||
0xa1,0x29,0x78,0x72,0x44,0x15,0xe2,0x49,0xc5,0xf6,0x36,0x1f,0xeb,0xcc,0xc6,0x90,
|
||||
0xd9,0x38,0xf4,0x83,0xb1,0x4e,0x2c,0x65,0xc2,0x58,0x77,0x6d,0x75,0x72,0x32,0x3e,
|
||||
0x6c,0x32,0x7f,0x1f,0x42,0xb1,0x55,0x10,0xa1,0xbc,0x8c,0xb2,0x88,0x80,0x01,0x66,
|
||||
0x45,0x80,0xe5,0x0b,0x74,0x66,0xad,0xec,0xd0,0x0c,0x17,0x4e,0x93,0xc1,0x5a,0x75,
|
||||
0x3b,0xce,0xac,0x71,0x22,0x15,0xd9,0xde,0xa6,0x39,0x6c,0x07,0x76,0xeb,0xeb,0x71,
|
||||
0x7b,0x34,0x88,0xe3,0x64,0x9b,0xd2,0x2d,0x21,0xb6,0x6b,0x7d,0xdf,0xf1,0x6d,0xb7,
|
||||
0xd3,0xed,0xf9,0x5d,0xb7,0xd3,0xa8,0x77,0x5b,0x0d,0xa7,0xd5,0x6f,0xa1,0xaf,0xd1,
|
||||
0xe8,0xd5,0x5a,0x2e,0xb8,0xad,0x6b,0x1e,0xe5,0x8c,0xdf,0x15,0x84,0xe2,0xf4,0x56,
|
||||
0x90,0xd9,0x7e,0xbd,0x06,0xd6,0x04,0x65,0xe4,0xce,0xd4,0xf8,0x44,0xe3,0xf8,0xdd,
|
||||
0x13,0x51,0x53,0xc2,0x60,0xac,0x55,0x2b,0x09,0x83,0xf4,0x97,0x54,0xc0,0x8b,0x79,
|
||||
0x79,0xbe,0xf0,0x6d,0xb5,0x90,0x2f,0x6e,0x86,0xc3,0xc1,0x14,0x39,0x56,0x06,0x2a,
|
||||
0xb8,0xb7,0x5b,0x4b,0x7d,0x16,0x38,0xd6,0x59,0x82,0x73,0xac,0x34,0x96,0x3d,0x30,
|
||||
0x66,0x49,0xc5,0x92,0xce,0x17,0x2e,0x67,0xdc,0x99,0x5c,0x13,0x30,0xbc,0xcd,0xf9,
|
||||
0x44,0xf6,0xb1,0x80,0xff,0x7b,0x1b,0x75,0x20,0x87,0xd8,0x17,0xda,0x95,0xf1,0xfa,
|
||||
0xbc,0x93,0x8d,0x40,0x66,0x33,0x84,0x99,0x39,0xd2,0xf6,0xbb,0xe2,0x8d,0x6b,0xc1,
|
||||
0x51,0x1b,0xf8,0x7e,0x5f,0xe1,0x54,0xac,0x98,0x65,0x2f,0x3d,0x31,0xe1,0x7a,0xef,
|
||||
0xbd,0x37,0x16,0xe4,0x98,0x1c,0x06,0x07,0xc4,0x76,0x7f,0x57,0xde,0xf3,0x65,0x99,
|
||||
0xd1,0x48,0x48,0x68,0x61,0x5b,0xda,0x55,0x5b,0x9a,0x5e,0x85,0x98,0x65,0xab,0xce,
|
||||
0x6e,0x56,0xd8,0x04,0x57,0x87,0x08,0x65,0x0c,0x37,0xab,0xca,0x85,0x2f,0x4a,0x65,
|
||||
0x3c,0x4a,0x51,0x31,0xcf,0xea,0x60,0x63,0x47,0xf0,0x3b,0xc4,0x67,0x83,0xc3,0xfb,
|
||||
0xa0,0x88,0x24,0x46,0x78,0xcd,0xc1,0xe7,0x45,0x1a,0x29,0x59,0x8a,0x06,0xc8,0xc0,
|
||||
0x8b,0x23,0x2e,0xd4,0x7f,0x92,0xbb,0x5f,0x97,0x4d,0x95,0x70,0xf9,0x19,0x75,0x12,
|
||||
0x25,0x98,0xce,0xba,0x7a,0x46,0x02,0x44,0x57,0x94,0xad,0xdc,0x00,0x27,0x39,0x9e,
|
||||
0x83,0xdd,0x56,0x7f,0xac,0x27,0x96,0x42,0x63,0xd0,0x1d,0x58,0x46,0x00,0xb2,0x26,
|
||||
0x81,0x43,0xe2,0x1a,0xb9,0x70,0x00,0xc4,0x42,0x4b,0xa2,0xc8,0x29,0x4c,0xb4,0xc5,
|
||||
0x42,0x12,0xb4,0x1f,0x04,0xcc,0x41,0x36,0xf0,0x5d,0x6a,0x41,0xcd,0xc5,0xd8,0x42,
|
||||
0x6d,0x23,0x89,0x60,0xf0,0xc6,0x9d,0x99,0xbc,0x11,0xe5,0x53,0x31,0xfb,0x09,0x52,
|
||||
0xb8,0x92,0xef,0x12,0xd6,0x5f,0xbf,0x69,0xf0,0x3a,0x75,0xed,0x4d,0x73,0x0d,0x7e,
|
||||
0xbd,0x5d,0x0f,0x04,0xaf,0x85,0xdc,0x86,0x04,0x15,0x15,0x18,0x8a,0xd7,0x49,0xbc,
|
||||
0xe4,0x63,0x5e,0x24,0xd0,0x2a,0x9f,0x3e,0xba,0x79,0xb9,0x5c,0x2c,0xcb,0x82,0x17,
|
||||
0x0a,0x4a,0xbb,0x1f,0xbe,0x18,0x2e,0x0d,0xf0,0x80,0xd2,0xf4,0x8d,0x6d,0x05,0x00,
|
||||
0xf0,0x09,0xf6,0xe9,0x4d,0xad,0x57,0xc7,0xe5,0x00,0xa0,0xbb,0xcc,0x06,0xa0,0x1c,
|
||||
0x30,0xa3,0xf6,0x38,0x94,0x78,0xd7,0x3c,0x08,0xcf,0xfc,0x6c,0xa8,0x50,0x03,0x06,
|
||||
0xa4,0x3f,0x32,0xe8,0xf6,0x7c,0x2d,0xbb,0x2b,0xd6,0x79,0x40,0x98,0x22,0x4d,0xa3,
|
||||
0x51,0x6c,0x04,0xf9,0xe6,0x29,0x69,0x47,0x5c,0x75,0x70,0x50,0x25,0x0e,0x30,0x36,
|
||||
0x2f,0x2b,0x1e,0x41,0xbc,0x5c,0xe6,0x3b,0xe0,0xfe,0x3c,0xf7,0xfb,0x04,0xb6,0xdc,
|
||||
0x92,0x94,0x45,0x12,0x0b,0x5e,0xc0,0x5f,0x4b,0xe5,0x7a,0x3c,0xcb,0xaa,0x5a,0xf8,
|
||||
0x35,0x87,0xfd,0x74,0xc0,0x7a,0x15,0xaf,0x9b,0x5c,0x38,0x16,0x21,0x44,0x2b,0x81,
|
||||
0x26,0x44,0xc3,0x5a,0x61,0x21,0x61,0xca,0xf8,0xe9,0xc3,0x66,0x87,0x41,0x10,0x39,
|
||||
0xe6,0xbb,0x7c,0x58,0x38,0x64,0x53,0xa2,0x35,0x55,0x24,0x37,0xa4,0x82,0x9d,0x7a,
|
||||
0xfe,0xa4,0xbc,0x94,0x15,0x71,0xb5,0xa3,0x7a,0x1a,0xac,0x90,0x82,0x48,0x5c,0x9e,
|
||||
0x94,0x55,0x9c,0xc0,0x86,0x8e,0x93,0x6d,0xe9,0xf1,0x6d,0xf0,0xf8,0xb6,0xf2,0xf8,
|
||||
0xda,0xd7,0xe1,0xf6,0x24,0xfa,0x86,0xed,0x49,0xac,0x60,0xd9,0xd4,0xf3,0x71,0xdb,
|
||||
0xe7,0x10,0x93,0x0e,0x98,0x50,0xac,0xbf,0x61,0xd8,0xf0,0xd8,0xea,0x48,0xff,0xd1,
|
||||
0x8e,0x2f,0x09,0x5a,0x2e,0xd3,0x2f,0xb7,0xa0,0xa2,0xca,0x62,0x55,0x71,0x34,0x13,
|
||||
0x5b,0xe9,0x75,0x53,0xef,0x90,0x0e,0x56,0x19,0x8d,0x4a,0xd9,0x27,0x91,0x68,0x87,
|
||||
0xf4,0x1e,0x5b,0xb9,0x69,0x92,0x18,0xa0,0x56,0xe1,0x20,0x4d,0xc2,0x34,0x94,0xd5,
|
||||
0xdb,0xfe,0x5a,0x91,0x7f,0x8f,0x66,0x9e,0x44,0x7a,0x35,0xa4,0x45,0xb2,0x94,0x59,
|
||||
0xd6,0x3d,0xd3,0xca,0x45,0x83,0xad,0x4d,0xdd,0x3d,0xae,0x29,0x0c,0xf7,0xcc,0x46,
|
||||
0x68,0xf3,0xb8,0x9e,0xeb,0x93,0xbe,0x21,0xd4,0x7b,0xe4,0x37,0xdb,0xd1,0x20,0xe4,
|
||||
0xd1,0xb6,0xe8,0x18,0xb7,0x8a,0xb7,0x28,0x6e,0x1c,0xaa,0x3a,0x7c,0x3a,0x4a,0xf9,
|
||||
0xc7,0x3b,0x01,0x62,0xea,0x1d,0xe3,0x14,0x48,0xb4,0x87,0x1e,0x2b,0x48,0x14,0xe8,
|
||||
0x3a,0x19,0xc4,0x24,0x62,0xf9,0xb9,0x49,0x19,0x8c,0x6a,0xcb,0x7c,0x78,0x4e,0x24,
|
||||
0x4d,0x68,0xcf,0x4d,0x0e,0x58,0x05,0x82,0x26,0xec,0xec,0xb4,0xeb,0x33,0x11,0x81,
|
||||
0x19,0x23,0xd9,0xf1,0x01,0xe7,0xb3,0x4e,0x60,0x80,0x8a,0x46,0x90,0x93,0x1d,0xf5,
|
||||
0x8f,0xf4,0x14,0x34,0xa4,0xc7,0x78,0x54,0xb4,0x25,0x21,0x9c,0x3d,0x14,0x65,0x7a,
|
||||
0xc0,0x5d,0x6a,0x45,0xf9,0x0b,0x25,0x9a,0x1a,0xe1,0xe8,0x20,0x01,0x39,0xd7,0xcb,
|
||||
0xab,0x76,0x79,0xd1,0xc1,0xb5,0xa3,0xba,0x5c,0x91,0xc5,0xa8,0x6b,0x02,0x5a,0xa7,
|
||||
0x2f,0xb6,0x35,0x68,0x1d,0x3f,0x1e,0xa9,0x6a,0xca,0x21,0x86,0xcb,0x1d,0x5b,0xd3,
|
||||
0x94,0xe7,0x5c,0xf0,0x91,0xa1,0x17,0xaa,0x15,0x23,0x91,0x59,0x31,0xd2,0xf4,0x92,
|
||||
0x57,0x81,0x64,0xc8,0xdb,0x11,0x45,0xe7,0x62,0x95,0xa5,0xd6,0x13,0xd7,0x49,0x1e,
|
||||
0x4b,0x8c,0x94,0xc7,0x7e,0x36,0xb1,0x85,0xfe,0xff,0x1f,0x53,0x5c,0x05,0x2c,0x33,
|
||||
0xe2,0x62,0xa7,0x1c,0xfb,0x94,0x6b,0xe4,0xd2,0x0e,0xd8,0xc1,0xda,0x8c,0xb0,0x2e,
|
||||
0xb1,0x81,0x3a,0xec,0xb7,0xc5,0x48,0x72,0xc4,0xa7,0x0c,0xbd,0xc5,0x7c,0x9d,0xf5,
|
||||
0xb0,0x65,0xda,0x26,0x20,0x9c,0xef,0x4f,0xf7,0x69,0x37,0xb5,0xad,0x63,0x82,0x61,
|
||||
0xeb,0xb4,0xa2,0x5e,0xe8,0x43,0xfa,0x52,0x5c,0x55,0x56,0xe9,0x1c,0x3a,0x93,0x98,
|
||||
0x60,0x55,0x5d,0xdc,0x07,0x3c,0x5f,0x54,0x0d,0x67,0xbd,0x35,0xd6,0xe9,0x3a,0x00,
|
||||
0x6c,0x9d,0x17,0x4e,0xde,0x55,0xcc,0x75,0x80,0xee,0x15,0xf4,0xad,0xb7,0xa9,0xe0,
|
||||
0xa2,0x2a,0xd7,0xe7,0xdd,0x23,0x98,0x51,0x80,0xea,0x08,0x35,0x9b,0xbe,0x6b,0x03,
|
||||
0x37,0xd5,0x87,0x6d,0x7a,0xfa,0x5e,0x74,0xdc,0x4e,0x94,0x9d,0xa4,0x17,0x41,0x6e,
|
||||
0xfe,0xbf,0x0d,0xc8,0xb5,0xd1,0xd1,0xcd,0xe8,0x80,0xf5,0xff,0xe5,0xde,0xe4,0x48,
|
||||
0x38,0xdc,0xa7,0xe8,0x32,0x43,0x2e,0x1e,0xd2,0xd8,0x82,0x24,0xce,0x73,0x69,0x47,
|
||||
0x16,0x9a,0xe6,0x12,0xf5,0x3d,0x3e,0x98,0x6d,0xe7,0x1a,0xe5,0x08,0xe1,0x86,0x1b,
|
||||
0x9b,0xc5,0xb0,0xef,0x09,0xcc,0x6c,0x53,0xb6,0x6a,0xae,0x83,0xbd,0xd3,0x56,0xc7,
|
||||
0x27,0x1e,0xc3,0xc2,0x39,0x6d,0x6f,0xb8,0x23,0x0e,0x52,0x70,0x3a,0xdb,0x70,0xd3,
|
||||
0xeb,0xa5,0xe0,0x14,0x75,0xb0,0x78,0x4f,0xf5,0x55,0x95,0xc3,0x09,0x32,0x58,0x64,
|
||||
0xa7,0xac,0xc7,0x8e,0x09,0x57,0x2c,0x3c,0x83,0x03,0xa3,0x34,0x05,0xa8,0xa7,0x01,
|
||||
0xcf,0xd8,0xd7,0x2f,0x67,0x46,0x23,0x14,0x49,0x8e,0x3c,0x46,0x69,0x99,0x34,0x0b,
|
||||
0x5e,0x08,0x8c,0x9f,0xab,0x39,0xc7,0x8f,0x97,0x76,0x6e,0xa6,0x63,0x0f,0x67,0x1f,
|
||||
0x3d,0xe6,0xd3,0xdc,0xe2,0x30,0x18,0xcd,0x39,0x7a,0x2c,0x54,0x54,0x69,0x18,0x71,
|
||||
0x18,0x9e,0xf6,0x81,0xb0,0x9e,0x69,0x1c,0x06,0xd5,0x10,0x86,0xf8,0xa2,0x4c,0xb9,
|
||||
0x85,0xf0,0x7b,0xc3,0xc1,0x20,0x3c,0xe7,0x38,0xdb,0x77,0x11,0xc2,0xc4,0x93,0x3a,
|
||||
0xbd,0xb6,0x06,0x48,0xdf,0x12,0x72,0xfb,0xcc,0x04,0x61,0x75,0xa9,0xa9,0xb1,0xf6,
|
||||
0xd6,0xf0,0xf4,0x4a,0xca,0x05,0x69,0x56,0xc7,0x1b,0x39,0x4f,0x23,0x5c,0x0d,0x25,
|
||||
0x0d,0x67,0x0d,0xab,0x1d,0x0a,0x40,0x15,0x4f,0x8a,0x61,0xee,0xf9,0x26,0x4c,0x2e,
|
||||
0x40,0xa9,0x73,0xb0,0x56,0x36,0x67,0x55,0xf5,0x16,0xa0,0x50,0xfc,0xab,0x4a,0x95,
|
||||
0xa6,0x0e,0x04,0xdf,0x16,0x97,0xd5,0x84,0xb4,0xda,0x0b,0xb5,0x90,0xb3,0x38,0xaf,
|
||||
0x39,0x23,0x32,0x1c,0x0f,0x2d,0xde,0x7a,0x23,0xa7,0x73,0xd7,0xf5,0x18,0xaf,0xe7,
|
||||
0xef,0x25,0x71,0x32,0xe7,0x53,0xaf,0x28,0xd7,0x6a,0xb0,0x02,0xf7,0x56,0x2c,0x10,
|
||||
0x73,0x5e,0x18,0x87,0x83,0x09,0x97,0x65,0x51,0x73,0xe9,0xf8,0x11,0xce,0x60,0x7e,
|
||||
0xa2,0x97,0x82,0xf2,0x23,0x13,0xf3,0xaa,0xdc,0x1a,0x5d,0xad,0x2a,0x50,0xd1,0xbb,
|
||||
0xa9,0xe8,0x8e,0x9a,0x22,0xde,0x84,0xfa,0x8c,0xca,0x78,0x26,0x14,0x35,0xda,0x6f,
|
||||
0x35,0xd0,0x95,0x86,0x3d,0xc0,0x4d,0x4b,0xa6,0xb8,0x0d,0x3a,0xb7,0xd5,0x80,0x75,
|
||||
0x4c,0xd0,0xe2,0x07,0x59,0xba,0xbf,0xef,0xe9,0xf9,0x66,0x8f,0x4d,0xbc,0xd1,0x9f,
|
||||
0x7f,0x8c,0xbc,0x89,0x01,0x98,0xc7,0x55,0x8a,0x23,0xc5,0x00,0x76,0xf5,0xf1,0x83,
|
||||
0x6f,0xbf,0xf1,0xb4,0x40,0x34,0x0c,0x6a,0x05,0xfc,0x49,0x85,0xf0,0xae,0x58,0xc3,
|
||||
0xd2,0xae,0x35,0xc2,0x95,0xe4,0xa3,0x42,0xa7,0x2c,0x12,0xe8,0xdc,0x1f,0xa3,0x6e,
|
||||
0x7a,0x51,0xb4,0x62,0x06,0x45,0x14,0x4d,0xf8,0xb1,0xfe,0xf2,0x4b,0xeb,0x0b,0xbc,
|
||||
0x77,0x0c,0x5b,0x7f,0xc0,0x0a,0xa8,0x21,0xff,0x0b,0xea,0x6c,0x17,0x5c,0xa5,0xa2,
|
||||
0xc5,0xa3,0xe8,0x1e,0xc5,0xa3,0xa2,0x2c,0xf8,0x62,0x09,0xb2,0x64,0x38,0xcd,0x1c,
|
||||
0xb4,0x4e,0x09,0x57,0x2d,0x3f,0x0c,0xf8,0x81,0xe4,0xe8,0xcb,0x8c,0xa9,0x1a,0xce,
|
||||
0x06,0xe5,0xe5,0xc8,0x31,0x89,0xa3,0xde,0x0d,0xca,0x2e,0xd8,0x57,0xe4,0xc6,0x58,
|
||||
0xa6,0xe2,0x94,0x57,0x12,0x02,0x47,0xb1,0x71,0xc2,0x5c,0xae,0x97,0xb3,0x19,0x76,
|
||||
0xd5,0xb3,0x7e,0x32,0x6f,0x8a,0x8f,0xd1,0x37,0x3f,0x7c,0x03,0x73,0x47,0x44,0x55,
|
||||
0x47,0x34,0x55,0x7d,0x9d,0xd8,0x01,0x0d,0x38,0x47,0x32,0xce,0xcd,0x2a,0x0d,0x40,
|
||||
0x9f,0x25,0xcc,0x9e,0x34,0xf1,0x2e,0xc7,0x79,0xd2,0xe4,0xb1,0xc0,0xb9,0x76,0xa9,
|
||||
0x44,0xdd,0x2c,0x60,0xa0,0xdd,0x23,0x0c,0x12,0x0f,0x3c,0xc3,0x31,0xa6,0x8d,0x67,
|
||||
0x8c,0x51,0x2f,0x97,0x4d,0x9e,0x82,0x45,0x85,0x84,0x85,0x7e,0xcc,0x47,0xb5,0x34,
|
||||
0x14,0x4f,0xa5,0xd3,0xd0,0xba,0x2b,0x87,0x18,0x56,0x39,0x43,0x8b,0x6f,0x82,0x20,
|
||||
0xca,0xd5,0x23,0xb4,0x97,0x3a,0xc9,0xd2,0x46,0x40,0x17,0x9f,0x01,0xdd,0x34,0xb1,
|
||||
0x90,0x0c,0x33,0x89,0x7a,0x36,0xed,0x83,0x28,0x51,0xc3,0x80,0x20,0x84,0x5f,0xac,
|
||||
0x78,0x40,0x39,0xee,0x81,0x0d,0xc6,0x8e,0xdf,0x55,0xa4,0x95,0xd5,0x82,0xfc,0x01,
|
||||
0x1f,0xae,0xc4,0x22,0x96,0x5e,0x82,0x08,0xa8,0x85,0x5c,0x7e,0x71,0xf7,0xce,0x0d,
|
||||
0x21,0x96,0x8f,0x68,0x09,0x68,0x10,0xae,0xc4,0xd3,0x92,0x7b,0xe0,0x68,0xcf,0x47,
|
||||
0x55,0xf0,0x1b,0x63,0xa1,0xbd,0x89,0x11,0xe3,0xa3,0x38,0x3e,0x7e,0x6a,0xf1,0x15,
|
||||
0x8f,0xa0,0x04,0xa0,0xb1,0xf7,0xe0,0xfe,0xe3,0x27,0x9e,0xaf,0x6b,0x14,0xd8,0x50,
|
||||
0x6f,0x6f,0x65,0x62,0x7e,0xb9,0xe2,0x32,0x9b,0xc0,0xd5,0x11,0x46,0x81,0x4b,0x82,
|
||||
0xfa,0x08,0x18,0xba,0xed,0x0b,0xae,0xb1,0xb3,0x19,0x7d,0x52,0x85,0x17,0x6f,0x81,
|
||||
0x1b,0x81,0x29,0xc9,0xf6,0x9c,0xd0,0x91,0x2f,0x02,0xaa,0xb2,0x69,0xfd,0x81,0xa4,
|
||||
0x79,0xfa,0x15,0x8c,0xd7,0xaf,0x2b,0x1f,0x94,0x22,0xce,0x59,0xd8,0x17,0x5c,0x34,
|
||||
0xb0,0x8f,0xa1,0xe5,0x4d,0xba,0x74,0x27,0xde,0x17,0x90,0xfd,0x2b,0x47,0x72,0x13,
|
||||
0x1b,0x6e,0x9c,0x69,0x00,0xd5,0x12,0xa3,0x08,0xe6,0x4b,0xcf,0x7b,0xea,0xe7,0x2b,
|
||||
0x12,0x10,0x1f,0x9f,0xb3,0x43,0x19,0x7e,0x29,0x15,0x9c,0xad,0x2c,0xcf,0x81,0xee,
|
||||
0x46,0x59,0x8a,0xc0,0x9b,0x2a,0x51,0x46,0x33,0x28,0x3d,0x10,0x99,0xe3,0x1b,0x4f,
|
||||
0x9e,0x3c,0x00,0xa1,0xba,0x64,0x27,0x1e,0x9b,0x5a,0x8b,0xba,0x44,0x3f,0x01,0x95,
|
||||
0x58,0xd8,0x23,0xeb,0x50,0x52,0x74,0x99,0x85,0x94,0x57,0x52,0xb6,0xa4,0x2c,0x0a,
|
||||
0xae,0xee,0xa0,0x8b,0x14,0x48,0x89,0x6a,0x27,0xf0,0x7a,0x89,0xc7,0x1b,0x65,0x25,
|
||||
0x0e,0x21,0x9e,0xc4,0x45,0xc2,0x73,0xa0,0xdf,0x8b,0x5f,0x63,0xa4,0x61,0xd4,0xa9,
|
||||
0xf8,0x22,0x00,0xd3,0x41,0x4d,0x0e,0xec,0xa9,0xb4,0xc4,0xff,0x2e,0xd6,0x76,0x75,
|
||||
0xc2,0x18,0x55,0x0f,0x53,0x94,0x44,0x7e,0xfb,0x56,0xa8,0xcd,0x26,0x5f,0xe5,0xa3,
|
||||
0x2c,0x53,0x8c,0x31,0x4a,0x25,0x43,0x1e,0xd9,0x23,0xf3,0x15,0xc3,0x8f,0xe6,0x2c,
|
||||
0xb5,0x8e,0x3f,0x77,0x03,0xff,0xd1,0x8f,0x43,0xfd,0x05,0x90,0x09,0x2d,0xa4,0x1f,
|
||||
0x35,0x00,0x00,
|
||||
};
|
||||
const uint32_t MESH_HTML_LIGHTWEIGHT_OTA_LEN = 619;
|
||||
const uint8_t MESH_HTML_LIGHTWEIGHT_OTA[] PROGMEM = {
|
||||
0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x74,0x52,0xc5,0x9a,0xdb,0x30,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
/* nRF52's Arduino recipe defaults to -Ofast, which more than doubles this
|
||||
* decoder. Keep the size policy local to tinf instead of changing MeshCore. */
|
||||
#pragma GCC push_options
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#if defined(NRF52_PLATFORM) && __GNUC__ >= 12
|
||||
#pragma GCC optimize ("Oz")
|
||||
#else
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
Vendored
+3
-3
@@ -1481,10 +1481,10 @@
|
||||
}
|
||||
],
|
||||
"noncompact_tokens_sha256": {
|
||||
"src/helpers/CommonCLI.cpp": "e060c15118b94db76a41391b1fee872759ddcf308576b60eb1ae6ddec9444b8a",
|
||||
"examples/simple_repeater/MyMesh.cpp": "bf614d2a4c672d33cbfbf2d4338f522c21e877abcfa7566f0e39a9fb6eecbbd9",
|
||||
"src/helpers/CommonCLI.cpp": "c39dce89a0222f32d3da3f66f9c130491c93cf1e14e7eefeccb5b57dd63e9612",
|
||||
"examples/simple_repeater/MyMesh.cpp": "c67ad3818404b7f1bd3fb99cd44aefac788f9c069342eb3124c693a066d03ca4",
|
||||
"src/helpers/RadioProfileCLI.cpp": "8e3d293cb522717b13901c27d7fc471f5c370710919fb3d74d35449bc497552d",
|
||||
"src/helpers/WirelessControl.h": "0944bfdefb7003b47af52104b4c7447d7e5e65d54b3396790878c5b49e472913",
|
||||
"src/helpers/WirelessControl.h": "d96f79fbc5736d9e08d4839c76b4af5c6cfccab5d3c2f01226cdbfeed1da56c5",
|
||||
"src/helpers/PowerSavingDiagnostics.h": "18b78f5780046ea8c772c4ce6c337741ae0c027039d9399c94c5f4205d114000",
|
||||
"src/helpers/FloodAdvertCLI.h": "0f378e4d0f6c3b9b79991068a4b4208139484d2144ee1d7c5094d39043463be4",
|
||||
"src/helpers/LedSettings.cpp": "df0681234ca1337148ad59caa935857bc692f98bf18603ca6d099c0382028400",
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
"""Exercise the compact OTA page against the existing ElegantOTA wire format."""
|
||||
from email import policy
|
||||
from email.parser import BytesParser
|
||||
import gzip
|
||||
import hashlib
|
||||
import http.server
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from test_webconfig_ui_runtime import BROWSER, run_browser
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
VENDOR = ROOT / "webui/vendor/spark-md5-3.0.2.min.js"
|
||||
HEADER = ROOT / "src/helpers/esp32/StaticHtml.h"
|
||||
SYMBOL = "MESH_HTML_ELEGANT_OTA_COMPACT"
|
||||
|
||||
|
||||
def embedded_page():
|
||||
source = HEADER.read_text(encoding="utf-8")
|
||||
length = int(re.search(SYMBOL + r"_LEN = (\d+);", source).group(1))
|
||||
array = source.split("const uint8_t " + SYMBOL + "[] PROGMEM = {", 1)[1].split("};", 1)[0]
|
||||
data = bytes(int(value, 16) for value in re.findall(r"0x([0-9a-f]{2})", array))
|
||||
if len(data) != length:
|
||||
raise AssertionError("compressed OTA page has a bad length")
|
||||
return gzip.decompress(data).decode("utf-8")
|
||||
|
||||
|
||||
class CompactElegantOtaTests(unittest.TestCase):
|
||||
def test_vendored_incremental_md5_vectors(self):
|
||||
self.assertEqual(hashlib.sha256(VENDOR.read_bytes()).hexdigest(),
|
||||
"d80e84c820cc5587a0ba3c8a20652099ea3fa7fc43944e812e56d449c1d9f1c9")
|
||||
node = shutil.which("node")
|
||||
if not node:
|
||||
self.skipTest("Node is unavailable")
|
||||
script = r'''
|
||||
const SparkMD5 = require(process.argv[1]);
|
||||
const crypto = require('crypto');
|
||||
if (SparkMD5.hash('') !== 'd41d8cd98f00b204e9800998ecf8427e' ||
|
||||
SparkMD5.hash('abc') !== '900150983cd24fb0d6963f7d28e17f72') process.exit(1);
|
||||
for (const size of [0, 3, 64, 262144, 700123]) {
|
||||
const input = Buffer.alloc(size);
|
||||
for (let i=0;i<size;i++) input[i] = i % 251;
|
||||
const digest = new SparkMD5.ArrayBuffer();
|
||||
for (let i=0;i<size;i+=65537) {
|
||||
const b=input.subarray(i,Math.min(size,i+65537));
|
||||
digest.append(b.buffer.slice(b.byteOffset,b.byteOffset+b.byteLength));
|
||||
}
|
||||
if (digest.end() !== crypto.createHash('md5').update(input).digest('hex')) process.exit(1);
|
||||
}
|
||||
'''
|
||||
subprocess.run([node, "-e", script, str(VENDOR)], check=True, timeout=30)
|
||||
|
||||
def test_only_tbeam_observers_select_the_page(self):
|
||||
source = (ROOT / "arch/esp32/AsyncElegantOTA/src/AsyncElegantOTA.h").read_text()
|
||||
self.assertIn("MESH_COMPACT_ELEGANT_OTA", source)
|
||||
self.assertIn("#include \"elegantWebpage.h\"", source)
|
||||
target = (ROOT / "variants/lilygo_tbeam_universal/platformio.ini").read_text()
|
||||
selected = re.findall(r"\[env:([^]]+)\]([^\[]*)", target)
|
||||
enabled = [name for name, section in selected if "-D MESH_COMPACT_ELEGANT_OTA=1" in section]
|
||||
self.assertEqual(enabled, ["Tbeam_repeater_observer_mqtt", "Tbeam_room_server_observer_mqtt"])
|
||||
self.assertEqual(embedded_page().count("MESH_SPARK_MD5"), 0)
|
||||
self.assertIn("SparkMD5", embedded_page())
|
||||
|
||||
@unittest.skipUnless(BROWSER, "Chromium-family browser is unavailable")
|
||||
def test_real_browser_streams_firmware_and_filesystem_with_correct_md5(self):
|
||||
payload = bytes(i % 251 for i in range(700123)) # three FileReader chunks
|
||||
requests = []
|
||||
page = embedded_page().replace("</body>", r'''
|
||||
<script>
|
||||
window.addEventListener('load',function(){
|
||||
var bytes=new Uint8Array(700123);for(var i=0;i<bytes.length;i++)bytes[i]=i%251;
|
||||
var input=document.getElementById('file'),button=document.getElementById('upload');
|
||||
function send(kind){
|
||||
document.querySelector('input[value="'+kind+'"]').checked=true;
|
||||
var selection=new DataTransfer();selection.items.add(new File([bytes],kind+'.bin'));
|
||||
input.files=selection.files;button.click();
|
||||
}
|
||||
var phase=0;send('firmware');
|
||||
document.body.setAttribute('data-test-busy',button.disabled&&input.disabled&&document.querySelector('input[value="filesystem"]').disabled?'yes':'no');
|
||||
var timer=setInterval(function(){
|
||||
var text=document.getElementById('status').textContent;
|
||||
if(phase===2){
|
||||
if(text.indexOf('Upload failed (HTTP 500)')<0)return;
|
||||
clearInterval(timer);document.body.setAttribute('data-test-done',!button.disabled&&!input.disabled&&!document.querySelector('input[value="filesystem"]').disabled?'yes':'no');
|
||||
document.body.setAttribute('data-test-identity',document.getElementById('identity').textContent);return;
|
||||
}
|
||||
if(text.indexOf('Update complete')<0)return;
|
||||
if(phase++===0){send('filesystem');return;}
|
||||
send('firmware');
|
||||
},30);
|
||||
});
|
||||
</script></body>''')
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, *_):
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
body = (b'{"id":"test node","hardware":"ESP32"}' if self.path == "/update/identity"
|
||||
else page.encode("utf-8"))
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json" if self.path == "/update/identity" else "text/html")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_POST(self):
|
||||
body = self.rfile.read(int(self.headers["Content-Length"]))
|
||||
message = BytesParser(policy=policy.default).parsebytes(
|
||||
b"Content-Type: " + self.headers["Content-Type"].encode("ascii")
|
||||
+ b"\r\nMIME-Version: 1.0\r\n\r\n" + body)
|
||||
parts = {p.get_param("name", header="Content-Disposition"): p for p in message.iter_parts()}
|
||||
requests.append((self.path, parts["MD5"].get_payload(decode=True).decode(),
|
||||
[(name, part.get_filename(), part.get_payload(decode=True))
|
||||
for name, part in parts.items() if name != "MD5"]))
|
||||
failure = len(requests) == 3
|
||||
response = b"invalid image" if failure else b"OK"
|
||||
self.send_response(500 if failure else 200)
|
||||
self.send_header("Content-Length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-ota-browser-") as folder:
|
||||
command = [BROWSER, "--headless=new", "--disable-gpu", "--no-first-run",
|
||||
"--user-data-dir=" + folder, "--virtual-time-budget=15000", "--dump-dom",
|
||||
"http://127.0.0.1:%d/update" % server.server_port]
|
||||
if os.name != "nt" and hasattr(os, "geteuid") and os.geteuid() == 0:
|
||||
command.insert(1, "--no-sandbox")
|
||||
result = run_browser(command)
|
||||
self.assertEqual(result.returncode, 0, result.stderr.decode(errors="replace"))
|
||||
self.assertIn('data-test-busy="yes"', result.stdout.decode(errors="replace"))
|
||||
self.assertIn('data-test-done="yes"', result.stdout.decode(errors="replace"))
|
||||
self.assertIn('data-test-identity="test node · ESP32"', result.stdout.decode(errors="replace"))
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
thread.join(timeout=5)
|
||||
digest = hashlib.md5(payload).hexdigest()
|
||||
self.assertEqual(requests, [("/update", digest, [(name, name, payload)])
|
||||
for name in ("firmware", "filesystem", "firmware")])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -22,7 +22,7 @@ HARNESS = r'''
|
||||
#define UI_HAS_JOYSTICK 0
|
||||
#define UI_WIFI_SETUP_HOME_PAGE 0
|
||||
#define MESH_DEBUG_PRINTLN(...)
|
||||
constexpr char KEY_ENTER='e', KEY_NEXT='n', KEY_PREV='p', KEY_SELECT='s', KEY_UP='u', KEY_DOWN='d';
|
||||
constexpr char KEY_ENTER='e', KEY_NEXT='n', KEY_PREV='p', KEY_SELECT='s', KEY_UP='u', KEY_DOWN='d', KEY_LEFT='l', KEY_RIGHT='r';
|
||||
constexpr int BUTTON_EVENT_CLICK=1, BUTTON_EVENT_LONG_PRESS=2, BUTTON_EVENT_DOUBLE_CLICK=3;
|
||||
constexpr int BUTTON_EVENT_TRIPLE_CLICK=4, BUTTON_EVENT_QUADRUPLE_CLICK=5;
|
||||
uint32_t now_ms=0;
|
||||
@@ -156,6 +156,13 @@ void visible() {
|
||||
event(ui, BUTTON_EVENT_LONG_PRESS);
|
||||
assert(ui.reader_closes==1 && ui.curr==ui.home);
|
||||
ui.home_screen.radio_page=true;
|
||||
#ifdef UI_HAS_NAV_INPUT
|
||||
event(ui, BUTTON_EVENT_CLICK);
|
||||
assert(ui.curr==ui.home && ui.reader_opens==0);
|
||||
#endif
|
||||
assert(ui.handleLongPress(KEY_LEFT)==KEY_LEFT);
|
||||
assert(ui.handleLongPress(KEY_RIGHT)==KEY_RIGHT);
|
||||
assert(ui.curr==ui.home && ui.reader_opens==0);
|
||||
event(ui, BUTTON_EVENT_LONG_PRESS);
|
||||
assert(ui.reader_opens==1 && ui.curr==ui.reader);
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Check the compact no-wireless Companion path against the full controller."""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from cpp_source import body
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
HARNESS = r'''
|
||||
#include <helpers/WirelessControl.h>
|
||||
#include "examples/companion_radio/CompanionWireless.h"
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Unavailable : mesh::wireless::Backend {
|
||||
unsigned sets = 0;
|
||||
uint8_t available() const override { return 0; }
|
||||
uint8_t enabled() const override { return 0; }
|
||||
uint8_t clients() const override { return 0; }
|
||||
mesh::wireless::Result set(uint8_t, bool) override {
|
||||
++sets;
|
||||
return mesh::wireless::Result::Failed;
|
||||
}
|
||||
};
|
||||
|
||||
@WRAPPER@
|
||||
|
||||
int main() {
|
||||
using namespace mesh::wireless;
|
||||
static Unavailable backend;
|
||||
Control reference;
|
||||
reference.begin(backend);
|
||||
control().begin(backend);
|
||||
const char* commands[] = {
|
||||
"", "get wifi", "get espnow", "get 2.4ghz", "get wifi.status",
|
||||
"set wifi on", "set wifi off", "set wifi off force", "set wifi on force",
|
||||
"set espnow on", "set espnow off", "set espnow off force",
|
||||
"set 2.4ghz on", "set 2.4ghz off", "set 2.4ghz off force",
|
||||
"set 2.4ghz on all", "set 2.4ghz on force", "set 2.4ghz on all force",
|
||||
" get wifi\t", "set\twifi on", "get wifi junk", "set wifi",
|
||||
"set wifi off force extra", "set espnow.channel 3", "get bluetooth",
|
||||
"set bluetooth off", "set 2.4ghzx on", "SET wifi on", "reset wifi"
|
||||
};
|
||||
const CompanionWirelessSource sources[] = {
|
||||
CompanionWirelessSource::Other, CompanionWirelessSource::Usb,
|
||||
CompanionWirelessSource::Network, CompanionWirelessSource::Framed
|
||||
};
|
||||
const uint8_t requesters[] = {0, Independent, WiFi, Bluetooth};
|
||||
for (const char* command : commands) for (size_t size = 0; size <= 160; ++size)
|
||||
for (unsigned route = 0; route < 4; ++route) {
|
||||
char expected[160], actual[160];
|
||||
memset(expected, 0x5a, sizeof(expected));
|
||||
memset(actual, 0x5a, sizeof(actual));
|
||||
const bool original = reference.handle(command, expected, size, 500, requesters[route]);
|
||||
const bool compact = handleCompanionWirelessCommand(command, actual, size, sources[route]);
|
||||
assert(original == compact);
|
||||
assert(memcmp(expected, actual, sizeof(expected)) == 0);
|
||||
reference.service(1000);
|
||||
control().service(1000);
|
||||
assert(!reference.pending() && !control().pending());
|
||||
assert(!reference.masterOff() && !control().masterOff());
|
||||
assert(!reference.blocked(All) && !control().blocked(All));
|
||||
assert(backend.sets == 0);
|
||||
}
|
||||
for (const char* command : {"get wifi", "set wifi on", "unknown"}) {
|
||||
assert(reference.handle(command, nullptr, 160, 500, Independent)
|
||||
== handleCompanionWirelessCommand(command, nullptr, 160, CompanionWirelessSource::Usb));
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
class NoWirelessCompanionTest(unittest.TestCase):
|
||||
def test_stm32_wrapper_matches_full_controller(self):
|
||||
wrapper = body((ROOT / "examples/companion_radio/main.cpp").read_text(),
|
||||
"bool handleCompanionWirelessCommand(")
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
source = Path(directory) / "no_wireless.cpp"
|
||||
source.write_text(HARNESS.replace("@WRAPPER@", wrapper))
|
||||
for compact in (0, 1):
|
||||
binary = Path(directory) / f"check-{compact}"
|
||||
command = [os.environ.get("CXX", "g++"), "-std=c++17", "-O1",
|
||||
"-Wall", "-Wextra", "-Werror", "-DSTM32_PLATFORM=1",
|
||||
f"-DMESH_CLI_COMPACT_REPLIES={compact}",
|
||||
"-I", str(ROOT / "src"), "-I", str(ROOT),
|
||||
str(source), "-o", str(binary)]
|
||||
if os.name != "nt":
|
||||
command[1:1] = ["-fsanitize=address,undefined",
|
||||
"-fno-sanitize-recover=all", "-fno-pie", "-no-pie"]
|
||||
subprocess.run(command, check=True, timeout=60)
|
||||
subprocess.run([str(binary)], check=True, timeout=30)
|
||||
|
||||
def test_wireless_capabilities_keep_the_full_controller(self):
|
||||
wrapper = body((ROOT / "examples/companion_radio/main.cpp").read_text(),
|
||||
"bool handleCompanionWirelessCommand(")
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
source = Path(directory) / "guard.cpp"
|
||||
source.write_text('#include <helpers/WirelessControl.h>\n'
|
||||
'#include "examples/companion_radio/CompanionWireless.h"\n'
|
||||
+ wrapper)
|
||||
for flags, compact in (
|
||||
(["-DSTM32_PLATFORM=1"], True),
|
||||
(["-DSTM32_PLATFORM=1", "-DMESH_PRIMARY_ESPNOW=0"], True),
|
||||
(["-DSTM32_PLATFORM=1", "-DBLE_PIN_CODE=123456"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DWIFI_SSID=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DMESH_PRIMARY_ESPNOW=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DWITH_BRIDGE=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DWITH_MQTT_BRIDGE=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DWITH_UDP_BRIDGE=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DWITH_ESPNOW_BRIDGE=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DWITH_WEBCONFIG=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DETHERNET_ENABLED=1"], False),
|
||||
(["-DSTM32_PLATFORM=1", "-DMESH_ENABLE_EMERGENCY_POWER=1"], False),
|
||||
([], False),
|
||||
):
|
||||
with self.subTest(flags=flags):
|
||||
result = subprocess.run(
|
||||
[os.environ.get("CXX", "g++"), "-std=c++17", "-E", "-P",
|
||||
*flags, "-I", str(ROOT / "src"), "-I", str(ROOT), str(source)],
|
||||
check=True, capture_output=True, text=True, timeout=60)
|
||||
self.assertEqual("return mesh::wireless::handleNoServices(" in result.stdout, compact)
|
||||
self.assertEqual("return mesh::wireless::control().handle(" in result.stdout, not compact)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -78,7 +78,7 @@ class CompanionReaderTest(unittest.TestCase):
|
||||
"#pragma once\n#include <stdint.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <string.h>\n"
|
||||
"class Stream { public: virtual size_t write(const uint8_t*,size_t);size_t print(const char*); };\n")
|
||||
flags = ["-mcpu=cortex-m4", "-mthumb", "-Oz", "-fno-inline-small-functions",
|
||||
"-fstack-usage", "-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1",
|
||||
"-fstack-usage", "-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DCOMPANION_FEATURE_READER=1",
|
||||
"-DENABLE_USB_INTERFACE=1", "-I" + str(directory),
|
||||
"-I" + str(ROOT / "src"), "-I" + str(FIXTURE)]
|
||||
if compact:
|
||||
@@ -239,7 +239,7 @@ class CompanionReaderTest(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-reader-") as directory:
|
||||
directory = Path(directory)
|
||||
for platform, compact in (("ESP32_PLATFORM", False), ("NRF52_PLATFORM", False), ("NRF52_PLATFORM", True)):
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"]
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=1"]
|
||||
if compact:
|
||||
flags.append("-DCOMPANION_READER_COMPACT_STREAM=1")
|
||||
obj, binary = directory / "tinf.o", directory / "reader.exe"
|
||||
@@ -264,7 +264,7 @@ class CompanionReaderTest(unittest.TestCase):
|
||||
self.skipTest("host G++ required")
|
||||
for platform, compact, expected in (("NRF52_PLATFORM", False, 3), ("NRF52_PLATFORM", True, 0),
|
||||
("ESP32_PLATFORM", True, None)):
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"]
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=1"]
|
||||
if compact:
|
||||
flags.append("-DCOMPANION_READER_COMPACT_STREAM=1")
|
||||
result = self.run_checked([cxx, "-E", "-P", *flags,
|
||||
@@ -280,6 +280,8 @@ class CompanionReaderTest(unittest.TestCase):
|
||||
self.skipTest("host G++ required")
|
||||
for flags in ([], ["-DESP32_PLATFORM=1"], ["-DNRF52_PLATFORM=1"],
|
||||
["-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1"],
|
||||
["-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"],
|
||||
["-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"],
|
||||
["-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1",
|
||||
"-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=0"]):
|
||||
result = self.run_checked([cxx, "-E", "-P", *flags,
|
||||
@@ -448,7 +450,7 @@ int main() {
|
||||
generated = directory / "dispatch.cpp"
|
||||
generated.write_text(harness)
|
||||
for platform in ("ESP32_PLATFORM", "NRF52_PLATFORM"):
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"]
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=1"]
|
||||
obj, binary = directory / "tinf.o", directory / "dispatch.exe"
|
||||
self.run_checked([cc, "-std=c11", "-Os", *flags, "-c",
|
||||
str(ROOT / "src/helpers/ota/OtaTinf.c"), "-o", str(obj)])
|
||||
@@ -465,7 +467,7 @@ int main() {
|
||||
self.skipTest("host GCC and G++ required")
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-reader-reader-") as directory:
|
||||
directory = Path(directory)
|
||||
flags = ["-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"]
|
||||
flags = ["-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=1"]
|
||||
obj, binary = directory / "tinf.o", directory / "reader.exe"
|
||||
self.run_checked([cc, "-Os", *flags, "-c", str(ROOT / "src/helpers/ota/OtaTinf.c"),
|
||||
"-o", str(obj)])
|
||||
@@ -582,7 +584,7 @@ int main() {
|
||||
generated = directory / "interlock.cpp"
|
||||
generated.write_text(harness)
|
||||
for platform in ("ESP32_PLATFORM", "NRF52_PLATFORM"):
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"]
|
||||
flags = [f"-D{platform}=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=1"]
|
||||
obj, binary = directory / "tinf.o", directory / "interlock.exe"
|
||||
self.run_checked([cc, "-std=c11", "-Os", *flags, "-c",
|
||||
str(ROOT / "src/helpers/ota/OtaTinf.c"), "-o", str(obj)])
|
||||
@@ -594,7 +596,11 @@ int main() {
|
||||
|
||||
def test_reader_button_routing_and_profile_gate(self):
|
||||
ui = (ROOT / "examples/companion_radio/ui-new/UITask.cpp").read_text(encoding="utf-8")
|
||||
long_press = ui[ui.index("char UITask::handleLongPress("):]
|
||||
home = extract_braced(ui, "class HomeScreen")
|
||||
short_press = extract_braced(home, "bool handleInput(char c) override")
|
||||
self.assertNotIn("showReader(", short_press)
|
||||
long_press = extract_braced(ui, "char UITask::handleLongPress(")
|
||||
self.assertRegex(long_press, r"if\s*\(c\s*==\s*KEY_ENTER[\s\S]*?isRadioPage\(\)\)\s*\{\s*showReader\(\)")
|
||||
self.assertLess(long_press.index("isReaderActive()"), long_press.index("enterCLIRescue()"))
|
||||
self.assertLess(long_press.index("isRadioPage()"), long_press.index("enterCLIRescue()"))
|
||||
self.assertIn("#if COMPANION_FEATURE_READER\n#include \"ReaderScreen.h\"", ui)
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from cpp_source import body
|
||||
from cpp_source import body, _uncommented
|
||||
from test_display_power import FS_STUB
|
||||
|
||||
|
||||
@@ -349,9 +350,11 @@ class LR2021CalibrationTests(unittest.TestCase):
|
||||
def test_all_cli_roles_load_before_init_and_service_pending(self):
|
||||
for role in ("simple_repeater", "simple_room_server", "simple_sensor", "companion_radio"):
|
||||
source = (ROOT / "examples" / role / "main.cpp").read_text()
|
||||
setup = body(source, "void setup()")
|
||||
setup = _uncommented(body(source, "void setup()"))
|
||||
with self.subTest(role=role):
|
||||
self.assertLess(setup.index("loadLR2021CalibrationSettings("), setup.index("radio_init()"))
|
||||
init = re.search(r"\bwhile\s*\([^\n;]*\bradio_init\s*\(\s*\)", setup)
|
||||
self.assertIsNotNone(init, "startup must initialize the radio")
|
||||
self.assertLess(setup.index("loadLR2021CalibrationSettings("), init.start())
|
||||
self.assertIn("loopLR2021CalibrationSettings(radio_driver)", source)
|
||||
self.assertIn("#if defined(USE_LR2021)", source)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class PocketReaderTest(unittest.TestCase):
|
||||
self.assertIsNotNone(font)
|
||||
selected = directory / "font.c"
|
||||
selected.write_text('#include "u8g2_fonts.h"\n' + font.group())
|
||||
flags = ["-g", "-O1", "-DHELTEC_MESH_POCKET=1", "-DCOMPANION_RADIO_FULL=1",
|
||||
flags = ["-g", "-O1", "-DHELTEC_MESH_POCKET=1", "-DCOMPANION_RADIO_FULL=1", "-DCOMPANION_FEATURE_READER=1",
|
||||
"-DNRF52_PLATFORM=1", "-DENABLE_USB_INTERFACE=1", "-DUI_POCKET_GUI=1",
|
||||
"-DUI_SMALL_MESSAGE_FONT=0", "-DUI_BUTTON_READER_HINT=1", "-DUI_READER_TOUCH_BAR=0",
|
||||
"-I" + str(FIXTURE), "-I" + str(ROOT / "src"), "-I" + str(ROOT),
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""The STM32WL no-service shortcut must not mask any wireless owner."""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class Stm32WirelessSpecializationTest(unittest.TestCase):
|
||||
def test_only_plain_stm32wl_uses_no_service_handler(self):
|
||||
compiler = shutil.which("gcc")
|
||||
if not compiler:
|
||||
self.skipTest("host GCC required")
|
||||
source = (ROOT / "src/helpers/CommonCLI.cpp").read_text()
|
||||
anchor = source.index("if (handleBeaconCommand(command, reply, 157))")
|
||||
start = source.index("#if defined(STM32_PLATFORM) && defined(STM32WLxx)", anchor)
|
||||
end = source.index("#endif", start) + len("#endif")
|
||||
branch = source[start:end]
|
||||
base = ("STM32_PLATFORM", "STM32WLxx", "USE_STM32WL")
|
||||
cases = [(base, True),
|
||||
(base[:-1], False),
|
||||
(("ESP32_PLATFORM",), False),
|
||||
(base + ("WITH_WEBCONFIG",), False),
|
||||
(base + ("WITH_MQTT_BRIDGE",), False),
|
||||
(base + ("WITH_UDP_BRIDGE",), False),
|
||||
(base + ("WITH_ESPNOW_BRIDGE",), False),
|
||||
(base + ("WITH_BRIDGE",), False),
|
||||
(base + ("ETHERNET_ENABLED",), False),
|
||||
(base + ("BLE_PIN_CODE=123456",), False),
|
||||
(base + ("WIFI_SSID=custom",), False),
|
||||
(base + ("MESH_PRIMARY_ESPNOW=1",), False),
|
||||
(base + ("MESH_ENABLE_EMERGENCY_POWER=1",), False)]
|
||||
for defines, expected in cases:
|
||||
with self.subTest(defines=defines):
|
||||
result = subprocess.run([compiler, "-E", "-P", "-x", "c", "-",
|
||||
*("-D" + value for value in defines)],
|
||||
input=branch, text=True, capture_output=True,
|
||||
check=True)
|
||||
self.assertEqual("handleNoServices" in result.stdout, expected)
|
||||
self.assertEqual("control().handle" in result.stdout, not expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -50,7 +50,12 @@ class WebConfigGzipTest(unittest.TestCase):
|
||||
self.assertIn("gzip, 1000 iterations", header)
|
||||
blobs = arrays(header)
|
||||
for page in (ROOT / "webui/pages").glob("*.html"):
|
||||
self.assertEqual(gzip.decompress(blobs["MESH_HTML_" + page.stem.upper()]), page.read_bytes())
|
||||
expected = page.read_bytes()
|
||||
if page.name == "elegant_ota_compact.html":
|
||||
marker = b"/* MESH_SPARK_MD5 */"
|
||||
self.assertEqual(expected.count(marker), 1)
|
||||
expected = expected.replace(marker, (ROOT / "webui/vendor/spark-md5-3.0.2.min.js").read_bytes())
|
||||
self.assertEqual(gzip.decompress(blobs["MESH_HTML_" + page.stem.upper()]), expected)
|
||||
|
||||
def test_loader_matches_legacy_android_browser_syntax(self):
|
||||
blobs = arrays((ROOT / "src/helpers/esp32/WebConfigHtml.h").read_text())
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Offline Reader lookup
|
||||
|
||||
ESP32 and nRF52 **Full Companion** USB terminals (and the ESP32 TCP terminal)
|
||||
include an embedded 879-verse corpus from the World English Bible:
|
||||
Reader is disabled in standard Companion builds to preserve flash space. A
|
||||
custom ESP32 or nRF52 **Full Companion** USB build can enable it with
|
||||
`COMPANION_FEATURE_READER=1`. The USB terminal (and ESP32 TCP terminal) then
|
||||
includes an embedded 879-verse corpus from the World English Bible:
|
||||
|
||||
```text
|
||||
get reader 3:16
|
||||
@@ -23,7 +25,7 @@ last verse of a range. An optional page number refers to the first verse in
|
||||
that command: `get reader 21:17 2`. No radio request can access the reader.
|
||||
No WiFi, phone, API key, filesystem
|
||||
space or bootloader update is needed. Reduced/ordinary Companion and other
|
||||
roles omit the corpus. `COMPANION_FEATURE_READER=0` excludes it from a Full build.
|
||||
roles omit the corpus; the opt-in requires a Full Companion USB build.
|
||||
|
||||
## On-device Reader
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ build_flags =
|
||||
-fno-partial-inlining
|
||||
-mno-target-align
|
||||
-D MESH_UNIVERSAL_LTO=1
|
||||
-D MESH_COMPACT_ELEGANT_OTA=1 ; full authenticated OTA protocol, smaller page
|
||||
-D ADVERT_NAME='"T-Beam MQTT Observer"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
@@ -201,6 +202,7 @@ build_flags =
|
||||
-fno-partial-inlining
|
||||
-mno-target-align
|
||||
-D MESH_UNIVERSAL_LTO=1
|
||||
-D MESH_COMPACT_ELEGANT_OTA=1 ; full authenticated OTA protocol, smaller page
|
||||
-D ADVERT_NAME='"T-Beam Room Observer"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>MeshCore OTA</title><style>
|
||||
body{font:16px sans-serif;max-width:38rem;margin:2rem auto;padding:0 1rem;line-height:1.45}
|
||||
fieldset{margin:1rem 0}button,input{font:inherit}button{padding:.5rem 1rem}progress{width:100%}
|
||||
#status{white-space:pre-wrap;overflow-wrap:anywhere}
|
||||
</style></head><body>
|
||||
<h1>MeshCore OTA</h1><p id="identity">Reading device identity…</p>
|
||||
<fieldset><legend>Update type</legend>
|
||||
<label><input type="radio" name="type" value="firmware" checked> Firmware</label>
|
||||
<label><input type="radio" name="type" value="filesystem"> Filesystem</label></fieldset>
|
||||
<label for="file">Image (.bin or .bin.gz)</label><br><input id="file" type="file" accept=".bin,.bin.gz"><br>
|
||||
<button type="button" id="upload">Upload</button>
|
||||
<progress id="progress" max="100" value="0" hidden></progress><p id="status" role="status"></p>
|
||||
<script>/* spark-md5 3.0.2, André Cruz, (MIT OR WTFPL); see webui/vendor. */
|
||||
/* MESH_SPARK_MD5 */
|
||||
</script><script>
|
||||
(function(){
|
||||
var input=document.getElementById('file'),button=document.getElementById('upload');
|
||||
var status=document.getElementById('status'),progress=document.getElementById('progress');
|
||||
var modes=document.querySelectorAll('input[name="type"]');
|
||||
function setBusy(busy){button.disabled=busy;input.disabled=busy;for(var i=0;i<modes.length;i++)modes[i].disabled=busy;}
|
||||
function finish(message){status.textContent=message;setBusy(false);}
|
||||
fetch('/update/identity',{cache:'no-store'}).then(function(response){
|
||||
if(!response.ok)throw Error('Identity unavailable');return response.json();
|
||||
}).then(function(identity){
|
||||
document.getElementById('identity').textContent=(identity.id||'MeshCore')+' · '+(identity.hardware||'ESP32');
|
||||
}).catch(function(){document.getElementById('identity').textContent='Device identity unavailable';});
|
||||
button.onclick=function(){
|
||||
var file=input.files&&input.files[0];
|
||||
if(!file||!file.size){finish('Select a nonempty image.');return;}
|
||||
var type=document.querySelector('input[name="type"]:checked').value;
|
||||
var md5=new SparkMD5.ArrayBuffer(),reader=new FileReader(),offset=0,chunk=262144;
|
||||
setBusy(true);progress.hidden=false;progress.value=0;
|
||||
status.textContent='Calculating checksum…';
|
||||
reader.onerror=function(){finish('Could not read the selected file.');};
|
||||
reader.onload=function(event){
|
||||
md5.append(event.target.result);offset+=event.target.result.byteLength;
|
||||
if(offset<file.size){readNext();return;}
|
||||
var form=new FormData(),request=new XMLHttpRequest();
|
||||
form.append('MD5',md5.end());form.append(type,file,type);
|
||||
request.open('POST','/update');request.withCredentials=true;
|
||||
request.upload.onprogress=function(event){if(event.lengthComputable){progress.value=Math.round(event.loaded*100/event.total);status.textContent='Uploading '+progress.value+'%';}};
|
||||
request.onload=function(){finish(request.status===200?'Update complete; the device will reboot.':'Upload failed (HTTP '+request.status+'): '+request.responseText);};
|
||||
request.onerror=function(){finish('Upload failed; check the connection and retry.');};
|
||||
request.onabort=function(){finish('Upload cancelled.');};
|
||||
request.send(form);
|
||||
};
|
||||
function readNext(){reader.readAsArrayBuffer(file.slice(offset,Math.min(file.size,offset+chunk)));}
|
||||
readNext();
|
||||
};
|
||||
})();
|
||||
</script></body></html>
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
`spark-md5-3.0.2.min.js` is SparkMD5 3.0.2 by André Cruz, from
|
||||
https://github.com/satazor/js-spark-md5/tree/v3.0.2. Its package metadata
|
||||
offers either WTFPL or MIT; MeshCore uses the MIT option. The upstream WTFPL
|
||||
license is also retained alongside the file.
|
||||
|
||||
SHA-256: `d80e84c820cc5587a0ba3c8a20652099ea3fa7fc43944e812e56d449c1d9f1c9`.
|
||||
The OTA page embeds this file locally; it never downloads scripts at runtime.
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2015 André Cruz <amdfcruz@gmail.com>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 André Cruz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
(function(factory){if(typeof exports==="object"){module.exports=factory()}else if(typeof define==="function"&&define.amd){define(factory)}else{var glob;try{glob=window}catch(e){glob=self}glob.SparkMD5=factory()}})(function(undefined){"use strict";var add32=function(a,b){return a+b&4294967295},hex_chr=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];function cmn(q,a,b,x,s,t){a=add32(add32(a,q),add32(x,t));return add32(a<<s|a>>>32-s,b)}function md5cycle(x,k){var a=x[0],b=x[1],c=x[2],d=x[3];a+=(b&c|~b&d)+k[0]-680876936|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[1]-389564586|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[2]+606105819|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[3]-1044525330|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[4]-176418897|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[5]+1200080426|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[6]-1473231341|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[7]-45705983|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[8]+1770035416|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[9]-1958414417|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[10]-42063|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[11]-1990404162|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[12]+1804603682|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[13]-40341101|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[14]-1502002290|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[15]+1236535329|0;b=(b<<22|b>>>10)+c|0;a+=(b&d|c&~d)+k[1]-165796510|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[6]-1069501632|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[11]+643717713|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[0]-373897302|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[5]-701558691|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[10]+38016083|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[15]-660478335|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[4]-405537848|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[9]+568446438|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[14]-1019803690|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[3]-187363961|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[8]+1163531501|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[13]-1444681467|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[2]-51403784|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[7]+1735328473|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[12]-1926607734|0;b=(b<<20|b>>>12)+c|0;a+=(b^c^d)+k[5]-378558|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[8]-2022574463|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[11]+1839030562|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[14]-35309556|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[1]-1530992060|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[4]+1272893353|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[7]-155497632|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[10]-1094730640|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[13]+681279174|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[0]-358537222|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[3]-722521979|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[6]+76029189|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[9]-640364487|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[12]-421815835|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[15]+530742520|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[2]-995338651|0;b=(b<<23|b>>>9)+c|0;a+=(c^(b|~d))+k[0]-198630844|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[7]+1126891415|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[14]-1416354905|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[5]-57434055|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[12]+1700485571|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[3]-1894986606|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[10]-1051523|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[1]-2054922799|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[8]+1873313359|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[15]-30611744|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[6]-1560198380|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[13]+1309151649|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[4]-145523070|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[11]-1120210379|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[2]+718787259|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[9]-343485551|0;b=(b<<21|b>>>11)+c|0;x[0]=a+x[0]|0;x[1]=b+x[1]|0;x[2]=c+x[2]|0;x[3]=d+x[3]|0}function md5blk(s){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=s.charCodeAt(i)+(s.charCodeAt(i+1)<<8)+(s.charCodeAt(i+2)<<16)+(s.charCodeAt(i+3)<<24)}return md5blks}function md5blk_array(a){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=a[i]+(a[i+1]<<8)+(a[i+2]<<16)+(a[i+3]<<24)}return md5blks}function md51(s){var n=s.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk(s.substring(i-64,i)))}s=s.substring(i-64);length=s.length;tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i<length;i+=1){tail[i>>2]|=s.charCodeAt(i)<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function md51_array(a){var n=a.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk_array(a.subarray(i-64,i)))}a=i-64<n?a.subarray(i-64):new Uint8Array(0);length=a.length;tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]Line truncated
|
||||
Reference in new issue
Block a user