dvb_si/owned.rs
1//! Own a parsed view past the input buffer's borrow (feature `yoke`).
2//!
3//! Tables and descriptor loops parse **zero-copy**, borrowing the input section
4//! slice — [`Pmt<'a>`](crate::tables::pmt::Pmt), [`Sdt<'a>`](crate::tables::sdt::Sdt),
5//! [`DescriptorLoop<'a>`](crate::descriptors::DescriptorLoop), and so on. That is
6//! ideal for parse-and-discard, but a consumer that needs to **retain** a parsed
7//! table — stash it in a struct field, a cache, or a `tokio::sync::watch`
8//! channel — would otherwise have to re-parse on every access or maintain a
9//! hand-written owned mirror type.
10//!
11//! [`Owned`] bundles the owned backing bytes (`Arc<[u8]>`) and the borrowing
12//! view into one `'static`, cheaply-`Clone`, `Send + Sync` value via the
13//! [`yoke`] crate. The view's lifetime disappears from your signatures, and no
14//! re-parse happens on access.
15//!
16//! ```
17//! use std::sync::Arc;
18//! use dvb_si::owned::Owned;
19//! use dvb_si::tables::pmt::Pmt;
20//! use dvb_common::Parse;
21//!
22//! # let section: Vec<u8> = dvb_si::owned::doc::pmt_section();
23//! // `section` is the complete PMT section bytes (e.g. straight off the demux).
24//! let bytes: Arc<[u8]> = Arc::from(section);
25//!
26//! // Parse once, keep the result — no borrow of a local buffer escapes.
27//! let owned: Owned<Pmt<'static>> =
28//! Owned::try_new(bytes, |b| Pmt::parse(b))?;
29//!
30//! // The owned value is 'static, so it can live in a struct field…
31//! struct Cache { pmt: Owned<Pmt<'static>> }
32//! let cache = Cache { pmt: owned };
33//!
34//! // …and move across a thread boundary, then read the typed view back out
35//! // with no re-parse.
36//! let handle = std::thread::spawn(move || {
37//! let pmt: &Pmt = cache.pmt.get();
38//! (pmt.program_number, pmt.streams.len())
39//! });
40//! let (program_number, stream_count) = handle.join().unwrap();
41//! assert_eq!(program_number, 1);
42//! assert_eq!(stream_count, 1);
43//! # Ok::<(), dvb_si::error::Error>(())
44//! ```
45
46use std::sync::Arc;
47
48use yoke::trait_hack::YokeTraitHack;
49use yoke::{Yoke, Yokeable};
50
51/// An owned, `'static`, `Send + Sync` bundle of (backing bytes, parsed view).
52///
53/// `Y` is the view type with its lifetime set to `'static` — e.g.
54/// `Owned<Pmt<'static>>`, `Owned<Sdt<'static>>`,
55/// `Owned<DescriptorLoop<'static>>`. The backing buffer is an `Arc<[u8]>`, so
56/// [`Clone`] is a refcount bump and the view is shared, never re-parsed.
57///
58/// Construct one with [`Owned::try_new`] (fallible parse) or [`Owned::new`]
59/// (infallible), then read the borrowing view with [`Owned::get`].
60pub struct Owned<Y: for<'a> Yokeable<'a>> {
61 yoke: Yoke<Y, Arc<[u8]>>,
62}
63
64impl<Y: for<'a> Yokeable<'a>> Owned<Y> {
65 /// Parse `bytes` into a view and bundle the two into an [`Owned`].
66 ///
67 /// `parse` receives a borrow of the backing bytes and returns the borrowing
68 /// view (or an error). The borrow does not escape `parse`: `yoke` re-binds
69 /// the view's lifetime to the `Arc<[u8]>` it now co-owns.
70 ///
71 /// # Errors
72 ///
73 /// Returns `parse`'s error verbatim if parsing fails.
74 pub fn try_new<F, E>(bytes: Arc<[u8]>, parse: F) -> Result<Self, E>
75 where
76 F: for<'a> FnOnce(&'a [u8]) -> Result<<Y as Yokeable<'a>>::Output, E>,
77 {
78 Ok(Self {
79 yoke: Yoke::try_attach_to_cart(bytes, parse)?,
80 })
81 }
82
83 /// Parse `bytes` into a view with an infallible parser and bundle them.
84 pub fn new<F>(bytes: Arc<[u8]>, parse: F) -> Self
85 where
86 F: for<'a> FnOnce(&'a [u8]) -> <Y as Yokeable<'a>>::Output,
87 {
88 Self {
89 yoke: Yoke::attach_to_cart(bytes, parse),
90 }
91 }
92
93 /// Borrow the parsed view. No re-parse; this is a field read.
94 #[must_use]
95 pub fn get(&self) -> &<Y as Yokeable<'_>>::Output {
96 self.yoke.get()
97 }
98
99 /// The backing section bytes the view borrows from.
100 #[must_use]
101 pub fn backing_bytes(&self) -> &[u8] {
102 self.yoke.backing_cart()
103 }
104}
105
106// Cloning is a refcount bump on the `Arc<[u8]>` plus a re-binding of the
107// (already-parsed) view — no re-parse. The bound mirrors yoke's own `Clone`
108// impl for `Yoke<Y, CloneableCart>`.
109impl<Y: for<'a> Yokeable<'a>> Clone for Owned<Y>
110where
111 for<'a> YokeTraitHack<<Y as Yokeable<'a>>::Output>: Clone,
112{
113 fn clone(&self) -> Self {
114 Self {
115 yoke: self.yoke.clone(),
116 }
117 }
118}
119
120impl<Y> std::fmt::Debug for Owned<Y>
121where
122 Y: for<'a> Yokeable<'a>,
123 for<'a> <Y as Yokeable<'a>>::Output: std::fmt::Debug,
124{
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("Owned").field("view", self.get()).finish()
127 }
128}
129
130#[doc(hidden)]
131pub mod doc {
132 //! Hidden helpers for the module-level doctest. Not part of the public API.
133
134 /// Build a minimal one-stream PMT section via the serializer, so the
135 /// doctest is self-contained and actually round-trips through the parser.
136 #[must_use]
137 pub fn pmt_section() -> Vec<u8> {
138 use crate::tables::pmt::{Pmt, PmtStream};
139 use dvb_common::Serialize;
140
141 let pmt = Pmt {
142 program_number: 1,
143 version_number: 0,
144 current_next_indicator: true,
145 pcr_pid: 0x0100,
146 program_info: crate::descriptors::DescriptorLoop::new(&[]),
147 streams: vec![PmtStream {
148 stream_type: 0x1B, // H.264 video
149 elementary_pid: 0x0101,
150 es_info: crate::descriptors::DescriptorLoop::new(&[]),
151 }],
152 };
153 let mut section = vec![0u8; pmt.serialized_len()];
154 pmt.serialize_into(&mut section).unwrap();
155 section
156 }
157}