1use crate::parsing;
4use crate::parsing::{Field, OptionalParser};
5use crate::v2::base::BaseVector;
6use crate::v2::temporal::TemporalVector;
7use serde::{Deserialize, Serialize};
8use std::fmt::{Error, Formatter};
9use std::str::Split;
10
11#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
15pub struct EnvironmentalVector {
16 pub collateral_damage_potential: Option<CollateralDamagePotential>,
18 pub target_distribution: Option<TargetDistribution>,
20 pub confidentiality_requirement: Option<ConfidentialityRequirement>,
22 pub integrity_requirement: Option<IntegrityRequirement>,
24 pub availability_requirement: Option<AvailabilityRequirement>,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
35pub enum CollateralDamagePotential {
36 None,
38 Low,
40 LowMedium,
42 MediumHigh,
44 High,
46 }
48
49#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
56pub enum TargetDistribution {
57 None,
59 Low,
61 Medium,
63 High,
65 }
67
68#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
75pub enum ConfidentialityRequirement {
76 Low,
78 Medium,
80 High,
82 }
84
85#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
92pub enum IntegrityRequirement {
93 Low,
95 Medium,
97 High,
99 }
101
102#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
109pub enum AvailabilityRequirement {
110 Low,
112 Medium,
114 High,
116 }
118
119impl EnvironmentalVector {
120 pub fn score(&self, base_vector: BaseVector, temporal_vector: Option<TemporalVector>) -> f64 {
126 let confidentiality_requirement = match self.confidentiality_requirement {
127 None => 1.0,
128 Some(requirement) => requirement.numerical_value(),
129 };
130
131 let integrity_requirement = match self.integrity_requirement {
132 None => 1.0,
133 Some(requirement) => requirement.numerical_value(),
134 };
135
136 let availability_requirement = match self.availability_requirement {
137 None => 1.0,
138 Some(requirement) => requirement.numerical_value(),
139 };
140
141 let adjusted_impact = f64::min(
142 10.0,
143 10.41
144 * (1.0
145 - (1.0
146 - base_vector.confidentiality_impact.numerical_value()
147 * confidentiality_requirement)
148 * (1.0
149 - base_vector.integrity_impact.numerical_value()
150 * integrity_requirement)
151 * (1.0
152 - base_vector.availability_impact.numerical_value()
153 * availability_requirement)),
154 );
155
156 let adjusted_base = base_vector.score_with_adjusted_impact(adjusted_impact);
157
158 let adjusted_temporal = match temporal_vector {
159 None => adjusted_base,
160 Some(vector) => vector.score(adjusted_base),
161 };
162
163 let collateral_damage_potential = match self.collateral_damage_potential {
164 None => 0.0,
165 Some(cdp) => cdp.numerical_value(),
166 };
167
168 let target_distribution = match self.target_distribution {
169 None => 1.0,
170 Some(td) => td.numerical_value(),
171 };
172
173 (10.0
174 * (adjusted_temporal + (10.0 - adjusted_temporal) * collateral_damage_potential)
175 * target_distribution)
176 .round()
177 / 10.0
178 }
179}
180
181impl CollateralDamagePotential {
182 pub fn numerical_value(&self) -> f64 {
186 match *self {
187 CollateralDamagePotential::None => 0.0,
188 CollateralDamagePotential::Low => 0.1,
189 CollateralDamagePotential::LowMedium => 0.3,
190 CollateralDamagePotential::MediumHigh => 0.4,
191 CollateralDamagePotential::High => 0.5,
192 }
193 }
194}
195
196impl TargetDistribution {
197 pub fn numerical_value(&self) -> f64 {
201 match *self {
202 TargetDistribution::None => 0.0,
203 TargetDistribution::Low => 0.25,
204 TargetDistribution::Medium => 0.75,
205 TargetDistribution::High => 1.0,
206 }
207 }
208}
209
210impl ConfidentialityRequirement {
211 pub fn numerical_value(&self) -> f64 {
215 match *self {
216 ConfidentialityRequirement::Low => 0.5,
217 ConfidentialityRequirement::Medium => 1.0,
218 ConfidentialityRequirement::High => 1.51,
219 }
220 }
221}
222
223impl IntegrityRequirement {
224 pub fn numerical_value(&self) -> f64 {
228 match *self {
229 IntegrityRequirement::Low => 0.5,
230 IntegrityRequirement::Medium => 1.0,
231 IntegrityRequirement::High => 1.51,
232 }
233 }
234}
235
236impl AvailabilityRequirement {
237 pub fn numerical_value(&self) -> f64 {
241 match *self {
242 AvailabilityRequirement::Low => 0.5,
243 AvailabilityRequirement::Medium => 1.0,
244 AvailabilityRequirement::High => 1.51,
245 }
246 }
247}
248
249impl OptionalParser for EnvironmentalVector {
250 fn parse_optional(split: &Box<Split<char>>) -> Option<Self>
251 where
252 Self: Sized,
253 {
254 let res = EnvironmentalVector {
255 collateral_damage_potential: CollateralDamagePotential::parse_optional(split),
256 target_distribution: TargetDistribution::parse_optional(split),
257 confidentiality_requirement: ConfidentialityRequirement::parse_optional(split),
258 integrity_requirement: IntegrityRequirement::parse_optional(split),
259 availability_requirement: AvailabilityRequirement::parse_optional(split),
260 };
261
262 if res.collateral_damage_potential.is_none()
263 && res.target_distribution.is_none()
264 && res.confidentiality_requirement.is_none()
265 && res.integrity_requirement.is_none()
266 && res.availability_requirement.is_none()
267 {
268 None
269 } else {
270 Some(res)
271 }
272 }
273}
274
275impl Field for CollateralDamagePotential {
276 fn abbreviated_form(&self) -> &'static str {
277 match *self {
278 CollateralDamagePotential::None => "CDP:N",
279 CollateralDamagePotential::Low => "CDP:L",
280 CollateralDamagePotential::LowMedium => "CDP:LM",
281 CollateralDamagePotential::MediumHigh => "CDP:MH",
282 CollateralDamagePotential::High => "CDP:H",
283 }
284 }
285
286 fn error_message() -> &'static str {
287 unreachable!()
289 }
290
291 fn parse(input: &str) -> Option<CollateralDamagePotential> {
292 if input == CollateralDamagePotential::None.abbreviated_form() {
293 Some(CollateralDamagePotential::None)
294 } else if input == CollateralDamagePotential::Low.abbreviated_form() {
295 Some(CollateralDamagePotential::Low)
296 } else if input == CollateralDamagePotential::LowMedium.abbreviated_form() {
297 Some(CollateralDamagePotential::LowMedium)
298 } else if input == CollateralDamagePotential::MediumHigh.abbreviated_form() {
299 Some(CollateralDamagePotential::MediumHigh)
300 } else if input == CollateralDamagePotential::High.abbreviated_form() {
301 Some(CollateralDamagePotential::High)
302 } else {
303 None
304 }
305 }
306}
307
308impl Field for TargetDistribution {
309 fn abbreviated_form(&self) -> &'static str {
310 match *self {
311 TargetDistribution::None => "TD:N",
312 TargetDistribution::Low => "TD:L",
313 TargetDistribution::Medium => "TD:M",
314 TargetDistribution::High => "TD:H",
315 }
316 }
317
318 fn error_message() -> &'static str {
319 unreachable!()
321 }
322
323 fn parse(input: &str) -> Option<TargetDistribution> {
324 if input == TargetDistribution::None.abbreviated_form() {
325 Some(TargetDistribution::None)
326 } else if input == TargetDistribution::Low.abbreviated_form() {
327 Some(TargetDistribution::Low)
328 } else if input == TargetDistribution::Medium.abbreviated_form() {
329 Some(TargetDistribution::Medium)
330 } else if input == TargetDistribution::High.abbreviated_form() {
331 Some(TargetDistribution::High)
332 } else {
333 None
334 }
335 }
336}
337
338impl Field for ConfidentialityRequirement {
339 fn abbreviated_form(&self) -> &'static str {
340 match *self {
341 ConfidentialityRequirement::Low => "CR:L",
342 ConfidentialityRequirement::Medium => "CR:M",
343 ConfidentialityRequirement::High => "CR:H",
344 }
345 }
346
347 fn error_message() -> &'static str {
348 unreachable!()
350 }
351
352 fn parse(input: &str) -> Option<ConfidentialityRequirement> {
353 if input == ConfidentialityRequirement::Low.abbreviated_form() {
354 Some(ConfidentialityRequirement::Low)
355 } else if input == ConfidentialityRequirement::Medium.abbreviated_form() {
356 Some(ConfidentialityRequirement::Medium)
357 } else if input == ConfidentialityRequirement::High.abbreviated_form() {
358 Some(ConfidentialityRequirement::High)
359 } else {
360 None
361 }
362 }
363}
364
365impl Field for IntegrityRequirement {
366 fn abbreviated_form(&self) -> &'static str {
367 match *self {
368 IntegrityRequirement::Low => "IR:L",
369 IntegrityRequirement::Medium => "IR:M",
370 IntegrityRequirement::High => "IR:H",
371 }
372 }
373
374 fn error_message() -> &'static str {
375 unreachable!()
377 }
378
379 fn parse(input: &str) -> Option<IntegrityRequirement> {
380 if input == IntegrityRequirement::Low.abbreviated_form() {
381 Some(IntegrityRequirement::Low)
382 } else if input == IntegrityRequirement::Medium.abbreviated_form() {
383 Some(IntegrityRequirement::Medium)
384 } else if input == IntegrityRequirement::High.abbreviated_form() {
385 Some(IntegrityRequirement::High)
386 } else {
387 None
388 }
389 }
390}
391
392impl Field for AvailabilityRequirement {
393 fn abbreviated_form(&self) -> &'static str {
394 match *self {
395 AvailabilityRequirement::Low => "AR:L",
396 AvailabilityRequirement::Medium => "AR:M",
397 AvailabilityRequirement::High => "AR:H",
398 }
399 }
400
401 fn error_message() -> &'static str {
402 unreachable!()
404 }
405
406 fn parse(input: &str) -> Option<AvailabilityRequirement> {
407 if input == AvailabilityRequirement::Low.abbreviated_form() {
408 Some(AvailabilityRequirement::Low)
409 } else if input == AvailabilityRequirement::Medium.abbreviated_form() {
410 Some(AvailabilityRequirement::Medium)
411 } else if input == AvailabilityRequirement::High.abbreviated_form() {
412 Some(AvailabilityRequirement::High)
413 } else {
414 None
415 }
416 }
417}
418
419impl OptionalParser for CollateralDamagePotential {
420 fn parse_optional(split: &Box<Split<char>>) -> Option<Self>
421 where
422 Self: Sized,
423 {
424 parsing::parse_optional_default_field(split)
425 }
426}
427
428impl OptionalParser for TargetDistribution {
429 fn parse_optional(split: &Box<Split<char>>) -> Option<Self>
430 where
431 Self: Sized,
432 {
433 parsing::parse_optional_default_field(split)
434 }
435}
436
437impl OptionalParser for ConfidentialityRequirement {
438 fn parse_optional(split: &Box<Split<char>>) -> Option<Self>
439 where
440 Self: Sized,
441 {
442 parsing::parse_optional_default_field(split)
443 }
444}
445
446impl OptionalParser for IntegrityRequirement {
447 fn parse_optional(split: &Box<Split<char>>) -> Option<Self>
448 where
449 Self: Sized,
450 {
451 parsing::parse_optional_default_field(split)
452 }
453}
454
455impl OptionalParser for AvailabilityRequirement {
456 fn parse_optional(split: &Box<Split<char>>) -> Option<Self>
457 where
458 Self: Sized,
459 {
460 parsing::parse_optional_default_field(split)
461 }
462}
463
464impl std::fmt::Display for CollateralDamagePotential {
465 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
466 write!(f, "{}", self.abbreviated_form())
467 }
468}
469
470impl std::fmt::Display for TargetDistribution {
471 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
472 write!(f, "{}", self.abbreviated_form())
473 }
474}
475
476impl std::fmt::Display for ConfidentialityRequirement {
477 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
478 write!(f, "{}", self.abbreviated_form())
479 }
480}
481
482impl std::fmt::Display for IntegrityRequirement {
483 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
484 write!(f, "{}", self.abbreviated_form())
485 }
486}
487
488impl std::fmt::Display for AvailabilityRequirement {
489 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
490 write!(f, "{}", self.abbreviated_form())
491 }
492}
493
494impl std::fmt::Display for EnvironmentalVector {
495 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
496 let mut fields = Vec::new();
498
499 match self.collateral_damage_potential {
500 None => (),
501 Some(field) => fields.push(field.abbreviated_form()),
502 };
503
504 match self.target_distribution {
505 None => (),
506 Some(field) => fields.push(field.abbreviated_form()),
507 };
508
509 match self.confidentiality_requirement {
510 None => (),
511 Some(field) => fields.push(field.abbreviated_form()),
512 };
513
514 match self.integrity_requirement {
515 None => (),
516 Some(field) => fields.push(field.abbreviated_form()),
517 };
518
519 match self.availability_requirement {
520 None => (),
521 Some(field) => fields.push(field.abbreviated_form()),
522 };
523
524 write!(f, "{}", fields.join("/"))
525 }
526}
527
528#[cfg(test)]
529pub mod tests {
530 use super::*;
531 use crate::v2::base::{
532 AccessComplexity, AccessVector, Authentication, AvailabilityImpact, BaseVector,
533 ConfidentialityImpact, IntegrityImpact,
534 };
535 use crate::v2::temporal::{Exploitability, RemediationLevel, ReportConfidence, TemporalVector};
536
537 #[test]
538 fn test_formatting() {
539 assert_eq!("CDP:N", format!("{}", CollateralDamagePotential::None));
541 assert_eq!("CDP:L", format!("{}", CollateralDamagePotential::Low));
542 assert_eq!(
543 "CDP:LM",
544 format!("{}", CollateralDamagePotential::LowMedium)
545 );
546 assert_eq!(
547 "CDP:MH",
548 format!("{}", CollateralDamagePotential::MediumHigh)
549 );
550 assert_eq!("CDP:H", format!("{}", CollateralDamagePotential::High));
551
552 assert_eq!("TD:N", format!("{}", TargetDistribution::None));
554 assert_eq!("TD:L", format!("{}", TargetDistribution::Low));
555 assert_eq!("TD:M", format!("{}", TargetDistribution::Medium));
556 assert_eq!("TD:H", format!("{}", TargetDistribution::High));
557
558 assert_eq!("CR:L", format!("{}", ConfidentialityRequirement::Low));
560 assert_eq!("CR:M", format!("{}", ConfidentialityRequirement::Medium));
561 assert_eq!("CR:H", format!("{}", ConfidentialityRequirement::High));
562
563 assert_eq!("IR:L", format!("{}", IntegrityRequirement::Low));
565 assert_eq!("IR:M", format!("{}", IntegrityRequirement::Medium));
566 assert_eq!("IR:H", format!("{}", IntegrityRequirement::High));
567
568 assert_eq!("AR:L", format!("{}", AvailabilityRequirement::Low));
570 assert_eq!("AR:M", format!("{}", AvailabilityRequirement::Medium));
571 assert_eq!("AR:H", format!("{}", AvailabilityRequirement::High));
572
573 assert_eq!(
575 "CDP:H/TD:H/CR:M/IR:M/AR:H",
576 format!("{}", provide_environmental_vector1())
577 );
578
579 let mut missing_fields = provide_environmental_vector1();
581 missing_fields.collateral_damage_potential = None;
582 missing_fields.target_distribution = None;
583 assert_eq!("CR:M/IR:M/AR:H", format!("{}", missing_fields));
584 }
585
586 #[test]
587 fn test_parsing() {
588 assert_eq!(
590 Some(CollateralDamagePotential::None),
591 CollateralDamagePotential::parse("CDP:N")
592 );
593 assert_eq!(
594 Some(CollateralDamagePotential::Low),
595 CollateralDamagePotential::parse("CDP:L")
596 );
597 assert_eq!(
598 Some(CollateralDamagePotential::LowMedium),
599 CollateralDamagePotential::parse("CDP:LM")
600 );
601 assert_eq!(
602 Some(CollateralDamagePotential::MediumHigh),
603 CollateralDamagePotential::parse("CDP:MH")
604 );
605 assert_eq!(
606 Some(CollateralDamagePotential::High),
607 CollateralDamagePotential::parse("CDP:H")
608 );
609 assert_eq!(
611 Some(TargetDistribution::None),
612 TargetDistribution::parse("TD:N")
613 );
614 assert_eq!(
615 Some(TargetDistribution::Low),
616 TargetDistribution::parse("TD:L")
617 );
618 assert_eq!(
619 Some(TargetDistribution::Medium),
620 TargetDistribution::parse("TD:M")
621 );
622 assert_eq!(
623 Some(TargetDistribution::High),
624 TargetDistribution::parse("TD:H")
625 );
626
627 assert_eq!(
629 Some(ConfidentialityRequirement::Low),
630 ConfidentialityRequirement::parse("CR:L")
631 );
632 assert_eq!(
633 Some(ConfidentialityRequirement::Medium),
634 ConfidentialityRequirement::parse("CR:M")
635 );
636 assert_eq!(
637 Some(ConfidentialityRequirement::High),
638 ConfidentialityRequirement::parse("CR:H")
639 );
640
641 assert_eq!(
643 Some(IntegrityRequirement::Low),
644 IntegrityRequirement::parse("IR:L")
645 );
646 assert_eq!(
647 Some(IntegrityRequirement::Medium),
648 IntegrityRequirement::parse("IR:M")
649 );
650 assert_eq!(
651 Some(IntegrityRequirement::High),
652 IntegrityRequirement::parse("IR:H")
653 );
654
655 assert_eq!(
657 Some(AvailabilityRequirement::Low),
658 AvailabilityRequirement::parse("AR:L")
659 );
660 assert_eq!(
661 Some(AvailabilityRequirement::Medium),
662 AvailabilityRequirement::parse("AR:M")
663 );
664 assert_eq!(
665 Some(AvailabilityRequirement::High),
666 AvailabilityRequirement::parse("AR:H")
667 );
668
669 assert_eq!(
672 Some(provide_environmental_vector1()),
673 EnvironmentalVector::parse_optional(&Box::new("CDP:H/TD:H/CR:M/IR:M/AR:H".split('/')))
674 );
675 assert_eq!(
677 Some(provide_environmental_vector1()),
678 EnvironmentalVector::parse_optional(&Box::new("TD:H/CR:M/CDP:H/IR:M/AR:H".split('/')))
679 );
680 let mut missing_fields = provide_environmental_vector1();
682 missing_fields.collateral_damage_potential = None;
683 missing_fields.target_distribution = None;
684 assert_eq!(
685 Some(missing_fields),
686 EnvironmentalVector::parse_optional(&Box::new("CR:M/IR:M/AR:H".split('/')))
687 );
688 assert_eq!(
690 None,
691 EnvironmentalVector::parse_optional(&mut Box::new("fsjfskhf".split('/')))
692 );
693 assert_eq!(
694 None,
695 EnvironmentalVector::parse_optional(&mut Box::new("fs//jf/skhf".split('/')))
696 );
697 }
698
699 #[test]
700 fn test_scoring() {
701 let environmental_vector = provide_environmental_vector1();
702
703 assert_eq!(
704 9.2,
705 environmental_vector.score(provide_base_vector1(), Some(provide_temporal_vector1()))
706 );
707 }
708
709 fn provide_environmental_vector1() -> EnvironmentalVector {
712 EnvironmentalVector {
713 collateral_damage_potential: Some(CollateralDamagePotential::High),
714 target_distribution: Some(TargetDistribution::High),
715 confidentiality_requirement: Some(ConfidentialityRequirement::Medium),
716 integrity_requirement: Some(IntegrityRequirement::Medium),
717 availability_requirement: Some(AvailabilityRequirement::High),
718 }
719 }
720
721 fn provide_temporal_vector1() -> TemporalVector {
723 TemporalVector {
724 exploitability: Some(Exploitability::Functional),
725 remediation_level: Some(RemediationLevel::OfficialFix),
726 report_confidence: Some(ReportConfidence::Confirmed),
727 }
728 }
729
730 fn provide_base_vector1() -> BaseVector {
732 BaseVector {
733 access_vector: AccessVector::Network,
734 access_complexity: AccessComplexity::Low,
735 authentication: Authentication::None,
736 confidentiality_impact: ConfidentialityImpact::None,
737 integrity_impact: IntegrityImpact::None,
738 availability_impact: AvailabilityImpact::Complete,
739 }
740 }
741}