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}