Skip to main content

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>;