zenoh_flow/types/
message.rs

1//
2// Copyright (c) 2021 - 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15use crate::bail;
16use crate::prelude::ErrorKind;
17use crate::traits::SendSyncAny;
18use crate::types::{FlowId, NodeId, PortId};
19use crate::{zferror, Result};
20
21use async_std::sync::Arc;
22use serde::{Deserialize, Serialize};
23use std::ops::Deref;
24use std::{cmp::Ordering, fmt::Debug};
25use uhlc::Timestamp;
26use uuid::Uuid;
27
28/// `SerializerFn` is a type-erased version of the serializer function provided by node developer.
29///
30/// It is passed to downstream nodes (residing on the same process) in case they need to serialize
31/// the data they receive typed.
32/// Passing around the function allows us to serialize only when needed and without requiring prior
33/// knowledge.
34pub(crate) type SerializerFn =
35    dyn Fn(&mut Vec<u8>, Arc<dyn SendSyncAny>) -> Result<()> + Send + Sync;
36
37/// This function is what Zenoh-Flow will use to deserialize the data received on the `Input`.
38///
39/// It will be called for instance when data is received serialized (i.e. from an upstream node that
40/// is either not implemented in Rust or on a different process) before it is given to the user's
41/// code.
42pub(crate) type DeserializerFn<T> = dyn Fn(&[u8]) -> anyhow::Result<T> + Send + Sync;
43
44/// A `Payload` is Zenoh-Flow's lowest message container.
45///
46/// It either contains serialized data, i.e. `Bytes` (if received from the network, or from nodes
47/// not written in Rust), or `Typed` data as a tuple `(`[Any](`std::any::Any`)`, SerializerFn)`.
48#[derive(Clone, Serialize, Deserialize)]
49pub enum Payload {
50    /// Serialized data, coming either from Zenoh of from non-Rust node.
51    Bytes(Arc<Vec<u8>>),
52    #[serde(skip_serializing, skip_deserializing)]
53    /// Data coming from another Rust node located on the same process that can either be downcasted
54    /// (provided that its actual type is known) or serialized.
55    Typed((Arc<dyn SendSyncAny>, Arc<SerializerFn>)),
56}
57
58impl Debug for Payload {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            Payload::Bytes(_) => write!(f, "Payload::Bytes"),
62            Payload::Typed(_) => write!(f, "Payload::Typed"),
63        }
64    }
65}
66
67impl Payload {
68    pub fn from_data<T: Send + Sync + 'static>(
69        data: Data<T>,
70        serializer: Arc<SerializerFn>,
71    ) -> Self {
72        match data.inner {
73            DataInner::Payload { payload, data: _ } => payload,
74            DataInner::Data(data) => {
75                Self::Typed((Arc::new(data) as Arc<dyn SendSyncAny>, serializer))
76            }
77        }
78    }
79
80    /// Populate `buffer` with the bytes representation of the [Payload].
81    ///
82    /// # Performance
83    ///
84    /// This method will serialize the [Payload] if it is `Typed`. Otherwise, the bytes
85    /// representation is simply cloned.
86    ///
87    /// The provided `buffer` is reused and cleared between calls, so once its capacity stabilizes
88    /// no more allocation is performed.
89    pub(crate) fn try_as_bytes_into(&self, buffer: &mut Vec<u8>) -> Result<()> {
90        buffer.clear(); // remove previous data but keep the allocated capacity
91
92        match self {
93            Payload::Bytes(bytes) => {
94                (**bytes).clone_into(buffer);
95                Ok(())
96            }
97            Payload::Typed((typed_data, serializer)) => {
98                (serializer)(buffer, Arc::clone(typed_data))
99            }
100        }
101    }
102
103    /// Return an [Arc] containing the bytes representation of the [Payload].
104    ///
105    /// # Performance
106    ///
107    /// This method will only serialize (and thus allocate) the [Payload] if it is typed. Otherwise
108    /// the [Arc] is cloned.
109    //
110    // NOTE: This method is used by, at least, our Python API.
111    pub fn try_as_bytes(&self) -> Result<Arc<Vec<u8>>> {
112        match self {
113            Payload::Bytes(bytes) => Ok(bytes.clone()),
114            Payload::Typed((typed_data, serializer)) => {
115                let mut buffer = Vec::default();
116                (serializer)(&mut buffer, Arc::clone(typed_data))?;
117                Ok(Arc::new(buffer))
118            }
119        }
120    }
121}
122
123/// Creates a new `Data` from a `Vec<u8>`.
124///
125/// In order to avoid copies it puts the data inside an `Arc`.
126impl From<Vec<u8>> for Payload {
127    fn from(bytes: Vec<u8>) -> Self {
128        Self::Bytes(Arc::new(bytes))
129    }
130}
131
132/// Creates a new `Data` from a `&[u8]`.
133impl From<&[u8]> for Payload {
134    fn from(bytes: &[u8]) -> Self {
135        Self::Bytes(Arc::new(bytes.to_vec()))
136    }
137}
138
139impl From<DataMessage> for Payload {
140    fn from(data_message: DataMessage) -> Self {
141        data_message.data
142    }
143}
144
145/// Zenoh-Flow data message.
146///
147/// It contains the actual data, the timestamp associated, the end to end deadline, the end to end
148/// deadline misses and loop contexts.
149#[derive(Clone, Debug, Serialize, Deserialize)]
150pub struct DataMessage {
151    pub(crate) data: Payload,
152    pub(crate) timestamp: Timestamp,
153}
154
155impl Deref for DataMessage {
156    type Target = Payload;
157
158    fn deref(&self) -> &Self::Target {
159        &self.data
160    }
161}
162
163impl DataMessage {
164    /// Creates a new message from serialized data.
165    ///
166    /// This is used when the message is coming from Zenoh or from a non-rust node.
167    pub fn new_serialized(data: Vec<u8>, timestamp: Timestamp) -> Self {
168        Self {
169            data: Payload::Bytes(Arc::new(data)),
170            timestamp,
171        }
172    }
173
174    /// Return the [Timestamp] associated with this [DataMessage].
175    //
176    // NOTE: This method is used by, at least, our Python API.
177    pub fn get_timestamp(&self) -> &Timestamp {
178        &self.timestamp
179    }
180}
181
182/// Metadata stored in Zenoh's time series storages.
183/// It contains information about the recording.
184/// Multiple [`RecordingMetadata`](`RecordingMetadata`) can be used
185/// to synchronize the recording from different Ports.
186#[derive(Clone, Debug, Serialize, Deserialize)]
187pub struct RecordingMetadata {
188    pub(crate) timestamp: Timestamp,
189    pub(crate) port_id: PortId,
190    pub(crate) node_id: NodeId,
191    pub(crate) flow_id: FlowId,
192    pub(crate) instance_id: Uuid,
193}
194
195/// Zenoh Flow control messages.
196/// It contains the control messages used within Zenoh Flow.
197/// For the time being only the `RecordingStart` and `RecordingStop` messages
198/// have been defined,
199/// *Note*: Most of messages are not yet defined.
200#[derive(Clone, Debug, Serialize, Deserialize)]
201pub enum ControlMessage {
202    // These messages are not yet defined, those are some ideas
203    // ReadyToMigrate,
204    // ChangeMode(u8, u128),
205    RecordingStart(RecordingMetadata),
206    RecordingStop(Timestamp),
207}
208
209/// The Zenoh-Flow message that is sent across `Link` and across Zenoh.
210///
211/// It contains either a [`DataMessage`](`DataMessage`) or a [`Timestamp`](`uhlc::Timestamp`),
212/// in such case the `LinkMessage` variant is `Watermark`.
213#[derive(Clone, Debug, Serialize, Deserialize)]
214pub enum LinkMessage {
215    Data(DataMessage),
216    Watermark(Timestamp),
217}
218
219impl LinkMessage {
220    /// Creates a `LinkMessage::Data` from a [`Payload`](`Payload`).
221    pub fn from_payload(output: Payload, timestamp: Timestamp) -> Self {
222        Self::Data(DataMessage {
223            data: output,
224            timestamp,
225        })
226    }
227
228    /// Serializes the [LinkMessage] using [bincode] into the given `buffer`.
229    ///
230    /// The `inner_buffer` is used to serialize (if need be) the [Payload] contained inside the
231    /// [LinkMessage].
232    ///
233    /// # Performance
234    ///
235    /// The provided `buffer` and `inner_buffer` are reused and cleared between calls, so once their
236    /// capacity stabilizes no (re)allocation is performed.
237    ///
238    /// # Errors
239    ///
240    /// An error variant is returned in case of:
241    /// - fails to serialize
242    pub fn serialize_bincode_into(
243        &self,
244        message_buffer: &mut Vec<u8>,
245        payload_buffer: &mut Vec<u8>,
246    ) -> Result<()> {
247        payload_buffer.clear(); // empty the buffers but keep their allocated capacity
248        message_buffer.clear();
249
250        match &self {
251            LinkMessage::Data(data_message) => match &data_message.data {
252                Payload::Bytes(_) => bincode::serialize_into(message_buffer, &self)
253                    .map_err(|e| zferror!(ErrorKind::SerializationError, e).into()),
254                Payload::Typed((data, serializer)) => {
255                    (serializer)(payload_buffer, Arc::clone(data))?;
256                    let serialized_message = LinkMessage::Data(DataMessage {
257                        data: Payload::Bytes(Arc::new(payload_buffer.clone())),
258                        timestamp: data_message.timestamp,
259                    });
260
261                    bincode::serialize_into(message_buffer, &serialized_message)
262                        .map_err(|e| zferror!(ErrorKind::SerializationError, e).into())
263                }
264            },
265            _ => bincode::serialize_into(message_buffer, &self)
266                .map_err(|e| zferror!(ErrorKind::SerializationError, e).into()),
267        }
268    }
269
270    /// Serializes the [LinkMessage] using [bincode] into the given `shm_buffer` shared memory
271    /// buffer.
272    ///
273    /// The `inner_buffer` is used to serialize (if need be) the [Payload] contained inside the
274    /// [LinkMessage].
275    ///
276    /// # Performance
277    ///
278    /// The provided `inner_buffer` is reused and cleared between calls, so once its capacity
279    /// stabilizes no (re)allocation is performed.
280    ///
281    /// # Errors
282    ///
283    /// An error variant is returned in case of:
284    /// - fails to serialize
285    /// - there is not enough space in the slice
286    pub fn serialize_bincode_into_shm(
287        &self,
288        shm_buffer: &mut [u8],
289        payload_buffer: &mut Vec<u8>,
290    ) -> Result<()> {
291        payload_buffer.clear(); // empty the buffer but keep the allocated capacity
292
293        match &self {
294            LinkMessage::Data(data_message) => match &data_message.data {
295                Payload::Bytes(_) => bincode::serialize_into(shm_buffer, &self)
296                    .map_err(|e| zferror!(ErrorKind::SerializationError, e).into()),
297                Payload::Typed(_) => {
298                    data_message.try_as_bytes_into(payload_buffer)?;
299                    let serialized_message = LinkMessage::Data(DataMessage::new_serialized(
300                        payload_buffer.clone(),
301                        data_message.timestamp,
302                    ));
303                    bincode::serialize_into(shm_buffer, &serialized_message)
304                        .map_err(|e| zferror!(ErrorKind::SerializationError, e).into())
305                }
306            },
307            _ => bincode::serialize_into(shm_buffer, &self)
308                .map_err(|e| zferror!(ErrorKind::SerializationError, e).into()),
309        }
310    }
311
312    /// Returns the `Timestamp` associated with the message.
313    pub fn get_timestamp(&self) -> Timestamp {
314        match self {
315            Self::Data(data) => data.timestamp,
316            Self::Watermark(ref ts) => *ts,
317            // Self::Control(ref ctrl) => match ctrl {
318            //     ControlMessage::RecordingStart(ref rs) => rs.timestamp,
319            //     ControlMessage::RecordingStop(ref ts) => *ts,
320            // },
321            // _ => Err(ErrorKind::Unsupported),
322        }
323    }
324}
325
326// Manual Ord implementation for message ordering when replay
327impl Ord for LinkMessage {
328    fn cmp(&self, other: &Self) -> Ordering {
329        self.get_timestamp().cmp(&other.get_timestamp())
330    }
331}
332
333impl PartialOrd for LinkMessage {
334    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
335        Some(self.cmp(other))
336    }
337}
338
339impl PartialEq for LinkMessage {
340    fn eq(&self, other: &Self) -> bool {
341        self.get_timestamp() == other.get_timestamp()
342    }
343}
344
345impl Eq for LinkMessage {}
346
347/// A `Message<T>` is what is received on an `Input<T>`, typically after a call to `try_recv` or
348/// `recv`.
349///
350/// A `Message<T>` can either contain [`Data<T>`](`Data`), or signal a _Watermark_.
351#[derive(Debug)]
352pub enum Message<T> {
353    Data(Data<T>),
354    Watermark,
355}
356
357/// A `Data<T>` is a convenience wrapper around `T`.
358///
359/// Upon reception, it transparently deserializes to `T` when the message is received serialized. It
360/// downcasts it to a `&T` when the data is passed "typed" through a channel.
361///
362/// ## Performance
363///
364/// When deserializing, an allocation is performed.
365#[derive(Debug)]
366pub struct Data<T> {
367    inner: DataInner<T>,
368}
369
370/// The `DataInner` enum represents the two ways to send data in an [`Output<T>`](`Output`).
371///
372/// The `Payload` variant corresponds to a previously generated `Data<T>` being sent.
373/// The `Data` variant corresponds to a new instance of `T` being sent.
374pub(crate) enum DataInner<T> {
375    Payload { payload: Payload, data: Option<T> },
376    Data(T),
377}
378
379impl<T> Debug for DataInner<T> {
380    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381        match self {
382            DataInner::Payload { payload, data } => {
383                let data = if data.is_some() { "Some" } else { "None" };
384                write!(f, "DataInner::Payload: {:?} - data: {}", payload, data)
385            }
386            DataInner::Data(_) => write!(f, "DataInner::Data(T)"),
387        }
388    }
389}
390
391// Implementing `From<T>` allows us to accept instances of `T` in the signature of `send` and
392// `try_send` methods as `T` will implement `impl Into<Data<T>>`.
393impl<T: Send + Sync + 'static> From<T> for Data<T> {
394    fn from(value: T) -> Self {
395        Self {
396            inner: DataInner::Data(value),
397        }
398    }
399}
400
401// The implementation of `Deref` is what allows users to transparently manipulate the type `T`.
402//
403// ## SAFETY
404//
405// Despite the presence of `expect` and `panic!`, we should never end up in these situations in
406// normal circumstances.
407//
408// Let us reason here as to why this is "safe".
409//
410// The call to `expect` happens when the inner data is a [`Typed`](`Payload::Typed`) payload and the
411// downcasts to `T` fails. This should not happen because of the way a [`Data`](`Data`) is created:
412// upon creation we first perform a check that the provided typed payload can actually be downcasted
413// to `T` — see the method `Data::try_from_payload`.
414//
415// The call to `panic!` happens when the inner data is a [`Bytes`](`Payload::Bytes`) payload and the
416// `data` field is `None`. Again, this should not happen because of the way a [`Data`](`Data`) is
417// created: upon creation, if the data is received as bytes, we first deserialize it and set the
418// `data` field to `Some(T)` — see the method `Data::try_from_payload`.
419impl<T: 'static> Deref for Data<T> {
420    type Target = T;
421
422    fn deref(&self) -> &Self::Target {
423        match &self.inner {
424            DataInner::Payload { payload, data } => {
425                if let Some(data) = data {
426                    data
427                } else if let Payload::Typed((typed, _)) = payload {
428                    (**typed).as_any().downcast_ref::<T>().expect(
429                        r#"You probably managed to find a very nasty flaw in Zenoh-Flow’s code as we
430believed this situation would never happen (unless explicitely triggered — "explicitely" being an
431understatement here, we feel it’s more like you really, really, wanted to see that message — in
432which case, congratulations!).
433
434Our guess as to what happened is that:
435- the data in `Payload::Typed` was, at first, correct (where we internally do the
436  `as_any().is::<T>()` check),
437- in between this check and the call to `deref` the underlying data somehow changed.
438
439If we did not do a mistake — fortunately the most likely scenario — then we do not know what
440happened and we would be eager to investigate.
441
442Feel free to contact us at < zenoh@zettascale.tech >.
443"#,
444                    )
445                } else {
446                    panic!(
447                        r#"You probably managed to find a very nasty flaw in Zenoh-Flow's code as we
448believed this situation would never happen (unless explicitely triggered — "explicitely" being an
449understatement here, we feel it's more like you really, really, wanted to see that message — in
450which case, congratulations!).
451
452Our guess as to what happened is that:
453- the `data` field is a `Payload::Bytes`,
454- the `typed` field is set to `None`.
455
456If we did not do a mistake — fortunately the most likely scenario — then we do not know what
457happened and we would be eager to investigate.
458
459Feel free to contact us at < zenoh@zettascale.tech >.
460"#
461                    )
462                }
463            }
464            DataInner::Data(data) => data,
465        }
466    }
467}
468
469impl<T: 'static> Data<T> {
470    /// Try to create a new [`Data<T>`](`Data`) based on a [`Payload`](`Payload`).
471    ///
472    /// Depending on the variant of [`Payload`](`Payload`) different steps are performed:
473    /// - if `Payload::Bytes` then Zenoh-Flow tries to deserialize to an instance of `T` (performing
474    ///   an allocation),
475    /// - if `Payload::Typed` then Zenoh-Flow checks that the underlying type matches `T` (relying
476    ///   on [`Any`](`Any`)).
477    ///
478    /// ## Errors
479    ///
480    /// An error will be returned if the Payload does not match `T`, i.e. if the deserialization or
481    /// the downcast failed.
482    pub(crate) fn try_from_payload(
483        payload: Payload,
484        deserializer: Arc<DeserializerFn<T>>,
485    ) -> Result<Self> {
486        let mut typed = None;
487
488        match payload {
489            Payload::Bytes(ref bytes) => typed = Some((deserializer)(bytes.as_slice())?),
490            Payload::Typed((ref typed, _)) => {
491                if !(**typed).as_any().is::<T>() {
492                    bail!(
493                        ErrorKind::DeserializationError,
494                        "Failed to downcast provided value",
495                    )
496                }
497            }
498        }
499
500        Ok(Self {
501            inner: DataInner::Payload {
502                payload,
503                data: typed,
504            },
505        })
506    }
507}