//! Basic messages of the Orchid extension API. //! //! The protocol is defined over a byte stream, normally the stdin/stdout of the //! extension. The implementations of [Coding] in this library are considered //! normative. Any breaking change here or in the default implementations of //! [Coding] must also increment the version number in the intro strings. //! //! 3 different kinds of messages are recognized; request, response, and //! notification. There are no general ordering guarantees about these, multiple //! requests, even requests of the same type may be sent concurrently, unless //! otherwise specified in the request's definition. //! //! Each message begins with a u32 length, followed by that many bytes of //! message content. The first byte of the content is a u64 combined request ID //! and discriminator, D. //! //! - If D = 0, the rest of the content is a notification. //! - If 0 < D < 2^63, it is a request with identifier D. //! - If 2^63 <= D, it is a response to request identifier !D. //! //! The order of both notifications and requests sent from the same thread must //! be preserved. Toolkits must ensure that the client code is able to observe //! the ordering of messages. use std::io; use std::pin::Pin; use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact}; use crate::{Sweeped, atom, expr, interner, lexer, logging, parser, system, tree}; static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n"; #[derive(Clone, Debug)] pub struct HostHeader { pub log_strategy: logging::LogStrategy, pub msg_logs: logging::LogStrategy, } impl Decode for HostHeader { async fn decode(mut read: Pin<&mut R>) -> io::Result { read_exact(read.as_mut(), HOST_INTRO).await?; Ok(Self { log_strategy: logging::LogStrategy::decode(read.as_mut()).await?, msg_logs: logging::LogStrategy::decode(read.as_mut()).await?, }) } } impl Encode for HostHeader { async fn encode(&self, mut write: Pin<&mut W>) -> io::Result<()> { write.write_all(HOST_INTRO).await?; self.log_strategy.encode(write.as_mut()).await?; self.msg_logs.encode(write.as_mut()).await } } static EXT_INTRO: &[u8] = b"Orchid extension, binary API v0\n"; #[derive(Clone, Debug)] pub struct ExtensionHeader { pub name: String, pub systems: Vec, } impl Decode for ExtensionHeader { async fn decode(mut read: Pin<&mut R>) -> io::Result { read_exact(read.as_mut(), EXT_INTRO).await?; Ok(Self { name: String::decode(read.as_mut()).await?, systems: Vec::decode(read).await? }) } } impl Encode for ExtensionHeader { async fn encode(&self, mut write: Pin<&mut W>) -> io::Result<()> { write.write_all(EXT_INTRO).await?; self.name.encode(write.as_mut()).await?; self.systems.encode(write).await } } #[derive(Clone, Debug, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] pub struct Ping; impl Request for Ping { type Response = (); } /// Requests running from the extension to the host #[derive(Clone, Debug, Coding, Hierarchy)] #[extendable] pub enum ExtHostReq { Ping(Ping), IntReq(interner::IntReq), Fwd(atom::Fwd), ExtAtomPrint(atom::ExtAtomPrint), SysFwd(system::SysFwd), ExprReq(expr::ExprReq), SubLex(lexer::SubLex), LsModule(tree::LsModule), ResolveNames(parser::ResolveNames), } /// Notifications sent from the extension to the host #[allow(clippy::enum_variant_names)] #[derive(Debug, Clone, Coding, Hierarchy)] #[extendable] pub enum ExtHostNotif { ExprNotif(expr::ExprNotif), Log(logging::Log), Sweeped(Sweeped), } pub struct ExtHostChannel; impl Channel for ExtHostChannel { type Notif = ExtHostNotif; type Req = ExtHostReq; } /// Requests running from the host to the extension #[derive(Clone, Debug, Coding, Hierarchy)] #[extendable] pub enum HostExtReq { Ping(Ping), SysReq(system::SysReq), Sweep(interner::Sweep), AtomReq(atom::AtomReq), DeserAtom(atom::DeserAtom), LexExpr(lexer::LexExpr), ParseLine(parser::ParseLine), FetchParsedConst(parser::FetchParsedConst), GetMember(tree::GetMember), SystemDrop(system::SystemDrop), AtomDrop(atom::AtomDrop), } /// Notifications sent from the host to the extension #[derive(Clone, Debug, Coding, Hierarchy)] #[extendable] pub enum HostExtNotif { /// The host can assume that after this notif is sent, a correctly written /// extension will eventually exit. Exit, } pub struct HostExtChannel; impl Channel for HostExtChannel { type Notif = HostExtNotif; type Req = HostExtReq; } /// Message set viewed from the extension's perspective pub struct ExtMsgSet; impl MsgSet for ExtMsgSet { type In = HostExtChannel; type Out = ExtHostChannel; } /// Message Set viewed from the host's perspective pub struct HostMsgSet; impl MsgSet for HostMsgSet { type In = ExtHostChannel; type Out = HostExtChannel; } #[cfg(test)] mod tests { use orchid_api_traits::enc_vec; use ordered_float::NotNan; use test_executors::spin_on; use super::*; #[test] fn host_header_enc() { spin_on(async { let hh = HostHeader { log_strategy: logging::LogStrategy::File("SomeFile".to_string()), msg_logs: logging::LogStrategy::File("SomeFile".to_string()), }; let mut enc = &enc_vec(&hh)[..]; eprintln!("Encoded to {enc:?}"); HostHeader::decode(Pin::new(&mut enc)).await.unwrap(); assert_eq!(enc, []); }) } #[test] fn ext_header_enc() { spin_on(async { let eh = ExtensionHeader { name: "my_extension".to_string(), systems: vec![system::SystemDecl { id: system::SysDeclId(1.try_into().unwrap()), name: "misc".to_string(), depends: vec!["std".to_string()], priority: NotNan::new(1f64).unwrap(), }], }; let mut enc = &enc_vec(&eh)[..]; eprintln!("Encoded to {enc:?}"); ExtensionHeader::decode(Pin::new(&mut enc)).await.unwrap(); assert_eq!(enc, []) }) } }