1use 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}