1use serde::{Deserialize, Serialize};
99use smallvec::SmallVec;
100use std::borrow::Cow;
101use std::collections::HashMap;
102use std::io;
103use std::net::SocketAddr;
104use std::time::Duration;
105use thiserror::Error;
106
107#[derive(Debug, Error)]
113pub enum P2PError {
114 #[error("Network error: {0}")]
116 Network(#[from] NetworkError),
117
118 #[error("DHT error: {0}")]
120 Dht(#[from] DhtError),
121
122 #[error("Identity error: {0}")]
124 Identity(#[from] IdentityError),
125
126 #[error("Cryptography error: {0}")]
128 Crypto(#[from] CryptoError),
129
130 #[error("Storage error: {0}")]
132 Storage(#[from] StorageError),
133
134 #[error("Transport error: {0}")]
136 Transport(#[from] TransportError),
137
138 #[error("Configuration error: {0}")]
140 Config(#[from] ConfigError),
141
142 #[error("Security error: {0}")]
144 Security(#[from] SecurityError),
145
146 #[error("Bootstrap error: {0}")]
148 Bootstrap(#[from] BootstrapError),
149
150 #[error("IO error: {0}")]
152 Io(#[from] io::Error),
153
154 #[error("Serialization error: {0}")]
156 Serialization(Cow<'static, str>),
157
158 #[error("Validation error: {0}")]
160 Validation(Cow<'static, str>),
161
162 #[error("Operation timed out after {0:?}")]
164 Timeout(Duration),
165
166 #[error("Resource exhausted: {0}")]
168 ResourceExhausted(Cow<'static, str>),
169
170 #[error("Internal error: {0}")]
172 Internal(Cow<'static, str>),
173
174 #[error("Encoding error: {0}")]
176 Encoding(Cow<'static, str>),
177
178 #[error("Record too large: {0} bytes (max 512)")]
180 RecordTooLarge(usize),
181
182 #[error("Time error")]
184 TimeError,
185
186 #[error("Invalid input: {0}")]
188 InvalidInput(String),
189
190 #[error("WebRTC error: {0}")]
192 WebRtcError(String),
193}
194
195#[derive(Debug, Error)]
197pub enum NetworkError {
198 #[error("Connection failed to {addr}: {reason}")]
199 ConnectionFailed {
200 addr: SocketAddr,
201 reason: Cow<'static, str>,
202 },
203
204 #[error("Connection closed unexpectedly")]
205 ConnectionClosed,
206
207 #[error("Invalid network address: {0}")]
208 InvalidAddress(Cow<'static, str>),
209
210 #[error("Peer not found: {0}")]
211 PeerNotFound(Cow<'static, str>),
212
213 #[error("Peer disconnected - peer: {peer}, reason: {reason}")]
214 PeerDisconnected { peer: String, reason: String },
215
216 #[error("Network timeout")]
217 Timeout,
218
219 #[error("Too many connections")]
220 TooManyConnections,
221
222 #[error("Protocol error: {0}")]
223 ProtocolError(Cow<'static, str>),
224
225 #[error("Bind error: {0}")]
226 BindError(Cow<'static, str>),
227}
228
229#[derive(Debug, Error)]
231pub enum DhtError {
232 #[error("Key not found: {0}")]
233 KeyNotFound(Cow<'static, str>),
234
235 #[error("Store operation failed: {0}")]
236 StoreFailed(Cow<'static, str>),
237
238 #[error("Invalid key format: {0}")]
239 InvalidKey(Cow<'static, str>),
240
241 #[error("Routing table full")]
242 RoutingTableFull,
243
244 #[error("No suitable peers found")]
245 NoPeersFound,
246
247 #[error("Replication failed: {0}")]
248 ReplicationFailed(Cow<'static, str>),
249
250 #[error("Query timeout")]
251 QueryTimeout,
252
253 #[error("Routing error: {0}")]
254 RoutingError(Cow<'static, str>),
255
256 #[error("Storage failed: {0}")]
257 StorageFailed(Cow<'static, str>),
258
259 #[error("Insufficient replicas: {0}")]
260 InsufficientReplicas(Cow<'static, str>),
261}
262
263#[derive(Debug, Error)]
265pub enum IdentityError {
266 #[error("Invalid three-word address: {0}")]
267 InvalidThreeWordAddress(Cow<'static, str>),
268
269 #[error("Invalid four-word address: {0}")]
270 InvalidFourWordAddress(Cow<'static, str>),
271
272 #[error("Identity not found: {0}")]
273 IdentityNotFound(Cow<'static, str>),
274
275 #[error("Identity already exists: {0}")]
276 IdentityExists(Cow<'static, str>),
277
278 #[error("Invalid signature")]
279 InvalidSignature,
280
281 #[error("Key derivation failed: {0}")]
282 KeyDerivationFailed(Cow<'static, str>),
283
284 #[error("Permission denied")]
285 PermissionDenied,
286
287 #[error("Invalid peer ID: {0}")]
288 InvalidPeerId(Cow<'static, str>),
289
290 #[error("Invalid format: {0}")]
291 InvalidFormat(Cow<'static, str>),
292
293 #[error("System time error: {0}")]
294 SystemTime(Cow<'static, str>),
295
296 #[error("Not found: {0}")]
297 NotFound(Cow<'static, str>),
298
299 #[error("Verification failed: {0}")]
300 VerificationFailed(Cow<'static, str>),
301
302 #[error("Insufficient entropy")]
303 InsufficientEntropy,
304
305 #[error("Access denied: {0}")]
306 AccessDenied(Cow<'static, str>),
307}
308
309#[derive(Debug, Error)]
311pub enum CryptoError {
312 #[error("Encryption failed: {0}")]
313 EncryptionFailed(Cow<'static, str>),
314
315 #[error("Decryption failed: {0}")]
316 DecryptionFailed(Cow<'static, str>),
317
318 #[error("Invalid key length: expected {expected}, got {actual}")]
319 InvalidKeyLength { expected: usize, actual: usize },
320
321 #[error("Signature verification failed")]
322 SignatureVerificationFailed,
323
324 #[error("Key generation failed: {0}")]
325 KeyGenerationFailed(Cow<'static, str>),
326
327 #[error("Invalid public key")]
328 InvalidPublicKey,
329
330 #[error("Invalid private key")]
331 InvalidPrivateKey,
332
333 #[error("HKDF expansion failed: {0}")]
334 HkdfError(Cow<'static, str>),
335}
336
337#[derive(Debug, Error)]
339pub enum StorageError {
340 #[error("Database error: {0}")]
341 Database(Cow<'static, str>),
342
343 #[error("Disk full")]
344 DiskFull,
345
346 #[error("Corrupt data: {0}")]
347 CorruptData(Cow<'static, str>),
348
349 #[error("Storage path not found: {0}")]
350 PathNotFound(Cow<'static, str>),
351
352 #[error("Permission denied: {0}")]
353 PermissionDenied(Cow<'static, str>),
354
355 #[error("Lock acquisition failed")]
356 LockFailed,
357
358 #[error("Lock poisoned: {0}")]
359 LockPoisoned(Cow<'static, str>),
360
361 #[error("File not found: {0}")]
362 FileNotFound(Cow<'static, str>),
363
364 #[error("Corruption detected: {0}")]
365 CorruptionDetected(Cow<'static, str>),
366}
367
368#[derive(Debug, Error)]
370pub enum TransportError {
371 #[error("QUIC error: {0}")]
372 Quic(Cow<'static, str>),
373
374 #[error("TCP error: {0}")]
375 Tcp(Cow<'static, str>),
376
377 #[error("Invalid transport configuration: {0}")]
378 InvalidConfig(Cow<'static, str>),
379
380 #[error("Transport not supported: {0}")]
381 NotSupported(Cow<'static, str>),
382
383 #[error("Stream error: {0}")]
384 StreamError(Cow<'static, str>),
385
386 #[error("Certificate error: {0}")]
387 CertificateError(Cow<'static, str>),
388
389 #[error("Setup failed: {0}")]
390 SetupFailed(Cow<'static, str>),
391
392 #[error("Connection failed to {addr}: {reason}")]
393 ConnectionFailed {
394 addr: SocketAddr,
395 reason: Cow<'static, str>,
396 },
397
398 #[error("Bind error: {0}")]
399 BindError(Cow<'static, str>),
400
401 #[error("Accept failed: {0}")]
402 AcceptFailed(Cow<'static, str>),
403
404 #[error("Not listening")]
405 NotListening,
406
407 #[error("Not initialized")]
408 NotInitialized,
409}
410
411#[derive(Debug, Error)]
413pub enum ConfigError {
414 #[error("Missing required field: {0}")]
415 MissingField(Cow<'static, str>),
416
417 #[error("Invalid value for {field}: {reason}")]
418 InvalidValue {
419 field: Cow<'static, str>,
420 reason: Cow<'static, str>,
421 },
422
423 #[error("Configuration file not found: {0}")]
424 FileNotFound(Cow<'static, str>),
425
426 #[error("Parse error: {0}")]
427 ParseError(Cow<'static, str>),
428
429 #[error("Validation failed: {0}")]
430 ValidationFailed(Cow<'static, str>),
431
432 #[error("IO error for {path}: {source}")]
433 IoError {
434 path: Cow<'static, str>,
435 #[source]
436 source: std::io::Error,
437 },
438}
439
440#[derive(Debug, Error)]
442pub enum SecurityError {
443 #[error("Authentication failed")]
444 AuthenticationFailed,
445
446 #[error("Authorization denied")]
447 AuthorizationDenied,
448
449 #[error("Invalid credentials")]
450 InvalidCredentials,
451
452 #[error("Certificate error: {0}")]
453 CertificateError(Cow<'static, str>),
454
455 #[error("Encryption failed: {0}")]
456 EncryptionFailed(Cow<'static, str>),
457
458 #[error("Decryption failed: {0}")]
459 DecryptionFailed(Cow<'static, str>),
460
461 #[error("Invalid key: {0}")]
462 InvalidKey(Cow<'static, str>),
463
464 #[error("Signature verification failed: {0}")]
465 SignatureVerificationFailed(Cow<'static, str>),
466
467 #[error("Key generation failed: {0}")]
468 KeyGenerationFailed(Cow<'static, str>),
469
470 #[error("Authorization failed: {0}")]
471 AuthorizationFailed(Cow<'static, str>),
472}
473
474#[derive(Debug, Error)]
476pub enum BootstrapError {
477 #[error("No bootstrap nodes available")]
478 NoBootstrapNodes,
479
480 #[error("Bootstrap failed: {0}")]
481 BootstrapFailed(Cow<'static, str>),
482
483 #[error("Invalid bootstrap node: {0}")]
484 InvalidBootstrapNode(Cow<'static, str>),
485
486 #[error("Bootstrap timeout")]
487 BootstrapTimeout,
488
489 #[error("Cache error: {0}")]
490 CacheError(Cow<'static, str>),
491
492 #[error("Invalid data: {0}")]
493 InvalidData(Cow<'static, str>),
494}
495
496pub type P2pResult<T> = Result<T, P2PError>;
498
499pub trait Recoverable {
503 fn is_transient(&self) -> bool;
505
506 fn suggested_retry_after(&self) -> Option<Duration>;
508
509 fn max_retries(&self) -> usize;
511}
512
513impl Recoverable for P2PError {
514 fn is_transient(&self) -> bool {
515 match self {
516 P2PError::Network(NetworkError::ConnectionFailed { .. }) => true,
517 P2PError::Network(NetworkError::Timeout) => true,
518 P2PError::Transport(TransportError::ConnectionFailed { .. }) => true,
519 P2PError::Dht(DhtError::QueryTimeout) => true,
520 P2PError::Timeout(_) => true,
521 P2PError::ResourceExhausted(_) => true,
522 P2PError::Io(err) => matches!(
523 err.kind(),
524 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut | io::ErrorKind::Interrupted
525 ),
526 _ => false,
527 }
528 }
529
530 fn suggested_retry_after(&self) -> Option<Duration> {
531 match self {
532 P2PError::Network(NetworkError::Timeout) => Some(Duration::from_secs(5)),
533 P2PError::Timeout(duration) => Some(*duration * 2),
534 P2PError::ResourceExhausted(_) => Some(Duration::from_secs(30)),
535 P2PError::Transport(TransportError::ConnectionFailed { .. }) => {
536 Some(Duration::from_secs(1))
537 }
538 _ => None,
539 }
540 }
541
542 fn max_retries(&self) -> usize {
543 match self {
544 P2PError::Network(NetworkError::ConnectionFailed { .. }) => 3,
545 P2PError::Transport(TransportError::ConnectionFailed { .. }) => 3,
546 P2PError::Timeout(_) => 2,
547 P2PError::ResourceExhausted(_) => 1,
548 _ => 0,
549 }
550 }
551}
552
553pub trait ErrorContext<T> {
555 fn context(self, msg: &str) -> Result<T, P2PError>;
557
558 fn with_context<F>(self, f: F) -> Result<T, P2PError>
560 where
561 F: FnOnce() -> String;
562}
563
564impl<T, E> ErrorContext<T> for Result<T, E>
565where
566 E: Into<P2PError>,
567{
568 fn context(self, msg: &str) -> Result<T, P2PError> {
569 self.map_err(|e| {
570 let base_error = e.into();
571 P2PError::Internal(format!("{}: {}", msg, base_error).into())
572 })
573 }
574
575 fn with_context<F>(self, f: F) -> Result<T, P2PError>
576 where
577 F: FnOnce() -> String,
578 {
579 self.map_err(|e| {
580 let base_error = e.into();
581 P2PError::Internal(format!("{}: {}", f(), base_error).into())
582 })
583 }
584}
585
586impl P2PError {
588 pub fn connection_failed(addr: SocketAddr, reason: impl Into<String>) -> Self {
590 P2PError::Network(NetworkError::ConnectionFailed {
591 addr,
592 reason: reason.into().into(),
593 })
594 }
595
596 pub fn timeout(duration: Duration) -> Self {
598 P2PError::Timeout(duration)
599 }
600
601 pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
603 P2PError::Validation(msg.into())
604 }
605
606 pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
608 P2PError::Internal(msg.into())
609 }
610}
611
612impl P2PError {
614 pub fn log(&self) {
616 use tracing::{error, warn};
617
618 match self {
619 P2PError::Network(NetworkError::Timeout) | P2PError::Timeout(_) => warn!("{}", self),
620
621 P2PError::Validation(_) | P2PError::Config(_) => warn!("{}", self),
622
623 _ => error!("{}", self),
624 }
625 }
626
627 pub fn log_with_context(&self, context: &str) {
629 use tracing::error;
630 error!("{}: {}", context, self);
631 }
632}
633
634impl From<serde_json::Error> for P2PError {
637 fn from(err: serde_json::Error) -> Self {
638 P2PError::Serialization(err.to_string().into())
639 }
640}
641
642impl From<bincode::Error> for P2PError {
643 fn from(err: bincode::Error) -> Self {
644 P2PError::Serialization(err.to_string().into())
645 }
646}
647
648impl From<std::net::AddrParseError> for P2PError {
649 fn from(err: std::net::AddrParseError) -> Self {
650 P2PError::Network(NetworkError::InvalidAddress(err.to_string().into()))
651 }
652}
653
654impl From<tokio::time::error::Elapsed> for P2PError {
655 fn from(_: tokio::time::error::Elapsed) -> Self {
656 P2PError::Network(NetworkError::Timeout)
657 }
658}
659
660impl From<crate::adaptive::AdaptiveNetworkError> for P2PError {
661 fn from(err: crate::adaptive::AdaptiveNetworkError) -> Self {
662 use crate::adaptive::AdaptiveNetworkError;
663 match err {
664 AdaptiveNetworkError::Network(io_err) => P2PError::Io(io_err),
665 AdaptiveNetworkError::Io(io_err) => P2PError::Io(io_err),
666 AdaptiveNetworkError::Serialization(ser_err) => {
667 P2PError::Serialization(ser_err.to_string().into())
668 }
669 AdaptiveNetworkError::Routing(msg) => {
670 P2PError::Internal(format!("Routing error: {}", msg).into())
671 }
672 AdaptiveNetworkError::Trust(msg) => {
673 P2PError::Internal(format!("Trust error: {}", msg).into())
674 }
675 AdaptiveNetworkError::Learning(msg) => {
676 P2PError::Internal(format!("Learning error: {}", msg).into())
677 }
678 AdaptiveNetworkError::Gossip(msg) => {
679 P2PError::Internal(format!("Gossip error: {}", msg).into())
680 }
681 AdaptiveNetworkError::Other(msg) => P2PError::Internal(msg.into()),
682 }
683 }
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize)]
690pub enum ErrorValue {
691 String(Cow<'static, str>),
692 Number(i64),
693 Bool(bool),
694 Duration(Duration),
695 Address(SocketAddr),
696}
697
698#[derive(Debug, Serialize, Deserialize)]
700pub struct ErrorLog {
701 pub timestamp: i64, pub error_type: &'static str,
703 pub message: Cow<'static, str>,
704 pub context: SmallVec<[(&'static str, ErrorValue); 4]>, pub stack_trace: Option<Cow<'static, str>>,
706}
707
708impl ErrorLog {
709 pub fn from_error(error: &P2PError) -> Self {
711 let mut context = SmallVec::new();
712
713 match error {
715 P2PError::Network(NetworkError::ConnectionFailed { addr, reason }) => {
716 context.push(("address", ErrorValue::Address(*addr)));
717 context.push(("reason", ErrorValue::String(reason.clone())));
718 }
719 P2PError::Timeout(duration) => {
720 context.push(("timeout", ErrorValue::Duration(*duration)));
721 }
722 P2PError::Crypto(CryptoError::InvalidKeyLength { expected, actual }) => {
723 context.push(("expected_length", ErrorValue::Number(*expected as i64)));
724 context.push(("actual_length", ErrorValue::Number(*actual as i64)));
725 }
726 _ => {}
727 }
728
729 ErrorLog {
730 timestamp: chrono::Utc::now().timestamp(),
731 error_type: error_type_name(error),
732 message: error.to_string().into(),
733 context,
734 stack_trace: None,
735 }
736 }
737
738 pub fn with_context(mut self, key: &'static str, value: ErrorValue) -> Self {
739 self.context.push((key, value));
740 self
741 }
742
743 pub fn log(&self) {
744 use log::{error, warn};
745
746 let json = serde_json::to_string(self).unwrap_or_else(|_| self.message.to_string());
747
748 match self.error_type {
749 "Validation" | "Config" => warn!("{}", json),
750 _ => error!("{}", json),
751 }
752 }
753}
754
755fn error_type_name(error: &P2PError) -> &'static str {
756 match error {
757 P2PError::Network(_) => "Network",
758 P2PError::Dht(_) => "DHT",
759 P2PError::Identity(_) => "Identity",
760 P2PError::Crypto(_) => "Crypto",
761 P2PError::Storage(_) => "Storage",
762 P2PError::Transport(_) => "Transport",
763 P2PError::Config(_) => "Config",
764 P2PError::Io(_) => "IO",
765 P2PError::Serialization(_) => "Serialization",
766 P2PError::Validation(_) => "Validation",
767 P2PError::Timeout(_) => "Timeout",
768 P2PError::ResourceExhausted(_) => "ResourceExhausted",
769 P2PError::Internal(_) => "Internal",
770 P2PError::Security(_) => "Security",
771 P2PError::Bootstrap(_) => "Bootstrap",
772 P2PError::Encoding(_) => "Encoding",
773 P2PError::RecordTooLarge(_) => "RecordTooLarge",
774 P2PError::TimeError => "TimeError",
775 P2PError::InvalidInput(_) => "InvalidInput",
776 P2PError::WebRtcError(_) => "WebRTC",
777 }
778}
779
780pub trait ErrorReporting {
782 fn report(&self) -> ErrorLog;
783 fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog;
784}
785
786impl ErrorReporting for P2PError {
787 fn report(&self) -> ErrorLog {
788 ErrorLog::from_error(self)
789 }
790
791 fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog {
792 let log = ErrorLog::from_error(self);
793 for (_key, _value) in context {
795 }
799 log
800 }
801}
802
803pub trait IntoAnyhow<T> {
807 fn into_anyhow(self) -> anyhow::Result<T>;
808}
809
810impl<T> IntoAnyhow<T> for P2pResult<T> {
811 fn into_anyhow(self) -> anyhow::Result<T> {
812 self.map_err(|e| anyhow::anyhow!(e))
813 }
814}
815
816pub trait FromAnyhowExt<T> {
817 fn into_p2p_result(self) -> P2pResult<T>;
818}
819
820impl<T> FromAnyhowExt<T> for anyhow::Result<T> {
821 fn into_p2p_result(self) -> P2pResult<T> {
822 self.map_err(|e| P2PError::Internal(e.to_string().into()))
823 }
824}
825
826pub use anyhow::{Context as AnyhowContext, Result as AnyhowResult};
828
829#[cfg(test)]
830mod tests {
831 use super::*;
832
833 #[test]
834 fn test_error_display() {
835 let err =
836 P2PError::connection_failed("127.0.0.1:8080".parse().unwrap(), "Connection refused");
837 assert_eq!(
838 err.to_string(),
839 "Network error: Connection failed to 127.0.0.1:8080: Connection refused"
840 );
841 }
842
843 #[test]
844 fn test_error_context() {
845 let result: Result<(), io::Error> =
846 Err(io::Error::new(io::ErrorKind::NotFound, "file not found"));
847
848 let with_context = crate::error::ErrorContext::context(result, "Failed to load config");
849 assert!(with_context.is_err());
850 assert!(
851 with_context
852 .unwrap_err()
853 .to_string()
854 .contains("Failed to load config")
855 );
856 }
857
858 #[test]
859 fn test_timeout_error() {
860 let err = P2PError::timeout(Duration::from_secs(30));
861 assert_eq!(err.to_string(), "Operation timed out after 30s");
862 }
863
864 #[test]
865 fn test_crypto_error() {
866 let err = P2PError::Crypto(CryptoError::InvalidKeyLength {
867 expected: 32,
868 actual: 16,
869 });
870 assert_eq!(
871 err.to_string(),
872 "Cryptography error: Invalid key length: expected 32, got 16"
873 );
874 }
875
876 #[test]
877 fn test_error_log_serialization() {
878 let error = P2PError::Network(NetworkError::ConnectionFailed {
879 addr: "127.0.0.1:8080".parse().unwrap(),
880 reason: "Connection refused".into(),
881 });
882
883 let log = error
884 .report()
885 .with_context("peer_id", ErrorValue::String("peer123".into()))
886 .with_context("retry_count", ErrorValue::Number(3));
887
888 let json = serde_json::to_string_pretty(&log).unwrap();
889 assert!(json.contains("Network"));
890 assert!(json.contains("127.0.0.1:8080"));
891 assert!(json.contains("peer123"));
892 }
893
894 #[test]
895 fn test_anyhow_conversion() {
896 let p2p_result: P2pResult<()> = Err(P2PError::validation("Invalid input"));
897 let anyhow_result = p2p_result.into_anyhow();
898 assert!(anyhow_result.is_err());
899
900 let anyhow_err = anyhow::anyhow!("Test error");
901 let anyhow_result: anyhow::Result<()> = Err(anyhow_err);
902 let p2p_result = crate::error::FromAnyhowExt::into_p2p_result(anyhow_result);
903 assert!(p2p_result.is_err());
904 match p2p_result.unwrap_err() {
905 P2PError::Internal(msg) => assert!(msg.contains("Test error")),
906 _ => panic!("Expected Internal error"),
907 }
908 }
909}