From 39e0f55a1dea698b0e548d502d9b05dd51f764f4 Mon Sep 17 00:00:00 2001 From: Nikolay Chirkov Date: Thu, 25 Jun 2026 14:04:59 +0300 Subject: [PATCH 01/16] Add prepared send message packet encoder --- aether/client_messages/p2p_message_stream.cpp | 35 ++++ aether/client_messages/p2p_message_stream.h | 7 + aether/prepared_packet/packet_encoder.h | 187 ++++++++++++++++++ .../client_server_connection.cpp | 84 ++++++++ .../client_server_connection.h | 9 + aether/stream_api/api_call_adapter.h | 111 ++++++++++- 6 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 aether/prepared_packet/packet_encoder.h diff --git a/aether/client_messages/p2p_message_stream.cpp b/aether/client_messages/p2p_message_stream.cpp index 74b2755c..966dca82 100644 --- a/aether/client_messages/p2p_message_stream.cpp +++ b/aether/client_messages/p2p_message_stream.cpp @@ -26,6 +26,7 @@ #include "aether/cloud_connections/cloud_visit.h" #include "aether/cloud_connections/cloud_request.h" #include "aether/cloud_connections/cloud_subscription.h" +#include "aether/cloud_connections/cloud_server_connection.h" #include "aether/client_messages/client_messages_tele.h" @@ -51,6 +52,29 @@ class MessageSendStream final : public IStream { }}, request_policy_); } + + std::optional + ExportPreparedSendMessageBlock(Uid target_uid, + std::uint32_t reserve_nonce_count) { + for (auto* sc : cloud_connection_->servers()) { + if (sc == nullptr) { + continue; + } + + auto* conn = sc->client_connection(); + if (conn == nullptr) { + continue; + } + + auto block = conn->ExportPreparedSendMessageBlock(target_uid, + reserve_nonce_count); + if (block) { + return block; + } + } + + return std::nullopt; + } StreamInfo stream_info() const override { return stream_info_; } OutDataEvent::Subscriber out_data_event() override { return out_data_event_; } StreamUpdateEvent::Subscriber stream_update_event() override { @@ -235,6 +259,17 @@ void P2pStream::WriteOut(DataBuffer const& data) { Uid const& P2pStream::destination() const { return destination_; } +std::optional +P2pStream::ExportPreparedSendMessageBlock(std::uint32_t reserve_nonce_count) { + if (!message_send_stream_) { + return std::nullopt; + } + + return message_send_stream_->ExportPreparedSendMessageBlock( + destination_, reserve_nonce_count); +} + + void P2pStream::ConnectReceive() { auto client_ptr = client_.Lock(); assert(client_ptr); diff --git a/aether/client_messages/p2p_message_stream.h b/aether/client_messages/p2p_message_stream.h index 7d856bf1..a9ebc5c8 100644 --- a/aether/client_messages/p2p_message_stream.h +++ b/aether/client_messages/p2p_message_stream.h @@ -17,6 +17,9 @@ #ifndef AETHER_CLIENT_MESSAGES_P2P_MESSAGE_STREAM_H_ #define AETHER_CLIENT_MESSAGES_P2P_MESSAGE_STREAM_H_ +#include +#include + #include "aether/common.h" #include "aether/types/uid.h" @@ -28,6 +31,7 @@ #include "aether/cloud_connections/cloud_server_connections.h" #include "aether/connection_manager/client_cloud_manager.h" #include "aether/connection_manager/client_connection_manager.h" +#include "aether/prepared_packet/packet_encoder.h" namespace ae { class Client; @@ -60,6 +64,9 @@ class P2pStream final : public ByteIStream { Uid const& destination() const; + std::optional + ExportPreparedSendMessageBlock(std::uint32_t reserve_nonce_count); + private: void ConnectReceive(); void ConnectSend(); diff --git a/aether/prepared_packet/packet_encoder.h b/aether/prepared_packet/packet_encoder.h new file mode 100644 index 00000000..5d05686d --- /dev/null +++ b/aether/prepared_packet/packet_encoder.h @@ -0,0 +1,187 @@ +/* + * Prepared packet encoder experiment. + * + * This code does not send anything. + * It only builds Aether packet bytes from prepared state + payload. + */ +#ifndef AETHER_PREPARED_PACKET_PACKET_ENCODER_H_ +#define AETHER_PREPARED_PACKET_PACKET_ENCODER_H_ + +#include +#include +#include +#include +#include +#include + +#include "aether/types/uid.h" +#include "aether/types/data_buffer.h" + +#include "aether/crypto/key.h" +#include "aether/crypto/crypto_nonce.h" +#include "aether/crypto/ikey_provider.h" +#include "aether/crypto/sync_crypto_provider.h" + +#include "aether/api_protocol/api_context.h" +#include "aether/api_protocol/sub_api.h" + +#include "aether/work_cloud_api/ae_message.h" +#include "aether/work_cloud_api/work_server_api/login_api.h" +#include "aether/work_cloud_api/work_server_api/authorized_api.h" + +namespace ae::prepared_packet { + +enum class PreparedIpVersion : std::uint8_t { + kIpV4 = 4, + kIpV6 = 6, +}; + +struct PreparedUdpEndpoint { + PreparedIpVersion version = PreparedIpVersion::kIpV4; + std::uint16_t port = 0; + + // IPv4 uses first 4 bytes. + // IPv6 uses all 16 bytes. + std::array ip{}; +}; + +enum class EncodePacketError { + kNone = 0, + kNonceExhausted, + kEncodeFailed, +}; + +inline char const* ToString(EncodePacketError error) { + switch (error) { + case EncodePacketError::kNone: + return "none"; + case EncodePacketError::kNonceExhausted: + return "nonce_exhausted"; + case EncodePacketError::kEncodeFailed: + return "encode_failed"; + } + return "unknown"; +} + +struct EncodePacketResult { + EncodePacketError error = EncodePacketError::kNone; + std::size_t bytes_written = 0; + + // Debug counter inside prepared block. This is not the cryptographic nonce value. + std::uint64_t nonce_index = 0; + + explicit operator bool() const { + return error == EncodePacketError::kNone; + } +}; + +// One block for now. +// On MCU this can live in RTC RAM. +// Later it can be split into flash/static and rtc/mutable parts. +struct PreparedSendMessageBlock { + // Used by external sender after EncodePacket(). + // EncodePacket() itself does not use endpoint. + PreparedUdpEndpoint endpoint; + + // Used for login_by_alias(...) + Uid sender_ephemeral_uid; + + // Used for AuthorizedApi::send_message(AeMessage{target_uid, payload}) + Uid target_uid; + + // Already derived client -> server key. + Key client_to_server_key; + + // Mutable nonce state. + CryptoNonce next_nonce; + std::uint32_t nonce_left = 0; + + // Debug only. + std::uint64_t nonce_index = 0; +}; + +class PreparedSendMessageKeyProvider final : public ISyncKeyProvider { + public: + explicit PreparedSendMessageKeyProvider(PreparedSendMessageBlock& block) + : block_{&block} {} + + Key GetKey() const override { + return block_->client_to_server_key; + } + + CryptoNonce const& Nonce() const override { + return block_->next_nonce; + } + + private: + PreparedSendMessageBlock* block_; +}; + +inline EncodePacketResult EncodePacket(PreparedSendMessageBlock& block, + DataBuffer const& payload, + DataBuffer& out) { + if (block.nonce_left == 0) { + out.clear(); + return EncodePacketResult{EncodePacketError::kNonceExhausted, 0, + block.nonce_index}; + } + + // Match existing ClientKeyProvider semantics: + // consume next nonce before encryption. + block.next_nonce.Next(); + --block.nonce_left; + + auto nonce_index = block.nonce_index; + ++block.nonce_index; + + auto key_provider = + std::make_unique(block); + SyncEncryptProvider encrypt_provider{std::move(key_provider)}; + + ProtocolContext protocol_context; + LoginApi login_api{protocol_context, encrypt_provider}; + + auto api_context = ApiContext{login_api}; + + api_context->login_by_alias( + block.sender_ephemeral_uid, + SubApi{ + [&block, &payload](ApiContext& auth_api) { + auth_api->send_message( + AeMessage{block.target_uid, DataBuffer{payload}}); + }}); + + out = std::move(api_context).Pack(); + + return EncodePacketResult{EncodePacketError::kNone, out.size(), nonce_index}; +} + +// Existing internal seam. Keep normal Aether path working. +struct PreparedPacketContext { + std::uint64_t next_nonce = 0; + std::uint64_t nonce_limit = (std::numeric_limits::max)(); + + EncodePacketResult TakeNonce(std::size_t bytes_written) { + if (next_nonce == nonce_limit) { + return EncodePacketResult{EncodePacketError::kNonceExhausted, 0, + next_nonce}; + } + + auto nonce = next_nonce; + ++next_nonce; + + return EncodePacketResult{EncodePacketError::kNone, bytes_written, nonce}; + } +}; + +template +EncodePacketResult EncodePacket(PreparedPacketContext& prepared, + ApiContext&& data, + DataBuffer& out) { + out = std::move(data).Pack(); + return prepared.TakeNonce(out.size()); +} + +} // namespace ae::prepared_packet + +#endif // AETHER_PREPARED_PACKET_PACKET_ENCODER_H_ diff --git a/aether/server_connections/client_server_connection.cpp b/aether/server_connections/client_server_connection.cpp index 04ee2901..bd523b8b 100644 --- a/aether/server_connections/client_server_connection.cpp +++ b/aether/server_connections/client_server_connection.cpp @@ -1,3 +1,6 @@ +#include +#include +#include /* * Copyright 2024 Aethernet Inc. * @@ -142,6 +145,7 @@ ClientServerConnection::ClientServerConnection(AeContext const& ae_context, Ptr const& client, Ptr const& server) : ae_context_{ae_context}, + client_{client}, server_{server}, ephemeral_uid_{client->ephemeral_uid()}, crypto_provider_{std::make_unique< @@ -201,6 +205,86 @@ ServerConnection& ClientServerConnection::server_connection() { return server_connection_.server_connection; } +std::optional +ClientServerConnection::ExportPreparedSendMessageBlock( + Uid target_uid, std::uint32_t reserve_nonce_count) { + auto client = client_.Lock(); + auto server = server_.Lock(); + + if (!client || !server || (reserve_nonce_count == 0)) { + return std::nullopt; + } + + std::optional prepared_endpoint; + + for (auto const& e : server->endpoints) { + if (e.protocol != Protocol::kUdp) { + continue; + } + + prepared_packet::PreparedUdpEndpoint candidate; + candidate.port = e.port; + + bool is_ip = false; + + std::visit( + [&](auto const& addr) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + candidate.version = prepared_packet::PreparedIpVersion::kIpV4; + for (std::size_t i = 0; i < 4; ++i) { + candidate.ip[i] = addr.ipv4_value[i]; + } + is_ip = true; + } else if constexpr (std::is_same_v) { + candidate.version = prepared_packet::PreparedIpVersion::kIpV6; + for (std::size_t i = 0; i < 16; ++i) { + candidate.ip[i] = addr.ipv6_value[i]; + } + is_ip = true; + } + }, + e.address); + + if (is_ip) { + prepared_endpoint = candidate; + break; + } + } + + if (!prepared_endpoint) { + return std::nullopt; + } + + auto* server_key = client->server_state(server->server_id); + if (server_key == nullptr) { + return std::nullopt; + } + + prepared_packet::PreparedSendMessageBlock block; + block.endpoint = *prepared_endpoint; + block.sender_ephemeral_uid = ephemeral_uid_; + block.target_uid = target_uid; + block.client_to_server_key = server_key->client_to_server(); + + // Store current nonce. EncodePacket() will call Next() before encryption, + // matching existing ClientKeyProvider behavior. + block.next_nonce = server_key->nonce(); + block.nonce_left = reserve_nonce_count; + block.nonce_index = 0; + + // Burn/reserve the same nonce range in the full client, + // so the normal Aether path cannot reuse it later. + for (std::uint32_t i = 0; i < reserve_nonce_count; ++i) { + server_key->Next(); + } + + return block; +} + + + void ClientServerConnection::OutData(DataBuffer const& data) { auto parser = ApiParser{protocol_context_, data}; parser.Parse(client_api_unsafe_); diff --git a/aether/server_connections/client_server_connection.h b/aether/server_connections/client_server_connection.h index 2c5abb42..aee479b6 100644 --- a/aether/server_connections/client_server_connection.h +++ b/aether/server_connections/client_server_connection.h @@ -17,6 +17,9 @@ #ifndef AETHER_SERVER_CONNECTIONS_CLIENT_SERVER_CONNECTION_H_ #define AETHER_SERVER_CONNECTIONS_CLIENT_SERVER_CONNECTION_H_ +#include +#include + #include "aether/common.h" #include "aether/ae_context.h" #include "aether/ae_actions/ping.h" @@ -29,6 +32,7 @@ #include "aether/work_cloud_api/work_server_api/authorized_api.h" #include "aether/server_connections/server_connection.h" +#include "aether/prepared_packet/packet_encoder.h" namespace ae { class Client; @@ -75,11 +79,16 @@ class ClientServerConnection { ServerConnection& server_connection(); + std::optional + ExportPreparedSendMessageBlock(Uid target_uid, + std::uint32_t reserve_nonce_count); + private: void OutData(DataBuffer const& data); void ChannelChanged(); AeContext ae_context_; + PtrView client_; PtrView server_; Uid ephemeral_uid_; diff --git a/aether/stream_api/api_call_adapter.h b/aether/stream_api/api_call_adapter.h index a5326a75..7ef95ca5 100644 --- a/aether/stream_api/api_call_adapter.h +++ b/aether/stream_api/api_call_adapter.h @@ -18,11 +18,106 @@ #define AETHER_STREAM_API_API_CALL_ADAPTER_H_ #include +#include +#include +#include +#include +#include #include "aether/stream_api/istream.h" #include "aether/api_protocol/api_context.h" +#include "aether/prepared_packet/packet_encoder.h" +#include "aether/types/data_buffer.h" namespace ae { + +// FASTTX_STEP3_ENCODE_PACKET_API +namespace fast_tx_internal { + +enum class EncodePacketError { + kNone = 0, + kNonceExhausted, + kOutputBufferTooSmall, + kPayloadTooLarge, + kEncodeFailed, +}; + +inline char const* ToString(EncodePacketError error) { + switch (error) { + case EncodePacketError::kNone: + return "none"; + case EncodePacketError::kNonceExhausted: + return "nonce_exhausted"; + case EncodePacketError::kOutputBufferTooSmall: + return "output_buffer_too_small"; + case EncodePacketError::kPayloadTooLarge: + return "payload_too_large"; + case EncodePacketError::kEncodeFailed: + return "encode_failed"; + } + return "unknown"; +} + +struct EncodePacketReport { + EncodePacketError error = EncodePacketError::kNone; + std::size_t bytes_written = 0; + std::uint64_t nonce_used = 0; + + explicit operator bool() const { return error == EncodePacketError::kNone; } +}; + +// Endpoint intentionally stays outside. +// External code will own ip:port and raw UDP socket. +// This struct must contain only data needed by Aether packet encoding. +struct PreparedPacketEncoder { + std::uint64_t next_nonce = 0; + std::uint64_t nonce_limit = (std::numeric_limits::max)(); + std::size_t max_packet_size = 1200; + + EncodePacketReport TakeNonce() { + if (next_nonce == nonce_limit) { + return EncodePacketReport{EncodePacketError::kNonceExhausted, 0, 0}; + } + + auto nonce = next_nonce; + ++next_nonce; + + return EncodePacketReport{EncodePacketError::kNone, 0, nonce}; + } +}; + +// Future external shape: +// +// EncodePacketResult EncodePacket(PreparedPacketEncoder& prepared, +// Span payload, +// DataBuffer& out); +// +// Current internal step keeps old Aether path alive by using ByteIStream as out. +struct EncodePacketResult { + EncodePacketReport report; + WriteAction* action = nullptr; + + explicit operator bool() const { + return static_cast(report) && (action != nullptr); + } +}; + +template +EncodePacketResult EncodePacket(PreparedPacketEncoder& prepared, + ApiContext&& data, + ByteIStream& out) { + auto report = prepared.TakeNonce(); + if (!report) { + return EncodePacketResult{report, nullptr}; + } + + auto& action = out.Write(std::move(data)); + + return EncodePacketResult{report, &action}; +} + +} // namespace fast_tx_internal + /** * \brief Api method call adapter to automatically flush the packet after all * method calls. @@ -35,7 +130,21 @@ class ApiCallAdapter { AE_CLASS_MOVE_ONLY(ApiCallAdapter) - WriteAction& Flush() { return byte_stream_->Write(std::move(api_context_)); } + WriteAction& Flush() { + fast_tx_internal::PreparedPacketEncoder prepared{}; + + auto result = fast_tx_internal::EncodePacket( + prepared, std::move(api_context_), *byte_stream_); + + if (!result) { + std::cerr << "FastTx EncodePacket failed: " + << fast_tx_internal::ToString(result.report.error) + << "\n"; + assert(false); + } + + return *result.action; + } ApiContext& operator->() { return api_context_; } From 92d677fdee64c6fe265f82e3ca5d206d1758eb19 Mon Sep 17 00:00:00 2001 From: Nikolay Chirkov Date: Thu, 25 Jun 2026 14:48:57 +0300 Subject: [PATCH 02/16] Clean up prepared message endpoint API --- aether/prepared_packet/packet_encoder.h | 25 +++++++------------ .../client_server_connection.cpp | 6 ++--- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/aether/prepared_packet/packet_encoder.h b/aether/prepared_packet/packet_encoder.h index 5d05686d..3b02f40f 100644 --- a/aether/prepared_packet/packet_encoder.h +++ b/aether/prepared_packet/packet_encoder.h @@ -16,6 +16,7 @@ #include "aether/types/uid.h" #include "aether/types/data_buffer.h" +#include "aether/types/address.h" #include "aether/crypto/key.h" #include "aether/crypto/crypto_nonce.h" @@ -36,8 +37,9 @@ enum class PreparedIpVersion : std::uint8_t { kIpV6 = 6, }; -struct PreparedUdpEndpoint { +struct PreparedEndpoint { PreparedIpVersion version = PreparedIpVersion::kIpV4; + Protocol protocol = Protocol::kUdp; std::uint16_t port = 0; // IPv4 uses first 4 bytes. @@ -68,7 +70,6 @@ struct EncodePacketResult { std::size_t bytes_written = 0; // Debug counter inside prepared block. This is not the cryptographic nonce value. - std::uint64_t nonce_index = 0; explicit operator bool() const { return error == EncodePacketError::kNone; @@ -81,7 +82,7 @@ struct EncodePacketResult { struct PreparedSendMessageBlock { // Used by external sender after EncodePacket(). // EncodePacket() itself does not use endpoint. - PreparedUdpEndpoint endpoint; + PreparedEndpoint endpoint; // Used for login_by_alias(...) Uid sender_ephemeral_uid; @@ -97,7 +98,6 @@ struct PreparedSendMessageBlock { std::uint32_t nonce_left = 0; // Debug only. - std::uint64_t nonce_index = 0; }; class PreparedSendMessageKeyProvider final : public ISyncKeyProvider { @@ -122,8 +122,7 @@ inline EncodePacketResult EncodePacket(PreparedSendMessageBlock& block, DataBuffer& out) { if (block.nonce_left == 0) { out.clear(); - return EncodePacketResult{EncodePacketError::kNonceExhausted, 0, - block.nonce_index}; + return EncodePacketResult{EncodePacketError::kNonceExhausted, 0}; } // Match existing ClientKeyProvider semantics: @@ -131,9 +130,6 @@ inline EncodePacketResult EncodePacket(PreparedSendMessageBlock& block, block.next_nonce.Next(); --block.nonce_left; - auto nonce_index = block.nonce_index; - ++block.nonce_index; - auto key_provider = std::make_unique(block); SyncEncryptProvider encrypt_provider{std::move(key_provider)}; @@ -153,7 +149,7 @@ inline EncodePacketResult EncodePacket(PreparedSendMessageBlock& block, out = std::move(api_context).Pack(); - return EncodePacketResult{EncodePacketError::kNone, out.size(), nonce_index}; + return EncodePacketResult{EncodePacketError::kNone, out.size()}; } // Existing internal seam. Keep normal Aether path working. @@ -163,14 +159,11 @@ struct PreparedPacketContext { EncodePacketResult TakeNonce(std::size_t bytes_written) { if (next_nonce == nonce_limit) { - return EncodePacketResult{EncodePacketError::kNonceExhausted, 0, - next_nonce}; + return EncodePacketResult{EncodePacketError::kNonceExhausted, 0}; } +++next_nonce; - auto nonce = next_nonce; - ++next_nonce; - - return EncodePacketResult{EncodePacketError::kNone, bytes_written, nonce}; + return EncodePacketResult{EncodePacketError::kNone, bytes_written}; } }; diff --git a/aether/server_connections/client_server_connection.cpp b/aether/server_connections/client_server_connection.cpp index bd523b8b..12a675d6 100644 --- a/aether/server_connections/client_server_connection.cpp +++ b/aether/server_connections/client_server_connection.cpp @@ -215,14 +215,15 @@ ClientServerConnection::ExportPreparedSendMessageBlock( return std::nullopt; } - std::optional prepared_endpoint; + std::optional prepared_endpoint; for (auto const& e : server->endpoints) { if (e.protocol != Protocol::kUdp) { continue; } - prepared_packet::PreparedUdpEndpoint candidate; + prepared_packet::PreparedEndpoint candidate; + candidate.protocol = e.protocol; candidate.port = e.port; bool is_ip = false; @@ -272,7 +273,6 @@ ClientServerConnection::ExportPreparedSendMessageBlock( // matching existing ClientKeyProvider behavior. block.next_nonce = server_key->nonce(); block.nonce_left = reserve_nonce_count; - block.nonce_index = 0; // Burn/reserve the same nonce range in the full client, // so the normal Aether path cannot reuse it later. From 494df92a0b352dc20c560452de0cd1e3abd60069 Mon Sep 17 00:00:00 2001 From: Nikolay Chirkov Date: Thu, 25 Jun 2026 18:11:25 +0300 Subject: [PATCH 03/16] Split prepared packet encoder implementation --- aether/CMakeLists.txt | 1 + aether/client_messages/p2p_message_stream.h | 2 +- aether/prepared_packet/packet_encoder.cpp | 73 ++++++++ aether/prepared_packet/packet_encoder.h | 175 +----------------- .../prepared_packet/prepared_send_message.h | 80 ++++++++ .../client_server_connection.h | 2 +- 6 files changed, 164 insertions(+), 169 deletions(-) create mode 100644 aether/prepared_packet/packet_encoder.cpp create mode 100644 aether/prepared_packet/prepared_send_message.h diff --git a/aether/CMakeLists.txt b/aether/CMakeLists.txt index be442990..c8989bd9 100644 --- a/aether/CMakeLists.txt +++ b/aether/CMakeLists.txt @@ -202,6 +202,7 @@ list(APPEND aether_srcs list(APPEND aether_srcs "server_connections/client_server_connection.cpp" + prepared_packet/packet_encoder.cpp "server_connections/channel_connection.cpp" "server_connections/server_connection.cpp") diff --git a/aether/client_messages/p2p_message_stream.h b/aether/client_messages/p2p_message_stream.h index a9ebc5c8..64d7a9f9 100644 --- a/aether/client_messages/p2p_message_stream.h +++ b/aether/client_messages/p2p_message_stream.h @@ -31,7 +31,7 @@ #include "aether/cloud_connections/cloud_server_connections.h" #include "aether/connection_manager/client_cloud_manager.h" #include "aether/connection_manager/client_connection_manager.h" -#include "aether/prepared_packet/packet_encoder.h" +#include "aether/prepared_packet/prepared_send_message.h" namespace ae { class Client; diff --git a/aether/prepared_packet/packet_encoder.cpp b/aether/prepared_packet/packet_encoder.cpp new file mode 100644 index 00000000..0c87e7bb --- /dev/null +++ b/aether/prepared_packet/packet_encoder.cpp @@ -0,0 +1,73 @@ +#include "aether/prepared_packet/packet_encoder.h" + +#include +#include + +#include "aether/crypto/ikey_provider.h" +#include "aether/crypto/sync_crypto_provider.h" + +#include "aether/api_protocol/api_context.h" +#include "aether/api_protocol/sub_api.h" + +#include "aether/work_cloud_api/ae_message.h" +#include "aether/work_cloud_api/work_server_api/authorized_api.h" +#include "aether/work_cloud_api/work_server_api/login_api.h" + +namespace ae::prepared_packet { +namespace { + +class PreparedSendMessageKeyProvider final : public ISyncKeyProvider { + public: + explicit PreparedSendMessageKeyProvider(PreparedSendMessageBlock& block) + : block_{&block} {} + + Key GetKey() const override { + return block_->client_to_server_key; + } + + CryptoNonce const& Nonce() const override { + return block_->next_nonce; + } + + private: + PreparedSendMessageBlock* block_; +}; + +} // namespace + +EncodePacketResult EncodePacket(PreparedSendMessageBlock& block, + DataBuffer const& payload, + DataBuffer& out) { + if (block.nonce_left == 0) { + out.clear(); + return EncodePacketResult{EncodePacketError::kNonceExhausted, 0}; + } + + // Match the existing ClientKeyProvider semantics: + // consume next nonce before encryption. + block.next_nonce.Next(); + --block.nonce_left; + + auto key_provider = + std::make_unique(block); + SyncEncryptProvider encrypt_provider{std::move(key_provider)}; + + ProtocolContext protocol_context; + LoginApi login_api{protocol_context, encrypt_provider}; + + auto api_context = ApiContext{login_api}; + + api_context->login_by_alias( + block.sender_ephemeral_uid, + SubApi{ + [&block, &payload](ApiContext& auth_api) { + auth_api->send_message( + AeMessage{block.target_uid, DataBuffer{payload}}); + }}); + + out = std::move(api_context).Pack(); + + return EncodePacketResult{EncodePacketError::kNone, out.size()}; +} + +} // namespace ae::prepared_packet diff --git a/aether/prepared_packet/packet_encoder.h b/aether/prepared_packet/packet_encoder.h index 3b02f40f..6f9d7371 100644 --- a/aether/prepared_packet/packet_encoder.h +++ b/aether/prepared_packet/packet_encoder.h @@ -1,179 +1,20 @@ /* - * Prepared packet encoder experiment. + * Prepared packet encoder. * - * This code does not send anything. - * It only builds Aether packet bytes from prepared state + payload. + * EncodePacket only builds Aether packet bytes and advances the reserved nonce + * range. It does not send, open sockets, resolve DNS, or know platform + * transport. */ #ifndef AETHER_PREPARED_PACKET_PACKET_ENCODER_H_ #define AETHER_PREPARED_PACKET_PACKET_ENCODER_H_ -#include -#include -#include -#include -#include -#include - -#include "aether/types/uid.h" -#include "aether/types/data_buffer.h" -#include "aether/types/address.h" - -#include "aether/crypto/key.h" -#include "aether/crypto/crypto_nonce.h" -#include "aether/crypto/ikey_provider.h" -#include "aether/crypto/sync_crypto_provider.h" - -#include "aether/api_protocol/api_context.h" -#include "aether/api_protocol/sub_api.h" - -#include "aether/work_cloud_api/ae_message.h" -#include "aether/work_cloud_api/work_server_api/login_api.h" -#include "aether/work_cloud_api/work_server_api/authorized_api.h" +#include "aether/prepared_packet/prepared_send_message.h" namespace ae::prepared_packet { -enum class PreparedIpVersion : std::uint8_t { - kIpV4 = 4, - kIpV6 = 6, -}; - -struct PreparedEndpoint { - PreparedIpVersion version = PreparedIpVersion::kIpV4; - Protocol protocol = Protocol::kUdp; - std::uint16_t port = 0; - - // IPv4 uses first 4 bytes. - // IPv6 uses all 16 bytes. - std::array ip{}; -}; - -enum class EncodePacketError { - kNone = 0, - kNonceExhausted, - kEncodeFailed, -}; - -inline char const* ToString(EncodePacketError error) { - switch (error) { - case EncodePacketError::kNone: - return "none"; - case EncodePacketError::kNonceExhausted: - return "nonce_exhausted"; - case EncodePacketError::kEncodeFailed: - return "encode_failed"; - } - return "unknown"; -} - -struct EncodePacketResult { - EncodePacketError error = EncodePacketError::kNone; - std::size_t bytes_written = 0; - - // Debug counter inside prepared block. This is not the cryptographic nonce value. - - explicit operator bool() const { - return error == EncodePacketError::kNone; - } -}; - -// One block for now. -// On MCU this can live in RTC RAM. -// Later it can be split into flash/static and rtc/mutable parts. -struct PreparedSendMessageBlock { - // Used by external sender after EncodePacket(). - // EncodePacket() itself does not use endpoint. - PreparedEndpoint endpoint; - - // Used for login_by_alias(...) - Uid sender_ephemeral_uid; - - // Used for AuthorizedApi::send_message(AeMessage{target_uid, payload}) - Uid target_uid; - - // Already derived client -> server key. - Key client_to_server_key; - - // Mutable nonce state. - CryptoNonce next_nonce; - std::uint32_t nonce_left = 0; - - // Debug only. -}; - -class PreparedSendMessageKeyProvider final : public ISyncKeyProvider { - public: - explicit PreparedSendMessageKeyProvider(PreparedSendMessageBlock& block) - : block_{&block} {} - - Key GetKey() const override { - return block_->client_to_server_key; - } - - CryptoNonce const& Nonce() const override { - return block_->next_nonce; - } - - private: - PreparedSendMessageBlock* block_; -}; - -inline EncodePacketResult EncodePacket(PreparedSendMessageBlock& block, - DataBuffer const& payload, - DataBuffer& out) { - if (block.nonce_left == 0) { - out.clear(); - return EncodePacketResult{EncodePacketError::kNonceExhausted, 0}; - } - - // Match existing ClientKeyProvider semantics: - // consume next nonce before encryption. - block.next_nonce.Next(); - --block.nonce_left; - - auto key_provider = - std::make_unique(block); - SyncEncryptProvider encrypt_provider{std::move(key_provider)}; - - ProtocolContext protocol_context; - LoginApi login_api{protocol_context, encrypt_provider}; - - auto api_context = ApiContext{login_api}; - - api_context->login_by_alias( - block.sender_ephemeral_uid, - SubApi{ - [&block, &payload](ApiContext& auth_api) { - auth_api->send_message( - AeMessage{block.target_uid, DataBuffer{payload}}); - }}); - - out = std::move(api_context).Pack(); - - return EncodePacketResult{EncodePacketError::kNone, out.size()}; -} - -// Existing internal seam. Keep normal Aether path working. -struct PreparedPacketContext { - std::uint64_t next_nonce = 0; - std::uint64_t nonce_limit = (std::numeric_limits::max)(); - - EncodePacketResult TakeNonce(std::size_t bytes_written) { - if (next_nonce == nonce_limit) { - return EncodePacketResult{EncodePacketError::kNonceExhausted, 0}; - } -++next_nonce; - - return EncodePacketResult{EncodePacketError::kNone, bytes_written}; - } -}; - -template -EncodePacketResult EncodePacket(PreparedPacketContext& prepared, - ApiContext&& data, - DataBuffer& out) { - out = std::move(data).Pack(); - return prepared.TakeNonce(out.size()); -} +EncodePacketResult EncodePacket(PreparedSendMessageBlock& block, + DataBuffer const& payload, + DataBuffer& out); } // namespace ae::prepared_packet diff --git a/aether/prepared_packet/prepared_send_message.h b/aether/prepared_packet/prepared_send_message.h new file mode 100644 index 00000000..efa30947 --- /dev/null +++ b/aether/prepared_packet/prepared_send_message.h @@ -0,0 +1,80 @@ +/* + * Prepared send_message block. + * + * This is transport-neutral state for encoding a send_message packet. + * It may contain an endpoint selected by the full Aether client, but it does + * not own sockets, DNS, connections, channels, or timers. + */ +#ifndef AETHER_PREPARED_PACKET_PREPARED_SEND_MESSAGE_H_ +#define AETHER_PREPARED_PACKET_PREPARED_SEND_MESSAGE_H_ + +#include +#include +#include + +#include "aether/types/address.h" +#include "aether/types/data_buffer.h" +#include "aether/types/uid.h" + +#include "aether/crypto/key.h" +#include "aether/crypto/crypto_nonce.h" + +namespace ae::prepared_packet { + +enum class PreparedIpVersion : std::uint8_t { + kIpV4 = 4, + kIpV6 = 6, +}; + +struct PreparedEndpoint { + PreparedIpVersion version = PreparedIpVersion::kIpV4; + Protocol protocol = Protocol::kUdp; + std::uint16_t port = 0; + + // IPv4 uses first 4 bytes. + // IPv6 uses all 16 bytes. + std::array ip{}; +}; + +struct PreparedSendMessageBlock { + PreparedEndpoint endpoint; + + Uid sender_ephemeral_uid; + Uid target_uid; + + Key client_to_server_key; + + CryptoNonce next_nonce; + std::uint32_t nonce_left = 0; +}; + +enum class EncodePacketError { + kNone = 0, + kNonceExhausted, + kEncodeFailed, +}; + +inline char const* ToString(EncodePacketError error) { + switch (error) { + case EncodePacketError::kNone: + return "none"; + case EncodePacketError::kNonceExhausted: + return "nonce_exhausted"; + case EncodePacketError::kEncodeFailed: + return "encode_failed"; + } + return "unknown"; +} + +struct EncodePacketResult { + EncodePacketError error = EncodePacketError::kNone; + std::size_t bytes_written = 0; + + explicit operator bool() const { + return error == EncodePacketError::kNone; + } +}; + +} // namespace ae::prepared_packet + +#endif // AETHER_PREPARED_PACKET_PREPARED_SEND_MESSAGE_H_ diff --git a/aether/server_connections/client_server_connection.h b/aether/server_connections/client_server_connection.h index aee479b6..39702362 100644 --- a/aether/server_connections/client_server_connection.h +++ b/aether/server_connections/client_server_connection.h @@ -32,7 +32,7 @@ #include "aether/work_cloud_api/work_server_api/authorized_api.h" #include "aether/server_connections/server_connection.h" -#include "aether/prepared_packet/packet_encoder.h" +#include "aether/prepared_packet/prepared_send_message.h" namespace ae { class Client; From 8b60c2f9113367f4b06091ec4006719cc79ab86a Mon Sep 17 00:00:00 2001 From: Nikolay Chirkov Date: Fri, 26 Jun 2026 11:03:59 +0300 Subject: [PATCH 04/16] Move prepared endpoint conversion into prepared packet module --- aether/CMakeLists.txt | 1 + .../prepared_packet/prepared_send_message.cpp | 41 +++++++++++++++++++ .../prepared_packet/prepared_send_message.h | 4 ++ .../client_server_connection.cpp | 31 ++------------ 4 files changed, 49 insertions(+), 28 deletions(-) create mode 100644 aether/prepared_packet/prepared_send_message.cpp diff --git a/aether/CMakeLists.txt b/aether/CMakeLists.txt index c8989bd9..55e2ff17 100644 --- a/aether/CMakeLists.txt +++ b/aether/CMakeLists.txt @@ -202,6 +202,7 @@ list(APPEND aether_srcs list(APPEND aether_srcs "server_connections/client_server_connection.cpp" + prepared_packet/prepared_send_message.cpp prepared_packet/packet_encoder.cpp "server_connections/channel_connection.cpp" "server_connections/server_connection.cpp") diff --git a/aether/prepared_packet/prepared_send_message.cpp b/aether/prepared_packet/prepared_send_message.cpp new file mode 100644 index 00000000..db965ee6 --- /dev/null +++ b/aether/prepared_packet/prepared_send_message.cpp @@ -0,0 +1,41 @@ +#include "aether/prepared_packet/prepared_send_message.h" + +#include + +namespace ae::prepared_packet { + +std::optional MakePreparedEndpoint(Endpoint const& endpoint) { + PreparedEndpoint candidate; + candidate.protocol = endpoint.protocol; + candidate.port = endpoint.port; + + bool is_ip = false; + + std::visit( + [&](auto const& addr) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + candidate.version = PreparedIpVersion::kIpV4; + for (std::size_t i = 0; i < 4; ++i) { + candidate.ip[i] = addr.ipv4_value[i]; + } + is_ip = true; + } else if constexpr (std::is_same_v) { + candidate.version = PreparedIpVersion::kIpV6; + for (std::size_t i = 0; i < 16; ++i) { + candidate.ip[i] = addr.ipv6_value[i]; + } + is_ip = true; + } + }, + endpoint.address); + + if (!is_ip) { + return std::nullopt; + } + + return candidate; +} + +} // namespace ae::prepared_packet diff --git a/aether/prepared_packet/prepared_send_message.h b/aether/prepared_packet/prepared_send_message.h index efa30947..345e97b1 100644 --- a/aether/prepared_packet/prepared_send_message.h +++ b/aether/prepared_packet/prepared_send_message.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "aether/types/address.h" #include "aether/types/data_buffer.h" @@ -75,6 +76,9 @@ struct EncodePacketResult { } }; + +std::optional MakePreparedEndpoint(Endpoint const& endpoint); + } // namespace ae::prepared_packet #endif // AETHER_PREPARED_PACKET_PREPARED_SEND_MESSAGE_H_ diff --git a/aether/server_connections/client_server_connection.cpp b/aether/server_connections/client_server_connection.cpp index 12a675d6..69e4fce4 100644 --- a/aether/server_connections/client_server_connection.cpp +++ b/aether/server_connections/client_server_connection.cpp @@ -222,34 +222,9 @@ ClientServerConnection::ExportPreparedSendMessageBlock( continue; } - prepared_packet::PreparedEndpoint candidate; - candidate.protocol = e.protocol; - candidate.port = e.port; - - bool is_ip = false; - - std::visit( - [&](auto const& addr) { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - candidate.version = prepared_packet::PreparedIpVersion::kIpV4; - for (std::size_t i = 0; i < 4; ++i) { - candidate.ip[i] = addr.ipv4_value[i]; - } - is_ip = true; - } else if constexpr (std::is_same_v) { - candidate.version = prepared_packet::PreparedIpVersion::kIpV6; - for (std::size_t i = 0; i < 16; ++i) { - candidate.ip[i] = addr.ipv6_value[i]; - } - is_ip = true; - } - }, - e.address); - - if (is_ip) { - prepared_endpoint = candidate; + auto endpoint = prepared_packet::MakePreparedEndpoint(e); + if (endpoint) { + prepared_endpoint = *endpoint; break; } } From 71a9151bd1e5afd6990175603cdf55a51f828e97 Mon Sep 17 00:00:00 2001 From: Nikolay Chirkov Date: Fri, 26 Jun 2026 11:27:44 +0300 Subject: [PATCH 05/16] Remove unused prepared packet encode error --- aether/prepared_packet/prepared_send_message.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/aether/prepared_packet/prepared_send_message.h b/aether/prepared_packet/prepared_send_message.h index 345e97b1..432d98c4 100644 --- a/aether/prepared_packet/prepared_send_message.h +++ b/aether/prepared_packet/prepared_send_message.h @@ -52,7 +52,6 @@ struct PreparedSendMessageBlock { enum class EncodePacketError { kNone = 0, kNonceExhausted, - kEncodeFailed, }; inline char const* ToString(EncodePacketError error) { @@ -61,8 +60,6 @@ inline char const* ToString(EncodePacketError error) { return "none"; case EncodePacketError::kNonceExhausted: return "nonce_exhausted"; - case EncodePacketError::kEncodeFailed: - return "encode_failed"; } return "unknown"; } From cd64d09af7c2b3e370faa4f6ea21e6a6c90ab8a9 Mon Sep 17 00:00:00 2001 From: Nikolay Chirkov Date: Fri, 26 Jun 2026 12:19:58 +0300 Subject: [PATCH 06/16] Add prepared send message API --- aether/CMakeLists.txt | 1 + .../prepared_packet/prepare_send_message.cpp | 12 +++++++++++ aether/prepared_packet/prepare_send_message.h | 21 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 aether/prepared_packet/prepare_send_message.cpp create mode 100644 aether/prepared_packet/prepare_send_message.h diff --git a/aether/CMakeLists.txt b/aether/CMakeLists.txt index 55e2ff17..1e40f059 100644 --- a/aether/CMakeLists.txt +++ b/aether/CMakeLists.txt @@ -203,6 +203,7 @@ list(APPEND aether_srcs list(APPEND aether_srcs "server_connections/client_server_connection.cpp" prepared_packet/prepared_send_message.cpp + prepared_packet/prepare_send_message.cpp prepared_packet/packet_encoder.cpp "server_connections/channel_connection.cpp" "server_connections/server_connection.cpp") diff --git a/aether/prepared_packet/prepare_send_message.cpp b/aether/prepared_packet/prepare_send_message.cpp new file mode 100644 index 00000000..45229f55 --- /dev/null +++ b/aether/prepared_packet/prepare_send_message.cpp @@ -0,0 +1,12 @@ +#include "aether/prepared_packet/prepare_send_message.h" + +#include "aether/client_messages/p2p_message_stream.h" + +namespace ae::prepared_packet { + +std::optional PrepareSendMessage( + P2pStream& stream, std::uint32_t reserve_nonce_count) { + return stream.ExportPreparedSendMessageBlock(reserve_nonce_count); +} + +} // namespace ae::prepared_packet diff --git a/aether/prepared_packet/prepare_send_message.h b/aether/prepared_packet/prepare_send_message.h new file mode 100644 index 00000000..52f1da23 --- /dev/null +++ b/aether/prepared_packet/prepare_send_message.h @@ -0,0 +1,21 @@ +#ifndef AETHER_PREPARED_PACKET_PREPARE_SEND_MESSAGE_H_ +#define AETHER_PREPARED_PACKET_PREPARE_SEND_MESSAGE_H_ + +#include +#include + +#include "aether/prepared_packet/prepared_send_message.h" + +namespace ae { + +class P2pStream; + +namespace prepared_packet { + +std::optional PrepareSendMessage( + P2pStream& stream, std::uint32_t reserve_nonce_count); + +} // namespace prepared_packet +} // namespace ae + +#endif From 536cce477a8c7a5040c0d8e701ca7a18616fd541 Mon Sep 17 00:00:00 2001 From: Nikolay Chirkov Date: Wed, 1 Jul 2026 16:16:56 +0300 Subject: [PATCH 07/16] Remove FASTTX_STEP debug marker from api_call_adapter. Co-authored-by: Cursor --- aether/stream_api/api_call_adapter.h | 1 - 1 file changed, 1 deletion(-) diff --git a/aether/stream_api/api_call_adapter.h b/aether/stream_api/api_call_adapter.h index 7ef95ca5..f8f42a71 100644 --- a/aether/stream_api/api_call_adapter.h +++ b/aether/stream_api/api_call_adapter.h @@ -31,7 +31,6 @@ namespace ae { -// FASTTX_STEP3_ENCODE_PACKET_API namespace fast_tx_internal { enum class EncodePacketError { From d5f1fc7c9daa776adc25039177c0c93a6eaecb28 Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Thu, 2 Jul 2026 14:58:42 +0300 Subject: [PATCH 08/16] Fix bugs. --- .../prepared_packet/prepared_send_message.h | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/aether/prepared_packet/prepared_send_message.h b/aether/prepared_packet/prepared_send_message.h index 432d98c4..fff70874 100644 --- a/aether/prepared_packet/prepared_send_message.h +++ b/aether/prepared_packet/prepared_send_message.h @@ -21,6 +21,19 @@ #include "aether/crypto/crypto_nonce.h" namespace ae::prepared_packet { +static constexpr std::uint32_t kMagic = 0x50534456; // "PSDV" +static constexpr std::uint32_t kVersion = 1; +// Serialized PreparedSendMessageBlock is a few hundred bytes. Keep the RTC +// footprint small enough for ESP32 RTC slow memory (8 KiB on ESP32-C6). +static constexpr std::size_t kMaxPreparedBlockBytes = 512; + +struct RetainedPreparedBlock { + std::uint32_t magic; + std::uint32_t version; + std::uint32_t size; + std::uint32_t checksum; + std::array bytes; +}; enum class PreparedIpVersion : std::uint8_t { kIpV4 = 4, @@ -28,6 +41,7 @@ enum class PreparedIpVersion : std::uint8_t { }; struct PreparedEndpoint { + AE_REFLECT_MEMBERS(version, protocol, port, ip) PreparedIpVersion version = PreparedIpVersion::kIpV4; Protocol protocol = Protocol::kUdp; std::uint16_t port = 0; @@ -38,6 +52,8 @@ struct PreparedEndpoint { }; struct PreparedSendMessageBlock { + AE_REFLECT_MEMBERS(endpoint, sender_ephemeral_uid, target_uid, + client_to_server_key, next_nonce, nonce_left) PreparedEndpoint endpoint; Uid sender_ephemeral_uid; @@ -68,12 +84,9 @@ struct EncodePacketResult { EncodePacketError error = EncodePacketError::kNone; std::size_t bytes_written = 0; - explicit operator bool() const { - return error == EncodePacketError::kNone; - } + explicit operator bool() const { return error == EncodePacketError::kNone; } }; - std::optional MakePreparedEndpoint(Endpoint const& endpoint); } // namespace ae::prepared_packet From d5e89e02b831495324cea951ea46e99f31bae8c7 Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Thu, 9 Jul 2026 12:14:30 +0300 Subject: [PATCH 09/16] Add windows_message_receiver example. --- CMakeLists.txt | 3 + .../windows_message_receiver/CMakeLists.txt | 50 +++++ .../config/cloud_config.ini | 38 ++++ .../windows_message_receiver/user_config.h | 39 ++++ .../windows_message_receiver.cpp | 192 ++++++++++++++++++ 5 files changed, 322 insertions(+) create mode 100644 examples/windows_message_receiver/CMakeLists.txt create mode 100644 examples/windows_message_receiver/config/cloud_config.ini create mode 100644 examples/windows_message_receiver/user_config.h create mode 100644 examples/windows_message_receiver/windows_message_receiver.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 150d9446..562f4285 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -365,6 +365,9 @@ if(AE_BUILD_EXAMPLES) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(examples/cloud) + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_subdirectory(examples/windows_message_receiver) + endif() add_subdirectory(examples/capi/oddity) add_subdirectory(examples/benches/send_message_delays) add_subdirectory(examples/benches/send_messages_bandwidth) diff --git a/examples/windows_message_receiver/CMakeLists.txt b/examples/windows_message_receiver/CMakeLists.txt new file mode 100644 index 00000000..ede10f15 --- /dev/null +++ b/examples/windows_message_receiver/CMakeLists.txt @@ -0,0 +1,50 @@ +# 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. + +cmake_minimum_required(VERSION 3.16.0) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +list( APPEND src_list + windows_message_receiver.cpp +) + +if(NOT CM_PLATFORM) + project("aether-client-cpp-windows_message_receiver" VERSION "1.0.0" LANGUAGES C CXX) + + add_executable(${PROJECT_NAME} ${src_list}) + target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(${PROJECT_NAME} PRIVATE aether) +else() + idf_build_get_property(CM_PLATFORM CM_PLATFORM) + if(CM_PLATFORM STREQUAL "ESP32") + #ESP32 CMake + idf_component_register(SRCS ${src_list} + INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} + REQUIRES + esp_wifi + esp_netif + nvs_flash + spiffs + esp_driver_uart + ) + + add_subdirectory("../../" aether) + target_link_libraries(${COMPONENT_LIB} PRIVATE aether) + else() + #Other platforms + message(FATAL_ERROR "Platform ${CM_PLATFORM} is not supported") + endif() +endif() diff --git a/examples/windows_message_receiver/config/cloud_config.ini b/examples/windows_message_receiver/config/cloud_config.ini new file mode 100644 index 00000000..0c275714 --- /dev/null +++ b/examples/windows_message_receiver/config/cloud_config.ini @@ -0,0 +1,38 @@ +; Static configuration for cloud test +; Register to save state as a header file by +; aether-registrator +; Use header file to build the client test cmake -DFS_INIT= . && cmake --build . + +; Required section +; Aether configuration +[Aether] +; Must have one of the following keys or both depending on the project configuration \see aether/config.h +ed25519_sign_key = 4F202A94AB729FE9B381613AE77A8A7D89EDAB9299C3320D1A0B994BA710CCEB +hydrogen_sign_key = 883B4D7E0FB04A38CA12B3A451B00942048858263EE6E6D61150F2EF15F40343 + +; Optional section +; If provided, wifi adapter would be configured and saved to the state +[AdapterWifi] +ssid = Test123 +pass = Test123 + +; Must have at least one registration server +; Registration server configuration +; Supported protocols: kTcp, kUdp +[RegServer_1] +address = registration.aethernet.io +port=9010 +protocol=kTcp + +[RegServer_2] +address = 34.60.244.148 +port=9010 +protocol=kTcp + +; List of clients +; Suffix after _ is used as user defined client id \see Aether::SelectClient +[Client_A] +parent_uid = 3ac93165-3d37-4970-87a6-fa4ee27744e4 + +[Client_B] +parent_uid = 3ac93165-3d37-4970-87a6-fa4ee27744e4 diff --git a/examples/windows_message_receiver/user_config.h b/examples/windows_message_receiver/user_config.h new file mode 100644 index 00000000..a2fd7142 --- /dev/null +++ b/examples/windows_message_receiver/user_config.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef USER_CONFIG_H_ +#define USER_CONFIG_H_ + +#include "aether/config_consts.h" +/** + * \brief For full config list and default values \see aether/config.h + */ + +// use hydrogen encryption +#define AE_CRYPTO_ASYNC AE_HYDRO_CRYPTO_PK +#define AE_CRYPTO_SYNC AE_HYDRO_CRYPTO_SK +#define AE_SIGNATURE AE_HYDRO_SIGNATURE +#define AE_KDF AE_HYDRO_KDF + +// disable debug telemetry on release +#define AE_TELE_ENABLED 1 +#define AE_TELE_LOG_CONSOLE 1 +#if defined NDEBUG +# define AE_TELE_DEBUG_MODULES 0 +#else +# define AE_TELE_DEBUG_MODULES AE_ALL +#endif +#endif // USER_CONFIG_H_ diff --git a/examples/windows_message_receiver/windows_message_receiver.cpp b/examples/windows_message_receiver/windows_message_receiver.cpp new file mode 100644 index 00000000..ada54558 --- /dev/null +++ b/examples/windows_message_receiver/windows_message_receiver.cpp @@ -0,0 +1,192 @@ +/* + * 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 +#include + +#include "aether/all.h" + +static constexpr auto kParentUid = + ae::Uid::FromString("3ac93165-3d37-4970-87a6-fa4ee27744e4"); + +class TimeSynchronizer { + public: + TimeSynchronizer() = default; + + void SetPingSentTime(ae::TimePoint ping_sent_time); + void SetPongSentTime(ae::TimePoint pong_sent_time); + + ae::Duration GetPingDuration() const; + ae::Duration GetPongDuration() const; + + private: + ae::TimePoint ping_sent_time_; + ae::TimePoint pong_sent_time_; +}; + +// Alice sends "ping"s to Bob +/* class Alice { + public: + explicit Alice(ae::AetherApp& aether_app, ae::Client::ptr client_alice, + TimeSynchronizer& time_synchronizer, ae::Uid bobs_uid); + + private: + void SendMessage(); + void ResponseReceived(ae::DataBuffer const& data_buffer); + + ae::AetherApp* aether_app_; + ae::Client::ptr client_alice_; + TimeSynchronizer* time_synchronizer_; + ae::P2pStream p2pstream_; + ae::RepeatableTask interval_sender_; + ae::Subscription receive_data_sub_; + ae::MultiSubscription send_subs_; +};*/ + +// Bob answers "pong" to each "ping" +class Bob { + public: + explicit Bob(ae::AetherApp& aether_app, ae::Client::ptr client_bob, + TimeSynchronizer& time_synchronizer); + + private: + void OnNewStream(ae::P2pPortHandle p2p_port); + void OnMessageReceived(ae::DataBuffer const& data_buffer); + + ae::AetherApp* aether_app_; + ae::Client::ptr client_bob_; + TimeSynchronizer* time_synchronizer_; + std::unique_ptr p2pstream_; + ae::Subscription new_stream_receive_sub_; + ae::Subscription message_receive_sub_; +}; + +int main() { + auto aether_app = ae::AetherApp::Construct(ae::AetherAppContext{}); + + //std::unique_ptr alice; + std::unique_ptr bob; + TimeSynchronizer time_synchronizer; + + // register or load clients + auto& bob_select = aether_app->aether()->SelectClient(kParentUid, "Bob"); + bob_select.result_event().Subscribe([&](auto const& bob_res) { + if (bob_res) { + bob = + ae::make_unique(*aether_app, bob_res.value(), time_synchronizer); + /* auto& alice_select = + aether_app->aether()->SelectClient(kParentUid, "Alice"); + alice_select.result_event().Subscribe( + [&, uid = bob_res.value()->uid()](auto const& alice_res) { + if (alice_res) { + alice = ae::make_unique(*aether_app, alice_res.value(), + time_synchronizer, uid); + // Save the current aether state + aether_app->aether().Save(); + } else { + aether_app->Exit(1); + } + });*/ + } else { + aether_app->Exit(1); + } + }); + + while (!aether_app->IsExited()) { + auto next_time = aether_app->Update(ae::Now()); + aether_app->WaitUntil(next_time); + } + return aether_app->ExitCode(); +} + +void TimeSynchronizer::SetPingSentTime(ae::TimePoint ping_sent_time) { + ping_sent_time_ = ping_sent_time; +} +void TimeSynchronizer::SetPongSentTime(ae::TimePoint pong_sent_time) { + pong_sent_time_ = pong_sent_time; +} +ae::Duration TimeSynchronizer::GetPingDuration() const { + return std::chrono::duration_cast(ae::Now() - ping_sent_time_); +} +ae::Duration TimeSynchronizer::GetPongDuration() const { + return std::chrono::duration_cast(ae::Now() - pong_sent_time_); +} + +/* Alice::Alice(ae::AetherApp& aether_app, ae::Client::ptr client_alice, + TimeSynchronizer& time_synchronizer, ae::Uid bobs_uid) + : aether_app_{&aether_app}, + client_alice_{std::move(client_alice)}, + time_synchronizer_{&time_synchronizer}, + p2pstream_{*aether_app_, client_alice_.Load(), bobs_uid, + client_alice_->message_stream_manager().CreatePort(bobs_uid)}, + interval_sender_{*aether_app_, [this]() { SendMessage(); }, + std::chrono::seconds{5}}, + receive_data_sub_{p2pstream_.out_data_event().Subscribe( + ae::MethodPtr<&Alice::ResponseReceived>{this})} {} + +void Alice::SendMessage() { + auto current_time = ae::Now(); + constexpr std::string_view ping_message = "ping"; + + time_synchronizer_->SetPingSentTime(current_time); + + std::cout << ae::Format("[{:%H:%M:%S}] Alice sends \"ping\"'\n", ae::Now()); + p2pstream_.Write({std::begin(ping_message), std::end(ping_message)}); +} + +void Alice::ResponseReceived(ae::DataBuffer const& data_buffer) { + auto pong_message = std::string_view{ + reinterpret_cast(data_buffer.data()), data_buffer.size()}; + std::cout << ae::Format( + "[{:%H:%M:%S}] Alice received \"{}\" within time {} ms\n", ae::Now(), + pong_message, + std::chrono::duration_cast( + time_synchronizer_->GetPongDuration()) + .count()); +}*/ + +Bob::Bob(ae::AetherApp& aether_app, ae::Client::ptr client_bob, + TimeSynchronizer& time_synchronizer) + : aether_app_{&aether_app}, + client_bob_{std::move(client_bob)}, + time_synchronizer_{&time_synchronizer}, + new_stream_receive_sub_{ + client_bob_->message_stream_manager().new_port_event().Subscribe( + ae::MethodPtr<&Bob::OnNewStream>{this})} {} + +void Bob::OnNewStream(ae::P2pPortHandle p2p_port) { + p2pstream_ = std::make_unique(*aether_app_, client_bob_.Load(), + p2p_port.destination(), + std::move(p2p_port)); + message_receive_sub_ = p2pstream_->out_data_event().Subscribe( + ae::MethodPtr<&Bob::OnMessageReceived>{this}); +} + +void Bob::OnMessageReceived(ae::DataBuffer const& data_buffer) { + auto ping_message = std::string_view{ + reinterpret_cast(data_buffer.data()), data_buffer.size()}; + std::cout << ae::Format( + "[{:%H:%M:%S}] Bob received \"{}\" within time {} ms\n", ae::Now(), + ping_message, + std::chrono::duration_cast( + time_synchronizer_->GetPingDuration()) + .count()); + std::cout << ae::Format("Hex string [{}]\n", data_buffer); + time_synchronizer_->SetPongSentTime(ae::Now()); + constexpr std::string_view pong_message = "pong"; + //std::cout << ae::Format("[{:%H:%M:%S}] Bob sends \"pong\"\n", ae::Now()); + //p2pstream_->Write({std::begin(pong_message), std::end(pong_message)}); +} From 293218059ff92f3aad670edda0318231e9093d31 Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Thu, 9 Jul 2026 17:02:56 +0300 Subject: [PATCH 10/16] Fixing zero packet send. --- aether/client_messages/p2p_message_stream.cpp | 12 +++++ .../cloud_server_connections.cpp | 6 +++ .../client_server_connection.cpp | 9 ++++ .../server_connections/server_connection.cpp | 5 ++ aether/stream_api/api_call_adapter.h | 6 ++- aether/transport/system_sockets/udp/udp.h | 2 + aether/wifi/esp_wifi_driver.cpp | 13 +++-- aether/write_action/buffer_write.h | 51 ++++++++++++++++++- 8 files changed, 98 insertions(+), 6 deletions(-) diff --git a/aether/client_messages/p2p_message_stream.cpp b/aether/client_messages/p2p_message_stream.cpp index f6180492..d3824b6c 100644 --- a/aether/client_messages/p2p_message_stream.cpp +++ b/aether/client_messages/p2p_message_stream.cpp @@ -43,8 +43,15 @@ class MessageSendStream final : public IStream { } WriteAction& Write(AeMessage&& message) override { + AE_TELED_ERROR("[CALL-CHAIN] MessageSendStream::Write data_size={} " + "cloud_connections={}", + message.data.size(), cloud_connection_->count_connections()); return cloud_connection_->CallApi( ApiCall{[&message](ApiContext& auth_api, auto*) { + AE_TELED_ERROR( + "[CALL-CHAIN] MessageSendStream::ApiCall send_message " + "data_size={}", + message.data.size()); auth_api->send_message(std::move(message)); }}, request_policy_); @@ -187,9 +194,12 @@ P2pStream::P2pStream(AeContext const& ae_context, Ptr const& client, P2pStream::~P2pStream() = default; WriteAction& P2pStream::Write(DataBuffer&& data) { + AE_TELED_ERROR("[CALL-CHAIN] P2pStream::Write input_size={}", data.size()); AE_TELED_DEBUG("Write message for uid {} size:{} data:{}", destination_, data.size(), data); AeMessage message_data{destination_, std::move(data)}; + AE_TELED_ERROR("[CALL-CHAIN] P2pStream::Write message_data_size={}", + message_data.data.size()); return buffer_write_.Write(std::move(message_data)); } @@ -278,6 +288,8 @@ std::unique_ptr P2pStream::MakeDestinationCloudConn( } WriteAction* P2pStream::OnWrite(AeMessage&& message) { + AE_TELED_ERROR("[CALL-CHAIN] P2pStream::OnWrite data_size={} connected={}", + message.data.size(), message_send_stream_ != nullptr); if (!message_send_stream_) { return {}; } diff --git a/aether/cloud_connections/cloud_server_connections.cpp b/aether/cloud_connections/cloud_server_connections.cpp index 576b97d8..0d246f21 100644 --- a/aether/cloud_connections/cloud_server_connections.cpp +++ b/aether/cloud_connections/cloud_server_connections.cpp @@ -275,15 +275,21 @@ std::vector CloudServerConnections::ServerCandidates() { WriteAction& CloudServerConnections::CallApi(ApiCall const& api_caller, RequestPolicy::Variant policy) { + AE_TELED_ERROR("[CALL-CHAIN] CloudServerConnections::CallApi selected={}", + selected_servers_.size()); std::vector swas; ForServers( [&](CloudServerConnection* sc) { auto* conn = sc->client_connection(); assert((conn != nullptr) && "Client connection is null"); + AE_TELED_ERROR("[CALL-CHAIN] CloudServerConnections::CallApi server={}", + sc->server()->server_id); swas.emplace_back(&conn->AuthorizedApiCall( SubApi{[&](auto& api) { api_caller(api, sc); }})); }, policy); + AE_TELED_ERROR("[CALL-CHAIN] CloudServerConnections::CallApi writes={}", + swas.size()); if (swas.empty()) { return EmptyWriteAction(); diff --git a/aether/server_connections/client_server_connection.cpp b/aether/server_connections/client_server_connection.cpp index 07f6540e..3c714538 100644 --- a/aether/server_connections/client_server_connection.cpp +++ b/aether/server_connections/client_server_connection.cpp @@ -98,6 +98,11 @@ BufferedServerConnection::BufferedServerConnection(AeContext const& ae_context, Ptr const& server) : buffer_write{ae_context, [&](DataBuffer&& in_data) -> WriteAction* { + AE_TELED_ERROR( + "[CALL-CHAIN] BufferedServerConnection::direct " + "data_size={} writable={}", + in_data.size(), + server_connection.stream_info().is_writable); if (server_connection.stream_info().is_writable) { return &server_connection.Write(std::move(in_data)); } @@ -121,6 +126,8 @@ BufferedServerConnection::BufferedServerConnection(AeContext const& ae_context, } WriteAction& BufferedServerConnection::Write(DataBuffer&& in_data) { + AE_TELED_ERROR("[CALL-CHAIN] BufferedServerConnection::Write data_size={}", + in_data.size()); return buffer_write.Write(std::move(in_data)); } @@ -186,8 +193,10 @@ WriteAction& ClientServerConnection::LoginApiCall(SubApi login_api) { WriteAction& ClientServerConnection::AuthorizedApiCall( SubApi auth_api) { + AE_TELED_ERROR("[CALL-CHAIN] ClientServerConnection::AuthorizedApiCall begin"); auto api_call = ApiCallAdapter{ApiContext{login_api_}, server_connection_}; api_call->login_by_alias(ephemeral_uid_, std::move(auth_api)); + AE_TELED_ERROR("[CALL-CHAIN] ClientServerConnection::AuthorizedApiCall flush"); // cppcheck reports false positive // cppcheck-suppress returnReference return api_call.Flush(); diff --git a/aether/server_connections/server_connection.cpp b/aether/server_connections/server_connection.cpp index cd0100df..e871992f 100644 --- a/aether/server_connections/server_connection.cpp +++ b/aether/server_connections/server_connection.cpp @@ -34,9 +34,14 @@ ServerConnection::ServerConnection(AeContext const& ae_context, } WriteAction& ServerConnection::Write(DataBuffer&& in_data) { + AE_TELED_ERROR("[CALL-CHAIN] ServerConnection::Write data_size={} " + "top_channel={}", + in_data.size(), top_channel_ != nullptr); assert((top_channel_ != nullptr) && "channel connection is not available"); auto* stream = top_channel_->connection.stream(); + AE_TELED_ERROR("[CALL-CHAIN] ServerConnection::Write stream={}", + stream != nullptr); assert((stream != nullptr) && "channel stream is not available"); return stream->Write(std::move(in_data)); diff --git a/aether/stream_api/api_call_adapter.h b/aether/stream_api/api_call_adapter.h index f8f42a71..28c6e782 100644 --- a/aether/stream_api/api_call_adapter.h +++ b/aether/stream_api/api_call_adapter.h @@ -28,6 +28,7 @@ #include "aether/api_protocol/api_context.h" #include "aether/prepared_packet/packet_encoder.h" #include "aether/types/data_buffer.h" +#include "aether/tele/tele.h" namespace ae { @@ -110,7 +111,10 @@ EncodePacketResult EncodePacket(PreparedPacketEncoder& prepared, return EncodePacketResult{report, nullptr}; } - auto& action = out.Write(std::move(data)); + auto packet = std::move(data).Pack(); + AE_TELED_ERROR("[CALL-CHAIN] ApiCallAdapter::EncodePacket packet_size={}", + packet.size()); + auto& action = out.Write(std::move(packet)); return EncodePacketResult{report, &action}; } diff --git a/aether/transport/system_sockets/udp/udp.h b/aether/transport/system_sockets/udp/udp.h index 9e574c9b..85d6bd31 100644 --- a/aether/transport/system_sockets/udp/udp.h +++ b/aether/transport/system_sockets/udp/udp.h @@ -157,6 +157,8 @@ class UdpTransport final : public upd_internal::UdpBase { } WriteAction& Write(DataBuffer&& in_data) override { + AE_TELED_ERROR("[CALL-CHAIN] UdpTransport::Write endpoint={} data_size={}", + endpoint_, in_data.size()); assert(in_data.size() != 0); AE_TELE_DEBUG(kUdpTransportSend, "Socket {} send data size:{}", endpoint_, in_data.size()); diff --git a/aether/wifi/esp_wifi_driver.cpp b/aether/wifi/esp_wifi_driver.cpp index 50d451ac..439ec72a 100644 --- a/aether/wifi/esp_wifi_driver.cpp +++ b/aether/wifi/esp_wifi_driver.cpp @@ -79,7 +79,7 @@ void EventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, } } -void SetupBssid(wifi_config_t& wifi_config, +esp_err_t SetupBssid(wifi_config_t& wifi_config, WiFiBaseStation const& base_station) { std::array debug_bssid; memcpy(debug_bssid.data(), base_station.target_bssid, @@ -93,8 +93,9 @@ void SetupBssid(wifi_config_t& wifi_config, // Copy the BSSID to the configuration memcpy(wifi_config.sta.bssid, base_station.target_bssid, sizeof(base_station.target_bssid)); - ESP_ERROR_CHECK( - esp_wifi_set_channel(base_station.target_channel, WIFI_SECOND_CHAN_NONE)); + auto err = esp_wifi_set_channel(base_station.target_channel, WIFI_SECOND_CHAN_NONE); + + return err; } void SetupCredentials(wifi_config_t& wifi_config, WifiCreds const& creds) { @@ -210,7 +211,11 @@ void StartWifiConnection(esp_netif_t* espt_init_sta, WiFiAp const& wifi_ap, wifi_config_t wifi_config{}; if (base_station) { // Restore saved Base Station - esp_wifi_driver_internal::SetupBssid(wifi_config, *base_station); + auto err = esp_wifi_driver_internal::SetupBssid(wifi_config, *base_station); + if (err != ESP_OK) { + AE_TELED_ERROR("Failed to set BSSID"); + // If an error occurs, exit + } } wifi_scan_threshold_t wifi_threshold{}; diff --git a/aether/write_action/buffer_write.h b/aether/write_action/buffer_write.h index 3a18370e..be1909fa 100644 --- a/aether/write_action/buffer_write.h +++ b/aether/write_action/buffer_write.h @@ -68,8 +68,20 @@ class BufferWrite { struct BufferEntry { BufferedWriteAction wa; T data; + bool sent = false; }; + template + static std::size_t DebugPayloadSize(TValue const& value) { + if constexpr (requires { value.size(); }) { + return value.size(); + } else if constexpr (requires { value.data.size(); }) { + return value.data.size(); + } else { + return 0; + } + } + public: using DirectWriteFunc = SmallFunction; @@ -82,8 +94,14 @@ class BufferWrite { /** * \brief Control buffering. */ - void buffer_on() { buffer_on_ = true; } + void buffer_on() { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::buffer_on buffered={}", + buffer_.size()); + buffer_on_ = true; + } void buffer_off() { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::buffer_off buffered={}", + buffer_.size()); buffer_on_ = false; DrainBuffer(); } @@ -93,6 +111,9 @@ class BufferWrite { * \brief Write data to or through buffer. */ WriteAction& Write(T&& data) { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::Write payload_size={} " + "buffer_on={} buffered={}", + DebugPayloadSize(data), buffer_on_, buffer_.size()); auto should_be_buffered = buffer_on_ || !buffer_.empty(); if (should_be_buffered) { return WriteToBuffer(std::move(data)); @@ -114,6 +135,9 @@ class BufferWrite { private: WriteAction& WriteToBuffer(T&& data) { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::WriteToBuffer payload_size={} " + "buffer_on={} buffered={}", + DebugPayloadSize(data), buffer_on_, buffer_.size()); if (buffer_.full()) { BW_LOG_WARNING("Buffer is full"); return FailedWrite(); @@ -135,8 +159,13 @@ class BufferWrite { } WriteAction& DirectWrite(T&& data) { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DirectWrite payload_size={} " + "buffered={}", + DebugPayloadSize(data), buffer_.size()); BW_LOG_DEBUG("BufferWrite: direct write"); auto* write_action = direct_write_(std::move(data)); + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DirectWrite result={}", + write_action != nullptr); if (write_action == nullptr) { return FailedWrite(); } @@ -147,24 +176,44 @@ class BufferWrite { * Write all buffered data in FIFO order. */ void DrainBuffer() { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer begin buffer_on={} " + "buffered={}", + buffer_on_, buffer_.size()); BW_LOG_DEBUG("BufferWrite: drain the buffer, size {}", buffer_.size()); // try send as many as possible for (auto& be : buffer_) { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer entry finished={} " + "sent={} payload_size={}", + be.wa.is_finished(), be.sent, DebugPayloadSize(be.data)); if (be.wa.is_finished()) { // notice! circular buffer pop is just incrementing the out pointer it // does not invalidate the iterators buffer_.pop(); continue; } + if (be.sent) { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer skip in-flight " + "payload_size={}", + DebugPayloadSize(be.data)); + continue; + } // buffer state might change during direct write if (buffer_on_) { + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer stop buffer_on"); break; } + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer direct before_move " + "payload_size={}", + DebugPayloadSize(be.data)); auto* dwa = direct_write_(std::move(be.data)); + BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer direct result={} " + "after_move_payload_size={}", + dwa != nullptr, DebugPayloadSize(be.data)); // empty write action means no data has been written if (dwa == nullptr) { break; } + be.sent = true; be.wa.Sent(*dwa); } } From 1c5649fdfd3c202226b00c3897a4ce773ba7db17 Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Fri, 10 Jul 2026 16:50:44 +0300 Subject: [PATCH 11/16] Removing comments. --- .../windows_message_receiver.cpp | 50 +------------------ 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/examples/windows_message_receiver/windows_message_receiver.cpp b/examples/windows_message_receiver/windows_message_receiver.cpp index ada54558..55531d32 100644 --- a/examples/windows_message_receiver/windows_message_receiver.cpp +++ b/examples/windows_message_receiver/windows_message_receiver.cpp @@ -87,19 +87,6 @@ int main() { if (bob_res) { bob = ae::make_unique(*aether_app, bob_res.value(), time_synchronizer); - /* auto& alice_select = - aether_app->aether()->SelectClient(kParentUid, "Alice"); - alice_select.result_event().Subscribe( - [&, uid = bob_res.value()->uid()](auto const& alice_res) { - if (alice_res) { - alice = ae::make_unique(*aether_app, alice_res.value(), - time_synchronizer, uid); - // Save the current aether state - aether_app->aether().Save(); - } else { - aether_app->Exit(1); - } - });*/ } else { aether_app->Exit(1); } @@ -125,39 +112,6 @@ ae::Duration TimeSynchronizer::GetPongDuration() const { return std::chrono::duration_cast(ae::Now() - pong_sent_time_); } -/* Alice::Alice(ae::AetherApp& aether_app, ae::Client::ptr client_alice, - TimeSynchronizer& time_synchronizer, ae::Uid bobs_uid) - : aether_app_{&aether_app}, - client_alice_{std::move(client_alice)}, - time_synchronizer_{&time_synchronizer}, - p2pstream_{*aether_app_, client_alice_.Load(), bobs_uid, - client_alice_->message_stream_manager().CreatePort(bobs_uid)}, - interval_sender_{*aether_app_, [this]() { SendMessage(); }, - std::chrono::seconds{5}}, - receive_data_sub_{p2pstream_.out_data_event().Subscribe( - ae::MethodPtr<&Alice::ResponseReceived>{this})} {} - -void Alice::SendMessage() { - auto current_time = ae::Now(); - constexpr std::string_view ping_message = "ping"; - - time_synchronizer_->SetPingSentTime(current_time); - - std::cout << ae::Format("[{:%H:%M:%S}] Alice sends \"ping\"'\n", ae::Now()); - p2pstream_.Write({std::begin(ping_message), std::end(ping_message)}); -} - -void Alice::ResponseReceived(ae::DataBuffer const& data_buffer) { - auto pong_message = std::string_view{ - reinterpret_cast(data_buffer.data()), data_buffer.size()}; - std::cout << ae::Format( - "[{:%H:%M:%S}] Alice received \"{}\" within time {} ms\n", ae::Now(), - pong_message, - std::chrono::duration_cast( - time_synchronizer_->GetPongDuration()) - .count()); -}*/ - Bob::Bob(ae::AetherApp& aether_app, ae::Client::ptr client_bob, TimeSynchronizer& time_synchronizer) : aether_app_{&aether_app}, @@ -179,7 +133,7 @@ void Bob::OnMessageReceived(ae::DataBuffer const& data_buffer) { auto ping_message = std::string_view{ reinterpret_cast(data_buffer.data()), data_buffer.size()}; std::cout << ae::Format( - "[{:%H:%M:%S}] Bob received \"{}\" within time {} ms\n", ae::Now(), + ">>>\n>>>\n>>>\n[{:%H:%M:%S}] Bob received \"{}\" within time {} ms\n>>>\n>>>\n>>>\n", ae::Now(), ping_message, std::chrono::duration_cast( time_synchronizer_->GetPingDuration()) @@ -187,6 +141,4 @@ void Bob::OnMessageReceived(ae::DataBuffer const& data_buffer) { std::cout << ae::Format("Hex string [{}]\n", data_buffer); time_synchronizer_->SetPongSentTime(ae::Now()); constexpr std::string_view pong_message = "pong"; - //std::cout << ae::Format("[{:%H:%M:%S}] Bob sends \"pong\"\n", ae::Now()); - //p2pstream_->Write({std::begin(pong_message), std::end(pong_message)}); } From 60ceb17ca808d83dc0937a4af358f7a07a703a1a Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Mon, 13 Jul 2026 15:16:05 +0300 Subject: [PATCH 12/16] Fixing CMake. --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 562f4285..afe913f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,8 +151,7 @@ CPMAddPackage( CPMAddPackage( NAME stdexec GIT_REPOSITORY "https://github.com/aethernetio/stdexec.git" -# TODO: switch to main - GIT_TAG "2091-compilation-failed-with-fno-exception" + GIT_TAG "main" OPTIONS "STDEXEC_BUILD_EXAMPLES OFF" "STDEXEC_INSTALL ${AE_INSTALL}" EXCLUDE_FROM_ALL FALSE ) From 420f278672d9cc582d30871f0c98ce361a454488 Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Tue, 14 Jul 2026 10:20:38 +0300 Subject: [PATCH 13/16] New parent UID. --- examples/windows_message_receiver/windows_message_receiver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/windows_message_receiver/windows_message_receiver.cpp b/examples/windows_message_receiver/windows_message_receiver.cpp index 55531d32..647760fe 100644 --- a/examples/windows_message_receiver/windows_message_receiver.cpp +++ b/examples/windows_message_receiver/windows_message_receiver.cpp @@ -20,7 +20,7 @@ #include "aether/all.h" static constexpr auto kParentUid = - ae::Uid::FromString("3ac93165-3d37-4970-87a6-fa4ee27744e4"); + ae::Uid::FromString("B1AC52C8-8D94-BD39-4C01-A631AC594165"); class TimeSynchronizer { public: From 0227e9710eed5f376befa6af8e79af0db2899a39 Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Wed, 12 Aug 2026 12:43:14 +0300 Subject: [PATCH 14/16] Removing debug output. --- aether/client_messages/p2p_message_stream.cpp | 17 ++------- .../cloud_server_connections.cpp | 6 ---- .../client_server_connection.cpp | 11 ------ .../server_connections/server_connection.cpp | 5 --- aether/stream_api/api_call_adapter.h | 11 +++--- aether/transport/system_sockets/udp/udp.h | 4 +-- aether/write_action/buffer_write.h | 35 +------------------ examples/cloud/aether_construct_esp_wifi.h | 4 +-- 8 files changed, 10 insertions(+), 83 deletions(-) diff --git a/aether/client_messages/p2p_message_stream.cpp b/aether/client_messages/p2p_message_stream.cpp index 1c9355aa..a1e9905b 100644 --- a/aether/client_messages/p2p_message_stream.cpp +++ b/aether/client_messages/p2p_message_stream.cpp @@ -43,15 +43,8 @@ class MessageSendStream final : public IStream { } WriteAction& Write(AeMessage&& message) override { - AE_TELED_ERROR("[CALL-CHAIN] MessageSendStream::Write data_size={} " - "cloud_connections={}", - message.data.size(), cloud_connection_->count_connections()); return cloud_connection_->CallApi( ApiCall{[&message](ApiContext& auth_api, auto*) { - AE_TELED_ERROR( - "[CALL-CHAIN] MessageSendStream::ApiCall send_message " - "data_size={}", - message.data.size()); auth_api->send_message(std::move(message)); }}, request_policy_); @@ -70,8 +63,8 @@ class MessageSendStream final : public IStream { continue; } - auto block = conn->ExportPreparedSendMessageBlock(target_uid, - reserve_nonce_count); + auto block = + conn->ExportPreparedSendMessageBlock(target_uid, reserve_nonce_count); if (block) { return block; } @@ -196,12 +189,9 @@ P2pStream::P2pStream(AeContext const& ae_context, Ptr const& client, P2pStream::~P2pStream() = default; WriteAction& P2pStream::Write(DataBuffer&& data) { - AE_TELED_ERROR("[CALL-CHAIN] P2pStream::Write input_size={}", data.size()); AE_TELED_DEBUG("Write message for uid {} size:{} data:{}", destination_, data.size(), data); AeMessage message_data{destination_, std::move(data)}; - AE_TELED_ERROR("[CALL-CHAIN] P2pStream::Write message_data_size={}", - message_data.data.size()); return buffer_write_.Write(std::move(message_data)); } @@ -247,7 +237,6 @@ P2pStream::ExportPreparedSendMessageBlock(std::uint32_t reserve_nonce_count) { destination_, reserve_nonce_count); } - void P2pStream::ConnectReceive() { out_data_sub_ = handle_.out_data_event().Subscribe(MethodPtr<&P2pStream::WriteOut>{this}); @@ -290,8 +279,6 @@ std::unique_ptr P2pStream::MakeDestinationCloudConn( } WriteAction* P2pStream::OnWrite(AeMessage&& message) { - AE_TELED_ERROR("[CALL-CHAIN] P2pStream::OnWrite data_size={} connected={}", - message.data.size(), message_send_stream_ != nullptr); if (!message_send_stream_) { return {}; } diff --git a/aether/cloud_connections/cloud_server_connections.cpp b/aether/cloud_connections/cloud_server_connections.cpp index e5d29142..5329038f 100644 --- a/aether/cloud_connections/cloud_server_connections.cpp +++ b/aether/cloud_connections/cloud_server_connections.cpp @@ -275,21 +275,15 @@ std::vector CloudServerConnections::ServerCandidates() { WriteAction& CloudServerConnections::CallApi(ApiCall const& api_caller, RequestPolicy::Variant policy) { - AE_TELED_ERROR("[CALL-CHAIN] CloudServerConnections::CallApi selected={}", - selected_servers_.size()); std::vector swas; ForServers( [&](CloudServerConnection* sc) { auto* conn = sc->client_connection(); assert((conn != nullptr) && "Client connection is null"); - AE_TELED_ERROR("[CALL-CHAIN] CloudServerConnections::CallApi server={}", - sc->server()->server_id); swas.emplace_back(&conn->AuthorizedApiCall( SubApi{[&](auto& api) { api_caller(api, sc); }})); }, policy); - AE_TELED_ERROR("[CALL-CHAIN] CloudServerConnections::CallApi writes={}", - swas.size()); if (swas.empty()) { return EmptyWriteAction(); diff --git a/aether/server_connections/client_server_connection.cpp b/aether/server_connections/client_server_connection.cpp index 3c714538..49da0a2f 100644 --- a/aether/server_connections/client_server_connection.cpp +++ b/aether/server_connections/client_server_connection.cpp @@ -98,11 +98,6 @@ BufferedServerConnection::BufferedServerConnection(AeContext const& ae_context, Ptr const& server) : buffer_write{ae_context, [&](DataBuffer&& in_data) -> WriteAction* { - AE_TELED_ERROR( - "[CALL-CHAIN] BufferedServerConnection::direct " - "data_size={} writable={}", - in_data.size(), - server_connection.stream_info().is_writable); if (server_connection.stream_info().is_writable) { return &server_connection.Write(std::move(in_data)); } @@ -126,8 +121,6 @@ BufferedServerConnection::BufferedServerConnection(AeContext const& ae_context, } WriteAction& BufferedServerConnection::Write(DataBuffer&& in_data) { - AE_TELED_ERROR("[CALL-CHAIN] BufferedServerConnection::Write data_size={}", - in_data.size()); return buffer_write.Write(std::move(in_data)); } @@ -193,10 +186,8 @@ WriteAction& ClientServerConnection::LoginApiCall(SubApi login_api) { WriteAction& ClientServerConnection::AuthorizedApiCall( SubApi auth_api) { - AE_TELED_ERROR("[CALL-CHAIN] ClientServerConnection::AuthorizedApiCall begin"); auto api_call = ApiCallAdapter{ApiContext{login_api_}, server_connection_}; api_call->login_by_alias(ephemeral_uid_, std::move(auth_api)); - AE_TELED_ERROR("[CALL-CHAIN] ClientServerConnection::AuthorizedApiCall flush"); // cppcheck reports false positive // cppcheck-suppress returnReference return api_call.Flush(); @@ -263,8 +254,6 @@ ClientServerConnection::ExportPreparedSendMessageBlock( return block; } - - void ClientServerConnection::OutData(DataBuffer const& data) { auto parser = ApiParser{protocol_context_, data}; parser.Parse(client_api_unsafe_); diff --git a/aether/server_connections/server_connection.cpp b/aether/server_connections/server_connection.cpp index 2f56948f..b63e07e1 100644 --- a/aether/server_connections/server_connection.cpp +++ b/aether/server_connections/server_connection.cpp @@ -68,16 +68,11 @@ ServerConnection::ServerConnection(AeContext const& ae_context, } WriteAction& ServerConnection::Write(DataBuffer&& in_data) { - AE_TELED_ERROR("[CALL-CHAIN] ServerConnection::Write data_size={} " - "top_channel={}", - in_data.size(), top_channel_ != nullptr); // Write allowed only if stream_info.is_writable == true assert(stream_info_.is_writable && "Channel is not writable"); assert((top_channel_ != nullptr) && "channel connection is not available"); auto* stream = top_channel_->connection.stream(); - AE_TELED_ERROR("[CALL-CHAIN] ServerConnection::Write stream={}", - stream != nullptr); assert((stream != nullptr) && "channel stream is not available"); return stream->Write(std::move(in_data)); diff --git a/aether/stream_api/api_call_adapter.h b/aether/stream_api/api_call_adapter.h index 28c6e782..dcf4ff20 100644 --- a/aether/stream_api/api_call_adapter.h +++ b/aether/stream_api/api_call_adapter.h @@ -92,7 +92,8 @@ struct PreparedPacketEncoder { // Span payload, // DataBuffer& out); // -// Current internal step keeps old Aether path alive by using ByteIStream as out. +// Current internal step keeps old Aether path alive by using ByteIStream as +// out. struct EncodePacketResult { EncodePacketReport report; WriteAction* action = nullptr; @@ -104,16 +105,13 @@ struct EncodePacketResult { template EncodePacketResult EncodePacket(PreparedPacketEncoder& prepared, - ApiContext&& data, - ByteIStream& out) { + ApiContext&& data, ByteIStream& out) { auto report = prepared.TakeNonce(); if (!report) { return EncodePacketResult{report, nullptr}; } auto packet = std::move(data).Pack(); - AE_TELED_ERROR("[CALL-CHAIN] ApiCallAdapter::EncodePacket packet_size={}", - packet.size()); auto& action = out.Write(std::move(packet)); return EncodePacketResult{report, &action}; @@ -141,8 +139,7 @@ class ApiCallAdapter { if (!result) { std::cerr << "FastTx EncodePacket failed: " - << fast_tx_internal::ToString(result.report.error) - << "\n"; + << fast_tx_internal::ToString(result.report.error) << "\n"; assert(false); } diff --git a/aether/transport/system_sockets/udp/udp.h b/aether/transport/system_sockets/udp/udp.h index 642f5052..7f7e7b5e 100644 --- a/aether/transport/system_sockets/udp/udp.h +++ b/aether/transport/system_sockets/udp/udp.h @@ -70,7 +70,7 @@ class SendAction final : public PacketSendAction { AE_TELED_ERROR("Send error, sent size isn't same as packet size"); SetStatus(WriteAction::Status::kFail); return; - } + } SetStatus(WriteAction::Status::kSuccess); } @@ -156,8 +156,6 @@ class UdpTransport final : public upd_internal::UdpBase { } WriteAction& Write(DataBuffer&& in_data) override { - AE_TELED_ERROR("[CALL-CHAIN] UdpTransport::Write endpoint={} data_size={}", - endpoint_, in_data.size()); assert(in_data.size() != 0); AE_TELE_DEBUG(kUdpTransportSend, "Socket {} send data size:{}", endpoint_, in_data.size()); diff --git a/aether/write_action/buffer_write.h b/aether/write_action/buffer_write.h index b7a8a72c..09fe11fa 100644 --- a/aether/write_action/buffer_write.h +++ b/aether/write_action/buffer_write.h @@ -101,14 +101,8 @@ class BufferWrite { /** * \brief Control buffering. */ - void buffer_on() { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::buffer_on buffered={}", - buffer_.size()); - buffer_on_ = true; - } + void buffer_on() { buffer_on_ = true; } void buffer_off() { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::buffer_off buffered={}", - buffer_.size()); buffer_on_ = false; DrainBuffer(); } @@ -118,9 +112,6 @@ class BufferWrite { * \brief Write data to or through buffer. */ WriteAction& Write(T&& data) { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::Write payload_size={} " - "buffer_on={} buffered={}", - DebugPayloadSize(data), buffer_on_, buffer_.size()); auto should_be_buffered = buffer_on_ || !buffer_.empty(); if (should_be_buffered) { return WriteToBuffer(std::move(data)); @@ -142,9 +133,6 @@ class BufferWrite { private: WriteAction& WriteToBuffer(T&& data) { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::WriteToBuffer payload_size={} " - "buffer_on={} buffered={}", - DebugPayloadSize(data), buffer_on_, buffer_.size()); if (buffer_.full()) { BW_LOG_WARNING("Buffer is full"); return FailedWrite(); @@ -166,13 +154,8 @@ class BufferWrite { } WriteAction& DirectWrite(T&& data) { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DirectWrite payload_size={} " - "buffered={}", - DebugPayloadSize(data), buffer_.size()); BW_LOG_DEBUG("BufferWrite: direct write"); auto* write_action = direct_write_(std::move(data)); - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DirectWrite result={}", - write_action != nullptr); if (write_action == nullptr) { return FailedWrite(); } @@ -183,15 +166,9 @@ class BufferWrite { * Write all buffered data in FIFO order. */ void DrainBuffer() { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer begin buffer_on={} " - "buffered={}", - buffer_on_, buffer_.size()); BW_LOG_DEBUG("BufferWrite: drain the buffer, size {}", buffer_.size()); // try send as many as possible for (auto& be : buffer_) { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer entry finished={} " - "sent={} payload_size={}", - be.wa.is_finished(), be.sent, DebugPayloadSize(be.data)); if (be.wa.is_finished()) { // notice! circular buffer pop is just incrementing the out pointer it // does not invalidate the iterators @@ -199,23 +176,13 @@ class BufferWrite { continue; } if (be.sent) { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer skip in-flight " - "payload_size={}", - DebugPayloadSize(be.data)); continue; } // buffer state might change during direct write if (buffer_on_) { - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer stop buffer_on"); break; } - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer direct before_move " - "payload_size={}", - DebugPayloadSize(be.data)); auto* dwa = direct_write_(std::move(be.data)); - BW_LOG_WARNING("[CALL-CHAIN] BufferWrite::DrainBuffer direct result={} " - "after_move_payload_size={}", - dwa != nullptr, DebugPayloadSize(be.data)); // empty write action means no data has been written if (dwa == nullptr) { break; diff --git a/examples/cloud/aether_construct_esp_wifi.h b/examples/cloud/aether_construct_esp_wifi.h index 44c06177..41f08c23 100644 --- a/examples/cloud/aether_construct_esp_wifi.h +++ b/examples/cloud/aether_construct_esp_wifi.h @@ -22,8 +22,8 @@ #if CLOUD_TEST_ESP_WIFI namespace ae::cloud_test { -static const std::string kWifi1Ssid = "Visuale"; -static const std::string kWifi1Pass = "Ws63$yhJ"; +static const std::string kWifi1Ssid = "Test1234"; +static const std::string kWifi1Pass = "Test1234"; static const std::string kWifi2Ssid = "Test2345"; static const std::string kWifi2Pass = "Test2345"; From ec03bcec17cd340f9ef91990802044ba5d35adce Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Wed, 12 Aug 2026 14:24:39 +0300 Subject: [PATCH 15/16] Removing the Alice class from the example. --- .../windows_message_receiver.cpp | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/examples/windows_message_receiver/windows_message_receiver.cpp b/examples/windows_message_receiver/windows_message_receiver.cpp index 647760fe..f728ae4c 100644 --- a/examples/windows_message_receiver/windows_message_receiver.cpp +++ b/examples/windows_message_receiver/windows_message_receiver.cpp @@ -37,25 +37,6 @@ class TimeSynchronizer { ae::TimePoint pong_sent_time_; }; -// Alice sends "ping"s to Bob -/* class Alice { - public: - explicit Alice(ae::AetherApp& aether_app, ae::Client::ptr client_alice, - TimeSynchronizer& time_synchronizer, ae::Uid bobs_uid); - - private: - void SendMessage(); - void ResponseReceived(ae::DataBuffer const& data_buffer); - - ae::AetherApp* aether_app_; - ae::Client::ptr client_alice_; - TimeSynchronizer* time_synchronizer_; - ae::P2pStream p2pstream_; - ae::RepeatableTask interval_sender_; - ae::Subscription receive_data_sub_; - ae::MultiSubscription send_subs_; -};*/ - // Bob answers "pong" to each "ping" class Bob { public: @@ -77,7 +58,7 @@ class Bob { int main() { auto aether_app = ae::AetherApp::Construct(ae::AetherAppContext{}); - //std::unique_ptr alice; + // std::unique_ptr alice; std::unique_ptr bob; TimeSynchronizer time_synchronizer; @@ -133,8 +114,9 @@ void Bob::OnMessageReceived(ae::DataBuffer const& data_buffer) { auto ping_message = std::string_view{ reinterpret_cast(data_buffer.data()), data_buffer.size()}; std::cout << ae::Format( - ">>>\n>>>\n>>>\n[{:%H:%M:%S}] Bob received \"{}\" within time {} ms\n>>>\n>>>\n>>>\n", ae::Now(), - ping_message, + ">>>\n>>>\n>>>\n[{:%H:%M:%S}] Bob received \"{}\" within time {} " + "ms\n>>>\n>>>\n>>>\n", + ae::Now(), ping_message, std::chrono::duration_cast( time_synchronizer_->GetPingDuration()) .count()); From c34ecfc7e66a07b6554a02bf56c519b01c7019df Mon Sep 17 00:00:00 2001 From: Kiryanov D V Date: Wed, 12 Aug 2026 14:30:08 +0300 Subject: [PATCH 16/16] Changing the build type from Debug to Release. --- .../vscode/aether-client-cpp/.vscode/settings.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/espressif_riscv/vscode/aether-client-cpp/.vscode/settings.json b/projects/espressif_riscv/vscode/aether-client-cpp/.vscode/settings.json index 8026dc8d..e487d68c 100644 --- a/projects/espressif_riscv/vscode/aether-client-cpp/.vscode/settings.json +++ b/projects/espressif_riscv/vscode/aether-client-cpp/.vscode/settings.json @@ -2,7 +2,7 @@ "idf.cmakeCompilerArgs": [ "-G", "Ninja", - "-DCMAKE_BUILD_TYPE=Debug", + "-DCMAKE_BUILD_TYPE=Release", "-DUSER_CONFIG=../../../../../config/user_config_hydrogen.h" ], "C_Cpp.intelliSenseEngine": "Tag Parser", @@ -120,6 +120,5 @@ "filesystem": "cpp" }, "idf.flashType": "UART", - "idf.portWin": "COM9", - "idf.currentSetup": "G:\\dev\\esp32\\v6.0.2\\esp-idf" + "idf.portWin": "COM9" } \ No newline at end of file