From 96f429d8fb2d088b15c84cf2f3df3aa41060235c Mon Sep 17 00:00:00 2001 From: mad201802 Date: Tue, 17 Mar 2026 21:32:33 +0100 Subject: [PATCH 1/2] feat: add app registration handling in subscription management and test for reconnect behavior after retry exhaustion --- include/sero/service/service_discovery.hpp | 32 ++++++++++------- test/test_service_discovery.cpp | 40 ++++++++++++++++++++++ 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/include/sero/service/service_discovery.hpp b/include/sero/service/service_discovery.hpp index 7767416..9e5421c 100644 --- a/include/sero/service/service_discovery.hpp +++ b/include/sero/service/service_discovery.hpp @@ -216,6 +216,7 @@ class ServiceDiscovery { st->requested_ttl = ttl_seconds; st->granted_ttl = 0; st->active = true; + st->app_registered = true; st->needs_send = true; st->last_sent_ms = now_ms; return true; @@ -225,6 +226,7 @@ class ServiceDiscovery { bool unsubscribe_event(uint16_t service_id, uint16_t event_id) { SubTracker* st = find_sub_tracker(service_id, event_id); if (!st) return false; + st->app_registered = false; // app no longer wants this — never reactivate on reconnect st->needs_unsubscribe = true; return true; } @@ -570,15 +572,16 @@ class ServiceDiscovery { }; struct SubTracker { - uint16_t service_id = 0; - uint16_t event_id = 0; - uint16_t requested_ttl = 0; - uint16_t granted_ttl = 0; - bool active = false; - bool needs_send = false; + uint16_t service_id = 0; + uint16_t event_id = 0; + uint16_t requested_ttl = 0; + uint16_t granted_ttl = 0; + bool active = false; + bool app_registered = false; ///< True while the app wants this subscription. + bool needs_send = false; bool needs_unsubscribe = false; - uint32_t last_sent_ms = 0; - uint8_t ack_retry_count = 0; ///< Retries when no ACK received. + uint32_t last_sent_ms = 0; + uint8_t ack_retry_count = 0; ///< Retries when no ACK received. }; std::array providers_{}; @@ -661,14 +664,17 @@ class ServiceDiscovery { // ── Resubscription on reboot detection (§4.7) ──────────────── - /// Flag all active subscriptions for a service as needing re-send. + /// Flag all app-registered subscriptions for a service as needing re-send. + /// Revives trackers whose retries were exhausted while the provider was away. void flag_resubscribe(uint16_t service_id, uint32_t now_ms) { for (std::size_t i = 0; i < sub_tracker_count_; ++i) { - if (sub_trackers_[i].active && + if (sub_trackers_[i].app_registered && sub_trackers_[i].service_id == service_id) { - sub_trackers_[i].needs_send = true; - sub_trackers_[i].granted_ttl = 0; - sub_trackers_[i].last_sent_ms = now_ms; + sub_trackers_[i].active = true; + sub_trackers_[i].ack_retry_count = 0; + sub_trackers_[i].needs_send = true; + sub_trackers_[i].granted_ttl = 0; + sub_trackers_[i].last_sent_ms = now_ms; } } } diff --git a/test/test_service_discovery.cpp b/test/test_service_discovery.cpp index 5d977d2..c89c978 100644 --- a/test/test_service_discovery.cpp +++ b/test/test_service_discovery.cpp @@ -442,6 +442,46 @@ TEST(ServiceDiscovery_UnsubscribeEvent, NotSubscribed_ReturnsFalse) { EXPECT_FALSE(sd.unsubscribe_event(0x1000, 0x8001)); } +// ── Reconnect after retry exhaustion ─────────────────────────── + +/// Regression test: if ACK retries are exhausted while the provider is away +/// (inactive ≈ 30 s gap), a fresh SD_OFFER from the same service must +/// reactivate the subscription and trigger a new SD_SUBSCRIBE_EVENT send. +TEST(ServiceDiscovery_SubscribeEvent, RetryExhausted_ReactivatesOnReconnect) { + SD sd; + sd.set_client_id(0x0001); + SdCallbackState cb_state; + setup_callbacks(sd, cb_state); + + sd.find(0x1000, 1, 0); + Addr provider = make_addr(42); + sd.handle_offer(0x1000, 1, 30, 0x0001, provider, 0); + sd.subscribe_event(0x1000, 0x8001, 30, 0); + + // Exhaust all retries without receiving any ACK. + // Retry fires at t = SubscriptionTtlSeconds, 2×, 3× (3rd = exhaustion). + constexpr uint32_t ttl_ms = static_cast(SmallConfig::SubscriptionTtlSeconds) * 1000u; + UnicastCapture uc; + uint32_t t = 0; + for (int retry = 0; retry <= 3; ++retry) { + t += ttl_ms; + sd.process_subscription_renewals(t, uc.fn()); + } + // Subscription should now be inactive (retries exhausted). + uc.count = 0; + sd.process_subscription_renewals(t + ttl_ms, uc.fn()); + EXPECT_EQ(uc.count, 0) << "No send expected while subscription is inactive"; + + // Provider comes back — new session_id simulates reboot / reconnect. + UnicastCapture resubscribe_uc; + t += ttl_ms; + sd.handle_offer(0x1000, 1, 30, 0x0002, provider, t); + + // The next process cycle must send SD_SUBSCRIBE_EVENT. + sd.process_subscription_renewals(t, resubscribe_uc.fn()); + EXPECT_GE(resubscribe_uc.count, 1) << "Expected SD_SUBSCRIBE_EVENT after provider reconnect"; +} + // ── Provider address change ───────────────────────────────────── TEST(ServiceDiscovery_HandleOffer, ProviderAddressChange_RenotifiesFound) { From 5d451a84247b2381013c5bc7cf46315d842c6f71 Mon Sep 17 00:00:00 2001 From: mad201802 Date: Tue, 17 Mar 2026 21:38:59 +0100 Subject: [PATCH 2/2] fix: update version to 0.2.2 in CMakeLists.txt and package.json; change server IP address in example server --- CMakeLists.txt | 2 +- examples/server.cpp | 2 +- node-module/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f27f5c2..5e00905 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) project(sero - VERSION 0.2.1 + VERSION 0.2.2 DESCRIPTION "Sero: Embedded-first realtime service-oriented communication" LANGUAGES CXX ) diff --git a/examples/server.cpp b/examples/server.cpp index ec30819..8edc9f0 100644 --- a/examples/server.cpp +++ b/examples/server.cpp @@ -83,7 +83,7 @@ class Calculator : public sero::IService { int main() { example::UdpTransport transport; - if (!transport.init("127.0.0.1", SERVER_PORT)) return 1; + if (!transport.init("192.168.1.132", SERVER_PORT)) return 1; Runtime rt(transport, MY_CLIENT_ID); rt.set_local_address(transport.local_addr()); diff --git a/node-module/package.json b/node-module/package.json index 87a8893..f29a43e 100644 --- a/node-module/package.json +++ b/node-module/package.json @@ -1,6 +1,6 @@ { "name": "@asl-gokart/sero-node", - "version": "0.2.1", + "version": "0.2.2", "description": "sero Node.js bindings", "main": "index.js",