use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use thiserror::Error;
use crate::message::{Message, MessageType, ProtocolVersion};
pub type CustomMigrationFn = Box<dyn Fn(&Message) -> Result<Message, VersionError> + Send + Sync>;
#[derive(Debug, Error)]
pub enum VersionError {
#[error("Unsupported protocol version: {version:?}")]
UnsupportedVersion { version: ProtocolVersion },
#[error("Incompatible protocol versions: {local:?} vs {remote:?}")]
IncompatibleVersions {
local: ProtocolVersion,
remote: ProtocolVersion,
},
#[error("Feature '{feature}' not available in version {version:?}")]
FeatureNotAvailable {
feature: String,
version: ProtocolVersion,
},
#[error("Migration from {from:?} to {to:?} failed: {reason}")]
MigrationFailed {
from: ProtocolVersion,
to: ProtocolVersion,
reason: String,
},
#[error("Serialization error: {reason}")]
SerializationError { reason: String },
}
#[derive(Debug, Clone)]
pub struct VersionRegistry {
supported_versions: Vec<VersionInfo>,
feature_matrix: HashMap<ProtocolVersion, HashSet<String>>,
message_compatibility: HashMap<ProtocolVersion, HashSet<MessageType>>,
migration_paths: HashMap<(ProtocolVersion, ProtocolVersion), MigrationStrategy>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionInfo {
pub version: ProtocolVersion,
pub name: String,
pub release_date: String,
pub features: Vec<String>,
pub deprecated_features: Vec<String>,
pub security_requirements: SecurityRequirements,
pub compatibility: CompatibilityInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityRequirements {
pub min_key_sizes: HashMap<String, u32>,
pub required_algorithms: Vec<String>,
pub forbidden_algorithms: Vec<String>,
pub quantum_resistant: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompatibilityInfo {
pub compatible_with: Vec<ProtocolVersion>,
pub min_supported_version: ProtocolVersion,
pub breaking_changes: Vec<String>,
pub migration_notes: Vec<String>,
}
pub enum MigrationStrategy {
Direct,
Transform(fn(&Message) -> Result<Message, VersionError>),
Custom(CustomMigrationFn),
NotSupported,
}
impl std::fmt::Debug for MigrationStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MigrationStrategy::Direct => write!(f, "Direct"),
MigrationStrategy::Transform(_) => write!(f, "Transform(<function>)"),
MigrationStrategy::Custom(_) => write!(f, "Custom(<closure>)"),
MigrationStrategy::NotSupported => write!(f, "NotSupported"),
}
}
}
impl Clone for MigrationStrategy {
fn clone(&self) -> Self {
match self {
MigrationStrategy::Direct => MigrationStrategy::Direct,
MigrationStrategy::Transform(f) => MigrationStrategy::Transform(*f),
MigrationStrategy::Custom(_) => {
MigrationStrategy::NotSupported
}
MigrationStrategy::NotSupported => MigrationStrategy::NotSupported,
}
}
}
pub struct VersionManager {
registry: VersionRegistry,
current_version: ProtocolVersion,
preferences: VersionPreferences,
}
#[derive(Debug, Clone)]
pub struct VersionPreferences {
pub preferred_version: ProtocolVersion,
pub min_version: ProtocolVersion,
pub max_version: ProtocolVersion,
pub allow_downgrade: bool,
pub required_features: Vec<String>,
pub preferred_features: Vec<String>,
}
impl VersionRegistry {
pub fn new() -> Self {
let mut registry = Self {
supported_versions: Vec::new(),
feature_matrix: HashMap::new(),
message_compatibility: HashMap::new(),
migration_paths: HashMap::new(),
};
registry.register_default_versions();
registry
}
fn register_default_versions(&mut self) {
let v1_0_0 = VersionInfo {
version: ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
},
name: "Genesis".to_string(),
release_date: "2024-01-01".to_string(),
features: vec![
"basic-messaging".to_string(),
"quantum-resistant-crypto".to_string(),
"dag-consensus".to_string(),
"anonymous-routing".to_string(),
],
deprecated_features: vec![],
security_requirements: SecurityRequirements {
min_key_sizes: [("ml-dsa".to_string(), 2048), ("ml-kem".to_string(), 768)]
.into_iter()
.collect(),
required_algorithms: vec![
"ML-DSA".to_string(),
"ML-KEM-768".to_string(),
"BLAKE3".to_string(),
],
forbidden_algorithms: vec![
"RSA".to_string(),
"ECDSA".to_string(),
"DH".to_string(),
],
quantum_resistant: true,
},
compatibility: CompatibilityInfo {
compatible_with: vec![],
min_supported_version: ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
},
breaking_changes: vec![],
migration_notes: vec!["Initial version".to_string()],
},
};
let v1_1_0 = VersionInfo {
version: ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
},
name: "Shadow".to_string(),
release_date: "2024-06-01".to_string(),
features: vec![
"basic-messaging".to_string(),
"quantum-resistant-crypto".to_string(),
"dag-consensus".to_string(),
"anonymous-routing".to_string(),
"dark-addressing".to_string(),
"enhanced-privacy".to_string(),
],
deprecated_features: vec![],
security_requirements: SecurityRequirements {
min_key_sizes: [("ml-dsa".to_string(), 2048), ("ml-kem".to_string(), 768)]
.into_iter()
.collect(),
required_algorithms: vec![
"ML-DSA".to_string(),
"ML-KEM-768".to_string(),
"BLAKE3".to_string(),
"HQC".to_string(),
],
forbidden_algorithms: vec![
"RSA".to_string(),
"ECDSA".to_string(),
"DH".to_string(),
],
quantum_resistant: true,
},
compatibility: CompatibilityInfo {
compatible_with: vec![v1_0_0.version.clone()],
min_supported_version: v1_0_0.version.clone(),
breaking_changes: vec![],
migration_notes: vec![
"Backward compatible with 1.0.0".to_string(),
"New dark addressing features are optional".to_string(),
],
},
};
self.register_version(v1_0_0);
self.register_version(v1_1_0);
self.setup_migration_paths();
}
pub fn register_version(&mut self, version_info: VersionInfo) {
let version = version_info.version.clone();
self.supported_versions.push(version_info.clone());
let features: HashSet<String> = version_info.features.into_iter().collect();
self.feature_matrix.insert(version.clone(), features);
let message_types: HashSet<MessageType> = [
MessageType::Handshake(crate::message::HandshakeType::Init),
MessageType::Handshake(crate::message::HandshakeType::Response),
MessageType::Handshake(crate::message::HandshakeType::Complete),
MessageType::Handshake(crate::message::HandshakeType::VersionNegotiation),
MessageType::Control(crate::message::ControlMessageType::Ping),
MessageType::Control(crate::message::ControlMessageType::Pong),
MessageType::Consensus(crate::message::ConsensusMessageType::VertexProposal),
MessageType::Consensus(crate::message::ConsensusMessageType::Vote),
]
.into_iter()
.collect();
self.message_compatibility.insert(version, message_types);
}
fn setup_migration_paths(&mut self) {
let v1_0_0 = ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
};
let v1_1_0 = ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
};
self.migration_paths
.insert((v1_0_0.clone(), v1_1_0.clone()), MigrationStrategy::Direct);
self.migration_paths.insert(
(v1_1_0, v1_0_0),
MigrationStrategy::Transform(downgrade_1_1_to_1_0),
);
}
pub fn is_supported(&self, version: &ProtocolVersion) -> bool {
self.supported_versions
.iter()
.any(|v| &v.version == version)
}
pub fn get_version_info(&self, version: &ProtocolVersion) -> Option<&VersionInfo> {
self.supported_versions
.iter()
.find(|v| &v.version == version)
}
pub fn get_supported_versions(&self) -> &[VersionInfo] {
&self.supported_versions
}
pub fn is_feature_supported(&self, version: &ProtocolVersion, feature: &str) -> bool {
self.feature_matrix
.get(version)
.map(|features| features.contains(feature))
.unwrap_or(false)
}
pub fn are_compatible(&self, v1: &ProtocolVersion, v2: &ProtocolVersion) -> bool {
if v1 == v2 {
return true;
}
if let Some(v1_info) = self.get_version_info(v1) {
if v1_info.compatibility.compatible_with.contains(v2) {
return true;
}
}
if let Some(v2_info) = self.get_version_info(v2) {
if v2_info.compatibility.compatible_with.contains(v1) {
return true;
}
}
v1.is_compatible(v2)
}
pub fn get_migration_strategy(
&self,
from: &ProtocolVersion,
to: &ProtocolVersion,
) -> Option<&MigrationStrategy> {
self.migration_paths.get(&(from.clone(), to.clone()))
}
pub fn find_best_compatible_version(
&self,
available_versions: &[ProtocolVersion],
preferences: &VersionPreferences,
) -> Option<ProtocolVersion> {
let mut compatible: Vec<&ProtocolVersion> = available_versions
.iter()
.filter(|v| {
v.major >= preferences.min_version.major &&
v.major <= preferences.max_version.major &&
self.is_supported(v) &&
preferences.required_features.iter().all(|feature| {
self.is_feature_supported(v, feature)
})
})
.collect();
if compatible.is_empty() {
return None;
}
compatible.sort_by(|a, b| {
if **a == preferences.preferred_version {
std::cmp::Ordering::Less
} else if **b == preferences.preferred_version {
std::cmp::Ordering::Greater
} else {
b.major
.cmp(&a.major)
.then(b.minor.cmp(&a.minor))
.then(b.patch.cmp(&a.patch))
}
});
Some(compatible[0].clone())
}
}
impl VersionManager {
pub fn new(current_version: ProtocolVersion) -> Self {
let preferences = VersionPreferences {
preferred_version: current_version.clone(),
min_version: ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
},
max_version: ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
},
allow_downgrade: true,
required_features: vec![
"quantum-resistant-crypto".to_string(),
"dag-consensus".to_string(),
],
preferred_features: vec![
"anonymous-routing".to_string(),
"dark-addressing".to_string(),
],
};
Self {
registry: VersionRegistry::new(),
current_version,
preferences,
}
}
pub fn negotiate_version(
&self,
peer_versions: &[ProtocolVersion],
peer_preferred: &ProtocolVersion,
) -> Result<ProtocolVersion, VersionError> {
let mut available_versions = peer_versions.to_vec();
if !available_versions.contains(peer_preferred) {
available_versions.push(peer_preferred.clone());
}
if let Some(best_version) = self
.registry
.find_best_compatible_version(&available_versions, &self.preferences)
{
Ok(best_version)
} else {
Err(VersionError::IncompatibleVersions {
local: self.current_version.clone(),
remote: peer_preferred.clone(),
})
}
}
pub fn migrate_message(
&self,
message: &Message,
from_version: &ProtocolVersion,
to_version: &ProtocolVersion,
) -> Result<Message, VersionError> {
if from_version == to_version {
return Ok(message.clone());
}
if let Some(strategy) = self
.registry
.get_migration_strategy(from_version, to_version)
{
match strategy {
MigrationStrategy::Direct => Ok(message.clone()),
MigrationStrategy::Transform(transform_fn) => transform_fn(message),
MigrationStrategy::Custom(custom_fn) => custom_fn(message),
MigrationStrategy::NotSupported => Err(VersionError::MigrationFailed {
from: from_version.clone(),
to: to_version.clone(),
reason: "Migration not supported".to_string(),
}),
}
} else {
Err(VersionError::MigrationFailed {
from: from_version.clone(),
to: to_version.clone(),
reason: "No migration path found".to_string(),
})
}
}
pub fn is_feature_available(&self, feature: &str) -> bool {
self.registry
.is_feature_supported(&self.current_version, feature)
}
pub fn current_version(&self) -> &ProtocolVersion {
&self.current_version
}
pub fn registry(&self) -> &VersionRegistry {
&self.registry
}
pub fn set_preferences(&mut self, preferences: VersionPreferences) {
self.preferences = preferences;
}
pub fn preferences(&self) -> &VersionPreferences {
&self.preferences
}
}
impl Default for VersionRegistry {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ProtocolVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
if !self.features.is_empty() {
write!(f, "+{}", self.features.join(","))?;
}
Ok(())
}
}
fn downgrade_1_1_to_1_0(message: &Message) -> Result<Message, VersionError> {
let mut migrated_message = message.clone();
migrated_message.version = ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
};
migrated_message.headers.remove("dark-address");
migrated_message.headers.remove("shadow-route");
match &message.msg_type {
MessageType::Anonymous(_) => {
migrated_message.msg_type =
MessageType::Routing(crate::message::RoutingMessageType::Direct);
}
_ => {
}
}
Ok(migrated_message)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_compatibility() {
let registry = VersionRegistry::new();
let v1_0_0 = ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
};
let v1_1_0 = ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
};
assert!(registry.are_compatible(&v1_0_0, &v1_1_0));
assert!(registry.are_compatible(&v1_1_0, &v1_0_0));
}
#[test]
fn test_feature_support() {
let registry = VersionRegistry::new();
let v1_0_0 = ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
};
let v1_1_0 = ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
};
assert!(registry.is_feature_supported(&v1_0_0, "basic-messaging"));
assert!(registry.is_feature_supported(&v1_1_0, "dark-addressing"));
assert!(!registry.is_feature_supported(&v1_0_0, "dark-addressing"));
}
#[test]
fn test_version_negotiation() {
let manager = VersionManager::new(ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
});
let peer_versions = vec![
ProtocolVersion {
major: 1,
minor: 0,
patch: 0,
features: vec![],
},
ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
},
];
let peer_preferred = ProtocolVersion {
major: 1,
minor: 1,
patch: 0,
features: vec![],
};
let negotiated = manager
.negotiate_version(&peer_versions, &peer_preferred)
.unwrap();
assert_eq!(negotiated, peer_preferred);
}
}