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