cvss_tools/
lib.rs

1use std::collections::HashMap;
2use std::fs::File;
3use std::io::BufReader;
4mod macro_vector_values;
5use macro_vector_values::build_macro_vector_values;
6
7fn build_av_levels() -> HashMap<String, f64> {
8    HashMap::from([
9        ("N".to_string(), 0.0),
10        ("A".to_string(), 0.1),
11        ("L".to_string(), 0.2),
12        ("P".to_string(), 0.3),
13    ])
14}
15
16fn build_pr_levels() -> HashMap<String, f64> {
17    HashMap::from([
18        ("N".to_string(), 0.0),
19        ("L".to_string(), 0.1),
20        ("H".to_string(), 0.2),
21    ])
22}
23
24fn build_ui_levels() -> HashMap<String, f64> {
25    HashMap::from([
26        ("N".to_string(), 0.0),
27        ("P".to_string(), 0.1),
28        ("A".to_string(), 0.2),
29    ])
30}
31
32fn build_ac_levels() -> HashMap<String, f64> {
33    HashMap::from([("L".to_string(), 0.0), ("H".to_string(), 0.1)])
34}
35
36fn build_at_levels() -> HashMap<String, f64> {
37    HashMap::from([("N".to_string(), 0.0), ("P".to_string(), 0.1)])
38}
39
40fn build_vc_levels() -> HashMap<String, f64> {
41    HashMap::from([
42        ("H".to_string(), 0.0),
43        ("L".to_string(), 0.1),
44        ("N".to_string(), 0.2),
45    ])
46}
47
48fn build_vi_levels() -> HashMap<String, f64> {
49    HashMap::from([
50        ("H".to_string(), 0.0),
51        ("L".to_string(), 0.1),
52        ("N".to_string(), 0.2),
53    ])
54}
55
56fn build_va_levels() -> HashMap<String, f64> {
57    HashMap::from([
58        ("H".to_string(), 0.0),
59        ("L".to_string(), 0.1),
60        ("N".to_string(), 0.2),
61    ])
62}
63
64fn build_sc_levels() -> HashMap<String, f64> {
65    HashMap::from([
66        ("H".to_string(), 0.1),
67        ("L".to_string(), 0.2),
68        ("N".to_string(), 0.3),
69    ])
70}
71
72fn build_si_levels() -> HashMap<String, f64> {
73    HashMap::from([
74        ("S".to_string(), 0.0),
75        ("H".to_string(), 0.1),
76        ("L".to_string(), 0.2),
77        ("N".to_string(), 0.3),
78    ])
79}
80
81fn build_sa_levels() -> HashMap<String, f64> {
82    HashMap::from([
83        ("S".to_string(), 0.0),
84        ("H".to_string(), 0.1),
85        ("L".to_string(), 0.2),
86        ("N".to_string(), 0.3),
87    ])
88}
89
90fn build_cr_levels() -> HashMap<String, f64> {
91    HashMap::from([
92        ("H".to_string(), 0.0),
93        ("M".to_string(), 0.1),
94        ("L".to_string(), 0.2),
95    ])
96}
97
98fn build_ir_levels() -> HashMap<String, f64> {
99    HashMap::from([
100        ("H".to_string(), 0.0),
101        ("M".to_string(), 0.1),
102        ("L".to_string(), 0.2),
103    ])
104}
105
106fn build_ar_levels() -> HashMap<String, f64> {
107    HashMap::from([
108        ("H".to_string(), 0.0),
109        ("M".to_string(), 0.1),
110        ("L".to_string(), 0.2),
111    ])
112}
113
114fn build_default_metric_values() -> HashMap<String, String> {
115    HashMap::from([
116        ("AV".to_string(), "N".to_string()),
117        ("AC".to_string(), "L".to_string()),
118        ("AT".to_string(), "N".to_string()),
119        ("PR".to_string(), "N".to_string()),
120        ("UI".to_string(), "N".to_string()),
121        ("VC".to_string(), "N".to_string()),
122        ("VI".to_string(), "N".to_string()),
123        ("VA".to_string(), "N".to_string()),
124        ("SC".to_string(), "N".to_string()),
125        ("SI".to_string(), "N".to_string()),
126        ("SA".to_string(), "N".to_string()),
127        ("S".to_string(), "X".to_string()),
128        ("AU".to_string(), "X".to_string()),
129        ("R".to_string(), "X".to_string()),
130        ("V".to_string(), "X".to_string()),
131        ("RE".to_string(), "X".to_string()),
132        ("U".to_string(), "X".to_string()),
133        ("MAV".to_string(), "X".to_string()),
134        ("MAC".to_string(), "X".to_string()),
135        ("MAT".to_string(), "X".to_string()),
136        ("MPR".to_string(), "X".to_string()),
137        ("MUI".to_string(), "X".to_string()),
138        ("MVC".to_string(), "X".to_string()),
139        ("MVI".to_string(), "X".to_string()),
140        ("MVA".to_string(), "X".to_string()),
141        ("MSC".to_string(), "X".to_string()),
142        ("MSI".to_string(), "X".to_string()),
143        ("MSA".to_string(), "X".to_string()),
144        ("CR".to_string(), "X".to_string()),
145        ("IR".to_string(), "X".to_string()),
146        ("AR".to_string(), "X".to_string()),
147        ("E".to_string(), "X".to_string()),
148    ])
149}
150
151fn build_full_vector(mut vector: HashMap<String, String>) -> HashMap<String, String> {
152    for (metric, value) in build_default_metric_values() {
153        if vector.contains_key(&metric) {
154            continue;
155        }
156
157        vector.insert(metric, value);
158    }
159
160    vector
161}
162
163fn preprocess_vector(vector: String) -> HashMap<String, String> {
164    let mut result: HashMap<String, String> = HashMap::new();
165    for metric_data in vector.split("/") {
166        if metric_data.contains("CVSS") {
167            continue;
168        }
169
170        let metric_and_value: Vec<&str> = metric_data.split(":").collect();
171        if metric_and_value == vec!["".to_string()] {
172            continue;
173        }
174
175        result.insert(
176            metric_and_value[0].to_string(),
177            metric_and_value[1].to_string(),
178        );
179    }
180
181    result
182}
183
184fn get_m(vector: HashMap<String, String>, metric: String) -> String {
185    // If E=X it will default to the worst case i.e. E=A
186    if metric == "E" && vector[&metric] == "X" {
187        return "A".to_string();
188    }
189
190    // If CR=X, IR=X or AR=X they will default to the worst case i.e. CR=H, IR=H and AR=H
191    if metric == "CR" && vector[&metric] == "X" {
192        return "H".to_string();
193    }
194
195    // IR:X is the same as IR:H
196    if metric == "IR" && vector[&metric] == "X" {
197        return "H".to_string();
198    }
199    // AR:X is the same as AR:H
200    if metric == "AR" && vector[&metric] == "X" {
201        return "H".to_string();
202    }
203
204    // All other environmental metrics just overwrite base score values,
205    // so if they’re not defined just use the base score value.
206    let metric_with_m: String = "M".to_string() + &metric;
207    if vector.contains_key(&metric_with_m) {
208        if vector[&metric_with_m] != "X" {
209            return match vector.get(&metric_with_m) {
210                Some(metric_value) => metric_value.to_string(),
211                None => "".to_string(),
212            };
213        }
214    }
215
216    match vector.get(&metric) {
217        Some(metric_value) => metric_value.to_string(),
218        None => "".to_string(),
219    }
220}
221
222fn get_eq_1(vector: HashMap<String, String>) -> u8 {
223    let av_value: String = get_m(vector.clone(), "AV".to_string());
224    let pr_value: String = get_m(vector.clone(), "PR".to_string());
225    let ui_value: String = get_m(vector.clone(), "UI".to_string());
226
227    if av_value == "N" && pr_value == "N" && ui_value == "N" {
228        return 0;
229    }
230
231    if (av_value == "N" || pr_value == "N" || ui_value == "N")
232        && !(av_value == "N" && pr_value == "N" && ui_value == "N")
233        && !(av_value == "P")
234    {
235        return 1;
236    }
237
238    if av_value == "P" || !(av_value == "N" || pr_value == "N" || ui_value == "N") {
239        return 2;
240    }
241
242    0
243}
244fn get_eq_2(vector: HashMap<String, String>) -> u8 {
245    // EQ2: 0-(AC:L and AT:N)
246    //      1-(not(AC:L and AT:N))
247    let ac_value: String = get_m(vector.clone(), "AC".to_string());
248    let at_value: String = get_m(vector.clone(), "AT".to_string());
249    if ac_value == "L" && at_value == "N" {
250        return 0;
251    }
252
253    if !(ac_value == "L" && at_value == "N") {
254        return 1;
255    }
256
257    0
258}
259fn get_eq_3(vector: HashMap<String, String>) -> u8 {
260    // EQ3: 0-(VC:H and VI:H)
261    //      1-(not(VC:H and VI:H) and (VC:H or VI:H or VA:H))
262    //      2-not (VC:H or VI:H or VA:H)
263    let vc_value: String = get_m(vector.clone(), "VC".to_string());
264    let vi_value: String = get_m(vector.clone(), "VI".to_string());
265    let va_value: String = get_m(vector.clone(), "VA".to_string());
266
267    if vc_value == "H" && vi_value == "H" {
268        return 0;
269    }
270
271    if !(vc_value == "H" && vi_value == "H")
272        && (vc_value == "H" || vi_value == "H" || va_value == "H")
273    {
274        return 1;
275    }
276
277    if !(vc_value == "H" || vi_value == "H" || va_value == "H") {
278        return 2;
279    }
280
281    0
282}
283
284fn get_eq_4(vector: HashMap<String, String>) -> u8 {
285    // EQ4: 0-(MSI:S or MSA:S)
286    //      1-not (MSI:S or MSA:S) and (SC:H or SI:H or SA:H)
287    //      2-not (MSI:S or MSA:S) and not (SC:H or SI:H or SA:H)
288
289    let msi_value: String = get_m(vector.clone(), "MSI".to_string());
290    let msa_value: String = get_m(vector.clone(), "MSA".to_string());
291    let si_value: String = get_m(vector.clone(), "SI".to_string());
292    let sa_value: String = get_m(vector.clone(), "SA".to_string());
293    let sc_value: String = get_m(vector.clone(), "SC".to_string());
294
295    if msi_value == "S" || msa_value == "S" {
296        return 0;
297    }
298
299    if !(msi_value == "S" || msa_value == "S")
300        && (sc_value == "H" || si_value == "H" || sa_value == "H")
301    {
302        return 1;
303    }
304
305    if !(msi_value == "S" || msa_value == "S")
306        && !(sc_value == "H" || si_value == "H" || sa_value == "H")
307    {
308        return 2;
309    }
310
311    0
312}
313
314fn get_eq_5(vector: HashMap<String, String>) -> u8 {
315    // EQ5: 0-E:A
316    //      1-E:P
317    //      2-E:U
318
319    match get_m(vector.clone(), "E".to_string()).as_str() {
320        "A" => 0,
321        "P" => 1,
322        "U" => 2,
323        _ => 0,
324    }
325}
326
327fn get_eq_6(vector: HashMap<String, String>) -> u8 {
328    // EQ6: 0-(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)
329    //      1-not[(CR:H and VC:H) or (IR:H and VI:H) or (AR:H and VA:H)]
330
331    let cr_value: String = get_m(vector.clone(), "CR".to_string());
332    let ir_value: String = get_m(vector.clone(), "IR".to_string());
333    let ar_value: String = get_m(vector.clone(), "AR".to_string());
334
335    let vc_value: String = get_m(vector.clone(), "VC".to_string());
336    let vi_value: String = get_m(vector.clone(), "VI".to_string());
337    let va_value: String = get_m(vector.clone(), "VA".to_string());
338
339    if (cr_value == "H" && vc_value == "H")
340        || (ir_value == "H" && vi_value == "H")
341        || (ar_value == "H" && va_value == "H")
342    {
343        return 0;
344    } else if !((cr_value == "H" && vc_value == "H")
345        || (ir_value == "H" && vi_value == "H")
346        || (ar_value == "H" && va_value == "H"))
347    {
348        return 1;
349    }
350
351    return 0;
352}
353
354fn calculate_macro_vector(vector: HashMap<String, String>) -> HashMap<String, u8> {
355    HashMap::from([
356        ("eq_1".to_string(), get_eq_1(vector.clone())),
357        ("eq_2".to_string(), get_eq_2(vector.clone())),
358        ("eq_3".to_string(), get_eq_3(vector.clone())),
359        ("eq_4".to_string(), get_eq_4(vector.clone())),
360        ("eq_5".to_string(), get_eq_5(vector.clone())),
361        ("eq_6".to_string(), get_eq_6(vector.clone())),
362    ])
363}
364
365fn build_max_composed() -> HashMap<String, HashMap<u8, Vec<String>>> {
366    HashMap::from([
367        (
368            "eq1".to_string(),
369            HashMap::from([
370                (0, vec!["AV:N/PR:N/UI:N/".to_string()]),
371                (
372                    1,
373                    vec![
374                        "AV:A/PR:N/UI:N/".to_string(),
375                        "AV:N/PR:L/UI:N/".to_string(),
376                        "AV:N/PR:N/UI:P/".to_string(),
377                    ],
378                ),
379                (
380                    2,
381                    vec!["AV:P/PR:N/UI:N/".to_string(), "AV:A/PR:L/UI:P/".to_string()],
382                ),
383            ]),
384        ),
385        (
386            "eq2".to_string(),
387            HashMap::from([
388                (0, vec!["AC:L/AT:N/".to_string()]),
389                (1, vec!["AC:H/AT:N/".to_string(), "AC:L/AT:P/".to_string()]),
390            ]),
391        ),
392        (
393            "eq4".to_string(),
394            HashMap::from([
395                (0 as u8, vec!["SC:H/SI:S/SA:S/".to_string()]),
396                (1 as u8, vec!["SC:H/SI:H/SA:H/".to_string()]),
397                (2 as u8, vec!["SC:L/SI:L/SA:L/".to_string()]),
398            ]),
399        ),
400        (
401            "eq5".to_string(),
402            HashMap::from([
403                (0 as u8, vec!["E:A/".to_string()]),
404                (1 as u8, vec!["E:P/".to_string()]),
405                (2 as u8, vec!["E:U/".to_string()]),
406            ]),
407        ),
408    ])
409}
410
411fn build_max_composed_eq_3() -> HashMap<u8, HashMap<String, Vec<String>>> {
412    HashMap::from([
413        (
414            0 as u8,
415            HashMap::from([
416                (
417                    "0".to_string(),
418                    vec!["VC:H/VI:H/VA:H/CR:H/IR:H/AR:H/".to_string()],
419                ),
420                (
421                    "1".to_string(),
422                    vec![
423                        "VC:H/VI:H/VA:L/CR:M/IR:M/AR:H/".to_string(),
424                        "VC:H/VI:H/VA:H/CR:M/IR:M/AR:M/".to_string(),
425                    ],
426                ),
427            ]),
428        ),
429        (
430            1 as u8,
431            HashMap::from([
432                (
433                    "0".to_string(),
434                    vec![
435                        "VC:L/VI:H/VA:H/CR:H/IR:H/AR:H/".to_string(),
436                        "VC:H/VI:L/VA:H/CR:H/IR:H/AR:H/".to_string(),
437                    ],
438                ),
439                (
440                    "1".to_string(),
441                    vec![
442                        "VC:L/VI:H/VA:L/CR:H/IR:M/AR:H/".to_string(),
443                        "VC:L/VI:H/VA:H/CR:H/IR:M/AR:M/".to_string(),
444                        "VC:H/VI:L/VA:H/CR:M/IR:H/AR:M/".to_string(),
445                        "VC:H/VI:L/VA:L/CR:M/IR:H/AR:H/".to_string(),
446                        "VC:L/VI:L/VA:H/CR:H/IR:H/AR:M/".to_string(),
447                    ],
448                ),
449            ]),
450        ),
451        (
452            2 as u8,
453            HashMap::from([(
454                "1".to_string(),
455                vec!["VC:L/VI:L/VA:L/CR:H/IR:H/AR:H/".to_string()],
456            )]),
457        ),
458    ])
459}
460
461fn get_eq_maxes(macro_vector: HashMap<String, u8>, eq: u8) -> Vec<String> {
462    let eq_number = format!("eq{}", eq);
463    let max_composed_value = &build_max_composed()[&eq_number];
464    let macro_vector_value: u8 = macro_vector[&format!("eq_{}", eq).to_string()];
465    max_composed_value[&macro_vector_value].clone()
466}
467
468fn get_eq_3_6_maxes(macro_vector: HashMap<String, u8>) -> Vec<String> {
469    let max_composed_value = &build_max_composed_eq_3();
470    let macro_vector_value: u8 = macro_vector["eq_3"];
471    let result = max_composed_value[&macro_vector_value].clone();
472    result[&macro_vector["eq_6"].to_string()].clone()
473}
474
475fn build_max_severity() -> HashMap<String, HashMap<i32, f64>> {
476    HashMap::from([
477        (
478            "eq1".to_string(),
479            HashMap::from([(0, 1.0), (1, 4.0), (2, 5.0)]),
480        ),
481        ("eq2".to_string(), HashMap::from([(0, 1.0), (1, 2.0)])),
482        (
483            "eq4".to_string(),
484            HashMap::from([(0, 6.0), (1, 5.0), (2, 4.0)]),
485        ),
486        (
487            "eq5".to_string(),
488            HashMap::from([(0, 1.0), (1, 1.0), (2, 1.0)]),
489        ),
490    ])
491}
492
493fn build_eq3eq6_max_severity() -> HashMap<i32, HashMap<i32, f64>> {
494    HashMap::from([
495        (0, HashMap::from([(0, 7.0), (1, 6.0)])),
496        (1, HashMap::from([(0, 8.0), (1, 8.0)])),
497        (2, HashMap::from([(1, 10.0)])),
498    ])
499}
500
501fn get_value_by_key(vector: HashMap<String, String>, key: String) -> String {
502    match vector.get(&key) {
503        Some(val) => (*val).clone(),
504        None => "".to_string(),
505    }
506}
507
508/// Calculate the score using the cvss vector of version 4.0
509///
510/// # Examples
511///
512/// ```
513/// use cvss_tools::calculate_score_v_4;
514///
515/// let vector: String = "CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N".to_string();
516/// let score: f64 = calculate_score_v_4(vector);
517///
518/// assert_eq!(7.1, score);
519/// ```
520pub fn calculate_score_v_4(vector: String) -> f64 {
521    let av_levels = build_av_levels();
522    let pr_levels = build_pr_levels();
523    let ui_levels = build_ui_levels();
524    let ac_levels = build_ac_levels();
525    let at_levels = build_at_levels();
526    let vc_levels = build_vc_levels();
527    let vi_levels = build_vi_levels();
528    let va_levels = build_va_levels();
529    let sc_levels = build_sc_levels();
530    let si_levels = build_si_levels();
531    let sa_levels = build_sa_levels();
532    let cr_levels = build_cr_levels();
533    let ir_levels = build_ir_levels();
534    let ar_levels = build_ar_levels();
535
536    let vector = preprocess_vector(vector);
537    let vector = build_full_vector(vector);
538    let macro_vector: HashMap<String, u8> = calculate_macro_vector(vector.clone());
539
540    if ["VC", "VI", "VA", "SC", "SI", "SA"]
541        .iter()
542        .all(|&metric| get_m(vector.clone(), metric.to_string()) == "N")
543    {
544        return 0.0;
545    }
546
547    let macro_vector_ordered: Vec<u8> = vec![
548        macro_vector["eq_1"],
549        macro_vector["eq_2"],
550        macro_vector["eq_3"],
551        macro_vector["eq_4"],
552        macro_vector["eq_5"],
553        macro_vector["eq_6"],
554    ];
555    let mut score: f64 = match build_macro_vector_values().get(
556        &macro_vector_ordered
557            .iter()
558            .map(|x| x.to_string())
559            .collect::<Vec<String>>()
560            .join(""),
561    ) {
562        Some(macro_vector_value) => *macro_vector_value,
563        None => 0.0,
564    };
565
566    // compute next lower macro, it can also not exist
567    let eq_1_next_lower_macro = vec![
568        macro_vector["eq_1"] + 1,
569        macro_vector["eq_2"],
570        macro_vector["eq_3"],
571        macro_vector["eq_4"],
572        macro_vector["eq_5"],
573        macro_vector["eq_6"],
574    ]
575    .iter()
576    .map(|x| x.to_string())
577    .collect::<Vec<String>>()
578    .join("");
579
580    let eq_2_next_lower_macro = vec![
581        macro_vector["eq_1"],
582        macro_vector["eq_2"] + 1,
583        macro_vector["eq_3"],
584        macro_vector["eq_4"],
585        macro_vector["eq_5"],
586        macro_vector["eq_6"],
587    ]
588    .iter()
589    .map(|x| x.to_string())
590    .collect::<Vec<String>>()
591    .join("");
592
593    // eq3 and eq6 are related
594    let mut eq3eq6_next_lower_macro: String = String::new();
595    let mut eq3eq6_next_lower_macro_left: String = String::new();
596    let mut eq3eq6_next_lower_macro_right: String = String::new();
597    let eq3: u8 = macro_vector["eq_3"];
598    let eq6: u8 = macro_vector["eq_6"];
599    if eq3 == 1 && eq6 == 1 {
600        // 11 --> 21
601        eq3eq6_next_lower_macro = vec![
602            macro_vector["eq_1"],
603            macro_vector["eq_2"],
604            macro_vector["eq_3"] + 1,
605            macro_vector["eq_4"],
606            macro_vector["eq_5"],
607            macro_vector["eq_6"],
608        ]
609        .iter()
610        .map(|x| x.to_string())
611        .collect::<Vec<String>>()
612        .join("");
613    } else if eq3 == 0 && eq6 == 1 {
614        // 01 --> 11
615        eq3eq6_next_lower_macro = vec![
616            macro_vector["eq_1"],
617            macro_vector["eq_2"],
618            macro_vector["eq_3"] + 1,
619            macro_vector["eq_4"],
620            macro_vector["eq_5"],
621            macro_vector["eq_6"],
622        ]
623        .iter()
624        .map(|x| x.to_string())
625        .collect::<Vec<String>>()
626        .join("");
627    } else if eq3 == 1 && eq6 == 0 {
628        // 10 --> 11
629        eq3eq6_next_lower_macro = vec![
630            macro_vector["eq_1"],
631            macro_vector["eq_2"],
632            macro_vector["eq_3"],
633            macro_vector["eq_4"],
634            macro_vector["eq_5"],
635            macro_vector["eq_6"] + 1,
636        ]
637        .iter()
638        .map(|x| x.to_string())
639        .collect::<Vec<String>>()
640        .join("");
641    } else if eq3 == 0 && eq6 == 0 {
642        // 00 --> 01
643        // 00 --> 10
644        eq3eq6_next_lower_macro_left = vec![
645            macro_vector["eq_1"],
646            macro_vector["eq_2"],
647            macro_vector["eq_3"],
648            macro_vector["eq_4"],
649            macro_vector["eq_5"],
650            macro_vector["eq_6"] + 1,
651        ]
652        .iter()
653        .map(|x| x.to_string())
654        .collect::<Vec<String>>()
655        .join("");
656        eq3eq6_next_lower_macro_right = vec![
657            macro_vector["eq_1"],
658            macro_vector["eq_2"],
659            macro_vector["eq_3"] + 1,
660            macro_vector["eq_4"],
661            macro_vector["eq_5"],
662            macro_vector["eq_6"],
663        ]
664        .iter()
665        .map(|x| x.to_string())
666        .collect::<Vec<String>>()
667        .join("");
668    } else {
669        // 21 --> 32 (do not exist)
670        eq3eq6_next_lower_macro = vec![
671            macro_vector["eq_1"],
672            macro_vector["eq_2"],
673            macro_vector["eq_3"] + 1,
674            macro_vector["eq_4"],
675            macro_vector["eq_5"],
676            macro_vector["eq_6"] + 1,
677        ]
678        .iter()
679        .map(|x| x.to_string())
680        .collect::<Vec<String>>()
681        .join("");
682    }
683
684    let eq4_next_lower_macro = vec![
685        macro_vector["eq_1"],
686        macro_vector["eq_2"],
687        macro_vector["eq_3"],
688        macro_vector["eq_4"] + 1,
689        macro_vector["eq_5"],
690        macro_vector["eq_6"],
691    ]
692    .iter()
693    .map(|x| x.to_string())
694    .collect::<Vec<String>>()
695    .join("");
696
697    let eq5_next_lower_macro = vec![
698        macro_vector["eq_1"],
699        macro_vector["eq_2"],
700        macro_vector["eq_3"],
701        macro_vector["eq_4"],
702        macro_vector["eq_5"] + 1,
703        macro_vector["eq_6"],
704    ]
705    .iter()
706    .map(|x| x.to_string())
707    .collect::<Vec<String>>()
708    .join("");
709
710    let score_eq1_next_lower_macro = match build_macro_vector_values().get(&eq_1_next_lower_macro) {
711        Some(macro_vector_value) => *macro_vector_value,
712        None => 0.0,
713    };
714
715    let score_eq2_next_lower_macro = match build_macro_vector_values().get(&eq_2_next_lower_macro) {
716        Some(macro_vector_value) => *macro_vector_value,
717        None => 0.0,
718    };
719
720    let mut score_eq3eq6_next_lower_macro: f64 = 0.0;
721    if eq3 == 0 && eq6 == 0 {
722        // multiple path take the one with higher score
723        let score_eq3eq6_next_lower_macro_left =
724            match build_macro_vector_values().get(&eq3eq6_next_lower_macro_left) {
725                Some(value) => *value,
726                None => 0.0,
727            };
728
729        let score_eq3eq6_next_lower_macro_right =
730            match build_macro_vector_values().get(&eq3eq6_next_lower_macro_right) {
731                Some(value) => *value,
732                None => 0.0,
733            };
734
735        if score_eq3eq6_next_lower_macro_left > score_eq3eq6_next_lower_macro_right {
736            score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_left
737        } else {
738            score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_right
739        }
740    } else {
741        score_eq3eq6_next_lower_macro =
742            match build_macro_vector_values().get(&eq3eq6_next_lower_macro) {
743                Some(value) => *value,
744                None => 0.0,
745            };
746    }
747
748    let score_eq4_next_lower_macro = match build_macro_vector_values().get(&eq4_next_lower_macro) {
749        Some(value) => *value,
750        None => 0.0,
751    };
752
753    let score_eq5_next_lower_macro = match build_macro_vector_values().get(&eq5_next_lower_macro) {
754        Some(value) => *value,
755        None => 0.0,
756    };
757
758    //   b. The severity distance of the to-be scored vector from a
759    //      highest severity vector in the same MacroVector is determined.
760    let eq1_maxes = get_eq_maxes(macro_vector.clone(), 1);
761    let eq2_maxes = get_eq_maxes(macro_vector.clone(), 2);
762    let eq3_eq6_maxes = get_eq_3_6_maxes(macro_vector.clone());
763    let eq4_maxes = get_eq_maxes(macro_vector.clone(), 4);
764    let eq5_maxes = get_eq_maxes(macro_vector.clone(), 5);
765
766    let mut max_vectors: Vec<String> = vec![];
767    for eq1_max in &eq1_maxes {
768        for eq2_max in &eq2_maxes {
769            for eq3_eq6_max in &eq3_eq6_maxes {
770                for eq4_max in &eq4_maxes {
771                    for eq5_max in &eq5_maxes {
772                        max_vectors.push(format!(
773                            "{}{}{}{}{}",
774                            eq1_max, eq2_max, eq3_eq6_max, eq4_max, eq5_max
775                        ));
776                    }
777                }
778            }
779        }
780    }
781
782    let mut severity_distance_av: f64 = 0.0;
783    let mut severity_distance_pr: f64 = 0.0;
784    let mut severity_distance_ui: f64 = 0.0;
785    let mut severity_distance_ac: f64 = 0.0;
786    let mut severity_distance_at: f64 = 0.0;
787    let mut severity_distance_vc: f64 = 0.0;
788    let mut severity_distance_vi: f64 = 0.0;
789    let mut severity_distance_va: f64 = 0.0;
790    let mut severity_distance_sc: f64 = 0.0;
791    let mut severity_distance_si: f64 = 0.0;
792    let mut severity_distance_sa: f64 = 0.0;
793    let mut severity_distance_cr: f64 = 0.0;
794    let mut severity_distance_ir: f64 = 0.0;
795    let mut severity_distance_ar: f64 = 0.0;
796
797    for max_vector in &max_vectors {
798        let max_vector_preprocessed = preprocess_vector(max_vector.to_string());
799        severity_distance_av = av_levels[&get_m(vector.clone(), "AV".to_string())]
800            - av_levels[&get_value_by_key(max_vector_preprocessed.clone(), "AV".to_string())];
801        severity_distance_pr = pr_levels[&get_m(vector.clone(), "PR".to_string())]
802            - pr_levels[&get_value_by_key(max_vector_preprocessed.clone(), "PR".to_string())];
803        severity_distance_ui = ui_levels[&get_m(vector.clone(), "UI".to_string())]
804            - ui_levels[&get_value_by_key(max_vector_preprocessed.clone(), "UI".to_string())];
805        severity_distance_ac = ac_levels[&get_m(vector.clone(), "AC".to_string())]
806            - ac_levels[&get_value_by_key(max_vector_preprocessed.clone(), "AC".to_string())];
807        severity_distance_at = at_levels[&get_m(vector.clone(), "AT".to_string())]
808            - at_levels[&get_value_by_key(max_vector_preprocessed.clone(), "AT".to_string())];
809        severity_distance_vc = vc_levels[&get_m(vector.clone(), "VC".to_string())]
810            - vc_levels[&get_value_by_key(max_vector_preprocessed.clone(), "VC".to_string())];
811        severity_distance_vi = vi_levels[&get_m(vector.clone(), "VI".to_string())]
812            - vi_levels[&get_value_by_key(max_vector_preprocessed.clone(), "VI".to_string())];
813        severity_distance_va = va_levels[&get_m(vector.clone(), "VA".to_string())]
814            - va_levels[&get_value_by_key(max_vector_preprocessed.clone(), "VA".to_string())];
815        severity_distance_sc = sc_levels[&get_m(vector.clone(), "SC".to_string())]
816            - sc_levels[&get_value_by_key(max_vector_preprocessed.clone(), "SC".to_string())];
817        severity_distance_si = si_levels[&get_m(vector.clone(), "SI".to_string())]
818            - si_levels[&get_value_by_key(max_vector_preprocessed.clone(), "SI".to_string())];
819        severity_distance_sa = sa_levels[&get_m(vector.clone(), "SA".to_string())]
820            - sa_levels[&get_value_by_key(max_vector_preprocessed.clone(), "SA".to_string())];
821        severity_distance_cr = cr_levels[&get_m(vector.clone(), "CR".to_string())]
822            - cr_levels[&get_value_by_key(max_vector_preprocessed.clone(), "CR".to_string())];
823        severity_distance_ir = ir_levels[&get_m(vector.clone(), "IR".to_string())]
824            - ir_levels[&get_value_by_key(max_vector_preprocessed.clone(), "IR".to_string())];
825        severity_distance_ar = ar_levels[&get_m(vector.clone(), "AR".to_string())]
826            - ar_levels[&get_value_by_key(max_vector_preprocessed.clone(), "AR".to_string())];
827
828        if vec![
829            severity_distance_av,
830            severity_distance_pr,
831            severity_distance_ui,
832            severity_distance_ac,
833            severity_distance_at,
834            severity_distance_vc,
835            severity_distance_vi,
836            severity_distance_va,
837            severity_distance_sc,
838            severity_distance_si,
839            severity_distance_sa,
840            severity_distance_cr,
841            severity_distance_ir,
842            severity_distance_ar,
843        ]
844        .iter()
845        .any(|x| x < &0.0)
846        {
847            continue;
848        }
849
850        break;
851    }
852
853    let current_severity_distance_eq1: f64 =
854        severity_distance_av + severity_distance_pr + severity_distance_ui;
855    let current_severity_distance_eq2: f64 = severity_distance_ac + severity_distance_at;
856    let current_severity_distance_eq3eq6: f64 = severity_distance_vc
857        + severity_distance_vi
858        + severity_distance_va
859        + severity_distance_cr
860        + severity_distance_ir
861        + severity_distance_ar;
862    let current_severity_distance_eq4: f64 =
863        severity_distance_sc + severity_distance_si + severity_distance_sa;
864
865    let step: f64 = 0.1;
866
867    // if the next lower macro score do not exist the result is Nan
868    // Rename to maximal scoring difference (aka MSD)
869    let mut available_distance_eq1: f64 = 0.0;
870    if score_eq1_next_lower_macro != 0.0 {
871        available_distance_eq1 = score - score_eq1_next_lower_macro;
872    }
873
874    let mut available_distance_eq2: f64 = 0.0;
875    if score_eq2_next_lower_macro != 0.0 {
876        available_distance_eq2 = score - score_eq2_next_lower_macro;
877    }
878
879    let mut available_distance_eq3eq6: f64 = 0.0;
880    if score_eq3eq6_next_lower_macro != 0.0 {
881        available_distance_eq3eq6 = score - score_eq3eq6_next_lower_macro;
882    }
883
884    let mut available_distance_eq4: f64 = 0.0;
885    if score_eq4_next_lower_macro != 0.0 {
886        available_distance_eq4 = score - score_eq4_next_lower_macro;
887    }
888
889    let mut available_distance_eq5: f64 = 0.0;
890    if score_eq5_next_lower_macro != 0.0 {
891        available_distance_eq5 = score - score_eq5_next_lower_macro;
892    }
893
894    // some of them do not exist, we will find them by retrieving the score. If score null then do not exist
895    let mut n_existing_lower: f64 = 0.0;
896
897    let mut normalized_severity_eq1: f64 = 0.0;
898    let mut normalized_severity_eq2: f64 = 0.0;
899    let mut normalized_severity_eq3eq6: f64 = 0.0;
900    let mut normalized_severity_eq4: f64 = 0.0;
901    let mut normalized_severity_eq5: f64 = 0.0;
902
903    let max_severity = build_max_severity();
904    let max_severity_eq1 = max_severity[&"eq1".to_string()][&(macro_vector["eq_1"] as i32)] * step;
905    let max_severity_eq2 = max_severity[&"eq2".to_string()][&(macro_vector["eq_2"] as i32)] * step;
906    let max_severity_eq3eq6 = build_eq3eq6_max_severity()[&(macro_vector["eq_3"] as i32)]
907        [&(macro_vector["eq_6"] as i32)]
908        * step;
909    let max_severity_eq4 = max_severity[&"eq4".to_string()][&(macro_vector["eq_4"] as i32)] * step;
910
911    if available_distance_eq1 != 0.0 {
912        n_existing_lower += 1.0;
913        let percent_to_next_eq1_severity = current_severity_distance_eq1 / max_severity_eq1;
914        normalized_severity_eq1 = available_distance_eq1 * percent_to_next_eq1_severity;
915    }
916
917    if available_distance_eq2 != 0.0 {
918        n_existing_lower += 1.0;
919        let percent_to_next_eq2_severity = current_severity_distance_eq2 / max_severity_eq2;
920        normalized_severity_eq2 = available_distance_eq2 * percent_to_next_eq2_severity;
921    }
922
923    if available_distance_eq3eq6 != 0.0 {
924        n_existing_lower += 1.0;
925        let percent_to_next_eq3eq6_severity =
926            current_severity_distance_eq3eq6 / max_severity_eq3eq6;
927        normalized_severity_eq3eq6 = available_distance_eq3eq6 * percent_to_next_eq3eq6_severity;
928    }
929
930    if available_distance_eq4 != 0.0 {
931        n_existing_lower += 1.0;
932        let percent_to_next_eq4_severity = (current_severity_distance_eq4) / max_severity_eq4;
933        normalized_severity_eq4 = available_distance_eq4 * percent_to_next_eq4_severity;
934    }
935
936    if available_distance_eq5 != 0.0 {
937        // for eq5 is always 0 the percentage
938        n_existing_lower += 1.0;
939        let percent_to_next_eq5_severity = 0.0;
940        normalized_severity_eq5 = available_distance_eq5 * percent_to_next_eq5_severity;
941    }
942
943    // sometimes we need to go up but there is nothing there, or down but there is nothing there so it's a change of 0.
944    let mean_distance: f64 = match n_existing_lower {
945        value if value == 0.0 => 0.0,
946        _ => {
947            (normalized_severity_eq1
948                + normalized_severity_eq2
949                + normalized_severity_eq3eq6
950                + normalized_severity_eq4
951                + normalized_severity_eq5)
952                / n_existing_lower
953        }
954    };
955
956    score -= mean_distance;
957    if score < 0.0 {
958        score = 0.0;
959    }
960
961    if score > 10.0 {
962        score = 10.0;
963    }
964
965    (score * 10.0).round() / 10.0
966}
967
968/// Calculate the severity by the score value for cvss v4.0
969///
970/// # Examples
971///
972/// ```
973/// use cvss_tools::calculate_severity_v_4;
974///
975/// let score: f64 = 7.1;
976/// let answer = calculate_severity_v_4(score);
977///
978/// assert_eq!("High".to_string(), answer);
979/// ```
980pub fn calculate_severity_v_4(score: f64) -> String {
981    if score == 0.0 {
982        "None".to_string()
983    } else if score < 4.0 {
984        "Low".to_string()
985    } else if score < 7.0 {
986        "Medium".to_string()
987    } else if score < 9.0 {
988        "High".to_string()
989    } else {
990        "Critical".to_string()
991    }
992}
993
994/// Calculate the value of the Base cvss vector of version 2
995///
996/// # Examples
997///
998/// ```
999/// use cvss_tools::calculate_base_score_v_2;
1000///
1001/// let vector: String = "AV:N/AC:L/Au:N/C:N/I:N/A:C".to_string();
1002/// let score: f64 = calculate_base_score_v_2(vector);
1003///
1004/// assert_eq!(score, 7.8);
1005/// ```
1006pub fn calculate_base_score_v_2(vector: String) -> f64 {
1007    let vector: HashMap<String, String> = preprocess_vector(vector);
1008
1009    let access_vector: f64 = match vector["AV"].as_str() {
1010        "L" => 0.395,
1011        "A" => 0.646,
1012        "N" => 1.0,
1013        _ => panic!("Invalid value for AccessVector (AV)."),
1014    };
1015
1016    let access_complexity: f64 = match vector["AC"].as_str() {
1017        "H" => 0.35,
1018        "M" => 0.61,
1019        "L" => 0.71,
1020        _ => panic!("Invalid value for AccessComplexity (AC)."),
1021    };
1022
1023    let authentication: f64 = match vector["Au"].as_str() {
1024        "M" => 0.45,
1025        "S" => 0.56,
1026        "N" => 0.704,
1027        _ => panic!("Invalid value for Authentication (Au)."),
1028    };
1029
1030    let conf_impact: f64 = match vector["C"].as_str() {
1031        "N" => 0.0,
1032        "P" => 0.275,
1033        "C" => 0.660,
1034        _ => panic!("Invalid value for Confidence Impact (C)."),
1035    };
1036
1037    let integ_impact: f64 = match vector["I"].as_str() {
1038        "N" => 0.0,
1039        "P" => 0.275,
1040        "C" => 0.660,
1041        _ => panic!("Invalid value for IntegImpact (I)."),
1042    };
1043
1044    let avail_impact: f64 = match vector["A"].as_str() {
1045        "N" => 0.0,
1046        "P" => 0.275,
1047        "C" => 0.660,
1048        _ => panic!("Invalid value for IntegImpact (A)."),
1049    };
1050
1051    let impact: f64 =
1052        10.41 * (1.0 - (1.0 - conf_impact) * (1.0 - integ_impact) * (1.0 - avail_impact));
1053    let exploitability: f64 = 20.0 * access_vector * access_complexity * authentication;
1054    let f_impact: f64 = match impact {
1055        value if value == 0.0 => 0.0,
1056        _ => 1.176,
1057    };
1058    let score: f64 = (0.6 * impact + (0.4 * exploitability - 1.5)) * f_impact;
1059
1060    (score * 10.0).round() / 10.0
1061}
1062
1063/// Calculate the value of the Temp cvss vector of version 2
1064///
1065/// # Examples
1066///
1067/// ```
1068/// use cvss_tools::calculate_temp_score_v_2;
1069///
1070/// let vector: String = "E:F/RL:OF/RC:C".to_string();
1071/// let score: f64 = calculate_temp_score_v_2(7.8, vector);
1072///
1073/// assert_eq!(score, 6.4);
1074/// ```
1075pub fn calculate_temp_score_v_2(base_score: f64, temp_vector: String) -> f64 {
1076    let vector: HashMap<String, String> = preprocess_vector(temp_vector);
1077    let exploitability: f64 = match vector["E"].as_str() {
1078        "U" => 0.85,
1079        "POC" => 0.9,
1080        "F" => 0.95,
1081        "H" => 1.0,
1082        "ND" => 1.0,
1083        _ => panic!("Invalid value for Exploitability (E)."),
1084    };
1085    let remediation_level: f64 = match vector["RL"].as_str() {
1086        "OF" => 0.87,
1087        "TF" => 0.90,
1088        "W" => 0.95,
1089        "U" => 1.0,
1090        "ND" => 1.0,
1091        _ => panic!("Invalid value for RemediationLevel (RL)."),
1092    };
1093    let report_confidence: f64 = match vector["RC"].as_str() {
1094        "UC" => 0.90,
1095        "UR" => 0.95,
1096        "C" => 1.0,
1097        "ND" => 1.0,
1098        _ => panic!("Invalid value for ReportConfidence (RC)"),
1099    };
1100
1101    let score: f64 = base_score * exploitability * remediation_level * report_confidence;
1102
1103    (score * 10.0).round() / 10.0
1104}
1105
1106/// Calculate the value of the Env cvss vector of version 2
1107///
1108/// # Examples
1109///
1110/// ```
1111/// use cvss_tools::calculate_env_score_v_2;
1112///
1113/// let vector: String = "AV:N/AC:L/Au:N/C:N/I:N/A:C/E:F/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:H".to_string();
1114/// let score: f64 = calculate_env_score_v_2(vector);
1115///
1116/// assert_eq!(score, 9.2);
1117/// ```
1118pub fn calculate_env_score_v_2(vector: String) -> f64 {
1119    let vector: HashMap<String, String> = preprocess_vector(vector);
1120
1121    let conf_impact: f64 = match vector["C"].as_str() {
1122        "N" => 0.0,
1123        "P" => 0.275,
1124        "C" => 0.660,
1125        _ => panic!("Invalid value for Confidence Impact (C)."),
1126    };
1127
1128    let integ_impact: f64 = match vector["I"].as_str() {
1129        "N" => 0.0,
1130        "P" => 0.275,
1131        "C" => 0.660,
1132        _ => panic!("Invalid value for IntegImpact (I)."),
1133    };
1134
1135    let collateral_damage_potential: f64 = match vector["CDP"].as_str() {
1136        "N" => 0.0,
1137        "L" => 0.1,
1138        "LM" => 0.3,
1139        "MH" => 0.4,
1140        "H" => 0.5,
1141        "ND" => 0.0,
1142        _ => panic!("Invalid value for CollateralDamagePotential (CDP)"),
1143    };
1144
1145    let target_distribution: f64 = match vector["TD"].as_str() {
1146        "N" => 0.0,
1147        "L" => 0.25,
1148        "M" => 0.75,
1149        "H" => 1.0,
1150        "ND" => 1.0,
1151        _ => panic!("Invalid value for TargetDistribution (TD)"),
1152    };
1153
1154    let conf_req: f64 = match vector["CR"].as_str() {
1155        "L" => 0.5,
1156        "M" => 1.0,
1157        "H" => 1.51,
1158        "ND" => 1.0,
1159        _ => panic!("Invalid value for ConfReq (CR)"),
1160    };
1161
1162    let integ_req: f64 = match vector["IR"].as_str() {
1163        "L" => 0.5,
1164        "M" => 1.0,
1165        "H" => 1.51,
1166        "ND" => 1.0,
1167        _ => panic!("Invalid value for IntegReq (IR)"),
1168    };
1169
1170    let avail_req: f64 = match vector["AR"].as_str() {
1171        "L" => 0.5,
1172        "M" => 1.0,
1173        "H" => 1.51,
1174        "ND" => 1.0,
1175        _ => panic!("Invalid value for AvailReq (AR)"),
1176    };
1177
1178    let avail_impact: f64 = match vector["A"].as_str() {
1179        "N" => 0.0,
1180        "P" => 0.275,
1181        "C" => 0.660,
1182        _ => panic!("Invalid value for IntegImpact (A)."),
1183    };
1184
1185    let access_vector: f64 = match vector["AV"].as_str() {
1186        "L" => 0.395,
1187        "A" => 0.646,
1188        "N" => 1.0,
1189        _ => panic!("Invalid value for AccessVector (AV)."),
1190    };
1191
1192    let access_complexity: f64 = match vector["AC"].as_str() {
1193        "H" => 0.35,
1194        "M" => 0.61,
1195        "L" => 0.71,
1196        _ => panic!("Invalid value for AccessComplexity (AC)."),
1197    };
1198
1199    let authentication: f64 = match vector["Au"].as_str() {
1200        "M" => 0.45,
1201        "S" => 0.56,
1202        "N" => 0.704,
1203        _ => panic!("Invalid value for Authentication (Au)."),
1204    };
1205
1206    let second_adjusted_impact: f64 = 10.41
1207        * (1.0
1208            - (1.0 - conf_impact * conf_req)
1209                * (1.0 - integ_impact * integ_req)
1210                * (1.0 - avail_impact * avail_req));
1211
1212    let adjusted_impact: f64 = match second_adjusted_impact {
1213        value if value < 10.0 => value,
1214        _ => 10.0,
1215    };
1216
1217    let impact: f64 =
1218        10.41 * (1.0 - (1.0 - conf_impact) * (1.0 - integ_impact) * (1.0 - avail_impact));
1219    let exploitability: f64 = 20.0 * access_vector * access_complexity * authentication;
1220    let f_impact: f64 = match impact {
1221        value if value == 0.0 => 0.0,
1222        _ => 1.176,
1223    };
1224    let adjusted_base: f64 = (0.6 * adjusted_impact + (0.4 * exploitability - 1.5)) * f_impact;
1225
1226    let exploitability: f64 = match vector["E"].as_str() {
1227        "U" => 0.85,
1228        "POC" => 0.9,
1229        "F" => 0.95,
1230        "H" => 1.0,
1231        "ND" => 1.0,
1232        _ => panic!("Invalid value for Exploitability (E)."),
1233    };
1234
1235    let remediation_level: f64 = match vector["RL"].as_str() {
1236        "OF" => 0.87,
1237        "TF" => 0.90,
1238        "W" => 0.95,
1239        "U" => 1.0,
1240        "ND" => 1.0,
1241        _ => panic!("Invalid value for RemediationLevel (RL)."),
1242    };
1243
1244    let report_confidence: f64 = match vector["RC"].as_str() {
1245        "UC" => 0.90,
1246        "UR" => 0.95,
1247        "C" => 1.0,
1248        "ND" => 1.0,
1249        _ => panic!("Invalid value for ReportConfidence (RC)"),
1250    };
1251
1252    let mut adjusted_temporal: f64 =
1253        adjusted_base * exploitability * remediation_level * report_confidence;
1254    adjusted_temporal = (adjusted_temporal * 10.0).round() / 10.0;
1255
1256    let score: f64 = (adjusted_temporal + (10.0 - adjusted_temporal) * collateral_damage_potential)
1257        * target_distribution;
1258
1259    (score * 10.0).round() / 10.0
1260}
1261
1262/// Calculate Severity for cvss version 2
1263///
1264/// # Examples
1265///
1266/// ```
1267/// use cvss_tools::calculate_severity_v_2;
1268///
1269/// let severity: String = calculate_severity_v_2(7.8);
1270///
1271/// assert_eq!(severity, "High".to_string());
1272/// ```
1273pub fn calculate_severity_v_2(base_score: f64) -> String {
1274    if base_score < 4.0 {
1275        "Low".to_string()
1276    } else if base_score < 7.0 {
1277        "Medium".to_string()
1278    } else {
1279        "High".to_string()
1280    }
1281}
1282
1283#[cfg(test)]
1284mod tests {
1285    use super::*;
1286
1287    #[test]
1288    fn calculate_base_score_v_2_positive() {
1289        assert_eq!(
1290            calculate_base_score_v_2("AV:N/AC:L/Au:N/C:N/I:N/A:C".to_string()),
1291            7.8
1292        );
1293    }
1294
1295    #[test]
1296    fn calculate_temp_score_v_2_positive() {
1297        assert_eq!(
1298            calculate_temp_score_v_2(7.8, "E:F/RL:OF/RC:C".to_string()),
1299            6.4
1300        );
1301    }
1302
1303    #[test]
1304    fn calculate_env_score_v_2_positive() {
1305        let vector: String =
1306            "AV:N/AC:L/Au:N/C:N/I:N/A:C/E:F/RL:OF/RC:C/CDP:H/TD:H/CR:M/IR:M/AR:H".to_string();
1307        assert_eq!(calculate_env_score_v_2(vector), 9.2);
1308    }
1309
1310    #[test]
1311    fn calculate_severity_v_2_low_positive() {
1312        for score in vec![0.0, 3.7, 3.9] {
1313            assert_eq!(calculate_severity_v_2(score), "Low".to_string());
1314        }
1315    }
1316
1317    #[test]
1318    fn calculate_severity_v_2_medium_positive() {
1319        for score in vec![4.0, 5.0, 6.9] {
1320            assert_eq!(calculate_severity_v_2(score), "Medium".to_string());
1321        }
1322    }
1323
1324    #[test]
1325    fn calculate_severity_v_2_high_positive() {
1326        for score in vec![7.0, 7.8, 10.0] {
1327            assert_eq!(calculate_severity_v_2(score), "High".to_string());
1328        }
1329    }
1330
1331    #[test]
1332    fn calculate_score_v_4_positive() {
1333        let file_obj: File = match File::open("src/test_data/base_score_cvss_v4_values.json") {
1334            Ok(value) => value,
1335            Err(_) => panic!("Can't read base_score_cvss_v4_values.json"),
1336        };
1337        let reader = BufReader::new(file_obj);
1338        let data: HashMap<String, f64> = match serde_json::from_reader(reader) {
1339            Ok(value) => value,
1340            Err(_) => panic!("Can't create HashMap<String, f64> from reader."),
1341        };
1342        for (vector, score) in data {
1343            assert_eq!(calculate_score_v_4(vector.clone()), score, "{}", vector);
1344        }
1345    }
1346
1347    #[test]
1348    fn calculate_severity_v_4_positive() {
1349        let file_obj: File = match File::open("src/test_data/base_severity_cvss_v4_values.json") {
1350            Ok(value) => value,
1351            Err(_) => panic!("Can't read base_severity_cvss_v4_values.json"),
1352        };
1353        let reader = BufReader::new(file_obj);
1354        let data: HashMap<String, String> = match serde_json::from_reader(reader) {
1355            Ok(value) => value,
1356            Err(_) => panic!("Can't create HashMap<String, String> from reader."),
1357        };
1358        for (vector, severity) in data {
1359            let score: f64 = calculate_score_v_4(vector);
1360            assert_eq!(calculate_severity_v_4(score), severity)
1361        }
1362    }
1363}