manul/protocol/
message.rs

1use alloc::string::{String, ToString};
2
3use digest::Digest;
4use serde::{Deserialize, Serialize};
5
6use super::{
7    errors::{DirectMessageError, EchoBroadcastError, LocalError, MessageValidationError, NormalBroadcastError},
8    BoxedFormat,
9};
10
11mod private {
12    use alloc::boxed::Box;
13    use serde::{Deserialize, Serialize};
14    use serde_encoded_bytes::{Base64, SliceLike};
15
16    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17    pub struct MessagePayload(#[serde(with = "SliceLike::<Base64>")] pub Box<[u8]>);
18
19    impl AsRef<[u8]> for MessagePayload {
20        fn as_ref(&self) -> &[u8] {
21            &self.0
22        }
23    }
24
25    pub trait ProtocolMessageWrapper: Sized {
26        fn new_inner(maybe_message: Option<MessagePayload>) -> Self;
27        fn maybe_message(&self) -> &Option<MessagePayload>;
28    }
29}
30
31use private::{MessagePayload, ProtocolMessageWrapper};
32
33/// A serialized part of the protocol message.
34///
35/// These would usually be generated separately by the round, but delivered together to
36/// [`Round::receive_message`](`crate::protocol::Round::receive_message`).
37pub trait ProtocolMessagePart: ProtocolMessageWrapper {
38    /// The error specific to deserializing this message.
39    ///
40    /// Used to distinguish which deserialization failed in
41    /// [`Round::receive_message`](`crate::protocol::Round::receive_message`)
42    /// and store the corresponding message in the evidence.
43    type Error: From<String>;
44
45    /// Creates an empty message.
46    ///
47    /// Use in case the round does not send a message of this type.
48    fn none() -> Self {
49        Self::new_inner(None)
50    }
51
52    /// Creates a new serialized message.
53    fn new<T>(format: &BoxedFormat, message: T) -> Result<Self, LocalError>
54    where
55        T: 'static + Serialize,
56    {
57        let payload = MessagePayload(format.serialize(message)?);
58        Ok(Self::new_inner(Some(payload)))
59    }
60
61    /// Returns `true` if this is an empty message.
62    fn is_none(&self) -> bool {
63        self.maybe_message().is_none()
64    }
65
66    /// Returns `Ok(())` if the message is indeed an empty message.
67    fn assert_is_none(&self) -> Result<(), Self::Error> {
68        if self.is_none() {
69            Ok(())
70        } else {
71            Err("The payload was expected to be `None`, but contains a message"
72                .to_string()
73                .into())
74        }
75    }
76
77    /// Returns `Ok(())` if the message cannot be deserialized into `T`.
78    ///
79    /// This is intended to be used in the implementations of
80    /// [`Protocol::verify_direct_message_is_invalid`](`crate::protocol::Protocol::verify_direct_message_is_invalid`) or
81    /// [`Protocol::verify_echo_broadcast_is_invalid`](`crate::protocol::Protocol::verify_echo_broadcast_is_invalid`).
82    fn verify_is_not<'de, T: Deserialize<'de>>(&'de self, format: &BoxedFormat) -> Result<(), MessageValidationError> {
83        if self.deserialize::<T>(format).is_err() {
84            Ok(())
85        } else {
86            Err(MessageValidationError::InvalidEvidence(
87                "Message deserialized successfully, as expected by the protocol".into(),
88            ))
89        }
90    }
91
92    /// Returns `Ok(())` if the message contains a payload.
93    ///
94    /// This is intended to be used in the implementations of
95    /// [`Protocol::verify_direct_message_is_invalid`](`crate::protocol::Protocol::verify_direct_message_is_invalid`) or
96    /// [`Protocol::verify_echo_broadcast_is_invalid`](`crate::protocol::Protocol::verify_echo_broadcast_is_invalid`).
97    fn verify_is_some(&self) -> Result<(), MessageValidationError> {
98        if self.maybe_message().is_some() {
99            Ok(())
100        } else {
101            Err(MessageValidationError::InvalidEvidence(
102                "The payload is `None`, as expected by the protocol".into(),
103            ))
104        }
105    }
106
107    /// Deserializes the message into `T`.
108    fn deserialize<'de, T>(&'de self, format: &BoxedFormat) -> Result<T, Self::Error>
109    where
110        T: Deserialize<'de>,
111    {
112        let payload = self
113            .maybe_message()
114            .as_ref()
115            .ok_or_else(|| "The payload is `None` and cannot be deserialized".into())?;
116        format.deserialize(&payload.0).map_err(|err| err.to_string().into())
117    }
118}
119
120#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
121pub(crate) enum PartKind {
122    EchoBroadcast,
123    NormalBroadcast,
124    DirectMessage,
125}
126
127pub(crate) trait HasPartKind {
128    const KIND: PartKind;
129}
130
131// We don't want to expose this functionality to the user, so it is separate from `ProtocolMessagePart` trait.
132pub(crate) trait ProtocolMessagePartHashable: ProtocolMessagePart + HasPartKind {
133    fn hash<D: Digest>(&self) -> digest::Output<D> {
134        let mut digest = D::new_with_prefix(b"ProtocolMessagePart");
135        match Self::KIND {
136            PartKind::EchoBroadcast => digest.update([0u8]),
137            PartKind::NormalBroadcast => digest.update([1u8]),
138            PartKind::DirectMessage => digest.update([2u8]),
139        }
140        match self.maybe_message().as_ref() {
141            None => digest.update([0u8]),
142            Some(payload) => {
143                let payload_len =
144                    u64::try_from(payload.as_ref().len()).expect("payload length does not exceed 18 exabytes");
145                digest.update([1u8]);
146                digest.update(payload_len.to_be_bytes());
147                digest.update(payload);
148            }
149        };
150        digest.finalize()
151    }
152}
153
154impl<T: ProtocolMessagePart + HasPartKind> ProtocolMessagePartHashable for T {}
155
156/// A serialized direct message.
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158pub struct DirectMessage(Option<MessagePayload>);
159
160impl ProtocolMessageWrapper for DirectMessage {
161    fn new_inner(maybe_message: Option<MessagePayload>) -> Self {
162        Self(maybe_message)
163    }
164
165    fn maybe_message(&self) -> &Option<MessagePayload> {
166        &self.0
167    }
168}
169
170impl HasPartKind for DirectMessage {
171    const KIND: PartKind = PartKind::DirectMessage;
172}
173
174impl ProtocolMessagePart for DirectMessage {
175    type Error = DirectMessageError;
176}
177
178/// A serialized echo broadcast.
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180pub struct EchoBroadcast(Option<MessagePayload>);
181
182impl ProtocolMessageWrapper for EchoBroadcast {
183    fn new_inner(maybe_message: Option<MessagePayload>) -> Self {
184        Self(maybe_message)
185    }
186
187    fn maybe_message(&self) -> &Option<MessagePayload> {
188        &self.0
189    }
190}
191
192impl HasPartKind for EchoBroadcast {
193    const KIND: PartKind = PartKind::EchoBroadcast;
194}
195
196impl ProtocolMessagePart for EchoBroadcast {
197    type Error = EchoBroadcastError;
198}
199
200/// A serialized regular (non-echo) broadcast.
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct NormalBroadcast(Option<MessagePayload>);
203
204impl ProtocolMessageWrapper for NormalBroadcast {
205    fn new_inner(maybe_message: Option<MessagePayload>) -> Self {
206        Self(maybe_message)
207    }
208
209    fn maybe_message(&self) -> &Option<MessagePayload> {
210        &self.0
211    }
212}
213
214impl HasPartKind for NormalBroadcast {
215    const KIND: PartKind = PartKind::NormalBroadcast;
216}
217
218impl ProtocolMessagePart for NormalBroadcast {
219    type Error = NormalBroadcastError;
220}
221
222/// A bundle containing the message parts for one round.
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct ProtocolMessage {
225    /// The echo-broadcased message part.
226    pub echo_broadcast: EchoBroadcast,
227    /// The message part broadcasted without additional verification.
228    pub normal_broadcast: NormalBroadcast,
229    /// The message part sent directly to one node.
230    pub direct_message: DirectMessage,
231}