logiguard fork v3: full patch set on verified 8c74db0 tree
Includes prior-session patches (carry forward so the app compiles): - crates/gpui/build.rs: cross-compile manifest fix - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method - crates/gpui/src/window.rs: Window::activate_with_token public API - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix Plus the focus-serial fix: - serial.rs: SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; latest_serial_of() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
43
crates/rpc/Cargo.toml
Normal file
43
crates/rpc/Cargo.toml
Normal file
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
description = "Shared logic for communication between the Zed app and the zed.dev server"
|
||||
edition.workspace = true
|
||||
name = "rpc"
|
||||
version = "0.1.0"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/rpc.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
gpui = ["dep:gpui"]
|
||||
test-support = ["collections/test-support", "gpui/test-support", "proto/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-tungstenite.workspace = true
|
||||
base64.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
gpui = { workspace = true, optional = true }
|
||||
parking_lot.workspace = true
|
||||
proto.workspace = true
|
||||
rand.workspace = true
|
||||
rsa.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
strum.workspace = true
|
||||
tracing = { version = "0.1.34", features = ["log"] }
|
||||
util.workspace = true
|
||||
zstd.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
proto = { workspace = true, features = ["test-support"] }
|
||||
zlog.workspace = true
|
||||
1
crates/rpc/LICENSE-GPL
Symbolic link
1
crates/rpc/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
240
crates/rpc/src/auth.rs
Normal file
240
crates/rpc/src/auth.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use base64::prelude::*;
|
||||
use rand::prelude::*;
|
||||
use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey};
|
||||
use rsa::traits::PaddingScheme;
|
||||
use rsa::{Oaep, Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
|
||||
use sha2::Sha256;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
fn oaep_sha256_padding() -> impl PaddingScheme {
|
||||
Oaep::new::<Sha256>()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum EncryptionFormat {
|
||||
/// The original encryption format.
|
||||
///
|
||||
/// This is using [`Pkcs1v15Encrypt`], which is vulnerable to side-channel attacks.
|
||||
/// As such, we're in the process of phasing it out.
|
||||
///
|
||||
/// See [here](https://people.redhat.com/~hkario/marvin/) for more details.
|
||||
V0,
|
||||
|
||||
/// The new encryption key format using Optimal Asymmetric Encryption Padding (OAEP) with a SHA-256 digest.
|
||||
V1,
|
||||
}
|
||||
|
||||
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 = RsaRngCompat::new();
|
||||
let bits = 2048;
|
||||
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 = rand::rng();
|
||||
let mut token_bytes = [0; 48];
|
||||
for byte in token_bytes.iter_mut() {
|
||||
*byte = rng.random();
|
||||
}
|
||||
BASE64_URL_SAFE.encode(token_bytes)
|
||||
}
|
||||
|
||||
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, format: EncryptionFormat) -> Result<String> {
|
||||
let mut rng = RsaRngCompat::new();
|
||||
let bytes = string.as_bytes();
|
||||
let encrypted_bytes = match format {
|
||||
EncryptionFormat::V0 => self.0.encrypt(&mut rng, Pkcs1v15Encrypt, bytes),
|
||||
EncryptionFormat::V1 => self.0.encrypt(&mut rng, oaep_sha256_padding(), bytes),
|
||||
}
|
||||
.context("failed to encrypt string with public key")?;
|
||||
let encrypted_string = BASE64_URL_SAFE.encode(&encrypted_bytes);
|
||||
Ok(encrypted_string)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey {
|
||||
/// Decrypt a base64-encoded string that was encrypted by the corresponding public key.
|
||||
pub fn decrypt_string(&self, encrypted_string: &str) -> Result<String> {
|
||||
let encrypted_bytes = BASE64_URL_SAFE
|
||||
.decode(encrypted_string)
|
||||
.context("failed to base64-decode encrypted string")?;
|
||||
let bytes = self
|
||||
.0
|
||||
.decrypt(oaep_sha256_padding(), &encrypted_bytes)
|
||||
.or_else(|_err| {
|
||||
// If we failed to decrypt using the new format, try decrypting with the old
|
||||
// one to handle mismatches between the client and server.
|
||||
self.0.decrypt(Pkcs1v15Encrypt, &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_der()
|
||||
.context("failed to serialize public key")?;
|
||||
let string = BASE64_URL_SAFE.encode(&bytes);
|
||||
Ok(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for PublicKey {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: String) -> Result<Self> {
|
||||
let bytes = BASE64_URL_SAFE
|
||||
.decode(&value)
|
||||
.context("failed to base64-decode public key string")?;
|
||||
let key = Self(RsaPublicKey::from_pkcs1_der(&bytes).context("failed to parse public key")?);
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove once we rsa v0.10 is released.
|
||||
struct RsaRngCompat(rand::rngs::ThreadRng);
|
||||
|
||||
impl RsaRngCompat {
|
||||
fn new() -> Self {
|
||||
Self(rand::rng())
|
||||
}
|
||||
}
|
||||
|
||||
impl rsa::signature::rand_core::RngCore for RsaRngCompat {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.next_u32()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.0.next_u64()
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
self.0.fill_bytes(dest);
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rsa::signature::rand_core::Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl rsa::signature::rand_core::CryptoRng for RsaRngCompat {}
|
||||
|
||||
#[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, EncryptionFormat::V1).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_generate_encrypt_and_decrypt_token_with_v0_encryption_format() {
|
||||
// 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, EncryptionFormat::V0).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_encode_and_decode_base64_public_key() {
|
||||
// A base64-encoded public key.
|
||||
//
|
||||
// We're using a literal string to ensure that encoding and decoding works across differences in implementations.
|
||||
let encoded_public_key = "MIGJAoGBAMPvufou8wOuUIF1Wlkbtn0ZMM9nC55QJ06nTZvgMfZv5esFVU9-cQO_JC1P9ZoEcMDJweFERnQuQLqzsrMDLFbkdgL128ZU43WOLiQraxaICFIZsPUeTtWMKp2D5bPWsNxs-lnCma7vCAry6fpXuj5AKQdk7cTZJNucgvZQ0uUfAgMBAAE=".to_string();
|
||||
|
||||
// Make sure we can parse the public key.
|
||||
let public_key = PublicKey::try_from(encoded_public_key.clone()).unwrap();
|
||||
|
||||
// Make sure we re-encode to the same format.
|
||||
assert_eq!(encoded_public_key, String::try_from(public_key).unwrap());
|
||||
}
|
||||
|
||||
#[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, EncryptionFormat::V1)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
102
crates/rpc/src/conn.rs
Normal file
102
crates/rpc/src/conn.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use async_tungstenite::tungstenite::Message as WebSocketMessage;
|
||||
use futures::{SinkExt as _, StreamExt as _};
|
||||
|
||||
pub struct Connection {
|
||||
pub(crate) tx:
|
||||
Box<dyn 'static + Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
|
||||
pub(crate) rx:
|
||||
Box<dyn 'static + Send + Unpin + futures::Stream<Item = anyhow::Result<WebSocketMessage>>>,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn new<S>(stream: S) -> Self
|
||||
where
|
||||
S: 'static
|
||||
+ Send
|
||||
+ Unpin
|
||||
+ futures::Sink<WebSocketMessage, Error = anyhow::Error>
|
||||
+ futures::Stream<Item = anyhow::Result<WebSocketMessage>>,
|
||||
{
|
||||
let (tx, rx) = stream.split();
|
||||
Self {
|
||||
tx: Box::new(tx),
|
||||
rx: Box::new(rx),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, message: WebSocketMessage) -> anyhow::Result<()> {
|
||||
self.tx.send(message).await
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn in_memory(
|
||||
executor: gpui::BackgroundExecutor,
|
||||
) -> (Self, Self, std::sync::Arc<std::sync::atomic::AtomicBool>) {
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
};
|
||||
|
||||
let killed = Arc::new(AtomicBool::new(false));
|
||||
let (a_tx, a_rx) = channel(killed.clone(), executor.clone());
|
||||
let (b_tx, b_rx) = channel(killed.clone(), executor);
|
||||
return (
|
||||
Self { tx: a_tx, rx: b_rx },
|
||||
Self { tx: b_tx, rx: a_rx },
|
||||
killed,
|
||||
);
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn channel(
|
||||
killed: Arc<AtomicBool>,
|
||||
executor: gpui::BackgroundExecutor,
|
||||
) -> (
|
||||
Box<dyn Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
|
||||
Box<dyn Send + Unpin + futures::Stream<Item = anyhow::Result<WebSocketMessage>>>,
|
||||
) {
|
||||
use anyhow::anyhow;
|
||||
use futures::channel::mpsc;
|
||||
use std::io::Error;
|
||||
|
||||
let (tx, rx) = mpsc::unbounded::<WebSocketMessage>();
|
||||
|
||||
let tx = tx.sink_map_err(|error| anyhow!(error)).with({
|
||||
let killed = killed.clone();
|
||||
let executor = executor.clone();
|
||||
move |msg| {
|
||||
let killed = killed.clone();
|
||||
let executor = executor.clone();
|
||||
Box::pin(async move {
|
||||
executor.simulate_random_delay().await;
|
||||
|
||||
// Writes to a half-open TCP connection will error.
|
||||
if killed.load(SeqCst) {
|
||||
std::io::Result::Err(Error::other("connection lost"))?;
|
||||
}
|
||||
|
||||
Ok(msg)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let rx = rx.then({
|
||||
move |msg| {
|
||||
let killed = killed.clone();
|
||||
let executor = executor.clone();
|
||||
Box::pin(async move {
|
||||
executor.simulate_random_delay().await;
|
||||
|
||||
// Reads from a half-open TCP connection will hang.
|
||||
if killed.load(SeqCst) {
|
||||
futures::future::pending::<()>().await;
|
||||
}
|
||||
|
||||
Ok(msg)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
(Box::new(tx), Box::new(rx))
|
||||
}
|
||||
}
|
||||
}
|
||||
73
crates/rpc/src/macros.rs
Normal file
73
crates/rpc/src/macros.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
#[macro_export]
|
||||
macro_rules! messages {
|
||||
($(($name:ident, $priority:ident)),* $(,)?) => {
|
||||
pub fn build_typed_envelope(sender_id: ConnectionId, received_at: Instant, envelope: Envelope) -> Option<Box<dyn AnyTypedEnvelope>> {
|
||||
match envelope.payload {
|
||||
$(Some(envelope::Payload::$name(payload)) => {
|
||||
Some(Box::new(TypedEnvelope {
|
||||
sender_id,
|
||||
original_sender_id: envelope.original_sender_id.map(|original_sender| PeerId {
|
||||
owner_id: original_sender.owner_id,
|
||||
id: original_sender.id
|
||||
}),
|
||||
message_id: envelope.id,
|
||||
payload,
|
||||
received_at,
|
||||
}))
|
||||
}, )*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl EnvelopedMessage for $name {
|
||||
const NAME: &'static str = std::stringify!($name);
|
||||
const PRIORITY: MessagePriority = MessagePriority::$priority;
|
||||
|
||||
fn into_envelope(
|
||||
self,
|
||||
id: u32,
|
||||
responding_to: Option<u32>,
|
||||
original_sender_id: Option<PeerId>,
|
||||
) -> Envelope {
|
||||
Envelope {
|
||||
id,
|
||||
responding_to,
|
||||
original_sender_id,
|
||||
payload: Some(envelope::Payload::$name(self)),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_envelope(envelope: Envelope) -> Option<Self> {
|
||||
if let Some(envelope::Payload::$name(msg)) = envelope.payload {
|
||||
Some(msg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! request_messages {
|
||||
($(($request_name:ident, $response_name:ident)),* $(,)?) => {
|
||||
$(impl RequestMessage for $request_name {
|
||||
type Response = $response_name;
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! entity_messages {
|
||||
({$id_field:ident, $entity_type:ty}, $($name:ident),* $(,)?) => {
|
||||
$(impl EntityMessage for $name {
|
||||
type Entity = $entity_type;
|
||||
|
||||
fn remote_entity_id(&self) -> u64 {
|
||||
self.$id_field
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
145
crates/rpc/src/message_stream.rs
Normal file
145
crates/rpc/src/message_stream.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub use ::proto::*;
|
||||
|
||||
use async_tungstenite::tungstenite::Message as WebSocketMessage;
|
||||
use futures::{SinkExt as _, StreamExt as _};
|
||||
use proto::Message as _;
|
||||
use std::time::Instant;
|
||||
use std::{fmt::Debug, io};
|
||||
|
||||
const KIB: usize = 1024;
|
||||
const MIB: usize = KIB * 1024;
|
||||
const MAX_BUFFER_LEN: usize = MIB;
|
||||
|
||||
/// A stream of protobuf messages.
|
||||
pub struct MessageStream<S> {
|
||||
stream: S,
|
||||
encoding_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
Envelope(Envelope),
|
||||
Ping,
|
||||
Pong,
|
||||
}
|
||||
|
||||
impl<S> MessageStream<S> {
|
||||
pub fn new(stream: S) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
encoding_buffer: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> MessageStream<S>
|
||||
where
|
||||
S: futures::Sink<WebSocketMessage, Error = anyhow::Error> + Unpin,
|
||||
{
|
||||
pub async fn write(&mut self, message: Message) -> anyhow::Result<()> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
const COMPRESSION_LEVEL: i32 = -7;
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
const COMPRESSION_LEVEL: i32 = 4;
|
||||
|
||||
match message {
|
||||
Message::Envelope(message) => {
|
||||
self.encoding_buffer.reserve(message.encoded_len());
|
||||
message
|
||||
.encode(&mut self.encoding_buffer)
|
||||
.map_err(io::Error::from)?;
|
||||
let buffer =
|
||||
zstd::stream::encode_all(self.encoding_buffer.as_slice(), COMPRESSION_LEVEL)
|
||||
.unwrap();
|
||||
|
||||
self.encoding_buffer.clear();
|
||||
self.encoding_buffer.shrink_to(MAX_BUFFER_LEN);
|
||||
self.stream
|
||||
.send(WebSocketMessage::Binary(buffer.into()))
|
||||
.await?;
|
||||
}
|
||||
Message::Ping => {
|
||||
self.stream
|
||||
.send(WebSocketMessage::Ping(Default::default()))
|
||||
.await?;
|
||||
}
|
||||
Message::Pong => {
|
||||
self.stream
|
||||
.send(WebSocketMessage::Pong(Default::default()))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> MessageStream<S>
|
||||
where
|
||||
S: futures::Stream<Item = anyhow::Result<WebSocketMessage>> + Unpin,
|
||||
{
|
||||
pub async fn read(&mut self) -> anyhow::Result<(Message, Instant)> {
|
||||
while let Some(bytes) = self.stream.next().await {
|
||||
let received_at = Instant::now();
|
||||
match bytes? {
|
||||
WebSocketMessage::Binary(bytes) => {
|
||||
zstd::stream::copy_decode(
|
||||
zstd::zstd_safe::WriteBuf::as_slice(&*bytes),
|
||||
&mut self.encoding_buffer,
|
||||
)?;
|
||||
let envelope = Envelope::decode(self.encoding_buffer.as_slice())
|
||||
.map_err(io::Error::from)?;
|
||||
|
||||
self.encoding_buffer.clear();
|
||||
self.encoding_buffer.shrink_to(MAX_BUFFER_LEN);
|
||||
return Ok((Message::Envelope(envelope), received_at));
|
||||
}
|
||||
WebSocketMessage::Ping(_) => return Ok((Message::Ping, received_at)),
|
||||
WebSocketMessage::Pong(_) => return Ok((Message::Pong, received_at)),
|
||||
WebSocketMessage::Close(_) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
anyhow::bail!("connection closed");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_buffer_size() {
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
let mut sink = MessageStream::new(tx.sink_map_err(|_| anyhow::anyhow!("")));
|
||||
sink.write(Message::Envelope(Envelope {
|
||||
payload: Some(envelope::Payload::UpdateWorktree(UpdateWorktree {
|
||||
root_name: "abcdefg".repeat(10),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(sink.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
|
||||
sink.write(Message::Envelope(Envelope {
|
||||
payload: Some(envelope::Payload::UpdateWorktree(UpdateWorktree {
|
||||
root_name: "abcdefg".repeat(1000000),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(sink.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
|
||||
|
||||
let mut stream = MessageStream::new(rx.map(anyhow::Ok));
|
||||
stream.read().await.unwrap();
|
||||
assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
|
||||
stream.read().await.unwrap();
|
||||
assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
|
||||
}
|
||||
}
|
||||
99
crates/rpc/src/notification.rs
Normal file
99
crates/rpc/src/notification.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use crate::proto;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, map};
|
||||
use strum::VariantNames;
|
||||
|
||||
const KIND: &str = "kind";
|
||||
const ENTITY_ID: &str = "entity_id";
|
||||
|
||||
/// A notification that can be stored, associated with a given recipient.
|
||||
///
|
||||
/// This struct is stored in the collab database as JSON, so it shouldn't be
|
||||
/// changed in a backward-incompatible way. For example, when renaming a
|
||||
/// variant, add a serde alias for the old name.
|
||||
///
|
||||
/// Most notification types have a special field which is aliased to
|
||||
/// `entity_id`. This field is stored in its own database column, and can
|
||||
/// be used to query the notification.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, VariantNames, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum Notification {
|
||||
ContactRequest {
|
||||
#[serde(rename = "entity_id")]
|
||||
sender_id: u64,
|
||||
},
|
||||
ContactRequestAccepted {
|
||||
#[serde(rename = "entity_id")]
|
||||
responder_id: u64,
|
||||
},
|
||||
ChannelInvitation {
|
||||
#[serde(rename = "entity_id")]
|
||||
channel_id: u64,
|
||||
channel_name: String,
|
||||
inviter_id: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl Notification {
|
||||
pub fn to_proto(&self) -> proto::Notification {
|
||||
let mut value = serde_json::to_value(self).unwrap();
|
||||
let mut entity_id = None;
|
||||
let value = value.as_object_mut().unwrap();
|
||||
let Some(Value::String(kind)) = value.remove(KIND) else {
|
||||
unreachable!("kind is the enum tag")
|
||||
};
|
||||
if let map::Entry::Occupied(e) = value.entry(ENTITY_ID)
|
||||
&& e.get().is_u64()
|
||||
{
|
||||
entity_id = e.remove().as_u64();
|
||||
}
|
||||
proto::Notification {
|
||||
kind,
|
||||
entity_id,
|
||||
content: serde_json::to_string(&value).unwrap(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_proto(notification: &proto::Notification) -> Option<Self> {
|
||||
let mut value = serde_json::from_str::<Value>(¬ification.content).ok()?;
|
||||
let object = value.as_object_mut()?;
|
||||
object.insert(KIND.into(), notification.kind.to_string().into());
|
||||
if let Some(entity_id) = notification.entity_id {
|
||||
object.insert(ENTITY_ID.into(), entity_id.into());
|
||||
}
|
||||
serde_json::from_value(value).ok()
|
||||
}
|
||||
|
||||
pub fn all_variant_names() -> &'static [&'static str] {
|
||||
Self::VARIANTS
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Notification;
|
||||
|
||||
#[test]
|
||||
fn test_notification() {
|
||||
// Notifications can be serialized and deserialized.
|
||||
for notification in [
|
||||
Notification::ContactRequest { sender_id: 1 },
|
||||
Notification::ContactRequestAccepted { responder_id: 2 },
|
||||
Notification::ChannelInvitation {
|
||||
channel_id: 100,
|
||||
channel_name: "the-channel".into(),
|
||||
inviter_id: 50,
|
||||
},
|
||||
] {
|
||||
let message = notification.to_proto();
|
||||
let deserialized = Notification::from_proto(&message).unwrap();
|
||||
assert_eq!(deserialized, notification);
|
||||
}
|
||||
|
||||
// When notifications are serialized, the `kind` and `actor_id` fields are
|
||||
// stored separately, and do not appear redundantly in the JSON.
|
||||
let notification = Notification::ContactRequest { sender_id: 1 };
|
||||
assert_eq!(notification.to_proto().content, "{}");
|
||||
}
|
||||
}
|
||||
1371
crates/rpc/src/peer.rs
Normal file
1371
crates/rpc/src/peer.rs
Normal file
File diff suppressed because it is too large
Load Diff
683
crates/rpc/src/proto_client.rs
Normal file
683
crates/rpc/src/proto_client.rs
Normal file
@@ -0,0 +1,683 @@
|
||||
use anyhow::{Context, Result};
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
Future, FutureExt as _, Stream, StreamExt as _,
|
||||
channel::oneshot,
|
||||
future::{BoxFuture, LocalBoxFuture},
|
||||
stream::BoxStream,
|
||||
};
|
||||
use gpui::{AnyEntity, AnyWeakEntity, AsyncApp, BackgroundExecutor, Entity, FutureExt as _};
|
||||
use parking_lot::Mutex;
|
||||
use proto::{
|
||||
AnyTypedEnvelope, EntityMessage, Envelope, EnvelopedMessage, LspRequestId, LspRequestMessage,
|
||||
RequestMessage, TypedEnvelope, error::ErrorExt as _,
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
sync::{
|
||||
Arc, OnceLock,
|
||||
atomic::{self, AtomicU64},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnyProtoClient(Arc<State>);
|
||||
|
||||
type RequestIds = Arc<
|
||||
Mutex<
|
||||
HashMap<
|
||||
LspRequestId,
|
||||
oneshot::Sender<
|
||||
Result<
|
||||
Option<TypedEnvelope<Vec<proto::ProtoLspResponse<Box<dyn AnyTypedEnvelope>>>>>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
|
||||
static NEXT_LSP_REQUEST_ID: OnceLock<Arc<AtomicU64>> = OnceLock::new();
|
||||
static REQUEST_IDS: OnceLock<RequestIds> = OnceLock::new();
|
||||
|
||||
struct State {
|
||||
client: Arc<dyn ProtoClient>,
|
||||
next_lsp_request_id: Arc<AtomicU64>,
|
||||
request_ids: RequestIds,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for State {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("State")
|
||||
.field("next_lsp_request_id", &self.next_lsp_request_id)
|
||||
.field("request_ids", &self.request_ids)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ProtoClient: Send + Sync {
|
||||
fn request(
|
||||
&self,
|
||||
envelope: Envelope,
|
||||
request_type: &'static str,
|
||||
) -> BoxFuture<'static, Result<Envelope>>;
|
||||
|
||||
fn request_stream(
|
||||
&self,
|
||||
envelope: Envelope,
|
||||
request_type: &'static str,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<Envelope>>>> {
|
||||
async move {
|
||||
anyhow::bail!(
|
||||
"stream requests are not supported for {request_type}: {:?}",
|
||||
envelope.payload
|
||||
)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn send(&self, envelope: Envelope, message_type: &'static str) -> Result<()>;
|
||||
|
||||
fn send_response(&self, envelope: Envelope, message_type: &'static str) -> Result<()>;
|
||||
|
||||
fn message_handler_set(&self) -> &parking_lot::Mutex<ProtoMessageHandlerSet>;
|
||||
|
||||
fn is_via_collab(&self) -> bool;
|
||||
fn has_wsl_interop(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProtoMessageHandlerSet {
|
||||
pub entity_types_by_message_type: HashMap<TypeId, TypeId>,
|
||||
pub entities_by_type_and_remote_id: HashMap<(TypeId, u64), EntityMessageSubscriber>,
|
||||
pub entity_id_extractors: HashMap<TypeId, fn(&dyn AnyTypedEnvelope) -> u64>,
|
||||
pub entities_by_message_type: HashMap<TypeId, AnyWeakEntity>,
|
||||
pub message_handlers: HashMap<TypeId, ProtoMessageHandler>,
|
||||
}
|
||||
|
||||
pub type ProtoMessageHandler = Arc<
|
||||
dyn Send
|
||||
+ Sync
|
||||
+ Fn(
|
||||
AnyEntity,
|
||||
Box<dyn AnyTypedEnvelope>,
|
||||
AnyProtoClient,
|
||||
AsyncApp,
|
||||
) -> LocalBoxFuture<'static, Result<()>>,
|
||||
>;
|
||||
|
||||
impl ProtoMessageHandlerSet {
|
||||
pub fn clear(&mut self) {
|
||||
self.message_handlers.clear();
|
||||
self.entities_by_message_type.clear();
|
||||
self.entities_by_type_and_remote_id.clear();
|
||||
self.entity_id_extractors.clear();
|
||||
}
|
||||
|
||||
fn add_message_handler(
|
||||
&mut self,
|
||||
message_type_id: TypeId,
|
||||
entity: gpui::AnyWeakEntity,
|
||||
handler: ProtoMessageHandler,
|
||||
) {
|
||||
self.entities_by_message_type
|
||||
.insert(message_type_id, entity);
|
||||
let prev_handler = self.message_handlers.insert(message_type_id, handler);
|
||||
if prev_handler.is_some() {
|
||||
panic!("registered handler for the same message twice");
|
||||
}
|
||||
}
|
||||
|
||||
fn add_entity_message_handler(
|
||||
&mut self,
|
||||
message_type_id: TypeId,
|
||||
entity_type_id: TypeId,
|
||||
entity_id_extractor: fn(&dyn AnyTypedEnvelope) -> u64,
|
||||
handler: ProtoMessageHandler,
|
||||
) {
|
||||
self.entity_id_extractors
|
||||
.entry(message_type_id)
|
||||
.or_insert(entity_id_extractor);
|
||||
self.entity_types_by_message_type
|
||||
.insert(message_type_id, entity_type_id);
|
||||
let prev_handler = self.message_handlers.insert(message_type_id, handler);
|
||||
if prev_handler.is_some() {
|
||||
panic!("registered handler for the same message twice");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_message(
|
||||
this: &parking_lot::Mutex<Self>,
|
||||
message: Box<dyn AnyTypedEnvelope>,
|
||||
client: AnyProtoClient,
|
||||
cx: AsyncApp,
|
||||
) -> Option<LocalBoxFuture<'static, Result<()>>> {
|
||||
let payload_type_id = message.payload_type_id();
|
||||
let mut this = this.lock();
|
||||
let handler = this.message_handlers.get(&payload_type_id)?.clone();
|
||||
let entity = if let Some(entity) = this.entities_by_message_type.get(&payload_type_id) {
|
||||
entity.upgrade()?
|
||||
} else {
|
||||
let extract_entity_id = *this.entity_id_extractors.get(&payload_type_id)?;
|
||||
let entity_type_id = *this.entity_types_by_message_type.get(&payload_type_id)?;
|
||||
let entity_id = (extract_entity_id)(message.as_ref());
|
||||
match this
|
||||
.entities_by_type_and_remote_id
|
||||
.get_mut(&(entity_type_id, entity_id))?
|
||||
{
|
||||
EntityMessageSubscriber::Pending(pending) => {
|
||||
pending.push(message);
|
||||
return None;
|
||||
}
|
||||
EntityMessageSubscriber::Entity { handle } => handle.upgrade()?,
|
||||
}
|
||||
};
|
||||
drop(this);
|
||||
Some(handler(entity, message, client, cx))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum EntityMessageSubscriber {
|
||||
Entity { handle: AnyWeakEntity },
|
||||
Pending(Vec<Box<dyn AnyTypedEnvelope>>),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EntityMessageSubscriber {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EntityMessageSubscriber::Entity { handle } => f
|
||||
.debug_struct("EntityMessageSubscriber::Entity")
|
||||
.field("handle", handle)
|
||||
.finish(),
|
||||
EntityMessageSubscriber::Pending(vec) => f
|
||||
.debug_struct("EntityMessageSubscriber::Pending")
|
||||
.field(
|
||||
"envelopes",
|
||||
&vec.iter()
|
||||
.map(|envelope| envelope.payload_type_name())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Arc<T>> for AnyProtoClient
|
||||
where
|
||||
T: ProtoClient + 'static,
|
||||
{
|
||||
fn from(client: Arc<T>) -> Self {
|
||||
Self::new(client)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyProtoClient {
|
||||
pub fn new<T: ProtoClient + 'static>(client: Arc<T>) -> Self {
|
||||
Self(Arc::new(State {
|
||||
client,
|
||||
next_lsp_request_id: NEXT_LSP_REQUEST_ID
|
||||
.get_or_init(|| Arc::new(AtomicU64::new(0)))
|
||||
.clone(),
|
||||
request_ids: REQUEST_IDS.get_or_init(RequestIds::default).clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn is_via_collab(&self) -> bool {
|
||||
self.0.client.is_via_collab()
|
||||
}
|
||||
|
||||
pub fn request<T: RequestMessage>(
|
||||
&self,
|
||||
request: T,
|
||||
) -> impl Future<Output = Result<T::Response>> + use<T> {
|
||||
let envelope = request.into_envelope(0, None, None);
|
||||
let response = self.0.client.request(envelope, T::NAME);
|
||||
async move {
|
||||
T::Response::from_envelope(response.await?)
|
||||
.context("received response of the wrong type")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_stream<T: RequestMessage>(
|
||||
&self,
|
||||
request: T,
|
||||
) -> impl Future<Output = Result<BoxStream<'static, Result<T::Response>>>> + use<T> {
|
||||
let envelope = request.into_envelope(0, None, None);
|
||||
let response_stream = self.0.client.request_stream(envelope, T::NAME);
|
||||
async move {
|
||||
Ok(response_stream
|
||||
.await?
|
||||
.map(|response| {
|
||||
T::Response::from_envelope(response?)
|
||||
.context("received response of the wrong type")
|
||||
})
|
||||
.boxed())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send<T: EnvelopedMessage>(&self, request: T) -> Result<()> {
|
||||
let envelope = request.into_envelope(0, None, None);
|
||||
self.0.client.send(envelope, T::NAME)
|
||||
}
|
||||
|
||||
pub fn send_response<T: EnvelopedMessage>(&self, request_id: u32, request: T) -> Result<()> {
|
||||
let envelope = request.into_envelope(0, Some(request_id), None);
|
||||
self.0.client.send(envelope, T::NAME)
|
||||
}
|
||||
|
||||
pub fn request_lsp<T>(
|
||||
&self,
|
||||
project_id: u64,
|
||||
server_id: Option<u64>,
|
||||
timeout: Duration,
|
||||
executor: BackgroundExecutor,
|
||||
request: T,
|
||||
) -> impl Future<
|
||||
Output = Result<Option<TypedEnvelope<Vec<proto::ProtoLspResponse<T::Response>>>>>,
|
||||
> + use<T>
|
||||
where
|
||||
T: LspRequestMessage,
|
||||
{
|
||||
let new_id = LspRequestId(
|
||||
self.0
|
||||
.next_lsp_request_id
|
||||
.fetch_add(1, atomic::Ordering::Acquire),
|
||||
);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
{
|
||||
self.0.request_ids.lock().insert(new_id, tx);
|
||||
}
|
||||
|
||||
let query = proto::LspQuery {
|
||||
project_id,
|
||||
server_id,
|
||||
lsp_request_id: new_id.0,
|
||||
request: Some(request.to_proto_query()),
|
||||
};
|
||||
let request = self.request(query);
|
||||
let request_ids = self.0.request_ids.clone();
|
||||
async move {
|
||||
match request.await {
|
||||
Ok(_request_enqueued) => {}
|
||||
Err(e) => {
|
||||
request_ids.lock().remove(&new_id);
|
||||
return Err(e).context("sending LSP proto request");
|
||||
}
|
||||
}
|
||||
|
||||
let response = rx.with_timeout(timeout, &executor).await;
|
||||
{
|
||||
request_ids.lock().remove(&new_id);
|
||||
}
|
||||
match response {
|
||||
Ok(Ok(response)) => {
|
||||
let response = response
|
||||
.context("waiting for LSP proto response")?
|
||||
.map(|response| {
|
||||
anyhow::Ok(TypedEnvelope {
|
||||
payload: response
|
||||
.payload
|
||||
.into_iter()
|
||||
.map(|lsp_response| lsp_response.into_response::<T>())
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
sender_id: response.sender_id,
|
||||
original_sender_id: response.original_sender_id,
|
||||
message_id: response.message_id,
|
||||
received_at: response.received_at,
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
.context("converting LSP proto response")?;
|
||||
Ok(response)
|
||||
}
|
||||
Err(_cancelled_due_timeout) => Ok(None),
|
||||
Ok(Err(_channel_dropped)) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_lsp_response<T: LspRequestMessage>(
|
||||
&self,
|
||||
project_id: u64,
|
||||
lsp_request_id: LspRequestId,
|
||||
server_responses: HashMap<u64, T::Response>,
|
||||
) -> Result<()> {
|
||||
self.send(proto::LspQueryResponse {
|
||||
project_id,
|
||||
lsp_request_id: lsp_request_id.0,
|
||||
responses: server_responses
|
||||
.into_iter()
|
||||
.map(|(server_id, response)| proto::LspResponse {
|
||||
server_id,
|
||||
response: Some(T::response_to_proto_query(response)),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_lsp_response(&self, mut envelope: TypedEnvelope<proto::LspQueryResponse>) {
|
||||
let request_id = LspRequestId(envelope.payload.lsp_request_id);
|
||||
let mut response_senders = self.0.request_ids.lock();
|
||||
if let Some(tx) = response_senders.remove(&request_id) {
|
||||
let responses = envelope.payload.responses.drain(..).collect::<Vec<_>>();
|
||||
tx.send(Ok(Some(proto::TypedEnvelope {
|
||||
sender_id: envelope.sender_id,
|
||||
original_sender_id: envelope.original_sender_id,
|
||||
message_id: envelope.message_id,
|
||||
received_at: envelope.received_at,
|
||||
payload: responses
|
||||
.into_iter()
|
||||
.filter_map(|response| {
|
||||
use proto::lsp_response::Response;
|
||||
|
||||
let server_id = response.server_id;
|
||||
let response = match response.response? {
|
||||
Response::GetReferencesResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDocumentColorResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetHoverResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetCodeActionsResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetSignatureHelpResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetCodeLensResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDocumentDiagnosticsResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDeclarationResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetTypeDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetImplementationResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::InlayHintsResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::SemanticTokensResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetFoldingRangesResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDocumentSymbolsResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
};
|
||||
Some(proto::ProtoLspResponse {
|
||||
server_id,
|
||||
response,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
})))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_request_handler<M, E, H, F>(&self, entity: gpui::WeakEntity<E>, handler: H)
|
||||
where
|
||||
M: RequestMessage,
|
||||
E: 'static,
|
||||
H: 'static + Sync + Fn(Entity<E>, TypedEnvelope<M>, AsyncApp) -> F + Send + Sync,
|
||||
F: 'static + Future<Output = Result<M::Response>>,
|
||||
{
|
||||
self.0
|
||||
.client
|
||||
.message_handler_set()
|
||||
.lock()
|
||||
.add_message_handler(
|
||||
TypeId::of::<M>(),
|
||||
entity.into(),
|
||||
Arc::new(move |entity, envelope, client, cx| {
|
||||
let entity = entity.downcast::<E>().unwrap();
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
let request_id = envelope.message_id();
|
||||
handler(entity, *envelope, cx)
|
||||
.then(move |result| async move {
|
||||
match result {
|
||||
Ok(response) => {
|
||||
client.send_response(request_id, response)?;
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
client.send_response(request_id, error.to_proto())?;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
.boxed_local()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_entity_request_handler<M, E, H, F>(&self, handler: H)
|
||||
where
|
||||
M: EnvelopedMessage + RequestMessage + EntityMessage,
|
||||
E: 'static,
|
||||
H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
|
||||
F: 'static + Future<Output = Result<M::Response>>,
|
||||
{
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
let entity_type_id = TypeId::of::<E>();
|
||||
let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
|
||||
(envelope as &dyn Any)
|
||||
.downcast_ref::<TypedEnvelope<M>>()
|
||||
.unwrap()
|
||||
.payload
|
||||
.remote_entity_id()
|
||||
};
|
||||
self.0
|
||||
.client
|
||||
.message_handler_set()
|
||||
.lock()
|
||||
.add_entity_message_handler(
|
||||
message_type_id,
|
||||
entity_type_id,
|
||||
entity_id_extractor,
|
||||
Arc::new(move |entity, envelope, client, cx| {
|
||||
let entity = entity.downcast::<E>().unwrap();
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
let request_id = envelope.message_id();
|
||||
handler(entity, *envelope, cx)
|
||||
.then(move |result| async move {
|
||||
match result {
|
||||
Ok(response) => {
|
||||
client.send_response(request_id, response)?;
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
client.send_response(request_id, error.to_proto())?;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
.boxed_local()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_entity_stream_request_handler<M, E, H, F, S>(&self, handler: H)
|
||||
where
|
||||
M: EnvelopedMessage + RequestMessage + EntityMessage,
|
||||
E: 'static,
|
||||
H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
|
||||
F: 'static + Future<Output = Result<S>>,
|
||||
S: 'static + Stream<Item = Result<M::Response>>,
|
||||
{
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
let entity_type_id = TypeId::of::<E>();
|
||||
let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
|
||||
(envelope as &dyn Any)
|
||||
.downcast_ref::<TypedEnvelope<M>>()
|
||||
.unwrap()
|
||||
.payload
|
||||
.remote_entity_id()
|
||||
};
|
||||
self.0
|
||||
.client
|
||||
.message_handler_set()
|
||||
.lock()
|
||||
.add_entity_message_handler(
|
||||
message_type_id,
|
||||
entity_type_id,
|
||||
entity_id_extractor,
|
||||
Arc::new(move |entity, envelope, client, cx| {
|
||||
let entity = entity.downcast::<E>().unwrap();
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
let request_id = envelope.message_id();
|
||||
let stream = handler(entity, *envelope, cx);
|
||||
async move {
|
||||
// An Error response is itself a terminal stream frame on
|
||||
// both transports (Peer and ChannelClient), so we don't
|
||||
// need to follow it with an EndStream.
|
||||
match stream.await {
|
||||
Ok(stream) => {
|
||||
futures::pin_mut!(stream);
|
||||
while let Some(result) = stream.next().await {
|
||||
match result {
|
||||
Ok(response) => {
|
||||
client.send_response(request_id, response)?
|
||||
}
|
||||
Err(error) => {
|
||||
client.send_response(request_id, error.to_proto())?;
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
client.send_response(request_id, proto::EndStream {})?;
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
client.send_response(request_id, error.to_proto())?;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed_local()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_entity_message_handler<M, E, H, F>(&self, handler: H)
|
||||
where
|
||||
M: EnvelopedMessage + EntityMessage,
|
||||
E: 'static,
|
||||
H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
|
||||
F: 'static + Future<Output = Result<()>>,
|
||||
{
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
let entity_type_id = TypeId::of::<E>();
|
||||
let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
|
||||
(envelope as &dyn Any)
|
||||
.downcast_ref::<TypedEnvelope<M>>()
|
||||
.unwrap()
|
||||
.payload
|
||||
.remote_entity_id()
|
||||
};
|
||||
self.0
|
||||
.client
|
||||
.message_handler_set()
|
||||
.lock()
|
||||
.add_entity_message_handler(
|
||||
message_type_id,
|
||||
entity_type_id,
|
||||
entity_id_extractor,
|
||||
Arc::new(move |entity, envelope, _, cx| {
|
||||
let entity = entity.downcast::<E>().unwrap();
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
handler(entity, *envelope, cx).boxed_local()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn subscribe_to_entity<E: 'static>(&self, remote_id: u64, entity: &Entity<E>) {
|
||||
let id = (TypeId::of::<E>(), remote_id);
|
||||
|
||||
let mut message_handlers = self.0.client.message_handler_set().lock();
|
||||
if message_handlers
|
||||
.entities_by_type_and_remote_id
|
||||
.contains_key(&id)
|
||||
{
|
||||
panic!("already subscribed to entity");
|
||||
}
|
||||
|
||||
message_handlers.entities_by_type_and_remote_id.insert(
|
||||
id,
|
||||
EntityMessageSubscriber::Entity {
|
||||
handle: entity.downgrade().into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn has_wsl_interop(&self) -> bool {
|
||||
self.0.client.has_wsl_interop()
|
||||
}
|
||||
}
|
||||
|
||||
fn to_any_envelope<T: EnvelopedMessage>(
|
||||
envelope: &TypedEnvelope<proto::LspQueryResponse>,
|
||||
response: T,
|
||||
) -> Box<dyn AnyTypedEnvelope> {
|
||||
Box::new(proto::TypedEnvelope {
|
||||
sender_id: envelope.sender_id,
|
||||
original_sender_id: envelope.original_sender_id,
|
||||
message_id: envelope.message_id,
|
||||
received_at: envelope.received_at,
|
||||
payload: response,
|
||||
}) as Box<_>
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct NoopProtoClient {
|
||||
handler_set: parking_lot::Mutex<ProtoMessageHandlerSet>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl NoopProtoClient {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
handler_set: parking_lot::Mutex::new(ProtoMessageHandlerSet::default()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl ProtoClient for NoopProtoClient {
|
||||
fn request(
|
||||
&self,
|
||||
_: proto::Envelope,
|
||||
_: &'static str,
|
||||
) -> futures::future::BoxFuture<'static, Result<proto::Envelope>> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn send(&self, _: proto::Envelope, _: &'static str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn send_response(&self, _: proto::Envelope, _: &'static str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn message_handler_set(&self) -> &parking_lot::Mutex<ProtoMessageHandlerSet> {
|
||||
&self.handler_set
|
||||
}
|
||||
fn is_via_collab(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn has_wsl_interop(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
19
crates/rpc/src/rpc.rs
Normal file
19
crates/rpc/src/rpc.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
pub mod auth;
|
||||
mod conn;
|
||||
mod message_stream;
|
||||
mod notification;
|
||||
mod peer;
|
||||
|
||||
pub use conn::Connection;
|
||||
pub use notification::*;
|
||||
pub use peer::*;
|
||||
pub use proto;
|
||||
pub use proto::{Receipt, TypedEnvelope, error::*};
|
||||
mod macros;
|
||||
|
||||
#[cfg(feature = "gpui")]
|
||||
mod proto_client;
|
||||
#[cfg(feature = "gpui")]
|
||||
pub use proto_client::*;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 68;
|
||||
Reference in New Issue
Block a user