binaryninja/
workflow.rs

1use binaryninjacore_sys::*;
2
3// Needed for documentation.
4#[allow(unused)]
5use crate::binary_view::{memory_map::MemoryMap, BinaryViewBase, BinaryViewExt};
6
7use crate::basic_block::BasicBlock;
8use crate::binary_view::{AddressRange, BinaryView};
9use crate::flowgraph::FlowGraph;
10use crate::function::{Function, NativeBlock};
11use crate::high_level_il::HighLevelILFunction;
12use crate::low_level_il::{LowLevelILMutableFunction, LowLevelILRegularFunction};
13use crate::medium_level_il::MediumLevelILFunction;
14use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
15use crate::section::Section;
16use crate::segment::{Segment, SegmentFlags};
17use crate::string::{BnString, IntoCStr};
18use std::ffi::c_char;
19use std::ptr;
20use std::ptr::NonNull;
21
22pub mod activity;
23pub use activity::Activity;
24
25#[repr(transparent)]
26/// The AnalysisContext struct is used to represent the current state of
27/// analysis for a given function. It allows direct modification of IL and other
28/// analysis information.
29pub struct AnalysisContext {
30    handle: NonNull<BNAnalysisContext>,
31}
32
33impl AnalysisContext {
34    pub(crate) unsafe fn from_raw(handle: NonNull<BNAnalysisContext>) -> Self {
35        Self { handle }
36    }
37
38    #[allow(unused)]
39    pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNAnalysisContext>) -> Ref<Self> {
40        Ref::new(Self { handle })
41    }
42
43    /// BinaryView for the current AnalysisContext
44    pub fn view(&self) -> Ref<BinaryView> {
45        let result = unsafe { BNAnalysisContextGetBinaryView(self.handle.as_ptr()) };
46        assert!(!result.is_null());
47        unsafe { BinaryView::ref_from_raw(result) }
48    }
49
50    /// [`Function`] for the current AnalysisContext
51    pub fn function(&self) -> Ref<Function> {
52        let result = unsafe { BNAnalysisContextGetFunction(self.handle.as_ptr()) };
53        assert!(!result.is_null());
54        unsafe { Function::ref_from_raw(result) }
55    }
56
57    /// [`LowLevelILMutableFunction`] used to represent Lifted Level IL
58    pub unsafe fn lifted_il_function(&self) -> Option<Ref<LowLevelILMutableFunction>> {
59        let result = unsafe { BNAnalysisContextGetLiftedILFunction(self.handle.as_ptr()) };
60        unsafe {
61            Some(LowLevelILMutableFunction::ref_from_raw(
62                NonNull::new(result)?.as_ptr(),
63            ))
64        }
65    }
66
67    pub fn set_lifted_il_function(&self, value: &LowLevelILRegularFunction) {
68        unsafe { BNSetLiftedILFunction(self.handle.as_ptr(), value.handle) }
69    }
70
71    /// [`LowLevelILMutableFunction`] used to represent Low Level IL
72    pub unsafe fn llil_function(&self) -> Option<Ref<LowLevelILMutableFunction>> {
73        let result = unsafe { BNAnalysisContextGetLowLevelILFunction(self.handle.as_ptr()) };
74        unsafe {
75            Some(LowLevelILMutableFunction::ref_from_raw(
76                NonNull::new(result)?.as_ptr(),
77            ))
78        }
79    }
80
81    pub fn set_llil_function(&self, value: &LowLevelILRegularFunction) {
82        unsafe { BNSetLowLevelILFunction(self.handle.as_ptr(), value.handle) }
83    }
84
85    /// [`MediumLevelILFunction`] used to represent Medium Level IL
86    pub fn mlil_function(&self) -> Option<Ref<MediumLevelILFunction>> {
87        let result = unsafe { BNAnalysisContextGetMediumLevelILFunction(self.handle.as_ptr()) };
88        unsafe {
89            Some(MediumLevelILFunction::ref_from_raw(
90                NonNull::new(result)?.as_ptr(),
91            ))
92        }
93    }
94
95    pub fn set_mlil_function(&self, value: &MediumLevelILFunction) {
96        // TODO: Mappings FFI
97        unsafe {
98            BNSetMediumLevelILFunction(
99                self.handle.as_ptr(),
100                value.handle,
101                ptr::null_mut(),
102                0,
103                ptr::null_mut(),
104                0,
105            )
106        }
107    }
108
109    /// [`HighLevelILFunction`] used to represent High Level IL
110    pub fn hlil_function(&self, full_ast: bool) -> Option<Ref<HighLevelILFunction>> {
111        let result = unsafe { BNAnalysisContextGetHighLevelILFunction(self.handle.as_ptr()) };
112        unsafe {
113            Some(HighLevelILFunction::ref_from_raw(
114                NonNull::new(result)?.as_ptr(),
115                full_ast,
116            ))
117        }
118    }
119
120    pub fn inform(&self, request: &str) -> bool {
121        let request = request.to_cstr();
122        unsafe { BNAnalysisContextInform(self.handle.as_ptr(), request.as_ptr()) }
123    }
124
125    pub fn set_basic_blocks<I>(&self, blocks: I)
126    where
127        I: IntoIterator<Item = BasicBlock<NativeBlock>>,
128    {
129        let blocks: Vec<_> = blocks.into_iter().collect();
130        let mut blocks_raw: Vec<*mut BNBasicBlock> =
131            blocks.iter().map(|block| block.handle).collect();
132        unsafe { BNSetBasicBlockList(self.handle.as_ptr(), blocks_raw.as_mut_ptr(), blocks.len()) }
133    }
134
135    // Settings cache access - lock-free access to cached settings
136
137    /// Get a boolean setting from the cached settings
138    pub fn get_setting_bool(&self, key: &str) -> bool {
139        let key = key.to_cstr();
140        unsafe { BNAnalysisContextGetSettingBool(self.handle.as_ptr(), key.as_ptr()) }
141    }
142
143    /// Get a double setting from the cached settings
144    pub fn get_setting_double(&self, key: &str) -> f64 {
145        let key = key.to_cstr();
146        unsafe { BNAnalysisContextGetSettingDouble(self.handle.as_ptr(), key.as_ptr()) }
147    }
148
149    /// Get a signed 64-bit integer setting from the cached settings
150    pub fn get_setting_int64(&self, key: &str) -> i64 {
151        let key = key.to_cstr();
152        unsafe { BNAnalysisContextGetSettingInt64(self.handle.as_ptr(), key.as_ptr()) }
153    }
154
155    /// Get an unsigned 64-bit integer setting from the cached settings
156    pub fn get_setting_uint64(&self, key: &str) -> u64 {
157        let key = key.to_cstr();
158        unsafe { BNAnalysisContextGetSettingUInt64(self.handle.as_ptr(), key.as_ptr()) }
159    }
160
161    /// Get a string setting from the cached settings
162    pub fn get_setting_string(&self, key: &str) -> BnString {
163        let key = key.to_cstr();
164        unsafe {
165            let result = BNAnalysisContextGetSettingString(self.handle.as_ptr(), key.as_ptr());
166            BnString::from_raw(result)
167        }
168    }
169
170    /// Get a string list setting from the cached settings
171    pub fn get_setting_string_list(&self, key: &str) -> Array<BnString> {
172        let key = key.to_cstr();
173        unsafe {
174            let mut count = 0;
175            let result = BNAnalysisContextGetSettingStringList(
176                self.handle.as_ptr(),
177                key.as_ptr(),
178                &mut count,
179            );
180            Array::new(result, count, ())
181        }
182    }
183
184    /// Check if an offset is mapped in the cached [`MemoryMap`].
185    ///
186    /// NOTE: This is a lock-free alternative to [`BinaryView::offset_valid`].
187    pub fn is_offset_valid(&self, offset: u64) -> bool {
188        unsafe { BNAnalysisContextIsValidOffset(self.handle.as_ptr(), offset) }
189    }
190
191    /// Check if an offset is readable in the cached [`MemoryMap`].
192    ///
193    /// NOTE: This is a lock-free alternative to [`BinaryView::offset_readable`].
194    pub fn is_offset_readable(&self, offset: u64) -> bool {
195        unsafe { BNAnalysisContextIsOffsetReadable(self.handle.as_ptr(), offset) }
196    }
197
198    /// Check if an offset is writable in the cached [`MemoryMap`].
199    ///
200    /// NOTE: This is a lock-free alternative to [`BinaryView::offset_writable`].
201    pub fn is_offset_writable(&self, offset: u64) -> bool {
202        unsafe { BNAnalysisContextIsOffsetWritable(self.handle.as_ptr(), offset) }
203    }
204
205    /// Check if an offset is executable in the cached [`MemoryMap`].
206    ///
207    /// NOTE: This is a lock-free alternative to [`BinaryView::offset_executable`].
208    pub fn is_offset_executable(&self, offset: u64) -> bool {
209        unsafe { BNAnalysisContextIsOffsetExecutable(self.handle.as_ptr(), offset) }
210    }
211
212    /// Check if an offset is backed by file in the cached [`MemoryMap`].
213    ///
214    /// NOTE: This is a lock-free alternative to [`BinaryView::offset_backed_by_file`].
215    pub fn is_offset_backed_by_file(&self, offset: u64) -> bool {
216        unsafe { BNAnalysisContextIsOffsetBackedByFile(self.handle.as_ptr(), offset) }
217    }
218
219    /// Check if an offset has code semantics in the cached section map.
220    ///
221    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::offset_has_code_semantics`].
222    pub fn is_offset_code_semantics(&self, offset: u64) -> bool {
223        unsafe { BNAnalysisContextIsOffsetCodeSemantics(self.handle.as_ptr(), offset) }
224    }
225
226    /// Check if an offset has external semantics in the cached section map.
227    ///
228    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::offset_has_extern_semantics`].
229    pub fn is_offset_extern_semantics(&self, offset: u64) -> bool {
230        unsafe { BNAnalysisContextIsOffsetExternSemantics(self.handle.as_ptr(), offset) }
231    }
232
233    /// Check if an offset has writable semantics in the cached section map.
234    ///
235    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::offset_has_writable_semantics`].
236    pub fn is_offset_writable_semantics(&self, offset: u64) -> bool {
237        unsafe { BNAnalysisContextIsOffsetWritableSemantics(self.handle.as_ptr(), offset) }
238    }
239
240    /// Check if an offset has read-only semantics in the cached section map.
241    ///
242    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::offset_has_read_only_semantics`].
243    pub fn is_offset_readonly_semantics(&self, offset: u64) -> bool {
244        unsafe { BNAnalysisContextIsOffsetReadOnlySemantics(self.handle.as_ptr(), offset) }
245    }
246
247    /// Get all sections from the cached section map.
248    ///
249    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::sections`].
250    pub fn sections(&self) -> Array<Section> {
251        unsafe {
252            let mut count = 0;
253            let sections = BNAnalysisContextGetSections(self.handle.as_ptr(), &mut count);
254            Array::new(sections, count, ())
255        }
256    }
257
258    /// Get a section by name from the cached section map.
259    ///
260    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::section_by_name`].
261    pub fn section_by_name(&self, name: impl IntoCStr) -> Option<Ref<Section>> {
262        unsafe {
263            let raw_name = name.to_cstr();
264            let name_ptr = raw_name.as_ptr();
265            let raw_section_ptr = BNAnalysisContextGetSectionByName(self.handle.as_ptr(), name_ptr);
266            match raw_section_ptr.is_null() {
267                false => Some(Section::ref_from_raw(raw_section_ptr)),
268                true => None,
269            }
270        }
271    }
272
273    /// Get all sections containing the given address from the cached section map.
274    ///
275    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::sections_at`].
276    pub fn sections_at(&self, addr: u64) -> Array<Section> {
277        unsafe {
278            let mut count = 0;
279            let sections = BNAnalysisContextGetSectionsAt(self.handle.as_ptr(), addr, &mut count);
280            Array::new(sections, count, ())
281        }
282    }
283
284    /// Get the start address (the lowest address) from the cached [`MemoryMap`].
285    ///
286    /// NOTE: This is a lock-free alternative to [`BinaryView::start`].
287    pub fn start(&self) -> u64 {
288        unsafe { BNAnalysisContextGetStart(self.handle.as_ptr()) }
289    }
290
291    /// Get the end address (the highest address) from the cached [`MemoryMap`].
292    ///
293    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::end`].
294    pub fn end(&self) -> u64 {
295        unsafe { BNAnalysisContextGetEnd(self.handle.as_ptr()) }
296    }
297
298    /// Get the length of the cached [`MemoryMap`].
299    ///
300    /// NOTE: This is a lock-free alternative to [`BinaryViewBase::len`].
301    pub fn length(&self) -> u64 {
302        unsafe { BNAnalysisContextGetLength(self.handle.as_ptr()) }
303    }
304
305    /// Get the next valid offset after the given offset from the cached [`MemoryMap`].
306    ///
307    /// NOTE: This is a lock-free alternative to [`BinaryView::next_valid_offset_after`].
308    pub fn next_valid_offset(&self, offset: u64) -> u64 {
309        unsafe { BNAnalysisContextGetNextValidOffset(self.handle.as_ptr(), offset) }
310    }
311
312    /// Get the next mapped address after the given address from the cached [`MemoryMap`].
313    pub fn next_mapped_address(&self, addr: u64, flags: &SegmentFlags) -> u64 {
314        unsafe {
315            BNAnalysisContextGetNextMappedAddress(self.handle.as_ptr(), addr, flags.into_raw())
316        }
317    }
318
319    /// Get the next backed address after the given address from the cached [`MemoryMap`].
320    pub fn next_backed_address(&self, addr: u64, flags: &SegmentFlags) -> u64 {
321        unsafe {
322            BNAnalysisContextGetNextBackedAddress(self.handle.as_ptr(), addr, flags.into_raw())
323        }
324    }
325
326    /// Get the segment containing the given address from the cached [`MemoryMap`].
327    ///
328    /// NOTE: This is a lock-free alternative to [`BinaryViewExt::segment_at`].
329    pub fn segment_at(&self, addr: u64) -> Option<Ref<Segment>> {
330        unsafe {
331            let result = BNAnalysisContextGetSegmentAt(self.handle.as_ptr(), addr);
332            if result.is_null() {
333                None
334            } else {
335                Some(Segment::ref_from_raw(result))
336            }
337        }
338    }
339
340    /// Get all mapped address ranges from the cached [`MemoryMap`].
341    pub fn mapped_address_ranges(&self) -> Array<AddressRange> {
342        unsafe {
343            let mut count = 0;
344            let ranges = BNAnalysisContextGetMappedAddressRanges(self.handle.as_ptr(), &mut count);
345            Array::new(ranges, count, ())
346        }
347    }
348
349    /// Get all backed address ranges from the cached [`MemoryMap`].
350    pub fn backed_address_ranges(&self) -> Array<AddressRange> {
351        unsafe {
352            let mut count = 0;
353            let ranges = BNAnalysisContextGetBackedAddressRanges(self.handle.as_ptr(), &mut count);
354            Array::new(ranges, count, ())
355        }
356    }
357}
358
359impl ToOwned for AnalysisContext {
360    type Owned = Ref<Self>;
361
362    fn to_owned(&self) -> Self::Owned {
363        unsafe { RefCountable::inc_ref(self) }
364    }
365}
366
367unsafe impl RefCountable for AnalysisContext {
368    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
369        Ref::new(Self {
370            handle: NonNull::new(BNNewAnalysisContextReference(handle.handle.as_ptr()))
371                .expect("valid handle"),
372        })
373    }
374
375    unsafe fn dec_ref(handle: &Self) {
376        BNFreeAnalysisContext(handle.handle.as_ptr());
377    }
378}
379
380#[repr(transparent)]
381pub struct Workflow {
382    handle: NonNull<BNWorkflow>,
383}
384
385impl Workflow {
386    pub(crate) unsafe fn from_raw(handle: NonNull<BNWorkflow>) -> Self {
387        Self { handle }
388    }
389
390    pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNWorkflow>) -> Ref<Self> {
391        Ref::new(Self { handle })
392    }
393
394    /// Create a new unregistered [Workflow] with no activities.
395    /// Returns a [WorkflowBuilder] that can be used to configure and register the new [Workflow].
396    ///
397    /// To get a copy of an existing registered [Workflow] use [Workflow::clone_to].
398    pub fn build(name: &str) -> WorkflowBuilder {
399        let name = name.to_cstr();
400        let result = unsafe { BNCreateWorkflow(name.as_ptr()) };
401        WorkflowBuilder {
402            handle: unsafe { Workflow::ref_from_raw(NonNull::new(result).unwrap()) },
403        }
404    }
405
406    /// Make a new unregistered [Workflow], copying all activities and the execution strategy.
407    /// Returns a [WorkflowBuilder] that can be used to configure and register the new [Workflow].
408    ///
409    /// * `name` - the name for the new [Workflow]
410    pub fn clone_to(&self, name: &str) -> WorkflowBuilder {
411        self.clone_to_with_root(name, "")
412    }
413
414    /// Make a new unregistered [Workflow], copying all activities, within `root_activity`, and the execution strategy.
415    ///
416    /// * `name` - the name for the new [Workflow]
417    /// * `root_activity` - perform the clone operation with this activity as the root
418    pub fn clone_to_with_root(&self, name: &str, root_activity: &str) -> WorkflowBuilder {
419        let raw_name = name.to_cstr();
420        let activity = root_activity.to_cstr();
421        let workflow = unsafe {
422            Self::ref_from_raw(
423                NonNull::new(BNWorkflowClone(
424                    self.handle.as_ptr(),
425                    raw_name.as_ptr(),
426                    activity.as_ptr(),
427                ))
428                .unwrap(),
429            )
430        };
431        WorkflowBuilder { handle: workflow }
432    }
433
434    /// Get an existing [Workflow] by name.
435    pub fn get(name: &str) -> Option<Ref<Workflow>> {
436        let name = name.to_cstr();
437        let result = unsafe { BNWorkflowGet(name.as_ptr()) };
438        let handle = NonNull::new(result)?;
439        Some(unsafe { Workflow::ref_from_raw(handle) })
440    }
441
442    /// Clone the existing [Workflow] named `name`.
443    /// Returns a [WorkflowBuilder] that can be used to configure and register the new [Workflow].
444    pub fn cloned(name: &str) -> Option<WorkflowBuilder> {
445        Self::get(name).map(|workflow| workflow.clone_to(name))
446    }
447
448    /// List of all registered [Workflow]'s
449    pub fn list() -> Array<Workflow> {
450        let mut count = 0;
451        let result = unsafe { BNGetWorkflowList(&mut count) };
452        assert!(!result.is_null());
453        unsafe { Array::new(result, count, ()) }
454    }
455
456    pub fn name(&self) -> String {
457        let result = unsafe { BNGetWorkflowName(self.handle.as_ptr()) };
458        assert!(!result.is_null());
459        unsafe { BnString::into_string(result) }
460    }
461
462    /// Determine if an Activity exists in this [Workflow].
463    pub fn contains(&self, activity: &str) -> bool {
464        let activity = activity.to_cstr();
465        unsafe { BNWorkflowContains(self.handle.as_ptr(), activity.as_ptr()) }
466    }
467
468    /// Retrieve the configuration as an adjacency list in JSON for the [Workflow].
469    pub fn configuration(&self) -> String {
470        self.configuration_with_activity("")
471    }
472
473    /// Retrieve the configuration as an adjacency list in JSON for the
474    /// [Workflow], just for the given `activity`.
475    ///
476    /// `activity` - return the configuration for the `activity`
477    pub fn configuration_with_activity(&self, activity: &str) -> String {
478        let activity = activity.to_cstr();
479        let result = unsafe { BNWorkflowGetConfiguration(self.handle.as_ptr(), activity.as_ptr()) };
480        assert!(!result.is_null());
481        unsafe { BnString::into_string(result) }
482    }
483
484    /// Whether this [Workflow] is registered or not. A [Workflow] becomes immutable once registered.
485    pub fn registered(&self) -> bool {
486        unsafe { BNWorkflowIsRegistered(self.handle.as_ptr()) }
487    }
488
489    pub fn size(&self) -> usize {
490        unsafe { BNWorkflowSize(self.handle.as_ptr()) }
491    }
492
493    /// Retrieve the Activity object for the specified `name`.
494    pub fn activity(&self, name: &str) -> Option<Ref<Activity>> {
495        let name = name.to_cstr();
496        let result = unsafe { BNWorkflowGetActivity(self.handle.as_ptr(), name.as_ptr()) };
497        NonNull::new(result).map(|a| unsafe { Activity::ref_from_raw(a) })
498    }
499
500    /// Retrieve the list of activity roots for the [Workflow], or if
501    /// specified just for the given `activity`.
502    ///
503    /// * `activity` - if specified, return the roots for the `activity`
504    pub fn activity_roots(&self, activity: &str) -> Array<BnString> {
505        let activity = activity.to_cstr();
506        let mut count = 0;
507        let result = unsafe {
508            BNWorkflowGetActivityRoots(self.handle.as_ptr(), activity.as_ptr(), &mut count)
509        };
510        assert!(!result.is_null());
511        unsafe { Array::new(result as *mut *mut c_char, count, ()) }
512    }
513
514    /// Retrieve the list of all activities, or optionally a filtered list.
515    ///
516    /// * `activity` - if specified, return the direct children and optionally the descendants of the `activity` (includes `activity`)
517    /// * `immediate` - whether to include only direct children of `activity` or all descendants
518    pub fn subactivities(&self, activity: &str, immediate: bool) -> Array<BnString> {
519        let activity = activity.to_cstr();
520        let mut count = 0;
521        let result = unsafe {
522            BNWorkflowGetSubactivities(
523                self.handle.as_ptr(),
524                activity.as_ptr(),
525                immediate,
526                &mut count,
527            )
528        };
529        assert!(!result.is_null());
530        unsafe { Array::new(result as *mut *mut c_char, count, ()) }
531    }
532
533    /// Generate a FlowGraph object for the current [Workflow] and optionally show it in the UI.
534    ///
535    /// * `activity` - if specified, generate the Flowgraph using `activity` as the root
536    /// * `sequential` - whether to generate a **Composite** or **Sequential** style graph
537    pub fn graph(&self, activity: &str, sequential: Option<bool>) -> Option<Ref<FlowGraph>> {
538        let sequential = sequential.unwrap_or(false);
539        let activity = activity.to_cstr();
540        let graph =
541            unsafe { BNWorkflowGetGraph(self.handle.as_ptr(), activity.as_ptr(), sequential) };
542        if graph.is_null() {
543            return None;
544        }
545        Some(unsafe { FlowGraph::ref_from_raw(graph) })
546    }
547
548    /// Not yet implemented.
549    pub fn show_metrics(&self) {
550        unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"metrics".as_ptr()) }
551    }
552
553    /// Show the Workflow topology in the UI.
554    pub fn show_topology(&self) {
555        unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"topology".as_ptr()) }
556    }
557
558    /// Not yet implemented.
559    pub fn show_trace(&self) {
560        unsafe { BNWorkflowShowReport(self.handle.as_ptr(), c"trace".as_ptr()) }
561    }
562}
563
564impl ToOwned for Workflow {
565    type Owned = Ref<Self>;
566
567    fn to_owned(&self) -> Self::Owned {
568        unsafe { RefCountable::inc_ref(self) }
569    }
570}
571
572unsafe impl RefCountable for Workflow {
573    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
574        Ref::new(Self {
575            handle: NonNull::new(BNNewWorkflowReference(handle.handle.as_ptr()))
576                .expect("valid handle"),
577        })
578    }
579
580    unsafe fn dec_ref(handle: &Self) {
581        BNFreeWorkflow(handle.handle.as_ptr());
582    }
583}
584
585impl CoreArrayProvider for Workflow {
586    type Raw = *mut BNWorkflow;
587    type Context = ();
588    type Wrapped<'a> = Guard<'a, Workflow>;
589}
590
591unsafe impl CoreArrayProviderInner for Workflow {
592    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
593        BNFreeWorkflowList(raw, count)
594    }
595
596    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
597        Guard::new(
598            Workflow::from_raw(NonNull::new(*raw).expect("valid handle")),
599            context,
600        )
601    }
602}
603
604#[must_use = "Workflow is not registered until `register` is called"]
605pub struct WorkflowBuilder {
606    handle: Ref<Workflow>,
607}
608
609impl WorkflowBuilder {
610    fn raw_handle(&self) -> *mut BNWorkflow {
611        self.handle.handle.as_ptr()
612    }
613
614    /// Register an [Activity] with this Workflow and insert it before the designated position.
615    ///
616    /// * `activity` - the [Activity] to register
617    /// * `sibling` - the activity to insert the new activity before
618    pub fn activity_before(self, activity: &Activity, sibling: &str) -> Result<Self, ()> {
619        self.register_activity(activity)?
620            .insert(sibling, vec![activity.name()])
621    }
622
623    /// Register an [Activity] with this Workflow and insert it in the designated position.
624    ///
625    /// * `activity` - the [Activity] to register
626    /// * `sibling` - the activity to insert the new activity after
627    pub fn activity_after(self, activity: &Activity, sibling: &str) -> Result<Self, ()> {
628        self.register_activity(activity)?
629            .insert_after(sibling, vec![activity.name()])
630    }
631
632    /// Register an [Activity] with this Workflow.
633    ///
634    /// * `activity` - the [Activity] to register
635    pub fn register_activity(self, activity: &Activity) -> Result<Self, ()> {
636        self.register_activity_with_subactivities::<Vec<String>>(activity, vec![])
637    }
638
639    /// Register an [Activity] with this Workflow.
640    ///
641    /// * `activity` - the [Activity] to register
642    /// * `subactivities` - the list of Activities to assign
643    pub fn register_activity_with_subactivities<I>(
644        self,
645        activity: &Activity,
646        subactivities: I,
647    ) -> Result<Self, ()>
648    where
649        I: IntoIterator,
650        I::Item: IntoCStr,
651    {
652        let subactivities_raw: Vec<_> = subactivities.into_iter().map(|x| x.to_cstr()).collect();
653        let mut subactivities_ptr: Vec<*const _> =
654            subactivities_raw.iter().map(|x| x.as_ptr()).collect();
655        let result = unsafe {
656            BNWorkflowRegisterActivity(
657                self.raw_handle(),
658                activity.handle.as_ptr(),
659                subactivities_ptr.as_mut_ptr(),
660                subactivities_ptr.len(),
661            )
662        };
663        let Some(activity_ptr) = NonNull::new(result) else {
664            return Err(());
665        };
666        let _ = unsafe { Activity::ref_from_raw(activity_ptr) };
667        Ok(self)
668    }
669
670    /// Register this [Workflow], making it immutable and available for use.
671    pub fn register(self) -> Result<Ref<Workflow>, ()> {
672        self.register_with_config("")
673    }
674
675    /// Register this [Workflow], making it immutable and available for use.
676    ///
677    /// * `configuration` - a JSON representation of the workflow configuration
678    pub fn register_with_config(self, config: &str) -> Result<Ref<Workflow>, ()> {
679        // TODO: We need to hide the JSON here behind a sensible/typed API.
680        let config = config.to_cstr();
681        if unsafe { BNRegisterWorkflow(self.raw_handle(), config.as_ptr()) } {
682            Ok(self.handle)
683        } else {
684            Err(())
685        }
686    }
687
688    /// Assign the list of `activities` as the new set of children for the specified `activity`.
689    ///
690    /// * `activity` - the Activity node to assign children
691    /// * `activities` - the list of Activities to assign
692    pub fn subactivities<I>(self, activity: &str, activities: I) -> Result<Self, ()>
693    where
694        I: IntoIterator,
695        I::Item: IntoCStr,
696    {
697        let activity = activity.to_cstr();
698        let input_list: Vec<_> = activities.into_iter().map(|a| a.to_cstr()).collect();
699        let mut input_list_ptr: Vec<*const _> = input_list.iter().map(|x| x.as_ptr()).collect();
700        let result = unsafe {
701            BNWorkflowAssignSubactivities(
702                self.raw_handle(),
703                activity.as_ptr(),
704                input_list_ptr.as_mut_ptr(),
705                input_list.len(),
706            )
707        };
708        if result {
709            Ok(self)
710        } else {
711            Err(())
712        }
713    }
714
715    /// Remove all Activity nodes from this [Workflow].
716    pub fn clear(self) -> Result<Self, ()> {
717        let result = unsafe { BNWorkflowClear(self.raw_handle()) };
718        if result {
719            Ok(self)
720        } else {
721            Err(())
722        }
723    }
724
725    /// Insert the list of `activities` before the specified `activity` and at the same level.
726    ///
727    /// * `activity` - the Activity node for which to insert `activities` before
728    /// * `activities` - the list of Activities to insert
729    pub fn insert<I>(self, activity: &str, activities: I) -> Result<Self, ()>
730    where
731        I: IntoIterator,
732        I::Item: IntoCStr,
733    {
734        let activity = activity.to_cstr();
735        let input_list: Vec<_> = activities.into_iter().map(|a| a.to_cstr()).collect();
736        let mut input_list_ptr: Vec<*const _> = input_list.iter().map(|x| x.as_ptr()).collect();
737        let result = unsafe {
738            BNWorkflowInsert(
739                self.raw_handle(),
740                activity.as_ptr(),
741                input_list_ptr.as_mut_ptr(),
742                input_list.len(),
743            )
744        };
745        if result {
746            Ok(self)
747        } else {
748            Err(())
749        }
750    }
751
752    /// Insert the list of `activities` after the specified `activity` and at the same level.
753    ///
754    /// * `activity` - the Activity node for which to insert `activities` after
755    /// * `activities` - the list of Activities to insert
756    pub fn insert_after<I>(self, activity: &str, activities: I) -> Result<Self, ()>
757    where
758        I: IntoIterator,
759        I::Item: IntoCStr,
760    {
761        let activity = activity.to_cstr();
762        let input_list: Vec<_> = activities.into_iter().map(|a| a.to_cstr()).collect();
763        let mut input_list_ptr: Vec<*const _> = input_list.iter().map(|x| x.as_ptr()).collect();
764        let result = unsafe {
765            BNWorkflowInsertAfter(
766                self.raw_handle(),
767                activity.as_ptr(),
768                input_list_ptr.as_mut_ptr(),
769                input_list.len(),
770            )
771        };
772        if result {
773            Ok(self)
774        } else {
775            Err(())
776        }
777    }
778
779    /// Remove the specified `activity`
780    pub fn remove(self, activity: &str) -> Result<Self, ()> {
781        let activity = activity.to_cstr();
782        let result = unsafe { BNWorkflowRemove(self.raw_handle(), activity.as_ptr()) };
783        if result {
784            Ok(self)
785        } else {
786            Err(())
787        }
788    }
789
790    /// Replace the specified `activity`.
791    ///
792    /// * `activity` - the Activity to replace
793    /// * `new_activity` - the replacement Activity
794    pub fn replace(self, activity: &str, new_activity: &str) -> Result<Self, ()> {
795        let activity = activity.to_cstr();
796        let new_activity = new_activity.to_cstr();
797        let result = unsafe {
798            BNWorkflowReplace(self.raw_handle(), activity.as_ptr(), new_activity.as_ptr())
799        };
800        if result {
801            Ok(self)
802        } else {
803            Err(())
804        }
805    }
806}