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`](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>;