1extern crate alloc;
30use alloc::string::String;
31use alloc::vec::Vec;
32
33use crate::encoding::{Endianness, read_u32, write_u32};
34use crate::error::XrceError;
35
36pub mod repr_disc {
38 pub const INVALID: u8 = 0x00;
40 pub const BY_REFERENCE: u8 = 0x01;
42 pub const AS_XML_STRING: u8 = 0x02;
44 pub const IN_BINARY: u8 = 0x03;
46}
47
48pub const REPR_MAX_INLINE_BYTES: usize = 65_536;
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum ObjectVariant {
56 ByReference(String),
58 ByXmlString(String),
60 InBinary(Vec<u8>),
62}
63
64impl ObjectVariant {
65 #[must_use]
67 pub fn discriminator(&self) -> u8 {
68 match self {
69 Self::ByReference(_) => repr_disc::BY_REFERENCE,
70 Self::ByXmlString(_) => repr_disc::AS_XML_STRING,
71 Self::InBinary(_) => repr_disc::IN_BINARY,
72 }
73 }
74
75 pub fn encode(&self, e: Endianness) -> Result<Vec<u8>, XrceError> {
90 let payload = match self {
91 Self::ByReference(s) | Self::ByXmlString(s) => s.as_bytes(),
92 Self::InBinary(b) => b.as_slice(),
93 };
94 if payload.len() > REPR_MAX_INLINE_BYTES {
95 return Err(XrceError::PayloadTooLarge {
96 limit: REPR_MAX_INLINE_BYTES,
97 actual: payload.len(),
98 });
99 }
100 let extra_nul = matches!(self, Self::ByReference(_) | Self::ByXmlString(_));
102 let payload_len = if extra_nul {
103 payload.len() + 1
104 } else {
105 payload.len()
106 };
107 let len_u32 = u32::try_from(payload_len).map_err(|_| XrceError::ValueOutOfRange {
108 message: "object variant length exceeds u32",
109 })?;
110 let mut out = Vec::with_capacity(8 + payload_len);
111 out.push(self.discriminator());
112 out.extend_from_slice(&[0u8, 0, 0]); let mut len_buf = [0u8; 4];
114 write_u32(&mut len_buf, len_u32, e)?;
115 out.extend_from_slice(&len_buf);
116 out.extend_from_slice(payload);
117 if extra_nul {
118 out.push(0u8);
119 }
120 Ok(out)
121 }
122
123 pub fn decode(bytes: &[u8], e: Endianness) -> Result<(Self, usize), XrceError> {
128 if bytes.len() < 8 {
129 return Err(XrceError::UnexpectedEof {
130 needed: 8,
131 offset: bytes.len(),
132 });
133 }
134 let disc = bytes[0];
135 let len = read_u32(&bytes[4..8], e)?;
137 let len_us = usize::try_from(len).map_err(|_| XrceError::ValueOutOfRange {
138 message: "object variant length exceeds usize",
139 })?;
140 if len_us > REPR_MAX_INLINE_BYTES {
141 return Err(XrceError::PayloadTooLarge {
142 limit: REPR_MAX_INLINE_BYTES,
143 actual: len_us,
144 });
145 }
146 if bytes.len() < 8 + len_us {
147 return Err(XrceError::UnexpectedEof {
148 needed: 8 + len_us,
149 offset: bytes.len(),
150 });
151 }
152 let payload = &bytes[8..8 + len_us];
153 let consumed = 8 + len_us;
154 let variant = match disc {
155 repr_disc::BY_REFERENCE => {
156 let s = decode_xcdr_string(payload)?;
157 Self::ByReference(s)
158 }
159 repr_disc::AS_XML_STRING => {
160 let s = decode_xcdr_string(payload)?;
161 Self::ByXmlString(s)
162 }
163 repr_disc::IN_BINARY => Self::InBinary(payload.to_vec()),
164 _ => {
165 return Err(XrceError::ValueOutOfRange {
166 message: "unknown ObjectVariant discriminator",
167 });
168 }
169 };
170 Ok((variant, consumed))
171 }
172}
173
174fn decode_xcdr_string(payload: &[u8]) -> Result<String, XrceError> {
177 let trimmed = if let Some((&last, rest)) = payload.split_last() {
178 if last == 0 { rest } else { payload }
179 } else {
180 payload
181 };
182 core::str::from_utf8(trimmed)
183 .map(alloc::string::ToString::to_string)
184 .map_err(|_| XrceError::ValueOutOfRange {
185 message: "object variant string is not valid utf-8",
186 })
187}
188
189#[cfg(test)]
190mod tests {
191 #![allow(clippy::expect_used, clippy::unwrap_used)]
192 use super::*;
193
194 #[test]
195 fn by_reference_roundtrip_le() {
196 let v = ObjectVariant::ByReference("MyTopicProfile".into());
197 let enc = v.encode(Endianness::Little).unwrap();
198 let (v2, n) = ObjectVariant::decode(&enc, Endianness::Little).unwrap();
199 assert_eq!(v, v2);
200 assert_eq!(n, enc.len());
201 }
202
203 #[test]
204 fn by_xml_string_roundtrip_be() {
205 let v = ObjectVariant::ByXmlString(
206 "<dds><topic name=\"Chat\" type=\"std::string\"/></dds>".into(),
207 );
208 let enc = v.encode(Endianness::Big).unwrap();
209 let (v2, _) = ObjectVariant::decode(&enc, Endianness::Big).unwrap();
210 assert_eq!(v, v2);
211 }
212
213 #[test]
214 fn in_binary_roundtrip() {
215 let v = ObjectVariant::InBinary(alloc::vec![0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x01, 0x02]);
216 let enc = v.encode(Endianness::Little).unwrap();
217 let (v2, _) = ObjectVariant::decode(&enc, Endianness::Little).unwrap();
218 assert_eq!(v, v2);
219 }
220
221 #[test]
222 fn unknown_discriminator_rejected() {
223 let mut bad = alloc::vec![0x42, 0, 0, 0];
225 bad.extend_from_slice(&0u32.to_le_bytes());
226 let res = ObjectVariant::decode(&bad, Endianness::Little);
227 assert!(matches!(res, Err(XrceError::ValueOutOfRange { .. })));
228 }
229
230 #[test]
231 fn truncated_header_returns_eof() {
232 let res = ObjectVariant::decode(&[0x01, 0, 0], Endianness::Little);
233 assert!(matches!(
234 res,
235 Err(XrceError::UnexpectedEof { needed: 8, .. })
236 ));
237 }
238
239 #[test]
240 fn truncated_payload_returns_eof() {
241 let mut bad = alloc::vec![0x01, 0, 0, 0];
243 bad.extend_from_slice(&10u32.to_le_bytes());
244 bad.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
245 let res = ObjectVariant::decode(&bad, Endianness::Little);
246 assert!(matches!(res, Err(XrceError::UnexpectedEof { .. })));
247 }
248
249 #[test]
250 fn oversized_length_rejected_as_payload_too_large() {
251 let mut bad = alloc::vec![0x01, 0, 0, 0];
252 let huge = (REPR_MAX_INLINE_BYTES as u32) + 1;
253 bad.extend_from_slice(&huge.to_le_bytes());
254 let res = ObjectVariant::decode(&bad, Endianness::Little);
255 assert!(matches!(res, Err(XrceError::PayloadTooLarge { .. })));
256 }
257
258 #[test]
259 fn invalid_utf8_rejected() {
260 let mut bad = alloc::vec![repr_disc::BY_REFERENCE, 0, 0, 0];
262 bad.extend_from_slice(&3u32.to_le_bytes());
263 bad.extend_from_slice(&[0xFF, 0xFE, 0xFD]);
264 let res = ObjectVariant::decode(&bad, Endianness::Little);
265 assert!(matches!(res, Err(XrceError::ValueOutOfRange { .. })));
266 }
267
268 #[test]
269 fn discriminator_returns_correct_byte() {
270 assert_eq!(
271 ObjectVariant::ByReference(String::new()).discriminator(),
272 repr_disc::BY_REFERENCE
273 );
274 assert_eq!(
275 ObjectVariant::ByXmlString(String::new()).discriminator(),
276 repr_disc::AS_XML_STRING
277 );
278 assert_eq!(
279 ObjectVariant::InBinary(Vec::new()).discriminator(),
280 repr_disc::IN_BINARY
281 );
282 }
283
284 #[test]
297 fn object_variant_carries_all_12_objk_kinds_through_outer_repr() {
298 use crate::object_kind::{
299 OBJK_AGENT, OBJK_APPLICATION, OBJK_CLIENT, OBJK_DATAREADER, OBJK_DATAWRITER,
300 OBJK_PARTICIPANT, OBJK_PUBLISHER, OBJK_QOSPROFILE, OBJK_SUBSCRIBER, OBJK_TOPIC,
301 OBJK_TYPE,
302 };
303 let kinds: &[u8] = &[
304 OBJK_PARTICIPANT,
305 OBJK_TOPIC,
306 OBJK_PUBLISHER,
307 OBJK_SUBSCRIBER,
308 OBJK_DATAWRITER,
309 OBJK_DATAREADER,
310 OBJK_TYPE,
311 OBJK_QOSPROFILE,
312 OBJK_APPLICATION,
313 OBJK_AGENT,
314 OBJK_CLIENT,
315 ];
316 for kind in kinds {
317 let inner = alloc::vec![*kind];
320 let v = ObjectVariant::InBinary(inner.clone());
321 let bytes = v.encode(Endianness::Little).expect("encode");
322 let (v2, _) = ObjectVariant::decode(&bytes, Endianness::Little).expect("decode");
323 assert_eq!(v2, ObjectVariant::InBinary(inner));
324 }
325 }
326
327 #[test]
328 fn object_variant_xml_form_supports_topic_qosprofile_application() {
329 for s in [
331 "<topic name=\"T\"/>",
332 "<qos_profile name=\"P\"/>",
333 "<application name=\"A\"/>",
334 ] {
335 let v = ObjectVariant::ByXmlString(s.into());
336 let bytes = v.encode(Endianness::Little).expect("encode");
337 let (v2, _) = ObjectVariant::decode(&bytes, Endianness::Little).expect("decode");
338 assert_eq!(v2, ObjectVariant::ByXmlString(s.into()));
339 }
340 }
341}