1use std::time::Duration;
70
71use crate::{
72 FrameMessageKind, FramePriority, PayloadEncoding, UCode, UFrameMetadata, UFrameMetadataError,
73 UUri, UUID,
74};
75
76pub const FRAME_FIELDS_VERSION: u8 = 1;
78
79pub const FIELD_SINK: u32 = 1 << 0;
81pub const FIELD_REQID: u32 = 1 << 1;
83pub const FIELD_TTL: u32 = 1 << 2;
85pub const FIELD_COMM_STATUS: u32 = 1 << 3;
87pub const FIELD_PERMISSION_LEVEL: u32 = 1 << 4;
89pub const FIELD_TOKEN: u32 = 1 << 5;
91pub const FIELD_TRACEPARENT: u32 = 1 << 6;
93pub const FIELD_PAYLOAD_ENCODING: u32 = 1 << 7;
95pub const FIELD_MASK_V1: u32 = (1 << 8) - 1;
97
98const ENCODING_HAS_REGISTRY_ID: u8 = 1 << 0;
99const ENCODING_HAS_LITERAL_ID: u8 = 1 << 1;
100const ENCODING_HAS_CONTENT_TYPE: u8 = 1 << 2;
101const ENCODING_MASK_V1: u8 = (1 << 3) - 1;
102
103#[derive(Clone, Debug, Eq, PartialEq)]
105pub enum UFrameFieldsError {
106 Malformed(String),
108 ValueTooLong {
110 field: &'static str,
112 },
113 Metadata(UFrameMetadataError),
115}
116
117impl std::fmt::Display for UFrameFieldsError {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 match self {
120 Self::Malformed(message) => write!(f, "malformed frame metadata fields: {message}"),
121 Self::ValueTooLong { field } => {
122 write!(
123 f,
124 "frame metadata field `{field}` does not fit the field block"
125 )
126 }
127 Self::Metadata(error) => write!(f, "invalid frame metadata: {error}"),
128 }
129 }
130}
131
132impl std::error::Error for UFrameFieldsError {}
133
134impl From<UFrameMetadataError> for UFrameFieldsError {
135 fn from(value: UFrameMetadataError) -> Self {
136 Self::Metadata(value)
137 }
138}
139
140pub fn encode_frame_metadata_fields(
147 metadata: &UFrameMetadata,
148) -> Result<Vec<u8>, UFrameFieldsError> {
149 metadata.validate()?;
150
151 let mut presence = 0_u32;
152 if metadata.sink().is_some() {
153 presence |= FIELD_SINK;
154 }
155 if metadata.reqid().is_some() {
156 presence |= FIELD_REQID;
157 }
158 if metadata.ttl().is_some() {
159 presence |= FIELD_TTL;
160 }
161 if metadata.comm_status().is_some() {
162 presence |= FIELD_COMM_STATUS;
163 }
164 if metadata.permission_level().is_some() {
165 presence |= FIELD_PERMISSION_LEVEL;
166 }
167 if metadata.token().is_some() {
168 presence |= FIELD_TOKEN;
169 }
170 if metadata.traceparent().is_some() {
171 presence |= FIELD_TRACEPARENT;
172 }
173 if metadata.payload_encoding().is_some() {
174 presence |= FIELD_PAYLOAD_ENCODING;
175 }
176
177 let mut out = Vec::with_capacity(64);
178 out.push(FRAME_FIELDS_VERSION);
179 out.push(metadata.kind().wire_code());
180 out.push(metadata.priority().map_or(0, FramePriority::wire_code));
181 out.push(0); out.extend_from_slice(&presence.to_le_bytes());
183 write_uuid(&mut out, metadata.id());
184 if let Some(reqid) = metadata.reqid() {
185 write_uuid(&mut out, reqid);
186 }
187 if let Some(ttl) = metadata.ttl() {
188 let nanos = u64::try_from(ttl.as_nanos())
189 .map_err(|_| UFrameFieldsError::ValueTooLong { field: "ttl" })?;
190 out.extend_from_slice(&nanos.to_le_bytes());
191 }
192 if let Some(comm_status) = metadata.comm_status() {
193 out.extend_from_slice(&comm_status.value().to_le_bytes());
194 }
195 if let Some(permission_level) = metadata.permission_level() {
196 out.extend_from_slice(&permission_level.to_le_bytes());
197 }
198 write_uuri(&mut out, metadata.source())?;
199 if let Some(sink) = metadata.sink() {
200 write_uuri(&mut out, sink)?;
201 }
202 if let Some(token) = metadata.token() {
203 let len = u16::try_from(token.len())
204 .map_err(|_| UFrameFieldsError::ValueTooLong { field: "token" })?;
205 out.extend_from_slice(&len.to_le_bytes());
206 out.extend_from_slice(token.as_bytes());
207 }
208 if let Some(traceparent) = metadata.traceparent() {
209 let len = u8::try_from(traceparent.len()).map_err(|_| UFrameFieldsError::ValueTooLong {
210 field: "traceparent",
211 })?;
212 out.push(len);
213 out.extend_from_slice(traceparent.as_bytes());
214 }
215 if let Some(encoding) = metadata.payload_encoding() {
216 write_payload_encoding(&mut out, encoding)?;
217 }
218 Ok(out)
219}
220
221pub fn decode_frame_metadata_fields(src: &[u8]) -> Result<UFrameMetadata, UFrameFieldsError> {
230 let mut reader = FieldsReader { src, pos: 0 };
231
232 let version = reader.u8()?;
233 if version != FRAME_FIELDS_VERSION {
234 return Err(UFrameFieldsError::Malformed(format!(
235 "unsupported field block version {version}"
236 )));
237 }
238 let kind = FrameMessageKind::from_wire_code(reader.u8()?).ok_or_else(|| {
239 UFrameFieldsError::Malformed("unknown frame message kind code".to_string())
240 })?;
241 let priority = match reader.u8()? {
242 0 => None,
243 code => Some(FramePriority::from_wire_code(code).ok_or_else(|| {
244 UFrameFieldsError::Malformed(format!("unknown frame priority code {code}"))
245 })?),
246 };
247 let reserved = reader.u8()?;
248 if reserved != 0 {
249 return Err(UFrameFieldsError::Malformed(
250 "reserved byte must be zero".to_string(),
251 ));
252 }
253 let presence = reader.u32()?;
254 if presence & !FIELD_MASK_V1 != 0 {
255 return Err(UFrameFieldsError::Malformed(format!(
256 "unknown presence bits {:#010x}",
257 presence & !FIELD_MASK_V1
258 )));
259 }
260
261 let id = reader.uuid()?;
262 let reqid = if presence & FIELD_REQID != 0 {
263 Some(reader.uuid()?)
264 } else {
265 None
266 };
267 let ttl = if presence & FIELD_TTL != 0 {
268 Some(Duration::from_nanos(reader.u64()?))
269 } else {
270 None
271 };
272 let comm_status = if presence & FIELD_COMM_STATUS != 0 {
273 let raw = reader.i32()?;
274 Some(UCode::try_from_i32(raw).map_err(|_| {
275 UFrameFieldsError::Malformed(format!("unknown communication status code {raw}"))
276 })?)
277 } else {
278 None
279 };
280 let permission_level = if presence & FIELD_PERMISSION_LEVEL != 0 {
281 Some(reader.u32()?)
282 } else {
283 None
284 };
285 let source = reader.uuri()?;
286 let sink = if presence & FIELD_SINK != 0 {
287 Some(reader.uuri()?)
288 } else {
289 None
290 };
291 let token = if presence & FIELD_TOKEN != 0 {
292 let len = usize::from(reader.u16()?);
293 Some(reader.utf8(len, "token")?)
294 } else {
295 None
296 };
297 let traceparent = if presence & FIELD_TRACEPARENT != 0 {
298 let len = usize::from(reader.u8()?);
299 Some(reader.utf8(len, "traceparent")?)
300 } else {
301 None
302 };
303 let payload_encoding = if presence & FIELD_PAYLOAD_ENCODING != 0 {
304 Some(reader.payload_encoding()?)
305 } else {
306 None
307 };
308 reader.finish()?;
309
310 let metadata = UFrameMetadata::from_decoded_parts(
311 kind,
312 id,
313 source,
314 sink,
315 reqid,
316 priority,
317 ttl,
318 comm_status,
319 permission_level,
320 token,
321 traceparent,
322 payload_encoding,
323 );
324 metadata.validate()?;
325 Ok(metadata)
326}
327
328fn write_uuid(out: &mut Vec<u8>, uuid: &UUID) {
329 let (msb, lsb) = uuid.as_u64_pair();
330 out.extend_from_slice(&msb.to_le_bytes());
331 out.extend_from_slice(&lsb.to_le_bytes());
332}
333
334fn write_uuri(out: &mut Vec<u8>, uri: &UUri) -> Result<(), UFrameFieldsError> {
335 let ue_id = (u32::from(uri.uentity_instance_id()) << 16) | u32::from(uri.uentity_type_id());
336 out.extend_from_slice(&ue_id.to_le_bytes());
337 out.extend_from_slice(&uri.resource_id().to_le_bytes());
338 out.push(uri.uentity_major_version());
339 let authority = uri.authority_name();
340 let len = u8::try_from(authority.len()).map_err(|_| UFrameFieldsError::ValueTooLong {
341 field: "authority_name",
342 })?;
343 out.push(len);
344 out.extend_from_slice(authority.as_bytes());
345 Ok(())
346}
347
348fn write_payload_encoding(
349 out: &mut Vec<u8>,
350 encoding: &PayloadEncoding,
351) -> Result<(), UFrameFieldsError> {
352 let mut components = 0_u8;
353 if encoding.registry_id().is_some() {
354 components |= ENCODING_HAS_REGISTRY_ID;
355 }
356 if encoding.literal_id().is_some() {
357 components |= ENCODING_HAS_LITERAL_ID;
358 }
359 if encoding.content_type().is_some() {
360 components |= ENCODING_HAS_CONTENT_TYPE;
361 }
362 out.push(components);
363 if let Some(registry_id) = encoding.registry_id() {
364 out.extend_from_slice(®istry_id.to_le_bytes());
365 }
366 if let Some(literal_id) = encoding.literal_id() {
367 let len = u16::try_from(literal_id.len()).map_err(|_| UFrameFieldsError::ValueTooLong {
368 field: "payload_encoding.literal_id",
369 })?;
370 out.extend_from_slice(&len.to_le_bytes());
371 out.extend_from_slice(literal_id.as_bytes());
372 }
373 if let Some(content_type) = encoding.content_type() {
374 let len =
375 u16::try_from(content_type.len()).map_err(|_| UFrameFieldsError::ValueTooLong {
376 field: "payload_encoding.content_type",
377 })?;
378 out.extend_from_slice(&len.to_le_bytes());
379 out.extend_from_slice(content_type.as_bytes());
380 }
381 Ok(())
382}
383
384struct FieldsReader<'a> {
385 src: &'a [u8],
386 pos: usize,
387}
388
389impl FieldsReader<'_> {
390 fn take(&mut self, len: usize) -> Result<&[u8], UFrameFieldsError> {
391 let end = self
392 .pos
393 .checked_add(len)
394 .ok_or_else(|| UFrameFieldsError::Malformed("length overflow".to_string()))?;
395 let chunk = self.src.get(self.pos..end).ok_or_else(|| {
396 UFrameFieldsError::Malformed(format!(
397 "needed {len} byte(s) at offset {}, input has {} byte(s)",
398 self.pos,
399 self.src.len()
400 ))
401 })?;
402 self.pos = end;
403 Ok(chunk)
404 }
405
406 fn finish(&self) -> Result<(), UFrameFieldsError> {
407 if self.pos == self.src.len() {
408 Ok(())
409 } else {
410 Err(UFrameFieldsError::Malformed(format!(
411 "{} trailing byte(s)",
412 self.src.len() - self.pos
413 )))
414 }
415 }
416
417 fn u8(&mut self) -> Result<u8, UFrameFieldsError> {
418 self.take(1)?
419 .first()
420 .copied()
421 .ok_or_else(|| UFrameFieldsError::Malformed("missing byte".to_string()))
422 }
423
424 fn u16(&mut self) -> Result<u16, UFrameFieldsError> {
425 Ok(u16::from_le_bytes(
426 self.take(2)?.try_into().expect("2 bytes"),
427 ))
428 }
429
430 fn u32(&mut self) -> Result<u32, UFrameFieldsError> {
431 Ok(u32::from_le_bytes(
432 self.take(4)?.try_into().expect("4 bytes"),
433 ))
434 }
435
436 fn i32(&mut self) -> Result<i32, UFrameFieldsError> {
437 Ok(i32::from_le_bytes(
438 self.take(4)?.try_into().expect("4 bytes"),
439 ))
440 }
441
442 fn u64(&mut self) -> Result<u64, UFrameFieldsError> {
443 Ok(u64::from_le_bytes(
444 self.take(8)?.try_into().expect("8 bytes"),
445 ))
446 }
447
448 fn utf8(&mut self, len: usize, field: &'static str) -> Result<String, UFrameFieldsError> {
449 let bytes = self.take(len)?;
450 std::str::from_utf8(bytes)
451 .map(ToOwned::to_owned)
452 .map_err(|error| {
453 UFrameFieldsError::Malformed(format!("field `{field}` is not UTF-8: {error}"))
454 })
455 }
456
457 fn uuid(&mut self) -> Result<UUID, UFrameFieldsError> {
458 let msb = self.u64()?;
459 let lsb = self.u64()?;
460 UUID::from_u64_pair(msb, lsb)
461 .map_err(|error| UFrameFieldsError::Malformed(format!("invalid UUID: {error}")))
462 }
463
464 fn uuri(&mut self) -> Result<UUri, UFrameFieldsError> {
465 let ue_id = self.u32()?;
466 let resource_id = self.u16()?;
467 let ue_version_major = self.u8()?;
468 let authority_len = usize::from(self.u8()?);
469 let authority = self.utf8(authority_len, "authority_name")?;
470 UUri::try_from_parts(&authority, ue_id, ue_version_major, resource_id)
471 .map_err(|error| UFrameFieldsError::Malformed(format!("invalid UUri: {error}")))
472 }
473
474 fn payload_encoding(&mut self) -> Result<PayloadEncoding, UFrameFieldsError> {
475 let components = self.u8()?;
476 if components & !ENCODING_MASK_V1 != 0 {
477 return Err(UFrameFieldsError::Malformed(format!(
478 "unknown payload encoding component bits {:#04x}",
479 components & !ENCODING_MASK_V1
480 )));
481 }
482 let registry_id = if components & ENCODING_HAS_REGISTRY_ID != 0 {
483 Some(self.u32()?)
484 } else {
485 None
486 };
487 let literal_id = if components & ENCODING_HAS_LITERAL_ID != 0 {
488 let len = usize::from(self.u16()?);
489 Some(self.utf8(len, "payload_encoding.literal_id")?)
490 } else {
491 None
492 };
493 let content_type = if components & ENCODING_HAS_CONTENT_TYPE != 0 {
494 let len = usize::from(self.u16()?);
495 Some(self.utf8(len, "payload_encoding.content_type")?)
496 } else {
497 None
498 };
499 PayloadEncoding::from_parts(registry_id, literal_id, content_type)
500 .map_err(UFrameFieldsError::from)
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use pretty_assertions::assert_eq;
507
508 use super::*;
509
510 fn topic() -> UUri {
511 UUri::try_from_parts("vehicle", 0x4210, 0x01, 0x9000).expect("topic")
512 }
513
514 fn method() -> UUri {
515 UUri::try_from_parts("vehicle", 0x4210, 0x01, 0x00b1).expect("method")
516 }
517
518 fn reply_to() -> UUri {
519 UUri::try_from_parts("cloud", 0x10ab, 0x02, 0x0000).expect("reply-to")
520 }
521
522 #[test]
523 fn minimal_publish_metadata_round_trips() {
524 let metadata = UFrameMetadata::publish(topic()).build().expect("metadata");
525 let bytes = encode_frame_metadata_fields(&metadata).expect("encode");
526 let decoded = decode_frame_metadata_fields(&bytes).expect("decode");
527 assert_eq!(decoded, metadata);
528 }
529
530 #[test]
531 fn fully_populated_request_metadata_round_trips() {
532 let metadata = UFrameMetadata::request(method(), reply_to(), Duration::from_millis(250))
533 .with_priority(FramePriority::CS5)
534 .with_comm_status(UCode::Ok)
535 .with_permission_level(4)
536 .with_token("bearer-token")
537 .with_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
538 .with_payload_encoding(
539 PayloadEncoding::custom("up.xcdr-v2", "application/vnd.uprotocol.xcdr-v2")
540 .expect("encoding"),
541 )
542 .build()
543 .expect("metadata");
544
545 let bytes = encode_frame_metadata_fields(&metadata).expect("encode");
546 let decoded = decode_frame_metadata_fields(&bytes).expect("decode");
547 assert_eq!(decoded, metadata);
548 }
549
550 #[test]
551 fn registered_encoding_round_trips_all_components() {
552 let metadata = UFrameMetadata::publish(topic())
553 .with_payload_encoding(PayloadEncoding::PROTOBUF)
554 .build()
555 .expect("metadata");
556 let decoded =
557 decode_frame_metadata_fields(&encode_frame_metadata_fields(&metadata).expect("encode"))
558 .expect("decode");
559 assert_eq!(decoded.payload_encoding(), Some(&PayloadEncoding::PROTOBUF));
560 }
561
562 #[test]
563 fn trailing_bytes_are_rejected() {
564 let metadata = UFrameMetadata::publish(topic()).build().expect("metadata");
565 let mut bytes = encode_frame_metadata_fields(&metadata).expect("encode");
566 bytes.push(0);
567 assert!(matches!(
568 decode_frame_metadata_fields(&bytes),
569 Err(UFrameFieldsError::Malformed(_))
570 ));
571 }
572
573 #[test]
574 fn unknown_presence_bits_are_rejected() {
575 let metadata = UFrameMetadata::publish(topic()).build().expect("metadata");
576 let mut bytes = encode_frame_metadata_fields(&metadata).expect("encode");
577 *bytes.get_mut(5).expect("presence byte") |= 0x01_u8; assert!(matches!(
579 decode_frame_metadata_fields(&bytes),
580 Err(UFrameFieldsError::Malformed(_))
581 ));
582 }
583
584 #[test]
585 fn unsupported_version_is_rejected() {
586 let metadata = UFrameMetadata::publish(topic()).build().expect("metadata");
587 let mut bytes = encode_frame_metadata_fields(&metadata).expect("encode");
588 *bytes.first_mut().expect("version byte") = 2;
589 assert!(matches!(
590 decode_frame_metadata_fields(&bytes),
591 Err(UFrameFieldsError::Malformed(_))
592 ));
593 }
594
595 #[test]
596 fn oversized_traceparent_fails_instead_of_truncating() {
597 let metadata = UFrameMetadata::publish(topic())
598 .with_traceparent("t".repeat(256))
599 .build()
600 .expect("metadata");
601 assert_eq!(
602 encode_frame_metadata_fields(&metadata).unwrap_err(),
603 UFrameFieldsError::ValueTooLong {
604 field: "traceparent"
605 }
606 );
607 }
608}