nvd_cvss/v4/
mod.rs

1//! ![](https://www.first.org/cvss/identity/cvssv4_web.png)
2//!
3//! Also available [in PDF format](https://www.first.org/cvss/v4-0/cvss-v40-specification.pdf).
4//!
5
6// https://github.com/FIRSTdotorg/cvss-v4-calculator/blob/main/cvss_lookup.js
7
8use std::fmt::{Display, Formatter};
9use std::str::FromStr;
10
11use serde::{Deserialize, Serialize};
12
13use crate::error::{CVSSError, Result};
14use crate::metric::Metric;
15use crate::severity::SeverityType;
16use crate::v4::attack_complexity::AttackComplexityType;
17use crate::v4::attack_requirements::AttackRequirementsType;
18use crate::v4::attack_vector::AttackVectorType;
19use crate::v4::constant::{
20  get_eq1245_max_composed, get_eq1245_max_severity, get_eq36_max_composed, get_eq36_max_severity,
21  lookup,
22};
23use crate::v4::environmental::{
24  AvailabilityRequirements, ConfidentialityRequirements, Environmental, IntegrityRequirements,
25};
26use crate::v4::exploit_maturity::ExploitMaturity;
27use crate::v4::privileges_required::PrivilegesRequiredType;
28use crate::v4::subsequent_impact_metrics::{
29  SubsequentAvailabilityImpactType, SubsequentConfidentialityImpactType, SubsequentImpact,
30  SubsequentIntegrityImpactType,
31};
32use crate::v4::user_interaction::UserInteractionType;
33use crate::v4::vulnerable_impact_metrics::{
34  VulnerableAvailabilityImpactType, VulnerableConfidentialityImpactType, VulnerableImpact,
35  VulnerableIntegrityImpactType,
36};
37use crate::version::Version;
38
39pub mod attack_complexity;
40pub mod attack_requirements;
41pub mod attack_vector;
42mod constant;
43pub mod environmental;
44pub mod exploit_maturity;
45pub mod privileges_required;
46pub mod subsequent_impact_metrics;
47pub mod user_interaction;
48pub mod vulnerable_impact_metrics;
49
50/// 2.1. Exploitability Metrics
51///
52/// As mentioned, the Exploitability metrics reflect the characteristics of the thing that is vulnerable, which we refer to formally as the vulnerable component. Therefore, each of the Exploitability metrics listed below should be scored relative to the vulnerable component, and reflect the properties of the vulnerability that lead to a successful attack.
53///
54#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
55#[serde(rename_all = "camelCase")]
56pub struct ExploitAbility {
57  /// [`AttackVectorType`] 访问途径(AV)
58  pub attack_vector: AttackVectorType,
59  /// [`AttackComplexityType`] 攻击复杂度(AC)
60  pub attack_complexity: AttackComplexityType,
61  /// [`AttackRequirementsType`] 攻击要求(AT)
62  pub attack_requirements: AttackRequirementsType,
63  /// [`PrivilegesRequiredType`] 所需权限(PR)
64  pub privileges_required: PrivilegesRequiredType,
65  /// [`UserInteractionType`] 用户交互(UI)
66  pub user_interaction: UserInteractionType,
67}
68
69impl ExploitAbility {
70  /// 8.22 × 𝐴𝑡𝑡𝑎𝑐𝑘𝑉𝑒𝑐𝑡𝑜𝑟 × 𝐴𝑡𝑡𝑎𝑐𝑘𝐶𝑜𝑚𝑝𝑙𝑒𝑥𝑖𝑡𝑦 × 𝑃𝑟𝑖𝑣𝑖𝑙𝑒𝑔𝑒𝑅𝑒𝑞𝑢𝑖𝑟𝑒𝑑 × 𝑈𝑠𝑒𝑟𝐼𝑛𝑡𝑒𝑟𝑎𝑐𝑡𝑖𝑜𝑛
71  pub fn score(&self) -> f32 {
72    self.attack_vector.score()
73      + self.attack_complexity.score()
74      + self.user_interaction.score()
75      + self.privileges_required.score()
76  }
77  // EQ1: 0-AV:N and PR:N and UI:N
78  //      1-(AV:N or PR:N or UI:N) and not (AV:N and PR:N and UI:N) and not AV:P
79  //      2-AV:P or not(AV:N or PR:N or UI:N)
80  fn eq1(&self) -> Option<u32> {
81    if self.attack_vector.is_network()
82      && self.privileges_required.is_none()
83      && self.user_interaction.is_none()
84    {
85      // 0: ["AV:N/PR:N/UI:N/"],
86      return Some(0);
87    } else if (self.attack_vector.is_network()
88      || self.privileges_required.is_none()
89      || self.user_interaction.is_none())
90      && !(self.attack_vector.is_network()
91        && self.privileges_required.is_none()
92        && self.user_interaction.is_none())
93      && !(self.attack_vector.is_physical())
94    {
95      // 1: ["AV:A/PR:N/UI:N/", "AV:N/PR:L/UI:N/", "AV:N/PR:N/UI:P/"],
96      return Some(1);
97    } else if self.attack_vector.is_physical()
98      || !(self.attack_vector.is_network()
99        || self.privileges_required.is_none()
100        || self.user_interaction.is_none())
101    {
102      // 2: ["AV:P/PR:N/UI:N/", "AV:A/PR:L/UI:P/"]
103      return Some(2);
104    }
105    None
106  }
107  // EQ2: 0-(AC:L and AT:N)
108  //      1-(not(AC:L and AT:N))
109  fn eq2(&self) -> Option<u32> {
110    if self.attack_complexity.is_low() && self.attack_requirements.is_none() {
111      return Some(0);
112    } else if !(self.attack_complexity.is_low() && self.attack_requirements.is_none()) {
113      return Some(1);
114    }
115    None
116  }
117}
118
119///
120/// The Common Vulnerability Scoring System (CVSS) captures the principal technical characteristics of software, hardware and firmware vulnerabilities. Its outputs include numerical scores indicating the severity of a vulnerability relative to other vulnerabilities.
121///
122/// CVSS is composed of three metric groups: Base, Temporal, and Environmental. The Base Score reflects the severity of a vulnerability according to its intrinsic characteristics which are constant over time and assumes the reasonable worst case impact across different deployed environments. The Temporal Metrics adjust the Base severity of a vulnerability based on factors that change over time, such as the availability of knowledge_base code. The Environmental Metrics adjust the Base and Temporal severities to a specific computing environment. They consider factors such as the presence of mitigations in that environment.
123///
124/// Base Scores are usually produced by the organization maintaining the vulnerable product, or a third party scoring on their behalf. It is typical for only the Base Metrics to be published as these do not change over time and are common to all environments. Consumers of CVSS should supplement the Base Score with Temporal and Environmental Scores specific to their use of the vulnerable product to produce a severity more accurate for their organizational environment. Consumers may use CVSS information as input to an organizational vulnerability management process that also considers factors that are not part of CVSS in order to rank the threats to their technology infrastructure and make informed remediation decisions. Such factors may include: number of customers on a product line, monetary losses due to a breach, life or property threatened, or public sentiment on highly publicized vulnerabilities. These are outside the scope of CVSS.
125///
126/// The benefits of CVSS include the provision of a standardized vendor and platform agnostic vulnerability scoring methodology. It is an open framework, providing transparency to the individual characteristics and methodology used to derive a score.
127///
128#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
129#[serde(rename_all = "camelCase")]
130pub struct CVSS {
131  /// Version 版本: 4.0
132  pub version: Version,
133  /// 向量: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H"
134  pub vector_string: String,
135  #[serde(flatten)]
136  pub exploit_ability: ExploitAbility,
137  #[serde(flatten)]
138  pub vulnerable_impact: VulnerableImpact,
139  #[serde(flatten)]
140  pub subsequent_impact: SubsequentImpact,
141  pub exploit: ExploitMaturity,
142  #[serde(flatten)]
143  pub environmental: Environmental,
144  /// 基础评分
145  pub base_score: f32,
146  /// [`SeverityType`] 基础评级
147  pub base_severity: SeverityType,
148}
149
150impl CVSS {
151  /// <https://nvd.nist.gov/vuln-metrics/cvss/v4-calculator>
152  /// only vector_string not version
153  pub fn vector_string(vectors: &str) -> Result<Self> {
154    let exploit_ability = ExploitAbility {
155      attack_vector: AttackVectorType::from_str(vectors)?,
156      attack_complexity: AttackComplexityType::from_str(vectors)?,
157      attack_requirements: AttackRequirementsType::from_str(vectors)?,
158      privileges_required: PrivilegesRequiredType::from_str(vectors)?,
159      user_interaction: UserInteractionType::from_str(vectors)?,
160    };
161    let vulnerable_impact = VulnerableImpact {
162      confidentiality_impact: VulnerableConfidentialityImpactType::from_str(vectors)?,
163      integrity_impact: VulnerableIntegrityImpactType::from_str(vectors)?,
164      availability_impact: VulnerableAvailabilityImpactType::from_str(vectors)?,
165    };
166    let subsequent_impact = SubsequentImpact {
167      confidentiality_impact: SubsequentConfidentialityImpactType::from_str(vectors)?,
168      integrity_impact: SubsequentIntegrityImpactType::from_str(vectors)?,
169      availability_impact: SubsequentAvailabilityImpactType::from_str(vectors)?,
170    };
171    let exploit = ExploitMaturity::from_str(vectors).unwrap_or_default();
172    let environmental = Environmental {
173      confidentiality_requirements: ConfidentialityRequirements::from_str(vectors)
174        .unwrap_or_default(),
175      integrity_requirements: IntegrityRequirements::from_str(vectors).unwrap_or_default(),
176      availability_requirements: AvailabilityRequirements::from_str(vectors).unwrap_or_default(),
177    };
178    Ok(CVSS {
179      version: Version::V4_0,
180      vector_string: format!("{}{}", Version::V4_0, vectors),
181      exploit_ability,
182      vulnerable_impact,
183      subsequent_impact,
184      exploit,
185      environmental,
186      base_score: 0.0,
187      base_severity: SeverityType::None,
188    })
189  }
190}
191
192impl Display for CVSS {
193  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
194    write!(
195      f,
196      "CVSS:{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}/{}",
197      self.version,
198      self.exploit_ability.attack_vector,
199      self.exploit_ability.attack_complexity,
200      self.exploit_ability.attack_requirements,
201      self.exploit_ability.privileges_required,
202      self.exploit_ability.user_interaction,
203      self.vulnerable_impact.confidentiality_impact,
204      self.vulnerable_impact.integrity_impact,
205      self.vulnerable_impact.availability_impact,
206      self.subsequent_impact.confidentiality_impact,
207      self.subsequent_impact.integrity_impact,
208      self.subsequent_impact.availability_impact,
209      self.environmental.confidentiality_requirements,
210      self.environmental.integrity_requirements,
211      self.environmental.availability_requirements,
212      self.exploit
213    )
214  }
215}
216impl FromStr for CVSS {
217  type Err = CVSSError;
218  fn from_str(vector_string: &str) -> Result<Self> {
219    let (version, vectors) = match vector_string.split_once('/') {
220      None => {
221        return Err(CVSSError::InvalidPrefix {
222          value: vector_string.to_string(),
223        })
224      }
225      Some((v, vector)) => {
226        let version = Version::from_str(v).unwrap_or_default();
227        (version, vector)
228      }
229    };
230    if matches!(version, Version::None) {
231      return Err(CVSSError::InvalidCVSSVersion {
232        value: version.to_string(),
233        expected: "4.0".to_string(),
234      });
235    }
236    let mut cvss = CVSS::vector_string(vectors)?;
237    cvss.base_score = cvss.base_score();
238    cvss.base_severity = SeverityType::from(cvss.base_score);
239    cvss.vector_string = cvss.to_string();
240    Ok(cvss)
241  }
242}
243
244impl CVSS {
245  fn base_score(&self) -> f32 {
246    if self.subsequent_impact.all_none() && self.vulnerable_impact.all_none() {
247      return 0.0;
248    }
249    let (eq1, eq2, eq3, eq4, eq5, eq6) = self.macro_vector();
250    let mv = format!("{}{}{}{}{}{}", eq1, eq2, eq3, eq4, eq5, eq6);
251    let score = lookup(&eq1, &eq2, &eq3, &eq4, &eq5, &eq6).unwrap_or(0.0);
252    let mut lower = 0;
253    let score_eq1_next_lower = if eq1 < 2 {
254      lower += 1;
255      lookup(&(eq1 + 1), &eq2, &eq3, &eq4, &eq5, &eq6)
256    } else {
257      None
258    };
259    let score_eq2_next_lower = if eq2 < 1 {
260      lower += 1;
261      lookup(&eq1, &(eq2 + 1), &eq3, &eq4, &eq5, &eq6)
262    } else {
263      None
264    };
265    let score_eq4_next_lower = if eq4 < 2 {
266      lower += 1;
267      lookup(&eq1, &eq2, &eq3, &(eq4 + 1), &eq5, &eq6)
268    } else {
269      None
270    };
271    let score_eq5_next_lower = if eq5 < 2 {
272      lower += 1;
273      lookup(&eq1, &eq2, &eq3, &eq4, &(eq5 + 1), &eq6)
274    } else {
275      None
276    };
277    let score_eq3eq6_next_lower = if (eq3 == 0 || eq3 == 1) && eq6 == 1 {
278      lower += 1;
279      lookup(&eq1, &eq2, &(eq3 + 1), &eq4, &eq5, &eq6)
280    } else if eq3 == 1 && eq6 == 0 {
281      lower += 1;
282      lookup(&eq1, &eq2, &eq3, &eq4, &eq5, &(eq6 + 1))
283    } else if eq3 == 0 && eq6 == 0 {
284      // multiple path take the one with higher score
285      // 如果存在多个分数,取最大的分数
286      lower += 1;
287      let left = lookup(&eq1, &eq2, &eq3, &eq4, &eq5, &(eq6 + 1)).unwrap_or(0.0);
288      let right = lookup(&eq1, &eq2, &(eq3 + 1), &eq4, &eq5, &eq6).unwrap_or(0.0);
289      let max_score = right.max(left);
290      Some(max_score)
291    } else {
292      None
293    };
294    // 根据lookup获取全部矩阵,然后取分数最大的那个
295    let max_vectors = self.max_vectors(mv);
296    let (
297      current_severity_distance_eq1,
298      current_severity_distance_eq2,
299      current_severity_distance_eq3eq6,
300      current_severity_distance_eq4,
301      current_severity_distance_eq5,
302    ) = self.severity_distances(max_vectors);
303    let step: f32 = 0.1;
304    // # multiply by step because distance is pure
305    let max_severity_eq1 = get_eq1245_max_severity(1, eq1).unwrap_or_default() * step;
306    let max_severity_eq2 = get_eq1245_max_severity(2, eq2).unwrap_or_default() * step;
307    let max_severity_eq3eq6 = get_eq36_max_severity(eq3, eq6).unwrap_or_default() * step;
308    let max_severity_eq4 = get_eq1245_max_severity(4, eq4).unwrap_or_default() * step;
309    let max_severity_eq5 = get_eq1245_max_severity(5, eq5).unwrap_or_default() * step;
310
311    let mut normalized_severity_eq1 = 0.0;
312    let mut normalized_severity_eq2 = 0.0;
313    let mut normalized_severity_eq3eq6 = 0.0;
314    let mut normalized_severity_eq4 = 0.0;
315    let mut normalized_severity_eq5 = 0.0;
316
317    if let Some(score_eq1_next_lower) = score_eq1_next_lower {
318      let available_distance_eq1 = score - score_eq1_next_lower;
319      let percent_to_next_eq1_severity = current_severity_distance_eq1 / max_severity_eq1;
320      normalized_severity_eq1 = available_distance_eq1 * percent_to_next_eq1_severity;
321    }
322    if let Some(score_eq2_next_lower) = score_eq2_next_lower {
323      let available_distance_eq2 = score - score_eq2_next_lower;
324      let percent_to_next_eq2_severity = current_severity_distance_eq2 / max_severity_eq2;
325      normalized_severity_eq2 = available_distance_eq2 * percent_to_next_eq2_severity
326    }
327    if let Some(score_eq3eq6_next_lower) = score_eq3eq6_next_lower {
328      let available_distance_eq3eq6 = score - score_eq3eq6_next_lower;
329      let percent_to_next_eq3eq6_severity = current_severity_distance_eq3eq6 / max_severity_eq3eq6;
330      normalized_severity_eq3eq6 = available_distance_eq3eq6 * percent_to_next_eq3eq6_severity;
331    }
332    if let Some(score_eq4_next_lower) = score_eq4_next_lower {
333      let available_distance_eq4 = score - score_eq4_next_lower;
334      let percent_to_next_eq4_severity = current_severity_distance_eq4 / max_severity_eq4;
335      normalized_severity_eq4 = available_distance_eq4 * percent_to_next_eq4_severity
336    }
337    if let Some(score_eq5_next_lower) = score_eq5_next_lower {
338      let available_distance_eq5 = score - score_eq5_next_lower;
339      let percent_to_next_eq5_severity = current_severity_distance_eq5 / max_severity_eq5;
340      normalized_severity_eq5 = available_distance_eq5 * percent_to_next_eq5_severity
341    }
342    let mut mean_distance = 0.0;
343    if lower != 0 {
344      mean_distance = (normalized_severity_eq1
345        + normalized_severity_eq2
346        + normalized_severity_eq3eq6
347        + normalized_severity_eq4
348        + normalized_severity_eq5)
349        / lower as f32;
350    }
351
352    roundup(score - mean_distance)
353  }
354  // EQ6: 0-(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)
355  //      1-not[(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)]
356  fn eq6(&self) -> Option<u32> {
357    if (self.environmental.confidentiality_requirements.is_high()
358      && self.vulnerable_impact.confidentiality_impact.is_high())
359      || (self.environmental.integrity_requirements.is_high()
360        && self.vulnerable_impact.integrity_impact.is_high())
361      || (self.environmental.availability_requirements.is_high()
362        && self.vulnerable_impact.availability_impact.is_high())
363    {
364      return Some(0);
365    } else if !(self.vulnerable_impact.confidentiality_impact.is_high()
366      || (self.environmental.integrity_requirements.is_high()
367        && self.vulnerable_impact.integrity_impact.is_high())
368      || (self.environmental.availability_requirements.is_high()
369        && self.vulnerable_impact.availability_impact.is_high()))
370    {
371      return Some(1);
372    }
373    None
374  }
375  fn max_vectors(&self, macro_vector: String) -> Vec<String> {
376    let mut vectors = vec![];
377    let get_index = |index| {
378      macro_vector
379        .chars()
380        .nth((index - 1) as usize)
381        .unwrap_or_default()
382        .to_digit(10)
383        .unwrap_or(0)
384    };
385    let eq1_maxes = get_eq1245_max_composed(1, get_index(1));
386    let eq2_maxes = get_eq1245_max_composed(2, get_index(2));
387    let eq3_eq6_maxes = get_eq36_max_composed(get_index(3), get_index(6));
388    let eq4_maxes = get_eq1245_max_composed(4, get_index(4));
389    let eq5_maxes = get_eq1245_max_composed(5, get_index(5));
390    // 笛卡尔积获取全部可能
391    for eq1_max in eq1_maxes {
392      for eq2_max in eq2_maxes.iter() {
393        for eq3_eq6_max in eq3_eq6_maxes.iter() {
394          for eq4_max in eq4_maxes.iter() {
395            for eq5_max in eq5_maxes.iter() {
396              vectors.push(format!(
397                "{}{}{}{}{}",
398                eq1_max, eq2_max, eq3_eq6_max, eq4_max, eq5_max
399              ));
400            }
401          }
402        }
403      }
404    }
405    vectors
406  }
407  fn severity_distances(&self, vectors: Vec<String>) -> (f32, f32, f32, f32, f32) {
408    // 每个都和self这个cvss的分数比较,返回第一个大于self本身的
409    let mut severity_distances = vec![];
410    for vector in vectors {
411      let max_vector = CVSS::vector_string(&vector);
412      if let Ok(max_vector) = max_vector {
413        let av = self.exploit_ability.attack_vector.score()
414          - max_vector.exploit_ability.attack_vector.score();
415        let pr = self.exploit_ability.privileges_required.score()
416          - max_vector.exploit_ability.privileges_required.score();
417        let ui = self.exploit_ability.user_interaction.score()
418          - max_vector.exploit_ability.user_interaction.score();
419
420        let ac = self.exploit_ability.attack_complexity.score()
421          - max_vector.exploit_ability.attack_complexity.score();
422        let at = self.exploit_ability.attack_requirements.score()
423          - max_vector.exploit_ability.attack_requirements.score();
424
425        let vc = self.vulnerable_impact.confidentiality_impact.score()
426          - max_vector.vulnerable_impact.confidentiality_impact.score();
427        let vi = self.vulnerable_impact.integrity_impact.score()
428          - max_vector.vulnerable_impact.integrity_impact.score();
429        let va = self.vulnerable_impact.availability_impact.score()
430          - max_vector.vulnerable_impact.availability_impact.score();
431
432        let sc = self.subsequent_impact.confidentiality_impact.score()
433          - max_vector.subsequent_impact.confidentiality_impact.score();
434        let si = self.subsequent_impact.integrity_impact.score()
435          - max_vector.subsequent_impact.integrity_impact.score();
436        let sa = self.subsequent_impact.availability_impact.score()
437          - max_vector.subsequent_impact.availability_impact.score();
438
439        let cr = self.environmental.confidentiality_requirements.score()
440          - max_vector
441            .environmental
442            .confidentiality_requirements
443            .score();
444        let ir = self.environmental.integrity_requirements.score()
445          - max_vector.environmental.integrity_requirements.score();
446        let ar = self.environmental.availability_requirements.score()
447          - max_vector.environmental.availability_requirements.score();
448        let all_severity_distances = vec![av, pr, ui, ac, at, vc, vi, va, sc, si, sa, cr, ir, ar];
449        // # if any is less than zero this is not the right max
450        if all_severity_distances.iter().any(|m| m < &0.0) {
451          continue;
452        }
453        severity_distances = all_severity_distances;
454        break;
455        // # if multiple maxes exist to reach it it is enough the first one
456      }
457    }
458    // 以前pop是从末尾开始的,所以要先倒过来
459    severity_distances.reverse();
460    let (av, pr, ui, ac, at, vc, vi, va, sc, si, sa, cr, ir, ar) = (
461      severity_distances.pop().unwrap_or_default(),
462      severity_distances.pop().unwrap_or_default(),
463      severity_distances.pop().unwrap_or_default(),
464      severity_distances.pop().unwrap_or_default(),
465      severity_distances.pop().unwrap_or_default(),
466      severity_distances.pop().unwrap_or_default(),
467      severity_distances.pop().unwrap_or_default(),
468      severity_distances.pop().unwrap_or_default(),
469      severity_distances.pop().unwrap_or_default(),
470      severity_distances.pop().unwrap_or_default(),
471      severity_distances.pop().unwrap_or_default(),
472      severity_distances.pop().unwrap_or_default(),
473      severity_distances.pop().unwrap_or_default(),
474      severity_distances.pop().unwrap_or_default(),
475    );
476    let current_severity_distance_eq1 = av + pr + ui;
477    let current_severity_distance_eq2 = ac + at;
478    let current_severity_distance_eq3eq6 = vc + vi + va + cr + ir + ar;
479    let current_severity_distance_eq4 = sc + si + sa;
480    let current_severity_distance_eq5 = 0.0;
481    (
482      current_severity_distance_eq1,
483      current_severity_distance_eq2,
484      current_severity_distance_eq3eq6,
485      current_severity_distance_eq4,
486      current_severity_distance_eq5,
487    )
488  }
489  fn macro_vector(&self) -> (u32, u32, u32, u32, u32, u32) {
490    let eq1 = self.exploit_ability.eq1().unwrap_or_default();
491    let eq2 = self.exploit_ability.eq2().unwrap_or_default();
492    let eq3 = self.vulnerable_impact.eq3().unwrap_or_default();
493    let eq4 = self.subsequent_impact.eq4().unwrap_or_default();
494    let eq5 = self.exploit.eq5().unwrap_or_default();
495    let eq6 = self.eq6().unwrap_or_default();
496    (eq1, eq2, eq3, eq4, eq5, eq6)
497  }
498}
499/// Roundup保留小数点后一位,小数点后第二位四舍五入。 例如, Roundup(4.02) = 4.0; 或者 Roundup(4.00) = 4.0
500fn roundup(input: f32) -> f32 {
501  let int_input = (input * 100.0) as u32;
502  if int_input % 10 < 5 {
503    (int_input / 10) as f32 / 10.0
504  } else {
505    let score_floor = ((int_input as f32) / 10.0).floor();
506    (score_floor + 1.0) / 10.0
507  }
508}
509#[cfg(test)]
510mod tests {
511  use std::collections::HashMap;
512  use std::str::FromStr;
513
514  use crate::v4::{roundup, CVSS};
515
516  #[test]
517  fn roundup_test() {
518    assert_eq!(roundup(0.12000), 0.1);
519    assert_eq!(roundup(0.15000), 0.2);
520    assert_eq!(roundup(0.94900), 0.9);
521  }
522  #[test]
523  fn cvss_score_test() {
524    let vs_map: HashMap<&'static str, f32> = HashMap::from_iter([
525      (
526        "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
527        10.0,
528      ),
529      (
530        "CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
531        9.4,
532      ),
533      (
534        "CVSS:4.0/AV:A/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
535        9.0,
536      ),
537      (
538        "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
539        8.9,
540      ),
541      (
542        "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
543        7.3,
544      ),
545      (
546        "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:H/VA:H/SC:H/SI:H/SA:H/E:A",
547        6.1,
548      ),
549      (
550        "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:L/VA:L/SC:H/SI:H/SA:H/E:A",
551        2.4,
552      ),
553      (
554        "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:L/VA:N/SC:L/SI:N/SA:H/E:A",
555        2.0,
556      ),
557      (
558        "CVSS:4.0/AV:A/AC:H/AT:P/PR:L/UI:P/VC:L/VI:L/VA:N/SC:L/SI:N/SA:H/E:P/CR:H/IR:M/AR:L",
559        0.9,
560      ),
561    ]);
562    for (k, v) in vs_map.iter() {
563      let cvss = CVSS::from_str(k).unwrap();
564      assert_eq!(cvss.base_score, *v)
565    }
566  }
567}