1use alloc::string::{String, ToString};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
10pub struct DelegateMessage {
11 pub message: String,
12 #[serde(default, skip_serializing_if = "Option::is_none")]
14 pub c2_profile: Option<String>,
15 pub uuid: Uuid,
16 #[serde(default, skip_serializing_if = "Option::is_none", alias = "new_uuid")]
17 pub mythic_uuid: Option<Uuid>,
18}
19
20pub type P2PMessage = DelegateMessage;
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23pub struct AlertMessage {
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub source: Option<String>,
26 #[serde(default = "default_alert_level", skip_serializing_if = "is_warning")]
27 pub level: Option<String>,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub alert: Option<String>,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub send_webhook: Option<bool>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub webhook_alert: Option<Value>,
34}
35
36impl Default for AlertMessage {
37 fn default() -> Self {
38 Self {
39 source: None,
40 level: default_alert_level(),
41 alert: None,
42 send_webhook: None,
43 webhook_alert: None,
44 }
45 }
46}
47
48fn default_alert_level() -> Option<String> {
49 Some("warning".to_string())
50}
51
52fn is_warning(level: &Option<String>) -> bool {
53 matches!(level.as_deref(), Some("warning"))
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
57pub struct EdgeMessage {
58 pub source: String,
59 pub destination: String,
60 pub action: String,
61 pub c2_profile: String,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub metadata: Option<String>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
67pub struct SocksMessage {
68 pub server_id: u32,
69 pub exit: bool,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub data: Option<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
75pub struct ReversePortForwardMessage {
76 pub server_id: u32,
77 pub exit: bool,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub data: Option<String>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub port: Option<u32>,
84}
85
86pub type RpfwdMessage = ReversePortForwardMessage;
87
88#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
89pub struct InteractiveMessage {
90 pub task_id: Uuid,
91 pub data: String,
92 pub message_type: u8,
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use alloc::string::ToString;
99
100 #[test]
101 fn peer_messages_roundtrip() {
102 let uuid = Uuid::nil();
103 let next_uuid = Uuid::from_u128(1);
104
105 let delegate = DelegateMessage {
106 message: "msg".to_string(),
107 c2_profile: Some("p2p".to_string()),
108 uuid,
109 mythic_uuid: Some(next_uuid),
110 };
111 assert_eq!(
112 serde_json::from_str::<DelegateMessage>(&serde_json::to_string(&delegate).unwrap())
113 .unwrap(),
114 delegate
115 );
116
117 let alert = AlertMessage {
118 source: Some("src".to_string()),
119 level: Some("low".to_string()),
120 alert: Some("warn".to_string()),
121 send_webhook: Some(true),
122 webhook_alert: Some(serde_json::json!({"a": 1})),
123 };
124 assert_eq!(
125 serde_json::from_str::<AlertMessage>(&serde_json::to_string(&alert).unwrap()).unwrap(),
126 alert
127 );
128
129 let edge = EdgeMessage {
130 source: "src".to_string(),
131 destination: "dst".to_string(),
132 action: "link".to_string(),
133 c2_profile: "http".to_string(),
134 metadata: Some("{}".to_string()),
135 };
136 assert_eq!(
137 serde_json::from_str::<EdgeMessage>(&serde_json::to_string(&edge).unwrap()).unwrap(),
138 edge
139 );
140
141 let socks = SocksMessage {
142 server_id: 9,
143 exit: false,
144 data: Some("d".to_string()),
145 };
146 assert_eq!(
147 serde_json::from_str::<SocksMessage>(&serde_json::to_string(&socks).unwrap()).unwrap(),
148 socks
149 );
150
151 let rpfwd = ReversePortForwardMessage {
152 server_id: 3,
153 exit: true,
154 data: None,
155 port: Some(80),
156 };
157 assert_eq!(
158 serde_json::from_str::<ReversePortForwardMessage>(
159 &serde_json::to_string(&rpfwd).unwrap()
160 )
161 .unwrap(),
162 rpfwd
163 );
164
165 let interactive = InteractiveMessage {
166 task_id: next_uuid,
167 data: "abc".to_string(),
168 message_type: 1,
169 };
170 assert_eq!(
171 serde_json::from_str::<InteractiveMessage>(
172 &serde_json::to_string(&interactive).unwrap()
173 )
174 .unwrap(),
175 interactive
176 );
177
178 let minimal_alert: AlertMessage = serde_json::from_str(r#"{"alert":"hello"}"#).unwrap();
179 assert_eq!(minimal_alert.alert.as_deref(), Some("hello"));
180 assert!(minimal_alert.source.is_none());
181 assert_eq!(minimal_alert.level.as_deref(), Some("warning"));
182
183 let socks_json: SocksMessage =
184 serde_json::from_str(r#"{"server_id":1,"exit":true}"#).unwrap();
185 assert!(socks_json.exit);
186 assert!(socks_json.data.is_none());
187
188 let rpfwd_json: ReversePortForwardMessage =
189 serde_json::from_str(r#"{"server_id":2,"exit":false,"data":"YQ"}"#).unwrap();
190 assert!(!rpfwd_json.exit);
191 assert_eq!(rpfwd_json.data.as_deref(), Some("YQ"));
192 assert!(rpfwd_json.port.is_none());
193
194 let rpfwd_with_port: ReversePortForwardMessage =
195 serde_json::from_str(r#"{"server_id":3,"exit":true,"data":"","port":445}"#).unwrap();
196 assert_eq!(rpfwd_with_port.port, Some(445));
197 }
198}