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::{Caps, Plugin, Rank, TypeFindFactory, TypeFindProbability, ffi};
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 { None } else { Some(len) }
87        }
88    }
89}
90
91impl TypeFindFactory {
92    #[doc(alias = "gst_type_find_factory_call_function")]
93    pub fn call_function<T: TypeFindImpl + ?Sized>(&self, mut find: &mut T) {
94        unsafe {
95            let find_ptr = &mut find as *mut &mut T as glib::ffi::gpointer;
96            let mut find = ffi::GstTypeFind {
97                peek: Some(type_find_peek::<T>),
98                suggest: Some(type_find_suggest::<T>),
99                data: find_ptr,
100                get_length: Some(type_find_get_length::<T>),
101                _gst_reserved: [ptr::null_mut(); 4],
102            };
103
104            ffi::gst_type_find_factory_call_function(self.to_glib_none().0, &mut find)
105        }
106    }
107}
108
109unsafe extern "C" fn type_find_trampoline<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
110    find: *mut ffi::GstTypeFind,
111    user_data: glib::ffi::gpointer,
112) {
113    unsafe {
114        let func: &F = &*(user_data as *const F);
115
116        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
117            func(&mut *(find as *mut TypeFind));
118        }));
119
120        if let Err(err) = panic_result {
121            let cause = err
122                .downcast_ref::<&str>()
123                .copied()
124                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
125            if let Some(cause) = cause {
126                crate::error!(
127                    crate::CAT_RUST,
128                    "Failed to call typefind function due to panic: {}",
129                    cause
130                );
131            } else {
132                crate::error!(
133                    crate::CAT_RUST,
134                    "Failed to call typefind function due to panic"
135                );
136            }
137        }
138    }
139}
140
141unsafe extern "C" fn type_find_closure_drop<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
142    data: glib::ffi::gpointer,
143) {
144    unsafe {
145        let _ = Box::<F>::from_raw(data as *mut _);
146    }
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    unsafe {
155        let find = &mut *(data as *mut &mut T);
156
157        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
158            match find.peek(offset, size) {
159                None => ptr::null(),
160                Some(data) => data.as_ptr(),
161            }
162        }));
163
164        match panic_result {
165            Ok(res) => res,
166            Err(err) => {
167                let cause = err
168                    .downcast_ref::<&str>()
169                    .copied()
170                    .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
171                if let Some(cause) = cause {
172                    crate::error!(
173                        crate::CAT_RUST,
174                        "Failed to call typefind peek function due to panic: {}",
175                        cause
176                    );
177                } else {
178                    crate::error!(
179                        crate::CAT_RUST,
180                        "Failed to call typefind peek function due to panic"
181                    );
182                }
183
184                ptr::null()
185            }
186        }
187    }
188}
189
190unsafe extern "C" fn type_find_suggest<T: TypeFindImpl + ?Sized>(
191    data: glib::ffi::gpointer,
192    probability: u32,
193    caps: *mut ffi::GstCaps,
194) {
195    unsafe {
196        let find = &mut *(data as *mut &mut T);
197
198        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
199            find.suggest(from_glib(probability as i32), &from_glib_borrow(caps));
200        }));
201
202        if let Err(err) = panic_result {
203            let cause = err
204                .downcast_ref::<&str>()
205                .copied()
206                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
207            if let Some(cause) = cause {
208                crate::error!(
209                    crate::CAT_RUST,
210                    "Failed to call typefind suggest function due to panic: {}",
211                    cause
212                );
213            } else {
214                crate::error!(
215                    crate::CAT_RUST,
216                    "Failed to call typefind suggest function due to panic"
217                );
218            }
219        }
220    }
221}
222
223unsafe extern "C" fn type_find_get_length<T: TypeFindImpl + ?Sized>(
224    data: glib::ffi::gpointer,
225) -> u64 {
226    unsafe {
227        let find = &*(data as *mut &mut T);
228
229        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
230            find.length().unwrap_or(u64::MAX)
231        }));
232
233        match panic_result {
234            Ok(res) => res,
235            Err(err) => {
236                let cause = err
237                    .downcast_ref::<&str>()
238                    .copied()
239                    .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
240                if let Some(cause) = cause {
241                    crate::error!(
242                        crate::CAT_RUST,
243                        "Failed to call typefind length function due to panic: {}",
244                        cause
245                    );
246                } else {
247                    crate::error!(
248                        crate::CAT_RUST,
249                        "Failed to call typefind length function due to panic"
250                    );
251                }
252
253                u64::MAX
254            }
255        }
256    }
257}
258
259#[derive(Debug)]
260pub struct SliceTypeFind<T: AsRef<[u8]>> {
261    pub probability: Option<TypeFindProbability>,
262    pub caps: Option<Caps>,
263    data: T,
264}
265
266impl<T: AsRef<[u8]>> SliceTypeFind<T> {
267    pub fn new(data: T) -> SliceTypeFind<T> {
268        assert_initialized_main_thread!();
269        SliceTypeFind {
270            probability: None,
271            caps: None,
272            data,
273        }
274    }
275
276    pub fn run(&mut self) {
277        let factories = TypeFindFactory::factories();
278
279        for factory in factories {
280            factory.call_function(self);
281            if let Some(prob) = self.probability
282                && prob >= TypeFindProbability::Maximum
283            {
284                break;
285            }
286        }
287    }
288
289    pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) {
290        assert_initialized_main_thread!();
291        let mut t = SliceTypeFind {
292            probability: None,
293            caps: None,
294            data,
295        };
296
297        t.run();
298
299        (t.probability.unwrap_or(TypeFindProbability::None), t.caps)
300    }
301}
302
303impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> {
304    fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
305        let data = self.data.as_ref();
306        let len = data.len();
307
308        let offset = if offset >= 0 {
309            usize::try_from(offset).ok()?
310        } else {
311            let offset = usize::try_from(offset.unsigned_abs()).ok()?;
312            if len < offset {
313                return None;
314            }
315
316            len - offset
317        };
318
319        let size = usize::try_from(size).ok()?;
320        let end_offset = offset.checked_add(size)?;
321        if end_offset <= len {
322            Some(&data[offset..end_offset])
323        } else {
324            None
325        }
326    }
327
328    fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
329        match self.probability {
330            None => {
331                self.probability = Some(probability);
332                self.caps = Some(caps.clone());
333            }
334            Some(old_probability) if old_probability < probability => {
335                self.probability = Some(probability);
336                self.caps = Some(caps.clone());
337            }
338            _ => (),
339        }
340    }
341    fn length(&self) -> Option<u64> {
342        Some(self.data.as_ref().len() as u64)
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_typefind_call_function() {
352        crate::init().unwrap();
353
354        let xml_factory = TypeFindFactory::factories()
355            .into_iter()
356            .find(|f| {
357                f.caps()
358                    .map(|c| {
359                        c.structure(0)
360                            .map(|s| s.name() == "application/xml")
361                            .unwrap_or(false)
362                    })
363                    .unwrap_or(false)
364            })
365            .unwrap();
366
367        let data = b"<?xml version=\"1.0\"?><test>test</test>";
368        let data = &data[..];
369        let mut typefind = SliceTypeFind::new(&data);
370        xml_factory.call_function(&mut typefind);
371
372        assert_eq!(
373            typefind.caps,
374            Some(Caps::builder("application/xml").build())
375        );
376        assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum));
377    }
378
379    #[test]
380    fn test_typefind_register() {
381        crate::init().unwrap();
382
383        TypeFind::register(
384            None,
385            "test_typefind",
386            crate::Rank::PRIMARY,
387            None,
388            Some(&Caps::builder("test/test").build()),
389            |typefind| {
390                assert_eq!(typefind.length(), Some(8));
391                let mut found = false;
392                if let Some(data) = typefind.peek(0, 8)
393                    && data == b"abcdefgh"
394                {
395                    found = true;
396                }
397
398                if found {
399                    typefind.suggest(
400                        TypeFindProbability::Likely,
401                        &Caps::builder("test/test").build(),
402                    );
403                }
404            },
405        )
406        .unwrap();
407
408        let data = b"abcdefgh";
409        let data = &data[..];
410        let (probability, caps) = SliceTypeFind::type_find(data);
411
412        assert_eq!(caps, Some(Caps::builder("test/test").build()));
413        assert_eq!(probability, TypeFindProbability::Likely);
414    }
415}