mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-14 10:05:36 +00:00
253 lines
9.4 KiB
C++
253 lines
9.4 KiB
C++
#include <helpers/BorrowableFrameBuffer.h>
|
|
#include <helpers/ota/OtaContext.h>
|
|
#include <helpers/CompanionMotaControl.h>
|
|
#include <helpers/ota/MotaStreamWritePolicy.h>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <vector>
|
|
#include "../../test_ota/mota_vectors.h"
|
|
|
|
using namespace mesh::ota;
|
|
|
|
// Only the flash identity probe is replaced: the context, queue, session
|
|
// engine, source parsing, Merkle proofs and transfer bytes are production code.
|
|
namespace mesh { namespace ota {
|
|
bool ota_self_firmware(SelfFwInfo& info) { info = SelfFwInfo(); return false; }
|
|
} }
|
|
|
|
struct Frame { uint8_t len; uint8_t buf[176]; };
|
|
struct Queue {
|
|
mesh::BorrowableFrameBuffer<Frame, 256, 128, OtaContext> buffer;
|
|
int count = 129, head = 191;
|
|
unsigned released = 0;
|
|
static OtaContext* acquire(void* owner) {
|
|
Queue& q = *static_cast<Queue*>(owner);
|
|
return q.buffer.acquire(q.count, q.head);
|
|
}
|
|
static void release(void* owner) {
|
|
Queue& q = *static_cast<Queue*>(owner);
|
|
q.buffer.release(q.head);
|
|
++q.released;
|
|
}
|
|
};
|
|
|
|
class Folder : public MotaSource {
|
|
public:
|
|
uint8_t count() override { return 1; }
|
|
bool describe(uint8_t idx, MotaDesc& d) override {
|
|
if (idx) return false;
|
|
MotaManifest m;
|
|
if (!mota_parse(MOTA_VEC, MOTA_VEC_LEN, m)) return false;
|
|
memcpy(d.mid, m.merkle_root, 4);
|
|
d.target_id = m.target_id;
|
|
d.fw_version = m.fw_version;
|
|
d.codec_id = m.codec_id;
|
|
d.flags = m.flags;
|
|
d.block_size_log2 = m.block_size_log2;
|
|
d.total_size = MOTA_VEC_LEN;
|
|
d.leaves_off = m.leaves - MOTA_VEC;
|
|
d.block_count = m.block_count;
|
|
d.payload_off = m.payload - MOTA_VEC;
|
|
d.payload_size = m.payload_size;
|
|
return true;
|
|
}
|
|
bool read(uint8_t idx, uint32_t offset, uint8_t* out, uint32_t length) override {
|
|
if (idx || uint64_t(offset) + length > MOTA_VEC_LEN) return false;
|
|
memcpy(out, MOTA_VEC + offset, length);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// The actual BLE source controller below sees a ready/not-ready link and a
|
|
// source with real container bytes. Hardware GATT/serial framing is separate.
|
|
#if !defined(ESP32_PLATFORM)
|
|
struct Bluetooth {
|
|
bool ready = false, active = false;
|
|
int motaStream() { return 0; }
|
|
bool isMotaChannelReady() const { return ready; }
|
|
bool isMotaStreamActive() const { return active; }
|
|
void setMotaStreamActive(bool value) { active = value; }
|
|
} bluetooth_interface;
|
|
namespace mesh {
|
|
struct Log { void println(const char*) {} };
|
|
Log& usbLoggingPort() { static Log log; return log; }
|
|
namespace ota {
|
|
class SerialMotaSource : public ::Folder {
|
|
public:
|
|
SerialMotaSource(int, MotaStreamWritePolicy, uint32_t) {}
|
|
};
|
|
} }
|
|
#include "ble_control_under_test.h"
|
|
#endif
|
|
|
|
struct Packet { bool to_source; std::vector<uint8_t> bytes; };
|
|
static std::deque<Packet> packets;
|
|
static bool send(void* to_source, const uint8_t* bytes, uint16_t length, bool) {
|
|
packets.push_back({to_source != nullptr, {bytes, bytes + length}});
|
|
return true;
|
|
}
|
|
|
|
static void transfer(OtaContext& context, void* reply_route) {
|
|
OtaManager receiver;
|
|
OtaStoreRam<8192> destination;
|
|
receiver.begin(EXP_TARGET_ID, send, reply_route);
|
|
receiver.set_fetch_store(&destination);
|
|
assert(receiver.pull(EXP_MERKLE_ROOT, EXP_TARGET_ID) == OtaManager::PULL_STARTED);
|
|
for (uint32_t time = 100; time < 300000 && receiver.fetchState() != OtaManager::COMPLETE;
|
|
time += 100) {
|
|
context.manager.set_clock(time);
|
|
receiver.set_clock(time);
|
|
context.manager.serviceEgress();
|
|
receiver.serviceEgress();
|
|
if (time % 1000 == 0) { context.manager.loop(); receiver.loop(); }
|
|
while (!packets.empty()) {
|
|
Packet packet = packets.front();
|
|
packets.pop_front();
|
|
(packet.to_source ? context.manager : receiver).on_message(
|
|
packet.bytes.data(), packet.bytes.size());
|
|
}
|
|
}
|
|
assert(receiver.fetchState() == OtaManager::COMPLETE);
|
|
assert(destination.staged_size() == MOTA_VEC_LEN);
|
|
assert(memcmp(destination.data(), MOTA_VEC, MOTA_VEC_LEN) == 0);
|
|
}
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
#include "test_wifi_shared_queue.h"
|
|
#endif
|
|
|
|
int main() {
|
|
Queue q;
|
|
for (int i = 0; i < q.count; ++i) {
|
|
Frame& frame = q.buffer.at((q.head + i) % q.buffer.capacity());
|
|
frame.len = 176;
|
|
memset(frame.buf, i, sizeof frame.buf);
|
|
}
|
|
auto check_messages = [&]() {
|
|
for (int i = 0; i < q.count; ++i) {
|
|
const Frame& frame = q.buffer.at((q.head + i) % q.buffer.capacity());
|
|
assert(frame.len == 176);
|
|
for (auto byte : frame.buf) assert(byte == i);
|
|
}
|
|
};
|
|
char reply[160] = {};
|
|
assert(!ota_acquire_context(reply, sizeof reply));
|
|
assert(strstr(reply, "not ready"));
|
|
ota_set_context_storage(&q, Queue::acquire, Queue::release);
|
|
uint8_t identity[4] = {1, 2, 3, 4};
|
|
ota_begin_context(0, send, nullptr, "Heltec_t096", identity);
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
assert(ota_hop_limit() == OTA_HOP_LIMIT_DEFAULT);
|
|
assert(!ota_acquire_context(reply, sizeof reply));
|
|
assert(strstr(reply, "sync unread messages"));
|
|
assert(q.head == 191 && q.count == 129 && q.buffer.capacity() == 256);
|
|
check_messages();
|
|
--q.count;
|
|
|
|
for (unsigned cycle = 0; cycle < 8; ++cycle) {
|
|
assert(ota_acquire_context(reply, sizeof reply));
|
|
OtaContext& context = ota_ctx();
|
|
assert(q.head == 0 && q.buffer.capacity() == 128);
|
|
assert(ota_acquire_context(reply, sizeof reply) && &ota_ctx() == &context);
|
|
assert(strcmp(context.hw_id, "Heltec_t096") == 0);
|
|
check_messages();
|
|
if (cycle) {
|
|
assert(context.manager.max_hops() == 5);
|
|
assert(context.manager.checkpoint_blocks() == 6);
|
|
assert(context.manager.advert_mins() == 42);
|
|
assert(context.allow.count() == 1);
|
|
}
|
|
context.manager.set_max_hops(5);
|
|
context.manager.set_checkpoint_blocks(6);
|
|
context.manager.set_advert_mins(42);
|
|
uint8_t key[32] = {7};
|
|
context.allow.add(key);
|
|
|
|
Folder folder;
|
|
const auto link = cycle % 2 ? OtaContext::FOLDER_LINK_BLE
|
|
: OtaContext::FOLDER_LINK_SERIAL;
|
|
assert(context.attach_folder_source(&folder, link, "test", reply, sizeof reply));
|
|
assert(context.manager.servedCount() == 1);
|
|
ota_release_context_if_idle(false); // attached sources hold the workspace
|
|
assert(ota_context_if_active());
|
|
assert(!context.attach_folder_source(&folder,
|
|
cycle % 2 ? OtaContext::FOLDER_LINK_SERIAL : OtaContext::FOLDER_LINK_BLE,
|
|
"wrong owner", reply, sizeof reply));
|
|
assert(context.folderLink() == link);
|
|
|
|
transfer(context, &q);
|
|
check_messages();
|
|
context.detach_folder(); // same cleanup for USB/BLE stop and disconnect
|
|
context.manager.announce(); // callers may still use the context until loop boundary
|
|
packets.clear();
|
|
ota_release_context_if_idle(true); // return slots even if TempRadio is still up
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
assert(ota_hop_limit() == 5 && q.released == cycle + 1);
|
|
check_messages();
|
|
// Reuse every returned slot, so stale transfer state cannot survive.
|
|
for (unsigned i = 128; i < 256; ++i)
|
|
memset(&q.buffer.at(i), 0xCC, sizeof(Frame));
|
|
ota_release_context_if_idle(false);
|
|
assert(q.released == cycle + 1);
|
|
}
|
|
|
|
assert(ota_acquire_context(reply, sizeof reply));
|
|
ota_release_context_if_idle(true); // discovery holds storage during TempRadio
|
|
assert(ota_context_if_active());
|
|
ota_release_context_if_idle(false); // expiry / normalradio releases it
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
|
|
assert(ota_acquire_context(reply, sizeof reply));
|
|
assert(!ota_ctx().attach_folder_source(nullptr, OtaContext::FOLDER_LINK_SERIAL,
|
|
"failed start", reply, sizeof reply));
|
|
ota_release_context_if_idle(false);
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
check_messages();
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
test_wifi_shared_queue(q, check_messages);
|
|
#else
|
|
Nrf52BleMotaSourceControl ble;
|
|
assert(!ble.start(reply, sizeof reply)); // no subscription, no storage loan
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
for (int disconnect = 0; disconnect < 2; ++disconnect) {
|
|
bluetooth_interface.ready = true;
|
|
assert(ble.start(reply, sizeof reply));
|
|
assert(ble.status().attached && q.buffer.capacity() == 128);
|
|
assert(ble.status().offered == 1 && ble.status().advertised == 1);
|
|
if (disconnect) {
|
|
bluetooth_interface.ready = false;
|
|
ble.loop();
|
|
} else {
|
|
assert(ble.stop(reply, sizeof reply));
|
|
}
|
|
assert(!ble.status().attached && !bluetooth_interface.active);
|
|
ota_release_context_if_idle(true);
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
assert(!ble.status().attached);
|
|
assert(ble.stop(reply, sizeof reply)); // idle stop/status/poll never acquire
|
|
ble.loop();
|
|
assert(!ota_context_if_active());
|
|
check_messages();
|
|
packets.clear();
|
|
}
|
|
|
|
// BLE start/stop cannot detach a USB-owned source or return its workspace.
|
|
assert(ota_acquire_context(reply, sizeof reply));
|
|
Folder usb;
|
|
assert(ota_ctx().attach_folder_source(&usb, OtaContext::FOLDER_LINK_SERIAL,
|
|
"USB", reply, sizeof reply));
|
|
bluetooth_interface.ready = true;
|
|
assert(!ble.start(reply, sizeof reply));
|
|
assert(ble.status().another_link_active);
|
|
assert(ble.stop(reply, sizeof reply));
|
|
ota_release_context_if_idle(false);
|
|
assert(ota_ctx().folder_active && q.buffer.capacity() == 128);
|
|
ota_ctx().detach_folder();
|
|
ota_release_context_if_idle(true);
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
check_messages();
|
|
#endif
|
|
}
|