use async_trait::async_trait; use modality_mutator_protocol::actuator::MutatorActuator; use modality_mutator_protocol::attrs::{AttrKey, AttrType, AttrVal}; use modality_mutator_protocol::descriptor::owned::{ MutatorLayer, MutatorOperation, OrganizationCustomMetadata, OwnedMutatorDescriptor, OwnedMutatorParamDescriptor, }; use modality_mutator_protocol::mutator::{ActuatorDescriptor, CombinedMutator}; use modality_plugin_utils::BearingConfigFilePath; use std::collections::{BTreeMap, HashMap}; use std::net::Ipv4Addr; use std::path::{Path, PathBuf}; use uuid::Uuid; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Default, clap::Parser)] pub struct CliOptions { /// Use configuration from file #[clap( long = "config", name = "config file", env = "MODALITY_REFLECTOR_PLUGIN_CONFIG_FILE" )] pub config_file: Option, } impl BearingConfigFilePath for CliOptions { fn config_file_path(&self) -> Option<&Path> { self.config_file.as_deref() } } fn main() { std::process::exit(modality_plugin_utils::server_main::( |config, _auth_token, _opts, shutdown_signal| async move { let mutators = std::iter::once(("lonesome".to_string(), mutator())).collect(); let port = config .mutation .as_ref() .and_then(|m| m.mutator_http_api_port) .unwrap_or(8080); modality_mutator_server::server::serve_mutators( mutators, None, (Ipv4Addr::UNSPECIFIED, port), async { shutdown_signal.await; }, ) .await; Ok(()) }, )); } fn mutator() -> Box { struct SingleValueSetActuator { original: i64, value: i64, } impl SingleValueSetActuator { fn new(v: i64) -> Self { SingleValueSetActuator { original: v, value: v, } } } #[async_trait] impl MutatorActuator for SingleValueSetActuator { async fn inject( &mut self, mutation_id: Uuid, mut params: BTreeMap, ) -> Result<(), Box> { let mutation_id_as_integer = i128::from_le_bytes(*mutation_id.as_bytes()); if let Some(AttrVal::Integer(i)) = params.remove(&AttrKey::from(MutatorOperation::SetToValue.name())) { if i < -50 { tracing::warn!("Clipping mutation to the minimum, -50"); self.value = -50; } else if i > 9000 { tracing::warn!("Clipping mutation to the maximum, 9000"); self.value = 9000; } else { tracing::info!("Applying mutation"); self.value = i; } tracing::info!( mutation.id = mutation_id_as_integer, mutation.success = true, name = "modality.mutation.injected" ); } else { tracing::error!( mutation.id = mutation_id_as_integer, mutation.success = false, name = "modality.mutation.injected", "Expected an integer-type parameter with the name set_to_value", ); } Ok(()) } async fn reset(&mut self) -> Result<(), Box> { tracing::info!("Reset to original value"); self.value = self.original; Ok(()) } } Box::new(CombinedMutator::new( SingleValueSetActuator::new(99), OwnedMutatorDescriptor { name: Some("single_value_mutator".to_owned()), description: Some("Manipulates the value of a single integer".to_owned()), layer: Some(MutatorLayer::Implementational), group: None, operation: Some(MutatorOperation::SetToValue), statefulness: None, organization_custom_metadata: OrganizationCustomMetadata::new( "test_org_name".to_string(), HashMap::from([("forty_two".to_string(), 42.into())]), ), params: vec![OwnedMutatorParamDescriptor { value_type: AttrType::Integer, name: MutatorOperation::SetToValue.name().to_string(), description: Some( "the critical parameter - what to set the targeted value in memory to" .to_owned(), ), value_min: Some(AttrVal::Integer(-50)), value_max: Some(9000.into()), default_value: None, least_effect_value: None, value_distribution_kind: None, value_distribution_scaling: None, value_distribution_option_set: None, organization_custom_metadata: None, }], }, )) }