Skip to main content

zencodec_media/
policy.rs

1//! Apply the shared application policy before publishing native video frames.
2//!
3//! Each `receive` holds the actual decoded frame while the callback inspects
4//! its immutable interpretation and presentation timing. Only successful policy
5//! resolution can return it to a caller or downstream converter. Native decode
6//! has already allocated/reconstructed the frame. Explicit workspace requirements
7//! refuse before any wrapped native operation because this adapter has no qualified
8//! workspace bound. Construction of the supplied decoder happened before wrapping;
9//! this is not an admission boundary for that construction or complete-container
10//! metadata privacy.
11use crate::{
12    frame::Frame,
13    packet::Packet,
14    session::{CodecConfig, Receive, SendStatus, SessionError, VideoDecoder, VideoStreamInfo},
15};
16use alloc::boxed::Box;
17use core::{error::Error as StdError, fmt};
18
19use zencodec::operation::{OperationPolicy, OperationReport, PolicySet, resolve_frame};
20
21/// Which part of the controlled session failed.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum Stage {
25    /// The native decoder failed.
26    Decode,
27    /// The shared policy refused, failed or was cancelled before publication.
28    Policy,
29}
30
31/// Native or application error with the decisions preceding it. Default
32/// formatting excludes native/application error text and metadata payloads.
33pub struct Error {
34    stage: Stage,
35    report: Box<OperationReport>,
36    cause: Box<dyn StdError + Send + Sync>,
37}
38impl Error {
39    fn new(
40        stage: Stage,
41        report: &OperationReport,
42        cause: impl StdError + Send + Sync + 'static,
43    ) -> Self {
44        Self {
45            stage,
46            report: Box::new(report.clone()),
47            cause: Box::new(cause),
48        }
49    }
50    /// The stage that failed.
51    pub const fn stage(&self) -> Stage {
52        self.stage
53    }
54    /// Bounded policy history, including the failed decision when available.
55    pub fn report(&self) -> &OperationReport {
56        &self.report
57    }
58}
59impl fmt::Debug for Error {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.debug_struct("VideoPolicyError")
62            .field("stage", &self.stage)
63            .field("report", &self.report)
64            .finish_non_exhaustive()
65    }
66}
67impl fmt::Display for Error {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        write!(f, "controlled video decode failed during {:?}", self.stage)
70    }
71}
72impl StdError for Error {
73    fn source(&self) -> Option<&(dyn StdError + 'static)> {
74        Some(self.cause.as_ref())
75    }
76}
77
78/// A native decoder with one shared application resolver before every output
79/// presentation. No native frame is published on refusal, cancellation or an
80/// invalid/stale choice. The original frame is returned unchanged on success.
81///
82/// The resolver runs for every presentation, even within the same epoch: policy
83/// may inspect timing, and equal epochs do not prove equal interpretation.
84/// Reports keep the most recent decisions in a fixed bound.
85///
86/// All failures and caught unwinds are terminal and drop the native decoder,
87/// including its queued frames/references. `reset` is available only while the
88/// operation remains healthy; after failure construct a fresh operation. Frames
89/// previously returned remain valid through their own native owners.
90pub struct PolicyDecoder<D, R> {
91    decoder: Option<D>,
92    resolver: R,
93    preset: PolicySet,
94    report: OperationReport,
95}
96impl<D, R> PolicyDecoder<D, R> {
97    /// Wrap a native decoder and a built-in policy, application object or closure.
98    /// Construction performs no decoding or policy decision. Native construction
99    /// already happened; use [`Self::try_new`] to admit it before the factory runs.
100    pub fn new(decoder: D, resolver: R) -> Self {
101        Self {
102            decoder: Some(decoder),
103            resolver,
104            preset: PolicySet::new(),
105            report: OperationReport::default(),
106        }
107    }
108    /// The built-in policy used when this resolver returns `UsePreset`.
109    /// During migration, these requirements and the policy argument
110    /// both apply. A bound policy supplies preferences in place of these.
111    #[deprecated(note = "Pass PolicySet or BoundPolicy as the operation policy argument")]
112    pub fn with_policy_set(mut self, preset: PolicySet) -> Self {
113        self.preset = preset;
114        self
115    }
116    /// Bounded decision history; also readable after terminal failure.
117    pub fn report(&self) -> &OperationReport {
118        &self.report
119    }
120    /// Whether this operation can no longer execute native work.
121    pub const fn is_failed(&self) -> bool {
122        self.decoder.is_none()
123    }
124}
125impl<D: VideoDecoder, R: OperationPolicy> PolicyDecoder<D, R> {
126    /// Check mandatory workspace requirements before constructing the native decoder.
127    ///
128    /// The factory is called once on admission, never on prior refusal/cancellation.
129    /// Cancellation is checked again after successful construction; a cancelled
130    /// decoder is dropped before it can be returned. The factory must poll during
131    /// lengthy work itself; this boundary cannot preempt a synchronous constructor.
132    /// This generic adapter has no qualified workspace bound, so any explicit
133    /// workspace limit refuses even if large. Default policies permit construction.
134    /// The factory must include native preparation; constructing a decoder first
135    /// and moving it into this closure cannot put those earlier effects under admission.
136    /// Frame interpretation and metadata decisions still happen at publication.
137    pub fn try_new<E: StdError + Send + Sync + 'static>(
138        factory: impl FnOnce() -> Result<D, E>,
139        resolver: R,
140        pulse: Option<&dyn zencodec::Pulse>,
141    ) -> Result<Self, Error> {
142        let report = OperationReport::default();
143        pulse
144            .unwrap_or(&zencodec::NoPulse)
145            .check()
146            .map_err(|reason| {
147                Error::new(
148                    Stage::Policy,
149                    &report,
150                    SessionError::<core::convert::Infallible>::Cancelled(reason),
151                )
152            })?;
153        if let Some(required) = resolver.requirements() {
154            required
155                .check_workspace(None)
156                .map_err(|cause| Error::new(Stage::Policy, &report, cause))?;
157        }
158        let decoder = factory().map_err(|cause| Error::new(Stage::Decode, &report, cause))?;
159        pulse
160            .unwrap_or(&zencodec::NoPulse)
161            .check()
162            .map_err(|reason| {
163                Error::new(
164                    Stage::Policy,
165                    &report,
166                    SessionError::<core::convert::Infallible>::Cancelled(reason),
167                )
168            })?;
169        Ok(Self::new(decoder, resolver))
170    }
171    fn run<T>(
172        &mut self,
173        pulse: Option<&dyn zencodec::Pulse>,
174        f: impl FnOnce(
175            &mut D,
176            &mut R,
177            PolicySet,
178            &mut OperationReport,
179        ) -> Result<T, SessionError<Error>>,
180    ) -> Result<T, SessionError<Error>> {
181        // Taking ownership before any application/backend call makes unwinding
182        // terminal too. The native backend drops with its queues on failure.
183        let mut decoder = self.decoder.take().ok_or(SessionError::Finished)?;
184        pulse.unwrap_or(&zencodec::NoPulse).check()?;
185        // A frame's eventual publication cannot discharge earlier native workspace.
186        // Keep both legacy and bound requirements, without inventing route evidence.
187        for required in [Some(self.preset), self.resolver.requirements()]
188            .into_iter()
189            .flatten()
190        {
191            required.check_workspace(None).map_err(|cause| {
192                SessionError::Codec(Error::new(Stage::Policy, &self.report, cause))
193            })?;
194        }
195        let output = f(
196            &mut decoder,
197            &mut self.resolver,
198            self.preset,
199            &mut self.report,
200        )?;
201        self.decoder = Some(decoder);
202        Ok(output)
203    }
204}
205fn native<E: StdError + Send + Sync + 'static>(
206    error: SessionError<E>,
207    report: &OperationReport,
208) -> SessionError<Error> {
209    error.map_codec(|error| Error::new(Stage::Decode, report, error))
210}
211impl<D: VideoDecoder, R: OperationPolicy> VideoDecoder for PolicyDecoder<D, R> {
212    type Error = Error;
213    fn configure(
214        &mut self,
215        config: &CodecConfig,
216        pulse: Option<&dyn zencodec::Pulse>,
217    ) -> Result<(), SessionError<Error>> {
218        self.run(pulse, |decoder, _, _, report| {
219            decoder
220                .configure(config, pulse)
221                .map_err(|e| native(e, report))
222        })
223    }
224    fn send(
225        &mut self,
226        packet: &mut Packet,
227        pulse: Option<&dyn zencodec::Pulse>,
228    ) -> Result<SendStatus, SessionError<Error>> {
229        self.run(pulse, |decoder, _, _, report| {
230            decoder.send(packet, pulse).map_err(|e| native(e, report))
231        })
232    }
233    fn receive(
234        &mut self,
235        pulse: Option<&dyn zencodec::Pulse>,
236    ) -> Result<Receive<Frame>, SessionError<Error>> {
237        self.run(pulse, |decoder, resolver, preset, report| {
238            let output = decoder.receive(pulse).map_err(|e| native(e, report))?;
239            if let Receive::Ready(frame) = &output {
240                match resolve_frame(
241                    frame.interpretation(),
242                    frame.timing(),
243                    resolver,
244                    preset,
245                    pulse,
246                ) {
247                    Ok(record) => report.record(record),
248                    Err(error) => {
249                        report.record(error.error().record());
250                        return Err(SessionError::Codec(Error::new(
251                            Stage::Policy,
252                            report,
253                            error,
254                        )));
255                    }
256                }
257            }
258            Ok(output)
259        })
260    }
261    fn end_input(&mut self) -> Result<(), SessionError<Error>> {
262        self.run(None, |decoder, _, _, report| {
263            decoder.end_input().map_err(|e| native(e, report))
264        })
265    }
266    fn reset(&mut self) -> Result<(), SessionError<Error>> {
267        self.run(None, |decoder, _, _, report| {
268            decoder.reset().map_err(|e| native(e, report))
269        })
270    }
271    fn stream_info(&self) -> Option<&VideoStreamInfo> {
272        self.decoder.as_ref().and_then(VideoDecoder::stream_info)
273    }
274}