diff --git a/AGENTS.md b/AGENTS.md index 6b89e4c3..8e8f164e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,7 @@ - There is a `ManualTaskScheduler` with methods to define a single `Task` and `DelayedTask` with duration after which the task will be executed. - To access this scheduler use `ae_context.scheduler()` method. - Tasks should be lightweight as possible because it stored in fixed-size object pool. -- To control task lifetime use `TaskSubscription` a RAII object which automatically resets task if subscribers die. +- To control task lifetime use `TaskSubscription` a RAII object which automatically resets task if subscribers die. This guarantees task would not be invoked if subscription dead. - In rare cases class can guarantee its lifetime, subscription may be omitted. ### Stdexec diff --git a/aether/CMakeLists.txt b/aether/CMakeLists.txt index 12f5d9b0..be442990 100644 --- a/aether/CMakeLists.txt +++ b/aether/CMakeLists.txt @@ -65,7 +65,6 @@ list(APPEND aether_srcs ) list(APPEND aether_srcs - "ae_actions/get_client_cloud.cpp" "ae_actions/get_servers.cpp" "ae_actions/ping.cpp" "ae_actions/check_access_for_send_message.cpp" @@ -208,6 +207,7 @@ list(APPEND aether_srcs list(APPEND aether_srcs "connection_manager/client_connection_manager.cpp" + "connection_manager/get_cloud_aether.cpp" "connection_manager/client_cloud_manager.cpp" "connection_manager/server_connection_manager.cpp") diff --git a/aether/ae_actions/get_client_cloud.cpp b/aether/ae_actions/get_client_cloud.cpp deleted file mode 100644 index bdf7c955..00000000 --- a/aether/ae_actions/get_client_cloud.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/ae_actions/get_client_cloud.h" - -#include "aether/ae_actions/ae_actions_tele.h" - -namespace ae { - -GetClientCloudAction::GetClientCloudAction( - AeContext const& ae_context, Uid client_uid, - CloudServerConnections& cloud_connection, - RequestPolicy::Variant request_policy) - : client_uid_{client_uid}, - cloud_request_{ - ae_context, - AuthApiCaller{[this](ApiContext& auth_api, auto*) { - AE_TELED_DEBUG("Resolve cloud for {}", client_uid_); - auth_api->resolver_clouds({client_uid_}); - }}, - ClientResponseListener{[this](ClientApiSafe& client_api, auto*, - auto* request) { - return client_api.send_cloud_event().Subscribe( - [this, request](auto const& uid, auto const& cloud_descriptor) { - if (uid == client_uid_) { - cloud_ = cloud_descriptor.sids; - request->Succeeded(); - } - }); - }}, - cloud_connection, - request_policy, - } { - AE_TELE_INFO(kGetClientCloud, "GetClientCloudAction created"); - cloud_request_subs_ += cloud_request_.success_event().Subscribe([this]() { - AE_TELED_DEBUG("Cloud resolved as [{}]", cloud_); - result_event_.Emit(Ok const&>{cloud_}); - Finish(); - }); - cloud_request_subs_ += cloud_request_.failure_event().Subscribe([this]() { - AE_TELED_ERROR("Cloud resolution failed"); - result_event_.Emit(Error{1}); - Finish(); - }); -} - -GetClientCloudAction::ResultEvent::Subscriber -GetClientCloudAction::result_event() { - return EventSubscriber{result_event_}; -} - -} // namespace ae diff --git a/aether/all.h b/aether/all.h index 264735f5..7c39e9d9 100644 --- a/aether/all.h +++ b/aether/all.h @@ -92,7 +92,6 @@ #include "aether/ae_actions/telemetry.h" #include "aether/ae_actions/get_servers.h" #include "aether/ae_actions/select_client.h" -#include "aether/ae_actions/get_client_cloud.h" #include "aether/ae_actions/check_access_for_send_message.h" #include "aether/modems/imodem_driver.h" diff --git a/aether/cloud_connections/cloud_request.cpp b/aether/cloud_connections/cloud_request.cpp index 7e94a3c8..4f658339 100644 --- a/aether/cloud_connections/cloud_request.cpp +++ b/aether/cloud_connections/cloud_request.cpp @@ -133,7 +133,7 @@ void CloudRequestAction::Succeeded() { void CloudRequestAction::Failed() { Finish(); - success_event_.Emit(); + failure_event_.Emit(); } CloudRequestAction::SuccessEvent::Subscriber diff --git a/aether/cloud_connections/cloud_server_connections.cpp b/aether/cloud_connections/cloud_server_connections.cpp index 5312d4b9..7734a2c5 100644 --- a/aether/cloud_connections/cloud_server_connections.cpp +++ b/aether/cloud_connections/cloud_server_connections.cpp @@ -147,7 +147,7 @@ void CloudServerConnections::SubscribeToServerState( void CloudServerConnections::ReselectServers() { // reselct servers on next cycle - ae_context_.scheduler().Task([&]() { InitServers(); }); + defer_sub_ = ae_context_.scheduler().Task([&]() { InitServers(); }); } void CloudServerConnections::UnselectServer( @@ -168,6 +168,7 @@ void CloudServerConnections::QuarantineTimer( server_connection.server()->server_id); static constexpr Duration kQuarantineDuration = std::chrono::milliseconds{AE_CLOUD_SERVER_QUARANTINE_TIME_MS}; + // TODO: add task subscription here ae_context_.scheduler().DelayedTask( [this, sc{&server_connection}]() { AE_TELED_DEBUG("Release from quarantine server {}", diff --git a/aether/cloud_connections/cloud_server_connections.h b/aether/cloud_connections/cloud_server_connections.h index 0f4c5cd4..13fb873c 100644 --- a/aether/cloud_connections/cloud_server_connections.h +++ b/aether/cloud_connections/cloud_server_connections.h @@ -72,6 +72,7 @@ class CloudServerConnections { ServersUpdate servers_update_event_; std::map server_state_subs_; + TaskSubscription defer_sub_; }; } // namespace ae diff --git a/aether/connection_manager/client_cloud_manager.cpp b/aether/connection_manager/client_cloud_manager.cpp index 2e2d0e20..c21f4e2a 100644 --- a/aether/connection_manager/client_cloud_manager.cpp +++ b/aether/connection_manager/client_cloud_manager.cpp @@ -24,9 +24,6 @@ #include "aether/client.h" #include "aether/work_cloud.h" -#include "aether/work_cloud_api/server_descriptor.h" -#include "aether/cloud_connections/cloud_server_connections.h" - #include "aether/tele/tele.h" namespace ae { @@ -45,165 +42,270 @@ GetCloudFromCache::result_event() noexcept { return EventSubscriber{result_event_}; } -GetCloudFromAether::GetCloudFromAether(AeContext const& ae_context, - Aether& aether, - ClientCloudManager& client_cloud_manager, - CloudServerConnections& cloud_connection, - Uid const& client_uid) - : ae_context_{ae_context}, - aether_{&aether}, - client_cloud_manager_{&client_cloud_manager}, - cloud_connection_{&cloud_connection}, - client_uid_{client_uid} { - RequestCloud(); -} - -GetCloudFromAether::ResultEvent::Subscriber -GetCloudFromAether::result_event() noexcept { - return EventSubscriber{result_event_}; -} - -void GetCloudFromAether::RequestCloud() { - AE_TELED_DEBUG("RequestCloud"); - - get_client_cloud_action_.emplace( - ae_context_, client_uid_, *cloud_connection_, - RequestPolicy::Replica{cloud_connection_->count_connections()}); +void BuildNewServers(Aether::ptr const& aether, + std::vector& servers, + std::vector const& server_descriptors) { + auto const& a = aether.Load(); + assert(a && "Aether did not loaded"); - get_client_cloud_sub_ = get_client_cloud_action_->result_event().Subscribe( - [this](auto const& res) { - if (res) { - auto const& resolved = res.value(); - if (!resolved.empty()) { - BuildServers(resolved); - } else { - Failed(); - } - } else { - Failed(); - } - }); -} - -void GetCloudFromAether::BuildServers(std::vector cloud) { - cloud_sids_ = std::move(cloud); - // find servers in cache and make request servers for missing - std::vector missing_servers; - for (auto const& sid : cloud_sids_) { - auto server = aether_->GetServer(sid); - if (server.is_valid()) { - servers_.emplace_back(std::move(server)); - } else { - missing_servers.push_back(sid); + for (auto const& sd : server_descriptors) { + // check if server exits first + if (auto s = a->GetServer(sd.server_id); s.is_valid()) { + servers.emplace_back(std::move(s)); + continue; } - } - if (!missing_servers.empty()) { - // If there are missing servers, resolve them from the cloud - get_servers_action_.emplace( - ae_context_, missing_servers, *cloud_connection_, - RequestPolicy::Replica{cloud_connection_->count_connections()}); - get_servers_sub_ = get_servers_action_->result_event().Subscribe( - [this](auto const& result) mutable { - if (result) { - auto new_servers = BuildNewServers(result.value()); - // extend the existing servers with the new ones - servers_.insert(std::end(servers_), std::begin(new_servers), - std::end(new_servers)); - // sort servers by cloud order - std::sort(std::begin(servers_), std::end(servers_), - [&](auto const& left, auto const& right) { - auto left_order = std::find(std::begin(cloud_sids_), - std::end(cloud_sids_), - left.Load()->server_id); - auto right_order = std::find(std::begin(cloud_sids_), - std::end(cloud_sids_), - right.Load()->server_id); - return left_order < right_order; - }); - - RegisterCloud(std::move(servers_)); - } else { - Failed(); - } - }); - } else { - // all servers already known to aether, just build a new cloud - RegisterCloud(std::move(servers_)); - } -} -std::vector GetCloudFromAether::BuildNewServers( - std::vector const& descriptors) { - std::vector servers; - for (auto const& sd : descriptors) { std::vector endpoints; for (auto const& ip : sd.ips) { for (auto const& pp : ip.protocol_and_ports) { endpoints.emplace_back(Endpoint{{ip.ip, pp.port}, pp.protocol}); } } - auto s = Server::ptr::Create(aether_->domain, sd.server_id, endpoints, - aether_->adapter_registry); - aether_->StoreServer(s); + + AE_TELED_DEBUG("Make new server id:{}, endpoints:{}", sd.server_id, + endpoints); + + auto s = Server::ptr::Create(aether.domain(), sd.server_id, endpoints, + a->adapter_registry); + aether->StoreServer(s); servers.emplace_back(std::move(s)); } - return servers; } -void GetCloudFromAether::RegisterCloud(std::vector servers) { - auto cloud = - client_cloud_manager_->RegisterCloud(client_uid_, std::move(servers)); - result_event_.Emit(Ok{std::move(cloud)}); - Finish(); +auto SplitMissingLoaded(Aether::ptr const& aether, + std::vector const& sids) { + return ex::create, + std::vector)>( + [aether_ = aether, sids](auto& ctx) noexcept { + // get servers from aether cache and make missing server list + auto const& a = aether_.Load(); + assert(a && "Aether did not loaded"); + + std::vector servers; + servers.reserve(sids.size()); + std::vector missing_servers; + missing_servers.reserve(sids.size()); + + for (auto const& sid : sids) { + auto server = a->GetServer(sid); + if (server.is_valid()) { + servers.emplace_back(std::move(server)); + } else { + missing_servers.emplace_back(sid); + } + } + ex::set_value(std::move(ctx.receiver), std::move(servers), + std::move(missing_servers)); + }); +} + +auto LoadMissing(Aether::ptr const& aether, Client::ptr const& client, + ClientCloudManager::GetServersPool& get_servers_pool) { + return ex::let_value([aether, client, &get_servers_pool]( + auto& servers, auto& missing) noexcept { + return ex::create), + ex::set_error_t(int)>([&](auto& ctx) noexcept { + // request server descriptors for missing server ids + if (missing.empty()) { + return ex::set_value(std::move(ctx.receiver), std::move(servers)); + } + auto const& a = aether.Load(); + auto const& c = client.Load(); + assert(a && c && "Aether and client did not loaded"); + + auto* get_servers = get_servers_pool.Create( + *a, missing, c->cloud_connection(), RequestPolicy::All{}); + assert(get_servers != nullptr && "Get servers action did not created"); + get_servers->result_event().Subscribe([&](auto const& res) { + if (res) { + BuildNewServers(aether, servers, res.value()); + ex::set_value(std::move(ctx.receiver), std::move(servers)); + } else { + ex::set_error(std::move(ctx.receiver), 1); + } + }); + }); + }); } -void GetCloudFromAether::Failed() { - result_event_.Emit(Error{1}); - Finish(); +auto SortNewServers(std::vector const& sids) { + return ex::then([sids](std::vector&& servers) noexcept { + // servers should be sorted in initial sids order + std::sort(std::begin(servers), std::end(servers), + [&](auto const& left, auto const& right) { + auto left_order = std::find(std::begin(sids), std::end(sids), + left.Load()->server_id); + auto right_order = std::find(std::begin(sids), std::end(sids), + right.Load()->server_id); + return left_order < right_order; + }); + return std::move(servers); + }); } + } // namespace client_cloud_manager_internal ClientCloudManager::ClientCloudManager(ObjProp prop, ObjPtr aether, ObjPtr client) - : Obj{prop}, aether_{std::move(aether)}, client_{std::move(client)} {} + : Obj{prop}, aether_{std::move(aether)}, client_{std::move(client)} { + // save cloud cache for current client + Client::ptr{client}.WithLoaded([&](auto const& c) { + cloud_cache_.emplace(c->uid(), client_cloud_manager_internal::CloudCache{ + .version_confirmed = true, + .subject_uid = c->uid(), + .version = 0, + .cloud = c->cloud(), + }); + }); + + // init the rest + Init(); +} + +ClientCloudManager::CloudUpdateEvent::Subscriber +ClientCloudManager::cloud_update_event() { + return EventSubscriber{cloud_update_event_}; +} GetCloudAction& ClientCloudManager::GetCloud(Uid client_uid) { + AE_TELED_DEBUG("Ask cloud for uid: {}", client_uid); + auto aether = Aether::ptr{aether_}.Load(); - assert(aether); + assert(aether && "Aether did not loaded"); - if (!cloud_actions_) { - cloud_actions_.emplace(*aether); - } + assert(cloud_actions_ && "Cloud actions did not initiated"); auto cached = cloud_cache_.find(client_uid); - if (cached != cloud_cache_.end()) { - assert(cached->second.is_valid()); + if ((cached != cloud_cache_.end()) && cached->second.cloud.is_valid()) { + // cloud stored in cache, return GetCloudFromCache auto* action = cloud_actions_ ->Create( - *aether, cached->second); + *aether, cached->second.cloud); assert(action != nullptr && "Failed to create GetCloudFromCache action"); return *action; } - // get from aethernet + // get from aethernet auto client = Client::ptr{client_}.Load(); assert(client); - auto* action = - cloud_actions_->Create( - *aether, *aether, *this, client->cloud_connection(), client_uid); + auto* action = cloud_actions_->Create( + *aether, *this, client->cloud_connection(), client_uid); assert(action != nullptr && "Failed to create GetCloudFromAether action"); return *action; } +void ClientCloudManager::Init() { + auto aether = Aether::ptr{aether_}.Load(); + assert(aether && "Aether must be loaded"); + + cloud_actions_.emplace(*aether); + get_servers_pool_.emplace(*aether); + + ListenForCloudUpdate(); +} + +void ClientCloudManager::ListenForCloudUpdate() { + auto client = Client::ptr{client_}.Load(); + assert(client != nullptr && "Client does not loaded"); + + cloud_update_sub_ = CloudSubscription{ + ClientListener{[this](ClientApiSafe& client_api, + CloudServerConnection* /*server_connection*/) { + return client_api.send_cloud_configs().Subscribe( + MethodPtr<&ClientCloudManager::CloudConfigs>{this}); + }}, + client->cloud_connection(), RequestPolicy::All{}}; +} + +auto ClientCloudManager::MakeServersSender(std::vector const& sids) { + using namespace client_cloud_manager_internal; // NOLINT(*using-namespace) + + auto aether = Aether::ptr{aether_}; + auto client = Client::ptr{client_}; + assert(get_servers_pool_.has_value() && "Get servers pool did not initiated"); + + return SplitMissingLoaded(aether, sids) | + LoadMissing(aether, client, *get_servers_pool_) | SortNewServers(sids); +} + +void ClientCloudManager::CloudConfigs(std::vector const& configs) { + for (auto const& conf : configs) { + AE_TELED_DEBUG("Got cloud config update subject:{}, ver:{}, cloud:[{}]", + conf.subject_uid, conf.config_version, conf.cloud.sids); + + // find this config in cache + auto it = cloud_cache_.find(conf.subject_uid); + // new cloud config or new cloud config version and finalizing of config + // is not in progress + if (it == cloud_cache_.end()) { + // new config + cloud_cache_.emplace(conf.subject_uid, + client_cloud_manager_internal::CloudCache{ + .version_confirmed = false, + .subject_uid = conf.subject_uid, + .version = conf.config_version, + .cloud = {}, // leave cloud empty + .finalizing = true, + }); + + FinalizeCloudConfig(conf); + } else if (!it->second.finalizing && + (it->second.version < conf.config_version)) { + it->second.version_confirmed = false, + it->second.subject_uid = conf.subject_uid; + it->second.version = conf.config_version; + it->second.finalizing = true; + + FinalizeCloudConfig(conf); + } else if (it->second.finalizing) { + // duplicate config + return; + } else { + // just confirm version + it->second.version_confirmed = true; + } + } +} + +void ClientCloudManager::FinalizeCloudConfig(CloudConfig const& conf) { + auto aether = Aether::ptr{aether_}.Load(); + + AE_TELED_DEBUG("Finalize servers for new cloud config [{}]", conf.cloud.sids); + // make async waiter for building the new cloud + make_servers_.emplace_back( + std::make_unique), + ex::set_error_t(int)>>( + AeContext{*aether}, MakeServersSender(conf.cloud.sids), + [this, conf](std::optional, int>> + res) noexcept { + assert(res.has_value() && "The result must exists"); + if (res->IsOk()) { + auto cloud = + RegisterCloud(conf.subject_uid, std::move(*res).value()); + cloud_update_event_.Emit(conf.subject_uid, + Ok{cloud}); + } else { + AE_TELED_ERROR("Cloud resolve failed!, Error {}", res->error()); + // TODO: how to handle such error? + cloud_update_event_.Emit(conf.subject_uid, Error{res->error()}); + } + })); +} + Cloud::ptr ClientCloudManager::RegisterCloud(Uid uid, std::vector servers) { - auto new_cloud = WorkCloud::ptr::Create(domain, uid); - new_cloud->SetServers(std::move(servers)); - - cloud_cache_[uid] = new_cloud; - return new_cloud; + auto it = cloud_cache_.find(uid); + assert((it != cloud_cache_.end()) && + "Cloud should be in cache before register"); + // update existing cloud in cache + it->second.finalizing = false; + if (!it->second.cloud.is_valid()) { + it->second.cloud = WorkCloud::ptr::Create(domain, uid); + } + it->second.cloud.Load()->SetServers(std::move(servers)); + return it->second.cloud; } } // namespace ae diff --git a/aether/connection_manager/client_cloud_manager.h b/aether/connection_manager/client_cloud_manager.h index ca3f3169..1fb06c33 100644 --- a/aether/connection_manager/client_cloud_manager.h +++ b/aether/connection_manager/client_cloud_manager.h @@ -18,21 +18,21 @@ #define AETHER_CONNECTION_MANAGER_CLIENT_CLOUD_MANAGER_H_ #include -#include #include +#include "aether/cloud.h" #include "aether/obj/obj.h" #include "aether/ptr/ptr.h" -#include "aether/actions/action.h" -#include "aether/actions/action_pool.h" - -#include "aether/cloud.h" #include "aether/types/uid.h" -#include "aether-miscpp/types/result.h" #include "aether/events/events.h" +#include "aether/actions/action_pool.h" +#include "aether/executors/executors.h" #include "aether/ae_actions/get_servers.h" -#include "aether/ae_actions/get_client_cloud.h" +#include "aether/cloud_connections/cloud_subscription.h" + +#include "aether/connection_manager/get_cloud_action.h" +#include "aether/connection_manager/get_cloud_aether.h" namespace ae { class Aether; @@ -41,17 +41,19 @@ class ClientCloudManager; class CloudServerConnections; struct ServerDescriptor; -/** - * \brief Action to get a cloud. - */ -class GetCloudAction : public Action { - public: - using ResultEvent = Event)>; +namespace client_cloud_manager_internal { +struct CloudCache { + AE_REFLECT_MEMBERS(subject_uid, version, version_confirmed, cloud); - virtual ResultEvent::Subscriber result_event() noexcept = 0; + bool version_confirmed = false; + Uid subject_uid; + std::int64_t version = -1; + Cloud::ptr cloud; + + // runtime info, do not serialize + bool finalizing = false; }; -namespace client_cloud_manager_internal { class GetCloudFromCache final : public GetCloudAction { public: GetCloudFromCache(AeContext const& ae_context, Cloud::ptr cloud); @@ -62,38 +64,6 @@ class GetCloudFromCache final : public GetCloudAction { Cloud::ptr cloud_; ResultEvent result_event_; }; - -class GetCloudFromAether final : public GetCloudAction { - public: - explicit GetCloudFromAether(AeContext const& ae_context, Aether& aether, - ClientCloudManager& client_cloud_manager, - CloudServerConnections& cloud_connection, - Uid const& client_uid); - - ResultEvent::Subscriber result_event() noexcept override; - - private: - void RequestCloud(); - void BuildServers(std::vector cloud); - std::vector BuildNewServers( - std::vector const& descriptors); - void RegisterCloud(std::vector servers); - void Failed(); - - AeContext ae_context_; - Aether* aether_; - ClientCloudManager* client_cloud_manager_; - CloudServerConnections* cloud_connection_; - Uid client_uid_; - ResultEvent result_event_; - std::optional get_client_cloud_action_; - Subscription get_client_cloud_sub_; - std::optional get_servers_action_; - Subscription get_servers_sub_; - std::vector cloud_sids_; - std::vector servers_; -}; - } // namespace client_cloud_manager_internal class ClientCloudManager : public Obj { @@ -102,27 +72,53 @@ class ClientCloudManager : public Obj { ClientCloudManager() = default; public: + using CloudUpdateEvent = + Event)>; + + using GetCloudActionPool = + ActionPool, + 5>; + using GetServersPool = ActionPool; + explicit ClientCloudManager(ObjProp prop, ObjPtr aether, ObjPtr client); AE_CLASS_NO_COPY_MOVE(ClientCloudManager) - GetCloudAction& GetCloud(Uid client_uid); + CloudUpdateEvent::Subscriber cloud_update_event(); - Cloud::ptr RegisterCloud(Uid uid, std::vector servers); + GetCloudAction& GetCloud(Uid client_uid); AE_OBJECT_REFLECT(AE_MMBRS(aether_, client_, cloud_cache_)) + template + void Load(CurrentVersion, Dnv& dnv) { + dnv(base_, aether_, client_, cloud_cache_); + Init(); + } private: + void Init(); + void ListenForCloudUpdate(); + void CloudConfigs(std::vector const& configs); + void FinalizeCloudConfig(CloudConfig const& conf); + auto MakeServersSender(std::vector const& sids); + Cloud::ptr RegisterCloud(Uid uid, std::vector servers); + + GetCloudActionPool& get_cloud_action_pool(); + Obj::ptr aether_; Obj::ptr client_; - std::map cloud_cache_; - std::optional, - 5>> - cloud_actions_; + std::map cloud_cache_; + + CloudUpdateEvent cloud_update_event_; + CloudSubscription cloud_update_sub_; + std::optional cloud_actions_; + std::optional get_servers_pool_; + std::vector), ex::set_error_t(int)>>> + make_servers_; }; } // namespace ae diff --git a/aether/connection_manager/get_cloud_action.h b/aether/connection_manager/get_cloud_action.h new file mode 100644 index 00000000..96391f86 --- /dev/null +++ b/aether/connection_manager/get_cloud_action.h @@ -0,0 +1,39 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_CONNECTION_MANAGER_H_ +#define AETHER_CONNECTION_MANAGER_H_ + +#include "aether-miscpp/types/result.h" + +#include "aether/events/events.h" +#include "aether/actions/action.h" + +#include "aether/cloud.h" + +namespace ae { +/** + * \brief Action to get a cloud. + */ +class GetCloudAction : public Action { + public: + using ResultEvent = Event)>; + + virtual ResultEvent::Subscriber result_event() noexcept = 0; +}; +} // namespace ae + +#endif // AETHER_CONNECTION_MANAGER_H_ diff --git a/aether/connection_manager/get_cloud_aether.cpp b/aether/connection_manager/get_cloud_aether.cpp new file mode 100644 index 00000000..4f6db608 --- /dev/null +++ b/aether/connection_manager/get_cloud_aether.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "aether/connection_manager/get_cloud_aether.h" + +#include "aether/connection_manager/client_cloud_manager.h" + +#include "aether/tele/tele.h" + +namespace ae { +GetCloudFromAether::GetCloudFromAether(AeContext const& ae_context, + ClientCloudManager& client_cloud_manager, + CloudServerConnections& cloud_connection, + Uid const& client_uid) + : ae_context_{ae_context}, + client_uid_{client_uid}, + cloud_update_sub_{client_cloud_manager.cloud_update_event().Subscribe( + MethodPtr<&GetCloudFromAether::CloudUpdate>{this})}, + cloud_request_{ae_context_, cloud_connection} { + RequestCloud(); +} + +GetCloudFromAether::ResultEvent::Subscriber +GetCloudFromAether::result_event() noexcept { + return EventSubscriber{result_event_}; +} + +void GetCloudFromAether::RequestCloud() { + AE_TELED_DEBUG("RequestCloud"); + + cloud_request_.CallApi( + AuthApiCaller{[&](ApiContext& auth_api, + CloudServerConnection* server_connection) { + AE_TELED_DEBUG("Send cloud request for uid:{} at server:{}", + client_uid_, server_connection->server()->server_id); + + auth_api->report_applied_config(std::vector{AppliedConfig{ + .subject_uid = client_uid_, + .config_version = -1, // -1 means request config + }}); + }}, + RequestPolicy::All{}); + // response shall be received through CloudUpdate by matching with uid +} + +void GetCloudFromAether::CloudUpdate( + Uid const& uid, Result const& res) { + if (uid != client_uid_) { + return; + } + + if (res) { + result_event_.Emit(Ok{res.value()}); + } else { + result_event_.Emit(Error{res.error()}); + } +} + +} // namespace ae diff --git a/aether/ae_actions/get_client_cloud.h b/aether/connection_manager/get_cloud_aether.h similarity index 50% rename from aether/ae_actions/get_client_cloud.h rename to aether/connection_manager/get_cloud_aether.h index 04773716..78fcd523 100644 --- a/aether/ae_actions/get_client_cloud.h +++ b/aether/connection_manager/get_cloud_aether.h @@ -1,5 +1,5 @@ /* - * Copyright 2024 Aethernet Inc. + * Copyright 2026 Aethernet Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,40 +14,41 @@ * limitations under the License. */ -#ifndef AETHER_AE_ACTIONS_GET_CLIENT_CLOUD_H_ -#define AETHER_AE_ACTIONS_GET_CLIENT_CLOUD_H_ - -#include +#ifndef AETHET_CONNECTION_MANAGER_CONNECTION_MANAGHER_H_ +#define AETHET_CONNECTION_MANAGER_CONNECTION_MANAGHER_H_ #include "aether/types/uid.h" #include "aether/ae_context.h" #include "aether/events/events.h" -#include "aether/types/server_id.h" -#include "aether/actions/action.h" -#include "aether/events/multi_subscription.h" + #include "aether/cloud_connections/cloud_request.h" -#include "aether/cloud_connections/request_policy.h" +#include "aether/connection_manager/get_cloud_action.h" #include "aether/cloud_connections/cloud_server_connections.h" namespace ae { -class GetClientCloudAction final : public Action { - public: - using ResultEvent = Event const&, int>)>; +class ClientCloudManager; +class CloudServerConnections; - GetClientCloudAction(AeContext const& ae_context, Uid client_uid, - CloudServerConnections& cloud_connection, - RequestPolicy::Variant request_policy); +class GetCloudFromAether final : public GetCloudAction { + public: + explicit GetCloudFromAether(AeContext const& ae_context, + ClientCloudManager& client_cloud_manager, + CloudServerConnections& cloud_connection, + Uid const& client_uid); - ResultEvent::Subscriber result_event(); + ResultEvent::Subscriber result_event() noexcept override; private: + void RequestCloud(); + void CloudUpdate(Uid const& uid, Result const& res); + + AeContext ae_context_; Uid client_uid_; - CloudRequestAction cloud_request_; - ResultEvent result_event_; - MultiSubscription cloud_request_subs_; - std::vector cloud_; + Subscription cloud_update_sub_; + CloudRequest cloud_request_; + ResultEvent result_event_; }; } // namespace ae -#endif // AETHER_AE_ACTIONS_GET_CLIENT_CLOUD_H_ +#endif // AETHET_CONNECTION_MANAGER_CONNECTION_MANAGHER_H_ diff --git a/aether/crypto/crypto_nonce.cpp b/aether/crypto/crypto_nonce.cpp index 9639e87e..824a03e5 100644 --- a/aether/crypto/crypto_nonce.cpp +++ b/aether/crypto/crypto_nonce.cpp @@ -28,7 +28,7 @@ namespace ae { void CryptoNonceChacha20Poly1305::Next() { static_assert(kNonceSize >= sizeof(std::uint64_t)); auto& v = *reinterpret_cast(value.data()); - v = +1; + v += 1; } void CryptoNonceChacha20Poly1305::Init() { randombytes_buf(value.data(), value.size()); diff --git a/aether/work_cloud_api/client_api/client_api_safe.cpp b/aether/work_cloud_api/client_api/client_api_safe.cpp index a3d53fde..d25e6bda 100644 --- a/aether/work_cloud_api/client_api/client_api_safe.cpp +++ b/aether/work_cloud_api/client_api/client_api_safe.cpp @@ -115,4 +115,9 @@ void ClientApiSafe::SendMessage(AeMessage const& msg) { send_message_event_.Emit(msg); } +void ClientApiSafe::SendCLoudConfig(std::vector const& configs) { + AE_TELED_DEBUG("Received cloud configs count {}", configs.size()); + send_cloud_configs_.Emit(configs); +} + } // namespace ae diff --git a/aether/work_cloud_api/client_api/client_api_safe.h b/aether/work_cloud_api/client_api/client_api_safe.h index 2a1d5af1..e1f08b01 100644 --- a/aether/work_cloud_api/client_api/client_api_safe.h +++ b/aether/work_cloud_api/client_api/client_api_safe.h @@ -23,7 +23,7 @@ #include "aether/api_protocol/api_protocol.h" #include "aether/work_cloud_api/ae_message.h" -#include "aether/work_cloud_api/uid_and_cloud.h" +#include "aether/work_cloud_api/cloud_configs.h" #include "aether/work_cloud_api/server_descriptor.h" namespace ae { @@ -73,6 +73,8 @@ class ClientApiSafe : public ApiClassImpl { void SendAccessCheckResults(std::vector const& results); void SendMessage(AeMessage const& message); + void SendCLoudConfig(std::vector const& configs); + ReturnResultApi return_result; AE_METHODS(RegMethod<3, &ClientApiSafe::ChangeParent>, @@ -93,6 +95,7 @@ class ClientApiSafe : public ApiClassImpl { RegMethod<18, &ClientApiSafe::SendAllAccessedClients>, RegMethod<19, &ClientApiSafe::SendAccessCheckResults>, RegMethod<20, &ClientApiSafe::SendMessage>, + RegMethod<21, &ClientApiSafe::SendCLoudConfig>, ExtApi<&ClientApiSafe::return_result>); auto send_message_event() { return EventSubscriber{send_message_event_}; } @@ -103,6 +106,7 @@ class ClientApiSafe : public ApiClassImpl { auto request_telemetry_event() { return EventSubscriber{request_telemetry_event_}; } + auto send_cloud_configs() { return EventSubscriber{send_cloud_configs_}; } private: Event send_message_event_; @@ -110,6 +114,7 @@ class ClientApiSafe : public ApiClassImpl { send_server_descriptor_event_; Event send_cloud_event_; Event request_telemetry_event_; + Event const& configs)> send_cloud_configs_; }; } // namespace ae diff --git a/aether/work_cloud_api/uid_and_cloud.h b/aether/work_cloud_api/cloud_configs.h similarity index 62% rename from aether/work_cloud_api/uid_and_cloud.h rename to aether/work_cloud_api/cloud_configs.h index 920c7805..18992c56 100644 --- a/aether/work_cloud_api/uid_and_cloud.h +++ b/aether/work_cloud_api/cloud_configs.h @@ -1,5 +1,5 @@ /* - * Copyright 2024 Aethernet Inc. + * Copyright 2026 Aethernet Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,12 @@ * limitations under the License. */ -#ifndef AETHER_WORK_CLOUD_API_UID_AND_CLOUD_H_ -#define AETHER_WORK_CLOUD_API_UID_AND_CLOUD_H_ +#ifndef AETHER_WORK_CLOUD_API_CLOUD_CONFIGS_H_ +#define AETHER_WORK_CLOUD_API_CLOUD_CONFIGS_H_ -#include +#include + +#include "aether-miscpp/reflect/reflect.h" #include "aether/types/uid.h" #include "aether/types/server_id.h" @@ -34,6 +36,22 @@ struct UidAndCloudDescriptor { Uid uid; CloudDescriptor cloud; }; + +struct CloudConfig { + AE_REFLECT_MEMBERS(subject_uid, config_version, cloud) + + Uid subject_uid; + std::int64_t config_version; + CloudDescriptor cloud; +}; + +struct AppliedConfig { + AE_REFLECT_MEMBERS(subject_uid, config_version) + + Uid subject_uid; + std::int64_t config_version; +}; + } // namespace ae -#endif // AETHER_WORK_CLOUD_API_UID_AND_CLOUD_H_ +#endif // AETHER_WORK_CLOUD_API_CLOUD_CONFIGS_H_ diff --git a/aether/work_cloud_api/work_server_api/authorized_api.cpp b/aether/work_cloud_api/work_server_api/authorized_api.cpp index 2c35cd74..22542c94 100644 --- a/aether/work_cloud_api/work_server_api/authorized_api.cpp +++ b/aether/work_cloud_api/work_server_api/authorized_api.cpp @@ -25,5 +25,6 @@ AuthorizedApi::AuthorizedApi(ProtocolContext& protocol_context) check_access_for_send_message{protocol_context}, resolver_servers{protocol_context}, resolver_clouds{protocol_context}, - send_telemetry{protocol_context} {} + send_telemetry{protocol_context}, + report_applied_config{protocol_context} {} } // namespace ae diff --git a/aether/work_cloud_api/work_server_api/authorized_api.h b/aether/work_cloud_api/work_server_api/authorized_api.h index bd744377..31e5b8b0 100644 --- a/aether/work_cloud_api/work_server_api/authorized_api.h +++ b/aether/work_cloud_api/work_server_api/authorized_api.h @@ -25,6 +25,7 @@ #include "aether/work_cloud_api/ae_message.h" #include "aether/work_cloud_api/telemetric.h" +#include "aether/work_cloud_api/cloud_configs.h" namespace ae { @@ -40,6 +41,8 @@ class AuthorizedApi : public ApiClass { Method<13, void(std::vector uids)> resolver_clouds; Method<18, void(Telemetric telemetric)> send_telemetry; + + Method<38, void(std::vector configs)> report_applied_config; }; } // namespace ae