diff options
| author | Mason Reed <mason@vector35.com> | 2026-03-13 12:24:18 -0700 |
|---|---|---|
| committer | Mason Reed <35282038+emesare@users.noreply.github.com> | 2026-03-24 18:46:48 -0700 |
| commit | 3c88b11e5df33116580ac008e36092775df66135 (patch) | |
| tree | cd64fbe149f05683ddabcccd0db7b87356f1f8cf /plugins/warp/src/plugin | |
| parent | f325aa7b6026a1daef84931baeb1f50d7da10c10 (diff) | |
[WARP] Improved UX and API
- Exposes WARP type objects directly
- Adds processor API (for generating warp files directly)
- Adds file and chunk API
- Misc cleanup
- Simplified the amount of commands
- Replaced the "Create" commands with a purpose built processor dialog
- Added a native QT viewer for WARP files
- Simplified committing to a remote with a purpose built commit dialog
Diffstat (limited to 'plugins/warp/src/plugin')
| -rw-r--r-- | plugins/warp/src/plugin/commit.rs | 149 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/create.rs | 265 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/debug.rs | 49 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi.rs | 3 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi/container.rs | 61 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi/file.rs | 137 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi/function.rs | 19 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi/processor.rs | 187 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/ffi/ty.rs | 75 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/file.rs | 41 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/function.rs | 102 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/project.rs | 286 |
12 files changed, 428 insertions, 946 deletions
diff --git a/plugins/warp/src/plugin/commit.rs b/plugins/warp/src/plugin/commit.rs deleted file mode 100644 index 4e8a9fee..00000000 --- a/plugins/warp/src/plugin/commit.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Commit file to a source. - -use crate::cache::container::cached_containers; -use crate::container::{SourceId, SourcePath}; -use crate::plugin::create::OpenFileField; -use binaryninja::command::GlobalCommand; -use binaryninja::interaction::{Form, FormInputField}; -use warp::chunk::ChunkKind; -use warp::WarpFile; - -pub struct SelectedSourceField { - sources: Vec<(SourceId, SourcePath)>, -} - -impl SelectedSourceField { - pub fn field(&self) -> FormInputField { - FormInputField::Choice { - prompt: "Selected Source".to_string(), - choices: self - .sources - .iter() - .map(|(id, path)| { - // For display purposes we only want to show the last path item. - let path_name = path - .to_string() - .rsplit_once('/') - .map_or(path.to_string(), |(_, last_path_item)| { - last_path_item.to_string() - }); - // TODO: Probably have a truncation limit here, this is just for display after all. - format!("{} ({})", path_name, id) - }) - .collect(), - default: None, - value: 0, - } - } - - pub fn from_form(&self, form: &Form) -> Option<SourceId> { - let field = form.get_field_with_name("Selected Source")?; - let field_value = field.try_value_index()?; - self.sources.get(field_value).map(|(id, _)| *id) - } -} - -pub struct CommitFile; - -impl CommitFile { - pub fn selected_source_field() -> SelectedSourceField { - let mut writable_sources = Vec::new(); - for container in cached_containers() { - if let Ok(container) = container.read() { - for source in container.sources().unwrap_or_default() { - if let Ok(true) = container.is_source_writable(&source) { - if let Ok(source_path) = container.source_path(&source) { - writable_sources.push((source, source_path)); - } - } - } - } - } - SelectedSourceField { - sources: writable_sources, - } - } - - pub fn execute() -> Option<()> { - let mut form = Form::new("Commit File"); - - // Users are going to get confused between this and adding functions to a source then commiting. - // So we should make it clear with a label, and also probably deprecate this command and replace it with "add functions to source" and "commit source". - form.add_field(FormInputField::Label { - prompt: "Commits a WARP file to an existing source, this is primarily used for committing to network containers".to_string() - }); - - form.add_field(OpenFileField::field()); - let source_field = Self::selected_source_field(); - form.add_field(source_field.field()); - - if !form.prompt() { - return None; - } - - let open_file_path = OpenFileField::from_form(&form)?; - let source_id = source_field.from_form(&form)?; - tracing::info!("Committing file to source: {}", source_id); - - let bytes = std::fs::read(open_file_path).ok()?; - let Some(warp_file) = WarpFile::from_bytes(&bytes) else { - tracing::error!("Failed to parse warp file!"); - return None; - }; - - for container in cached_containers() { - let Ok(mut container) = container.write() else { - continue; - }; - - if let Ok(true) = container.is_source_writable(&source_id) { - // TODO: We need to find a sane way to do this procedure through the FFI. - for chunk in &warp_file.chunks { - match &chunk.kind { - ChunkKind::Signature(sc) => { - let functions: Vec<_> = sc.functions().collect(); - tracing::info!( - "Adding {} functions to source: {}", - functions.len(), - source_id - ); - if let Err(e) = container.add_functions( - &chunk.header.target, - &source_id, - &functions, - ) { - tracing::error!("Failed to add functions to source: {}", e); - } - } - ChunkKind::Type(sc) => { - let types: Vec<_> = sc.types().collect(); - tracing::info!("Adding {} types to source: {}", types.len(), source_id); - if let Err(e) = container.add_computed_types(&source_id, &types) { - tracing::error!("Failed to add types to source: {}", e); - } - } - } - } - if let Err(e) = container.commit_source(&source_id) { - tracing::error!("Failed to commit source: {}", e); - } - tracing::info!("Committed file to source: {}", source_id); - return Some(()); - } - } - - Some(()) - } -} - -impl GlobalCommand for CommitFile { - fn action(&self) { - std::thread::spawn(move || { - Self::execute(); - }); - } - - fn valid(&self) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/create.rs b/plugins/warp/src/plugin/create.rs deleted file mode 100644 index 5f5527a7..00000000 --- a/plugins/warp/src/plugin/create.rs +++ /dev/null @@ -1,265 +0,0 @@ -use crate::processor::{ - new_processing_state_background_thread, CompressionTypeField, FileDataKindField, - IncludedFunctionsField, SaveReportToDiskField, WarpFileProcessor, -}; -use crate::report::{ReportGenerator, ReportKindField}; -use crate::{user_signature_dir, INCLUDE_TAG_NAME}; -use binaryninja::background_task::BackgroundTask; -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::command::{Command, GlobalCommand}; -use binaryninja::file_metadata::FileMetadata; -use binaryninja::interaction::form::{Form, FormInputField}; -use binaryninja::interaction::{MessageBoxButtonResult, MessageBoxButtonSet, MessageBoxIcon}; -use binaryninja::rc::Ref; -use std::path::PathBuf; -use std::thread; -use warp::chunk::Chunk; -use warp::WarpFile; - -pub struct SaveFileField; - -impl SaveFileField { - pub fn field(view: &BinaryView) -> FormInputField { - let file = view.file(); - let default_name = match file.project_file() { - None => { - // Not in a project, use the file name directly. - file.display_name() - } - Some(project_file) => project_file.name(), - }; - let signature_dir = user_signature_dir(); - let default_file_path = signature_dir.join(&default_name).with_extension("warp"); - FormInputField::SaveFileName { - prompt: "File Path".to_string(), - // TODO: This is called extension but is really a filter. - extension: Some("*.warp".to_string()), - default: Some(default_file_path.to_string_lossy().to_string()), - value: None, - } - } - - pub fn from_form(form: &Form) -> Option<PathBuf> { - let field = form.get_field_with_name("File Path")?; - let field_value = field.try_value_string()?; - Some(PathBuf::from(field_value)) - } -} - -pub struct OpenFileField; - -impl OpenFileField { - pub fn field() -> FormInputField { - FormInputField::OpenFileName { - prompt: "Input File Path".to_string(), - extension: None, - default: None, - value: None, - } - } - - pub fn from_form(form: &Form) -> Option<PathBuf> { - let field = form.get_field_with_name("Input File Path")?; - let field_value = field.try_value_string()?; - Some(PathBuf::from(field_value)) - } -} - -pub struct CreateFromCurrentView; - -impl CreateFromCurrentView { - pub fn execute(view: Ref<BinaryView>, external_file: bool) -> Option<()> { - // Prompt the user first so that they can go do other things and not worry about a popup. - let mut form = Form::new("Create From View"); - - if external_file { - form.add_field(OpenFileField::field()); - } - - form.add_field(SaveFileField::field(&view)); - - let fd_field = FileDataKindField::default(); - form.add_field(fd_field.to_field()); - - let compression_field = CompressionTypeField::default(); - form.add_field(compression_field.to_field()); - - let mut included_field = IncludedFunctionsField::default(); - // If the view has the include tag, we better set the default to the selected functions. - if view.tag_type_by_name(INCLUDE_TAG_NAME).is_some() { - included_field = IncludedFunctionsField::Selected; - } - form.add_field(included_field.to_field()); - - let report_field = ReportKindField::default(); - form.add_field(report_field.to_field()); - let report_to_disk_field = SaveReportToDiskField::default(); - form.add_field(report_to_disk_field.to_field()); - - if !form.prompt() { - return None; - } - let compression_type = CompressionTypeField::from_form(&form).unwrap_or_default(); - let file_path = SaveFileField::from_form(&form)?; - let file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default(); - let file_included_functions = IncludedFunctionsField::from_form(&form).unwrap_or_default(); - let report_kind = ReportKindField::from_form(&form).unwrap_or_default(); - let save_report_to_disk = SaveReportToDiskField::from_form(&form).unwrap_or_default(); - let open_file_path = OpenFileField::from_form(&form); - - // If we already have a file, prompt the user if they want to add the data. - let mut existing_chunks = Vec::new(); - if file_path.exists() { - let prompt_result = binaryninja::interaction::show_message_box( - "Keep existing file data?", - "The file already exists. Do you want to keep the existing data?", - MessageBoxButtonSet::YesNoCancelButtonSet, - MessageBoxIcon::QuestionIcon, - ); - - match prompt_result { - MessageBoxButtonResult::NoButton => { - // User wants to overwrite the file. - } - MessageBoxButtonResult::YesButton | MessageBoxButtonResult::OKButton => { - // User wants to keep the existing data. - let data = std::fs::read(&file_path).ok()?; - let existing_file = WarpFile::from_owned_bytes(data)?; - existing_chunks.extend(existing_file.chunks); - } - MessageBoxButtonResult::CancelButton => { - tracing::info!( - "User cancelled signature file creation, no operations were performed." - ); - return None; - } - } - } - - let processor = WarpFileProcessor::new() - .with_compression_type(compression_type) - .with_file_data(file_data_kind) - .with_included_functions(file_included_functions); - - let file = match open_file_path { - None => { - // We are processing the current view. NOT an external file. - // Reference path is just used for the state tracking. Does not need to be readable. - let reference_path = file_path.clone(); - processor.process_view(reference_path, &view) - } - Some(open_file_path) => { - // This thread will show the state in a background task. - let background_task = BackgroundTask::new("Processing started...", true); - new_processing_state_background_thread(background_task.clone(), processor.state()); - let file = processor.process(open_file_path); - background_task.finish(); - file - } - }; - - if let Err(err) = file { - binaryninja::interaction::show_message_box( - "Failed to create signature file", - &err.to_string(), - MessageBoxButtonSet::OKButtonSet, - MessageBoxIcon::ErrorIcon, - ); - tracing::error!("Failed to create signature file: {}", err); - return None; - } - - let background_task = BackgroundTask::new("Creating WARP File...", false); - let mut file = file.unwrap(); - // Add back the existing chunks if the user selected to keep them. - if !existing_chunks.is_empty() { - file.chunks.extend(existing_chunks); - // TODO: Make merging optional? - // TODO: Merging can lose chunk data if it goes above the maximum table count. - // TODO: We should probably solve that in the warp crate itself? - file.chunks = Chunk::merge(&file.chunks, compression_type.into()); - - // After merging, we should have at least one chunk. If not, merging actually removed data. - if file.chunks.len() < 1 { - tracing::error!( - "Failed to merge chunks! Please report this, it should not happen." - ); - return None; - } - } - - let file_bytes = file.to_bytes(); - let file_size = file_bytes.len(); - if std::fs::write(&file_path, file_bytes).is_err() { - tracing::error!("Failed to write data to signature file!"); - } - tracing::info!("Saved signature file to: '{}'", file_path.display()); - background_task.finish(); - - // Show a report of the generate signatures, if desired. - let report_generator = ReportGenerator::new(); - if let Some(report_string) = report_generator.report(&report_kind, &file) { - if save_report_to_disk == SaveReportToDiskField::Yes { - let report_ext = report_generator - .report_extension(&report_kind) - .unwrap_or_default(); - let report_path = file_path.with_extension(report_ext); - let _ = std::fs::write(report_path, &report_string); - } - - // The ReportWidget uses a QTextBrowser, which cannot render large files very well. - if file_size > 10000000 { - tracing::warn!("WARP report file is too large to show in the UI. Please see the report file on disk."); - } else { - match report_kind { - ReportKindField::None => {} - ReportKindField::Html => { - view.show_html_report("Generated WARP File", report_string.as_str(), ""); - } - ReportKindField::Markdown => { - view.show_markdown_report( - "Generated WARP File", - report_string.as_str(), - "", - ); - } - ReportKindField::Json => { - view.show_plaintext_report("Generated WARP File", report_string.as_str()); - } - } - } - } - - Some(()) - } -} - -impl Command for CreateFromCurrentView { - fn action(&self, view: &BinaryView) { - let view = view.to_owned(); - thread::spawn(move || { - CreateFromCurrentView::execute(view, false); - }); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} - -pub struct CreateFromFiles; - -impl GlobalCommand for CreateFromFiles { - fn action(&self) { - let empty_file_metadata = FileMetadata::new(); - let empty_bv = BinaryView::from_data(&empty_file_metadata, &[]); - thread::spawn(move || { - CreateFromCurrentView::execute(empty_bv.to_owned(), true); - empty_bv.file().close(); - }); - } - - fn valid(&self) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/debug.rs b/plugins/warp/src/plugin/debug.rs deleted file mode 100644 index 4198ea30..00000000 --- a/plugins/warp/src/plugin/debug.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::cache::container::for_cached_containers; -use crate::{build_function, cache}; -use binaryninja::binary_view::BinaryView; -use binaryninja::command::{Command, FunctionCommand}; -use binaryninja::function::Function; -use binaryninja::object_destructor::ObjectDestructor; - -pub struct DebugFunction; - -impl FunctionCommand for DebugFunction { - fn action(&self, _view: &BinaryView, func: &Function) { - tracing::info!( - "{:#?}", - build_function(func, || func.lifted_il().ok(), false) - ); - } - - fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { - true - } -} - -pub struct DebugCache; - -impl Command for DebugCache { - fn action(&self, _view: &BinaryView) { - for_cached_containers(|c| { - tracing::info!("Container: {:#?}", c); - }); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} - -pub struct DebugInvalidateCache; - -impl Command for DebugInvalidateCache { - fn action(&self, view: &BinaryView) { - let destructor = cache::CacheDestructor {}; - destructor.destruct_view(view); - tracing::info!("Invalidated all WARP caches..."); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/ffi.rs b/plugins/warp/src/plugin/ffi.rs index c1f5acb8..bb6fb483 100644 --- a/plugins/warp/src/plugin/ffi.rs +++ b/plugins/warp/src/plugin/ffi.rs @@ -1,6 +1,8 @@ mod container; mod file; mod function; +mod processor; +mod ty; use binaryninjacore_sys::{ BNBasicBlock, BNBinaryView, BNFunction, BNLowLevelILFunction, BNPlatform, @@ -48,6 +50,7 @@ pub type BNWARPTypeGUID = TypeGUID; pub type BNWARPTarget = warp::target::Target; pub type BNWARPFunction = warp::signature::function::Function; pub type BNWARPContainer = RwLock<Box<dyn Container>>; +pub type BNWARPType = warp::r#type::Type; // TODO: Some sort of callback for loading functions // TODO: Be able to run matcher for a specific file diff --git a/plugins/warp/src/plugin/ffi/container.rs b/plugins/warp/src/plugin/ffi/container.rs index aa0c37d3..09f15d99 100644 --- a/plugins/warp/src/plugin/ffi/container.rs +++ b/plugins/warp/src/plugin/ffi/container.rs @@ -3,17 +3,11 @@ use crate::container::disk::DiskContainer; use crate::container::{ ContainerSearchItem, ContainerSearchItemKind, ContainerSearchQuery, SourcePath, SourceTag, }; -use crate::convert::{from_bn_type, to_bn_type}; use crate::plugin::ffi::{ BNWARPConstraintGUID, BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource, - BNWARPTarget, BNWARPTypeGUID, + BNWARPTarget, BNWARPType, BNWARPTypeGUID, }; -use binaryninja::architecture::CoreArchitecture; -use binaryninja::binary_view::BinaryView; -use binaryninja::rc::Ref; use binaryninja::string::BnString; -use binaryninja::types::Type; -use binaryninjacore_sys::{BNArchitecture, BNBinaryView, BNType}; use std::collections::HashMap; use std::ffi::{c_char, CStr}; use std::mem::ManuallyDrop; @@ -98,32 +92,25 @@ pub unsafe extern "C" fn BNWARPContainerSearchItemGetSource( #[no_mangle] pub unsafe extern "C" fn BNWARPContainerSearchItemGetType( - arch: *mut BNArchitecture, item: *mut BNWARPContainerSearchItem, -) -> *mut BNType { - // NOTE: to convert the type, we must have an architecture. - let arch = match !arch.is_null() { - true => Some(CoreArchitecture::from_raw(arch)), - false => None, - }; - +) -> *mut BNWARPType { let item = ManuallyDrop::new(Arc::from_raw(item)); match &item.kind { ContainerSearchItemKind::Source { .. } => std::ptr::null_mut(), ContainerSearchItemKind::Function(func) => { match &func.ty { None => std::ptr::null_mut(), - Some(ty) => { - let bn_ty = to_bn_type(arch, &ty); - // NOTE: The type ref has been pre-incremented for the caller. - unsafe { Ref::into_raw(bn_ty) }.handle + Some(func_ty) => { + let arc_func_ty = Arc::new(func_ty.clone()); + // NOTE: Freed by BNWARPFreeTypeReference + Arc::into_raw(arc_func_ty) as *mut BNWARPType } } } ContainerSearchItemKind::Type(ty) => { - let bn_ty = to_bn_type(arch, &ty); - // NOTE: The type ref has been pre-incremented for the caller. - unsafe { Ref::into_raw(bn_ty) }.handle + let arc_ty = Arc::new(ty.clone()); + // NOTE: Freed by BNWARPFreeTypeReference + Arc::into_raw(arc_ty) as *mut BNWARPType } ContainerSearchItemKind::Symbol(_) => std::ptr::null_mut(), } @@ -254,7 +241,7 @@ pub unsafe extern "C" fn BNWARPContainerGetSources( return std::ptr::null_mut(); }; - // NOTE: Leak the sources to be freed by BNWARPFreeSourceList + // NOTE: Leak the sources to be freed by BNWARPFreeUUIDList let boxed_sources: Box<[_]> = container.sources().unwrap_or_default().into_boxed_slice(); *count = boxed_sources.len(); Box::into_raw(boxed_sources) as *mut BNWARPSource @@ -389,14 +376,11 @@ pub unsafe extern "C" fn BNWARPContainerAddFunctions( #[no_mangle] pub unsafe extern "C" fn BNWARPContainerAddTypes( - view: *mut BNBinaryView, container: *mut BNWARPContainer, source: *const BNWARPSource, - types: *mut *mut BNType, + types: *mut *mut BNWARPType, count: usize, ) -> bool { - let view = unsafe { BinaryView::from_raw(view) }; - let arc_container = ManuallyDrop::new(Arc::from_raw(container)); let Ok(mut container) = arc_container.write() else { return false; @@ -405,10 +389,11 @@ pub unsafe extern "C" fn BNWARPContainerAddTypes( let source = unsafe { *source }; let types_ptr = std::slice::from_raw_parts(types, count); + // TODO: We have to clone the objects here to make the type checker happy. + // TODO: See about avoiding this later. let types: Vec<_> = types_ptr .iter() - .map(|&t| Type::from_raw(t)) - .map(|ty| from_bn_type(&view, &ty, 255)) + .map(|&t| unsafe { ManuallyDrop::new(Arc::from_raw(t)).as_ref().clone() }) .collect(); container.add_types(&source, &types).is_ok() } @@ -476,7 +461,7 @@ pub unsafe extern "C" fn BNWARPContainerGetSourcesWithFunctionGUID( let guid = unsafe { *guid }; - // NOTE: Leak the sources to be freed by BNWARPFreeSourceList + // NOTE: Leak the sources to be freed by BNWARPFreeUUIDList let boxed_sources: Box<[_]> = container .sources_with_function_guid(&target, &guid) .unwrap_or_default() @@ -498,7 +483,7 @@ pub unsafe extern "C" fn BNWARPContainerGetSourcesWithTypeGUID( let guid = unsafe { *guid }; - // NOTE: Leak the sources to be freed by BNWARPFreeSourceList + // NOTE: Leak the sources to be freed by BNWARPFreeUUIDList let boxed_sources: Box<[_]> = container .sources_with_type_guid(&guid) .unwrap_or_default() @@ -538,22 +523,17 @@ pub unsafe extern "C" fn BNWARPContainerGetFunctionsWithGUID( Box::into_raw(raw_boxed_functions) as *mut *mut BNWARPFunction } -// TODO: Swap arch to Target? #[no_mangle] pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID( - arch: *mut BNArchitecture, container: *mut BNWARPContainer, source: *const BNWARPSource, guid: *const BNWARPTypeGUID, -) -> *mut BNType { +) -> *mut BNWARPType { let arc_container = ManuallyDrop::new(Arc::from_raw(container)); let Ok(container) = arc_container.read() else { return std::ptr::null_mut(); }; - // NOTE: to convert the type, we must have an architecture. - let arch = CoreArchitecture::from_raw(arch); - let source = unsafe { *source }; let guid = unsafe { *guid }; @@ -561,9 +541,10 @@ pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID( let Some(ty) = container.type_with_guid(&source, &guid).unwrap_or_default() else { return std::ptr::null_mut(); }; - let function_type = to_bn_type(Some(arch), &ty); - // NOTE: The type ref has been pre-incremented for the caller. - unsafe { Ref::into_raw(function_type) }.handle + + let arc_ty = Arc::new(ty); + // NOTE: Freed by BNWARPFreeTypeReference + Arc::into_raw(arc_ty) as *mut BNWARPType } #[no_mangle] diff --git a/plugins/warp/src/plugin/ffi/file.rs b/plugins/warp/src/plugin/ffi/file.rs index 951b5eb2..3abfe5aa 100644 --- a/plugins/warp/src/plugin/ffi/file.rs +++ b/plugins/warp/src/plugin/ffi/file.rs @@ -1,11 +1,43 @@ +use crate::plugin::ffi::{BNWARPFunction, BNWARPTarget, BNWARPType}; +use binaryninja::data_buffer::DataBuffer; +use binaryninjacore_sys::BNDataBuffer; use std::ffi::c_char; +use std::mem::ManuallyDrop; use std::sync::Arc; -use warp::WarpFile; +use warp::chunk::ChunkKind; +use warp::{WarpFile, WarpFileHeader}; -pub type BNWARPFile = WarpFile<'static>; +/// A [`WarpFile`] wrapper that uses reference counting to manage its chunks lifetime. +/// +/// Used primarily when passing to the C FFI so that chunks do not need to have a complicated lifetime. +pub struct RcWarpFile { + pub header: WarpFileHeader, + pub chunks: Vec<Arc<warp::chunk::Chunk<'static>>>, +} + +impl From<WarpFile<'static>> for RcWarpFile { + fn from(file: WarpFile<'static>) -> Self { + let chunks = file.chunks.into_iter().map(|c| Arc::new(c)).collect(); + Self { + header: file.header, + chunks, + } + } +} + +impl From<&RcWarpFile> for WarpFile<'static> { + fn from(rc_file: &RcWarpFile) -> Self { + let chunks = rc_file.chunks.iter().map(|c| (**c).clone()).collect(); + Self { + header: rc_file.header.clone(), + chunks, + } + } +} + +pub type BNWARPFile = RcWarpFile; -// TODO: At some point we may want to expose chunks directly. For now we will just enumerate all of them. -// pub type BNWARPChunk = warp::chunk::Chunk<'static>; +pub type BNWARPChunk = warp::chunk::Chunk<'static>; // TODO: From bytes as well. #[no_mangle] @@ -20,7 +52,78 @@ pub unsafe extern "C" fn BNWARPNewFileFromPath(path: *mut c_char) -> *mut BNWARP let Some(file) = WarpFile::from_owned_bytes(bytes) else { return std::ptr::null_mut(); }; - Arc::into_raw(Arc::new(file)) as *mut BNWARPFile + let rc_file = RcWarpFile::from(file); + Arc::into_raw(Arc::new(rc_file)) as *mut BNWARPFile +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFileGetChunks( + file: *mut BNWARPFile, + count: *mut usize, +) -> *mut *mut BNWARPChunk { + let arc_file = ManuallyDrop::new(Arc::from_raw(file)); + *count = arc_file.chunks.len(); + let boxed_chunks: Box<[_]> = arc_file + .chunks + .iter() + .map(|c| Arc::into_raw(c.clone())) + .collect(); + Box::into_raw(boxed_chunks) as *mut *mut BNWARPChunk +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFileToDataBuffer(file: *mut BNWARPFile) -> *mut BNDataBuffer { + let arc_file = ManuallyDrop::new(Arc::from_raw(file)); + let warp_file = WarpFile::from(arc_file.as_ref()); + let buffer = DataBuffer::new(&warp_file.to_bytes()); + buffer.into_raw() +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPChunkGetTarget(chunk: *const BNWARPChunk) -> *mut BNWARPTarget { + let chunk = unsafe { &*chunk }; + let chunk_target = chunk.header.target.clone(); + Arc::into_raw(Arc::new(chunk_target)) as *mut BNWARPTarget +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPChunkGetFunctions( + chunk: *const BNWARPChunk, + count: *mut usize, +) -> *mut *mut BNWARPFunction { + let chunk = unsafe { &*chunk }; + match &chunk.kind { + ChunkKind::Signature(sc) => { + let boxed_funcs: Box<[_]> = sc + .functions() + .into_iter() + .map(|f| Arc::into_raw(Arc::new(f.clone()))) + .collect(); + *count = boxed_funcs.len(); + Box::into_raw(boxed_funcs) as *mut *mut BNWARPFunction + } + ChunkKind::Type(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPChunkGetTypes( + chunk: *const BNWARPChunk, + count: *mut usize, +) -> *mut *mut BNWARPType { + let chunk = unsafe { &*chunk }; + match &chunk.kind { + ChunkKind::Signature(_) => std::ptr::null_mut(), + ChunkKind::Type(tc) => { + let boxed_types: Box<[_]> = tc + .types() + .into_iter() + .map(|t| Arc::into_raw(Arc::new(t.ty.clone()))) + .collect(); + *count = boxed_types.len(); + Box::into_raw(boxed_types) as *mut *mut BNWARPType + } + } } #[no_mangle] @@ -36,3 +139,27 @@ pub unsafe extern "C" fn BNWARPFreeFileReference(file: *mut BNWARPFile) { } Arc::decrement_strong_count(file); } + +#[no_mangle] +pub unsafe extern "C" fn BNWARPNewChunkReference(chunk: *mut BNWARPChunk) -> *mut BNWARPChunk { + Arc::increment_strong_count(chunk); + chunk +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeChunkReference(chunk: *mut BNWARPChunk) { + if chunk.is_null() { + return; + } + Arc::decrement_strong_count(chunk); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeChunkList(chunks: *mut *mut BNWARPChunk, count: usize) { + let chunks_ptr = std::ptr::slice_from_raw_parts_mut(chunks, count); + let chunks = unsafe { Box::from_raw(chunks_ptr) }; + for chunk in chunks { + // NOTE: The chunks themselves should also be arc. + BNWARPFreeChunkReference(chunk); + } +} diff --git a/plugins/warp/src/plugin/ffi/function.rs b/plugins/warp/src/plugin/ffi/function.rs index 138c0a7b..2eff9e4a 100644 --- a/plugins/warp/src/plugin/ffi/function.rs +++ b/plugins/warp/src/plugin/ffi/function.rs @@ -1,11 +1,11 @@ use crate::build_function; use crate::cache::{insert_cached_function_match, try_cached_function_match}; -use crate::convert::{to_bn_symbol_at_address, to_bn_type}; -use crate::plugin::ffi::{BNWARPConstraint, BNWARPFunction, BNWARPFunctionGUID}; +use crate::convert::to_bn_symbol_at_address; +use crate::plugin::ffi::{BNWARPConstraint, BNWARPFunction, BNWARPFunctionGUID, BNWARPType}; use binaryninja::function::Function; use binaryninja::rc::Ref; use binaryninja::string::BnString; -use binaryninjacore_sys::{BNFunction, BNSymbol, BNType}; +use binaryninjacore_sys::{BNFunction, BNSymbol}; use std::ffi::c_char; use std::mem::ManuallyDrop; use std::sync::Arc; @@ -111,19 +111,14 @@ pub unsafe extern "C" fn BNWARPFunctionGetSymbolName(function: *mut BNWARPFuncti } #[no_mangle] -pub unsafe extern "C" fn BNWARPFunctionGetType( - function: *mut BNWARPFunction, - analysis_function: *mut BNFunction, -) -> *mut BNType { - let analysis_function = Function::from_raw(analysis_function); +pub unsafe extern "C" fn BNWARPFunctionGetType(function: *mut BNWARPFunction) -> *mut BNWARPType { // We do not own function so we should not drop. let function = ManuallyDrop::new(Arc::from_raw(function)); match &function.ty { Some(func_ty) => { - let arch = analysis_function.arch(); - let function_type = to_bn_type(Some(arch), func_ty); - // NOTE: The type ref has been pre-incremented for the caller. - unsafe { Ref::into_raw(function_type) }.handle + let arc_func_ty = Arc::new(func_ty.clone()); + // NOTE: Freed by BNWARPFreeTypeReference + Arc::into_raw(arc_func_ty) as *mut BNWARPType } None => std::ptr::null_mut(), } diff --git a/plugins/warp/src/plugin/ffi/processor.rs b/plugins/warp/src/plugin/ffi/processor.rs new file mode 100644 index 00000000..83b47f5a --- /dev/null +++ b/plugins/warp/src/plugin/ffi/processor.rs @@ -0,0 +1,187 @@ +use crate::plugin::ffi::file::{BNWARPFile, RcWarpFile}; +use crate::processor::{ + IncludedDataField, IncludedFunctionsField, ProcessingFileState, WarpFileProcessor, + WarpFileProcessorEntry, +}; +use binaryninja::binary_view::BinaryView; +use binaryninja::project::file::ProjectFile; +use binaryninja::project::Project; +use binaryninjacore_sys::{BNBinaryView, BNProject, BNProjectFile}; +use std::ffi::{c_char, CStr, CString}; +use std::mem::ManuallyDrop; +use std::path::PathBuf; +use std::ptr::NonNull; +use std::sync::Arc; +use warp::chunk::CompressionType; + +pub type BNWARPProcessor = WarpFileProcessor; + +#[repr(C)] +pub struct BNWARPProcessorState { + cancelled: bool, + unprocessed_files_count: usize, + processed_files_count: usize, + analyzing_files: *mut *mut c_char, + analyzing_files_count: usize, + processing_files: *mut *mut c_char, + processing_files_count: usize, +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPNewProcessor( + included_data: IncludedDataField, + included_functions: IncludedFunctionsField, + worker_count: usize, +) -> *mut BNWARPProcessor { + let processor = WarpFileProcessor::new() + .with_file_data(included_data) + .with_included_functions(included_functions) + .with_compression_type(CompressionType::Zstd) + .with_entry_worker_count(worker_count); + Box::into_raw(Box::new(processor)) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorAddPath( + processor: *mut BNWARPProcessor, + path: *const c_char, +) { + let mut processor = ManuallyDrop::new(Box::from_raw(processor)); + let path_cstr = unsafe { CStr::from_ptr(path) }; + let path = PathBuf::from(path_cstr.to_str().unwrap()); + // TODO: Not thread safe. + processor.add_entry(WarpFileProcessorEntry::Path(path)); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorAddProject( + processor: *mut BNWARPProcessor, + project: *mut BNProject, +) { + let mut processor = ManuallyDrop::new(Box::from_raw(processor)); + let project = Project::from_raw(NonNull::new(project).unwrap()); + // TODO: Not thread safe. + processor.add_entry(WarpFileProcessorEntry::Project(project.to_owned())); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorAddProjectFile( + processor: *mut BNWARPProcessor, + project_file: *mut BNProjectFile, +) { + let mut processor = ManuallyDrop::new(Box::from_raw(processor)); + let project_file = ProjectFile::from_raw(NonNull::new(project_file).unwrap()); + // TODO: Not thread safe. + processor.add_entry(WarpFileProcessorEntry::ProjectFile(project_file.to_owned())); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorAddBinaryView( + processor: *mut BNWARPProcessor, + view: *mut BNBinaryView, +) { + let mut processor = ManuallyDrop::new(Box::from_raw(processor)); + let view = BinaryView::from_raw(view); + // TODO: Not thread safe. + processor.add_entry(WarpFileProcessorEntry::BinaryView(view.to_owned())); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorStart(processor: *mut BNWARPProcessor) -> *mut BNWARPFile { + let mut processor = ManuallyDrop::new(Box::from_raw(processor)); + // TODO: Not thread safe. + match processor.process_entries() { + Ok(file) => { + let rc_file = RcWarpFile::from(file); + Arc::into_raw(Arc::new(rc_file)) as *mut BNWARPFile + } + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorIsCancelled(processor: *mut BNWARPProcessor) -> bool { + let processor = ManuallyDrop::new(Box::from_raw(processor)); + processor + .state() + .cancelled + .load(std::sync::atomic::Ordering::Relaxed) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorCancel(processor: *mut BNWARPProcessor) { + let processor = ManuallyDrop::new(Box::from_raw(processor)); + processor + .state() + .cancelled + .store(true, std::sync::atomic::Ordering::Relaxed) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPProcessorGetState( + processor: *mut BNWARPProcessor, +) -> BNWARPProcessorState { + let processor = ManuallyDrop::new(Box::from_raw(processor)); + let processor_state = processor.state(); + + let mut unprocessed_files_count = 0; + let mut processed_files_count = 0; + let mut analyzing_files = Vec::new(); + let mut processing_files = Vec::new(); + + for file_state in &processor_state.files { + match file_state.value() { + ProcessingFileState::Unprocessed => unprocessed_files_count += 1, + ProcessingFileState::Analyzing => analyzing_files.push(file_state.key().clone()), + ProcessingFileState::Processing => processing_files.push(file_state.key().clone()), + ProcessingFileState::Processed => processed_files_count += 1, + } + } + + let raw_analyzing_files: Box<[_]> = analyzing_files + .into_iter() + .map(|p| CString::new(p.to_str().unwrap()).unwrap().into_raw()) + .collect(); + let raw_analyzing_files_count = raw_analyzing_files.len(); + let raw_analyzing_files_ptr = Box::into_raw(raw_analyzing_files); + + let raw_processing_files: Box<[_]> = processing_files + .into_iter() + .map(|p| CString::new(p.to_str().unwrap()).unwrap().into_raw()) + .collect(); + let raw_processing_files_count = raw_processing_files.len(); + let raw_processing_files_ptr = Box::into_raw(raw_processing_files); + + BNWARPProcessorState { + cancelled: processor_state + .cancelled + .load(std::sync::atomic::Ordering::Relaxed), + unprocessed_files_count, + processed_files_count, + analyzing_files: raw_analyzing_files_ptr as *mut *mut c_char, + analyzing_files_count: raw_analyzing_files_count, + processing_files: raw_processing_files_ptr as *mut *mut c_char, + processing_files_count: raw_processing_files_count, + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeProcessor(processor: *mut BNWARPProcessor) { + let _ = Box::from_raw(processor); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeProcessorState(state: BNWARPProcessorState) { + let a_files_ptr = + std::ptr::slice_from_raw_parts_mut(state.analyzing_files, state.analyzing_files_count); + let a_files_boxed = unsafe { Box::from_raw(a_files_ptr) }; + for path in a_files_boxed.iter() { + let _ = CString::from_raw(*path); + } + let p_files_ptr = + std::ptr::slice_from_raw_parts_mut(state.processing_files, state.processing_files_count); + let p_files_boxed = unsafe { Box::from_raw(p_files_ptr) }; + for path in p_files_boxed.iter() { + let _ = CString::from_raw(*path); + } +} diff --git a/plugins/warp/src/plugin/ffi/ty.rs b/plugins/warp/src/plugin/ffi/ty.rs new file mode 100644 index 00000000..6a16fdab --- /dev/null +++ b/plugins/warp/src/plugin/ffi/ty.rs @@ -0,0 +1,75 @@ +use crate::convert::{from_bn_type, to_bn_type}; +use crate::plugin::ffi::BNWARPType; +use binaryninja::architecture::CoreArchitecture; +use binaryninja::binary_view::BinaryView; +use binaryninja::file_metadata::FileMetadata; +use binaryninja::rc::Ref as BnRef; +use binaryninja::string::BnString; +use binaryninja::types::Type; +use binaryninjacore_sys::{BNArchitecture, BNType}; +use std::ffi::c_char; +use std::mem::ManuallyDrop; +use std::sync::Arc; + +#[no_mangle] +pub unsafe extern "C" fn BNWARPGetType( + analysis_type: *mut BNType, + confidence: u8, +) -> *mut BNWARPType { + let analysis_type = Type::from_raw(analysis_type); + // TODO: This will leak a bunch of memory, but we need to remove the view requirement anyways. + let binary_view = BinaryView::from_data(&FileMetadata::new(), &[]); + let ty = from_bn_type(&binary_view, &analysis_type, confidence); + Arc::into_raw(Arc::new(ty)) as *mut BNWARPType +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPTypeGetName(ty: *mut BNWARPType) -> *mut c_char { + let ty = ManuallyDrop::new(Arc::from_raw(ty)); + match ty.name.as_deref() { + Some(name) => BnString::into_raw(BnString::new(name)), + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPTypeGetConfidence(ty: *mut BNWARPType) -> u8 { + let ty = ManuallyDrop::new(Arc::from_raw(ty)); + ty.confidence +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPTypeGetAnalysisType( + arch: *mut BNArchitecture, + ty: *mut BNWARPType, +) -> *mut BNType { + let ty = ManuallyDrop::new(Arc::from_raw(ty)); + let analysis_ty = match arch.is_null() { + true => to_bn_type::<CoreArchitecture>(None, &ty), + false => to_bn_type(Some(CoreArchitecture::from_raw(arch)), &ty), + }; + BnRef::into_raw(analysis_ty).handle +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPNewTypeReference(ty: *mut BNWARPType) -> *mut BNWARPType { + Arc::increment_strong_count(ty); + ty +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeTypeReference(ty: *mut BNWARPType) { + if ty.is_null() { + return; + } + Arc::decrement_strong_count(ty); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeTypeList(types: *mut *mut BNWARPType, count: usize) { + let types_ptr = std::ptr::slice_from_raw_parts_mut(types, count); + let types = unsafe { Box::from_raw(types_ptr) }; + for ty in types { + unsafe { BNWARPFreeTypeReference(ty) }; + } +} diff --git a/plugins/warp/src/plugin/file.rs b/plugins/warp/src/plugin/file.rs deleted file mode 100644 index cd5fb626..00000000 --- a/plugins/warp/src/plugin/file.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::report::ReportGenerator; -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::command::Command; - -pub struct ShowFileReport; - -impl Command for ShowFileReport { - fn action(&self, view: &BinaryView) { - let view = view.to_owned(); - std::thread::spawn(move || { - let Some(path) = - binaryninja::interaction::get_open_filename_input("Select file to show", "*.warp") - else { - return; - }; - - let Ok(bytes) = std::fs::read(&path) else { - tracing::error!("Failed to read file: {:?}", path); - return; - }; - - let Some(file) = warp::WarpFile::from_bytes(&bytes) else { - tracing::error!("Failed to parse file: {:?}", path); - return; - }; - - let report_generator = ReportGenerator::new(); - if let Some(html_string) = report_generator.html_report(&file) { - view.show_html_report( - &format!("WARP File: {}", path.to_string_lossy()), - html_string.as_str(), - "", - ); - } - }); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/function.rs b/plugins/warp/src/plugin/function.rs index 36db7d55..908bd0db 100644 --- a/plugins/warp/src/plugin/function.rs +++ b/plugins/warp/src/plugin/function.rs @@ -1,18 +1,10 @@ -use crate::cache::{ - cached_function_guid, insert_cached_function_match, try_cached_function_guid, - try_cached_function_match, -}; +use crate::cache::{insert_cached_function_match, try_cached_function_match}; use crate::{ get_warp_ignore_tag_type, get_warp_include_tag_type, IGNORE_TAG_NAME, INCLUDE_TAG_NAME, }; -use binaryninja::background_task::BackgroundTask; -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::command::{Command, FunctionCommand}; +use binaryninja::binary_view::BinaryView; +use binaryninja::command::FunctionCommand; use binaryninja::function::{Function, FunctionUpdateType}; -use binaryninja::rc::Guard; -use rayon::iter::ParallelIterator; -use std::thread; -use warp::signature::function::FunctionGUID; pub struct IncludeFunction; @@ -101,91 +93,3 @@ impl FunctionCommand for RemoveFunction { try_cached_function_match(func).is_some() } } - -pub struct CopyFunctionGUID; - -impl FunctionCommand for CopyFunctionGUID { - fn action(&self, _view: &BinaryView, func: &Function) { - let Some(guid) = cached_function_guid(func, || func.lifted_il().ok()) else { - tracing::error!("Could not get guid for copied function"); - return; - }; - tracing::info!( - "Function GUID for {:?}... {}", - func.symbol().short_name(), - guid - ); - if let Ok(mut clipboard) = arboard::Clipboard::new() { - let _ = clipboard.set_text(guid.to_string()); - } - } - - fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { - true - } -} - -pub struct FindFunctionFromGUID; - -impl Command for FindFunctionFromGUID { - fn action(&self, view: &BinaryView) { - let Some(guid_str) = binaryninja::interaction::get_text_line_input( - "Function GUID", - "Find Function from GUID", - ) else { - return; - }; - - let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else { - tracing::error!("Failed to parse function guid... {}", guid_str); - return; - }; - - tracing::info!("Searching functions for GUID... {}", searched_guid); - let funcs = view.functions(); - let view = view.to_owned(); - thread::spawn(move || { - let background_task = BackgroundTask::new( - &format!("Searching functions for GUID... {}", searched_guid), - false, - ); - - // Only run this for functions which have already generated a GUID. - let matched: Vec<Guard<Function>> = funcs - .par_iter() - .filter(|func| { - try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid) - }) - .collect(); - - if matched.is_empty() { - tracing::info!("No matches found for GUID... {}", searched_guid); - } else { - for func in &matched { - // Also navigate the user, as that is probably what they want. - if matched.len() == 1 { - let current_view = view.file().current_view(); - if view - .file() - .navigate_to(¤t_view, func.start()) - .is_err() - { - tracing::error!( - "Failed to navigate to found function 0x{:0x} in view {}", - func.start(), - current_view - ); - } - } - tracing::info!("Match found at function... 0x{:0x}", func.start()); - } - } - - background_task.finish(); - }); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/project.rs b/plugins/warp/src/plugin/project.rs deleted file mode 100644 index b6081c1f..00000000 --- a/plugins/warp/src/plugin/project.rs +++ /dev/null @@ -1,286 +0,0 @@ -use crate::processor::{ - new_processing_state_background_thread, CompressionTypeField, FileDataKindField, - FileFilterField, ProcessingFileState, RequestAnalysisField, WarpFileProcessor, -}; -use crate::report::{ReportGenerator, ReportKindField}; -use binaryninja::background_task::BackgroundTask; -use binaryninja::command::ProjectCommand; -use binaryninja::interaction::{Form, FormInputField}; -use binaryninja::project::folder::ProjectFolder; -use binaryninja::project::Project; -use binaryninja::rc::Ref; -use binaryninja::worker_thread::{set_worker_thread_count, worker_thread_count}; -use rayon::ThreadPoolBuilder; -use regex::Regex; -use std::path::Path; -use std::thread; -use std::time::Instant; -use warp::WarpFile; - -pub struct CreateSignaturesForm { - form: Form, -} - -impl CreateSignaturesForm { - pub fn new(_project: &Project) -> CreateSignaturesForm { - let mut form = Form::new("Create Signature File"); - form.add_field(Self::file_data_field()); - form.add_field(Self::file_filter_field()); - form.add_field(Self::generated_report_field()); - form.add_field(Self::compression_type_field()); - form.add_field(Self::save_individual_files_field()); - form.add_field(Self::skip_existing_warp_files_field()); - form.add_field(Self::request_analysis_field()); - form.add_field(Self::processing_thread_count_field()); - Self { form } - } - - pub fn file_data_field() -> FormInputField { - FileDataKindField::default().to_field() - } - - pub fn file_data_kind(&self) -> FileDataKindField { - FileDataKindField::from_form(&self.form).unwrap_or_default() - } - - pub fn file_filter_field() -> FormInputField { - FileFilterField::to_field() - } - - pub fn file_filter(&self) -> Option<Result<Regex, regex::Error>> { - FileFilterField::from_form(&self.form) - } - - pub fn generated_report_field() -> FormInputField { - ReportKindField::default().to_field() - } - - pub fn generated_report_kind(&self) -> ReportKindField { - ReportKindField::from_form(&self.form).unwrap_or_default() - } - - pub fn compression_type_field() -> FormInputField { - CompressionTypeField::default().to_field() - } - - pub fn compression_type(&self) -> CompressionTypeField { - CompressionTypeField::from_form(&self.form).unwrap_or_default() - } - - pub fn save_individual_files_field() -> FormInputField { - FormInputField::Checkbox { - prompt: "Save individual files".to_string(), - default: None, - value: false, - } - } - - pub fn save_individual_files(&self) -> bool { - let field = self.form.get_field_with_name("Save individual files"); - let field_value = field.and_then(|f| f.try_value_int()).unwrap_or(0); - match field_value { - 1 => true, - _ => false, - } - } - - pub fn skip_existing_warp_files_field() -> FormInputField { - FormInputField::Checkbox { - prompt: "Skip existing WARP files".to_string(), - default: Some(true), - value: false, - } - } - - pub fn skip_existing_warp_files(&self) -> bool { - let field = self.form.get_field_with_name("Skip existing WARP files"); - let field_value = field.and_then(|f| f.try_value_int()).unwrap_or(0); - match field_value { - 1 => true, - _ => false, - } - } - - pub fn request_analysis_field() -> FormInputField { - RequestAnalysisField::default().to_field() - } - - pub fn request_analysis(&self) -> RequestAnalysisField { - RequestAnalysisField::from_form(&self.form).unwrap_or_default() - } - - pub fn processing_thread_count_field() -> FormInputField { - let default = rayon::current_num_threads(); - FormInputField::Integer { - prompt: "Processing threads".to_string(), - default: Some(default as i64), - value: 0, - } - } - - pub fn processing_thread_count(&self) -> usize { - let field = self.form.get_field_with_name("Processing threads"); - let worker_thread_count = worker_thread_count(); - field - .and_then(|f| f.try_value_int()) - .unwrap_or(worker_thread_count as i64) - .abs() as usize - } - - pub fn prompt(&mut self) -> bool { - self.form.prompt() - } -} - -pub struct CreateSignatures; - -impl CreateSignatures { - pub fn execute(project: Ref<Project>) { - let mut form = CreateSignaturesForm::new(&project); - if !form.prompt() { - return; - } - let file_data_kind = form.file_data_kind(); - let report_kind = form.generated_report_kind(); - let compression_type = form.compression_type(); - let save_individual_files = form.save_individual_files(); - let skip_existing_warp_files = form.skip_existing_warp_files(); - let request_analysis = form.request_analysis(); - let processing_thread_count = form.processing_thread_count(); - - // Save the warp file to the project. - let save_warp_file = move |project: &Project, - folder: Option<&ProjectFolder>, - name: &str, - warp_file: &WarpFile| { - if project - .create_file(&warp_file.to_bytes(), folder, name, "") - .is_err() - { - tracing::error!("Failed to create project file!"); - } - - let report = ReportGenerator::new(); - if let Some(generated) = report.report(&report_kind, &warp_file) { - let ext = report.report_extension(&report_kind).unwrap_or_default(); - let file_name = format!("{}_report.{}", name, ext); - if project - .create_file(&generated.into_bytes(), folder, &file_name, "Warp file") - .is_err() - { - tracing::error!("Failed to create project file!"); - } - } - }; - - // Optional callback for saving off the individual project files. - let callback_project = project.clone(); - let save_individual_files_cb = move |path: &Path, file: &WarpFile| { - if file.chunks.is_empty() { - tracing::debug!("Skipping empty file: {}", path.display()); - return; - } - // The path returned will be the one on disk, so we will go and grab the project for it. - let Some(project_file) = callback_project.file_by_path(path) else { - tracing::error!("Failed to find project file for path: {}", path.display()); - return; - }; - let project_file = project_file.to_owned(); - let file_name = format!("{}.warp", project_file.name()); - let project_folder = project_file.folder(); - save_warp_file( - &callback_project, - project_folder.as_deref(), - &file_name, - file, - ); - }; - - let mut processor = WarpFileProcessor::new() - .with_file_data(file_data_kind) - .with_compression_type(compression_type) - .with_skip_warp_files(skip_existing_warp_files) - .with_request_analysis(request_analysis == RequestAnalysisField::Yes); - - if save_individual_files { - processor = processor.with_processed_file_callback(save_individual_files_cb); - } - - // Construct the user-supplied file filter. This will filter files in the project only, files - // in an archive will be considered a part of the archive file. - if let Some(filter) = form.file_filter() { - match filter { - Ok(f) => { - processor = processor.with_file_filter(f); - } - Err(err) => { - tracing::error!("Failed to parse file filter: {}", err); - tracing::error!( - "Consider using a substring instead of a glob pattern, e.g. *.exe => exe" - ); - return; - } - } - } - - // This thread will show the state in a background task. - let background_task = BackgroundTask::new("Processing started...", true); - new_processing_state_background_thread(background_task.clone(), processor.state()); - - let Ok(thread) = ThreadPoolBuilder::new() - .num_threads(processing_thread_count) - .build() - else { - tracing::error!("Failed to create processing thread pool!"); - return; - }; - - // We have to bump the number of worker threads up so that view destruction's and analysis - // does not halt, using a multiple of three seems good. This is only temporary. - let previous_worker_thread_count = worker_thread_count(); - let upgraded_thread_count = previous_worker_thread_count * 3; - if upgraded_thread_count > previous_worker_thread_count { - tracing::info!( - "Setting worker thread count to {} for the duration of processing...", - upgraded_thread_count - ); - set_worker_thread_count(upgraded_thread_count); - } - - let start = Instant::now(); - thread.scope(|_| match processor.process_project(&project) { - Ok(warp_file) => { - save_warp_file(&project, None, "generated.warp", &warp_file); - } - Err(e) => { - tracing::error!("Failed to process project: {}", e); - } - }); - - let processed_file_count = processor - .state() - .files_with_state(ProcessingFileState::Processed); - tracing::info!( - "Processing {} project files took: {:?}", - processed_file_count, - start.elapsed() - ); - // Reset the worker thread count to the user specified; either way it will not persist. - set_worker_thread_count(previous_worker_thread_count); - // Tells the processing state thread to finish. - background_task.finish(); - } -} - -impl ProjectCommand for CreateSignatures { - fn action(&self, project: &Project) { - let project = project.to_owned(); - thread::spawn(move || { - CreateSignatures::execute(project); - }); - } - - fn valid(&self, _view: &Project) -> bool { - true - } -} |
