Skip to main content

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}