Skip to main content

workflow_nw/ipc/
messages.rs

1use crate::ipc::error::Error;
2use crate::ipc::imports::*;
3use borsh::{BorshDeserialize, BorshSerialize};
4use js_sys::{ArrayBuffer, Uint8Array};
5use std::fmt::Debug;
6
7/// Serializes the given header and appends the payload bytes, returning a
8/// JavaScript [`ArrayBuffer`] suitable for transmission over the IPC channel.
9pub fn to_msg<Ops, Id>(header: BorshHeader<Id>, payload: &[u8]) -> Result<ArrayBuffer>
10where
11    Id: IdT,
12    Ops: BorshSerialize + BorshDeserialize,
13{
14    let header = borsh::to_vec(&header).expect("to_msg header serialize error");
15    // log_info!("header: {:?}", header);
16    // log_info!("payload: {:?}", payload);
17    let header_len = header.len();
18    let len = payload.len() + header_len;
19    let mut buffer = Vec::with_capacity(len);
20    #[allow(clippy::uninit_vec)]
21    unsafe {
22        buffer.set_len(len);
23    }
24    buffer[0..header_len].copy_from_slice(&header);
25    buffer[header_len..].copy_from_slice(payload);
26    // log_info!("to_msg buffer: {:?}", buffer);
27
28    let array = Uint8Array::from(&buffer[..]);
29    Ok(array.buffer())
30}
31
32#[derive(Debug, Clone, Copy, BorshSerialize, BorshDeserialize)]
33#[repr(u8)]
34#[borsh(use_discriminant = true)]
35/// Discriminates the three kinds of IPC messages exchanged between peers.
36pub enum MessageKind {
37    /// A one-way message that expects no response.
38    Notification = 0,
39    /// A message expecting a matching [`Response`](Self::Response).
40    Request = 1,
41    /// A reply to a previously received [`Request`](Self::Request).
42    Response = 2,
43}
44
45impl From<MessageKind> for u32 {
46    fn from(kind: MessageKind) -> u32 {
47        kind as u32
48    }
49}
50
51#[derive(Debug, BorshSerialize, BorshDeserialize)]
52/// Header prefixed to every IPC message, encoding the message kind, an
53/// optional correlation id, and the Borsh-serialized operation discriminator.
54pub struct BorshHeader<Id = Id64>
55where
56    Id: BorshSerialize + BorshDeserialize,
57{
58    /// Whether this message is a notification, request or response.
59    pub kind: MessageKind,
60    /// Correlation id linking a request to its response (absent for notifications).
61    pub id: Option<Id>,
62    /// Borsh-serialized bytes of the operation identifier.
63    pub op: Vec<u8>,
64}
65
66impl<Id> BorshHeader<Id>
67where
68    Id: BorshSerialize + BorshDeserialize,
69{
70    /// Constructs a header for a request message carrying the given operation
71    /// and an optional id used to correlate the eventual response.
72    pub fn request<Ops>(id: Option<Id>, op: Ops) -> Self
73    where
74        Ops: OpsT,
75    {
76        BorshHeader {
77            id,
78            op: borsh::to_vec(&op).expect("request op serialize error"),
79            kind: MessageKind::Request,
80        }
81    }
82
83    /// Constructs a header for a response message carrying the given operation
84    /// and the id of the request it answers.
85    pub fn response<Ops>(id: Option<Id>, op: Ops) -> Self
86    where
87        Ops: OpsT,
88    {
89        BorshHeader {
90            id,
91            op: borsh::to_vec(&op).expect("response op serialize error"),
92            kind: MessageKind::Response,
93        }
94    }
95
96    /// Constructs a header for a notification message carrying the given
97    /// operation (notifications have no id since no response is expected).
98    pub fn notification<Ops>(op: Ops) -> Self
99    where
100        Ops: OpsT,
101    {
102        BorshHeader {
103            id: None,
104            op: borsh::to_vec(&op).expect("notification op serialize error"),
105            kind: MessageKind::Notification,
106        }
107    }
108}
109
110#[derive(Debug)]
111/// A decoded IPC message, pairing its [`BorshHeader`] with a borrowed slice of
112/// the raw payload bytes that follow the header.
113pub struct BorshMessage<'data, Id = Id64>
114where
115    Id: BorshSerialize + BorshDeserialize + 'data,
116{
117    /// Decoded message header describing the kind, id and operation.
118    pub header: BorshHeader<Id>,
119    /// Raw payload bytes that follow the header in the encoded message.
120    pub payload: &'data [u8],
121}
122
123impl<'data, Id> TryFrom<&'data Vec<u8>> for BorshMessage<'data, Id>
124where
125    Id: Debug + BorshSerialize + BorshDeserialize + 'data,
126{
127    type Error = Error;
128
129    fn try_from(src: &'data Vec<u8>) -> std::result::Result<Self, Self::Error> {
130        let v: BorshMessage<Id> = src[..].try_into()?;
131        Ok(v)
132    }
133}
134
135impl<'data, Id> TryFrom<&'data [u8]> for BorshMessage<'data, Id>
136where
137    Id: Debug + BorshSerialize + BorshDeserialize + 'data,
138{
139    type Error = Error;
140
141    fn try_from(src: &'data [u8]) -> std::result::Result<Self, Self::Error> {
142        let mut payload = src;
143        let header = BorshHeader::<Id>::deserialize(&mut payload)?;
144        let message = BorshMessage { header, payload };
145        Ok(message)
146    }
147}