rustdds/dds/key.rs
1// This module defines traits to specify a key as defined in DDS specification.
2// See e.g. Figure 2.3 in "2.2.1.2.2 Overall Conceptual Model"
3use std::hash::Hash;
4
5use byteorder::BigEndian;
6use rand::Rng;
7use log::error;
8use serde::{Deserialize, Serialize};
9pub use cdr_encoding_size::*;
10
11use crate::serialization::{
12 pl_cdr_adapters::{PlCdrDeserializeError, PlCdrSerializeError},
13 to_vec,
14};
15
16/// Data sample must implement [`Keyed`] to be used in a WITH_KEY topic.
17///
18/// It allows a Key to be extracted from the
19/// sample. In its simplest form, the key may be just a part of the sample data,
20/// but it can be anything computable from an immutable sample by an
21/// application-defined function. It is recommended that this function be
22/// lightweight to compute.
23///
24/// The key is used to distinguish between different Instances of the data in a
25/// DDS Topic.
26///
27/// DDS WITH_KEY Topics are similar to distributed key-value maps. A Sample
28/// corresponds to a key-value-pair and the `Keyed` trait allows to extract the
29/// key out of the pair. An Instance means all the Samples with the same Key.
30/// These samples can be viewed as updates to the key. WITH_KEY topics also
31/// support a Dispose operation, which corresponds to removing a key from the
32/// map.
33///
34/// A `Keyed` type has an associated type `K`, which is the corresponding key
35/// type. `K` must implement [`Key`]. Otherwise, `K` can be chosen to suit the
36/// application. It is advisable that `K` is something that can be cloned with
37/// reasonable effort.
38///
39/// [`Key`]: trait.Key.html
40pub trait Keyed {
41 type K: Key;
42
43 fn key(&self) -> Self::K;
44}
45
46// See RTPS spec Section 8.7.10 Key Hash
47// and Section 9.6.3.8 KeyHash
48#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy)]
49pub struct KeyHash([u8; 16]);
50
51impl KeyHash {
52 pub fn zero() -> Self {
53 Self([0; 16])
54 }
55
56 pub fn to_vec(self) -> Vec<u8> {
57 Vec::from(self.0)
58 }
59
60 pub fn into_pl_cdr_bytes(self) -> Result<Vec<u8>, PlCdrSerializeError> {
61 Ok(self.to_vec())
62 }
63
64 pub fn from_pl_cdr_bytes(bytes: Vec<u8>) -> Result<Self, PlCdrDeserializeError> {
65 <[u8; 16]>::try_from(bytes)
66 .map(Self)
67 .map_err(|_e| speedy::Error::custom("expected 16 bytes for KeyHash").into())
68 }
69}
70
71/// Trait for instance lookup key in a WITH_KEY topic.
72///
73/// The corresponding data sample type must implement [`Keyed`].
74/// If the topic is NO_KEY, both of these can be ignored.
75///
76/// It is a combination of traits from the standard library
77/// * [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
78/// * [Eq](https://doc.rust-lang.org/std/cmp/trait.Eq.html)
79/// * [PartialOrd](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
80/// * [Ord](https://doc.rust-lang.org/std/cmp/trait.Ord.html)
81/// * [Hash](https://doc.rust-lang.org/std/hash/trait.Hash.html)
82/// * [Clone](https://doc.rust-lang.org/std/clone/trait.Clone.html)
83///
84/// and Serde traits
85/// * [Serialize](https://docs.serde.rs/serde/trait.Serialize.html) and
86/// * [DeserializeOwned](https://docs.serde.rs/serde/de/trait.DeserializeOwned.html)
87///
88/// and a RustDDS-specific trait
89/// * [CdrEncodingSize] , for which we provide a [derive
90/// macro](derive@cdr_encoding_size::CdrEncodingSize).
91///
92/// No other methods are required, so for many types it should be possible to
93/// `#[derive]` all the prerequisite traits and implement as `impl Key for Foo
94/// {}`. Consider also deriving [`Copy`] for your key, if the usual
95/// preconditions are satisfied.
96///
97/// Note: When implementing Key, DeserializeOwned cannot and need not be
98/// derived, as it is a type alias. Derive (or implement) the [`Deserialize`]
99/// trait instead.
100///
101/// # Example
102/// ```
103/// use rustdds::*;
104/// use serde::{Serialize, Deserialize};
105///
106/// #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
107/// Serialize, Deserialize, CdrEncodingSize)]
108/// pub struct MyKey {
109/// number: u32,
110/// name: String,
111/// }
112///
113/// impl Key for MyKey {}
114/// ```
115pub trait Key:
116 Eq + PartialEq + PartialOrd + Ord + Hash + Clone + Serialize + CdrEncodingSize
117{
118 // no methods required
119
120 // provided method:
121 fn hash_key(&self, force_md5: bool) -> KeyHash {
122 // See RTPS Spec v2.3 Section 9.6.3.8 KeyHash
123
124 /* The KeyHash_t is computed from the Data as follows using one of two algorithms depending on whether
125 the Data type is such that the maximum size of the sequential CDR encapsulation of
126 all the key fields is less than or equal to 128 bits (the size of the KeyHash_t).
127
128 • If the maximum size of the sequential CDR representation of all the key fields is less
129 than or equal to 128 bits, then the KeyHash_t shall be computed as the CDR Big-Endian
130 representation of all the Key fields in sequence. Any unfilled bits in the KeyHash_t
131 shall be set to zero.
132 • Otherwise the KeyHash_t shall be computed as a 128-bit MD5 Digest (IETF RFC 1321)
133 applied to the CDR Big- Endian representation of all the Key fields in sequence.
134
135 Note that the choice of the algorithm to use depends on the data-type,
136 not on any particular data value.
137 */
138
139 // The specification calls for "sequential CDR representation of all the key
140 // fields" and "CDR Big- Endian representation of all the Key fields in
141 // sequence". We take this to mean the CDR encoding of the Key.
142 // (Does it include CDR-specified alignment padding too?)
143 //
144
145 /*
146 DDS Security specification v1.1, Section 7.3.4 Mandatory use of the KeyHash for encrypted
147 messages:
148
149 [...] For this reason the DDS Security specification imposes additional constraints in the use
150 of the key hash. These constraints apply only to the Data or DataFrag RTPS SubMessages where
151 the SerializedPayload SubmessageElement is encrypted by the operation
152 encode_serialized_payload of the CryptoTransform plugin:
153
154 (1) The KeyHash shall be included in the Inline Qos.
155
156 (2) The KeyHash shall be computed as the 128 bit MD5 Digest (IETF RFC 1321) applied to the CDR
157 Big- Endian encapsulation of all the Key fields in sequence. Unlike the rule stated in sub
158 clause 9.6.3.3 of the DDS specification, the MD5 hash shall be used regardless of the
159 maximum-size of the serialized key.
160 */
161
162 let mut cdr_bytes = to_vec::<Self, BigEndian>(self).unwrap_or_else(|e| {
163 error!("Hashing key {:?} failed!", e);
164 // This would cause a lot of hash collisions, but wht else we could do
165 // if the key cannot be serialized? Are there any realistic conditions
166 // this could even occur?
167 vec![0; 16]
168 });
169
170 KeyHash(
171 if force_md5 || Self::cdr_encoding_max_size() > CdrEncodingMaxSize::Bytes(16) {
172 // use MD5 hash to get the hash. The MD5 hash is always exactly
173 // 16 bytes, so just deref it to [u8;16]
174 *md5::compute(&cdr_bytes)
175 } else {
176 cdr_bytes.resize(16, 0x00); // pad with zeros to get 16 bytes
177 <[u8; 16]>::try_from(cdr_bytes).unwrap() // this succeeds, because of
178 // the resize above
179 },
180 )
181 }
182}
183
184impl Key for () {
185 fn hash_key(&self, _force_md5: bool) -> KeyHash {
186 KeyHash::zero()
187 }
188}
189
190/// Key for a reference type `&D` is the same as for the value type `D`.
191/// This is required internally for the implementation of NoKey topics.
192impl<D: Keyed> Keyed for &D {
193 type K = D::K;
194 fn key(&self) -> Self::K {
195 (*self).key()
196 }
197}
198
199// TODO: might want to implement this for each primitive?
200impl Key for bool {}
201impl Key for char {}
202impl Key for i8 {}
203impl Key for i16 {}
204impl Key for i32 {}
205impl Key for i64 {}
206impl Key for i128 {}
207// impl Key for isize {} // should not be used in serializable data, as size is
208// platform-dependent
209impl Key for u8 {}
210impl Key for u16 {}
211impl Key for u32 {}
212impl Key for u64 {}
213impl Key for u128 {}
214// impl Key for usize {} // should not be used in serializable data, as size is
215// platform-dependent
216
217impl Key for String {}
218
219#[derive(
220 Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize, CdrEncodingSize,
221)]
222/// Key type to identify data instances in builtin topics
223pub struct BuiltInTopicKey {
224 /// IDL PSM (2.3.3, pg 138) uses array of 3x long to implement this
225 value: [i32; 3],
226}
227
228impl BuiltInTopicKey {
229 pub fn random_key() -> Self {
230 let mut rng = rand::rng();
231 Self {
232 value: [rng.random(), rng.random(), rng.random()],
233 }
234 }
235}