use super::classification::DataType;
use super::{QoSClass, QoSPolicy};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum MissionContext {
Ingress,
Execution,
Egress,
Emergency,
#[default]
Standby,
}
impl MissionContext {
pub fn all() -> &'static [MissionContext] {
&[
MissionContext::Ingress,
MissionContext::Execution,
MissionContext::Egress,
MissionContext::Emergency,
MissionContext::Standby,
]
}
pub fn has_elevations(&self) -> bool {
!matches!(self, Self::Standby)
}
pub fn enables_bulk_sync(&self) -> bool {
matches!(self, Self::Standby)
}
pub fn profile(&self) -> ContextProfile {
match self {
Self::Ingress => ContextProfile::ingress(),
Self::Execution => ContextProfile::execution(),
Self::Egress => ContextProfile::egress(),
Self::Emergency => ContextProfile::emergency(),
Self::Standby => ContextProfile::standby(),
}
}
}
impl fmt::Display for MissionContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ingress => write!(f, "Ingress"),
Self::Execution => write!(f, "Execution"),
Self::Egress => write!(f, "Egress"),
Self::Emergency => write!(f, "Emergency"),
Self::Standby => write!(f, "Standby"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum QoSClassAdjustment {
Elevate(u8),
Demote(u8),
Override(QoSClass),
#[default]
NoChange,
}
impl QoSClassAdjustment {
pub fn apply(&self, base: QoSClass) -> QoSClass {
match self {
Self::Elevate(n) => {
let base_val = base.as_u8();
let new_val = base_val.saturating_sub(*n).max(1);
match new_val {
1 => QoSClass::Critical,
2 => QoSClass::High,
3 => QoSClass::Normal,
4 => QoSClass::Low,
_ => QoSClass::Bulk,
}
}
Self::Demote(n) => {
let base_val = base.as_u8();
let new_val = base_val.saturating_add(*n).min(5);
match new_val {
1 => QoSClass::Critical,
2 => QoSClass::High,
3 => QoSClass::Normal,
4 => QoSClass::Low,
_ => QoSClass::Bulk,
}
}
Self::Override(class) => *class,
Self::NoChange => base,
}
}
pub fn elevates(&self) -> bool {
matches!(
self,
Self::Elevate(_) | Self::Override(QoSClass::Critical | QoSClass::High)
)
}
pub fn demotes(&self) -> bool {
matches!(
self,
Self::Demote(_) | Self::Override(QoSClass::Low | QoSClass::Bulk)
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextProfile {
pub context: MissionContext,
pub adjustments: HashMap<DataType, QoSClassAdjustment>,
pub description: Option<String>,
}
impl ContextProfile {
pub fn new(context: MissionContext) -> Self {
Self {
context,
adjustments: HashMap::new(),
description: None,
}
}
pub fn with_description(context: MissionContext, description: impl Into<String>) -> Self {
Self {
context,
adjustments: HashMap::new(),
description: Some(description.into()),
}
}
pub fn ingress() -> Self {
let mut profile = Self::with_description(
MissionContext::Ingress,
"Moving to objective - prioritize enemy detection",
);
profile
.adjustments
.insert(DataType::HistoricalTrack, QoSClassAdjustment::Demote(1));
profile
.adjustments
.insert(DataType::PositionUpdate, QoSClassAdjustment::Demote(1));
profile
}
pub fn execution() -> Self {
let mut profile = Self::with_description(
MissionContext::Execution,
"On objective - prioritize intel products",
);
profile
.adjustments
.insert(DataType::TargetImage, QoSClassAdjustment::Elevate(1));
profile
.adjustments
.insert(DataType::AudioIntercept, QoSClassAdjustment::Elevate(1));
profile
.adjustments
.insert(DataType::CapabilityChange, QoSClassAdjustment::Elevate(1));
profile
.adjustments
.insert(DataType::FormationChange, QoSClassAdjustment::Elevate(1));
profile
}
pub fn egress() -> Self {
let mut profile = Self::with_description(
MissionContext::Egress,
"Returning from objective - prioritize health/status",
);
profile
.adjustments
.insert(DataType::HealthStatus, QoSClassAdjustment::Elevate(1));
profile
.adjustments
.insert(DataType::CapabilityChange, QoSClassAdjustment::Elevate(1));
profile
.adjustments
.insert(DataType::FormationUpdate, QoSClassAdjustment::Elevate(1));
profile
}
pub fn emergency() -> Self {
let mut profile = Self::with_description(
MissionContext::Emergency,
"Emergency - elevate all status data to critical",
);
profile
.adjustments
.insert(DataType::HealthStatus, QoSClassAdjustment::Elevate(2));
profile
.adjustments
.insert(DataType::CapabilityChange, QoSClassAdjustment::Elevate(2));
profile.adjustments.insert(
DataType::EmergencyAlert,
QoSClassAdjustment::Override(QoSClass::Critical),
);
profile
.adjustments
.insert(DataType::PositionUpdate, QoSClassAdjustment::Elevate(2));
profile.adjustments.insert(
DataType::TrainingData,
QoSClassAdjustment::Override(QoSClass::Bulk),
);
profile
}
pub fn standby() -> Self {
Self::with_description(
MissionContext::Standby,
"Standby - normal priorities, opportunistic sync",
)
}
pub fn get_adjustment(&self, data_type: &DataType) -> QoSClassAdjustment {
self.adjustments
.get(data_type)
.copied()
.unwrap_or(QoSClassAdjustment::NoChange)
}
pub fn set_adjustment(&mut self, data_type: DataType, adjustment: QoSClassAdjustment) {
self.adjustments.insert(data_type, adjustment);
}
pub fn apply_to_policy(&self, base: &QoSPolicy, data_type: &DataType) -> QoSPolicy {
let adjustment = self.get_adjustment(data_type);
let adjusted_class = adjustment.apply(base.base_class);
let mut adjusted = base.clone();
adjusted.base_class = adjusted_class;
if adjusted_class > base.base_class {
if let Some(latency) = adjusted.max_latency_ms {
adjusted.max_latency_ms = Some(latency / 2);
}
} else if adjusted_class < base.base_class {
if let Some(latency) = adjusted.max_latency_ms {
adjusted.max_latency_ms = Some(latency * 2);
}
}
adjusted
}
pub fn adjusted_types(&self) -> impl Iterator<Item = (&DataType, &QoSClassAdjustment)> {
self.adjustments
.iter()
.filter(|(_, adj)| !matches!(adj, QoSClassAdjustment::NoChange))
}
pub fn adjustment_count(&self) -> usize {
self.adjustments
.values()
.filter(|adj| !matches!(adj, QoSClassAdjustment::NoChange))
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mission_context_display() {
assert_eq!(MissionContext::Ingress.to_string(), "Ingress");
assert_eq!(MissionContext::Execution.to_string(), "Execution");
assert_eq!(MissionContext::Egress.to_string(), "Egress");
assert_eq!(MissionContext::Emergency.to_string(), "Emergency");
assert_eq!(MissionContext::Standby.to_string(), "Standby");
}
#[test]
fn test_mission_context_default() {
assert_eq!(MissionContext::default(), MissionContext::Standby);
}
#[test]
fn test_adjustment_elevate() {
let adj = QoSClassAdjustment::Elevate(1);
assert_eq!(adj.apply(QoSClass::Normal), QoSClass::High);
assert_eq!(adj.apply(QoSClass::High), QoSClass::Critical);
assert_eq!(adj.apply(QoSClass::Critical), QoSClass::Critical);
assert_eq!(adj.apply(QoSClass::Bulk), QoSClass::Low);
}
#[test]
fn test_adjustment_demote() {
let adj = QoSClassAdjustment::Demote(1);
assert_eq!(adj.apply(QoSClass::Normal), QoSClass::Low);
assert_eq!(adj.apply(QoSClass::Low), QoSClass::Bulk);
assert_eq!(adj.apply(QoSClass::Bulk), QoSClass::Bulk);
assert_eq!(adj.apply(QoSClass::Critical), QoSClass::High);
}
#[test]
fn test_adjustment_override() {
let adj = QoSClassAdjustment::Override(QoSClass::Critical);
assert_eq!(adj.apply(QoSClass::Normal), QoSClass::Critical);
assert_eq!(adj.apply(QoSClass::Bulk), QoSClass::Critical);
assert_eq!(adj.apply(QoSClass::Critical), QoSClass::Critical);
}
#[test]
fn test_adjustment_no_change() {
let adj = QoSClassAdjustment::NoChange;
assert_eq!(adj.apply(QoSClass::Normal), QoSClass::Normal);
assert_eq!(adj.apply(QoSClass::Bulk), QoSClass::Bulk);
}
#[test]
fn test_adjustment_elevates_demotes() {
assert!(QoSClassAdjustment::Elevate(1).elevates());
assert!(!QoSClassAdjustment::Elevate(1).demotes());
assert!(QoSClassAdjustment::Demote(1).demotes());
assert!(!QoSClassAdjustment::Demote(1).elevates());
assert!(!QoSClassAdjustment::NoChange.elevates());
assert!(!QoSClassAdjustment::NoChange.demotes());
}
#[test]
fn test_ingress_profile() {
let profile = ContextProfile::ingress();
assert_eq!(profile.context, MissionContext::Ingress);
let adj = profile.get_adjustment(&DataType::HistoricalTrack);
assert!(adj.demotes());
let adj = profile.get_adjustment(&DataType::ContactReport);
assert_eq!(adj, QoSClassAdjustment::NoChange);
}
#[test]
fn test_execution_profile() {
let profile = ContextProfile::execution();
assert_eq!(profile.context, MissionContext::Execution);
let adj = profile.get_adjustment(&DataType::TargetImage);
assert!(adj.elevates());
assert_eq!(adj.apply(QoSClass::High), QoSClass::Critical);
let adj = profile.get_adjustment(&DataType::AudioIntercept);
assert!(adj.elevates());
}
#[test]
fn test_egress_profile() {
let profile = ContextProfile::egress();
assert_eq!(profile.context, MissionContext::Egress);
let adj = profile.get_adjustment(&DataType::HealthStatus);
assert!(adj.elevates());
assert_eq!(adj.apply(QoSClass::Normal), QoSClass::High);
}
#[test]
fn test_emergency_profile() {
let profile = ContextProfile::emergency();
assert_eq!(profile.context, MissionContext::Emergency);
let adj = profile.get_adjustment(&DataType::HealthStatus);
assert!(adj.elevates());
assert_eq!(adj.apply(QoSClass::Normal), QoSClass::Critical);
let adj = profile.get_adjustment(&DataType::PositionUpdate);
assert!(adj.elevates());
}
#[test]
fn test_standby_profile() {
let profile = ContextProfile::standby();
assert_eq!(profile.context, MissionContext::Standby);
assert_eq!(profile.adjustment_count(), 0);
}
#[test]
fn test_apply_to_policy() {
let profile = ContextProfile::execution();
let base_policy = QoSPolicy::high();
let adjusted = profile.apply_to_policy(&base_policy, &DataType::TargetImage);
assert_eq!(adjusted.base_class, QoSClass::Critical);
assert!(adjusted.max_latency_ms < base_policy.max_latency_ms);
}
#[test]
fn test_mission_context_serialization() {
let context = MissionContext::Execution;
let json = serde_json::to_string(&context).unwrap();
assert_eq!(json, "\"Execution\"");
let deserialized: MissionContext = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, MissionContext::Execution);
}
#[test]
fn test_profile_adjustment_count() {
let profile = ContextProfile::emergency();
assert!(profile.adjustment_count() > 0);
let standby = ContextProfile::standby();
assert_eq!(standby.adjustment_count(), 0);
}
#[test]
fn test_enables_bulk_sync() {
assert!(MissionContext::Standby.enables_bulk_sync());
assert!(!MissionContext::Emergency.enables_bulk_sync());
assert!(!MissionContext::Execution.enables_bulk_sync());
}
#[test]
fn test_has_elevations() {
assert!(!MissionContext::Standby.has_elevations());
assert!(MissionContext::Emergency.has_elevations());
assert!(MissionContext::Execution.has_elevations());
}
}