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