binaryninja/
transform.rs

1use crate::binary_view::{BinaryReader, BinaryView};
2use crate::data_buffer::DataBuffer;
3use crate::metadata::Metadata;
4use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable};
5use crate::settings::Settings;
6use crate::string::{raw_to_string, strings_to_string_list, BnString, IntoCStr};
7use binaryninjacore_sys::*;
8use std::borrow::Cow;
9use std::collections::HashMap;
10use std::ffi::{c_char, c_void, CStr};
11use std::fmt::{Debug, Formatter};
12use std::mem::ManuallyDrop;
13use std::path::Path;
14
15pub type TransformType = BNTransformType;
16pub type TransformResult = BNTransformResult;
17pub type TransformSessionMode = BNTransformSessionMode;
18
19/// Represents the input parameters for a transform operation, derived from the transforms [`TransformParameterInfo`]s.
20pub type TransformInputParameters = HashMap<String, DataBuffer>;
21
22bitflags::bitflags! {
23    pub struct TransformCapabilities: u32 {
24        const NONE = 0b00000000;
25        const SUPPORTS_DETECTION = 0b00000001;
26        const SUPPORTS_CONTEXT = 0b00000010;
27    }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ProcessResult {
32    /// if processing is incomplete and requires user input (file selection, password),
33    /// additional parameters, or if an error occurred during transformation.
34    Incomplete,
35    /// If processing completed successfully (all transforms applied and no user input required).
36    Complete,
37}
38
39/// Registers a custom transform without detection or context capabilities.
40pub fn register_transform<C: CustomTransform>(custom: C) -> (&'static mut C, Transform) {
41    register_transform_with_callbacks(custom, TransformCapabilities::NONE, None, None)
42}
43
44/// Registers a custom transform with the detection capability.
45pub fn register_transform_with_detection<C>(custom: C) -> (&'static mut C, Transform)
46where
47    C: CustomTransform + DetectionTransform,
48{
49    register_transform_with_callbacks(
50        custom,
51        TransformCapabilities::SUPPORTS_DETECTION,
52        Some(cb_can_decode::<C>),
53        None,
54    )
55}
56
57/// Registers a custom transform with the context capability.
58pub fn register_transform_with_context<C>(custom: C) -> (&'static mut C, Transform)
59where
60    C: CustomTransform + ContextTransform,
61{
62    register_transform_with_callbacks(
63        custom,
64        TransformCapabilities::SUPPORTS_CONTEXT,
65        None,
66        Some(cb_decode_with_context::<C>),
67    )
68}
69
70/// Registers a custom transform with both detection and context capabilities.
71pub fn register_transform_with_detection_and_context<C>(custom: C) -> (&'static mut C, Transform)
72where
73    C: CustomTransform + DetectionTransform + ContextTransform,
74{
75    register_transform_with_callbacks(
76        custom,
77        TransformCapabilities::SUPPORTS_DETECTION | TransformCapabilities::SUPPORTS_CONTEXT,
78        Some(cb_can_decode::<C>),
79        Some(cb_decode_with_context::<C>),
80    )
81}
82
83fn register_transform_with_callbacks<C: CustomTransform>(
84    custom: C,
85    capabilities: TransformCapabilities,
86    can_decode: Option<unsafe extern "C" fn(*mut c_void, *mut BNBinaryView) -> bool>,
87    decode_with_context: Option<
88        unsafe extern "C" fn(
89            *mut c_void,
90            *mut BNTransformContext,
91            *mut BNTransformParameter,
92            usize,
93        ) -> bool,
94    >,
95) -> (&'static mut C, Transform) {
96    let name = C::NAME.to_cstr();
97    let long_name = C::LONG_NAME.to_cstr();
98    let group = C::GROUP.to_cstr();
99    let transform = Box::leak(Box::new(custom));
100    let mut callbacks = BNCustomTransform {
101        context: transform as *mut _ as *mut c_void,
102        getParameters: Some(cb_get_params::<C>),
103        freeParameters: Some(cb_free_params),
104        decode: Some(cb_decode::<C>),
105        encode: Some(cb_encode::<C>),
106        decodeWithContext: decode_with_context,
107        canDecode: can_decode,
108    };
109    let result = unsafe {
110        BNRegisterTransformTypeWithCapabilities(
111            C::TYPE,
112            capabilities.bits(),
113            name.as_ptr(),
114            long_name.as_ptr(),
115            group.as_ptr(),
116            &mut callbacks,
117        )
118    };
119    assert!(
120        !result.is_null(),
121        "Should always be a valid pointer returned"
122    );
123    let core = unsafe { Transform::from_raw(result) };
124    (transform, core)
125}
126
127pub trait CustomTransform {
128    const TYPE: TransformType;
129    const NAME: &'static str;
130    const LONG_NAME: &'static str = Self::NAME;
131    const GROUP: &'static str;
132    const PARAMETERS: &'static [TransformParameterInfo] = &[];
133
134    fn decode(&self, input: &[u8], params: &TransformInputParameters) -> Option<DataBuffer>;
135
136    fn encode(&self, input: &[u8], params: &TransformInputParameters) -> Option<DataBuffer>;
137}
138
139/// A custom transform that can detect whether it can decode an input.
140pub trait DetectionTransform: CustomTransform {
141    fn can_decode(&self, reader: &mut BinaryReader) -> bool;
142}
143
144/// A custom transform that can decode an input within a [`TransformContext`].
145pub trait ContextTransform: CustomTransform {
146    fn decode_within_context(
147        &self,
148        context: &TransformContext,
149        params: &TransformInputParameters,
150    ) -> bool;
151}
152
153/// A [`Transform`] manages the decoding and encoding of data for a given format, such as a cryptographic
154/// routine like AES, or a compression routine like LZMA.
155///
156/// Besides simple routines like the ones above, transforms can also be registered for "container" like
157/// formats like ZIP, these formats are used to encapsulate multiple files or other data, and are operated
158/// on using a [`TransformSession`].
159pub struct Transform {
160    handle: *mut BNTransform,
161}
162
163impl Transform {
164    pub(crate) unsafe fn from_raw(handle: *mut BNTransform) -> Self {
165        assert!(!handle.is_null());
166        Self { handle }
167    }
168
169    pub fn by_name(name: &str) -> Option<Self> {
170        let name = name.to_cstr();
171        let handle = unsafe { BNGetTransformByName(name.as_ptr()) };
172        if handle.is_null() {
173            None
174        } else {
175            Some(unsafe { Self::from_raw(handle) })
176        }
177    }
178
179    pub fn transform_type(&self) -> TransformType {
180        unsafe { BNGetTransformType(self.handle) }
181    }
182
183    pub fn name(&self) -> String {
184        unsafe { BnString::into_string(BNGetTransformName(self.handle)) }
185    }
186
187    pub fn long_name(&self) -> String {
188        unsafe { BnString::into_string(BNGetTransformLongName(self.handle)) }
189    }
190
191    pub fn group(&self) -> String {
192        unsafe { BnString::into_string(BNGetTransformGroup(self.handle)) }
193    }
194
195    pub fn capabilities(&self) -> TransformCapabilities {
196        TransformCapabilities::from_bits_retain(unsafe { BNGetTransformCapabilities(self.handle) })
197    }
198
199    pub fn supports_detection(&self) -> bool {
200        unsafe { BNTransformSupportsDetection(self.handle) }
201    }
202
203    pub fn supports_context(&self) -> bool {
204        unsafe { BNTransformSupportsContext(self.handle) }
205    }
206
207    pub fn params(&self) -> Array<TransformParameterInfo> {
208        let mut count = 0;
209        let list = unsafe { BNGetTransformParameterList(self.handle, &mut count) };
210        unsafe { Array::new(list, count, ()) }
211    }
212
213    pub fn can_decode(&self, input: &BinaryView) -> bool {
214        unsafe { BNCanDecode(self.handle, input.handle) }
215    }
216
217    pub fn decode(&self, input: &[u8], params: &TransformInputParameters) -> Option<DataBuffer> {
218        let buffer = DataBuffer::new(input);
219        let output = DataBuffer::new(&[]);
220        let mut param_list: Vec<BNTransformParameter> = params
221            .iter()
222            .map(|(name, value)| BNTransformParameter {
223                name: BnString::into_raw(BnString::new(name)),
224                value: value.as_raw(),
225            })
226            .collect();
227        let result = unsafe {
228            BNDecode(
229                self.handle,
230                buffer.as_raw(),
231                output.as_raw(),
232                param_list.as_mut_ptr(),
233                param_list.len(),
234            )
235        };
236        for param in param_list {
237            unsafe { BnString::free_raw(param.name as *mut c_char) };
238        }
239        match result {
240            true => Some(output),
241            false => None,
242        }
243    }
244
245    pub fn encode(&self, input: &[u8], params: &TransformInputParameters) -> Option<DataBuffer> {
246        let buffer = DataBuffer::new(input);
247        let output = DataBuffer::new(&[]);
248        let mut param_list: Vec<BNTransformParameter> = params
249            .iter()
250            .map(|(name, value)| BNTransformParameter {
251                name: BnString::into_raw(BnString::new(name)),
252                value: value.as_raw(),
253            })
254            .collect();
255        let result = unsafe {
256            BNEncode(
257                self.handle,
258                buffer.as_raw(),
259                output.as_raw(),
260                param_list.as_mut_ptr(),
261                param_list.len(),
262            )
263        };
264        for param in param_list {
265            unsafe { BnString::free_raw(param.name as *mut c_char) };
266        }
267        match result {
268            true => Some(output),
269            false => None,
270        }
271    }
272
273    pub fn decode_within_context(
274        &self,
275        context: &TransformContext,
276        params: &TransformInputParameters,
277    ) -> bool {
278        let mut param_list: Vec<BNTransformParameter> = params
279            .iter()
280            .map(|(name, value)| BNTransformParameter {
281                name: BnString::into_raw(BnString::new(name)),
282                value: value.as_raw(),
283            })
284            .collect();
285        let result = unsafe {
286            BNDecodeWithContext(
287                self.handle,
288                context.handle,
289                param_list.as_mut_ptr(),
290                param_list.len(),
291            )
292        };
293        match result {
294            true => true,
295            false => false,
296        }
297    }
298}
299
300impl Debug for Transform {
301    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
302        f.debug_struct("Transform")
303            .field("name", &self.name())
304            .field("long_name", &self.long_name())
305            .field("group", &self.group())
306            .field("params", &self.params().to_vec())
307            .finish()
308    }
309}
310
311/// A [`TransformContext`] represents a node in the transform tree, for use in a [`TransformSession`].
312pub struct TransformContext {
313    handle: *mut BNTransformContext,
314}
315
316impl TransformContext {
317    pub(crate) unsafe fn from_raw(handle: *mut BNTransformContext) -> Self {
318        assert!(!handle.is_null());
319        Self { handle }
320    }
321
322    pub(crate) unsafe fn ref_from_raw(handle: *mut BNTransformContext) -> Ref<Self> {
323        Ref::new(Self::from_raw(handle))
324    }
325
326    /// The [`BinaryView`] that this context is associated with.
327    ///
328    /// This contains the data that is to be processed by the selected transform.
329    pub fn input(&self) -> Ref<BinaryView> {
330        unsafe { BinaryView::ref_from_raw(BNTransformContextGetInput(self.handle)) }
331    }
332
333    /// The name associated with this context, typically the name of the file in the case of a container like zip or archive.
334    pub fn name(&self) -> String {
335        unsafe { BnString::into_string(BNTransformContextGetFileName(self.handle)) }
336    }
337
338    /// A list of transforms that can be used to process this context.
339    ///
340    /// The list of available transforms is gathered by checking [`Transform::can_decode`] on all
341    /// the registered transforms.
342    pub fn available_transforms(&self) -> Array<BnString> {
343        let mut count = 0;
344        let transforms_raw =
345            unsafe { BNTransformContextGetAvailableTransforms(self.handle, &mut count) };
346        unsafe { Array::new(transforms_raw, count, ()) }
347    }
348
349    /// The name of the transform that created this context.
350    pub fn transform_name(&self) -> String {
351        unsafe { BnString::into_string(BNTransformContextGetTransformName(self.handle)) }
352    }
353
354    /// Override the [`Transform`] used to process this context.
355    ///
356    /// This is used when automatically selecting a transform is not possible (e.g. no magic bytes)
357    /// or when there is multiple conflicting transforms.
358    ///
359    /// After setting the transform name, process the context with [`TransformSession::process_from`].
360    pub fn set_transform_name(&self, transform_name: &str) {
361        let raw_transform_name = transform_name.to_cstr();
362        unsafe { BNTransformContextSetTransformName(self.handle, raw_transform_name.as_ptr()) }
363    }
364
365    pub fn set_transform_params(&self, params: &TransformInputParameters) {
366        let mut param_list: Vec<BNTransformParameter> = params
367            .iter()
368            .map(|(name, value)| BNTransformParameter {
369                name: BnString::into_raw(BnString::new(name)),
370                value: value.as_raw(),
371            })
372            .collect();
373        unsafe {
374            BNTransformContextSetTransformParameters(
375                self.handle,
376                param_list.as_mut_ptr(),
377                param_list.len(),
378            )
379        };
380        for param in param_list {
381            unsafe { BnString::from_raw(param.name as *mut c_char) };
382        }
383    }
384
385    pub fn set_transform_param(&self, name: &str, value: &DataBuffer) {
386        let raw_name = name.to_cstr();
387        unsafe {
388            BNTransformContextSetTransformParameter(self.handle, raw_name.as_ptr(), value.as_raw())
389        }
390    }
391
392    pub fn has_transform_param(&self, name: &str) -> bool {
393        let raw_name = name.to_cstr();
394        unsafe { BNTransformContextHasTransformParameter(self.handle, raw_name.as_ptr()) }
395    }
396
397    pub fn clear_transform_params(&self, name: &str) {
398        let raw_name = name.to_cstr();
399        unsafe { BNTransformContextClearTransformParameter(self.handle, raw_name.as_ptr()) }
400    }
401
402    /// The message associated with the extraction operation performed by the parent.
403    ///
404    /// Originates from a call to [`TransformContext::create_child`].
405    pub fn extraction_message(&self) -> String {
406        unsafe { BnString::into_string(BNTransformContextGetExtractionMessage(self.handle)) }
407    }
408
409    /// The result associated with the extraction operation performed by the parent.
410    ///
411    /// Originates from a call to [`TransformContext::create_child`].
412    pub fn extraction_result(&self) -> TransformResult {
413        unsafe { BNTransformContextGetExtractionResult(self.handle) }
414    }
415
416    /// The result of applying the transform to the current input.
417    ///
418    /// Can be set with [`TransformContext::set_transform_result`].
419    pub fn transform_result(&self) -> TransformResult {
420        unsafe { BNTransformContextGetTransformResult(self.handle) }
421    }
422
423    /// Set the result of the applied transform on the current input.
424    pub fn set_transform_result(&self, result: TransformResult) {
425        unsafe { BNTransformContextSetTransformResult(self.handle, result) }
426    }
427
428    /// Metadata associated with this context.
429    ///
430    /// Can be accessed by transforms to store format-specific information.
431    pub fn metadata(&self) -> Ref<Metadata> {
432        // SAFETY: The metadata field in the core is always initialized to a key value data type.
433        unsafe { Metadata::ref_from_raw(BNTransformContextGetMetadata(self.handle)) }
434    }
435
436    /// The parent context will be `None` if the context is the root context.
437    pub fn parent(&self) -> Option<Ref<TransformContext>> {
438        let parent_handle = unsafe { BNTransformContextGetParent(self.handle) };
439        if parent_handle.is_null() {
440            None
441        } else {
442            Some(unsafe { TransformContext::ref_from_raw(parent_handle) })
443        }
444    }
445
446    pub fn children(&self) -> Array<TransformContext> {
447        let mut count = 0;
448        let raw = unsafe { BNTransformContextGetChildren(self.handle, &mut count) };
449        unsafe { Array::new(raw, count, ()) }
450    }
451
452    pub fn child_by_name(&self, name: &str) -> Option<Ref<TransformContext>> {
453        let name = name.to_cstr();
454        let child_handle = unsafe { BNTransformContextGetChild(self.handle, name.as_ptr()) };
455        if child_handle.is_null() {
456            None
457        } else {
458            Some(unsafe { TransformContext::ref_from_raw(child_handle) })
459        }
460    }
461
462    /// Create a new child context with the given data.
463    ///
464    /// The child context will be added as a child of this context.
465    ///
466    /// - `data`: The contents of the child context.
467    /// - `extraction_result`: The result of the extraction operation.
468    /// - `extraction_message`: The message associated with the extraction operation.
469    /// - `is_descriptor`: Indicates whether the child context represents a descriptor, if so, the name
470    ///   of the child context when shown will show both the parent name and then the child name ('parent.child').
471    pub fn create_child(
472        &self,
473        name: &str,
474        data: &DataBuffer,
475        extraction_result: TransformResult,
476        extraction_message: &str,
477        is_descriptor: bool,
478    ) -> Ref<TransformContext> {
479        let name = name.to_cstr();
480        let message = extraction_message.to_cstr();
481        let child_handle = unsafe {
482            BNTransformContextSetChild(
483                self.handle,
484                data.as_raw(),
485                name.as_ptr(),
486                extraction_result,
487                message.as_ptr(),
488                is_descriptor,
489            )
490        };
491        assert!(
492            !child_handle.is_null(),
493            "Core always constructs a valid child context handle"
494        );
495        unsafe { TransformContext::ref_from_raw(child_handle) }
496    }
497
498    /// This context has no children.
499    pub fn is_leaf(&self) -> bool {
500        unsafe { BNTransformContextIsLeaf(self.handle) }
501    }
502
503    /// This context is the root of the transform session, it has no parent.
504    pub fn is_root(&self) -> bool {
505        unsafe { BNTransformContextIsRoot(self.handle) }
506    }
507
508    /// The list of available files for extraction.
509    pub fn available_files(&self) -> Array<BnString> {
510        let mut count = 0;
511        let file_list = unsafe { BNTransformContextGetAvailableFiles(self.handle, &mut count) };
512        assert!(
513            !file_list.is_null(),
514            "Core always returns a valid file list"
515        );
516        unsafe { Array::new(file_list, count, ()) }
517    }
518
519    /// Set the list of available files available for extraction.
520    ///
521    /// A [`Transform`] will call this to set the files that are possible to extract, but this won't
522    /// actually mark the files for extraction by the [`TransformSession`]. To mark files for extraction,
523    /// call [`TransformContext::set_requested_files`] _after_ calling this function.
524    pub fn set_available_files(&self, files: &[&str]) {
525        let file_list = strings_to_string_list(files);
526        unsafe { BNTransformContextSetAvailableFiles(self.handle, file_list as _, files.len()) };
527        unsafe { BNFreeStringList(file_list as _, files.len()) };
528    }
529
530    /// The files with which to extract from this context.
531    ///
532    /// This is used in [`TransformSession::process_from`] to actually perform the extraction of the
533    /// child contexts.
534    pub fn requested_files(&self) -> Array<BnString> {
535        let mut count = 0;
536        let file_list = unsafe { BNTransformContextGetRequestedFiles(self.handle, &mut count) };
537        assert!(
538            !file_list.is_null(),
539            "Core always returns a valid file list"
540        );
541        unsafe { Array::new(file_list, count, ()) }
542    }
543
544    /// Specify which files to extract from this context. The associated [`TransformSession`] will then
545    /// extract the specified files with [`TransformSession::process_from`].
546    ///
547    /// To get the list of possible files, use [`TransformContext::available_files`].
548    pub fn set_requested_files(&self, files: &[&str]) {
549        let file_list = strings_to_string_list(files);
550        unsafe { BNTransformContextSetRequestedFiles(self.handle, file_list as _, files.len()) };
551        unsafe { BNFreeStringList(file_list as _, files.len()) };
552    }
553
554    /// If the contents of this context represent a database file (BNDB).
555    pub fn is_database(&self) -> bool {
556        unsafe { BNTransformContextIsDatabase(self.handle) }
557    }
558
559    /// If this context is being used interactively.
560    ///
561    /// Transforms can use this to adjust their behavior. For example, filtering children in non-interactive
562    /// mode while showing all children in interactive mode.
563    pub fn is_interactive(&self) -> bool {
564        unsafe { BNTransformContextIsInteractive(self.handle) }
565    }
566
567    pub fn settings(&self) -> Ref<Settings> {
568        unsafe { Settings::ref_from_raw(BNTransformContextGetSettings(self.handle)) }
569    }
570}
571
572impl ToOwned for TransformContext {
573    type Owned = Ref<Self>;
574
575    fn to_owned(&self) -> Self::Owned {
576        unsafe { RefCountable::inc_ref(self) }
577    }
578}
579
580unsafe impl RefCountable for TransformContext {
581    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
582        Ref::new(Self {
583            handle: BNNewTransformContextReference(handle.handle),
584        })
585    }
586
587    unsafe fn dec_ref(handle: &Self) {
588        BNFreeTransformContext(handle.handle);
589    }
590}
591
592impl CoreArrayProvider for TransformContext {
593    type Raw = *mut BNTransformContext;
594    type Context = ();
595    type Wrapped<'a> = Self;
596}
597
598unsafe impl CoreArrayProviderInner for TransformContext {
599    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
600        unsafe { BNFreeTransformContextList(raw, count) };
601    }
602
603    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
604        Self::from_raw(*raw)
605    }
606}
607
608/// A [`TransformSession`] manages the lifetime of a set of [`TransformContext`]s, performing transformations
609/// on said contexts, and extracting the results.
610///
611/// Sessions automatically apply appropriate transforms to navigate through nested containers, maintaining
612/// a tree of [`TransformContext`] objects representing each extraction stage.
613///
614/// NOTE: All sessions have a [`TransformSessionMode`] that configures the session for one of the below:
615///
616/// - [`TransformSessionMode::TransformSessionModeDisabled`] The session will immediately end processing
617///   and return the root context data as-is.
618/// - [`TransformSessionMode::TransformSessionModeInteractive`] The session will process a context then
619///   require another round of processing to traverse to the next level of contexts, used to progressively
620///   transform the inputs rather than transforming all at once.
621/// - [`TransformSessionMode::TransformSessionModeFull`] The session will exhaust all processable
622///   contexts, or in other words, processing all at once.
623pub struct TransformSession {
624    handle: *mut BNTransformSession,
625}
626
627impl TransformSession {
628    pub(crate) unsafe fn from_raw(handle: *mut BNTransformSession) -> Self {
629        Self { handle }
630    }
631
632    pub(crate) unsafe fn ref_from_raw(handle: *mut BNTransformSession) -> Ref<Self> {
633        unsafe { Ref::new(Self::from_raw(handle)) }
634    }
635
636    /// Creates the new transform session for the given `file_path` on disk.
637    ///
638    /// NOTE: The [`TransformSessionMode`] will be set to the default specified by the `files.container.mode`
639    /// setting in the Binary Ninja global settings (_not_ the settings passed into this function). To
640    /// specify the mode, use [`TransformSession::new_with_mode`] instead.
641    pub fn new(file_path: &Path, settings: &Settings) -> Ref<TransformSession> {
642        let file_path = file_path.to_cstr();
643        let settings = settings.serialize_schema().to_cstr();
644        let session = unsafe { BNCreateTransformSession(file_path.as_ptr(), settings.as_ptr()) };
645        assert!(
646            !session.is_null(),
647            "Transform session should always be valid"
648        );
649        unsafe { Self::ref_from_raw(session) }
650    }
651
652    /// Creates the new transform session for the given `file_path` on disk, with the specified `mode`.
653    pub fn new_with_mode(
654        file_path: &Path,
655        settings: &Settings,
656        mode: TransformSessionMode,
657    ) -> Ref<TransformSession> {
658        let file_path = file_path.to_cstr();
659        let settings = settings.serialize_schema().to_cstr();
660        let session = unsafe {
661            BNCreateTransformSessionWithMode(file_path.as_ptr(), mode, settings.as_ptr())
662        };
663        assert!(
664            !session.is_null(),
665            "Transform session should always be valid"
666        );
667        unsafe { Self::ref_from_raw(session) }
668    }
669
670    /// Creates the new transform session for the given `view`.
671    pub fn from_view(view: &BinaryView, settings: &Settings) -> Ref<TransformSession> {
672        let settings = settings.serialize_schema().to_cstr();
673        let session =
674            unsafe { BNCreateTransformSessionFromBinaryView(view.handle, settings.as_ptr()) };
675        assert!(
676            !session.is_null(),
677            "Transform session should always be valid"
678        );
679        unsafe { Self::ref_from_raw(session) }
680    }
681
682    /// Creates the new transform session for the given `view`.
683    pub fn from_view_with_mode(
684        view: &BinaryView,
685        settings: &Settings,
686        mode: TransformSessionMode,
687    ) -> Ref<TransformSession> {
688        let settings = settings.serialize_schema().to_cstr();
689        let session = unsafe {
690            BNCreateTransformSessionFromBinaryViewWithMode(view.handle, mode, settings.as_ptr())
691        };
692        assert!(
693            !session.is_null(),
694            "Transform session should always be valid"
695        );
696        unsafe { Self::ref_from_raw(session) }
697    }
698
699    /// Creates the new transform session for the given `context`.
700    pub fn from_context(
701        context: &TransformContext,
702        settings: &Settings,
703        mode: TransformSessionMode,
704    ) -> Ref<TransformSession> {
705        let settings = settings.serialize_schema().to_cstr();
706        let session = unsafe {
707            BNCreateTransformSessionFromTransformContextWithMode(
708                context.handle,
709                mode,
710                settings.as_ptr(),
711            )
712        };
713        assert!(
714            !session.is_null(),
715            "Transform session should always be valid"
716        );
717        unsafe { Self::ref_from_raw(session) }
718    }
719
720    pub fn root_context(&self) -> Option<Ref<TransformContext>> {
721        let handle = unsafe { BNTransformSessionGetRootContext(self.handle) };
722        if handle.is_null() {
723            None
724        } else {
725            Some(unsafe { TransformContext::ref_from_raw(handle) })
726        }
727    }
728
729    pub fn current_context(&self) -> Option<Ref<TransformContext>> {
730        let handle = unsafe { BNTransformSessionGetCurrentContext(self.handle) };
731        if handle.is_null() {
732            None
733        } else {
734            Some(unsafe { TransformContext::ref_from_raw(handle) })
735        }
736    }
737
738    /// Process starting at the [`TransformSession::root_context`].
739    ///
740    /// See [`TransformSession`] for more information on how the different [`TransformSessionMode`]s affect processing.
741    pub fn process(&self) -> ProcessResult {
742        match unsafe { BNTransformSessionProcess(self.handle) } {
743            false => ProcessResult::Incomplete,
744            true => ProcessResult::Complete,
745        }
746    }
747
748    /// Process starting at the given `context`.
749    ///
750    /// See [`TransformSession`] for more information on how the different [`TransformSessionMode`]s affect processing.
751    pub fn process_from(&self, context: &TransformContext) -> ProcessResult {
752        match unsafe { BNTransformSessionProcessFrom(self.handle, context.handle) } {
753            false => ProcessResult::Incomplete,
754            true => ProcessResult::Complete,
755        }
756    }
757
758    /// Is there anything in the session to process?
759    ///
760    /// Checks the root context to see if it has any possible transforms, if it does not, then that
761    /// means the session will have nothing to process.
762    pub fn has_any_stages(&self) -> bool {
763        unsafe { BNTransformSessionHasAnyStages(self.handle) }
764    }
765
766    /// If the tree has a single linear path, meaning that there is only one possible sequence of contexts to process.
767    pub fn has_single_path(&self) -> bool {
768        unsafe { BNTransformSessionHasSinglePath(self.handle) }
769    }
770
771    /// Selected contexts are the contexts that will be loaded for analysis.
772    ///
773    /// Use [`TransformSession::set_selected_contexts`] to specify the contexts which to keep for further analysis.
774    pub fn selected_contexts(&self) -> Array<TransformContext> {
775        let mut count = 0;
776        let handle = unsafe { BNTransformSessionGetSelectedContexts(self.handle, &mut count) };
777        unsafe { Array::new(handle, count, ()) }
778    }
779
780    /// Specifies the contexts which will be loaded for analysis, anything not in this list will be
781    /// considered temporary and will be discarded.
782    pub fn set_selected_contexts(&self, contexts: &[Ref<TransformContext>]) {
783        let mut contexts: Vec<*mut BNTransformContext> =
784            contexts.iter().map(|c| c.handle).collect();
785        unsafe {
786            BNTransformSessionSetSelectedContexts(
787                self.handle,
788                contexts.as_mut_ptr(),
789                contexts.len(),
790            )
791        }
792    }
793}
794
795impl ToOwned for TransformSession {
796    type Owned = Ref<Self>;
797
798    fn to_owned(&self) -> Self::Owned {
799        unsafe { RefCountable::inc_ref(self) }
800    }
801}
802
803unsafe impl RefCountable for TransformSession {
804    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
805        Ref::new(Self {
806            handle: BNNewTransformSessionReference(handle.handle),
807        })
808    }
809
810    unsafe fn dec_ref(handle: &Self) {
811        BNFreeTransformSession(handle.handle);
812    }
813}
814
815#[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
816pub struct TransformParameterInfo {
817    pub name: Cow<'static, str>,
818    pub long_name: Cow<'static, str>,
819    pub length: Option<usize>,
820}
821
822impl TransformParameterInfo {
823    pub fn new(name: &'static str, length: Option<usize>) -> Self {
824        Self {
825            name: Cow::Borrowed(name),
826            long_name: Cow::Borrowed(name),
827            length,
828        }
829    }
830
831    pub fn new_with_long(
832        name: &'static str,
833        long_name: &'static str,
834        length: Option<usize>,
835    ) -> Self {
836        Self {
837            name: Cow::Borrowed(name),
838            long_name: Cow::Borrowed(long_name),
839            length,
840        }
841    }
842
843    pub fn from_raw(value: BNTransformParameterInfo) -> Self {
844        Self {
845            name: Cow::Owned(raw_to_string(value.name).unwrap_or_default()),
846            long_name: Cow::Owned(raw_to_string(value.longName).unwrap_or_default()),
847            length: Some(value.fixedLength),
848        }
849    }
850
851    pub fn into_raw(self) -> BNTransformParameterInfo {
852        BNTransformParameterInfo {
853            name: BnString::into_raw(BnString::new(self.name)),
854            longName: BnString::into_raw(BnString::new(self.long_name)),
855            fixedLength: self.length.unwrap_or(0),
856        }
857    }
858
859    pub fn free_raw(value: BNTransformParameterInfo) {
860        unsafe {
861            BnString::free_raw(value.name);
862            BnString::free_raw(value.longName);
863        }
864    }
865}
866
867impl CoreArrayProvider for TransformParameterInfo {
868    type Raw = BNTransformParameterInfo;
869    type Context = ();
870    type Wrapped<'a> = Self;
871}
872
873unsafe impl CoreArrayProviderInner for TransformParameterInfo {
874    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
875        unsafe { BNFreeTransformParameterList(raw, count) };
876    }
877
878    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
879        Self::from_raw(*raw)
880    }
881}
882
883unsafe extern "C" fn cb_get_params<C: CustomTransform>(
884    _ctxt: *mut c_void,
885    count: *mut usize,
886) -> *mut BNTransformParameterInfo {
887    let boxed_params: Box<[_]> = C::PARAMETERS
888        .iter()
889        .cloned()
890        .map(TransformParameterInfo::into_raw)
891        .collect();
892    let leaked_params = Box::leak(boxed_params);
893    *count = leaked_params.len();
894    leaked_params as *mut _ as *mut BNTransformParameterInfo
895}
896
897unsafe extern "C" fn cb_free_params(params: *mut BNTransformParameterInfo, count: usize) {
898    let params = Box::from_raw(std::ptr::slice_from_raw_parts_mut(params, count));
899    for param in params {
900        TransformParameterInfo::free_raw(param);
901    }
902}
903
904unsafe extern "C" fn cb_decode<C: CustomTransform>(
905    ctxt: *mut c_void,
906    input: *mut BNDataBuffer,
907    output: *mut BNDataBuffer,
908    params: *mut BNTransformParameter,
909    param_count: usize,
910) -> bool {
911    let ctxt = ctxt as *mut C;
912    let raw_params = core::slice::from_raw_parts(params, param_count);
913    let params: TransformInputParameters = raw_params
914        .iter()
915        .map(|p| {
916            let name = CStr::from_ptr(p.name).to_string_lossy();
917            // We do not own the buffer, clone it and then make sure not to drop the original.
918            let buffer = DataBuffer::from_raw(p.value);
919            let owned_buffer = buffer.clone();
920            std::mem::forget(buffer);
921            (name.to_string(), owned_buffer)
922        })
923        .collect();
924    let input = ManuallyDrop::new(DataBuffer::from_raw(input));
925    let mut output = ManuallyDrop::new(DataBuffer::from_raw(output));
926    match (*ctxt).decode(input.as_ref(), &params) {
927        Some(buffer) => {
928            output.set_data(buffer.as_ref());
929            true
930        }
931        None => false,
932    }
933}
934
935unsafe extern "C" fn cb_encode<C: CustomTransform>(
936    ctxt: *mut c_void,
937    input: *mut BNDataBuffer,
938    output: *mut BNDataBuffer,
939    params: *mut BNTransformParameter,
940    param_count: usize,
941) -> bool {
942    let ctxt = ctxt as *mut C;
943    let raw_params = core::slice::from_raw_parts(params, param_count);
944    let params: TransformInputParameters = raw_params
945        .iter()
946        .map(|p| {
947            let name = CStr::from_ptr(p.name).to_string_lossy();
948            // We do not own the buffer, clone it and then make sure not to drop the original.
949            let buffer = DataBuffer::from_raw(p.value);
950            let owned_buffer = buffer.clone();
951            std::mem::forget(buffer);
952            (name.to_string(), owned_buffer)
953        })
954        .collect();
955    let input = ManuallyDrop::new(DataBuffer::from_raw(input));
956    let mut output = ManuallyDrop::new(DataBuffer::from_raw(output));
957    match (*ctxt).encode(input.as_ref(), &params) {
958        Some(buffer) => {
959            output.set_data(buffer.as_ref());
960            true
961        }
962        None => false,
963    }
964}
965
966unsafe extern "C" fn cb_decode_with_context<C: CustomTransform + ContextTransform>(
967    ctxt: *mut c_void,
968    input: *mut BNTransformContext,
969    params: *mut BNTransformParameter,
970    param_count: usize,
971) -> bool {
972    let ctxt = ctxt as *mut C;
973    let raw_params = core::slice::from_raw_parts(params, param_count);
974    let params: TransformInputParameters = raw_params
975        .iter()
976        .map(|p| {
977            let name = CStr::from_ptr(p.name).to_string_lossy();
978            // We do not own the buffer, clone it and then make sure not to drop the original.
979            let buffer = DataBuffer::from_raw(p.value);
980            let owned_buffer = buffer.clone();
981            std::mem::forget(buffer);
982            (name.to_string(), owned_buffer)
983        })
984        .collect();
985    let input = TransformContext::from_raw(input);
986    (*ctxt).decode_within_context(&input, &params)
987}
988
989unsafe extern "C" fn cb_can_decode<C: CustomTransform + DetectionTransform>(
990    ctxt: *mut c_void,
991    input: *mut BNBinaryView,
992) -> bool {
993    let ctxt = ctxt as *mut C;
994    let input = BinaryView::from_raw(input);
995    let mut reader = BinaryReader::new(input.as_ref());
996    (*ctxt).can_decode(&mut reader)
997}