#include #include #include "NetworkLoggingMacro.h" #include #include #include uint32_t mock_millis = 0; RawSerial Serial; MockFicr mock_ficr; MockEthernet Ethernet; TwoWire Wire; MockCh390 CH390; std::string ethernet_output; std::string ethernet_input; bool accept_client = false; class MockUsb : public Stream { public: int capacity = 256; bool refuse_write = false; unsigned int tries = 0; std::string output; int available() override { return 0; } int read() override { return -1; } int peek() override { return -1; } void flush() override { assert(false && "blocking flush"); } int availableForWrite() override { return capacity; } size_t write(uint8_t) override { assert(false && "blocking write"); return 0; } size_t write(const uint8_t*, size_t) override { assert(false && "blocking write"); return 0; } }; static MockUsb usb; static bool logging_enabled = true; static bool debug_enabled = true; static size_t tryUsbWrite(void*, const uint8_t* data, size_t size) { ++usb.tries; if (usb.refuse_write || usb.capacity <= 0) return 0; const size_t written = size < static_cast(usb.capacity) ? size : static_cast(usb.capacity); usb.output.append(reinterpret_cast(data), written); return written; } static mesh::SingleAttemptNonBlockingStream logging_port(usb, tryUsbWrite); namespace mesh { bool isUsbLoggingEnabled() { return logging_enabled; } bool isUsbDebugEnabled() { return debug_enabled; } Stream& usbLoggingPort() { return logging_port; } } static void reset() { usb.output.clear(); usb.capacity = 256; usb.refuse_write = false; usb.tries = 0; logging_enabled = true; debug_enabled = true; ethernet_running = false; ethernet_client = EthernetClient(); ethernet_output.clear(); ethernet_input.clear(); accept_client = false; Ethernet = MockEthernet(); mock_millis = 0; } static void acceptClientAndReply() { accept_client = true; ethernet_loop_maintain(); assert(ethernet_take_session_reset()); assert(!ethernet_take_session_reset()); ethernet_input = "eth.status\r"; char command[80] = {}; assert(ethernet_read_line(command, sizeof(command))); assert(strcmp(command, "eth.status") == 0); char reply[80] = {}; assert(ethernet_handle_command(command, reply)); ethernet_send_reply(reply); assert(ethernet_output == "MeshCore CLI\r\n\r\n -> ETH: 192.168.1.42:23\r\n"); } int main() { (void)ðernet_start_task; // Runtime-off diagnostics must not attempt USB, while functional Ethernet // commands and the client banner still work. reset(); logging_enabled = false; ethernet_task(nullptr); acceptClientAndReply(); assert(usb.output.empty() && usb.tries == 0 && mock_millis == 0); // The master switch can remain on for packets while verbose ETH output is // disabled. Its functional banner and command reply still reach the client. reset(); debug_enabled = false; ethernet_task(nullptr); acceptClientAndReply(); assert(logging_enabled && usb.output.empty() && usb.tries == 0); reset(); ethernet_task(nullptr); acceptClientAndReply(); assert(usb.output.find("ETH: MAC: 02:92:1F:12:34:56\n") != std::string::npos); assert(usb.output.find("ETH: Listening on TCP port 23\n") != std::string::npos); assert(usb.output.find("ETH: Client connected from 192.168.1.42\n") != std::string::npos); assert(usb.tries == 6 && mock_millis == 0); // An open-but-stalled host has no TX space. No raw write or wait is allowed. reset(); usb.capacity = 0; ethernet_task(nullptr); acceptClientAndReply(); assert(usb.output.empty() && usb.tries == 0 && mock_millis == 0); // The host can also vanish after the capacity preflight. The real shared // single-attempt facade drops each diagnostic instead of retrying or waiting. reset(); usb.refuse_write = true; ethernet_task(nullptr); acceptClientAndReply(); assert(usb.output.empty() && usb.tries == 6 && mock_millis == 0); for (int failure : {0, 1, 2}) { reset(); Ethernet.failures = 1; if (failure == 0) Ethernet.hardware = EthernetNoHardware; if (failure == 1) Ethernet.link = LinkOFF; ethernet_task(nullptr); const char* expected = failure == 0 ? "Hardware not found, giving up" : failure == 1 ? "Cable not connected, will retry" : "DHCP failed, will retry"; assert(usb.output.find(expected) != std::string::npos); assert(mock_millis == (failure == 0 ? 0u : 30000u)); } reset(); int evaluated = 0; logging_enabled = false; NETWORK_DEBUG_PRINTLN("suppressed %d", ++evaluated); assert(evaluated == 0 && usb.tries == 0); logging_enabled = true; debug_enabled = false; NETWORK_DEBUG_PRINTLN("packet-only %d", ++evaluated); assert(evaluated == 0 && usb.tries == 0); debug_enabled = true; usb.capacity = 0; NETWORK_DEBUG_PRINTLN("stalled %d", ++evaluated); assert(evaluated == 0 && usb.tries == 0); usb.capacity = 256; NETWORK_DEBUG_PRINTLN("visible %d", ++evaluated); #if defined(MQTT_DEBUG) && MQTT_DEBUG assert(evaluated == 1 && usb.output == "MQTT: visible 1\n" && usb.tries == 1); usb.refuse_write = true; NETWORK_DEBUG_PRINTLN("disconnected %d", ++evaluated); assert(usb.tries == 2 && usb.output == "MQTT: visible 1\n"); #else assert(evaluated == 0 && usb.output.empty() && usb.tries == 0); #endif assert(mock_millis == 0); // Touch probe, late-presence and optional raw frames use the same runtime // logging gate as every other diagnostic, including protected boot mode. reset(); logging_enabled = false; Wire = TwoWire(); CHSC6XTouch found; assert(found.begin()); Wire.frame[0] = 0x1F; assert(!found.checkTap(500)); assert(found.checkTap(600)); assert(usb.output.empty() && usb.tries == 0); Wire = TwoWire(); Wire.present = false; CHSC6XTouch late; assert(!late.begin()); assert(Wire.probes == 1); // No diagnostic I2C scan when logging is off. Wire.present = true; Wire.frame[0] = 0x1F; assert(!late.checkTap(500)); assert(late.checkTap(600)); Wire.reply_length = 2; late.checkTap(700); assert(usb.output.empty() && usb.tries == 0); reset(); debug_enabled = false; Wire = TwoWire(); CHSC6XTouch packet_only; assert(packet_only.begin()); Wire.frame[0] = 0x1F; assert(!packet_only.checkTap(500)); assert(packet_only.checkTap(600)); assert(logging_enabled && usb.output.empty() && usb.tries == 0); Wire = TwoWire(); Wire.present = false; CHSC6XTouch quiet_scan; assert(!quiet_scan.begin() && Wire.probes == 1); reset(); Wire = TwoWire(); CHSC6XTouch visible; assert(visible.begin()); assert(usb.output == "Touch: CHSC6X found at 0x2E\n"); visible.checkTap(500); #if defined(DISPLAY_TOUCH_DEBUG) assert(usb.output.find("Touch: raw 00 01 02 03 04") != std::string::npos); #endif usb.output.clear(); usb.capacity = 0; CHSC6XTouch stalled; assert(stalled.begin()); stalled.checkTap(500); assert(usb.output.empty()); reset(); Wire = TwoWire(); Wire.present = false; Wire.all_addresses_present = false; CHSC6XTouch absent; assert(!absent.begin()); assert(usb.output.find("Touch: idle or absent at 0x2E; I2C bus currently answers:\n") == 0); assert(Wire.probes == 113); reset(); usb.capacity = 640; Wire = TwoWire(); Wire.all_addresses_present = true; Wire.fail_probe_count = 1; // Idle boot probe, then every scan address replies. CHSC6XTouch full_scan; assert(!full_scan.begin()); assert(usb.output.size() > 600 && usb.output.size() < 640 && usb.tries == 1); assert(usb.output.substr(usb.output.size() - 6) == " 0x77\n"); reset(); CH390 = MockCh390(); logging_enabled = false; assert(beginConfiguredCH390("invalid hostname")); assert(CH390.begins == 1 && usb.output.empty() && usb.tries == 0); logging_enabled = true; debug_enabled = false; assert(beginConfiguredCH390("invalid hostname")); assert(usb.output.empty() && usb.tries == 0); debug_enabled = true; assert(beginConfiguredCH390("invalid hostname")); assert(usb.output == "Network: invalid Ethernet hostname invalid hostname\n"); usb.output.clear(); usb.capacity = 0; assert(beginConfiguredCH390("invalid hostname")); assert(usb.output.empty()); std::cout << "PASS: gated USB diagnostics\n"; }