Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
39e0f55
Add prepared send message packet encoder
NikolayChirkov Jun 25, 2026
92d677f
Clean up prepared message endpoint API
NikolayChirkov Jun 25, 2026
494df92
Split prepared packet encoder implementation
NikolayChirkov Jun 25, 2026
8b60c2f
Move prepared endpoint conversion into prepared packet module
NikolayChirkov Jun 26, 2026
71a9151
Remove unused prepared packet encode error
NikolayChirkov Jun 26, 2026
cd64d09
Add prepared send message API
NikolayChirkov Jun 26, 2026
4e06593
Merge origin/main into prepared-packet-v0.
NikolayChirkov Jul 1, 2026
536cce4
Remove FASTTX_STEP debug marker from api_call_adapter.
NikolayChirkov Jul 1, 2026
d5f1fc7
Fix bugs.
dmitriyk-aethernet Jul 2, 2026
d5e89e0
Add windows_message_receiver example.
dmitriyk-aethernet Jul 9, 2026
2932180
Fixing zero packet send.
dmitriyk-aethernet Jul 9, 2026
7243acd
Merge branch 'main' into prepared-packet-v0
dmitriyk-aethernet Jul 10, 2026
3ec3de1
Merge branch 'prepared-packet-v0' of https://github.com/aethernetio/a…
dmitriyk-aethernet Jul 10, 2026
1c5649f
Removing comments.
dmitriyk-aethernet Jul 10, 2026
60ceb17
Fixing CMake.
dmitriyk-aethernet Jul 13, 2026
420f278
New parent UID.
dmitriyk-aethernet Jul 14, 2026
1b5f8ca
Merge branch 'main' into prepared-packet-v0
dmitriyk-aethernet Jul 20, 2026
0227e97
Removing debug output.
dmitriyk-aethernet Aug 12, 2026
ec03bce
Removing the Alice class from the example.
dmitriyk-aethernet Aug 12, 2026
c34ecfc
Changing the build type from Debug to Release.
dmitriyk-aethernet Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,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)
Expand Down
3 changes: 3 additions & 0 deletions aether/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ 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")

Expand Down
34 changes: 34 additions & 0 deletions aether/client_messages/p2p_message_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#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"

Expand All @@ -48,6 +49,29 @@ class MessageSendStream final : public IStream<AeMessage, AeMessage> {
}},
request_policy_);
}

std::optional<prepared_packet::PreparedSendMessageBlock>
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 {
Expand Down Expand Up @@ -203,6 +227,16 @@ void P2pStream::WriteOut(DataBuffer const& data) {

Uid const& P2pStream::destination() const { return destination_; }

std::optional<prepared_packet::PreparedSendMessageBlock>
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() {
out_data_sub_ =
handle_.out_data_event().Subscribe(MethodPtr<&P2pStream::WriteOut>{this});
Expand Down
7 changes: 7 additions & 0 deletions aether/client_messages/p2p_message_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#ifndef AETHER_CLIENT_MESSAGES_P2P_MESSAGE_STREAM_H_
#define AETHER_CLIENT_MESSAGES_P2P_MESSAGE_STREAM_H_

#include <optional>
#include <cstdint>

#include "aether/common.h"

#include "aether/ae_context.h"
Expand All @@ -28,6 +31,7 @@
#include "aether/client_messages/p2p_port_handle.h"
#include "aether/cloud_connections/cloud_server_connections.h"
#include "aether/connection_manager/client_cloud_manager.h"
#include "aether/prepared_packet/prepared_send_message.h"

namespace ae {
class Client;
Expand All @@ -54,6 +58,9 @@ class P2pStream final : public ByteIStream {
void WriteOut(DataBuffer const& data);
Uid const& destination() const;

std::optional<prepared_packet::PreparedSendMessageBlock>
ExportPreparedSendMessageBlock(std::uint32_t reserve_nonce_count);

private:
void ConnectReceive();
void ConnectSend();
Expand Down
73 changes: 73 additions & 0 deletions aether/prepared_packet/packet_encoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "aether/prepared_packet/packet_encoder.h"

#include <memory>
#include <utility>

#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<PreparedSendMessageKeyProvider>(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<AuthorizedApi>{
[&block, &payload](ApiContext<AuthorizedApi>& 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
21 changes: 21 additions & 0 deletions aether/prepared_packet/packet_encoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Prepared packet encoder.
*
* 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 "aether/prepared_packet/prepared_send_message.h"

namespace ae::prepared_packet {

EncodePacketResult EncodePacket(PreparedSendMessageBlock& block,
DataBuffer const& payload,
DataBuffer& out);

} // namespace ae::prepared_packet

#endif // AETHER_PREPARED_PACKET_PACKET_ENCODER_H_
12 changes: 12 additions & 0 deletions aether/prepared_packet/prepare_send_message.cpp
Original file line number Diff line number Diff line change
@@ -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<PreparedSendMessageBlock> PrepareSendMessage(
P2pStream& stream, std::uint32_t reserve_nonce_count) {
return stream.ExportPreparedSendMessageBlock(reserve_nonce_count);
}

} // namespace ae::prepared_packet
21 changes: 21 additions & 0 deletions aether/prepared_packet/prepare_send_message.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef AETHER_PREPARED_PACKET_PREPARE_SEND_MESSAGE_H_
#define AETHER_PREPARED_PACKET_PREPARE_SEND_MESSAGE_H_

#include <cstdint>
#include <optional>

#include "aether/prepared_packet/prepared_send_message.h"

namespace ae {

class P2pStream;

namespace prepared_packet {

std::optional<PreparedSendMessageBlock> PrepareSendMessage(
P2pStream& stream, std::uint32_t reserve_nonce_count);

} // namespace prepared_packet
} // namespace ae

#endif
41 changes: 41 additions & 0 deletions aether/prepared_packet/prepared_send_message.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "aether/prepared_packet/prepared_send_message.h"

#include <type_traits>

namespace ae::prepared_packet {

std::optional<PreparedEndpoint> 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<decltype(addr)>;

if constexpr (std::is_same_v<T, IpV4Addr>) {
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<T, IpV6Addr>) {
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
94 changes: 94 additions & 0 deletions aether/prepared_packet/prepared_send_message.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 <array>
#include <cstddef>
#include <cstdint>
#include <optional>

#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 {
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<std::uint8_t, kMaxPreparedBlockBytes> bytes;
};

enum class PreparedIpVersion : std::uint8_t {
kIpV4 = 4,
kIpV6 = 6,
};

struct PreparedEndpoint {
AE_REFLECT_MEMBERS(version, protocol, port, ip)
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<std::uint8_t, 16> ip{};
};

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;
Uid target_uid;

Key client_to_server_key;

CryptoNonce next_nonce;
std::uint32_t nonce_left = 0;
};

enum class EncodePacketError {
kNone = 0,
kNonceExhausted,
};

inline char const* ToString(EncodePacketError error) {
switch (error) {
case EncodePacketError::kNone:
return "none";
case EncodePacketError::kNonceExhausted:
return "nonce_exhausted";
}
return "unknown";
}

struct EncodePacketResult {
EncodePacketError error = EncodePacketError::kNone;
std::size_t bytes_written = 0;

explicit operator bool() const { return error == EncodePacketError::kNone; }
};

std::optional<PreparedEndpoint> MakePreparedEndpoint(Endpoint const& endpoint);

} // namespace ae::prepared_packet

#endif // AETHER_PREPARED_PACKET_PREPARED_SEND_MESSAGE_H_
Loading