gstreamer/
typefind.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{ptr, slice};
4
5use glib::translate::*;
6
7use crate::{ffi, Caps, Plugin, Rank, TypeFindFactory, TypeFindProbability};
8
9#[repr(transparent)]
10#[derive(Debug)]
11#[doc(alias = "GstTypeFind")]
12pub struct TypeFind(ffi::GstTypeFind);
13
14pub trait TypeFindImpl {
15    fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]>;
16    fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps);
17    #[doc(alias = "get_length")]
18    fn length(&self) -> Option<u64> {
19        None
20    }
21}
22
23impl TypeFind {
24    #[doc(alias = "gst_type_find_register")]
25    pub fn register<F>(
26        plugin: Option<&Plugin>,
27        name: &str,
28        rank: Rank,
29        extensions: Option<&str>,
30        possible_caps: Option<&Caps>,
31        func: F,
32    ) -> Result<(), glib::error::BoolError>
33    where
34        F: Fn(&mut TypeFind) + Send + Sync + 'static,
35    {
36        skip_assert_initialized!();
37        unsafe {
38            let func: Box<F> = Box::new(func);
39            let func = Box::into_raw(func);
40
41            let res = ffi::gst_type_find_register(
42                plugin.to_glib_none().0,
43                name.to_glib_none().0,
44                rank.into_glib() as u32,
45                Some(type_find_trampoline::<F>),
46                extensions.to_glib_none().0,
47                possible_caps.to_glib_none().0,
48                func as *mut _,
49                Some(type_find_closure_drop::<F>),
50            );
51
52            glib::result_from_gboolean!(res, "Failed to register typefind factory")
53        }
54    }
55
56    #[doc(alias = "gst_type_find_peek")]
57    pub fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
58        unsafe {
59            let data = ffi::gst_type_find_peek(&mut self.0, offset, size);
60            if data.is_null() {
61                None
62            } else if size == 0 {
63                Some(&[])
64            } else {
65                Some(slice::from_raw_parts(data, size as usize))
66            }
67        }
68    }
69
70    #[doc(alias = "gst_type_find_suggest")]
71    pub fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
72        unsafe {
73            ffi::gst_type_find_suggest(
74                &mut self.0,
75                probability.into_glib() as u32,
76                caps.to_glib_none().0,
77            );
78        }
79    }
80
81    #[doc(alias = "get_length")]
82    #[doc(alias = "gst_type_find_get_length")]
83    pub fn length(&mut self) -> Option<u64> {
84        unsafe {
85            let len = ffi::gst_type_find_get_length(&mut self.0);
86            if len == 0 {
87                None
88            } else {
89                Some(len)
90            }
91        }
92    }
93}
94
95impl TypeFindFactory {
96    #[doc(alias = "gst_type_find_factory_call_function")]
97    pub fn call_function<T: TypeFindImpl + ?Sized>(&self, mut find: &mut T) {
98        unsafe {
99            let find_ptr = &mut find as *mut &mut T as glib::ffi::gpointer;
100            let mut find = ffi::GstTypeFind {
101                peek: Some(type_find_peek::<T>),
102                suggest: Some(type_find_suggest::<T>),
103                data: find_ptr,
104                get_length: Some(type_find_get_length::<T>),
105                _gst_reserved: [ptr::null_mut(); 4],
106            };
107
108            ffi::gst_type_find_factory_call_function(self.to_glib_none().0, &mut find)
109        }
110    }
111}
112
113unsafe extern "C" fn type_find_trampoline<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
114    find: *mut ffi::GstTypeFind,
115    user_data: glib::ffi::gpointer,
116) {
117    let func: &F = &*(user_data as *const F);
118
119    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
120        func(&mut *(find as *mut TypeFind));
121    }));
122
123    if let Err(err) = panic_result {
124        let cause = err
125            .downcast_ref::<&str>()
126            .copied()
127            .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
128        if let Some(cause) = cause {
129            crate::error!(
130                crate::CAT_RUST,
131                "Failed to call typefind function due to panic: {}",
132                cause
133            );
134        } else {
135            crate::error!(
136                crate::CAT_RUST,
137                "Failed to call typefind function due to panic"
138            );
139        }
140    }
141}
142
143unsafe extern "C" fn type_find_closure_drop<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
144    data: glib::ffi::gpointer,
145) {
146    let _ = Box::<F>::from_raw(data as *mut _);
147}
148
149unsafe extern "C" fn type_find_peek<T: TypeFindImpl + ?Sized>(
150    data: glib::ffi::gpointer,
151    offset: i64,
152    size: u32,
153) -> *const u8 {
154    let find = &mut *(data as *mut &mut T);
155
156    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
157        match find.peek(offset, size) {
158            None => ptr::null(),
159            Some(data) => data.as_ptr(),
160        }
161    }));
162
163    match panic_result {
164        Ok(res) => res,
165        Err(err) => {
166            let cause = err
167                .downcast_ref::<&str>()
168                .copied()
169                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
170            if let Some(cause) = cause {
171                crate::error!(
172                    crate::CAT_RUST,
173                    "Failed to call typefind peek function due to panic: {}",
174                    cause
175                );
176            } else {
177                crate::error!(
178                    crate::CAT_RUST,
179                    "Failed to call typefind peek function due to panic"
180                );
181            }
182
183            ptr::null()
184        }
185    }
186}
187
188unsafe extern "C" fn type_find_suggest<T: TypeFindImpl + ?Sized>(
189    data: glib::ffi::gpointer,
190    probability: u32,
191    caps: *mut ffi::GstCaps,
192) {
193    let find = &mut *(data as *mut &mut T);
194
195    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
196        find.suggest(from_glib(probability as i32), &from_glib_borrow(caps));
197    }));
198
199    if let Err(err) = panic_result {
200        let cause = err
201            .downcast_ref::<&str>()
202            .copied()
203            .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
204        if let Some(cause) = cause {
205            crate::error!(
206                crate::CAT_RUST,
207                "Failed to call typefind suggest function due to panic: {}",
208                cause
209            );
210        } else {
211            crate::error!(
212                crate::CAT_RUST,
213                "Failed to call typefind suggest function due to panic"
214            );
215        }
216    }
217}
218
219unsafe extern "C" fn type_find_get_length<T: TypeFindImpl + ?Sized>(
220    data: glib::ffi::gpointer,
221) -> u64 {
222    let find = &*(data as *mut &mut T);
223
224    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
225        find.length().unwrap_or(u64::MAX)
226    }));
227
228    match panic_result {
229        Ok(res) => res,
230        Err(err) => {
231            let cause = err
232                .downcast_ref::<&str>()
233                .copied()
234                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
235            if let Some(cause) = cause {
236                crate::error!(
237                    crate::CAT_RUST,
238                    "Failed to call typefind length function due to panic: {}",
239                    cause
240                );
241            } else {
242                crate::error!(
243                    crate::CAT_RUST,
244                    "Failed to call typefind length function due to panic"
245                );
246            }
247
248            u64::MAX
249        }
250    }
251}
252
253#[derive(Debug)]
254pub struct SliceTypeFind<T: AsRef<[u8]>> {
255    pub probability: Option<TypeFindProbability>,
256    pub caps: Option<Caps>,
257    data: T,
258}
259
260impl<T: AsRef<[u8]>> SliceTypeFind<T> {
261    pub fn new(data: T) -> SliceTypeFind<T> {
262        assert_initialized_main_thread!();
263        SliceTypeFind {
264            probability: None,
265            caps: None,
266            data,
267        }
268    }
269
270    pub fn run(&mut self) {
271        let factories = TypeFindFactory::factories();
272
273        for factory in factories {
274            factory.call_function(self);
275            if let Some(prob) = self.probability {
276                if prob >= TypeFindProbability::Maximum {
277                    break;
278                }
279            }
280        }
281    }
282
283    pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) {
284        assert_initialized_main_thread!();
285        let mut t = SliceTypeFind {
286            probability: None,
287            caps: None,
288            data,
289        };
290
291        t.run();
292
293        (t.probability.unwrap_or(TypeFindProbability::None), t.caps)
294    }
295}
296
297impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> {
298    fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
299        let data = self.data.as_ref();
300        let len = data.len();
301
302        let offset = if offset >= 0 {
303            usize::try_from(offset).ok()?
304        } else {
305            let offset = usize::try_from(offset.unsigned_abs()).ok()?;
306            if len < offset {
307                return None;
308            }
309
310            len - offset
311        };
312
313        let size = usize::try_from(size).ok()?;
314        let end_offset = offset.checked_add(size)?;
315        if end_offset <= len {
316            Some(&data[offset..end_offset])
317        } else {
318            None
319        }
320    }
321
322    fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
323        match self.probability {
324            None => {
325                self.probability = Some(probability);
326                self.caps = Some(caps.clone());
327            }
328            Some(old_probability) if old_probability < probability => {
329                self.probability = Some(probability);
330                self.caps = Some(caps.clone());
331            }
332            _ => (),
333        }
334    }
335    fn length(&self) -> Option<u64> {
336        Some(self.data.as_ref().len() as u64)
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343
344    #[test]
345    fn test_typefind_call_function() {
346        crate::init().unwrap();
347
348        let xml_factory = TypeFindFactory::factories()
349            .into_iter()
350            .find(|f| {
351                f.caps()
352                    .map(|c| {
353                        c.structure(0)
354                            .map(|s| s.name() == "application/xml")
355                            .unwrap_or(false)
356                    })
357                    .unwrap_or(false)
358            })
359            .unwrap();
360
361        let data = b"<?xml version=\"1.0\"?><test>test</test>";
362        let data = &data[..];
363        let mut typefind = SliceTypeFind::new(&data);
364        xml_factory.call_function(&mut typefind);
365
366        assert_eq!(
367            typefind.caps,
368            Some(Caps::builder("application/xml").build())
369        );
370        assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum));
371    }
372
373    #[test]
374    fn test_typefind_register() {
375        crate::init().unwrap();
376
377        TypeFind::register(
378            None,
379            "test_typefind",
380            crate::Rank::PRIMARY,
381            None,
382            Some(&Caps::builder("test/test").build()),
383            |typefind| {
384                assert_eq!(typefind.length(), Some(8));
385                let mut found = false;
386                if let Some(data) = typefind.peek(0, 8) {
387                    if data == b"abcdefgh" {
388                        found = true;
389                    }
390                }
391
392                if found {
393                    typefind.suggest(
394                        TypeFindProbability::Likely,
395                        &Caps::builder("test/test").build(),
396                    );
397                }
398            },
399        )
400        .unwrap();
401
402        let data = b"abcdefgh";
403        let data = &data[..];
404        let (probability, caps) = SliceTypeFind::type_find(data);
405
406        assert_eq!(caps, Some(Caps::builder("test/test").build()));
407        assert_eq!(probability, TypeFindProbability::Likely);
408    }
409}