diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 553c47b21..02f135302 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -6,6 +6,7 @@ - ファイル(CSV, JSON, JSONL)出力の際に`Level`の余分なスペースを削除した。 (#979) (@hitenkoku) - `-M, --multiline`オプション利用時にルール作者名の出力を複数行出力対応をした。 (#980) (@hitenkoku) +- Stringの代わりにCoWを利用することで、約5%の速度向上を実現した。 (#984) (@hitenkoku) **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index c53a745b8..9c2f18a1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Removed an extra space when outputting `Level` to files(CSV, JSON, JSONL). (#979) (@hitenkoku) - Made rule authors multiple lines with `-M, --multiline` option. (#980) (@hitenkoku) +- Approximately 3-5% speed increase by replaced String with CoW. (#984) (@hitenkoku) **Bug Fixes:** diff --git a/src/afterfact.rs b/src/afterfact.rs index a9fd88c55..40bebe1ad 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -101,7 +101,7 @@ pub fn set_output_color(no_color_flag: bool) -> HashMap { fn _get_output_color(color_map: &HashMap, level: &str) -> Option { let mut color = None; if let Some(c) = color_map.get(&CompactString::from(level.to_lowercase())) { - color = Some(c.output_color.to_owned()); + color = Some(c.output_color); } color } @@ -112,7 +112,7 @@ fn _get_table_color( ) -> Option { let mut color = None; if let Some(c) = color_map.get(&CompactString::from(level.to_lowercase())) { - color = Some(c.table_color.to_owned()); + color = Some(c.table_color); } color } @@ -282,7 +282,7 @@ fn emit_csv( let mut rule_title_path_map: HashMap = HashMap::new(); let mut rule_author_counter: HashMap = HashMap::new(); - let levels = Vec::from(["crit", "high", "med ", "low ", "info", "undefined"]); + let levels = ["crit", "high", "med ", "low ", "info", "undefined"]; // レベル別、日ごとの集計用変数の初期化 for level_init in levels { detect_counts_by_date_and_level.insert(CompactString::from(level_init), HashMap::new()); @@ -292,7 +292,7 @@ fn emit_csv( if displayflag { println!(); } - let mut timestamps: Vec = Vec::new(); + let mut timestamps: Vec = vec![0; MESSAGEKEYS.lock().unwrap().len()]; let mut plus_header = true; let mut detected_record_idset: HashSet = HashSet::new(); @@ -361,7 +361,7 @@ fn emit_csv( get_writable_color( _get_output_color( &color_map, - LEVEL_FULL.get(&detect_info.level.as_str()).unwrap_or(&""), + LEVEL_FULL.get(detect_info.level.as_str()).unwrap_or(&""), ), stored_static.common_options.no_color, ), @@ -762,12 +762,12 @@ fn _get_serialized_disp_output(data: &Vec<(CompactString, Profile)>, header: boo ret.push( _format_cellpos( &d.1.to_value() - .replace("🛂r", "\r") - .replace("🛂n", "\n") - .replace("🛂t", "\t") - .replace(['\n', '\r', '\t'], " ") - .split_whitespace() - .join(" "), + .replace("🛂r", "") + .replace("🛂n", "") + .replace("🛂t", ""), + // .replace(['\n', '\r', '\t'], " ") + // .split_whitespace() + // .join(" "), ColPos::First, ) .replace('|', "🦅"), @@ -776,12 +776,12 @@ fn _get_serialized_disp_output(data: &Vec<(CompactString, Profile)>, header: boo ret.push( _format_cellpos( &d.1.to_value() - .replace("🛂r", "\r") - .replace("🛂n", "\n") - .replace("🛂t", "\t") - .replace(['\n', '\r', '\t'], " ") - .split_whitespace() - .join(" "), + .replace("🛂r", "") + .replace("🛂n", "") + .replace("🛂t", ""), + // .replace(['\n', '\r', '\t'], " ") + // .split_whitespace() + // .join(" "), ColPos::Last, ) .replace('|', "🦅"), @@ -790,12 +790,12 @@ fn _get_serialized_disp_output(data: &Vec<(CompactString, Profile)>, header: boo ret.push( _format_cellpos( &d.1.to_value() - .replace("🛂r", "\r") - .replace("🛂n", "\n") - .replace("🛂t", "\t") - .replace(['\n', '\r', '\t'], " ") - .split_whitespace() - .join(" "), + .replace("🛂r", "") + .replace("🛂n", "") + .replace("🛂t", ""), + // .replace(['\n', '\r', '\t'], " ") + // .split_whitespace() + // .join(" "), ColPos::Other, ) .replace('|', "🦅"), @@ -934,27 +934,28 @@ fn _print_detection_summary_by_date( for (idx, level) in level_abbr.iter().enumerate() { // output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施 let detections_by_day = detect_counts_by_date.get(&level[1]).unwrap(); - let mut max_detect_str = String::default(); + let mut max_detect_str = CompactString::default(); let mut tmp_cnt: i128 = 0; let mut exist_max_data = false; for (date, cnt) in detections_by_day { if cnt > &tmp_cnt { exist_max_data = true; - max_detect_str = format!("{} ({})", date, cnt.to_formatted_string(&Locale::en)); + max_detect_str = + format!("{} ({})", date, cnt.to_formatted_string(&Locale::en)).into(); tmp_cnt = *cnt; } } wtr.set_color(ColorSpec::new().set_fg(_get_output_color( color_map, - LEVEL_FULL.get(&level[1].as_str()).unwrap(), + LEVEL_FULL.get(level[1].as_str()).unwrap(), ))) .ok(); if !exist_max_data { - max_detect_str = "n/a".to_string(); + max_detect_str = "n/a".into(); } let output_str = format!( "{}: {}", - LEVEL_FULL.get(&level[1].as_str()).unwrap(), + LEVEL_FULL.get(level[1].as_str()).unwrap(), &max_detect_str ); write!(wtr, "{output_str}").ok(); @@ -998,8 +999,8 @@ fn _print_detection_summary_by_computer( if stored_static.html_report_flag { html_output_stock.push(format!( "### Computers with most unique {} detections: {{#computers_with_most_unique_{}_detections}}", - LEVEL_FULL.get(&level[1].as_str()).unwrap(), - LEVEL_FULL.get(&level[1].as_str()).unwrap() + LEVEL_FULL.get(level[1].as_str()).unwrap(), + LEVEL_FULL.get(level[1].as_str()).unwrap() )); for x in sorted_detections.iter() { html_output_stock.push(format!( @@ -1025,13 +1026,13 @@ fn _print_detection_summary_by_computer( wtr.set_color(ColorSpec::new().set_fg(_get_output_color( color_map, - LEVEL_FULL.get(&level[1].as_str()).unwrap(), + LEVEL_FULL.get(level[1].as_str()).unwrap(), ))) .ok(); writeln!( wtr, "{}: {}", - LEVEL_FULL.get(&level[1].as_str()).unwrap(), + LEVEL_FULL.get(level[1].as_str()).unwrap(), &result_str ) .ok(); @@ -1054,15 +1055,15 @@ fn _print_detection_summary_tables( let mut output = vec![]; let mut col_color = vec![]; for level in level_abbr.iter() { - let mut col_output: Vec = vec![]; + let mut col_output: Nested = Nested::::new(); col_output.push(format!( "Top {} alerts:", - LEVEL_FULL.get(&level[1].as_str()).unwrap() + LEVEL_FULL.get(level[1].as_str()).unwrap() )); col_color.push(_get_table_color( color_map, - LEVEL_FULL.get(&level[1].as_str()).unwrap(), + LEVEL_FULL.get(level[1].as_str()).unwrap(), )); // output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施 @@ -1076,8 +1077,8 @@ fn _print_detection_summary_tables( if stored_static.html_report_flag { html_output_stock.push(format!( "### Top {} alerts: {{#top_{}_alerts}}", - LEVEL_FULL.get(&level[1].as_str()).unwrap(), - LEVEL_FULL.get(&level[1].as_str()).unwrap() + LEVEL_FULL.get(level[1].as_str()).unwrap(), + LEVEL_FULL.get(level[1].as_str()).unwrap() )); for x in sorted_detections.iter() { html_output_stock.push(format!( @@ -1093,7 +1094,7 @@ fn _print_detection_summary_tables( html_output_stock.push(""); } - let take_cnt = if "informational" == *LEVEL_FULL.get(&level[1].as_str()).unwrap_or(&"-") { + let take_cnt = if "informational" == *LEVEL_FULL.get(level[1].as_str()).unwrap_or(&"-") { 10 } else { 5 @@ -1111,7 +1112,7 @@ fn _print_detection_summary_tables( take_cnt - sorted_detections.len() }; for _x in 0..na_cnt { - col_output.push("n/a".to_string()); + col_output.push("n/a"); } output.push(col_output); } @@ -1133,15 +1134,15 @@ fn _print_detection_summary_tables( .set_style(TableComponent::BottomBorderIntersections, hlch); tb.add_row(vec![ - Cell::new(output[2 * x][1..].join("\n")) + Cell::new(output[2 * x].iter().skip(1).join("\n")) .fg(col_color[2 * x].unwrap_or(comfy_table::Color::Reset)), - Cell::new(output[2 * x + 1][1..].join("\n")) + Cell::new(output[2 * x + 1].iter().skip(1).join("\n")) .fg(col_color[2 * x + 1].unwrap_or(comfy_table::Color::Reset)), ]); } - let odd_row = &output[4][1..6]; - let even_row = &output[4][6..11]; + let odd_row = &mut output[4].iter().skip(1).take(5); + let even_row = &mut output[4].iter().skip(1).take(5); tb.add_row(vec![ Cell::new(&output[4][0]).fg(col_color[4].unwrap_or(comfy_table::Color::Reset)), Cell::new(""), @@ -1565,7 +1566,7 @@ fn extract_author_name(yaml_path: &str, stored_static: &StoredStatic) -> Nested< { if let Some(author) = yaml["author"].as_str() { let mut ret = Nested::::new(); - for author in author.to_string().split(',').map(|s| { + for author in author.split(',').map(|s| { // 各要素の括弧以降の記載は名前としないためtmpの一番最初の要素のみを参照する // データの中にdouble quote と single quoteが入っているためここで除外する s.split('(').next().unwrap_or_default().to_string() @@ -1577,7 +1578,7 @@ fn extract_author_name(yaml_path: &str, stored_static: &StoredStatic) -> Nested< .iter() .map(|r| { r.split('/') - .map(|p| p.to_string().replace(['"', '\''], "").trim().to_string()) + .map(|p| p.trim().replace(['"', '\''], "")) .collect::() }) .collect(); @@ -1754,54 +1755,29 @@ mod tests { }, enable_unsupported_rules: false, }; + let ch = mock_ch_filter + .get(&CompactString::from("security")) + .unwrap_or(&CompactString::default()) + .clone(); let mut profile_converter: HashMap<&str, Profile> = HashMap::from([ ( "Timestamp", - Profile::Timestamp(format_time(&expect_time, false, &output_option)), - ), - ( - "Computer", - Profile::Computer(CompactString::from(test_computername2)), - ), - ( - "Channel", - Profile::Channel( - mock_ch_filter - .get(&CompactString::from("security")) - .unwrap_or(&CompactString::default()) - .to_owned(), - ), - ), - ("Level", Profile::Level(CompactString::from(test_level))), - ( - "EventID", - Profile::EventID(CompactString::from(test_eventid)), - ), - ( - "MitreAttack", - Profile::MitreTactics(CompactString::from(test_attack)), - ), - ( - "RecordID", - Profile::RecordID(CompactString::from(test_record_id)), - ), - ( - "RuleTitle", - Profile::RuleTitle(CompactString::from(test_title)), + Profile::Timestamp(format_time(&expect_time, false, &output_option).into()), ), + ("Computer", Profile::Computer(test_computername2.into())), + ("Channel", Profile::Channel(ch.into())), + ("Level", Profile::Level(test_level.into())), + ("EventID", Profile::EventID(test_eventid.into())), + ("MitreAttack", Profile::MitreTactics(test_attack.into())), + ("RecordID", Profile::RecordID(test_record_id.into())), + ("RuleTitle", Profile::RuleTitle(test_title.into())), ( "RecordInformation", - Profile::AllFieldInfo(CompactString::from(test_recinfo)), - ), - ( - "RuleFile", - Profile::RuleFile(CompactString::from(test_rulepath)), + Profile::AllFieldInfo(test_recinfo.into()), ), - ( - "EvtxFile", - Profile::EvtxFile(CompactString::from(test_filepath)), - ), - ("Tags", Profile::MitreTags(CompactString::from(test_attack))), + ("RuleFile", Profile::RuleFile(test_rulepath.into())), + ("EvtxFile", Profile::EvtxFile(test_filepath.into())), + ("Tags", Profile::MitreTags(test_attack.into())), ]); let eventkey_alias = load_eventkey_alias( utils::check_setting_path( @@ -1832,7 +1808,7 @@ mod tests { &eventkey_alias, ); *profile_converter.get_mut("Computer").unwrap() = - Profile::Computer(CompactString::from(test_computername)); + Profile::Computer(test_computername.into()); message::insert( &event, @@ -2072,54 +2048,26 @@ mod tests { }, enable_unsupported_rules: false, }; + let ch = mock_ch_filter + .get(&CompactString::from("security")) + .unwrap_or(&CompactString::default()) + .clone(); let mut profile_converter: HashMap<&str, Profile> = HashMap::from([ ( "Timestamp", - Profile::Timestamp(format_time(&expect_time, false, &output_option)), - ), - ( - "Computer", - Profile::Computer(CompactString::from(test_computername2)), + Profile::Timestamp(format_time(&expect_time, false, &output_option).into()), ), - ( - "Channel", - Profile::Channel( - mock_ch_filter - .get(&CompactString::from("security")) - .unwrap_or(&CompactString::default()) - .to_owned(), - ), - ), - ("Level", Profile::Level(CompactString::from(test_level))), - ( - "EventID", - Profile::EventID(CompactString::from(test_eventid)), - ), - ( - "MitreAttack", - Profile::MitreTactics(CompactString::from(test_attack)), - ), - ( - "RecordID", - Profile::RecordID(CompactString::from(test_record_id)), - ), - ( - "RuleTitle", - Profile::RuleTitle(CompactString::from(test_title)), - ), - ( - "AllFieldInfo", - Profile::AllFieldInfo(CompactString::from(test_recinfo)), - ), - ( - "RuleFile", - Profile::RuleFile(CompactString::from(test_rulepath)), - ), - ( - "EvtxFile", - Profile::EvtxFile(CompactString::from(test_filepath)), - ), - ("Tags", Profile::MitreTags(CompactString::from(test_attack))), + ("Computer", Profile::Computer(test_computername2.into())), + ("Channel", Profile::Channel(ch.into())), + ("Level", Profile::Level(test_level.into())), + ("EventID", Profile::EventID(test_eventid.into())), + ("MitreAttack", Profile::MitreTactics(test_attack.into())), + ("RecordID", Profile::RecordID(test_record_id.into())), + ("RuleTitle", Profile::RuleTitle(test_title.into())), + ("AllFieldInfo", Profile::AllFieldInfo(test_recinfo.into())), + ("RuleFile", Profile::RuleFile(test_rulepath.into())), + ("EvtxFile", Profile::EvtxFile(test_filepath.into())), + ("Tags", Profile::MitreTags(test_attack.into())), ]); let eventkey_alias = load_eventkey_alias( utils::check_setting_path( @@ -2150,7 +2098,7 @@ mod tests { &eventkey_alias, ); *profile_converter.get_mut("Computer").unwrap() = - Profile::Computer(CompactString::from(test_computername)); + Profile::Computer(test_computername.into()); message::insert( &event, @@ -2319,43 +2267,39 @@ mod tests { let data: Vec<(CompactString, Profile)> = vec![ ( CompactString::new("Timestamp"), - Profile::Timestamp(CompactString::new(format_time( - &test_timestamp, - false, - &output_option, - ))), + Profile::Timestamp(format_time(&test_timestamp, false, &output_option).into()), ), ( CompactString::new("Computer"), - Profile::Computer(CompactString::new(test_computername)), + Profile::Computer(test_computername.into()), ), ( CompactString::new("Channel"), - Profile::Channel(CompactString::new(test_channel)), + Profile::Channel(test_channel.into()), ), ( CompactString::new("EventID"), - Profile::EventID(CompactString::new(test_eventid)), + Profile::EventID(test_eventid.into()), ), ( CompactString::new("Level"), - Profile::Level(CompactString::new(test_level)), + Profile::Level(test_level.into()), ), ( CompactString::new("RecordID"), - Profile::RecordID(CompactString::new(test_recid)), + Profile::RecordID(test_recid.into()), ), ( CompactString::new("RuleTitle"), - Profile::RuleTitle(CompactString::new(test_title)), + Profile::RuleTitle(test_title.into()), ), ( CompactString::new("Details"), - Profile::Details(CompactString::new(output)), + Profile::Details(output.into()), ), ( CompactString::new("RecordInformation"), - Profile::AllFieldInfo(CompactString::new(test_recinfo)), + Profile::AllFieldInfo(test_recinfo.into()), ), ]; assert_eq!(_get_serialized_disp_output(&data, true), expect_header); diff --git a/src/detections/configs.rs b/src/detections/configs.rs index d293fc8de..7f9df33d3 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -1291,7 +1291,7 @@ pub fn load_pivot_keywords(path: &str) { PIVOT_KEYWORD .write() .unwrap() - .get_mut(&key.to_string()) + .get_mut(key) .unwrap() .fields .insert(value.to_string()); diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 212450e66..205c02a69 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -250,7 +250,7 @@ impl Detection { let default_time = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); let time = message::get_event_time(&record_info.record, stored_static.json_input_flag) .unwrap_or(default_time); - let level = rule.yaml["level"].as_str().unwrap_or("-"); + let level = rule.yaml["level"].as_str().unwrap_or("-").to_string(); let mut profile_converter: HashMap<&str, Profile> = HashMap::new(); let tags_config_values: Vec<&CompactString> = TAGS_CONFIG.values().collect(); @@ -262,21 +262,25 @@ impl Detection { Timestamp(_) => { profile_converter.insert( key.as_str(), - Timestamp(format_time( - &time, - false, - stored_static.output_option.as_ref().unwrap(), - )), + Timestamp( + format_time( + &time, + false, + stored_static.output_option.as_ref().unwrap(), + ) + .into(), + ), ); } Computer(_) => { profile_converter.insert( key.as_str(), - Computer(CompactString::from( + Computer( record_info.record["Event"]["System"]["Computer"] .to_string() - .replace('\"', ""), - )), + .replace('\"', "") + .into(), + ), ); } Channel(_) => { @@ -298,47 +302,50 @@ impl Detection { ); } Level(_) => { - profile_converter.insert( - key.as_str(), - Level(CompactString::from( - *LEVEL_ABBR_MAP.get(level).unwrap_or(&level), - )), - ); + let str_level = level.as_str(); + let prof_level = LEVEL_ABBR_MAP + .get(str_level) + .unwrap_or(&str_level) + .to_string(); + profile_converter.insert(key.as_str(), Level(prof_level.into())); } EventID(_) => { - profile_converter.insert(key.as_str(), EventID(eid.clone())); + profile_converter.insert(key.as_str(), EventID(eid.to_string().into())); } RecordID(_) => { - profile_converter.insert(key.as_str(), RecordID(rec_id.to_owned())); + profile_converter.insert(key.as_str(), RecordID(rec_id.to_string().into())); } RuleTitle(_) => { profile_converter.insert( key.as_str(), - RuleTitle(CompactString::from( - rule.yaml["title"].as_str().unwrap_or(""), - )), + RuleTitle( + rule.yaml["title"] + .as_str() + .unwrap_or_default() + .to_string() + .into(), + ), ); } RuleFile(_) => { - profile_converter.insert( - key.as_str(), - RuleFile(CompactString::from( - Path::new(&rule.rulepath) - .file_name() - .unwrap_or_default() - .to_str() - .unwrap_or_default(), - )), + let rule_file_path = CompactString::from( + Path::new(&rule.rulepath) + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default(), ); + profile_converter.insert(key.as_str(), RuleFile(rule_file_path.into())); } EvtxFile(_) => { profile_converter.insert( key.as_str(), - EvtxFile(CompactString::from( + EvtxFile( Path::new(&record_info.evtx_filepath) - .to_str() - .unwrap_or_default(), - )), + .display() + .to_string() + .into(), + ), ); } MitreTactics(_) => { @@ -349,83 +356,85 @@ impl Detection { .join(" ¦ "), ); - profile_converter.insert(key.as_str(), MitreTactics(tactics)); + profile_converter.insert(key.as_str(), MitreTactics(tactics.into())); } MitreTags(_) => { - let techniques = CompactString::from( - &tag_info - .iter() - .filter(|x| { - !tags_config_values.contains(&&CompactString::from(*x)) - && (x.starts_with("attack.t") - || x.starts_with("attack.g") - || x.starts_with("attack.s")) - }) - .map(|y| { - let replaced_tag = y.replace("attack.", ""); - make_ascii_titlecase(&replaced_tag) - }) - .join(" ¦ "), - ); - profile_converter.insert(key.as_str(), MitreTags(techniques)); - } - OtherTags(_) => { - let tags = CompactString::from( - &tag_info - .iter() - .filter(|x| { - !(TAGS_CONFIG.values().contains(&CompactString::from(*x)) - || x.starts_with("attack.t") + let techniques = tag_info + .iter() + .filter(|x| { + !tags_config_values.contains(&&CompactString::from(*x)) + && (x.starts_with("attack.t") || x.starts_with("attack.g") || x.starts_with("attack.s")) - }) - .join(" ¦ "), - ); - profile_converter.insert(key.as_str(), OtherTags(tags)); + }) + .map(|y| { + let replaced_tag = y.replace("attack.", ""); + make_ascii_titlecase(&replaced_tag) + }) + .join(" ¦ "); + profile_converter.insert(key.as_str(), MitreTags(techniques.into())); + } + OtherTags(_) => { + let tags = tag_info + .iter() + .filter(|x| { + !(TAGS_CONFIG.values().contains(&CompactString::from(*x)) + || x.starts_with("attack.t") + || x.starts_with("attack.g") + || x.starts_with("attack.s")) + }) + .join(" ¦ "); + profile_converter.insert(key.as_str(), OtherTags(tags.into())); } RuleAuthor(_) => { let author = if stored_static.multiline_flag { - CompactString::from( - rule.yaml["author"] - .as_str() - .unwrap_or("-") - .split([',', '/', ';']) - .map(|x| x.trim()) - .join("🛂🛂"), - ) + rule.yaml["author"] + .as_str() + .unwrap_or("-") + .split([',', '/', ';']) + .map(|x| x.trim()) + .join("🛂🛂") } else { - CompactString::from(rule.yaml["author"].as_str().unwrap_or("-")) + rule.yaml["author"].as_str().unwrap_or("-").to_string() }; - profile_converter.insert(key.as_str(), RuleAuthor(author)); + profile_converter.insert(key.as_str(), RuleAuthor(author.into())); } RuleCreationDate(_) => { profile_converter.insert( key.as_str(), - RuleCreationDate(CompactString::from( - rule.yaml["date"].as_str().unwrap_or("-"), - )), + RuleCreationDate( + rule.yaml["date"].as_str().unwrap_or("-").to_string().into(), + ), ); } RuleModifiedDate(_) => { profile_converter.insert( key.as_str(), - RuleModifiedDate(CompactString::from( - rule.yaml["modified"].as_str().unwrap_or("-"), - )), + RuleModifiedDate( + rule.yaml["modified"] + .as_str() + .unwrap_or("-") + .to_string() + .into(), + ), ); } Status(_) => { profile_converter.insert( key.as_str(), - Status(CompactString::from( - rule.yaml["status"].as_str().unwrap_or("-"), - )), + Status( + rule.yaml["status"] + .as_str() + .unwrap_or("-") + .to_string() + .into(), + ), ); } RuleID(_) => { profile_converter.insert( key.as_str(), - RuleID(CompactString::from(rule.yaml["id"].as_str().unwrap_or("-"))), + RuleID(rule.yaml["id"].as_str().unwrap_or("-").to_string().into()), ); } Provider(_) => { @@ -436,32 +445,33 @@ impl Detection { ); profile_converter.insert( key.as_str(), - Provider(CompactString::from( - stored_static.disp_abbr_generic.replace_all( - stored_static - .provider_abbr_config - .get(&provider_value) - .unwrap_or(&provider_value), - &stored_static.disp_abbr_general_values, - ), - )), + Provider( + stored_static + .disp_abbr_generic + .replace_all( + stored_static + .provider_abbr_config + .get(&provider_value) + .unwrap_or(&provider_value), + &stored_static.disp_abbr_general_values, + ) + .into(), + ), ); } RenderedMessage(_) => { let convert_value = if let Some(message) = record_info.record["Event"]["RenderingInfo"]["Message"].as_str() { - CompactString::from( - message - .replace('\t', "\\t") - .split("\r\n") - .map(|x| x.trim()) - .join("\\r\\n"), - ) + message + .replace('\t', "\\t") + .split("\r\n") + .map(|x| x.trim()) + .join("\\r\\n") } else { - CompactString::from("n/a") + "n/a".into() }; - profile_converter.insert(key.as_str(), RenderedMessage(convert_value)); + profile_converter.insert(key.as_str(), RenderedMessage(convert_value.into())); } TgtASN(_) | TgtCountry(_) | TgtCity(_) => { if profile_converter.contains_key(key.as_str()) { @@ -526,13 +536,13 @@ impl Detection { .map(|x| if x.is_empty() { "-" } else { x }); profile_converter .entry("TgtASN") - .and_modify(|p| *p = TgtASN(tgt_data.next().unwrap().into())); - profile_converter - .entry("TgtCountry") - .and_modify(|p| *p = TgtCountry(tgt_data.next().unwrap().into())); + .and_modify(|p| *p = TgtASN(tgt_data.next().unwrap().to_owned().into())); + profile_converter.entry("TgtCountry").and_modify(|p| { + *p = TgtCountry(tgt_data.next().unwrap().to_owned().into()) + }); profile_converter .entry("TgtCity") - .and_modify(|p| *p = TgtCity(tgt_data.next().unwrap().into())); + .and_modify(|p| *p = TgtCity(tgt_data.next().unwrap().to_owned().into())); } SrcASN(_) | SrcCountry(_) | SrcCity(_) => { if profile_converter.contains_key(key.as_str()) { @@ -600,13 +610,13 @@ impl Detection { .map(|x| if x.is_empty() { "-" } else { x }); profile_converter .entry("SrcASN") - .and_modify(|p| *p = SrcASN(src_data.next().unwrap().into())); - profile_converter - .entry("SrcCountry") - .and_modify(|p| *p = SrcCountry(src_data.next().unwrap().into())); + .and_modify(|p| *p = SrcASN(src_data.next().unwrap().to_owned().into())); + profile_converter.entry("SrcCountry").and_modify(|p| { + *p = SrcCountry(src_data.next().unwrap().to_owned().into()) + }); profile_converter .entry("SrcCity") - .and_modify(|p| *p = SrcCity(src_data.next().unwrap().into())); + .and_modify(|p| *p = SrcCity(src_data.next().unwrap().to_owned().into())); } _ => {} } @@ -625,10 +635,16 @@ impl Detection { let detect_info = DetectInfo { rulepath: CompactString::from(&rule.rulepath), ruletitle: CompactString::from(rule.yaml["title"].as_str().unwrap_or("-")), - level: CompactString::from(LEVEL_ABBR_MAP.get(level).unwrap_or(&level).to_string()), + level: CompactString::from( + LEVEL_ABBR_MAP + .get(&level.as_str()) + .unwrap_or(&level.as_str()) + .to_string(), + ), computername: CompactString::from( record_info.record["Event"]["System"]["Computer"] - .to_string() + .as_str() + .unwrap_or_default() .replace('\"', ""), ), eventid: eid, @@ -653,7 +669,7 @@ impl Detection { let output = Detection::create_count_output(rule, &agg_result); let mut profile_converter: HashMap<&str, Profile> = HashMap::new(); - let level = rule.yaml["level"].as_str().unwrap_or("-"); + let level = rule.yaml["level"].as_str().unwrap_or("-").to_string(); let tags_config_values: Vec<&CompactString> = TAGS_CONFIG.values().collect(); for (key, profile) in stored_static.profiles.as_ref().unwrap().iter() { @@ -661,148 +677,153 @@ impl Detection { Timestamp(_) => { profile_converter.insert( key.as_str(), - Timestamp(format_time( - &agg_result.start_timedate, - false, - stored_static.output_option.as_ref().unwrap(), - )), + Timestamp( + format_time( + &agg_result.start_timedate, + false, + stored_static.output_option.as_ref().unwrap(), + ) + .into(), + ), ); } Computer(_) => { - profile_converter.insert(key.as_str(), Computer(CompactString::from("-"))); + profile_converter.insert(key.as_str(), Computer("-".into())); } Channel(_) => { - profile_converter.insert(key.as_str(), Channel(CompactString::from("-"))); + profile_converter.insert(key.as_str(), Channel("-".into())); } Level(_) => { - profile_converter.insert( - key.as_str(), - Level(CompactString::from( - LEVEL_ABBR_MAP.get(level).unwrap_or(&level).to_string(), - )), - ); + let str_level = level.as_str(); + let prof_level = LEVEL_ABBR_MAP + .get(str_level) + .unwrap_or(&str_level) + .to_string(); + + profile_converter.insert(key.as_str(), Level(prof_level.into())); } EventID(_) => { - profile_converter.insert(key.as_str(), EventID(CompactString::from("-"))); + profile_converter.insert(key.as_str(), EventID("-".into())); } RecordID(_) => { - profile_converter.insert(key.as_str(), RecordID(CompactString::from(""))); + profile_converter.insert(key.as_str(), RecordID("".into())); } RuleTitle(_) => { profile_converter.insert( key.as_str(), - RuleTitle(CompactString::from( - rule.yaml["title"].as_str().unwrap_or(""), - )), + RuleTitle( + rule.yaml["title"] + .as_str() + .unwrap_or_default() + .to_owned() + .into(), + ), ); } RuleFile(_) => { - profile_converter.insert( - key.as_str(), - RuleFile(CompactString::from( - Path::new(&rule.rulepath) - .file_name() - .unwrap_or_default() - .to_str() - .unwrap_or_default(), - )), - ); + let rule_path = Path::new(&rule.rulepath) + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .to_string(); + + profile_converter.insert(key.as_str(), RuleFile(rule_path.into())); } EvtxFile(_) => { - profile_converter.insert(key.as_str(), EvtxFile(CompactString::from("-"))); + profile_converter.insert(key.as_str(), EvtxFile("-".into())); } MitreTactics(_) => { - let tactics = CompactString::from( - &tag_info - .iter() - .filter(|x| tags_config_values.contains(&&CompactString::from(*x))) - .join(" ¦ "), - ); - profile_converter.insert(key.as_str(), MitreTactics(tactics)); + let tactics = tag_info + .iter() + .filter(|x| tags_config_values.contains(&&CompactString::from(*x))) + .join(" ¦ "); + profile_converter.insert(key.as_str(), MitreTactics(tactics.into())); } MitreTags(_) => { - let techniques = CompactString::from( - &tag_info - .iter() - .filter(|x| { - !tags_config_values.contains(&&CompactString::from(*x)) - && (x.starts_with("attack.t") - || x.starts_with("attack.g") - || x.starts_with("attack.s")) - }) - .map(|y| { - let replaced_tag = y.replace("attack.", ""); - make_ascii_titlecase(&replaced_tag) - }) - .join(" ¦ "), - ); - profile_converter.insert(key.as_str(), MitreTags(techniques)); - } - OtherTags(_) => { - let tags = CompactString::from( - &tag_info - .iter() - .filter(|x| { - !(tags_config_values.contains(&&CompactString::from(*x)) - || x.starts_with("attack.t") + let techniques = tag_info + .iter() + .filter(|x| { + !tags_config_values.contains(&&CompactString::from(*x)) + && (x.starts_with("attack.t") || x.starts_with("attack.g") || x.starts_with("attack.s")) - }) - .join(" ¦ "), - ); - profile_converter.insert(key.as_str(), OtherTags(tags)); + }) + .map(|y| { + let replaced_tag = y.replace("attack.", ""); + make_ascii_titlecase(&replaced_tag) + }) + .join(" ¦ "); + profile_converter.insert(key.as_str(), MitreTags(techniques.into())); + } + OtherTags(_) => { + let tags = tag_info + .iter() + .filter(|x| { + !(tags_config_values.contains(&&CompactString::from(*x)) + || x.starts_with("attack.t") + || x.starts_with("attack.g") + || x.starts_with("attack.s")) + }) + .join(" ¦ "); + profile_converter.insert(key.as_str(), OtherTags(tags.into())); } RuleAuthor(_) => { let author = if stored_static.multiline_flag { - CompactString::from( - rule.yaml["author"] - .as_str() - .unwrap_or("-") - .split([',', '/', ';']) - .map(|x| x.trim()) - .join("🛂🛂"), - ) + rule.yaml["author"] + .as_str() + .unwrap_or("-") + .split([',', '/', ';']) + .map(|x| x.trim()) + .join("🛂🛂") } else { - CompactString::from(rule.yaml["author"].as_str().unwrap_or("-")) + rule.yaml["author"].as_str().unwrap_or("-").to_string() }; - profile_converter.insert(key.as_str(), RuleAuthor(author)); + profile_converter.insert(key.as_str(), RuleAuthor(author.into())); } RuleCreationDate(_) => { profile_converter.insert( key.as_str(), - RuleCreationDate(CompactString::from( - rule.yaml["date"].as_str().unwrap_or("-"), - )), + RuleCreationDate( + rule.yaml["date"].as_str().unwrap_or("-").to_owned().into(), + ), ); } RuleModifiedDate(_) => { profile_converter.insert( key.as_str(), - RuleModifiedDate(CompactString::from( - rule.yaml["modified"].as_str().unwrap_or("-"), - )), + RuleModifiedDate( + rule.yaml["modified"] + .as_str() + .unwrap_or("-") + .to_owned() + .into(), + ), ); } Status(_) => { profile_converter.insert( key.as_str(), - Status(CompactString::from( - rule.yaml["status"].as_str().unwrap_or("-"), - )), + Status( + rule.yaml["status"] + .as_str() + .unwrap_or("-") + .to_owned() + .into(), + ), ); } RuleID(_) => { profile_converter.insert( key.as_str(), - RuleID(CompactString::from(rule.yaml["id"].as_str().unwrap_or("-"))), + RuleID(rule.yaml["id"].as_str().unwrap_or("-").to_owned().into()), ); } Provider(_) => { - profile_converter.insert(key.as_str(), Provider(CompactString::from("-"))); + profile_converter.insert(key.as_str(), Provider("-".into())); } RenderedMessage(_) => { - profile_converter - .insert(key.as_str(), RenderedMessage(CompactString::from("-"))); + profile_converter.insert(key.as_str(), RenderedMessage("-".into())); } TgtASN(_) | TgtCountry(_) | TgtCity(_) => { if profile_converter.contains_key(key.as_str()) { @@ -823,11 +844,16 @@ impl Detection { _ => {} } } - + let str_level = level.as_str(); let detect_info = DetectInfo { rulepath: CompactString::from(&rule.rulepath), ruletitle: CompactString::from(rule.yaml["title"].as_str().unwrap_or("-")), - level: CompactString::from(*LEVEL_ABBR_MAP.get(level).unwrap_or(&level)), + level: CompactString::from( + LEVEL_ABBR_MAP + .get(str_level) + .unwrap_or(&str_level) + .to_string(), + ), computername: CompactString::from("-"), eventid: CompactString::from("-"), detail: output, diff --git a/src/detections/message.rs b/src/detections/message.rs index 7551e867d..13371cfd7 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -143,19 +143,17 @@ pub fn insert( if detect_info.detail.is_empty() { replaced_profiles.push((key.to_owned(), profile.to_owned())); } else { - replaced_profiles.push((key.to_owned(), Details(detect_info.detail))); + replaced_profiles.push((key.to_owned(), Details(detect_info.detail.into()))); detect_info.detail = CompactString::default(); } } AllFieldInfo(_) => { if is_agg { - replaced_profiles - .push((key.to_owned(), AllFieldInfo(CompactString::from("-")))); + replaced_profiles.push((key.to_owned(), AllFieldInfo("-".into()))); } else { let rec = utils::create_recordinfos(event_record); let rec = if rec.is_empty() { "-".to_string() } else { rec }; - replaced_profiles - .push((key.to_owned(), AllFieldInfo(CompactString::from(rec)))); + replaced_profiles.push((key.to_owned(), AllFieldInfo(rec.into()))); } } Literal(_) => replaced_profiles.push((key.to_owned(), profile.to_owned())), @@ -190,7 +188,7 @@ pub fn parse_message( eventkey_alias: &EventKeyAliasConfig, ) -> CompactString { let mut return_message = output; - let mut hash_map: HashMap = HashMap::new(); + let mut hash_map: HashMap = HashMap::new(); for caps in ALIASREGEX.captures_iter(&return_message) { let full_target_str = &caps[0]; let target_length = full_target_str.chars().count() - 2; // The meaning of 2 is two percent @@ -227,15 +225,15 @@ pub fn parse_message( let hash_value = get_serde_number_to_string(tmp_event_record); if hash_value.is_some() { if let Some(hash_value) = hash_value { - hash_map.insert(full_target_str.to_string(), hash_value.to_string()); + hash_map.insert(CompactString::from(full_target_str), hash_value); } } else { - hash_map.insert(full_target_str.to_string(), "n/a".to_string()); + hash_map.insert(CompactString::from(full_target_str), "n/a".into()); } } - for (k, v) in &hash_map { - return_message = CompactString::new(return_message.replace(k, v)); + for (k, v) in hash_map { + return_message = CompactString::new(return_message.replace(k.as_str(), v.as_str())); } return_message } diff --git a/src/options/profile.rs b/src/options/profile.rs index 17c1e4f62..2fb2541f9 100644 --- a/src/options/profile.rs +++ b/src/options/profile.rs @@ -11,6 +11,7 @@ use crate::yaml; use compact_str::CompactString; use itertools::Itertools; use nested::Nested; +use std::borrow::Cow; use std::fs::OpenOptions; use std::io::{BufWriter, Write}; use std::path::Path; @@ -18,34 +19,34 @@ use yaml_rust::{Yaml, YamlEmitter, YamlLoader}; #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum Profile { - Timestamp(CompactString), - Computer(CompactString), - Channel(CompactString), - Level(CompactString), - EventID(CompactString), - RecordID(CompactString), - RuleTitle(CompactString), - AllFieldInfo(CompactString), - RuleFile(CompactString), - EvtxFile(CompactString), - MitreTactics(CompactString), - MitreTags(CompactString), - OtherTags(CompactString), - RuleAuthor(CompactString), - RuleCreationDate(CompactString), - RuleModifiedDate(CompactString), - Status(CompactString), - RuleID(CompactString), - Provider(CompactString), - Details(CompactString), - RenderedMessage(CompactString), - SrcASN(CompactString), - SrcCountry(CompactString), - SrcCity(CompactString), - TgtASN(CompactString), - TgtCountry(CompactString), - TgtCity(CompactString), - Literal(CompactString), // profiles.yamlの固定文字列を変換なしでそのまま出力する場合 + Timestamp(Cow<'static, str>), + Computer(Cow<'static, str>), + Channel(Cow<'static, str>), + Level(Cow<'static, str>), + EventID(Cow<'static, str>), + RecordID(Cow<'static, str>), + RuleTitle(Cow<'static, str>), + AllFieldInfo(Cow<'static, str>), + RuleFile(Cow<'static, str>), + EvtxFile(Cow<'static, str>), + MitreTactics(Cow<'static, str>), + MitreTags(Cow<'static, str>), + OtherTags(Cow<'static, str>), + RuleAuthor(Cow<'static, str>), + RuleCreationDate(Cow<'static, str>), + RuleModifiedDate(Cow<'static, str>), + Status(Cow<'static, str>), + RuleID(Cow<'static, str>), + Provider(Cow<'static, str>), + Details(Cow<'static, str>), + RenderedMessage(Cow<'static, str>), + SrcASN(Cow<'static, str>), + SrcCountry(Cow<'static, str>), + SrcCity(Cow<'static, str>), + TgtASN(Cow<'static, str>), + TgtCountry(Cow<'static, str>), + TgtCity(Cow<'static, str>), + Literal(Cow<'static, str>), // profiles.yamlの固定文字列を変換なしでそのまま出力する場合 } impl Profile { @@ -62,31 +63,31 @@ impl Profile { pub fn convert(&self, converted_string: &CompactString) -> Self { match self { - Timestamp(_) => Timestamp(converted_string.to_owned()), - Computer(_) => Computer(converted_string.to_owned()), - Channel(_) => Channel(converted_string.to_owned()), - Level(_) => Level(converted_string.to_owned()), - EventID(_) => EventID(converted_string.to_owned()), - RecordID(_) => RecordID(converted_string.to_owned()), - RuleTitle(_) => RuleTitle(converted_string.to_owned()), - RuleFile(_) => RuleFile(converted_string.to_owned()), - EvtxFile(_) => EvtxFile(converted_string.to_owned()), - MitreTactics(_) => MitreTactics(converted_string.to_owned()), - MitreTags(_) => MitreTags(converted_string.to_owned()), - OtherTags(_) => OtherTags(converted_string.to_owned()), - RuleAuthor(_) => RuleAuthor(converted_string.to_owned()), - RuleCreationDate(_) => RuleCreationDate(converted_string.to_owned()), - RuleModifiedDate(_) => RuleModifiedDate(converted_string.to_owned()), - Status(_) => Status(converted_string.to_owned()), - RuleID(_) => RuleID(converted_string.to_owned()), - Provider(_) => Provider(converted_string.to_owned()), - RenderedMessage(_) => RenderedMessage(converted_string.to_owned()), - SrcASN(_) => SrcASN(converted_string.to_owned()), - SrcCountry(_) => SrcCountry(converted_string.to_owned()), - SrcCity(_) => SrcCity(converted_string.to_owned()), - TgtASN(_) => TgtASN(converted_string.to_owned()), - TgtCountry(_) => TgtCountry(converted_string.to_owned()), - TgtCity(_) => TgtCity(converted_string.to_owned()), + Timestamp(_) => Timestamp(converted_string.to_owned().into()), + Computer(_) => Computer(converted_string.to_owned().into()), + Channel(_) => Channel(converted_string.to_owned().into()), + Level(_) => Level(converted_string.to_owned().into()), + EventID(_) => EventID(converted_string.to_owned().into()), + RecordID(_) => RecordID(converted_string.to_owned().into()), + RuleTitle(_) => RuleTitle(converted_string.to_owned().into()), + RuleFile(_) => RuleFile(converted_string.to_owned().into()), + EvtxFile(_) => EvtxFile(converted_string.to_owned().into()), + MitreTactics(_) => MitreTactics(converted_string.to_owned().into()), + MitreTags(_) => MitreTags(converted_string.to_owned().into()), + OtherTags(_) => OtherTags(converted_string.to_owned().into()), + RuleAuthor(_) => RuleAuthor(converted_string.to_owned().into()), + RuleCreationDate(_) => RuleCreationDate(converted_string.to_owned().into()), + RuleModifiedDate(_) => RuleModifiedDate(converted_string.to_owned().into()), + Status(_) => Status(converted_string.to_owned().into()), + RuleID(_) => RuleID(converted_string.to_owned().into()), + Provider(_) => Provider(converted_string.to_owned().into()), + RenderedMessage(_) => RenderedMessage(converted_string.to_owned().into()), + SrcASN(_) => SrcASN(converted_string.to_owned().into()), + SrcCountry(_) => SrcCountry(converted_string.to_owned().into()), + SrcCity(_) => SrcCity(converted_string.to_owned().into()), + TgtASN(_) => TgtASN(converted_string.to_owned().into()), + TgtCountry(_) => TgtCountry(converted_string.to_owned().into()), + TgtCity(_) => TgtCity(converted_string.to_owned().into()), p => p.to_owned(), } } @@ -116,7 +117,7 @@ impl From<&str> for Profile { "%Provider%" => Provider(Default::default()), "%Details%" => Details(Default::default()), "%RenderedMessage%" => RenderedMessage(Default::default()), - s => Literal(CompactString::from(s)), // profiles.yamlの固定文字列を変換なしでそのまま出力する場合 + s => Literal(s.to_string().into()), // profiles.yamlの固定文字列を変換なしでそのまま出力する場合 } } } @@ -229,30 +230,18 @@ pub fn load_profile( } // insert preserved keyword when get-ip option specified. if GEOIP_DB_PARSER.read().unwrap().is_some() { - ret.push(( - CompactString::from("SrcASN"), - SrcASN(CompactString::default()), - )); + ret.push((CompactString::from("SrcASN"), SrcASN(Cow::default()))); ret.push(( CompactString::from("SrcCountry"), - SrcCountry(CompactString::default()), - )); - ret.push(( - CompactString::from("SrcCity"), - SrcCity(CompactString::default()), - )); - ret.push(( - CompactString::from("TgtASN"), - TgtASN(CompactString::default()), + SrcCountry(Cow::default()), )); + ret.push((CompactString::from("SrcCity"), SrcCity(Cow::default()))); + ret.push((CompactString::from("TgtASN"), TgtASN(Cow::default()))); ret.push(( CompactString::from("TgtCountry"), - TgtCountry(CompactString::default()), - )); - ret.push(( - CompactString::from("TgtCity"), - TgtCity(CompactString::default()), + TgtCountry(Cow::default()), )); + ret.push((CompactString::from("TgtCity"), TgtCity(Cow::default()))); } Some(ret) }