diff options
Diffstat (limited to 'plugins/warp/src/matcher.rs')
| -rw-r--r-- | plugins/warp/src/matcher.rs | 557 |
1 files changed, 229 insertions, 328 deletions
diff --git a/plugins/warp/src/matcher.rs b/plugins/warp/src/matcher.rs index 0ce258ad..c6ab7f9f 100644 --- a/plugins/warp/src/matcher.rs +++ b/plugins/warp/src/matcher.rs @@ -1,363 +1,282 @@ +use crate::cache::cached_constraints; +use crate::container::{Container, SourceId}; +use crate::convert::to_bn_type; use binaryninja::architecture::Architecture as BNArchitecture; use binaryninja::binary_view::{BinaryView, BinaryViewExt}; use binaryninja::function::Function as BNFunction; -use binaryninja::platform::Platform; -use binaryninja::rc::Guard; -use binaryninja::rc::Ref as BNRef; -use dashmap::DashMap; +use binaryninja::settings::{QueryOptions, Settings as BNSettings}; use serde_json::json; use std::cmp::Ordering; -use std::collections::{HashMap, HashSet}; -use std::hash::{DefaultHasher, Hash, Hasher}; -use std::path::PathBuf; -use std::sync::OnceLock; -use walkdir::{DirEntry, WalkDir}; +use std::collections::HashSet; +use std::hash::Hash; use warp::r#type::class::TypeClass; -use warp::r#type::guid::TypeGUID; use warp::r#type::Type; -use warp::signature::function::{Function, FunctionGUID}; -use warp::signature::Data; - -use crate::cache::{ - cached_adjacency_constraints, cached_call_site_constraints, cached_function_match, - try_cached_function_guid, -}; -use crate::convert::to_bn_type; -use crate::plugin::on_matched_function; -use crate::{core_signature_dir, user_signature_dir}; - -pub static PLAT_MATCHER_CACHE: OnceLock<DashMap<PlatformID, Matcher>> = OnceLock::new(); - -pub fn cached_function_matcher(function: &BNFunction) { - let platform = function.platform(); - let platform_id = PlatformID::from(platform.as_ref()); - let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default); - match matcher_cache.get(&platform_id) { - Some(matcher) => matcher.match_function(function), - None => { - let matcher = Matcher::from_platform(platform); - matcher.match_function(function); - matcher_cache.insert(platform_id, matcher); - } - } -} - -// TODO: Maybe just clear individual platforms? This works well enough either way. -pub fn invalidate_function_matcher_cache() { - let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default); - matcher_cache.clear(); -} +use warp::signature::function::Function; -#[derive(Debug, Default, Clone)] +/// A matcher represents a specific configuration for identify functions using WARP. A matcher +/// does not store/own any WARP information directly, instead the matcher is given a [`Container`] +/// that holds all of that information. +/// +/// The separation of the WARP information from the [`Matcher`] allows a greater degree of control and +/// provides a clean interface for further logic to be built on top of. A matcher instance, unlike +/// a typical [`Container`] implementation, is cheap to create. +#[derive(Debug, Clone, Copy)] pub struct Matcher { - // TODO: Storing the settings here means that they are effectively global. - // TODO: If we want scoped or view settings they must be moved out. pub settings: MatcherSettings, - pub functions: DashMap<FunctionGUID, Vec<Function>>, - pub types: DashMap<TypeGUID, Type>, - pub named_types: DashMap<String, Type>, } impl Matcher { - /// Create a matcher from the platforms signature subdirectory. - pub fn from_platform(platform: BNRef<Platform>) -> Self { - let platform_name = platform.name().to_string(); + pub fn new(settings: MatcherSettings) -> Self { + Matcher { settings } + } - // Get core and user signatures. - // TODO: Separate each file into own bucket for filtering? - let plat_core_sig_dir = core_signature_dir().join(&platform_name); - let mut data = get_data_from_dir(&plat_core_sig_dir); - let plat_user_sig_dir = user_signature_dir().join(&platform_name); - let user_data = get_data_from_dir(&plat_user_sig_dir); + pub fn match_function_from_constraints<'a>( + &self, + function: &BNFunction, + matched_functions: &'a [Function], + ) -> Option<&'a Function> { + let function_len = function.highest_address() - function.lowest_address(); + let is_function_trivial = { function_len < self.settings.trivial_function_len }; + let is_function_allowed = { + function_len >= self.settings.minimum_function_len + && function_len < self.settings.maximum_function_len.unwrap_or(u64::MAX) + }; - data.extend(user_data); - let merged_data = Data::merge(data.values().cloned().collect::<Vec<_>>()); - log::debug!("Loaded signatures: {:?}", data.keys()); - Matcher::from_data(merged_data) - } + // Function isn't allowed, or no matches so stop early. + if !is_function_allowed || matched_functions.is_empty() { + return None; + } - pub fn from_data(data: Data) -> Self { - let functions = data.functions.into_iter().fold( - DashMap::new(), - |map: DashMap<FunctionGUID, Vec<_>>, func| { - map.entry(func.guid).or_default().push(func); - map - }, - ); - let types = data - .types - .iter() - .map(|ty| (ty.guid, ty.ty.clone())) - .collect(); - let named_types = data - .types - .into_iter() - .filter_map(|ty| ty.ty.name.to_owned().map(|name| (name, ty.ty))) - .collect(); + // If we have a single possible match than that must be our function. + // We must also not be a trivial function, as those will likely be artifacts of an incomplete dataset + if matched_functions.len() == 1 && !is_function_trivial { + return matched_functions.first(); + } + // Filter out adjacent functions which are trivial, this helps avoid false positives. + // NOTE: If the user sets `trivial_function_adjacent_allowed` to true we will always match. + // TODO: Expand on this more later. We might want to match on adjacent functions smaller than this. + let adjacent_function_filter = |adj_func: &BNFunction| { + let adj_func_len = adj_func.highest_address() - adj_func.lowest_address(); + adj_func_len >= self.settings.trivial_function_len + || self.settings.trivial_function_adjacent_allowed + }; - Self { - // NOTE: Settings will be retrieved from global state every time this is called. - settings: MatcherSettings::global(), - functions, - types, - named_types, + // TODO: When the highest count has two matches we return None. Need to alert the user. + // "common" being the intersection between the observed and matched. + let constraints = cached_constraints(function, adjacent_function_filter); + let mut highest_count = 0; + let mut matched_func = None; + for matched in matched_functions { + let common_count = constraints.intersection(&matched.constraints).count(); + match common_count.cmp(&highest_count) { + Ordering::Equal => matched_func = None, + Ordering::Greater => { + highest_count = common_count; + matched_func = Some(matched); + } + Ordering::Less => {} + } } - } - pub fn extend_with_matcher(&mut self, matcher: Matcher) { - self.functions.extend(matcher.functions); - self.types.extend(matcher.types); - self.named_types.extend(matcher.named_types); + // If we have a match below the minimum threshold, ignore. + match highest_count.cmp(&self.settings.minimum_matched_constraints) { + Ordering::Equal => matched_func, + Ordering::Greater => matched_func, + Ordering::Less => None, + } } - pub fn add_type_to_view<A: BNArchitecture>(&self, view: &BinaryView, arch: &A, ty: &Type) { + // TODO: I would really like for WARP types to be added in a seperate type container, so that we don't + // TODO: just add them as system or user types. + pub fn add_type_to_view<A: BNArchitecture>( + &self, + container: &dyn Container, + source: &SourceId, + view: &BinaryView, + arch: &A, + ty: &Type, + ) where + Self: Sized, + { fn inner_add_type_to_view<A: BNArchitecture>( - matcher: &Matcher, + container: &dyn Container, + source: &SourceId, view: &BinaryView, arch: &A, visited_refs: &mut HashSet<String>, ty: &Type, ) { - let ty_id_str = TypeGUID::from(ty).to_string(); - if view.type_by_id(&ty_id_str).is_some() { - // Type already added. - return; - } // Type not already added to the view. // Verify all nested types are added before adding type. - match ty.class.as_ref() { - TypeClass::Pointer(c) => { - inner_add_type_to_view(matcher, view, arch, visited_refs, &c.child_type) - } - TypeClass::Array(c) => { - inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type) - } + match &ty.class { + TypeClass::Pointer(c) => inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &c.child_type, + ), + TypeClass::Array(c) => inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &c.member_type, + ), TypeClass::Structure(c) => { for member in &c.members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &member.ty, + ) } } - TypeClass::Enumeration(c) => { - inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type) - } + TypeClass::Enumeration(c) => inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &c.member_type, + ), TypeClass::Union(c) => { for member in &c.members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &member.ty, + ) } } TypeClass::Function(c) => { for out_member in &c.out_members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &out_member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &out_member.ty, + ) } for in_member in &c.in_members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &in_member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &in_member.ty, + ) } } TypeClass::Referrer(c) => { // Check to see if the referrer has been added to the view. - let mut resolved = false; + let mut resolved_ty = None; if let Some(ref_guid) = c.guid { // NOTE: We do not need to check for cyclic reference here because // NOTE: GUID references are unable to be referenced by themselves. if view.type_by_id(&ref_guid.to_string()).is_none() { // Add the referrer to the view if it is in the Matcher types - if let Some(ref_ty) = matcher.types.get(&ref_guid) { - inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty); - resolved = true; + if let Ok(Some(ref_ty)) = container.type_with_guid(source, &ref_guid) { + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &ref_ty, + ); + resolved_ty = Some(ref_ty); } } } if let Some(ref_name) = &c.name { // Only try and resolve by name if not already visiting. - if !resolved + if resolved_ty.is_none() && visited_refs.insert(ref_name.to_string()) && view.type_by_name(ref_name).is_none() { // Add the ref to the view if it is in the Matcher types - if let Some(ref_ty) = matcher.named_types.get(ref_name) { - inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty); + let type_guids = container + .type_guids_with_name(source, ref_name) + .unwrap_or_default(); + // TODO: What happens if we have more than one? + if type_guids.len() == 1 { + // TODO: What happens if we cant get the guid? + if let Ok(Some(ref_ty)) = + container.type_with_guid(source, &type_guids[0]) + { + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &ref_ty, + ); + resolved_ty = Some(ref_ty); + } } // No longer visiting type. visited_refs.remove(ref_name); } } - // All nested types _should_ be added now, we can add this type. - // TODO: Do we want to make unnamed types visible? I think we should, but some people might be opposed. - let ty_name = ty.name.to_owned().unwrap_or_else(|| ty_id_str.clone()); - view.define_auto_type_with_id(ty_name, &ty_id_str, &to_bn_type(arch, ty)); - } - _ => {} - } - } - inner_add_type_to_view(self, view, arch, &mut HashSet::new(), ty) - } - - pub fn match_function(&self, function: &BNFunction) { - // Call this the first time you matched on the function. - let resolve_new_types = |matched: &Function| { - // We also want to resolve the types here. - if let TypeClass::Function(c) = matched.ty.class.as_ref() { - // Recursively go through the function type and resolve referrers - let view = function.view(); - let arch = function.arch(); - for out_member in &c.out_members { - self.add_type_to_view(&view, &arch, &out_member.ty); - } - for in_member in &c.in_members { - self.add_type_to_view(&view, &arch, &in_member.ty); - } - } - }; - - if let Some(matched_function) = cached_function_match(function, || { - // We have yet to match on this function. - let function_len = function.highest_address() - function.lowest_address(); - let is_function_trivial = { function_len < self.settings.trivial_function_len }; - let is_function_allowed = { - function_len > self.settings.minimum_function_len - && function_len < self.settings.maximum_function_len.unwrap_or(u64::MAX) - }; - let warp_func_guid = try_cached_function_guid(function)?; - match self.functions.get(&warp_func_guid) { - _ if !is_function_allowed => None, - Some(matched) if matched.len() == 1 && !is_function_trivial => { - resolve_new_types(&matched[0]); - Some(matched[0].to_owned()) - } - Some(matched) => { - let matched_on = self.match_function_from_constraints(function, &matched)?; - resolve_new_types(matched_on); - Some(matched_on.to_owned()) - } - None => None, - } - }) { - on_matched_function(function, &matched_function); - } - } - - pub fn match_function_from_constraints<'a>( - &self, - function: &BNFunction, - matched_functions: &'a [Function], - ) -> Option<&'a Function> { - // Filter out adjacent functions which are trivial, this helps avoid false positives. - // NOTE: If the user sets `trivial_function_adjacent_allowed` to true we will always match. - // TODO: Expand on this more later. We might want to match on adjacent functions smaller than this. - let adjacent_function_filter = |adj_func: &BNFunction| { - let adj_func_len = adj_func.highest_address() - adj_func.lowest_address(); - adj_func_len > self.settings.trivial_function_len - || self.settings.trivial_function_adjacent_allowed - }; - - let call_sites = cached_call_site_constraints(function); - let adjacent = cached_adjacency_constraints(function, adjacent_function_filter); - - // "common" being the intersection between the observed and matched. - fn find_highest_common_count<'a, F, T>( - observed_items: &HashSet<T>, - matched_functions: &'a [Function], - extract_items: F, - ) -> (usize, Option<&'a Function>) - where - F: Fn(&Function) -> HashSet<T>, - T: Hash + Eq, - { - let mut highest_count = 0; - let mut matched_func = None; - for matched in matched_functions { - let matched_items = extract_items(matched); - let common_count = observed_items.intersection(&matched_items).count(); - match common_count.cmp(&highest_count) { - Ordering::Equal => matched_func = None, - Ordering::Greater => { - highest_count = common_count; - matched_func = Some(matched); + // Adds the ref'd type to the view. + match (c.guid, &c.name, resolved_ty) { + (Some(guid), Some(name), Some(ref_ty)) => { + view.define_auto_type_with_id( + name, + &guid.to_string(), + &to_bn_type(arch, &ref_ty), + ); + } + (Some(_guid), Some(_name), None) => { + // TODO: Got name and guid but no type? Do we add a bare NTR? + } + (Some(_guid), None, _) => { + // TODO: How would we reference this type without a name??? + } + (None, Some(_name), _) => { + // TODO: Cyclic type reference if no guid, so... dont define? + } + (None, None, _) => { + // TODO: What?!?!? + } } - Ordering::Less => {} } + TypeClass::Void + | TypeClass::Boolean(_) + | TypeClass::Integer(_) + | TypeClass::Character(_) + | TypeClass::Float(_) => {} } - (highest_count, matched_func) - } - - let call_site_guids: HashSet<_> = call_sites.iter().filter_map(|c| c.guid).collect(); - let call_site_symbol_names: HashSet<_> = call_sites - .into_iter() - .filter_map(|c| c.symbol.map(|s| s.name)) - .collect(); - let adjacent_guids: HashSet<_> = adjacent.iter().filter_map(|c| c.guid).collect(); - let adjacent_symbol_names: HashSet<_> = adjacent - .into_iter() - .filter_map(|c| c.symbol.map(|s| s.name)) - .collect(); - - // Ordered from the lowest confidence to the highest confidence constraint. - let checked_constraints = [ - find_highest_common_count(&adjacent_symbol_names, matched_functions, |matched| { - matched - .constraints - .adjacent - .iter() - .filter_map(|c| c.symbol.to_owned().map(|s| s.name)) - .collect() - }), - find_highest_common_count(&adjacent_guids, matched_functions, |matched| { - matched - .constraints - .adjacent - .iter() - .filter_map(|c| c.guid) - .collect() - }), - find_highest_common_count(&call_site_symbol_names, matched_functions, |matched| { - matched - .constraints - .call_sites - .iter() - .filter_map(|c| c.symbol.to_owned().map(|s| s.name)) - .collect() - }), - find_highest_common_count(&call_site_guids, matched_functions, |matched| { - matched - .constraints - .call_sites - .iter() - .filter_map(|c| c.guid) - .collect() - }), - ]; - // If there is a tie, the last one wins, which should be call_site guid. - checked_constraints - .into_iter() - .max_by_key(|&(count, _)| count) - .filter(|&(count, _)| count >= self.settings.minimum_matched_constraints) - .and_then(|(_, func)| func) + // TODO: Some refs likely need to ommitted because they are just that, refs to another type. + // let guid = TypeGUID::from(ty); + // let name = ty.name.clone().unwrap_or(guid.to_string()); + // view.define_auto_type_with_id(name, &guid.to_string(), &to_bn_type(arch, ty)); + } + inner_add_type_to_view(container, source, view, arch, &mut HashSet::new(), ty) } } -fn get_data_from_dir(dir: &PathBuf) -> HashMap<PathBuf, Data> { - let data_from_entry = |entry: DirEntry| { - let path = entry.path(); - let contents = std::fs::read(path).ok()?; - Data::from_bytes(&contents) - }; - - WalkDir::new(dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.file_type().is_file()) - .filter_map(|e| Some((e.clone().into_path(), data_from_entry(e)?))) - .collect() -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MatcherSettings { /// Any function under this length will be required to constrain. /// - /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default. + /// This is set to [MatcherSettings::TRIVIAL_FUNCTION_LEN_DEFAULT] by default. pub trivial_function_len: u64, /// Any function under this length will not match. /// @@ -367,13 +286,15 @@ pub struct MatcherSettings { /// /// This is set to [MatcherSettings::MAXIMUM_FUNCTION_LEN_DEFAULT] by default. pub maximum_function_len: Option<u64>, - /// For a successful constrained function match the number of matches must be above this. + /// For a successful constrained function match, the number of matches must be above this. /// - /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default. + /// This is set to [MatcherSettings::MINIMUM_MATCHED_CONSTRAINTS_DEFAULT] by default. pub minimum_matched_constraints: usize, - /// For a successful constrained function match the number of matches must be above this. + /// When function constraints are checked, if this is enabled, functions can match based off trivial adjacent functions. + /// + /// Any function under `trivial_function_len` will be considered trivial. /// - /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default. + /// This is set to [MatcherSettings::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_DEFAULT] by default. pub trivial_function_adjacent_allowed: bool, } @@ -395,17 +316,15 @@ impl MatcherSettings { /// /// Call this once when you initialize so that the settings exist. /// - /// NOTE: If you are using this as a library then just modify the MatcherSettings directly + /// NOTE: If you are using this as a library, then modify the [`MatcherSettings`] directly /// in the matcher instance, that way you don't need to round-trip through Binary Ninja. - pub fn register() { - let bn_settings = binaryninja::settings::Settings::new(); - + pub fn register(bn_settings: &mut BNSettings) { let trivial_function_len_props = json!({ "title" : "Trivial Function Length", "type" : "number", "default" : Self::TRIVIAL_FUNCTION_LEN_DEFAULT, "description" : "Functions below this length in bytes will be required to match on constraints.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::TRIVIAL_FUNCTION_LEN_SETTING, @@ -417,7 +336,7 @@ impl MatcherSettings { "type" : "number", "default" : Self::MINIMUM_FUNCTION_LEN_DEFAULT, "description" : "Functions below this length will not be matched.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::MINIMUM_FUNCTION_LEN_SETTING, @@ -429,7 +348,7 @@ impl MatcherSettings { "type" : "number", "default" : Self::MAXIMUM_FUNCTION_LEN_DEFAULT, "description" : "Functions above this length will not be matched. A value of 0 will disable this check.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::MAXIMUM_FUNCTION_LEN_SETTING, @@ -441,7 +360,7 @@ impl MatcherSettings { "type" : "number", "default" : Self::MINIMUM_MATCHED_CONSTRAINTS_DEFAULT, "description" : "When function constraints are checked the amount of constraints matched must be at-least this.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING, @@ -453,7 +372,7 @@ impl MatcherSettings { "type" : "boolean", "default" : Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_DEFAULT, "description" : "When function constraints are checked if this is enabled functions can match based off trivial adjacent functions.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING, @@ -461,26 +380,32 @@ impl MatcherSettings { ); } - pub fn global() -> Self { + /// Retrieve matcher settings from [`BNSettings`]. + pub fn from_settings(bn_settings: &BNSettings, query_opts: &mut QueryOptions) -> Self { let mut settings = MatcherSettings::default(); - let bn_settings = binaryninja::settings::Settings::new(); if bn_settings.contains(Self::TRIVIAL_FUNCTION_LEN_SETTING) { settings.trivial_function_len = - bn_settings.get_integer(Self::TRIVIAL_FUNCTION_LEN_SETTING); + bn_settings.get_integer_with_opts(Self::TRIVIAL_FUNCTION_LEN_SETTING, query_opts); } if bn_settings.contains(Self::MINIMUM_FUNCTION_LEN_SETTING) { settings.minimum_function_len = - bn_settings.get_integer(Self::MINIMUM_FUNCTION_LEN_SETTING); + bn_settings.get_integer_with_opts(Self::MINIMUM_FUNCTION_LEN_SETTING, query_opts); } if bn_settings.contains(Self::MAXIMUM_FUNCTION_LEN_SETTING) { - match bn_settings.get_integer(Self::MAXIMUM_FUNCTION_LEN_SETTING) { + match bn_settings.get_integer_with_opts(Self::MAXIMUM_FUNCTION_LEN_SETTING, query_opts) + { 0 => settings.maximum_function_len = None, len => settings.maximum_function_len = Some(len), } } if bn_settings.contains(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING) { - settings.minimum_matched_constraints = - bn_settings.get_integer(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING) as usize; + settings.minimum_matched_constraints = bn_settings + .get_integer_with_opts(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING, query_opts) + as usize; + } + if bn_settings.contains(Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING) { + settings.trivial_function_adjacent_allowed = bn_settings + .get_bool_with_opts(Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING, query_opts); } settings } @@ -498,27 +423,3 @@ impl Default for MatcherSettings { } } } - -/// A unique platform ID, used for caching. -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct PlatformID(u64); - -impl From<&Platform> for PlatformID { - fn from(value: &Platform) -> Self { - let mut hasher = DefaultHasher::new(); - hasher.write(value.name().as_bytes()); - Self(hasher.finish()) - } -} - -impl From<BNRef<Platform>> for PlatformID { - fn from(value: BNRef<Platform>) -> Self { - Self::from(value.as_ref()) - } -} - -impl From<Guard<'_, Platform>> for PlatformID { - fn from(value: Guard<'_, Platform>) -> Self { - Self::from(value.as_ref()) - } -} |
