From 1c83c32397c90c17767cc52fd3b45e501781fad4 Mon Sep 17 00:00:00 2001 From: Ivan FB <128452529+Ivansete-status@users.noreply.github.com> Date: Thu, 16 Jul 2026 18:38:05 +0200 Subject: [PATCH] feat(channels): add channelExists to reliable channels API (#4045) --- library/channels_api/channel_api.nim | 15 +++++++ library/liblogosdelivery.h | 7 ++++ .../api/reliable_channel_manager_api.nim | 1 + .../channels/api/channel_lifecycle.nim | 6 +++ tests/channels/test_all.nim | 1 + tests/channels/test_channel_lifecycle.nim | 39 +++++++++++++++++++ 6 files changed, 69 insertions(+) create mode 100644 tests/channels/test_channel_lifecycle.nim diff --git a/library/channels_api/channel_api.nim b/library/channels_api/channel_api.nim index c9b96312f..6468804d7 100644 --- a/library/channels_api/channel_api.nim +++ b/library/channels_api/channel_api.nim @@ -30,6 +30,21 @@ proc logosdelivery_channel_create( return ok(string(id)) +proc logosdelivery_channel_exists( + ctx: ptr FFIContext[LogosDelivery], + callback: FFICallBack, + userData: pointer, + channelIdStr: cstring, +) {.ffi.} = + ## Returns `"true"` or `"false"`; a missing channel is not an error. + requireInitializedNode(ctx, "ChannelExists"): + return err(errMsg) + + requireChannels(ctx, "ChannelExists"): + return err(errMsg) + + return ok($ctx.myLib[].reliableChannelManager.channelExists(ChannelId($channelIdStr))) + proc logosdelivery_channel_send( ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, diff --git a/library/liblogosdelivery.h b/library/liblogosdelivery.h index 21d8890d2..f06ef4ae5 100644 --- a/library/liblogosdelivery.h +++ b/library/liblogosdelivery.h @@ -86,6 +86,13 @@ extern "C" const char *contentTopic, const char *senderId); + // Check whether a reliable channel is currently open. Returns "true" or + // "false"; an unknown channel id is not an error. + int logosdelivery_channel_exists(void *ctx, + FFICallBack callback, + void *userData, + const char *channelId); + // Send a message on a reliable channel. // messageJson: { "payload": "base64-encoded-payload", "ephemeral": false } // Returns a request ID that can be used to track delivery. diff --git a/logos_delivery/api/reliable_channel_manager_api.nim b/logos_delivery/api/reliable_channel_manager_api.nim index 8d3f63ff3..a43670665 100644 --- a/logos_delivery/api/reliable_channel_manager_api.nim +++ b/logos_delivery/api/reliable_channel_manager_api.nim @@ -10,6 +10,7 @@ type ReliableChannelApi* = concept c createReliableChannel( c, channelId = ChannelId, contentTopic = ContentTopic, senderId = SdsParticipantID ) is Result[ChannelId, string] + channelExists(c, channelId = ChannelId) is bool closeChannel(c, channelId = ChannelId) is Future[Result[void, string]] send(c, channelId = ChannelId, appPayload = seq[byte]) is Future[Result[RequestId, string]] diff --git a/logos_delivery/channels/api/channel_lifecycle.nim b/logos_delivery/channels/api/channel_lifecycle.nim index d110ceb72..bacce83d8 100644 --- a/logos_delivery/channels/api/channel_lifecycle.nim +++ b/logos_delivery/channels/api/channel_lifecycle.nim @@ -74,6 +74,12 @@ proc createReliableChannel*( self.channels[channelId] = chn return ok(channelId) +proc channelExists*(self: ReliableChannelManager, channelId: ChannelId): bool = + ## True while the channel is held by the manager, i.e. between a successful + ## `createReliableChannel` and `closeChannel`. Persisted SDS state for a + ## closed channel does not count as existing. + return self.channels.hasKey(channelId) + proc closeChannel*( self: ReliableChannelManager, channelId: ChannelId ): Future[Result[void, string]] {.async: (raises: []).} = diff --git a/tests/channels/test_all.nim b/tests/channels/test_all.nim index 04b448707..0c0855278 100644 --- a/tests/channels/test_all.nim +++ b/tests/channels/test_all.nim @@ -1,3 +1,4 @@ {.used.} +import ./test_channel_lifecycle import ./test_reliable_channel_send_receive diff --git a/tests/channels/test_channel_lifecycle.nim b/tests/channels/test_channel_lifecycle.nim new file mode 100644 index 000000000..c5cf810cf --- /dev/null +++ b/tests/channels/test_channel_lifecycle.nim @@ -0,0 +1,39 @@ +{.used.} + +import chronos, testutils/unittests +import brokers/broker_context + +import ../testlib/[common, testasync] + +import logos_delivery/api/conf/channels_conf +import logos_delivery/channels/reliable_channel_manager +import logos_delivery/channels/api/channel_lifecycle +import logos_delivery/channels/encryption/noop_encryption + +suite "Reliable Channel - lifecycle": + asyncTest "channelExists tracks create and close": + const + channelId = ChannelId("lifecycle-channel") + contentTopic = ContentTopic("/reliable-channel/test/proto") + + var manager: ReliableChannelManager + lockNewGlobalBrokerContext: + manager = ReliableChannelManager.new(ReliableChannelManagerConf()).expect( + "ReliableChannelManager.new" + ) + setNoopEncryption() + + check not manager.channelExists(channelId) + + discard manager + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) + .expect("createReliableChannel") + + check manager.channelExists(channelId) + + ## An unrelated id must not be reported as existing. + check not manager.channelExists(ChannelId("other-channel")) + + (await manager.closeChannel(channelId)).expect("closeChannel") + + check not manager.channelExists(channelId)