diff --git a/examples/desktop_peer.cpp b/examples/desktop_peer.cpp index f48c2a9..937a711 100644 --- a/examples/desktop_peer.cpp +++ b/examples/desktop_peer.cpp @@ -264,7 +264,9 @@ int main(int argc, char* argv[]) { app.last_tick_ms = now; app.last_diag_ms = now; - rt.enable_diagnostics(now); + if (!rt.enable_diagnostics(now)) { + std::fprintf(stderr, "[main] Failed to enable diagnostics; diagnostics will be unavailable.\n"); + } std::printf("\n[main] Entering main loop (Ctrl+C to quit)\n\n"); // ════════════════════════════════════════════════════════════ diff --git a/examples/diag_scanner.cpp b/examples/diag_scanner.cpp index 756ea9d..3a427cb 100644 --- a/examples/diag_scanner.cpp +++ b/examples/diag_scanner.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include // ─── Configuration ────────────────────────────────────────────── @@ -75,7 +76,7 @@ struct DiscoveredDevice { DeviceCounters counters; }; -static std::vector devices; +static std::deque devices; static Runtime* g_rt = nullptr; static volatile std::sig_atomic_t running = 1; static uint32_t g_last_scan_ms = 0; // last time queries were issued diff --git a/include/sero/runtime.hpp b/include/sero/runtime.hpp index 69662cc..708bd45 100644 --- a/include/sero/runtime.hpp +++ b/include/sero/runtime.hpp @@ -154,24 +154,8 @@ class Runtime { if (!sd_.get_provider_address(service_id, provider)) { return std::nullopt; // service not found } - - auto rid = request_tracker_.allocate(service_id, method_id, - timeout_ms, now_ms, - callback, user_ctx); - if (!rid) return std::nullopt; // table full - - bool auth = should_auth_outgoing(provider); - MessageHeader hdr = make_request_header(service_id, method_id, - payload_length, *rid, auth); - hdr.sequence_counter = e2e_.next_sequence(); - std::size_t total = build_message(hdr, payload, payload_length, auth, provider); - if (!transport_.send(provider, tx_buffer_, total)) { - // Send failed — cancel the pending request - request_tracker_.complete(*rid, ReturnCode::E_NOT_OK, nullptr, 0); - diag_.increment(DiagnosticCounter::DroppedMessages); - return std::nullopt; - } - return rid; + return request_impl(provider, service_id, method_id, payload, payload_length, + callback, user_ctx, timeout_ms, now_ms); } /// Send a REQUEST to an explicit target address, bypassing SD lookup. @@ -187,22 +171,8 @@ class Runtime { uint32_t timeout_ms, uint32_t now_ms) { - auto rid = request_tracker_.allocate(service_id, method_id, - timeout_ms, now_ms, - callback, user_ctx); - if (!rid) return std::nullopt; // table full - - bool auth = should_auth_outgoing(target); - MessageHeader hdr = make_request_header(service_id, method_id, - payload_length, *rid, auth); - hdr.sequence_counter = e2e_.next_sequence(); - std::size_t total = build_message(hdr, payload, payload_length, auth, target); - if (!transport_.send(target, tx_buffer_, total)) { - request_tracker_.complete(*rid, ReturnCode::E_NOT_OK, nullptr, 0); - diag_.increment(DiagnosticCounter::DroppedMessages); - return std::nullopt; - } - return rid; + return request_impl(target, service_id, method_id, payload, payload_length, + callback, user_ctx, timeout_ms, now_ms); } /// Fire-and-forget REQUEST_NO_RETURN (§5.2). @@ -727,6 +697,35 @@ class Runtime { // ── Message building ──────────────────────────────────────── + /// Internal: allocate a request ID, build, and send to a known target. + std::optional request_impl(const Addr& target, + uint16_t service_id, + uint16_t method_id, + const uint8_t* payload, + std::size_t payload_length, + RequestCallback callback, + void* user_ctx, + uint32_t timeout_ms, + uint32_t now_ms) + { + auto rid = request_tracker_.allocate(service_id, method_id, + timeout_ms, now_ms, + callback, user_ctx); + if (!rid) return std::nullopt; // table full + + bool auth = should_auth_outgoing(target); + MessageHeader hdr = make_request_header(service_id, method_id, + payload_length, *rid, auth); + hdr.sequence_counter = e2e_.next_sequence(); + std::size_t total = build_message(hdr, payload, payload_length, auth, target); + if (!transport_.send(target, tx_buffer_, total)) { + request_tracker_.complete(*rid, ReturnCode::E_NOT_OK, nullptr, 0); + diag_.increment(DiagnosticCounter::DroppedMessages); + return std::nullopt; + } + return rid; + } + /// Build a complete message in tx_buffer_. Returns total byte count. std::size_t build_message(const MessageHeader& hdr, const uint8_t* payload, diff --git a/test/test_runtime.cpp b/test/test_runtime.cpp index 188d3c4..273f936 100644 --- a/test/test_runtime.cpp +++ b/test/test_runtime.cpp @@ -349,6 +349,55 @@ TEST_F(RuntimeTest, ClientRequest_ServiceFound_SendsAndTracksRequest) { EXPECT_TRUE(callback_called); } +// ── Client-side Explicit-Target Request ───────────────────────── + +TEST_F(RuntimeTest, ClientRequest_ExplicitTarget_SendsWithoutSD) { + // SD has no provider registered — explicit-target overload must still send + Addr target = make_addr(42); + + bool callback_called = false; + auto cb = [](ReturnCode, const uint8_t*, std::size_t, void* ctx) { + *static_cast(ctx) = true; + }; + + auto rid = rt->request(target, 0x1000, 0x0001, nullptr, 0, + cb, &callback_called, 500, 0); + ASSERT_TRUE(rid.has_value()); + ASSERT_EQ(transport.sent.size(), 1u); + + // Verify the message was sent to the explicit target + EXPECT_EQ(transport.sent[0].destination, target); + + MessageHeader sent_hdr = MessageHeader::deserialize(transport.sent[0].data.data()); + EXPECT_EQ(sent_hdr.message_type, static_cast(MessageType::REQUEST)); + EXPECT_EQ(sent_hdr.service_id, 0x1000); + EXPECT_EQ(sent_hdr.method_event_id, 0x0001); +} + +TEST_F(RuntimeTest, ClientRequest_ExplicitTarget_ResponseCompletesCallback) { + Addr target = make_addr(42); + + bool callback_called = false; + ReturnCode received_rc = ReturnCode::E_NOT_OK; + auto cb = [](ReturnCode rc, const uint8_t*, std::size_t, void* ctx) { + auto* pair = static_cast*>(ctx); + *pair->first = true; + *pair->second = rc; + }; + + std::pair ctx{&callback_called, &received_rc}; + auto rid = rt->request(target, 0x1000, 0x0001, nullptr, 0, + cb, &ctx, 500, 100); + ASSERT_TRUE(rid.has_value()); + + // Inject a RESPONSE for this request_id from the explicit target + MessageHeader resp_hdr = make_response(0x1000, 0x0001, 0, *rid); + inject_and_process(resp_hdr, nullptr, 0, target, 200); + + EXPECT_TRUE(callback_called); + EXPECT_EQ(received_rc, ReturnCode::E_OK); +} + // ── Client-side Fire-and-Forget ───────────────────────────────── TEST_F(RuntimeTest, ClientFireAndForget_ServiceFound_Sends) {