mythic/error.rs
1//! Unified error type for the full mythic-c2 lifecycle — codec, crypto,
2//! transport, protocol, task execution, and runtime.
3//!
4//! Each variant carries a stable numeric code used by [`Display`](MythicError::fmt).
5//! In `no_std` release builds every error is a single-digit number — zero strings.
6
7use alloc::string::String;
8use core::fmt;
9
10/// Unified error covering the full C2 agent lifecycle.
11///
12/// # Error codes
13///
14/// | Code | Variant | Category |
15/// |------|---------|----------|
16/// | 1 | `Serialize` | Codec |
17/// | 2 | `Deserialize` | Codec |
18/// | 3 | `Base64` | Codec |
19/// | 4 | `Utf8` | Codec |
20/// | 5 | `InvalidPacket` | Codec |
21/// | 6 | `InvalidUuid` | Codec |
22/// | 7 | `UuidMismatch` | Codec |
23/// | 8 | `Crypto` | Crypto |
24/// | 9 | `Timeout` | Transport |
25/// | 10 | `ConnectionFailed` | Transport |
26/// | 11 | `DnsFailed` | Transport |
27/// | 12 | `TlsFailed` | Transport |
28/// | 13 | `HttpStatus(u16)` | Transport |
29/// | 14 | `ServerError(u16)` | Transport |
30/// | 15 | `AuthFailed` | Protocol |
31/// | 16 | `ServerRejected` | Protocol |
32/// | 17 | `NotCheckedIn` | Protocol |
33/// | 18 | `PayloadTooLarge` | Protocol |
34/// | 19 | `KeyExchangeFailed` | Protocol |
35/// | 20 | `RateLimited` | Protocol |
36/// | 21 | `CommandNotFound` | Task |
37/// | 22 | `InvalidTaskData` | Task |
38/// | 23 | `TaskTimeout` | Task |
39/// | 24 | `ResourceExhausted` | Runtime |
40/// | 25 | `PermissionDenied` | Runtime |
41/// | 26 | `ProcessFailed` | Runtime |
42/// | 27 | `IoFailed` | Runtime |
43/// | 28 | `Transport` | Fallback |
44/// | 29 | `Protocol` | Fallback |
45/// | 30 | `Task` | Fallback |
46/// | 31 | `Runtime` | Fallback |
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[repr(u8)]
49pub enum MythicError {
50 // ── Codec ──────────────────────────────────────────
51 Serialize = 1,
52 Deserialize = 2,
53 Base64 = 3,
54 Utf8 = 4,
55 InvalidPacket = 5,
56 InvalidUuid = 6,
57 UuidMismatch = 7,
58
59 // ── Crypto ─────────────────────────────────────────
60 Crypto = 8,
61
62 // ── Transport ──────────────────────────────────────
63 Timeout = 9,
64 ConnectionFailed = 10,
65 DnsFailed = 11,
66 TlsFailed = 12,
67 HttpStatus(u16) = 13,
68 ServerError(u16) = 14,
69
70 // ── Protocol ───────────────────────────────────────
71 AuthFailed = 15,
72 ServerRejected = 16,
73 NotCheckedIn = 17,
74 PayloadTooLarge = 18,
75 KeyExchangeFailed = 19,
76 RateLimited = 20,
77
78 // ── Task ───────────────────────────────────────────
79 CommandNotFound = 21,
80 InvalidTaskData = 22,
81 TaskTimeout = 23,
82
83 // ── Runtime ────────────────────────────────────────
84 ResourceExhausted = 24,
85 PermissionDenied = 25,
86 ProcessFailed = 26,
87 IoFailed = 27,
88
89 // ── Fallback ───────────────────────────────────────
90 Transport(String) = 28,
91 Protocol(String) = 29,
92 Task(String) = 30,
93 Runtime(String) = 31,
94}
95
96impl MythicError {
97 /// Numeric error code.
98 pub const fn code(&self) -> u8 {
99 match self {
100 Self::Serialize => 1,
101 Self::Deserialize => 2,
102 Self::Base64 => 3,
103 Self::Utf8 => 4,
104 Self::InvalidPacket => 5,
105 Self::InvalidUuid => 6,
106 Self::UuidMismatch => 7,
107 Self::Crypto => 8,
108 Self::Timeout => 9,
109 Self::ConnectionFailed => 10,
110 Self::DnsFailed => 11,
111 Self::TlsFailed => 12,
112 Self::HttpStatus(_) => 13,
113 Self::ServerError(_) => 14,
114 Self::AuthFailed => 15,
115 Self::ServerRejected => 16,
116 Self::NotCheckedIn => 17,
117 Self::PayloadTooLarge => 18,
118 Self::KeyExchangeFailed => 19,
119 Self::RateLimited => 20,
120 Self::CommandNotFound => 21,
121 Self::InvalidTaskData => 22,
122 Self::TaskTimeout => 23,
123 Self::ResourceExhausted => 24,
124 Self::PermissionDenied => 25,
125 Self::ProcessFailed => 26,
126 Self::IoFailed => 27,
127 Self::Transport(_) => 28,
128 Self::Protocol(_) => 29,
129 Self::Task(_) => 30,
130 Self::Runtime(_) => 31,
131 }
132 }
133
134 /// Build a `Transport` variant from any `Display` error.
135 pub fn transport<E: fmt::Display>(e: E) -> Self {
136 Self::Transport(alloc::format!("{e}"))
137 }
138
139 /// Build a `Protocol` variant from any `Display` error.
140 pub fn protocol<E: fmt::Display>(e: E) -> Self {
141 Self::Protocol(alloc::format!("{e}"))
142 }
143
144 /// Build a `Task` variant from any `Display` error.
145 pub fn task<E: fmt::Display>(e: E) -> Self {
146 Self::Task(alloc::format!("{e}"))
147 }
148
149 /// Build a `Runtime` variant from any `Display` error.
150 pub fn runtime<E: fmt::Display>(e: E) -> Self {
151 Self::Runtime(alloc::format!("{e}"))
152 }
153}
154
155impl fmt::Display for MythicError {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 write!(f, "{}", self.code())
158 }
159}
160
161/// Convenience alias.
162pub type MythicResult<T> = Result<T, MythicError>;