use crate::{Event, Machine, Result};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum PropertyType {
Reachability,
Safety,
Liveness,
Fairness,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Property {
pub name: String,
pub property_type: PropertyType,
pub target_states: Vec<String>,
pub description: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VerificationResult {
pub property_name: String,
pub satisfied: bool,
pub counterexample: Option<Vec<Event>>,
pub message: Option<String>,
}
#[derive(Debug)]
pub struct ModelChecker<'a> {
machine: &'a Machine,
visited_states: HashSet<String>,
transition_graph: HashMap<String, Vec<(String, String)>>,
}
impl<'a> ModelChecker<'a> {
pub fn new(machine: &'a Machine) -> Self {
Self {
machine,
visited_states: HashSet::new(),
transition_graph: HashMap::new(),
}
}
fn build_graph(&mut self) -> Result<()> {
self.visited_states.clear();
self.transition_graph.clear();
let mut queue = VecDeque::new();
queue.push_back(self.machine.initial.clone());
while let Some(state) = queue.pop_front() {
if self.visited_states.contains(&state) {
continue;
}
self.visited_states.insert(state.clone());
let outgoing_transitions = self
.machine
.transitions
.iter()
.filter(|t| t.source == state && t.target.is_some())
.map(|t| (t.event.clone(), t.target.clone().unwrap()))
.collect::<Vec<_>>();
self.transition_graph
.insert(state.clone(), outgoing_transitions.clone());
for (_, target) in outgoing_transitions {
if !self.visited_states.contains(&target) {
queue.push_back(target);
}
}
}
Ok(())
}
pub fn verify_property(&mut self, property: &Property) -> VerificationResult {
if let Err(err) = self.build_graph() {
return VerificationResult {
property_name: property.name.clone(),
satisfied: false,
counterexample: None,
message: Some(format!("Error building state graph: {}", err)),
};
}
match property.property_type {
PropertyType::Reachability => self.verify_reachability(property),
PropertyType::Safety => self.verify_safety(property),
PropertyType::Liveness => self.verify_liveness(property),
PropertyType::Fairness => self.verify_fairness(property),
}
}
fn verify_reachability(&self, property: &Property) -> VerificationResult {
let mut result = VerificationResult {
property_name: property.name.clone(),
satisfied: false,
counterexample: None,
message: None,
};
for target in &property.target_states {
if !self.visited_states.contains(target) {
result.message = Some(format!("State '{}' is not reachable", target));
return result;
}
}
result.satisfied = true;
result.message = Some("All target states are reachable".to_string());
result
}
fn verify_safety(&self, property: &Property) -> VerificationResult {
let mut result = VerificationResult {
property_name: property.name.clone(),
satisfied: true,
counterexample: None,
message: None,
};
for target in &property.target_states {
if self.visited_states.contains(target) {
result.satisfied = false;
if let Some(path) = self.find_path_to_state(target) {
result.counterexample = Some(path);
}
result.message = Some(format!("Safety violation: State '{}' is reachable", target));
return result;
}
}
result.message = Some("None of the target states are reachable".to_string());
result
}
fn verify_liveness(&self, property: &Property) -> VerificationResult {
let mut result = VerificationResult {
property_name: property.name.clone(),
satisfied: false,
counterexample: None,
message: None,
};
for target in &property.target_states {
if !self.visited_states.contains(target) {
result.message = Some(format!(
"Liveness violation: State '{}' is not reachable",
target
));
return result;
}
}
result.satisfied = true;
result.message =
Some("Liveness property seems to be satisfied (simplified check)".to_string());
result
}
fn verify_fairness(&self, property: &Property) -> VerificationResult {
let mut result = VerificationResult {
property_name: property.name.clone(),
satisfied: false,
counterexample: None,
message: None,
};
for target in &property.target_states {
if !self.visited_states.contains(target) {
result.message = Some(format!("State '{}' is not reachable", target));
return result;
}
if !self.has_cycle_through_state(target) {
result.message = Some(format!(
"Fairness violation: No infinite path through state '{}'",
target
));
return result;
}
}
result.satisfied = true;
result.message =
Some("Fairness property seems to be satisfied (simplified check)".to_string());
result
}
fn has_cycle_through_state(&self, state: &str) -> bool {
if let Some(transitions) = self.transition_graph.get(state) {
for (_, target) in transitions {
let mut visited = HashSet::new();
if self.dfs_find_path(target, state, &mut visited) {
return true;
}
}
}
false
}
fn dfs_find_path(&self, current: &str, target: &str, visited: &mut HashSet<String>) -> bool {
if current == target {
return true;
}
if visited.contains(current) {
return false;
}
visited.insert(current.to_string());
if let Some(transitions) = self.transition_graph.get(current) {
for (_, next) in transitions {
if self.dfs_find_path(next, target, visited) {
return true;
}
}
}
false
}
fn find_path_to_state(&self, target: &str) -> Option<Vec<Event>> {
let mut queue = VecDeque::new();
let mut visited = HashSet::new();
let mut path_map: HashMap<String, (String, String)> = HashMap::new();
queue.push_back(self.machine.initial.clone());
visited.insert(self.machine.initial.clone());
while let Some(current) = queue.pop_front() {
if current == target {
let mut path = Vec::new();
let mut current_state = current.clone();
while current_state != self.machine.initial {
let (prev_state, event) = path_map.get(¤t_state).unwrap().clone();
path.push(Event::new(event));
current_state = prev_state;
}
path.reverse();
return Some(path);
}
if let Some(transitions) = self.transition_graph.get(¤t) {
for (event, next) in transitions {
if !visited.contains(next) {
visited.insert(next.clone());
path_map.insert(next.clone(), (current.clone(), event.clone()));
queue.push_back(next.clone());
}
}
}
}
None
}
pub fn get_reachable_states(&mut self) -> HashSet<String> {
if self.visited_states.is_empty() {
let _ = self.build_graph();
}
self.visited_states.clone()
}
pub fn detect_deadlocks(&mut self) -> Vec<String> {
if self.visited_states.is_empty() {
let _ = self.build_graph();
}
let mut deadlocks = Vec::new();
for state in &self.visited_states {
if let Some(transitions) = self.transition_graph.get(state) {
if transitions.is_empty() {
deadlocks.push(state.clone());
}
}
}
deadlocks
}
pub fn detect_unreachable_states(&mut self) -> Vec<String> {
if self.visited_states.is_empty() {
let _ = self.build_graph();
}
let all_states: HashSet<_> = self.machine.states.keys().cloned().collect();
let unreachable: Vec<_> = all_states
.difference(&self.visited_states)
.cloned()
.collect();
unreachable
}
}