Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/desktop_peer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");

// ════════════════════════════════════════════════════════════
Expand Down
3 changes: 2 additions & 1 deletion examples/diag_scanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <thread>
#include <chrono>
#include <csignal>
#include <deque>
#include <vector>

// ─── Configuration ──────────────────────────────────────────────
Expand Down Expand Up @@ -75,7 +76,7 @@ struct DiscoveredDevice {
DeviceCounters counters;
};

static std::vector<DiscoveredDevice> devices;
static std::deque<DiscoveredDevice> 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
Expand Down
67 changes: 33 additions & 34 deletions include/sero/runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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).
Expand Down Expand Up @@ -727,6 +697,35 @@ class Runtime {

// ── Message building ────────────────────────────────────────

/// Internal: allocate a request ID, build, and send to a known target.
std::optional<uint32_t> 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,
Expand Down
49 changes: 49 additions & 0 deletions test/test_runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool*>(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<uint8_t>(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<std::pair<bool*, ReturnCode*>*>(ctx);
*pair->first = true;
*pair->second = rc;
};

std::pair<bool*, ReturnCode*> 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) {
Expand Down