Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -383,6 +383,9 @@ if(AE_BUILD_EXAMPLES)
add_subdirectory(examples/common)
add_subdirectory(examples/cloud)
add_subdirectory(examples/a_b_message_exchange)
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
2 changes: 2 additions & 0 deletions aether/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ 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_select_action.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
8 changes: 8 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 <cstdint>
#include <optional>

#include "aether/common.h"

#include "aether/ae_context.h"
Expand All @@ -29,6 +32,8 @@
#include "aether/cloud_connections/cloud_server_connections.h"
#include "aether/connection_manager/client_cloud_manager.h"

#include "aether/prepared_packet/packet_encoder.h"

namespace ae {
class Client;
class Cloud;
Expand All @@ -54,6 +59,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
2 changes: 1 addition & 1 deletion aether/cloud_connections/cloud_server_connections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ WriteAction& CloudServerConnections::CallApi(ApiCall const& api_caller,
ForServers(
[&](CloudServerConnection* sc) {
auto* conn = sc->client_connection();
assert(conn != nullptr);
assert(conn != nullptr && "Client connection is null");
swas.emplace_back(&conn->AuthorizedApiCall(
SubApi<AuthorizedApi>{[&](auto& api) { api_caller(api, sc); }}));
},
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
Loading