Radix Relay
Hybrid mesh communications with Signal Protocol encryption
Loading...
Searching...
No Matches
message_handler.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <bit>
6#include <core/events.hpp>
8#include <ctime>
9#include <fmt/format.h>
10#include <internal_use_only/config.hpp>
11#include <iterator>
12#include <memory>
13#include <nlohmann/json.hpp>
14#include <nostr/events.hpp>
15#include <nostr/protocol.hpp>
17#include <optional>
18#include <spdlog/spdlog.h>
19#include <string>
20#include <vector>
21
22namespace radix_relay::nostr {
23
28{
29 std::string event_id;
30 std::vector<std::byte> bytes;
31 std::uint32_t pre_key_id;
32 std::uint32_t signed_pre_key_id;
33 std::uint32_t kyber_pre_key_id;
34};
35
44template<concepts::signal_bridge Bridge> class message_handler
45{
46public:
52 explicit message_handler(std::shared_ptr<Bridge> bridge) : bridge_(bridge) {}
53
60 [[nodiscard]] auto handle(const nostr::events::incoming::encrypted_message &event)
61 -> std::optional<core::events::message_received>
62 {
63 std::vector<uint8_t> encrypted_bytes;
64 constexpr int hex_base = 16;
65 for (size_t i = 0; i < event.content.length(); i += 2) {
66 auto byte_string = event.content.substr(i, 2);
67 auto byte_value = static_cast<uint8_t>(std::stoul(byte_string, nullptr, hex_base));
68 encrypted_bytes.push_back(byte_value);
69 }
70
71 // Pass Nostr pubkey as peer_hint - decrypt_message will:
72 // - For PreKeySignalMessage: extract identity key and create contact automatically
73 // - For SignalMessage: look up existing contact by this pubkey
74 auto result = bridge_->decrypt_message(event.pubkey, encrypted_bytes);
75
76 const std::string decrypted_content(result.plaintext.begin(), result.plaintext.end());
77
78 bridge_->update_last_message_timestamp(event.created_at);
79
80 // After successful decryption, get the sender's contact info (now guaranteed to exist)
81 auto sender_contact = bridge_->lookup_contact(event.pubkey);
82
83 return core::events::message_received{ .sender_rdx = sender_contact.rdx_fingerprint,
84 .sender_alias = sender_contact.user_alias,
85 .content = decrypted_content,
86 .timestamp = event.created_at,
87 .should_republish_bundle = result.should_republish_bundle };
88 }
89
97 [[nodiscard]] static auto handle(const nostr::events::incoming::bundle_announcement &event) -> std::optional<
98 std::variant<core::events::bundle_announcement_received, core::events::bundle_announcement_removed>>
99 {
100 auto version = nostr::extract_version_from_tags(event.tags);
101 if (not version.has_value()) { return std::nullopt; }
102
104 return std::nullopt;
105 }
106
107 if (event.content.empty()) {
108 return core::events::bundle_announcement_removed{ .pubkey = event.pubkey, .event_id = event.id };
109 }
110
112 .pubkey = event.pubkey, .bundle_content = event.content, .event_id = event.id
113 };
114 }
115
122 [[nodiscard]] auto handle(const core::events::send &cmd) -> std::pair<std::string, std::vector<std::byte>>
123 {
124 std::vector<uint8_t> plaintext_bytes(cmd.message.begin(), cmd.message.end());
125 auto encrypted_bytes = bridge_->encrypt_message(cmd.peer, plaintext_bytes);
126
127 std::string hex_content;
128 for (const auto &byte : encrypted_bytes) { hex_content += fmt::format("{:02x}", byte); }
129
130 auto signed_event_json = bridge_->create_and_sign_encrypted_message(
131 cmd.peer, hex_content, static_cast<std::uint32_t>(std::time(nullptr)), std::string{ cmake::project_version });
132
133 auto event_json = nlohmann::json::parse(signed_event_json);
134 const std::string event_id = event_json["id"].template get<std::string>();
135
137 event_data.id = event_id;
138 event_data.pubkey = event_json["pubkey"].template get<std::string>();
139 event_data.created_at = event_json["created_at"].template get<std::uint64_t>();
140 event_data.kind = event_json["kind"].template get<nostr::protocol::kind>();
141 event_data.content = event_json["content"].template get<std::string>();
142 event_data.sig = event_json["sig"].template get<std::string>();
143
144 for (const auto &tag : event_json["tags"]) {
145 std::vector<std::string> tag_vec;
146 std::ranges::transform(
147 tag, std::back_inserter(tag_vec), [](const auto &item) { return item.template get<std::string>(); });
148 event_data.tags.push_back(tag_vec);
149 }
150
151 auto protocol_event = nostr::protocol::event::from_event_data(event_data);
152 auto json_str = protocol_event.serialize();
153
154 std::vector<std::byte> bytes;
155 bytes.resize(json_str.size());
156 std::ranges::transform(json_str, bytes.begin(), [](char character) { return std::bit_cast<std::byte>(character); });
157
158 return { event_id, bytes };
159 }
160
167 [[nodiscard]] auto handle(const core::events::publish_identity & /*command*/) -> publish_bundle_result
168 {
169 const std::string version_str{ radix_relay::cmake::project_version };
170 auto bundle_info = bridge_->generate_prekey_bundle_announcement(version_str);
171 auto event_json = nlohmann::json::parse(bundle_info.announcement_json);
172
173 const std::string event_id = event_json["id"].template get<std::string>();
174
176 event_data.id = event_id;
177 event_data.pubkey = event_json["pubkey"].template get<std::string>();
178 event_data.created_at = event_json["created_at"].template get<std::uint64_t>();
179 event_data.kind = event_json["kind"].template get<nostr::protocol::kind>();
180 event_data.content = event_json["content"].template get<std::string>();
181 event_data.sig = event_json["sig"].template get<std::string>();
182
183 for (const auto &tag : event_json["tags"]) {
184 std::vector<std::string> tag_vec;
185 std::ranges::transform(
186 tag, std::back_inserter(tag_vec), [](const auto &item) { return item.template get<std::string>(); });
187 event_data.tags.push_back(tag_vec);
188 }
189
190 auto protocol_event = nostr::protocol::event::from_event_data(event_data);
191 auto json_str = protocol_event.serialize();
192
193 std::vector<std::byte> bytes;
194 bytes.resize(json_str.size());
195 std::ranges::transform(json_str, bytes.begin(), [](char character) { return std::bit_cast<std::byte>(character); });
196
197 return publish_bundle_result{ .event_id = event_id,
198 .bytes = std::move(bytes),
199 .pre_key_id = bundle_info.pre_key_id,
200 .signed_pre_key_id = bundle_info.signed_pre_key_id,
201 .kyber_pre_key_id = bundle_info.kyber_pre_key_id };
202 }
203
210 [[nodiscard]] auto handle(const core::events::unpublish_identity & /*command*/)
211 -> std::pair<std::string, std::vector<std::byte>>
212 {
213 const std::string version_str{ radix_relay::cmake::project_version };
214 auto bundle_json = bridge_->generate_empty_bundle_announcement(version_str);
215 auto event_json = nlohmann::json::parse(bundle_json);
216
217 const std::string event_id = event_json["id"].template get<std::string>();
218
220 event_data.id = event_id;
221 event_data.pubkey = event_json["pubkey"].template get<std::string>();
222 event_data.created_at = event_json["created_at"].template get<std::uint64_t>();
223 event_data.kind = event_json["kind"].template get<nostr::protocol::kind>();
224 event_data.content = event_json["content"].template get<std::string>();
225 event_data.sig = event_json["sig"].template get<std::string>();
226
227 for (const auto &tag : event_json["tags"]) {
228 std::vector<std::string> tag_vec;
229 std::ranges::transform(
230 tag, std::back_inserter(tag_vec), [](const auto &item) { return item.template get<std::string>(); });
231 event_data.tags.push_back(tag_vec);
232 }
233
234 auto protocol_event = nostr::protocol::event::from_event_data(event_data);
235 auto json_str = protocol_event.serialize();
236
237 std::vector<std::byte> bytes;
238 bytes.resize(json_str.size());
239 std::ranges::transform(json_str, bytes.begin(), [](char character) { return std::bit_cast<std::byte>(character); });
240
241 return { event_id, bytes };
242 }
243
249 auto handle(const core::events::trust &cmd) -> void { bridge_->assign_contact_alias(cmd.peer, cmd.alias); }
250
257 [[nodiscard]] auto handle(const core::events::establish_session &cmd)
258 -> std::optional<core::events::session_established>
259 {
260 auto peer_rdx = bridge_->add_contact_and_establish_session_from_base64(cmd.bundle_data, "");
261 return core::events::session_established{ peer_rdx };
262 }
263
270 [[nodiscard]] static auto handle(const core::events::subscribe &cmd) -> std::pair<std::string, std::vector<std::byte>>
271 {
272 std::vector<std::byte> bytes;
273 bytes.resize(cmd.subscription_json.size());
274 std::ranges::transform(
275 cmd.subscription_json, bytes.begin(), [](char character) { return std::bit_cast<std::byte>(character); });
276
277 auto parsed = nlohmann::json::parse(cmd.subscription_json);
278 const std::string subscription_id = parsed[1].get<std::string>();
279
280 return { subscription_id, bytes };
281 }
282
288 static auto handle(const nostr::events::incoming::ok &event) -> void
289 {
290 spdlog::debug("[nostr_handler] OK received: event_id={}, accepted={}, message={}",
291 event.event_id,
292 event.accepted,
293 event.message);
294 }
295
301 static auto handle(const nostr::events::incoming::eose &event) -> void
302 {
303 spdlog::debug("[nostr_handler] EOSE received: subscription_id={}", event.subscription_id);
304 }
305
311 static auto handle(const nostr::events::incoming::unknown_message &event) -> void
312 {
313 spdlog::warn("[nostr_handler] Unknown message kind: {}", static_cast<std::uint16_t>(event.kind));
314 }
315
321 static auto handle(const nostr::events::incoming::unknown_protocol &event) -> void
322 {
323 spdlog::warn("[nostr_handler] Unknown protocol message: {}", event.message);
324 }
325
331 static auto handle(const nostr::events::incoming::identity_announcement & /*event*/) -> void {}
332
338 static auto handle(const nostr::events::incoming::session_request & /*event*/) -> void {}
339
345 static auto handle(const nostr::events::incoming::node_status & /*event*/) -> void {}
346
347private:
348 std::shared_ptr<Bridge> bridge_;
349};
350
351}// namespace radix_relay::nostr
Handles processing of incoming and outgoing Nostr messages.
static auto handle(const nostr::events::incoming::identity_announcement &) -> void
Handles an incoming identity announcement (no-op).
auto handle(const core::events::send &cmd) -> std::pair< std::string, std::vector< std::byte > >
Handles a send command by encrypting and serializing a message.
auto handle(const core::events::trust &cmd) -> void
Handles a trust command by assigning an alias to a peer.
static auto handle(const core::events::subscribe &cmd) -> std::pair< std::string, std::vector< std::byte > >
Handles a subscription request.
message_handler(std::shared_ptr< Bridge > bridge)
Constructs a message handler.
auto handle(const nostr::events::incoming::encrypted_message &event) -> std::optional< core::events::message_received >
Handles an incoming encrypted message event.
static auto handle(const nostr::events::incoming::unknown_message &event) -> void
Handles an unknown message type (logs warning).
static auto handle(const nostr::events::incoming::session_request &) -> void
Handles an incoming session request (no-op).
static auto handle(const nostr::events::incoming::unknown_protocol &event) -> void
Handles an unknown protocol message (logs warning).
auto handle(const core::events::unpublish_identity &) -> std::pair< std::string, std::vector< std::byte > >
Handles an unpublish identity command by generating an empty bundle.
static auto handle(const nostr::events::incoming::bundle_announcement &event) -> std::optional< std::variant< core::events::bundle_announcement_received, core::events::bundle_announcement_removed > >
Handles an incoming bundle announcement event.
static auto handle(const nostr::events::incoming::ok &event) -> void
Handles an incoming OK response (logs only).
auto handle(const core::events::publish_identity &) -> publish_bundle_result
Handles a publish identity command by generating and serializing a bundle.
static auto handle(const nostr::events::incoming::node_status &) -> void
Handles an incoming node status update (no-op).
static auto handle(const nostr::events::incoming::eose &event) -> void
Handles an incoming EOSE marker (logs only).
auto handle(const core::events::establish_session &cmd) -> std::optional< core::events::session_established >
Handles session establishment from bundle data.
auto is_version_compatible(const std::string &version_str, const std::string &minimum_version_str) -> bool
Checks if a version meets a minimum version requirement.
constexpr auto bundle_announcement_minimum_version
Minimum protocol version required for bundle announcements.
Definition protocol.hpp:14
auto extract_version_from_tags(const std::vector< std::vector< std::string > > &tags) -> std::optional< std::string >
Extracts Radix protocol version from Nostr event tags.
Notification of received bundle announcement.
Definition events.hpp:158
Notification of removed bundle announcement.
Definition events.hpp:166
Establish session from a received bundle.
Definition events.hpp:130
Notification of received encrypted message.
Definition events.hpp:142
std::string sender_rdx
RDX fingerprint of sender.
Definition events.hpp:143
Publish identity bundle to the network.
Definition events.hpp:80
Send encrypted message to a specific peer.
Definition events.hpp:56
Notification of successfully established session.
Definition events.hpp:152
Subscribe to custom Nostr events.
Definition events.hpp:114
Establish trust with a peer and assign an alias.
Definition events.hpp:90
Remove identity bundle from the network.
Definition events.hpp:85
Received encrypted message event (kind 40001)
Definition events.hpp:24
Received End of Stored Events marker.
Definition events.hpp:54
Received identity announcement event.
Definition events.hpp:18
Received node status announcement.
Definition events.hpp:36
Received OK response from relay.
Definition events.hpp:48
Received session establishment request.
Definition events.hpp:30
Received unknown/unrecognized message type.
Definition events.hpp:42
Received unknown protocol message.
Definition events.hpp:60
Nostr event data structure.
Definition protocol.hpp:44
std::string sig
Schnorr signature (64-byte hex)
Definition protocol.hpp:51
std::string id
Event ID (32-byte hex hash)
Definition protocol.hpp:45
std::string content
Event content.
Definition protocol.hpp:50
std::string pubkey
Public key of event creator (32-byte hex)
Definition protocol.hpp:46
std::uint64_t created_at
Unix timestamp.
Definition protocol.hpp:47
std::vector< std::vector< std::string > > tags
Event tags (arbitrary string arrays)
Definition protocol.hpp:49
static auto from_event_data(const event_data &evt) -> event
Creates an event message from event_data.
Result of publishing a bundle to Nostr.
std::uint32_t kyber_pre_key_id
Kyber prekey ID used.
std::uint32_t pre_key_id
One-time prekey ID used.
std::uint32_t signed_pre_key_id
Signed prekey ID used.
std::vector< std::byte > bytes
Serialized event bytes.