use crate::beacon::{GeographicBeacon, HierarchyLevel, NodeProfile};
use crate::hierarchy::{DynamicHierarchyStrategy, ElectionConfig, HierarchyStrategy, NodeRole};
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct FlatMeshCoordinator {
node_id: String,
profile: NodeProfile,
strategy: Arc<DynamicHierarchyStrategy>,
current_role: Arc<RwLock<NodeRole>>,
peers: Arc<RwLock<Vec<GeographicBeacon>>>,
}
impl FlatMeshCoordinator {
pub fn new(
node_id: String,
profile: NodeProfile,
election_config: Option<ElectionConfig>,
) -> Self {
let strategy = Arc::new(DynamicHierarchyStrategy::new(
HierarchyLevel::Squad, election_config.unwrap_or_default(),
false, ));
Self {
node_id,
profile,
strategy,
current_role: Arc::new(RwLock::new(NodeRole::Standalone)),
peers: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn update_peers(&self, beacons: Vec<GeographicBeacon>) -> NodeRole {
*self.peers.write().await = beacons.clone();
let new_role = self.strategy.determine_role(&self.profile, &beacons);
*self.current_role.write().await = new_role;
new_role
}
pub async fn current_role(&self) -> NodeRole {
*self.current_role.read().await
}
pub fn node_id(&self) -> &str {
&self.node_id
}
pub fn hierarchy_level(&self) -> HierarchyLevel {
HierarchyLevel::Squad
}
pub async fn peer_count(&self) -> usize {
self.peers.read().await.len()
}
pub async fn is_leader(&self) -> bool {
*self.current_role.read().await == NodeRole::Leader
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::beacon::{GeoPosition, NodeMobility, NodeResources};
fn create_test_profile(mobility: NodeMobility, cpu_usage: u8) -> NodeProfile {
NodeProfile {
mobility,
resources: NodeResources {
cpu_cores: 4,
memory_mb: 2048,
bandwidth_mbps: 100,
cpu_usage_percent: cpu_usage,
memory_usage_percent: 50,
battery_percent: Some(80),
},
can_parent: true,
prefer_leaf: false,
parent_priority: 128,
}
}
fn create_test_beacon(
node_id: &str,
position: GeoPosition,
profile: NodeProfile,
) -> GeographicBeacon {
let mut beacon =
GeographicBeacon::new(node_id.to_string(), position, HierarchyLevel::Squad);
beacon.mobility = Some(profile.mobility);
beacon.resources = Some(profile.resources.clone());
beacon.can_parent = profile.can_parent;
beacon.parent_priority = profile.parent_priority;
beacon
}
#[tokio::test]
async fn test_flat_mesh_single_node() {
let profile = create_test_profile(NodeMobility::Static, 30);
let coordinator = FlatMeshCoordinator::new("node1".to_string(), profile, None);
let role = coordinator.update_peers(vec![]).await;
assert_eq!(role, NodeRole::Standalone);
assert_eq!(coordinator.hierarchy_level(), HierarchyLevel::Squad);
}
#[tokio::test]
async fn test_flat_mesh_leader_election() {
let node1_profile = create_test_profile(NodeMobility::Static, 20); let node2_profile = create_test_profile(NodeMobility::SemiMobile, 40); let node3_profile = create_test_profile(NodeMobility::Mobile, 70);
let node1 = FlatMeshCoordinator::new("node1".to_string(), node1_profile.clone(), None);
let node2_beacon =
create_test_beacon("node2", GeoPosition::new(37.7750, -122.4195), node2_profile);
let node3_beacon =
create_test_beacon("node3", GeoPosition::new(37.7760, -122.4185), node3_profile);
let role = node1.update_peers(vec![node2_beacon, node3_beacon]).await;
assert_eq!(role, NodeRole::Leader);
assert!(node1.is_leader().await);
assert_eq!(node1.peer_count().await, 2);
}
#[tokio::test]
async fn test_flat_mesh_member_role() {
let node2_profile = create_test_profile(NodeMobility::Mobile, 60);
let node2 = FlatMeshCoordinator::new("node2".to_string(), node2_profile.clone(), None);
let node1_profile = create_test_profile(NodeMobility::Static, 20);
let node1_beacon =
create_test_beacon("node1", GeoPosition::new(37.7749, -122.4194), node1_profile);
let role = node2.update_peers(vec![node1_beacon]).await;
assert_eq!(role, NodeRole::Member);
assert!(!node2.is_leader().await);
}
#[test]
fn test_flat_mesh_node_id() {
let profile = create_test_profile(NodeMobility::Static, 30);
let coordinator = FlatMeshCoordinator::new("my-node".to_string(), profile, None);
assert_eq!(coordinator.node_id(), "my-node");
}
#[test]
fn test_flat_mesh_hierarchy_level() {
let profile = create_test_profile(NodeMobility::Static, 30);
let coordinator = FlatMeshCoordinator::new("n".to_string(), profile, None);
assert_eq!(coordinator.hierarchy_level(), HierarchyLevel::Squad);
}
#[tokio::test]
async fn test_flat_mesh_initial_role() {
let profile = create_test_profile(NodeMobility::Static, 30);
let coordinator = FlatMeshCoordinator::new("n".to_string(), profile, None);
assert_eq!(coordinator.current_role().await, NodeRole::Standalone);
}
#[tokio::test]
async fn test_flat_mesh_peer_count_empty() {
let profile = create_test_profile(NodeMobility::Static, 30);
let coordinator = FlatMeshCoordinator::new("n".to_string(), profile, None);
assert_eq!(coordinator.peer_count().await, 0);
}
#[tokio::test]
async fn test_flat_mesh_role_transition() {
let profile = create_test_profile(NodeMobility::Static, 20);
let coordinator = FlatMeshCoordinator::new("node-a".to_string(), profile, None);
let worse_profile = create_test_profile(NodeMobility::Mobile, 70);
let worse_beacon =
create_test_beacon("node-b", GeoPosition::new(37.775, -122.419), worse_profile);
let role = coordinator.update_peers(vec![worse_beacon]).await;
assert_eq!(role, NodeRole::Leader);
let better_profile = create_test_profile(NodeMobility::Static, 10);
let better_beacon =
create_test_beacon("node-c", GeoPosition::new(37.776, -122.418), better_profile);
let role = coordinator.update_peers(vec![better_beacon]).await;
assert_eq!(role, NodeRole::Member);
}
#[tokio::test]
async fn test_flat_mesh_custom_election_config() {
use crate::hierarchy::ElectionWeights;
let profile = create_test_profile(NodeMobility::Static, 30);
let custom_config = ElectionConfig {
priority_weights: ElectionWeights {
mobility: 0.5,
resources: 0.3,
battery: 0.2,
},
hysteresis: 0.15,
};
let coordinator =
FlatMeshCoordinator::new("custom".to_string(), profile, Some(custom_config));
assert_eq!(coordinator.node_id(), "custom");
assert_eq!(coordinator.hierarchy_level(), HierarchyLevel::Squad);
}
}