1use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};
4use std::{ptr, slice};
5
6use glib::translate::*;
7
8use crate::{Caps, Plugin, Rank, TypeFindFactory, TypeFindProbability, ffi};
9
10#[repr(transparent)]
13#[derive(Debug)]
14#[doc(alias = "GstTypeFind")]
15pub struct TypeFind(ffi::GstTypeFind);
16
17pub trait TypeFindImpl {
18 fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]>;
19 fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps);
20 #[doc(alias = "get_length")]
21 fn length(&self) -> Option<u64> {
22 None
23 }
24}
25
26impl TypeFind {
27 #[doc(alias = "gst_type_find_register")]
52 pub fn register<F>(
53 plugin: Option<&Plugin>,
54 name: &str,
55 rank: Rank,
56 extensions: Option<&str>,
57 possible_caps: Option<&Caps>,
58 func: F,
59 ) -> Result<(), glib::error::BoolError>
60 where
61 F: Fn(&mut TypeFind) + Send + Sync + 'static,
62 {
63 skip_assert_initialized!();
64 unsafe {
65 let func: Box<F> = Box::new(func);
66 let func = Box::into_raw(func);
67
68 let res = ffi::gst_type_find_register(
69 plugin.to_glib_none().0,
70 name.to_glib_none().0,
71 rank.into_glib() as u32,
72 Some(type_find_trampoline::<F>),
73 extensions.to_glib_none().0,
74 possible_caps.to_glib_none().0,
75 func as *mut _,
76 Some(type_find_closure_drop::<F>),
77 );
78
79 glib::result_from_gboolean!(res, "Failed to register typefind factory")
80 }
81 }
82
83 #[doc(alias = "gst_type_find_peek")]
84 pub fn peek_var(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
85 assert!(size > 0);
86
87 unsafe {
88 let data = ffi::gst_type_find_peek(&mut self.0, offset, size);
89 if data.is_null() {
90 None
91 } else {
92 Some(slice::from_raw_parts(data, size as usize))
93 }
94 }
95 }
96
97 #[doc(alias = "gst_type_find_peek")]
110 pub fn peek<const S: usize>(&mut self, offset: i64) -> Option<&[u8; S]> {
111 assert!(S <= u32::MAX as usize);
112 assert!(S > 0);
113
114 unsafe {
115 let data = ffi::gst_type_find_peek(&mut self.0, offset, S as u32);
116 if data.is_null() {
117 None
118 } else {
119 Some(&*(data as *const [u8; S]))
120 }
121 }
122 }
123
124 #[doc(alias = "gst_type_find_suggest")]
133 pub fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
134 unsafe {
135 ffi::gst_type_find_suggest(
136 &mut self.0,
137 probability.into_glib() as u32,
138 caps.to_glib_none().0,
139 );
140 }
141 }
142
143 #[doc(alias = "get_length")]
149 #[doc(alias = "gst_type_find_get_length")]
150 pub fn length(&mut self) -> Option<u64> {
151 unsafe {
152 let len = ffi::gst_type_find_get_length(&mut self.0);
153 if len == 0 { None } else { Some(len) }
154 }
155 }
156
157 pub fn as_reader(&mut self) -> TypeFindReader<'_> {
158 TypeFindReader::from(self)
159 }
160}
161
162pub struct TypeFindReader<'a> {
163 buf: &'a mut TypeFind,
164 pos: u64,
165}
166
167impl<'a> TypeFindReader<'a> {
168 pub fn into_typefind(self) -> &'a mut TypeFind {
169 self.buf
170 }
171
172 pub fn as_typefind(&mut self) -> &mut TypeFind {
173 self.buf
174 }
175
176 fn len(&mut self) -> u64 {
177 self.buf.length().unwrap_or(0)
178 }
179}
180
181impl<'a> From<&'a mut TypeFind> for TypeFindReader<'a> {
182 fn from(buf: &'a mut TypeFind) -> Self {
183 skip_assert_initialized!();
184 Self { buf, pos: 0 }
185 }
186}
187
188impl Read for TypeFindReader<'_> {
189 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
190 if self.pos > i64::MAX as u64 {
194 return Ok(0);
195 }
196
197 let max_len = buf.len().min(u32::MAX as usize);
199 if let Some(v) = self.buf.peek_var(self.pos as i64, max_len as u32) {
200 buf[..max_len].copy_from_slice(v);
201 self.pos += max_len as u64;
203 return Ok(max_len);
204 }
205
206 let remaining = match self.len().checked_sub(self.pos) {
208 Some(v) => v,
209 None => return Ok(0),
210 };
211
212 let remaining = remaining.min(u32::MAX as u64) as usize;
214 let max_len = remaining.min(buf.len());
215 if let Some(v) = self.buf.peek_var(self.pos as i64, max_len as u32) {
216 buf[..max_len].copy_from_slice(v);
217 self.pos += max_len as u64;
219 return Ok(max_len);
220 }
221
222 Ok(0)
223 }
224}
225
226impl Seek for TypeFindReader<'_> {
227 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
228 match pos {
229 SeekFrom::Start(v) => {
230 if v >= i64::MAX as u64 {
231 return Err(Error::from(ErrorKind::FileTooLarge));
232 }
233
234 let len = self.len();
235 let v = len.min(v);
236 self.pos = v;
237 }
238 SeekFrom::End(v) => {
239 let Some(v) = self.len().checked_add_signed(v) else {
240 return Err(Error::from(ErrorKind::InvalidInput));
241 };
242 if v >= i64::MAX as u64 {
243 return Err(Error::from(ErrorKind::FileTooLarge));
244 }
245
246 self.pos = v;
247 }
248 SeekFrom::Current(v) => {
249 let Some(v) = self.pos.checked_add_signed(v) else {
250 return Err(Error::from(ErrorKind::InvalidInput));
251 };
252 if v >= i64::MAX as u64 {
253 return Err(Error::from(ErrorKind::FileTooLarge));
254 }
255
256 let len = self.len();
257 let v = len.min(v);
258 self.pos = v;
259 }
260 };
261
262 Ok(self.pos)
263 }
264}
265
266impl TypeFindFactory {
267 #[doc(alias = "gst_type_find_factory_call_function")]
272 pub fn call_function<T: TypeFindImpl + ?Sized>(&self, mut find: &mut T) {
273 unsafe {
274 let find_ptr = &mut find as *mut &mut T as glib::ffi::gpointer;
275 let mut find = ffi::GstTypeFind {
276 peek: Some(type_find_peek::<T>),
277 suggest: Some(type_find_suggest::<T>),
278 data: find_ptr,
279 get_length: Some(type_find_get_length::<T>),
280 _gst_reserved: [ptr::null_mut(); 4],
281 };
282
283 ffi::gst_type_find_factory_call_function(self.to_glib_none().0, &mut find)
284 }
285 }
286}
287
288unsafe extern "C" fn type_find_trampoline<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
289 find: *mut ffi::GstTypeFind,
290 user_data: glib::ffi::gpointer,
291) {
292 unsafe {
293 let func: &F = &*(user_data as *const F);
294
295 let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
296 func(&mut *(find as *mut TypeFind));
297 }));
298
299 if let Err(err) = panic_result {
300 let cause = err
301 .downcast_ref::<&str>()
302 .copied()
303 .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
304 if let Some(cause) = cause {
305 crate::error!(
306 crate::CAT_RUST,
307 "Failed to call typefind function due to panic: {}",
308 cause
309 );
310 } else {
311 crate::error!(
312 crate::CAT_RUST,
313 "Failed to call typefind function due to panic"
314 );
315 }
316 }
317 }
318}
319
320unsafe extern "C" fn type_find_closure_drop<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
321 data: glib::ffi::gpointer,
322) {
323 unsafe {
324 let _ = Box::<F>::from_raw(data as *mut _);
325 }
326}
327
328unsafe extern "C" fn type_find_peek<T: TypeFindImpl + ?Sized>(
329 data: glib::ffi::gpointer,
330 offset: i64,
331 size: u32,
332) -> *const u8 {
333 unsafe {
334 let find = &mut *(data as *mut &mut T);
335
336 let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
337 match find.peek(offset, size) {
338 None => ptr::null(),
339 Some(data) => data.as_ptr(),
340 }
341 }));
342
343 match panic_result {
344 Ok(res) => res,
345 Err(err) => {
346 let cause = err
347 .downcast_ref::<&str>()
348 .copied()
349 .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
350 if let Some(cause) = cause {
351 crate::error!(
352 crate::CAT_RUST,
353 "Failed to call typefind peek function due to panic: {}",
354 cause
355 );
356 } else {
357 crate::error!(
358 crate::CAT_RUST,
359 "Failed to call typefind peek function due to panic"
360 );
361 }
362
363 ptr::null()
364 }
365 }
366 }
367}
368
369unsafe extern "C" fn type_find_suggest<T: TypeFindImpl + ?Sized>(
370 data: glib::ffi::gpointer,
371 probability: u32,
372 caps: *mut ffi::GstCaps,
373) {
374 unsafe {
375 let find = &mut *(data as *mut &mut T);
376
377 let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
378 find.suggest(from_glib(probability as i32), &from_glib_borrow(caps));
379 }));
380
381 if let Err(err) = panic_result {
382 let cause = err
383 .downcast_ref::<&str>()
384 .copied()
385 .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
386 if let Some(cause) = cause {
387 crate::error!(
388 crate::CAT_RUST,
389 "Failed to call typefind suggest function due to panic: {}",
390 cause
391 );
392 } else {
393 crate::error!(
394 crate::CAT_RUST,
395 "Failed to call typefind suggest function due to panic"
396 );
397 }
398 }
399 }
400}
401
402unsafe extern "C" fn type_find_get_length<T: TypeFindImpl + ?Sized>(
403 data: glib::ffi::gpointer,
404) -> u64 {
405 unsafe {
406 let find = &*(data as *mut &mut T);
407
408 let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
409 find.length().unwrap_or(u64::MAX)
410 }));
411
412 match panic_result {
413 Ok(res) => res,
414 Err(err) => {
415 let cause = err
416 .downcast_ref::<&str>()
417 .copied()
418 .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
419 if let Some(cause) = cause {
420 crate::error!(
421 crate::CAT_RUST,
422 "Failed to call typefind length function due to panic: {}",
423 cause
424 );
425 } else {
426 crate::error!(
427 crate::CAT_RUST,
428 "Failed to call typefind length function due to panic"
429 );
430 }
431
432 u64::MAX
433 }
434 }
435 }
436}
437
438#[derive(Debug)]
439pub struct SliceTypeFind<T: AsRef<[u8]>> {
440 pub probability: Option<TypeFindProbability>,
441 pub caps: Option<Caps>,
442 data: T,
443}
444
445impl<T: AsRef<[u8]>> SliceTypeFind<T> {
446 pub fn new(data: T) -> SliceTypeFind<T> {
447 assert_initialized_main_thread!();
448 SliceTypeFind {
449 probability: None,
450 caps: None,
451 data,
452 }
453 }
454
455 pub fn run(&mut self) {
456 let factories = TypeFindFactory::factories();
457
458 for factory in factories {
459 factory.call_function(self);
460 if let Some(prob) = self.probability
461 && prob >= TypeFindProbability::Maximum
462 {
463 break;
464 }
465 }
466 }
467
468 pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) {
469 assert_initialized_main_thread!();
470 let mut t = SliceTypeFind {
471 probability: None,
472 caps: None,
473 data,
474 };
475
476 t.run();
477
478 (t.probability.unwrap_or(TypeFindProbability::None), t.caps)
479 }
480}
481
482impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> {
483 fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
484 let data = self.data.as_ref();
485 let len = data.len();
486
487 let offset = if offset >= 0 {
488 usize::try_from(offset).ok()?
489 } else {
490 let offset = usize::try_from(offset.unsigned_abs()).ok()?;
491 if len < offset {
492 return None;
493 }
494
495 len - offset
496 };
497
498 let size = usize::try_from(size).ok()?;
499 let end_offset = offset.checked_add(size)?;
500 if end_offset <= len {
501 Some(&data[offset..end_offset])
502 } else {
503 None
504 }
505 }
506
507 fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
508 match self.probability {
509 None => {
510 self.probability = Some(probability);
511 self.caps = Some(caps.clone());
512 }
513 Some(old_probability) if old_probability < probability => {
514 self.probability = Some(probability);
515 self.caps = Some(caps.clone());
516 }
517 _ => (),
518 }
519 }
520 fn length(&self) -> Option<u64> {
521 Some(self.data.as_ref().len() as u64)
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_typefind_call_function() {
531 crate::init().unwrap();
532
533 let xml_factory = TypeFindFactory::factories()
534 .into_iter()
535 .find(|f| {
536 f.caps()
537 .map(|c| {
538 c.structure(0)
539 .map(|s| s.name() == "application/xml")
540 .unwrap_or(false)
541 })
542 .unwrap_or(false)
543 })
544 .unwrap();
545
546 let data = b"<?xml version=\"1.0\"?><test>test</test>";
547 let data = &data[..];
548 let mut typefind = SliceTypeFind::new(&data);
549 xml_factory.call_function(&mut typefind);
550
551 assert_eq!(
552 typefind.caps,
553 Some(Caps::builder("application/xml").build())
554 );
555 assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum));
556 }
557
558 #[test]
559 fn test_typefind_register() {
560 crate::init().unwrap();
561
562 TypeFind::register(
563 None,
564 "test_typefind",
565 crate::Rank::PRIMARY,
566 None,
567 Some(&Caps::builder("test/test").build()),
568 |typefind| {
569 assert_eq!(typefind.length(), Some(8));
570 let mut found = false;
571 if let Some(data) = typefind.peek::<8>(0)
572 && data == b"abcdefgh"
573 {
574 found = true;
575 }
576
577 if found {
578 typefind.suggest(
579 TypeFindProbability::Likely,
580 &Caps::builder("test/test").build(),
581 );
582 }
583 },
584 )
585 .unwrap();
586
587 let data = b"abcdefgh";
588 let data = &data[..];
589 let (probability, caps) = SliceTypeFind::type_find(data);
590
591 assert_eq!(caps, Some(Caps::builder("test/test").build()));
592 assert_eq!(probability, TypeFindProbability::Likely);
593 }
594}