nostr-rs-relay

My dev fork of nostr-rs-relay
git clone git://jb55.com/nostr-rs-relay
Log | Files | Refs | README | LICENSE

commit ceaa01e8b4aa0085788afcb2167da63f2b682a4a
parent bc68cd0c74722ce49d492feaa272d43724b959ea
Author: Greg Heartsfield <scsibug@imap.cc>
Date:   Sat, 12 Feb 2022 16:19:10 -0600

fix: removed manual nostr stream, so websocket pings work

Diffstat:
Msrc/main.rs | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Dsrc/protostream.rs | 141-------------------------------------------------------------------------------
2 files changed, 91 insertions(+), 159 deletions(-)

diff --git a/src/main.rs b/src/main.rs @@ -9,16 +9,17 @@ use hyper::{ }; use log::*; use nostr_rs_relay::close::Close; +use nostr_rs_relay::close::CloseCmd; use nostr_rs_relay::config; use nostr_rs_relay::conn; use nostr_rs_relay::db; use nostr_rs_relay::error::{Error, Result}; use nostr_rs_relay::event::Event; +use nostr_rs_relay::event::EventCmd; use nostr_rs_relay::info::RelayInfo; use nostr_rs_relay::nip05; -use nostr_rs_relay::protostream; -use nostr_rs_relay::protostream::NostrMessage::*; -use nostr_rs_relay::protostream::NostrResponse::*; +use nostr_rs_relay::subscription::Subscription; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::convert::Infallible; use std::env; @@ -29,7 +30,9 @@ use tokio::sync::broadcast::{self, Receiver, Sender}; use tokio::sync::mpsc; use tokio::sync::oneshot; use tokio_tungstenite::WebSocketStream; +use tungstenite::error::Error as WsError; use tungstenite::handshake; +use tungstenite::protocol::Message; use tungstenite::protocol::WebSocketConfig; /// Return a requested DB name from command line arguments. @@ -312,11 +315,57 @@ fn main() -> Result<(), Error> { Ok(()) } +/// Nostr protocol messages from a client +#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)] +#[serde(untagged)] +pub enum NostrMessage { + /// An `EVENT` message + EventMsg(EventCmd), + /// A `REQ` message + SubMsg(Subscription), + /// A `CLOSE` message + CloseMsg(CloseCmd), +} + +/// Nostr protocol messages from a relay/server +#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)] +pub enum NostrResponse { + /// A `NOTICE` response + NoticeRes(String), + /// An `EVENT` response, composed of the subscription identifier, + /// and serialized event JSON + EventRes(String, String), +} + +/// Convert Message to NostrMessage +fn convert_to_msg(msg: String) -> Result<NostrMessage> { + let config = config::SETTINGS.read().unwrap(); + let parsed_res: Result<NostrMessage> = serde_json::from_str(&msg).map_err(|e| e.into()); + match parsed_res { + Ok(m) => { + if let NostrMessage::EventMsg(_) = m { + if let Some(max_size) = config.limits.max_event_bytes { + // check length, ensure that some max size is set. + if msg.len() > max_size && max_size > 0 { + return Err(Error::EventMaxLengthError(msg.len())); + } + } + } + Ok(m) + } + Err(e) => { + debug!("proto parse error: {:?}", e); + debug!("parse error on message: {}", msg.trim()); + Err(Error::ProtoParseError) + } + } +} + /// Handle new client connections. This runs through an event loop /// for all client communication. async fn nostr_server( pool: db::SqlitePool, - ws_stream: WebSocketStream<Upgraded>, + mut ws_stream: WebSocketStream<Upgraded>, broadcast: Sender<Event>, event_tx: tokio::sync::mpsc::Sender<Event>, mut shutdown: Receiver<()>, @@ -327,7 +376,10 @@ async fn nostr_server( //let conn = tokio_tungstenite::accept_async_with_config(stream, Some(config)).await; //let ws_stream = conn.expect("websocket handshake error"); // wrap websocket into a stream & sink of Nostr protocol messages - let mut nostr_stream = protostream::wrap_ws_in_nostr(ws_stream); + + // don't wrap in a proto stream, because it broke pings. + //let mut nostr_stream = protostream::wrap_ws_in_nostr(ws_stream); + // Track internal client state let mut conn = conn::ClientConn::new(); let cid = conn.get_client_prefix(); @@ -351,9 +403,12 @@ async fn nostr_server( }, Some(query_result) = query_rx.recv() => { // database informed us of a query result we asked for - let res = EventRes(query_result.sub_id,query_result.event); + //let res = EventRes(query_result.sub_id,query_result.event); client_received_event_count += 1; - nostr_stream.send(res).await.ok(); + // send a result + let subesc = query_result.sub_id.replace("\"", ""); + let send_str = format!("[\"EVENT\",\"{}\",{}]", subesc, &query_result.event); + ws_stream.send(Message::Text(send_str)).await.ok(); }, // TODO: consider logging the LaggedRecv error Ok(global_event) = bcast_rx.recv() => { @@ -368,17 +423,34 @@ async fn nostr_server( cid, s, global_event.get_event_id_prefix()); // create an event response and send it - let res = EventRes(s.to_owned(),event_str); - nostr_stream.send(res).await.ok(); + let subesc = s.replace("\"", ""); + ws_stream.send(Message::Text(format!("[\"EVENT\",\"{}\",{}]", subesc, event_str))).await.ok(); + //nostr_stream.send(res).await.ok(); } else { warn!("could not serialize event {:?}", global_event.get_event_id_prefix()); } } }, // check if this client has a subscription - proto_next = nostr_stream.next() => { - match proto_next { - Some(Ok(EventMsg(ec))) => { + ws_next = ws_stream.next() => { + let protomsg = match ws_next { + Some(Ok(Message::Text(m))) => { + let msg_parse = convert_to_msg(m); + Some(msg_parse) + }, + None | Some(Ok(Message::Close(_))) | Some(Err(WsError::AlreadyClosed)) | Some(Err(WsError::ConnectionClosed)) => { + info!("Closing connection"); + None + }, + x => { + info!("message was: {:?} (ignoring)", x); + continue; + } + }; + + // convert ws_next into proto_next + match protomsg { + Some(Ok(NostrMessage::EventMsg(ec))) => { // An EventCmd needs to be validated to be converted into an Event // handle each type of message let parsed : Result<Event> = Result::<Event>::from(ec); @@ -398,11 +470,11 @@ async fn nostr_server( }, Err(_) => { info!("client {:?} sent an invalid event", cid); - nostr_stream.send(NoticeRes("event was invalid".to_owned())).await.ok(); + ws_stream.send(Message::Text(format!("[\"NOTICE\",\"{}\"]", "event was invalid"))).await.ok(); } } }, - Some(Ok(SubMsg(s))) => { + Some(Ok(NostrMessage::SubMsg(s))) => { debug!("client {} requesting a subscription", cid); // subscription handling consists of: // * registering the subscription so future events can be matched @@ -422,12 +494,12 @@ async fn nostr_server( }, Err(e) => { info!("Subscription error: {}", e); - nostr_stream.send(NoticeRes(format!("{}",e))).await.ok(); - + let s = e.to_string().replace("\"", ""); + ws_stream.send(Message::Text(format!("[\"NOTICE\",\"{}\"]", s))).await.ok(); } } }, - Some(Ok(CloseMsg(cc))) => { + Some(Ok(NostrMessage::CloseMsg(cc))) => { // closing a request simply removes the subscription. let parsed : Result<Close> = Result::<Close>::from(cc); match parsed { @@ -458,7 +530,8 @@ async fn nostr_server( } Some(Err(Error::EventMaxLengthError(s))) => { info!("client {:?} sent event larger ({} bytes) than max size", cid, s); - nostr_stream.send(NoticeRes("event exceeded max size".to_owned())).await.ok(); + //TODO + //nostr_stream.send(NoticeRes("event exceeded max size".to_owned())).await.ok(); }, Some(Err(e)) => { info!("got non-fatal error from client: {:?}, error: {:?}", cid, e); diff --git a/src/protostream.rs b/src/protostream.rs @@ -1,141 +0,0 @@ -//! Nostr protocol layered over WebSocket -use crate::close::CloseCmd; -use crate::config; -use crate::error::{Error, Result}; -use crate::event::EventCmd; -use crate::subscription::Subscription; -use core::pin::Pin; -use futures::sink::Sink; -use futures::stream::Stream; -use futures::task::Context; -use futures::task::Poll; -use hyper::upgrade::Upgraded; -use log::*; -use serde::{Deserialize, Serialize}; -use tokio_tungstenite::WebSocketStream; -use tungstenite::error::Error as WsError; -use tungstenite::protocol::Message; - -/// Nostr protocol messages from a client -#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)] -#[serde(untagged)] -pub enum NostrMessage { - /// An `EVENT` message - EventMsg(EventCmd), - /// A `REQ` message - SubMsg(Subscription), - /// A `CLOSE` message - CloseMsg(CloseCmd), -} - -/// Nostr protocol messages from a relay/server -#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)] -pub enum NostrResponse { - /// A `NOTICE` response - NoticeRes(String), - /// An `EVENT` response, composed of the subscription identifier, - /// and serialized event JSON - EventRes(String, String), -} - -/// A Nostr protocol stream is layered on top of a Websocket stream. -pub struct NostrStream { - ws_stream: WebSocketStream<Upgraded>, -} - -/// Given a websocket, return a protocol stream wrapper. -pub fn wrap_ws_in_nostr(ws: WebSocketStream<Upgraded>) -> NostrStream { - NostrStream { ws_stream: ws } -} - -/// Implement the [`Stream`] interface to produce Nostr messages. -impl Stream for NostrStream { - type Item = Result<NostrMessage>; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { - // get the configuration - /// Convert Message to NostrMessage - fn convert(msg: String) -> Result<NostrMessage> { - let config = config::SETTINGS.read().unwrap(); - let parsed_res: Result<NostrMessage> = serde_json::from_str(&msg).map_err(|e| e.into()); - match parsed_res { - Ok(m) => { - if let NostrMessage::EventMsg(_) = m { - if let Some(max_size) = config.limits.max_event_bytes { - // check length, ensure that some max size is set. - if msg.len() > max_size && max_size > 0 { - return Err(Error::EventMaxLengthError(msg.len())); - } - } - } - Ok(m) - } - Err(e) => { - debug!("proto parse error: {:?}", e); - debug!("parse error on message: {}", msg.trim()); - Err(Error::ProtoParseError) - } - } - } - match Pin::new(&mut self.ws_stream).poll_next(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(None) => Poll::Ready(None), - Poll::Ready(Some(v)) => match v { - Ok(Message::Text(vs)) => Poll::Ready(Some(convert(vs))), - Ok(Message::Ping(x)) => { - debug!("client ping ({:?})", x); - //Pin::new(&mut self.ws_stream).start_send(Message::Pong(x)); - // TODO: restructure this so that Pongs work - //Pin::new(&mut self.ws_stream).write_pending(); - //info!("sent pong"); - Poll::Pending - } - Ok(Message::Binary(_)) => Poll::Ready(Some(Err(Error::ProtoParseError))), - Ok(Message::Pong(_)) => Poll::Pending, - Ok(Message::Close(_)) => Poll::Ready(None), - Err(WsError::AlreadyClosed) | Err(WsError::ConnectionClosed) => Poll::Ready(None), - Err(_) => Poll::Ready(Some(Err(Error::ConnError))), - }, - } - } -} - -/// Implement the [`Sink`] interface to produce Nostr responses. -impl Sink<NostrResponse> for NostrStream { - type Error = Error; - - fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { - // map the error type - match Pin::new(&mut self.ws_stream).poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(_)) => Poll::Ready(Err(Error::ConnWriteError)), - Poll::Pending => Poll::Pending, - } - } - - fn start_send(mut self: Pin<&mut Self>, item: NostrResponse) -> Result<(), Self::Error> { - // TODO: do real escaping for these - at least on NOTICE, - // which surely has some problems if arbitrary text is sent. - let send_str = match item { - NostrResponse::NoticeRes(msg) => { - let s = msg.replace("\"", ""); - format!("[\"NOTICE\",\"{}\"]", s) - } - NostrResponse::EventRes(sub, eventstr) => { - let subesc = sub.replace("\"", ""); - format!("[\"EVENT\",\"{}\",{}]", subesc, eventstr) - } - }; - match Pin::new(&mut self.ws_stream).start_send(Message::Text(send_str)) { - Ok(()) => Ok(()), - Err(_) => Err(Error::ConnWriteError), - } - } - - fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { - Poll::Ready(Ok(())) - } - - fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { - Poll::Ready(Ok(())) - } -}