Rename zed-rpc to zrpc

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Antonio Scandurra
2021-07-13 18:22:00 +02:00
co-authored by Nathan Sobo Max Brunsfeld
parent c4e37dc47c
commit 391ad489ff
23 changed files with 48 additions and 48 deletions
+136
View File
@@ -0,0 +1,136 @@
use anyhow::{Context, Result};
use rand::{thread_rng, Rng as _};
use rsa::{PublicKey as _, PublicKeyEncoding, RSAPrivateKey, RSAPublicKey};
use std::convert::TryFrom;
pub struct PublicKey(RSAPublicKey);
pub struct PrivateKey(RSAPrivateKey);
/// Generate a public and private key for asymmetric encryption.
pub fn keypair() -> Result<(PublicKey, PrivateKey)> {
let mut rng = thread_rng();
let bits = 1024;
let private_key = RSAPrivateKey::new(&mut rng, bits)?;
let public_key = RSAPublicKey::from(&private_key);
Ok((PublicKey(public_key), PrivateKey(private_key)))
}
/// Generate a random 64-character base64 string.
pub fn random_token() -> String {
let mut rng = thread_rng();
let mut token_bytes = [0; 48];
for byte in token_bytes.iter_mut() {
*byte = rng.gen();
}
base64::encode_config(&token_bytes, base64::URL_SAFE)
}
impl PublicKey {
/// Convert a string to a base64-encoded string that can only be decoded with the corresponding
/// private key.
pub fn encrypt_string(&self, string: &str) -> Result<String> {
let mut rng = thread_rng();
let bytes = string.as_bytes();
let encrypted_bytes = self
.0
.encrypt(&mut rng, PADDING_SCHEME, bytes)
.context("failed to encrypt string with public key")?;
let encrypted_string = base64::encode_config(&encrypted_bytes, base64::URL_SAFE);
Ok(encrypted_string)
}
}
impl PrivateKey {
/// Decrypt a base64-encoded string that was encrypted by the correspoding public key.
pub fn decrypt_string(&self, encrypted_string: &str) -> Result<String> {
let encrypted_bytes = base64::decode_config(encrypted_string, base64::URL_SAFE)
.context("failed to base64-decode encrypted string")?;
let bytes = self
.0
.decrypt(PADDING_SCHEME, &encrypted_bytes)
.context("failed to decrypt string with private key")?;
let string = String::from_utf8(bytes).context("decrypted content was not valid utf8")?;
Ok(string)
}
}
impl TryFrom<PublicKey> for String {
type Error = anyhow::Error;
fn try_from(key: PublicKey) -> Result<Self> {
let bytes = key.0.to_pkcs1().context("failed to serialize public key")?;
let string = base64::encode_config(&bytes, base64::URL_SAFE);
Ok(string)
}
}
impl TryFrom<String> for PublicKey {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self> {
let bytes = base64::decode_config(&value, base64::URL_SAFE)
.context("failed to base64-decode public key string")?;
let key = Self(RSAPublicKey::from_pkcs1(&bytes).context("failed to parse public key")?);
Ok(key)
}
}
const PADDING_SCHEME: rsa::PaddingScheme = rsa::PaddingScheme::PKCS1v15Encrypt;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_encrypt_and_decrypt_token() {
// CLIENT:
// * generate a keypair for asymmetric encryption
// * serialize the public key to send it to the server.
let (public, private) = keypair().unwrap();
let public_string = String::try_from(public).unwrap();
assert_printable(&public_string);
// SERVER:
// * parse the public key
// * generate a random token.
// * encrypt the token using the public key.
let public = PublicKey::try_from(public_string).unwrap();
let token = random_token();
let encrypted_token = public.encrypt_string(&token).unwrap();
assert_eq!(token.len(), 64);
assert_ne!(encrypted_token, token);
assert_printable(&token);
assert_printable(&encrypted_token);
// CLIENT:
// * decrypt the token using the private key.
let decrypted_token = private.decrypt_string(&encrypted_token).unwrap();
assert_eq!(decrypted_token, token);
}
#[test]
fn test_tokens_are_always_url_safe() {
for _ in 0..5 {
let token = random_token();
let (public_key, _) = keypair().unwrap();
let encrypted_token = public_key.encrypt_string(&token).unwrap();
let public_key_str = String::try_from(public_key).unwrap();
assert_printable(&token);
assert_printable(&public_key_str);
assert_printable(&encrypted_token);
}
}
fn assert_printable(token: &str) {
for c in token.chars() {
assert!(
c.is_ascii_graphic(),
"token {:?} has non-printable char {}",
token,
c
);
assert_ne!(c, '/', "token {:?} is not URL-safe", token);
assert_ne!(c, '&', "token {:?} is not URL-safe", token);
}
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod auth;
mod peer;
pub mod proto;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
pub use peer::*;
+702
View File
@@ -0,0 +1,702 @@
use crate::proto::{self, EnvelopedMessage, MessageStream, RequestMessage};
use anyhow::{anyhow, Context, Result};
use async_lock::{Mutex, RwLock};
use async_tungstenite::tungstenite::{Error as WebSocketError, Message as WebSocketMessage};
use futures::{
future::{BoxFuture, LocalBoxFuture},
FutureExt, StreamExt,
};
use postage::{
mpsc,
prelude::{Sink, Stream},
};
use std::{
any::TypeId,
collections::{HashMap, HashSet},
fmt,
future::Future,
marker::PhantomData,
sync::{
atomic::{self, AtomicU32},
Arc,
},
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ConnectionId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PeerId(pub u32);
type MessageHandler = Box<
dyn Send
+ Sync
+ Fn(&mut Option<proto::Envelope>, ConnectionId) -> Option<BoxFuture<'static, ()>>,
>;
type ForegroundMessageHandler =
Box<dyn Fn(&mut Option<proto::Envelope>, ConnectionId) -> Option<LocalBoxFuture<'static, ()>>>;
pub struct Receipt<T> {
sender_id: ConnectionId,
message_id: u32,
payload_type: PhantomData<T>,
}
pub struct TypedEnvelope<T> {
pub sender_id: ConnectionId,
original_sender_id: Option<PeerId>,
pub message_id: u32,
pub payload: T,
}
impl<T> TypedEnvelope<T> {
pub fn original_sender_id(&self) -> Result<PeerId> {
self.original_sender_id
.ok_or_else(|| anyhow!("missing original_sender_id"))
}
}
impl<T: RequestMessage> TypedEnvelope<T> {
pub fn receipt(&self) -> Receipt<T> {
Receipt {
sender_id: self.sender_id,
message_id: self.message_id,
payload_type: PhantomData,
}
}
}
pub type Router = RouterInternal<MessageHandler>;
pub type ForegroundRouter = RouterInternal<ForegroundMessageHandler>;
pub struct RouterInternal<H> {
message_handlers: Vec<H>,
handler_types: HashSet<TypeId>,
}
pub struct Peer {
connections: RwLock<HashMap<ConnectionId, Connection>>,
next_connection_id: AtomicU32,
}
#[derive(Clone)]
struct Connection {
outgoing_tx: mpsc::Sender<proto::Envelope>,
next_message_id: Arc<AtomicU32>,
response_channels: Arc<Mutex<HashMap<u32, mpsc::Sender<proto::Envelope>>>>,
}
impl Peer {
pub fn new() -> Arc<Self> {
Arc::new(Self {
connections: Default::default(),
next_connection_id: Default::default(),
})
}
pub async fn add_connection<Conn, H, Fut>(
self: &Arc<Self>,
conn: Conn,
router: Arc<RouterInternal<H>>,
) -> (
ConnectionId,
impl Future<Output = anyhow::Result<()>> + Send,
impl Future<Output = ()>,
)
where
H: Fn(&mut Option<proto::Envelope>, ConnectionId) -> Option<Fut>,
Fut: Future<Output = ()>,
Conn: futures::Sink<WebSocketMessage, Error = WebSocketError>
+ futures::Stream<Item = Result<WebSocketMessage, WebSocketError>>
+ Send
+ Unpin,
{
let (tx, rx) = conn.split();
let connection_id = ConnectionId(
self.next_connection_id
.fetch_add(1, atomic::Ordering::SeqCst),
);
let (mut incoming_tx, mut incoming_rx) = mpsc::channel(64);
let (outgoing_tx, mut outgoing_rx) = mpsc::channel(64);
let connection = Connection {
outgoing_tx,
next_message_id: Default::default(),
response_channels: Default::default(),
};
let mut writer = MessageStream::new(tx);
let mut reader = MessageStream::new(rx);
let handle_io = async move {
loop {
let read_message = reader.read_message().fuse();
futures::pin_mut!(read_message);
loop {
futures::select_biased! {
incoming = read_message => match incoming {
Ok(incoming) => {
if incoming_tx.send(incoming).await.is_err() {
return Ok(());
}
break;
}
Err(error) => {
Err(error).context("received invalid RPC message")?;
}
},
outgoing = outgoing_rx.recv().fuse() => match outgoing {
Some(outgoing) => {
if let Err(result) = writer.write_message(&outgoing).await {
Err(result).context("failed to write RPC message")?;
}
}
None => return Ok(()),
}
}
}
}
};
let response_channels = connection.response_channels.clone();
let handle_messages = async move {
while let Some(message) = incoming_rx.recv().await {
if let Some(responding_to) = message.responding_to {
let channel = response_channels.lock().await.remove(&responding_to);
if let Some(mut tx) = channel {
tx.send(message).await.ok();
} else {
log::warn!("received RPC response to unknown request {}", responding_to);
}
} else {
router.handle(connection_id, message).await;
}
}
response_channels.lock().await.clear();
};
self.connections
.write()
.await
.insert(connection_id, connection);
(connection_id, handle_io, handle_messages)
}
pub async fn disconnect(&self, connection_id: ConnectionId) {
self.connections.write().await.remove(&connection_id);
}
pub async fn reset(&self) {
self.connections.write().await.clear();
}
pub fn request<T: RequestMessage>(
self: &Arc<Self>,
receiver_id: ConnectionId,
request: T,
) -> impl Future<Output = Result<T::Response>> {
self.request_internal(None, receiver_id, request)
}
pub fn forward_request<T: RequestMessage>(
self: &Arc<Self>,
sender_id: ConnectionId,
receiver_id: ConnectionId,
request: T,
) -> impl Future<Output = Result<T::Response>> {
self.request_internal(Some(sender_id), receiver_id, request)
}
pub fn request_internal<T: RequestMessage>(
self: &Arc<Self>,
original_sender_id: Option<ConnectionId>,
receiver_id: ConnectionId,
request: T,
) -> impl Future<Output = Result<T::Response>> {
let this = self.clone();
let (tx, mut rx) = mpsc::channel(1);
async move {
let mut connection = this.connection(receiver_id).await?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.response_channels
.lock()
.await
.insert(message_id, tx);
connection
.outgoing_tx
.send(request.into_envelope(message_id, None, original_sender_id.map(|id| id.0)))
.await?;
let response = rx
.recv()
.await
.ok_or_else(|| anyhow!("connection was closed"))?;
T::Response::from_envelope(response)
.ok_or_else(|| anyhow!("received response of the wrong type"))
}
}
pub fn send<T: EnvelopedMessage>(
self: &Arc<Self>,
receiver_id: ConnectionId,
message: T,
) -> impl Future<Output = Result<()>> {
let this = self.clone();
async move {
let mut connection = this.connection(receiver_id).await?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.send(message.into_envelope(message_id, None, None))
.await?;
Ok(())
}
}
pub fn forward_send<T: EnvelopedMessage>(
self: &Arc<Self>,
sender_id: ConnectionId,
receiver_id: ConnectionId,
message: T,
) -> impl Future<Output = Result<()>> {
let this = self.clone();
async move {
let mut connection = this.connection(receiver_id).await?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.send(message.into_envelope(message_id, None, Some(sender_id.0)))
.await?;
Ok(())
}
}
pub fn respond<T: RequestMessage>(
self: &Arc<Self>,
receipt: Receipt<T>,
response: T::Response,
) -> impl Future<Output = Result<()>> {
let this = self.clone();
async move {
let mut connection = this.connection(receipt.sender_id).await?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.send(response.into_envelope(message_id, Some(receipt.message_id), None))
.await?;
Ok(())
}
}
fn connection(
self: &Arc<Self>,
connection_id: ConnectionId,
) -> impl Future<Output = Result<Connection>> {
let this = self.clone();
async move {
let connections = this.connections.read().await;
let connection = connections
.get(&connection_id)
.ok_or_else(|| anyhow!("no such connection: {}", connection_id))?;
Ok(connection.clone())
}
}
}
impl<H, Fut> RouterInternal<H>
where
H: Fn(&mut Option<proto::Envelope>, ConnectionId) -> Option<Fut>,
Fut: Future<Output = ()>,
{
pub fn new() -> Self {
Self {
message_handlers: Default::default(),
handler_types: Default::default(),
}
}
async fn handle(&self, connection_id: ConnectionId, message: proto::Envelope) {
let mut envelope = Some(message);
for handler in self.message_handlers.iter() {
if let Some(future) = handler(&mut envelope, connection_id) {
future.await;
return;
}
}
log::warn!("unhandled message: {:?}", envelope.unwrap().payload);
}
}
impl Router {
pub fn add_message_handler<T, Fut, F>(&mut self, handler: F)
where
T: EnvelopedMessage,
Fut: 'static + Send + Future<Output = Result<()>>,
F: 'static + Send + Sync + Fn(TypedEnvelope<T>) -> Fut,
{
if !self.handler_types.insert(TypeId::of::<T>()) {
panic!("duplicate handler type");
}
self.message_handlers
.push(Box::new(move |envelope, connection_id| {
if envelope.as_ref().map_or(false, T::matches_envelope) {
let envelope = Option::take(envelope).unwrap();
let message_id = envelope.id;
let future = handler(TypedEnvelope {
sender_id: connection_id,
original_sender_id: envelope.original_sender_id.map(PeerId),
message_id,
payload: T::from_envelope(envelope).unwrap(),
});
Some(
async move {
if let Err(error) = future.await {
log::error!(
"error handling message {} {}: {:?}",
T::NAME,
message_id,
error
);
}
}
.boxed(),
)
} else {
None
}
}));
}
}
impl ForegroundRouter {
pub fn add_message_handler<T, Fut, F>(&mut self, handler: F)
where
T: EnvelopedMessage,
Fut: 'static + Future<Output = Result<()>>,
F: 'static + Fn(TypedEnvelope<T>) -> Fut,
{
if !self.handler_types.insert(TypeId::of::<T>()) {
panic!("duplicate handler type");
}
self.message_handlers
.push(Box::new(move |envelope, connection_id| {
if envelope.as_ref().map_or(false, T::matches_envelope) {
let envelope = Option::take(envelope).unwrap();
let message_id = envelope.id;
let future = handler(TypedEnvelope {
sender_id: connection_id,
original_sender_id: envelope.original_sender_id.map(PeerId),
message_id,
payload: T::from_envelope(envelope).unwrap(),
});
Some(
async move {
if let Err(error) = future.await {
log::error!(
"error handling message {} {}: {:?}",
T::NAME,
message_id,
error
);
}
}
.boxed_local(),
)
} else {
None
}
}));
}
}
impl<T> Clone for Receipt<T> {
fn clone(&self) -> Self {
Self {
sender_id: self.sender_id,
message_id: self.message_id,
payload_type: PhantomData,
}
}
}
impl<T> Copy for Receipt<T> {}
impl fmt::Display for ConnectionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Display for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test;
#[test]
fn test_request_response() {
smol::block_on(async move {
// create 2 clients connected to 1 server
let server = Peer::new();
let client1 = Peer::new();
let client2 = Peer::new();
let mut router = Router::new();
router.add_message_handler({
let server = server.clone();
move |envelope: TypedEnvelope<proto::Auth>| {
let server = server.clone();
async move {
let receipt = envelope.receipt();
let message = envelope.payload;
server
.respond(
receipt,
match message.user_id {
1 => {
assert_eq!(message.access_token, "access-token-1");
proto::AuthResponse {
credentials_valid: true,
}
}
2 => {
assert_eq!(message.access_token, "access-token-2");
proto::AuthResponse {
credentials_valid: false,
}
}
_ => {
panic!("unexpected user id {}", message.user_id);
}
},
)
.await
}
}
});
router.add_message_handler({
let server = server.clone();
move |envelope: TypedEnvelope<proto::OpenBuffer>| {
let server = server.clone();
async move {
let receipt = envelope.receipt();
let message = envelope.payload;
server
.respond(
receipt,
match message.path.as_str() {
"path/one" => {
assert_eq!(message.worktree_id, 1);
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 101,
content: "path/one content".to_string(),
history: vec![],
selections: vec![],
}),
}
}
"path/two" => {
assert_eq!(message.worktree_id, 2);
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 102,
content: "path/two content".to_string(),
history: vec![],
selections: vec![],
}),
}
}
_ => {
panic!("unexpected path {}", message.path);
}
},
)
.await
}
}
});
let router = Arc::new(router);
let (client1_to_server_conn, server_to_client_1_conn) = test::Channel::bidirectional();
let (client1_conn_id, io_task1, msg_task1) = client1
.add_connection(client1_to_server_conn, router.clone())
.await;
let (_, io_task2, msg_task2) = server
.add_connection(server_to_client_1_conn, router.clone())
.await;
let (client2_to_server_conn, server_to_client_2_conn) = test::Channel::bidirectional();
let (client2_conn_id, io_task3, msg_task3) = client2
.add_connection(client2_to_server_conn, router.clone())
.await;
let (_, io_task4, msg_task4) = server
.add_connection(server_to_client_2_conn, router.clone())
.await;
smol::spawn(io_task1).detach();
smol::spawn(io_task2).detach();
smol::spawn(io_task3).detach();
smol::spawn(io_task4).detach();
smol::spawn(msg_task1).detach();
smol::spawn(msg_task2).detach();
smol::spawn(msg_task3).detach();
smol::spawn(msg_task4).detach();
assert_eq!(
client1
.request(
client1_conn_id,
proto::Auth {
user_id: 1,
access_token: "access-token-1".to_string(),
},
)
.await
.unwrap(),
proto::AuthResponse {
credentials_valid: true,
}
);
assert_eq!(
client2
.request(
client2_conn_id,
proto::Auth {
user_id: 2,
access_token: "access-token-2".to_string(),
},
)
.await
.unwrap(),
proto::AuthResponse {
credentials_valid: false,
}
);
assert_eq!(
client1
.request(
client1_conn_id,
proto::OpenBuffer {
worktree_id: 1,
path: "path/one".to_string(),
},
)
.await
.unwrap(),
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 101,
content: "path/one content".to_string(),
history: vec![],
selections: vec![],
}),
}
);
assert_eq!(
client2
.request(
client2_conn_id,
proto::OpenBuffer {
worktree_id: 2,
path: "path/two".to_string(),
},
)
.await
.unwrap(),
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 102,
content: "path/two content".to_string(),
history: vec![],
selections: vec![],
}),
}
);
client1.disconnect(client1_conn_id).await;
client2.disconnect(client1_conn_id).await;
});
}
#[test]
fn test_disconnect() {
smol::block_on(async move {
let (client_conn, mut server_conn) = test::Channel::bidirectional();
let client = Peer::new();
let router = Arc::new(Router::new());
let (connection_id, io_handler, message_handler) =
client.add_connection(client_conn, router).await;
let (mut io_ended_tx, mut io_ended_rx) = postage::barrier::channel();
smol::spawn(async move {
io_handler.await.ok();
io_ended_tx.send(()).await.unwrap();
})
.detach();
let (mut messages_ended_tx, mut messages_ended_rx) = postage::barrier::channel();
smol::spawn(async move {
message_handler.await;
messages_ended_tx.send(()).await.unwrap();
})
.detach();
client.disconnect(connection_id).await;
io_ended_rx.recv().await;
messages_ended_rx.recv().await;
assert!(
futures::SinkExt::send(&mut server_conn, WebSocketMessage::Binary(vec![]))
.await
.is_err()
);
});
}
#[test]
fn test_io_error() {
smol::block_on(async move {
let (client_conn, server_conn) = test::Channel::bidirectional();
drop(server_conn);
let client = Peer::new();
let router = Arc::new(Router::new());
let (connection_id, io_handler, message_handler) =
client.add_connection(client_conn, router).await;
smol::spawn(io_handler).detach();
smol::spawn(message_handler).detach();
let err = client
.request(
connection_id,
proto::Auth {
user_id: 42,
access_token: "token".to_string(),
},
)
.await
.unwrap_err();
assert_eq!(err.to_string(), "connection was closed");
});
}
}
+181
View File
@@ -0,0 +1,181 @@
use async_tungstenite::tungstenite::{Error as WebSocketError, Message as WebSocketMessage};
use futures::{SinkExt as _, StreamExt as _};
use prost::Message;
use std::{
io,
time::{Duration, SystemTime, UNIX_EPOCH},
};
include!(concat!(env!("OUT_DIR"), "/zed.messages.rs"));
pub trait EnvelopedMessage: Clone + Sized + Send + 'static {
const NAME: &'static str;
fn into_envelope(
self,
id: u32,
responding_to: Option<u32>,
original_sender_id: Option<u32>,
) -> Envelope;
fn matches_envelope(envelope: &Envelope) -> bool;
fn from_envelope(envelope: Envelope) -> Option<Self>;
}
pub trait RequestMessage: EnvelopedMessage {
type Response: EnvelopedMessage;
}
macro_rules! message {
($name:ident) => {
impl EnvelopedMessage for $name {
const NAME: &'static str = std::stringify!($name);
fn into_envelope(
self,
id: u32,
responding_to: Option<u32>,
original_sender_id: Option<u32>,
) -> Envelope {
Envelope {
id,
responding_to,
original_sender_id,
payload: Some(envelope::Payload::$name(self)),
}
}
fn matches_envelope(envelope: &Envelope) -> bool {
matches!(&envelope.payload, Some(envelope::Payload::$name(_)))
}
fn from_envelope(envelope: Envelope) -> Option<Self> {
if let Some(envelope::Payload::$name(msg)) = envelope.payload {
Some(msg)
} else {
None
}
}
}
};
}
macro_rules! request_message {
($req:ident, $resp:ident) => {
message!($req);
message!($resp);
impl RequestMessage for $req {
type Response = $resp;
}
};
}
request_message!(Auth, AuthResponse);
request_message!(ShareWorktree, ShareWorktreeResponse);
request_message!(OpenWorktree, OpenWorktreeResponse);
message!(UpdateWorktree);
message!(CloseWorktree);
request_message!(OpenBuffer, OpenBufferResponse);
message!(CloseBuffer);
message!(UpdateBuffer);
request_message!(SaveBuffer, BufferSaved);
message!(AddPeer);
message!(RemovePeer);
/// A stream of protobuf messages.
pub struct MessageStream<S> {
stream: S,
}
impl<S> MessageStream<S> {
pub fn new(stream: S) -> Self {
Self { stream }
}
pub fn inner_mut(&mut self) -> &mut S {
&mut self.stream
}
}
impl<S> MessageStream<S>
where
S: futures::Sink<WebSocketMessage, Error = WebSocketError> + Unpin,
{
/// Write a given protobuf message to the stream.
pub async fn write_message(&mut self, message: &Envelope) -> Result<(), WebSocketError> {
let mut buffer = Vec::with_capacity(message.encoded_len());
message
.encode(&mut buffer)
.map_err(|err| io::Error::from(err))?;
self.stream.send(WebSocketMessage::Binary(buffer)).await?;
Ok(())
}
}
impl<S> MessageStream<S>
where
S: futures::Stream<Item = Result<WebSocketMessage, WebSocketError>> + Unpin,
{
/// Read a protobuf message of the given type from the stream.
pub async fn read_message(&mut self) -> Result<Envelope, WebSocketError> {
while let Some(bytes) = self.stream.next().await {
match bytes? {
WebSocketMessage::Binary(bytes) => {
let envelope = Envelope::decode(bytes.as_slice()).map_err(io::Error::from)?;
return Ok(envelope);
}
WebSocketMessage::Close(_) => break,
_ => {}
}
}
Err(WebSocketError::ConnectionClosed)
}
}
impl Into<SystemTime> for Timestamp {
fn into(self) -> SystemTime {
UNIX_EPOCH
.checked_add(Duration::new(self.seconds, self.nanos))
.unwrap()
}
}
impl From<SystemTime> for Timestamp {
fn from(time: SystemTime) -> Self {
let duration = time.duration_since(UNIX_EPOCH).unwrap();
Self {
seconds: duration.as_secs(),
nanos: duration.subsec_nanos(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test;
#[test]
fn test_round_trip_message() {
smol::block_on(async {
let stream = test::Channel::new();
let message1 = Auth {
user_id: 5,
access_token: "the-access-token".into(),
}
.into_envelope(3, None, None);
let message2 = OpenBuffer {
worktree_id: 0,
path: "some/path".to_string(),
}
.into_envelope(5, None, None);
let mut message_stream = MessageStream::new(stream);
message_stream.write_message(&message1).await.unwrap();
message_stream.write_message(&message2).await.unwrap();
let decoded_message1 = message_stream.read_message().await.unwrap();
let decoded_message2 = message_stream.read_message().await.unwrap();
assert_eq!(decoded_message1, message1);
assert_eq!(decoded_message2, message2);
});
}
}
+64
View File
@@ -0,0 +1,64 @@
use async_tungstenite::tungstenite::{Error as WebSocketError, Message as WebSocketMessage};
use std::{
io,
pin::Pin,
task::{Context, Poll},
};
pub struct Channel {
tx: futures::channel::mpsc::UnboundedSender<WebSocketMessage>,
rx: futures::channel::mpsc::UnboundedReceiver<WebSocketMessage>,
}
impl Channel {
pub fn new() -> Self {
let (tx, rx) = futures::channel::mpsc::unbounded();
Self { tx, rx }
}
pub fn bidirectional() -> (Self, Self) {
let (a_tx, a_rx) = futures::channel::mpsc::unbounded();
let (b_tx, b_rx) = futures::channel::mpsc::unbounded();
let a = Self { tx: a_tx, rx: b_rx };
let b = Self { tx: b_tx, rx: a_rx };
(a, b)
}
}
impl futures::Sink<WebSocketMessage> for Channel {
type Error = WebSocketError;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.tx)
.poll_ready(cx)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err).into())
}
fn start_send(mut self: Pin<&mut Self>, item: WebSocketMessage) -> Result<(), Self::Error> {
Pin::new(&mut self.tx)
.start_send(item)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err).into())
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.tx)
.poll_flush(cx)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err).into())
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.tx)
.poll_close(cx)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err).into())
}
}
impl futures::Stream for Channel {
type Item = Result<WebSocketMessage, WebSocketError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.rx)
.poll_next(cx)
.map(|i| i.map(|i| Ok(i)))
}
}