use crate::csaf::macros::skip_if_document_status_is_not::skip_if_document_status_is_not;
use crate::csaf::types::csaf_datetime::{CsafDateTime, ValidCsafDateTime};
use crate::csaf_traits::{
ContentTrait, CsafTrait, DocumentTrait, EpssTrait, MetricTrait, TrackingTrait, VulnerabilityTrait,
};
use crate::validation::ValidationError;
use chrono::TimeDelta;
fn create_old_epss_timestamp_error(
epss_timestamp: &ValidCsafDateTime,
newest_revision_date: &ValidCsafDateTime,
content_json_path: &str,
) -> ValidationError {
ValidationError {
message: format!(
"EPSS timestamp ({epss_timestamp}) is more than 15 days older than the newest revision date ({newest_revision_date}).",
),
instance_path: format!("{content_json_path}/epss/timestamp"),
}
}
pub fn test_6_2_41_old_epss_timestamp(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> {
let document = doc.get_document();
let tracking = document.get_tracking();
skip_if_document_status_is_not!(tracking.get_status(), Final, Interim);
let mut revision_history = tracking.aggregate_revision_history();
revision_history.inplace_sort_by_date_then_number();
let newest_revision_date = match revision_history.last() {
Some(rev) => match &rev.date {
CsafDateTime::Valid(date) => date,
CsafDateTime::Invalid(_) => return Ok(()), },
None => return Ok(()), };
let vulnerabilities = doc.get_vulnerabilities();
if vulnerabilities.is_empty() {
return Ok(());
}
let mut errors: Option<Vec<ValidationError>> = None;
let fifteen_days = TimeDelta::days(15);
for (i_v, vulnerability) in vulnerabilities.iter().enumerate() {
if let Some(metrics) = vulnerability.get_metrics() {
let mut newest_epss: Option<(ValidCsafDateTime, usize)> = None;
for (i_m, metric) in metrics.iter().enumerate() {
let content = metric.get_content();
if let Some(epss) = content.get_epss() {
match epss.get_timestamp() {
CsafDateTime::Valid(valid_timestamp) => {
if newest_epss.as_ref().is_none_or(|(prev, _)| valid_timestamp > *prev) {
newest_epss = Some((valid_timestamp, i_m));
}
},
CsafDateTime::Invalid(_) => {
},
}
}
}
if let Some((newest_epss_timestamp, path_metric_idx)) = newest_epss {
let diff = newest_revision_date.get_as_utc() - newest_epss_timestamp.get_as_utc();
if diff > fifteen_days {
errors.get_or_insert_default().push(create_old_epss_timestamp_error(
&newest_epss_timestamp,
newest_revision_date,
&metrics[path_metric_idx]
.get_content()
.get_content_json_path(i_v, path_metric_idx),
));
}
}
}
}
errors.map_or(Ok(()), Err)
}
crate::test_validation::impl_validator!(csaf2_1, ValidatorForTest6_2_41, test_6_2_41_old_epss_timestamp);
#[cfg(test)]
mod tests {
use super::*;
use crate::csaf2_1::testcases::TESTS_2_1;
use std::str::FromStr;
#[test]
fn test_test_6_2_41() {
let case_01_old_epss = Err(vec![create_old_epss_timestamp_error(
&ValidCsafDateTime::from_str("2023-01-24T10:00:00.000Z").unwrap(),
&ValidCsafDateTime::from_str("2024-01-24T10:00:00.000Z").unwrap(),
"/vulnerabilities/0/metrics/0/content",
)]);
let case_02_old_epss_with_timezone = Err(vec![create_old_epss_timestamp_error(
&ValidCsafDateTime::from_str("2024-03-14T14:30:00.000-19:00").unwrap(),
&ValidCsafDateTime::from_str("2024-03-30T10:00:00.000Z").unwrap(),
"/vulnerabilities/0/metrics/0/content",
)]);
TESTS_2_1
.test_6_2_41
.expect(case_01_old_epss, case_02_old_epss_with_timezone, Ok(()), Ok(()));
}
}