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