tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit 34c47f2e83f4a4f9977d1cb685ee5f181c5e087b
parent b8992e9381892358079e3042ba91c07cf8a3fc21
Author: triesap <tyson@radroots.org>
Date:   Mon, 15 Jun 2026 14:20:02 -0700

runtime: parse event auth through Pocket

Diffstat:
Mcrates/tangle_runtime/Cargo.toml | 2+-
Acrates/tangle_runtime/src/client_message.rs | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/tangle_runtime/src/lib.rs | 1+
Mcrates/tangle_runtime/src/session.rs | 7+++----
4 files changed, 105 insertions(+), 5 deletions(-)

diff --git a/crates/tangle_runtime/Cargo.toml b/crates/tangle_runtime/Cargo.toml @@ -12,7 +12,7 @@ axum = { version = "0.8", features = ["ws"] } getrandom = "0.3" http = "1" serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde_json = { version = "1", features = ["raw_value"] } tangle_crypto = { path = "../tangle_crypto" } tangle_groups = { path = "../tangle_groups" } tangle_protocol = { path = "../tangle_protocol" } diff --git a/crates/tangle_runtime/src/client_message.rs b/crates/tangle_runtime/src/client_message.rs @@ -0,0 +1,100 @@ +#![forbid(unsafe_code)] + +use crate::{errors::BaseRelayError, pocket_conversion::pocket_event_to_tangle}; +use serde_json::value::RawValue; +use tangle_protocol::{ClientMessage, parse_client_message}; +use tangle_store_pocket::parse_pocket_event_json; + +pub(crate) fn parse_runtime_client_message(raw: &str) -> Result<ClientMessage, String> { + let Ok(values) = serde_json::from_str::<Vec<Box<RawValue>>>(raw) else { + return parse_client_message(raw); + }; + let Some(command) = values + .first() + .and_then(|value| serde_json::from_str::<String>(value.get()).ok()) + else { + return parse_client_message(raw); + }; + match command.as_str() { + "EVENT" => parse_event_or_auth(&values, ClientMessage::Event), + "AUTH" => parse_event_or_auth(&values, ClientMessage::Auth), + _ => parse_client_message(raw), + } +} + +fn parse_event_or_auth( + values: &[Box<RawValue>], + build: impl FnOnce(tangle_protocol::Event) -> ClientMessage, +) -> Result<ClientMessage, String> { + if values.len() != 2 { + return Err("EVENT and AUTH client messages must contain exactly one event".to_owned()); + } + let event = parse_pocket_event_json(values[1].get().as_bytes()) + .map_err(|error| error.message().to_owned()) + .and_then(|event| pocket_event_to_tangle(&event).map_err(base_relay_error_message))?; + Ok(build(event)) +} + +fn base_relay_error_message(error: BaseRelayError) -> String { + error.message().to_owned() +} + +#[cfg(test)] +mod tests { + use super::parse_runtime_client_message; + use serde_json::json; + use tangle_protocol::{ClientMessage, event_to_value}; + use tangle_test_support::{FixtureKey, tangle_v2_auth_event, tangle_v2_event}; + + #[test] + fn runtime_parser_maps_event_and_auth_through_pocket_event_json() { + let event = tangle_v2_event(FixtureKey::Member, 1_714_124_433, 1, Vec::new(), "hello") + .expect("event"); + assert_eq!( + parse_runtime_client_message(&json!(["EVENT", event_to_value(&event)]).to_string()) + .expect("event"), + ClientMessage::Event(event.clone()) + ); + + let auth = + tangle_v2_auth_event(FixtureKey::Member, "challenge-a", 1_714_124_434).expect("auth"); + assert_eq!( + parse_runtime_client_message(&json!(["AUTH", event_to_value(&auth)]).to_string()) + .expect("auth"), + ClientMessage::Auth(auth) + ); + } + + #[test] + fn runtime_parser_rejects_malformed_event_and_auth_payloads() { + assert_eq!( + parse_runtime_client_message("[\"EVENT\"]").expect_err("missing event"), + "EVENT and AUTH client messages must contain exactly one event" + ); + assert_eq!( + parse_runtime_client_message("[\"AUTH\",{},{}]").expect_err("too many auth values"), + "EVENT and AUTH client messages must contain exactly one event" + ); + assert!( + !parse_runtime_client_message("[\"EVENT\",{\"id\":5}]") + .expect_err("invalid event") + .is_empty() + ); + } + + #[test] + fn runtime_parser_delegates_req_count_and_close_until_filter_slice() { + assert!(matches!( + parse_runtime_client_message("[\"REQ\",\"sub\",{\"kinds\":[1]}]").expect("req"), + ClientMessage::Req { .. } + )); + assert!(matches!( + parse_runtime_client_message("[\"COUNT\",\"sub\",{\"kinds\":[1]}]").expect("count"), + ClientMessage::Count { .. } + )); + assert!(matches!( + parse_runtime_client_message("[\"CLOSE\",\"sub\"]").expect("close"), + ClientMessage::Close(_) + )); + } +} diff --git a/crates/tangle_runtime/src/lib.rs b/crates/tangle_runtime/src/lib.rs @@ -1,5 +1,6 @@ #![forbid(unsafe_code)] +pub(crate) mod client_message; pub mod config; pub mod errors; pub mod event_bus; diff --git a/crates/tangle_runtime/src/session.rs b/crates/tangle_runtime/src/session.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] use crate::{ + client_message::parse_runtime_client_message, errors::BaseRelayError, event_bus::{TangleEventReceiveError, TangleEventReceiver}, logging, @@ -20,9 +21,7 @@ use std::{ sync::atomic::{AtomicU64, Ordering}, time::{Instant, SystemTime, UNIX_EPOCH}, }; -use tangle_protocol::{ - ClientMessage, Filter, RelayMessage, SubscriptionId, UnixTimestamp, parse_client_message, -}; +use tangle_protocol::{ClientMessage, Filter, RelayMessage, SubscriptionId, UnixTimestamp}; use tokio::sync::{mpsc, watch}; #[derive(Debug)] @@ -248,7 +247,7 @@ impl TangleWebSocketSession { .map(|_| TangleSessionControl::Continue) .unwrap_or_else(|control| control); } - let replies = match parse_client_message(raw) { + let replies = match parse_runtime_client_message(raw) { Ok(message) => match self.handle_client_message(message).await { Ok(replies) => replies, Err(error) => vec![RelayMessage::Notice(error.prefixed_message())],