mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-14 06:36:28 +00:00
160 lines
6.2 KiB
C++
160 lines
6.2 KiB
C++
#pragma once
|
|
|
|
#include <WiFi.h>
|
|
#include <helpers/esp32/WiFiOtaSeeder.h>
|
|
#include <helpers/ota/FolderMotaStore.h>
|
|
#include <helpers/ota/MotaSeederProto.h>
|
|
#include <helpers/ota/OtaByteIO.h>
|
|
|
|
// Simulate only the TCP endpoint/host. The listener, stream framing, context,
|
|
// queue, mOTA manager, source reader and complete transferred bytes are real.
|
|
static std::shared_ptr<MockTcpState> connect_host() {
|
|
auto state = std::make_shared<MockTcpState>();
|
|
state->respond = [](const uint8_t* request, size_t length) {
|
|
assert(length >= 4 && request[0] == 'M' && request[1] == 'S');
|
|
uint8_t checksum = 0;
|
|
for (size_t i = 2; i < length; ++i) checksum ^= request[i];
|
|
assert(checksum == 0);
|
|
std::vector<uint8_t> payload;
|
|
const uint8_t op = request[2];
|
|
if (op == MS_OP_COUNT) {
|
|
payload.push_back(1);
|
|
} else if (op == MS_OP_DESCRIBE) {
|
|
assert(request[3] == 0);
|
|
MotaDesc desc;
|
|
Folder folder;
|
|
assert(folder.describe(0, desc));
|
|
payload.resize(MOTA_DESC_WIRE);
|
|
memcpy(payload.data(), desc.mid, 4);
|
|
wr_u32le(payload.data() + 4, desc.target_id);
|
|
wr_u32le(payload.data() + 8, desc.fw_version);
|
|
payload[12] = desc.codec_id;
|
|
payload[13] = desc.flags;
|
|
wr_u32le(payload.data() + 14, desc.total_size);
|
|
wr_u32le(payload.data() + 18, desc.leaves_off);
|
|
wr_u32le(payload.data() + 22, desc.block_count);
|
|
wr_u32le(payload.data() + 26, desc.payload_off);
|
|
wr_u32le(payload.data() + 30, desc.payload_size);
|
|
payload[34] = desc.block_size_log2;
|
|
} else {
|
|
assert(op == MS_OP_READ && length == 11 && request[3] == 0);
|
|
uint32_t offset = rd_u32le(request + 4);
|
|
uint16_t count = request[8] | (uint16_t(request[9]) << 8);
|
|
assert(uint64_t(offset) + count <= MOTA_VEC_LEN);
|
|
payload.assign(MOTA_VEC + offset, MOTA_VEC + offset + count);
|
|
}
|
|
std::vector<uint8_t> reply = {'m', 's', op, MS_STATUS_OK};
|
|
reply.insert(reply.end(), payload.begin(), payload.end());
|
|
checksum = 0;
|
|
for (uint8_t byte : reply) checksum ^= byte;
|
|
reply.push_back(checksum);
|
|
return reply;
|
|
};
|
|
WiFiServer::pending.emplace_back(state);
|
|
return state;
|
|
}
|
|
|
|
template<class CheckMessages>
|
|
static void test_wifi_shared_queue(Queue& q, CheckMessages check_messages) {
|
|
WiFiOtaSeeder::stop(); // stopping before startup never allocates
|
|
WiFiOtaSeeder::loop();
|
|
assert(!WiFiOtaSeeder::isListening() && !ota_context_if_active());
|
|
WiFi.connected = true;
|
|
for (int i = 0; i < 100; ++i) WiFiOtaSeeder::loop();
|
|
assert(WiFiOtaSeeder::isListening() && !ota_context_if_active());
|
|
assert(q.buffer.capacity() == 256);
|
|
|
|
q.count = 129;
|
|
Frame& extra = q.buffer.at((q.head + 128) % 256);
|
|
extra.len = 176;
|
|
memset(extra.buf, 128, sizeof extra.buf);
|
|
auto refused = connect_host();
|
|
WiFiOtaSeeder::loop();
|
|
assert(!refused->connected && refused->requests == 0);
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
assert(mesh::usbLoggingPort().output.find("sync unread messages") != std::string::npos);
|
|
check_messages();
|
|
--q.count;
|
|
|
|
for (int cycle = 0; cycle < 12; ++cycle) {
|
|
WiFi.connected = true;
|
|
auto host = connect_host();
|
|
WiFiOtaSeeder::loop();
|
|
assert(host->connected && host->no_delay && WiFiOtaSeeder::isAttached());
|
|
assert(q.buffer.capacity() == 128 && ota_ctx().folder_dest);
|
|
assert(ota_ctx().folderLink() == OtaContext::FOLDER_LINK_TCP);
|
|
assert(ota_ctx().manager.servedCount() == 1);
|
|
transfer(ota_ctx(), &q);
|
|
assert(host->requests > 2 && host->flushes == 0);
|
|
ota_release_context_if_idle(false);
|
|
assert(ota_context_if_active()); // a connected host owns the loan
|
|
check_messages();
|
|
switch (cycle % 4) {
|
|
case 0: // TCP disconnect while listener remains available
|
|
host->connected = false;
|
|
WiFiOtaSeeder::loop();
|
|
break;
|
|
case 1: // WiFi loss
|
|
WiFi.connected = false;
|
|
WiFiOtaSeeder::loop();
|
|
assert(!WiFiOtaSeeder::isListening());
|
|
break;
|
|
case 2: // CLI off can return the context before the listener sees it
|
|
ota_ctx().detach_folder();
|
|
ota_ctx().clear_folder_dest();
|
|
ota_release_context_if_idle(true);
|
|
assert(!ota_context_if_active());
|
|
WiFiOtaSeeder::loop();
|
|
break;
|
|
case 3: WiFiOtaSeeder::stop(); break;
|
|
}
|
|
ota_release_context_if_idle(true);
|
|
assert(!host->connected && !WiFiOtaSeeder::isAttached());
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
check_messages();
|
|
packets.clear();
|
|
}
|
|
|
|
// An incoming TCP client and listener stop must not detach a USB source.
|
|
char reply[160];
|
|
assert(ota_acquire_context(reply, sizeof reply));
|
|
Folder usb;
|
|
assert(ota_ctx().attach_folder_source(&usb, OtaContext::FOLDER_LINK_SERIAL,
|
|
"USB", reply, sizeof reply));
|
|
WiFi.connected = true;
|
|
auto conflict = connect_host();
|
|
WiFiOtaSeeder::loop();
|
|
assert(!conflict->connected && !WiFiOtaSeeder::isAttached());
|
|
WiFiOtaSeeder::stop();
|
|
ota_release_context_if_idle(false);
|
|
assert(ota_ctx().folderLink() == OtaContext::FOLDER_LINK_SERIAL);
|
|
ota_ctx().detach_folder();
|
|
ota_release_context_if_idle(true);
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
|
|
// TCP's stale attached flag must not clear a new USB capture destination.
|
|
auto old_host = connect_host();
|
|
WiFiOtaSeeder::loop();
|
|
assert(WiFiOtaSeeder::isAttached());
|
|
ota_ctx().detach_folder();
|
|
ota_ctx().clear_folder_dest();
|
|
ota_release_context_if_idle(true);
|
|
assert(ota_acquire_context(reply, sizeof reply));
|
|
assert(ota_ctx().attach_folder_source(&usb, OtaContext::FOLDER_LINK_SERIAL,
|
|
"USB", reply, sizeof reply));
|
|
WiFiClient usb_stream;
|
|
FolderMotaStore usb_dest(usb_stream, MotaStreamWritePolicy::NoFlush);
|
|
ota_ctx().set_folder_dest(&usb_dest, "USB");
|
|
WiFiOtaSeeder::loop();
|
|
assert(!old_host->connected && !WiFiOtaSeeder::isAttached());
|
|
assert(ota_ctx().folder_dest == &usb_dest);
|
|
assert(ota_ctx().folderLink() == OtaContext::FOLDER_LINK_SERIAL);
|
|
ota_ctx().detach_folder();
|
|
ota_ctx().clear_folder_dest();
|
|
ota_release_context_if_idle(true);
|
|
WiFiOtaSeeder::stop();
|
|
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
|
|
check_messages();
|
|
packets.clear();
|
|
}
|