1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use thiserror::Error;
6
7use crate::message::{Message, MessageType, ProtocolVersion};
8use crate::versioning::{VersionError, VersionManager};
9
10#[derive(Debug, Error)]
12pub enum CompatibilityError {
13 #[error("Version not supported: {version:?}")]
15 VersionNotSupported { version: ProtocolVersion },
16
17 #[error("Feature '{feature}' not available in version {version:?}")]
19 FeatureNotAvailable {
20 feature: String,
21 version: ProtocolVersion,
22 },
23
24 #[error("Message transformation failed: {reason}")]
26 TransformationFailed { reason: String },
27
28 #[error("Incompatible message format for version {version:?}")]
30 IncompatibleFormat { version: ProtocolVersion },
31
32 #[error("Version error: {0}")]
34 Version(#[from] VersionError),
35}
36
37pub struct CompatibilityAdapter {
39 version_manager: VersionManager,
41 transformers: HashMap<(ProtocolVersion, ProtocolVersion), Box<dyn MessageTransformer>>,
43 feature_compatibility: HashMap<ProtocolVersion, Vec<String>>,
45}
46
47pub trait MessageTransformer: Send + Sync {
49 fn transform(&self, message: &Message) -> Result<Message, CompatibilityError>;
51
52 fn can_transform(&self, message: &Message) -> bool;
54
55 fn description(&self) -> &str;
57}
58
59pub struct DirectTransformer {
61 from_version: ProtocolVersion,
62 to_version: ProtocolVersion,
63}
64
65pub struct DowngradeTransformer {
67 from_version: ProtocolVersion,
68 to_version: ProtocolVersion,
69 removed_features: Vec<String>,
70}
71
72pub struct UpgradeTransformer {
74 from_version: ProtocolVersion,
75 to_version: ProtocolVersion,
76 added_features: Vec<String>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct LegacyMessage {
82 pub msg_type: LegacyMessageType,
84 pub payload: Vec<u8>,
86 pub timestamp: u64,
88 pub signature: Vec<u8>,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub enum LegacyMessageType {
95 Handshake,
96 Data,
97 Control,
98 Sync,
99}
100
101impl CompatibilityAdapter {
102 pub fn new(version_manager: VersionManager) -> Self {
104 let mut adapter = Self {
105 version_manager,
106 transformers: HashMap::new(),
107 feature_compatibility: HashMap::new(),
108 };
109
110 adapter.setup_default_transformers();
111 adapter.setup_feature_compatibility();
112 adapter
113 }
114
115 fn setup_default_transformers(&mut self) {
117 let v1_0_0 = ProtocolVersion {
118 major: 1,
119 minor: 0,
120 patch: 0,
121 features: vec![],
122 };
123 let v1_1_0 = ProtocolVersion {
124 major: 1,
125 minor: 1,
126 patch: 0,
127 features: vec![],
128 };
129
130 self.transformers.insert(
132 (v1_0_0.clone(), v1_1_0.clone()),
133 Box::new(UpgradeTransformer {
134 from_version: v1_0_0.clone(),
135 to_version: v1_1_0.clone(),
136 added_features: vec!["dark-addressing".to_string()],
137 }),
138 );
139
140 self.transformers.insert(
142 (v1_1_0.clone(), v1_0_0.clone()),
143 Box::new(DowngradeTransformer {
144 from_version: v1_1_0,
145 to_version: v1_0_0,
146 removed_features: vec!["dark-addressing".to_string()],
147 }),
148 );
149 }
150
151 fn setup_feature_compatibility(&mut self) {
153 let v1_0_0 = ProtocolVersion {
154 major: 1,
155 minor: 0,
156 patch: 0,
157 features: vec![],
158 };
159 let v1_1_0 = ProtocolVersion {
160 major: 1,
161 minor: 1,
162 patch: 0,
163 features: vec![],
164 };
165
166 self.feature_compatibility.insert(
167 v1_0_0,
168 vec![
169 "basic-messaging".to_string(),
170 "quantum-resistant-crypto".to_string(),
171 "dag-consensus".to_string(),
172 "anonymous-routing".to_string(),
173 ],
174 );
175
176 self.feature_compatibility.insert(
177 v1_1_0,
178 vec![
179 "basic-messaging".to_string(),
180 "quantum-resistant-crypto".to_string(),
181 "dag-consensus".to_string(),
182 "anonymous-routing".to_string(),
183 "dark-addressing".to_string(),
184 "enhanced-privacy".to_string(),
185 ],
186 );
187 }
188
189 pub fn transform_message(
191 &self,
192 message: &Message,
193 target_version: &ProtocolVersion,
194 ) -> Result<Message, CompatibilityError> {
195 if &message.version == target_version {
196 return Ok(message.clone());
197 }
198
199 if !self
201 .version_manager
202 .registry()
203 .are_compatible(&message.version, target_version)
204 {
205 return Err(CompatibilityError::VersionNotSupported {
206 version: target_version.clone(),
207 });
208 }
209
210 if let Some(transformer) = self
212 .transformers
213 .get(&(message.version.clone(), target_version.clone()))
214 {
215 return transformer.transform(message);
216 }
217
218 self.version_manager
220 .migrate_message(message, &message.version, target_version)
221 .map_err(CompatibilityError::Version)
222 }
223
224 pub fn is_feature_available(&self, version: &ProtocolVersion, feature: &str) -> bool {
226 self.feature_compatibility
227 .get(version)
228 .map(|features| features.contains(&feature.to_string()))
229 .unwrap_or(false)
230 }
231
232 pub fn get_supported_features(&self, version: &ProtocolVersion) -> Vec<String> {
234 self.feature_compatibility
235 .get(version)
236 .cloned()
237 .unwrap_or_default()
238 }
239
240 pub fn to_legacy_format(&self, message: &Message) -> Result<LegacyMessage, CompatibilityError> {
242 let legacy_type = match &message.msg_type {
243 MessageType::Handshake(_) => LegacyMessageType::Handshake,
244 MessageType::Control(_) => LegacyMessageType::Control,
245 MessageType::Sync(_) => LegacyMessageType::Sync,
246 _ => LegacyMessageType::Data,
247 };
248
249 Ok(LegacyMessage {
250 msg_type: legacy_type,
251 payload: message.payload.clone(),
252 timestamp: message.timestamp,
253 signature: message.signature.clone().unwrap_or_default(),
254 })
255 }
256
257 pub fn from_legacy_format(
259 &self,
260 legacy: &LegacyMessage,
261 ) -> Result<Message, CompatibilityError> {
262 use crate::message::{ControlMessageType, HandshakeType, SyncMessageType};
263
264 let msg_type = match legacy.msg_type {
265 LegacyMessageType::Handshake => MessageType::Handshake(HandshakeType::Init),
266 LegacyMessageType::Control => MessageType::Control(ControlMessageType::Ping),
267 LegacyMessageType::Sync => MessageType::Sync(SyncMessageType::StateRequest),
268 LegacyMessageType::Data => {
269 MessageType::Routing(crate::message::RoutingMessageType::Direct)
270 }
271 };
272
273 let mut message = Message::new(msg_type, legacy.payload.clone());
274 message.timestamp = legacy.timestamp;
275 if !legacy.signature.is_empty() {
276 message.signature = Some(legacy.signature.clone());
277 }
278
279 message.version = ProtocolVersion {
281 major: 1,
282 minor: 0,
283 patch: 0,
284 features: vec![],
285 };
286
287 Ok(message)
288 }
289
290 pub fn add_transformer(
292 &mut self,
293 from_version: ProtocolVersion,
294 to_version: ProtocolVersion,
295 transformer: Box<dyn MessageTransformer>,
296 ) {
297 self.transformers
298 .insert((from_version, to_version), transformer);
299 }
300
301 pub fn check_compatibility(
303 &self,
304 version1: &ProtocolVersion,
305 version2: &ProtocolVersion,
306 ) -> Result<Vec<String>, CompatibilityError> {
307 let mut compatibility_notes = Vec::new();
308
309 if version1 == version2 {
310 compatibility_notes.push("Versions are identical".to_string());
311 return Ok(compatibility_notes);
312 }
313
314 if !self
315 .version_manager
316 .registry()
317 .are_compatible(version1, version2)
318 {
319 return Err(CompatibilityError::VersionNotSupported {
320 version: version2.clone(),
321 });
322 }
323
324 let features1 = self.get_supported_features(version1);
326 let features2 = self.get_supported_features(version2);
327
328 let added_features: Vec<_> = features2
329 .iter()
330 .filter(|f| !features1.contains(f))
331 .cloned()
332 .collect();
333
334 let removed_features: Vec<_> = features1
335 .iter()
336 .filter(|f| !features2.contains(f))
337 .cloned()
338 .collect();
339
340 if !added_features.is_empty() {
341 compatibility_notes.push(format!("Added features: {}", added_features.join(", ")));
342 }
343
344 if !removed_features.is_empty() {
345 compatibility_notes.push(format!("Removed features: {}", removed_features.join(", ")));
346 }
347
348 if added_features.is_empty() && removed_features.is_empty() {
349 compatibility_notes.push("No feature differences".to_string());
350 }
351
352 Ok(compatibility_notes)
353 }
354}
355
356impl MessageTransformer for DirectTransformer {
357 fn transform(&self, message: &Message) -> Result<Message, CompatibilityError> {
358 let mut transformed = message.clone();
359 transformed.version = self.to_version.clone();
360 Ok(transformed)
361 }
362
363 fn can_transform(&self, message: &Message) -> bool {
364 message.version == self.from_version
365 }
366
367 fn description(&self) -> &str {
368 "Direct transformation with version update"
369 }
370}
371
372impl MessageTransformer for DowngradeTransformer {
373 fn transform(&self, message: &Message) -> Result<Message, CompatibilityError> {
374 let mut transformed = message.clone();
375 transformed.version = self.to_version.clone();
376
377 for feature in &self.removed_features {
379 if feature.as_str() == "dark-addressing" {
380 transformed.headers.remove("dark-address");
381 transformed.headers.remove("shadow-route");
382 }
383 }
384
385 if let MessageType::Anonymous(_) = &message.msg_type {
387 transformed.msg_type = MessageType::Routing(crate::message::RoutingMessageType::Direct);
389 }
390
391 Ok(transformed)
392 }
393
394 fn can_transform(&self, message: &Message) -> bool {
395 message.version == self.from_version
396 }
397
398 fn description(&self) -> &str {
399 "Downgrade transformation removing unsupported features"
400 }
401}
402
403impl MessageTransformer for UpgradeTransformer {
404 fn transform(&self, message: &Message) -> Result<Message, CompatibilityError> {
405 let mut transformed = message.clone();
406 transformed.version = self.to_version.clone();
407
408 for feature in &self.added_features {
410 if feature.as_str() == "dark-addressing" {
411 if !transformed.headers.contains_key("addressing-mode") {
413 transformed
414 .headers
415 .insert("addressing-mode".to_string(), "standard".to_string());
416 }
417 }
418 }
419
420 Ok(transformed)
421 }
422
423 fn can_transform(&self, message: &Message) -> bool {
424 message.version == self.from_version
425 }
426
427 fn description(&self) -> &str {
428 "Upgrade transformation adding default values for new features"
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use crate::message::MessageFactory;
436 use crate::versioning::VersionManager;
437
438 #[test]
439 fn test_version_compatibility() {
440 let version_manager = VersionManager::new(ProtocolVersion {
441 major: 1,
442 minor: 1,
443 patch: 0,
444 features: vec![],
445 });
446 let adapter = CompatibilityAdapter::new(version_manager);
447
448 let v1_0_0 = ProtocolVersion {
449 major: 1,
450 minor: 0,
451 patch: 0,
452 features: vec![],
453 };
454 let v1_1_0 = ProtocolVersion {
455 major: 1,
456 minor: 1,
457 patch: 0,
458 features: vec![],
459 };
460
461 let notes = adapter.check_compatibility(&v1_0_0, &v1_1_0).unwrap();
462 assert!(!notes.is_empty());
463 }
464
465 #[test]
466 fn test_message_transformation() {
467 let version_manager = VersionManager::new(ProtocolVersion {
468 major: 1,
469 minor: 1,
470 patch: 0,
471 features: vec![],
472 });
473 let adapter = CompatibilityAdapter::new(version_manager);
474
475 let mut message = MessageFactory::create_ping().unwrap();
476 message.version = ProtocolVersion {
477 major: 1,
478 minor: 1,
479 patch: 0,
480 features: vec![],
481 };
482
483 let target_version = ProtocolVersion {
484 major: 1,
485 minor: 0,
486 patch: 0,
487 features: vec![],
488 };
489 let transformed = adapter
490 .transform_message(&message, &target_version)
491 .unwrap();
492
493 assert_eq!(transformed.version, target_version);
494 }
495
496 #[test]
497 fn test_legacy_conversion() {
498 let version_manager = VersionManager::new(ProtocolVersion {
499 major: 1,
500 minor: 0,
501 patch: 0,
502 features: vec![],
503 });
504 let adapter = CompatibilityAdapter::new(version_manager);
505
506 let message = MessageFactory::create_ping().unwrap();
507 let legacy = adapter.to_legacy_format(&message).unwrap();
508 let restored = adapter.from_legacy_format(&legacy).unwrap();
509
510 assert_eq!(message.payload, restored.payload);
511 assert_eq!(message.timestamp, restored.timestamp);
512 }
513}