pub(crate) const NFTNL_UDATA_RULE_COMMENT: u8 = 0;
pub(crate) const NFTNL_UDATA_COMMENT_MAXLEN: usize = 128;
const NLINK_PREFIX: &str = "nlink:";
pub(crate) fn encode_nlink_comment(key: &str) -> Option<Vec<u8>> {
let body = format!("{NLINK_PREFIX}{key}\0");
let body_bytes = body.as_bytes();
if body_bytes.len() > NFTNL_UDATA_COMMENT_MAXLEN {
return None;
}
let mut tlv = Vec::with_capacity(2 + body_bytes.len());
tlv.push(NFTNL_UDATA_RULE_COMMENT);
tlv.push(body_bytes.len() as u8);
tlv.extend_from_slice(body_bytes);
Some(tlv)
}
pub(crate) fn parse_nlink_comment(userdata: &[u8]) -> Option<String> {
let mut cursor = userdata;
while cursor.len() >= 2 {
let ty = cursor[0];
let len = cursor[1] as usize;
if cursor.len() < 2 + len {
return None;
}
let payload = &cursor[2..2 + len];
if ty == NFTNL_UDATA_RULE_COMMENT {
let s = std::str::from_utf8(payload)
.ok()?
.trim_end_matches('\0');
return s.strip_prefix(NLINK_PREFIX).map(str::to_string);
}
cursor = &cursor[2 + len..];
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_then_parse_round_trips() {
let key = "input/ssh-accept";
let encoded = encode_nlink_comment(key).expect("encode");
assert_eq!(encoded[0], NFTNL_UDATA_RULE_COMMENT);
assert_eq!(encoded[1] as usize, NLINK_PREFIX.len() + key.len() + 1);
let decoded = parse_nlink_comment(&encoded).expect("decode");
assert_eq!(decoded, key);
}
#[test]
fn encode_rejects_overlong_key() {
let too_long = "x".repeat(NFTNL_UDATA_COMMENT_MAXLEN);
assert!(encode_nlink_comment(&too_long).is_none());
}
#[test]
fn encode_accepts_max_size_key() {
let max_key_len = NFTNL_UDATA_COMMENT_MAXLEN - NLINK_PREFIX.len() - 1;
let max_key = "x".repeat(max_key_len);
assert!(encode_nlink_comment(&max_key).is_some());
}
#[test]
fn parse_ignores_foreign_comment_prefix() {
let body = b"cilium:abc123\0";
let mut tlv = vec![NFTNL_UDATA_RULE_COMMENT, body.len() as u8];
tlv.extend_from_slice(body);
assert_eq!(parse_nlink_comment(&tlv), None);
}
#[test]
fn parse_skips_unknown_tlv_types() {
let mut tlv = Vec::new();
tlv.extend_from_slice(&[99, 4, 0xde, 0xad, 0xbe, 0xef]);
let body = b"nlink:my-key\0";
tlv.extend_from_slice(&[NFTNL_UDATA_RULE_COMMENT, body.len() as u8]);
tlv.extend_from_slice(body);
assert_eq!(parse_nlink_comment(&tlv), Some("my-key".to_string()));
}
#[test]
fn parse_handles_empty_userdata() {
assert_eq!(parse_nlink_comment(&[]), None);
}
#[test]
fn parse_handles_truncated_tlv() {
let tlv = [NFTNL_UDATA_RULE_COMMENT, 10, 0x6e, 0x6c];
assert_eq!(parse_nlink_comment(&tlv), None);
}
#[test]
fn parse_handles_invalid_utf8() {
let tlv = [NFTNL_UDATA_RULE_COMMENT, 2, 0xff, 0xfe];
assert_eq!(parse_nlink_comment(&tlv), None);
}
}