Recover constrained firmware flash and update USB dispatch fixtures
This commit is contained in:
1 parent
7390e29086
commit
4261889095
15 files changed
+195
-102
No files matched your search
@@ -6426,6 +6426,105 @@ bool MyMesh::handleDirectOtaCommand(const char* command, uint32_t timestamp,
|
||||
}
|
||||
#endif
|
||||
|
||||
__attribute__((noinline))
|
||||
void MyMesh::handleSetChannelFrame(const uint8_t* cmd_frame) {
|
||||
uint8_t channel_idx = cmd_frame[1];
|
||||
ChannelDetails previous{};
|
||||
ChannelDetails channel;
|
||||
StrHelper::strncpy(channel.name, (char *)&cmd_frame[2], 32);
|
||||
memset(channel.channel.secret, 0, sizeof(channel.channel.secret));
|
||||
memcpy(channel.channel.secret, &cmd_frame[2 + 32], 16); // NOTE: only 128-bit supported
|
||||
const auto alias = parseCompanionChannelAlias(channel.name);
|
||||
if (alias.kind == CompanionChannelKind::Invalid) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
return;
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT
|
||||
// Legacy apps fetch only 16 bytes. A label-only edit must not silently
|
||||
// truncate a provisioned MT AES-256 key to AES-128. An explicit 32-byte SET
|
||||
// (zero upper half for AES-128) is the unambiguous way to replace that key.
|
||||
if (alias.kind == CompanionChannelKind::Meshtext
|
||||
&& getChannelForSave(channel_idx, previous) && isMeshtextChannel(previous)
|
||||
&& !memcmp(channel.channel.secret, previous.channel.secret, 16)) {
|
||||
memcpy(channel.channel.secret + 16, previous.channel.secret + 16, 16);
|
||||
}
|
||||
if (!validateMeshtextChannelChange(channel_idx, channel)) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE); // Conflicting preset, manual RF, region or busy owner.
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (alias.kind == CompanionChannelKind::Meshtext && isAliasRadio2Defined()) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); // Meshtext transport not implemented yet.
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
const bool have_previous = getChannelForSave(channel_idx, previous);
|
||||
ChannelDetails wire_channel = channel;
|
||||
ChannelDetails previous_wire_channel = previous;
|
||||
resolveChannelAlias(wire_channel);
|
||||
if (have_previous) resolveChannelAlias(previous_wire_channel);
|
||||
if (have_previous && channel.name[0] != 0 && previous.name[0] != 0
|
||||
&& !memcmp(wire_channel.channel.secret, previous_wire_channel.channel.secret, sizeof(channel.channel.secret))) {
|
||||
channel.channel.tx_radio = previous.channel.tx_radio;
|
||||
}
|
||||
const char* preset = companionChannelRadio2Preset(channel.name);
|
||||
const char* previous_preset = companionChannelRadio2Preset(previous.name);
|
||||
// A join or rename selects the saved preset, just like set radio2. App
|
||||
// replays/key edits must not undo a later manual Off or custom RF setting.
|
||||
const bool setup_preset = have_previous && preset
|
||||
&& (!previous_preset || strcmp(preset, previous_preset));
|
||||
if (setup_preset) {
|
||||
#if !MESH_ENABLE_MESHTEXT_CHAT
|
||||
// Even without the foreign-protocol backend, do not turn old fallback
|
||||
// mt- channels into inactive protocol requests as a hidden side effect.
|
||||
for (int i = 0; i < MAX_GROUP_CHANNELS; ++i) {
|
||||
ChannelDetails other;
|
||||
if (i != channel_idx && BaseChatMesh::getChannel(i, other)
|
||||
&& parseCompanionChannelAlias(other.name).kind == CompanionChannelKind::Meshtext) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!_radio_profiles.stageChannelPreset(preset)) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!have_previous
|
||||
|| !setChannel(channel_idx, channel)) {
|
||||
if (setup_preset) _radio_profiles.finishChannelPreset(false);
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
|
||||
} else if (saveChannels()) {
|
||||
if (setup_preset && !_radio_profiles.finishChannelPreset(true)) {
|
||||
// Never report a complete setup when the RF image did not commit.
|
||||
// Restore the channel when storage permits; report failure even if
|
||||
// an indeterminate channels write has quarantined further saves.
|
||||
setChannel(channel_idx, previous);
|
||||
saveChannels();
|
||||
_radio_profiles.finishChannelPreset(false);
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
return;
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT
|
||||
_mt_seen.clear();
|
||||
_mt_profile_dirty = true;
|
||||
refreshMeshtextProfile();
|
||||
#endif
|
||||
writeOKFrame();
|
||||
// Local history seed only after the durable save and its acknowledgement.
|
||||
seedEmergencyChannelNotice(channel_idx, previous);
|
||||
} else {
|
||||
// AtomicFileWriter preserves the last complete channels file. Keep the
|
||||
// live table consistent with it when a full or failing filesystem cannot
|
||||
// commit this update, and never acknowledge a change that will disappear
|
||||
// after reboot.
|
||||
setChannel(channel_idx, previous);
|
||||
if (setup_preset) _radio_profiles.finishChannelPreset(false);
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::handleCmdFrame(size_t len) {
|
||||
if (len == 0) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
@@ -7453,101 +7552,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
#endif
|
||||
} else if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16) {
|
||||
uint8_t channel_idx = cmd_frame[1];
|
||||
ChannelDetails previous{};
|
||||
ChannelDetails channel;
|
||||
StrHelper::strncpy(channel.name, (char *)&cmd_frame[2], 32);
|
||||
memset(channel.channel.secret, 0, sizeof(channel.channel.secret));
|
||||
memcpy(channel.channel.secret, &cmd_frame[2 + 32], 16); // NOTE: only 128-bit supported
|
||||
const auto alias = parseCompanionChannelAlias(channel.name);
|
||||
if (alias.kind == CompanionChannelKind::Invalid) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
return;
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT
|
||||
// Legacy apps fetch only 16 bytes. A label-only edit must not silently
|
||||
// truncate a provisioned MT AES-256 key to AES-128. An explicit 32-byte SET
|
||||
// (zero upper half for AES-128) is the unambiguous way to replace that key.
|
||||
if (alias.kind == CompanionChannelKind::Meshtext
|
||||
&& getChannelForSave(channel_idx, previous) && isMeshtextChannel(previous)
|
||||
&& !memcmp(channel.channel.secret, previous.channel.secret, 16)) {
|
||||
memcpy(channel.channel.secret + 16, previous.channel.secret + 16, 16);
|
||||
}
|
||||
if (!validateMeshtextChannelChange(channel_idx, channel)) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE); // Conflicting preset, manual RF, region or busy owner.
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (alias.kind == CompanionChannelKind::Meshtext && isAliasRadio2Defined()) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); // Meshtext transport not implemented yet.
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
const bool have_previous = getChannelForSave(channel_idx, previous);
|
||||
ChannelDetails wire_channel = channel;
|
||||
ChannelDetails previous_wire_channel = previous;
|
||||
resolveChannelAlias(wire_channel);
|
||||
if (have_previous) resolveChannelAlias(previous_wire_channel);
|
||||
if (have_previous && channel.name[0] != 0 && previous.name[0] != 0
|
||||
&& !memcmp(wire_channel.channel.secret, previous_wire_channel.channel.secret, sizeof(channel.channel.secret))) {
|
||||
channel.channel.tx_radio = previous.channel.tx_radio;
|
||||
}
|
||||
const char* preset = companionChannelRadio2Preset(channel.name);
|
||||
const char* previous_preset = companionChannelRadio2Preset(previous.name);
|
||||
// A join or rename selects the saved preset, just like set radio2. App
|
||||
// replays/key edits must not undo a later manual Off or custom RF setting.
|
||||
const bool setup_preset = have_previous && preset
|
||||
&& (!previous_preset || strcmp(preset, previous_preset));
|
||||
if (setup_preset) {
|
||||
#if !MESH_ENABLE_MESHTEXT_CHAT
|
||||
// Even without the foreign-protocol backend, do not turn old fallback
|
||||
// mt- channels into inactive protocol requests as a hidden side effect.
|
||||
for (int i = 0; i < MAX_GROUP_CHANNELS; ++i) {
|
||||
ChannelDetails other;
|
||||
if (i != channel_idx && BaseChatMesh::getChannel(i, other)
|
||||
&& parseCompanionChannelAlias(other.name).kind == CompanionChannelKind::Meshtext) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!_radio_profiles.stageChannelPreset(preset)) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!have_previous
|
||||
|| !setChannel(channel_idx, channel)) {
|
||||
if (setup_preset) _radio_profiles.finishChannelPreset(false);
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
|
||||
} else if (saveChannels()) {
|
||||
if (setup_preset && !_radio_profiles.finishChannelPreset(true)) {
|
||||
// Never report a complete setup when the RF image did not commit.
|
||||
// Restore the channel when storage permits; report failure even if
|
||||
// an indeterminate channels write has quarantined further saves.
|
||||
setChannel(channel_idx, previous);
|
||||
saveChannels();
|
||||
_radio_profiles.finishChannelPreset(false);
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
return;
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_CHAT
|
||||
_mt_seen.clear();
|
||||
_mt_profile_dirty = true;
|
||||
refreshMeshtextProfile();
|
||||
#endif
|
||||
writeOKFrame();
|
||||
// Local history seed only after the durable save and its acknowledgement.
|
||||
seedEmergencyChannelNotice(channel_idx, previous);
|
||||
} else {
|
||||
// AtomicFileWriter preserves the last complete channels file. Keep the
|
||||
// live table consistent with it when a full or failing filesystem cannot
|
||||
// commit this update, and never acknowledge a change that will disappear
|
||||
// after reboot.
|
||||
setChannel(channel_idx, previous);
|
||||
if (setup_preset) _radio_profiles.finishChannelPreset(false);
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
}
|
||||
handleSetChannelFrame(cmd_frame);
|
||||
} else if (cmd_frame[0] == CMD_SIGN_START) {
|
||||
BaseSerialInterface* signing_route = _serial->captureReplyRoute();
|
||||
if (signing_route == NULL) {
|
||||
|
||||
@@ -332,6 +332,7 @@ public:
|
||||
// constrained STM32 targets; merging their branches increases code size.
|
||||
__attribute__((noinline))
|
||||
#endif
|
||||
void handleSetChannelFrame(const uint8_t* cmd_frame);
|
||||
void handleCmdFrame(size_t len);
|
||||
bool advert();
|
||||
void enterCLIRescue();
|
||||
|
||||
@@ -6,6 +6,18 @@ Import("env")
|
||||
# to the LTO driver automatically. Keep this list matched by the profile test.
|
||||
# No compiler/ABI, flash boundary or capacity check is overridden here.
|
||||
if env["PIOENV"] == "wio-e5-mini_repeater":
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
# Reuse the qualified RAK CLI's equivalent libc reply copies. Restrict
|
||||
# this flag to CommonCLI.cpp; radio, framework and other objects retain
|
||||
# the existing optimizer recipe.
|
||||
source = Path(__file__).resolve().with_name("nrf52_flash_trim.py")
|
||||
spec = importlib.util.spec_from_file_location("mesh_mini_cli_replies", source)
|
||||
replies = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(replies)
|
||||
env.AddBuildMiddleware(replies.compile_shared_cli_replies, "*CommonCLI.cpp")
|
||||
|
||||
env.Append(LINKFLAGS=[
|
||||
"-fno-caller-saves",
|
||||
"-fno-tree-dominator-opts",
|
||||
|
||||
Vendored
+1
@@ -206,6 +206,7 @@ public:
|
||||
void writeErrFrame(uint8_t value) { error=value; }
|
||||
void writeOKFrame() { ok=true; }
|
||||
bool sendGroupMessage(uint32_t,mesh::GroupChannel&,const char*,const char*,int) { ++meshcore_sends; return true; }
|
||||
void handleSetChannelFrame(const uint8_t*);
|
||||
void command(uint8_t* cmd_frame,int len);
|
||||
void commit(int index,const ChannelDetails& value) {
|
||||
assert(setChannel(index,value)); _mt_profile_dirty=true; refreshMeshtextProfile();
|
||||
|
||||
+4
-3
@@ -9,6 +9,7 @@
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/CompanionHardwareCommandCompat.h>
|
||||
#include <helpers/radiolib/RXPowerSaving.h>
|
||||
#include "usb_mode_console.h"
|
||||
|
||||
namespace mesh {enum class RadioParamApplyResult {APPLIED,BUSY,FAILED};}
|
||||
struct Prefs {
|
||||
@@ -40,7 +41,7 @@ struct Driver {
|
||||
}
|
||||
} radio_driver;
|
||||
struct BaseSerialInterface {};
|
||||
struct Serial {
|
||||
struct SerialTransport {
|
||||
BaseSerialInterface requester, other;
|
||||
BaseSerialInterface* current = &requester;
|
||||
BaseSerialInterface* reply_route = nullptr;
|
||||
@@ -54,8 +55,8 @@ struct Serial {
|
||||
};
|
||||
struct MyMesh {
|
||||
Prefs _prefs, durable;
|
||||
Serial serial;
|
||||
Serial* _serial = &serial;
|
||||
SerialTransport serial;
|
||||
SerialTransport* _serial = &serial;
|
||||
bool _radio_available = true, storage_accepts = true, saved_radio_apply_pending = false;
|
||||
bool _primary_radio_blocked=false;
|
||||
unsigned saves = 0;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/CompanionFrameLimits.h>
|
||||
#include <helpers/CompanionHardwareCommandCompat.h>
|
||||
#include "../usb_mode_console.h"
|
||||
|
||||
struct Board {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
@@ -31,7 +32,7 @@ struct Board {
|
||||
#endif
|
||||
};
|
||||
struct BaseSerialInterface { unsigned id; };
|
||||
struct Serial {
|
||||
struct SerialTransport {
|
||||
BaseSerialInterface usb{1}, ble{2};
|
||||
BaseSerialInterface* current = &usb;
|
||||
BaseSerialInterface* captured = nullptr;
|
||||
@@ -62,8 +63,8 @@ struct Output {
|
||||
};
|
||||
struct MyMesh {
|
||||
Board board;
|
||||
Serial serial;
|
||||
Serial* _serial = &serial;
|
||||
SerialTransport serial;
|
||||
SerialTransport* _serial = &serial;
|
||||
Output output;
|
||||
char reply_buf[160] = {};
|
||||
uint8_t out_frame[MAX_FRAME_SIZE] = {};
|
||||
@@ -142,6 +143,9 @@ static void dispatch(MyMesh& mesh, BaseSerialInterface* requester, bool nul) {
|
||||
}
|
||||
int main() {
|
||||
MyMesh mesh;
|
||||
char usb_reply[160] = {};
|
||||
assert(mesh.handleCommand("AB|get usb.mode", 0, usb_reply));
|
||||
assert(std::string(usb_reply) == std::string("AB|usb.mode ") + mesh::usbModeName<decltype(Serial)>());
|
||||
bounds(mesh);
|
||||
#if defined(NRF52_PLATFORM)
|
||||
assert(mesh.board.reader_capacity == 128);
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
// Dispatch fixtures have no Arduino console, but must retain the real USB-mode
|
||||
// handler. Model its backend types separately from each fixture's framed reply
|
||||
// transport; no console reads, writes or USB peripherals are needed here.
|
||||
struct HardwareSerial {};
|
||||
struct HWCDC {};
|
||||
struct USBCDC {};
|
||||
struct Adafruit_USBD_CDC {};
|
||||
#if (defined(ESP32) || defined(ESP32_PLATFORM)) \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
||||
#if defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1
|
||||
inline HWCDC Serial;
|
||||
#else
|
||||
inline USBCDC Serial;
|
||||
#endif
|
||||
#elif defined(NRF52_PLATFORM) && defined(USE_TINYUSB)
|
||||
inline Adafruit_USBD_CDC Serial;
|
||||
#else
|
||||
inline HardwareSerial Serial;
|
||||
#endif
|
||||
|
||||
#include <helpers/UsbMode.h>
|
||||
@@ -404,6 +404,7 @@ struct Serial {
|
||||
'''
|
||||
|
||||
COMMAND_MEMBERS = r'''
|
||||
void handleSetChannelFrame(const uint8_t*);
|
||||
Serial serial; Serial* _serial = &serial;
|
||||
struct ProfileSetup {
|
||||
bool stage_ok = true, commit_ok = true, pending = false;
|
||||
@@ -1027,6 +1028,7 @@ class CompanionChannelAliasTest(unittest.TestCase):
|
||||
"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 32)",
|
||||
"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16)",
|
||||
)
|
||||
methods += "\n" + body(companion, "void MyMesh::handleSetChannelFrame(")
|
||||
branches = " else ".join(body(companion, signature) for signature in command_signatures)
|
||||
macros = ("CMD_GET_CHANNEL", "CMD_SET_CHANNEL", "RESP_CODE_CHANNEL_INFO",
|
||||
"ERR_CODE_UNSUPPORTED_CMD", "ERR_CODE_NOT_FOUND", "ERR_CODE_FILE_IO_ERROR",
|
||||
@@ -1166,6 +1168,7 @@ int main() {
|
||||
"MyMesh::Frame& MyMesh::heldDMFrameAt(",
|
||||
"void MyMesh::removeHeldOneKeyDM(",
|
||||
)) + "\n#endif\n"
|
||||
methods += "\n" + body(companion, "void MyMesh::handleSetChannelFrame(")
|
||||
branches = " else ".join(body(companion, signature) for signature in (
|
||||
"if (cmd_frame[0] == CMD_GET_CHANNEL && len >= 2)",
|
||||
"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 32)",
|
||||
|
||||
@@ -88,6 +88,7 @@ def publication_fixture(work, missing_latch, skip_noop_save=False):
|
||||
methods = recovery + "\n" + route
|
||||
methods += "\n" + method(store, "void DataStore::loadChannels(")
|
||||
methods += "\n" + save + "\n" + method(store, "bool DataStore::hasIncompleteContactLoad(")
|
||||
methods += "\n" + method(mesh, "void MyMesh::handleSetChannelFrame(")
|
||||
binary_start = mesh.index(" } else if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16) {")
|
||||
binary_end = mesh.index(" } else if (cmd_frame[0] == CMD_SIGN_START)", binary_start)
|
||||
methods += ("\nvoid MyMesh::applyChannelFrame(const uint8_t* cmd_frame, size_t len) {\n"
|
||||
@@ -129,6 +130,7 @@ struct StrHelper {
|
||||
void writeOKFrame() { binary_ack = true; }
|
||||
void seedEmergencyChannelNotice(uint8_t, const ChannelDetails&) {}
|
||||
bool resolveChannelAlias(ChannelDetails&) const { return true; }
|
||||
void handleSetChannelFrame(const uint8_t*);
|
||||
void applyChannelFrame(const uint8_t*, size_t);
|
||||
""", 1)
|
||||
source = source.replace("@RECOVERY_METHOD@", method(header, " bool contactRecoveryPending() const"))
|
||||
|
||||
@@ -29,6 +29,7 @@ HARNESS = r'''
|
||||
#include <helpers/FleetCommand.h>
|
||||
#include <helpers/TempRadioReplyBarrier.h>
|
||||
#include "regulator_cli_stub.h"
|
||||
#include "usb_mode_console.h"
|
||||
#include <helpers/CompanionFrameLimits.h>
|
||||
#include <helpers/HostKissCodec.h>
|
||||
#include <openssl/evp.h>
|
||||
@@ -364,6 +365,10 @@ static void compactBroadcast(MyMesh& node, const char* command) {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
assert(argc == 2);
|
||||
MyMesh usb_probe;
|
||||
char usb_reply[160] = {};
|
||||
assert(usb_probe.handleCommand("AB|get usb.mode", 0, usb_reply));
|
||||
assert(std::string(usb_reply) == "AB|usb.mode unknown" && usb_probe.queued == 0);
|
||||
const std::string scenario = argv[1];
|
||||
MyMesh node; char reply[160] = {};
|
||||
if (scenario == "local_boundary") {
|
||||
@@ -1513,6 +1518,7 @@ class CompanionFleetControlTests(unittest.TestCase):
|
||||
#include <cstring>
|
||||
#include <helpers/FleetCommand.h>
|
||||
#include "regulator_cli_stub.h"
|
||||
#include "usb_mode_console.h"
|
||||
static_assert(MESH_ENABLE_FLEET_CONTROL == 0, "compact profile must disable fleet");
|
||||
// No channels, clock, signing identity, codec definitions or sendGroupData.
|
||||
class MyMesh {
|
||||
|
||||
@@ -60,7 +60,7 @@ class CompanionMeshtextTest(unittest.TestCase):
|
||||
"static bool isEmergencyNoticeChannelName(",
|
||||
"void MyMesh::seedEmergencyChannelNotice(",
|
||||
"void MyMesh::servicePendingChannelNotices(", "bool MyMesh::queueChannelText(",
|
||||
"bool MyMesh::saveAdvertName(")
|
||||
"bool MyMesh::saveAdvertName(", "void MyMesh::handleSetChannelFrame(")
|
||||
methods = "\n".join(body(source, signature) for signature in signatures)
|
||||
utils_source = (ROOT / "src/Utils.cpp").read_text(encoding="utf-8")
|
||||
# CLI keys use the production strict hexadecimal parser as well.
|
||||
|
||||
@@ -22,6 +22,7 @@ HARNESS = r'''
|
||||
#include <helpers/esp32/PowerTestControls.h>
|
||||
#include "companion_fleet_dispatch.h"
|
||||
#include "battery_calibration_cli_stub.h"
|
||||
#include "usb_mode_console.h"
|
||||
#include <sys/stat.h>
|
||||
#include <limits>
|
||||
#define constrain(value,low,high) ((value)<(low)?(low):((value)>(high)?(high):(value)))
|
||||
|
||||
@@ -392,6 +392,7 @@ int main() {
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include "battery_calibration_cli_stub.h"
|
||||
#include "usb_mode_console.h"
|
||||
struct BatteryBoard {} board;
|
||||
struct Radio { bool active = false; };
|
||||
namespace mesh {
|
||||
|
||||
@@ -282,6 +282,7 @@ int main() {
|
||||
built = subprocess.run([
|
||||
compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror", *sanitizers,
|
||||
"-DCOMPANION_FEATURE_TEMP_RADIO=0", "-I", str(ROOT / "src"),
|
||||
"-I", str(ROOT / "test/fixtures"),
|
||||
"-isystem", str(ROOT / "test/mocks"), str(cpp),
|
||||
str(ROOT / "src/helpers/radiolib/RXPowerSaving.cpp"), "-o", str(binary),
|
||||
], capture_output=True, text=True, timeout=60)
|
||||
|
||||
@@ -39,6 +39,15 @@ def read_config(path):
|
||||
|
||||
|
||||
class FakeEnvironment(dict):
|
||||
def AddBuildMiddleware(self, callback, pattern):
|
||||
self.middleware = getattr(self, "middleware", []) + [(callback, pattern)]
|
||||
|
||||
def subst(self, value):
|
||||
return value.replace("$PROJECT_DIR", str(ROOT))
|
||||
|
||||
def Object(self, node, **flags):
|
||||
return node, flags
|
||||
|
||||
def Append(self, **values):
|
||||
for name, items in values.items():
|
||||
self.setdefault(name, []).extend(items)
|
||||
@@ -126,6 +135,29 @@ class MiniRepeaterProfileTest(unittest.TestCase):
|
||||
expected = ["keep-existing-flag", *(LINK_SIZE_FLAGS if target == TARGET else ())]
|
||||
self.assertEqual(environment["LINKFLAGS"], expected)
|
||||
self.assertEqual(set(environment), {"PIOENV", "LINKFLAGS"})
|
||||
self.assertEqual(len(getattr(environment, "middleware", [])),
|
||||
int(target == TARGET))
|
||||
|
||||
def test_shared_reply_copy_policy_changes_only_the_exact_mini_cli_object(self):
|
||||
class Node:
|
||||
def __init__(self, path): self.path = path
|
||||
def srcnode(self): return self
|
||||
def get_abspath(self): return str(self.path)
|
||||
|
||||
environment = FakeEnvironment(PIOENV=TARGET, CCFLAGS=["-Oz", "-flto"])
|
||||
runpy.run_path(str(ROOT / "scripts/stm32_mini_size.py"), init_globals={
|
||||
"env": environment, "Import": lambda name: self.assertEqual(name, "env")})
|
||||
callback, pattern = environment.middleware[0]
|
||||
self.assertEqual(pattern, "*CommonCLI.cpp")
|
||||
cli = Node(ROOT / "src/helpers/CommonCLI.cpp")
|
||||
same, flags = callback(environment, cli)
|
||||
self.assertIs(same, cli)
|
||||
self.assertEqual(flags["CCFLAGS"], ["-Oz", "-flto", "-fno-builtin-strcpy"])
|
||||
self.assertEqual(environment["CCFLAGS"], ["-Oz", "-flto"])
|
||||
for path in ("test/fixtures/CommonCLI.cpp", "src/helpers/RadioProfileCLI.cpp",
|
||||
"src/helpers/UsbLogging.cpp"):
|
||||
other = Node(ROOT / path)
|
||||
self.assertIs(callback(environment, other), other)
|
||||
|
||||
def test_capability_contract_declares_only_exact_mini_repeater_omissions(self):
|
||||
command = r'''
|
||||
|
||||
Reference in new issue
Block a user