use btf_rs::{Btf, Type};
use super::MAX_BTF_ID_PROBE;
#[derive(Debug, Clone)]
pub struct PayloadTypeChoice {
pub target_type_id: u32,
pub reason: String,
}
pub fn discover_payload_btf_id(
btf: &Btf,
payload_size: usize,
allocator_name: &str,
) -> PayloadTypeChoice {
if payload_size == 0 {
return PayloadTypeChoice {
target_type_id: 0,
reason: "payload_size == 0".into(),
};
}
let mut size_matches: Vec<(u32, String)> = Vec::new();
const CONSECUTIVE_FAIL_CAP: u32 = 64;
let (first_id, last_id) = match btf.split() {
Some(prog) => prog.type_id_range(),
None => btf.base().type_id_range(),
};
let scan_end = probe_scan_end(first_id, last_id);
let mut tid: u32 = first_id.max(1);
let mut consecutive_fail: u32 = 0;
while tid <= scan_end {
match btf.resolve_type_by_id(tid) {
Ok(ty) => {
consecutive_fail = 0;
if let Type::Struct(s) = ty
&& s.size() == payload_size
&& let Ok(name) = btf.resolve_name(&s)
&& !name.is_empty()
{
size_matches.push((tid, name));
}
}
Err(_) => {
consecutive_fail += 1;
if consecutive_fail >= CONSECUTIVE_FAIL_CAP {
break;
}
}
}
tid += 1;
}
match size_matches.len() {
0 => PayloadTypeChoice {
target_type_id: 0,
reason: format!("no candidate of size {payload_size}"),
},
1 => PayloadTypeChoice {
target_type_id: size_matches[0].0,
reason: String::new(),
},
n => {
let name_stem = allocator_name
.strip_suffix("_allocator")
.unwrap_or(allocator_name);
if !name_stem.is_empty() {
let stems: &[&str] = &[name_stem, name_stem.strip_prefix("scx_").unwrap_or("")];
for stem in stems {
if stem.is_empty() {
continue;
}
let hits: Vec<u32> = size_matches
.iter()
.filter(|(_, sn)| sn == stem)
.map(|(id, _)| *id)
.collect();
if hits.len() == 1 {
return PayloadTypeChoice {
target_type_id: hits[0],
reason: String::new(),
};
}
}
}
type Pat = fn(&str) -> bool;
let patterns: &[Pat] = &[
|n: &str| n == "task_ctx",
|n: &str| n.ends_with("_arena_ctx"),
|n: &str| n.ends_with("_task_ctx"),
|n: &str| n.ends_with("_ctx"),
];
for pat in patterns {
let hits: Vec<u32> = size_matches
.iter()
.filter(|(_, n)| pat(n))
.map(|(id, _)| *id)
.collect();
match hits.len() {
0 => continue,
1 => {
return PayloadTypeChoice {
target_type_id: hits[0],
reason: String::new(),
};
}
_ => {
continue;
}
}
}
PayloadTypeChoice {
target_type_id: 0,
reason: format!("ambiguous: {n} candidates"),
}
}
}
}
fn probe_scan_end(first_id: u32, last_id: u32) -> u32 {
last_id.min(first_id.saturating_add(MAX_BTF_ID_PROBE).saturating_sub(1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probe_scan_end_bounds_window_not_absolute_id() {
assert_eq!(probe_scan_end(1, 50), 50);
assert_eq!(probe_scan_end(3, 500), 500);
let first = MAX_BTF_ID_PROBE + 1; let last = first + 4; let end = probe_scan_end(first, last);
assert_eq!(end, last, "window must cover the full program range");
assert!(
end >= first,
"scan window must reach first_id even when the base exceeds \
MAX_BTF_ID_PROBE (got end={end}, first={first})"
);
assert_eq!(probe_scan_end(10, u32::MAX), 10 + MAX_BTF_ID_PROBE - 1);
assert_eq!(probe_scan_end(u32::MAX, u32::MAX), u32::MAX - 1);
}
}