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