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