pub use nlink_macros::{
genl_family, GenlAttribute, GenlCommand, GenlEnum, GenlMessage, NetlinkAttrs,
};
mod genl_dispatch;
pub use genl_dispatch::GenlTypedDumpStream;
use crate::netlink::MessageBuilder;
use crate::Result;
pub trait GenlMessage: Sized {
const CMD: u8;
fn to_bytes(&self, builder: &mut MessageBuilder) -> Result<()>;
fn from_bytes(payload: &[u8]) -> Result<Self>;
}
pub trait GenlFamily {
const VERSION: u8;
const NAME: &'static str;
fn family_id(&self) -> u16;
fn mcast_group(&self, _name: &str) -> ::core::option::Option<u32> {
::core::option::Option::None
}
}
pub trait NetlinkAttrs: Sized {
fn write_attrs(&self, builder: &mut MessageBuilder) -> Result<()>;
fn read_attrs(payload: &[u8]) -> Result<Self>;
}
#[doc(hidden)]
pub mod __rt {
use crate::netlink::{attr::AttrIter, MessageBuilder};
use crate::{Error, Result};
pub fn emit_u8_attr(b: &mut MessageBuilder, attr_type: u16, v: u8) {
b.append_attr_u8(attr_type, v);
}
pub fn emit_u16_attr(b: &mut MessageBuilder, attr_type: u16, v: u16) {
b.append_attr_u16(attr_type, v);
}
pub fn emit_u32_attr(b: &mut MessageBuilder, attr_type: u16, v: u32) {
b.append_attr_u32(attr_type, v);
}
pub fn emit_u64_attr(b: &mut MessageBuilder, attr_type: u16, v: u64) {
b.append_attr_u64(attr_type, v);
}
pub fn emit_i32_attr(b: &mut MessageBuilder, attr_type: u16, v: i32) {
b.append_attr_u32(attr_type, v as u32);
}
pub fn emit_i64_attr(b: &mut MessageBuilder, attr_type: u16, v: i64) {
b.append_attr_u64(attr_type, v as u64);
}
pub fn emit_str_attr(b: &mut MessageBuilder, attr_type: u16, v: &str) {
b.append_attr_str(attr_type, v);
}
pub fn emit_bytes_attr(b: &mut MessageBuilder, attr_type: u16, v: &[u8]) {
b.append_attr(attr_type, v);
}
pub fn emit_flag_attr(b: &mut MessageBuilder, attr_type: u16) {
b.append_attr_empty(attr_type);
}
pub fn emit_u16_be_attr(b: &mut MessageBuilder, attr_type: u16, v: u16) {
b.append_attr_u16_be(attr_type, v);
}
pub fn emit_u32_be_attr(b: &mut MessageBuilder, attr_type: u16, v: u32) {
b.append_attr_u32_be(attr_type, v);
}
pub fn emit_u64_be_attr(b: &mut MessageBuilder, attr_type: u16, v: u64) {
b.append_attr_u64_be(attr_type, v);
}
pub fn parse_u8_attr(payload: &[u8]) -> Result<u8> {
if payload.is_empty() {
return Err(Error::Truncated {
expected: 1,
actual: 0,
});
}
Ok(payload[0])
}
pub fn parse_u16_attr(payload: &[u8]) -> Result<u16> {
if payload.len() < 2 {
return Err(Error::Truncated {
expected: 2,
actual: payload.len(),
});
}
Ok(u16::from_ne_bytes([payload[0], payload[1]]))
}
pub fn parse_u32_attr(payload: &[u8]) -> Result<u32> {
if payload.len() < 4 {
return Err(Error::Truncated {
expected: 4,
actual: payload.len(),
});
}
Ok(u32::from_ne_bytes([
payload[0], payload[1], payload[2], payload[3],
]))
}
pub fn parse_u64_attr(payload: &[u8]) -> Result<u64> {
if payload.len() < 8 {
return Err(Error::Truncated {
expected: 8,
actual: payload.len(),
});
}
let mut a = [0u8; 8];
a.copy_from_slice(&payload[..8]);
Ok(u64::from_ne_bytes(a))
}
pub fn parse_i32_attr(payload: &[u8]) -> Result<i32> {
if payload.len() < 4 {
return Err(Error::Truncated {
expected: 4,
actual: payload.len(),
});
}
Ok(i32::from_ne_bytes([
payload[0], payload[1], payload[2], payload[3],
]))
}
pub fn parse_i64_attr(payload: &[u8]) -> Result<i64> {
if payload.len() < 8 {
return Err(Error::Truncated {
expected: 8,
actual: payload.len(),
});
}
let mut a = [0u8; 8];
a.copy_from_slice(&payload[..8]);
Ok(i64::from_ne_bytes(a))
}
pub fn parse_str_attr(payload: &[u8]) -> Result<String> {
let trimmed = payload
.iter()
.position(|&b| b == 0)
.map(|n| &payload[..n])
.unwrap_or(payload);
Ok(String::from_utf8_lossy(trimmed).into_owned())
}
pub fn parse_bytes_attr(payload: &[u8]) -> Result<Vec<u8>> {
Ok(payload.to_vec())
}
pub fn parse_u16_be_attr(payload: &[u8]) -> Result<u16> {
if payload.len() < 2 {
return Err(Error::Truncated {
expected: 2,
actual: payload.len(),
});
}
Ok(u16::from_be_bytes([payload[0], payload[1]]))
}
pub fn parse_u32_be_attr(payload: &[u8]) -> Result<u32> {
if payload.len() < 4 {
return Err(Error::Truncated {
expected: 4,
actual: payload.len(),
});
}
Ok(u32::from_be_bytes([
payload[0], payload[1], payload[2], payload[3],
]))
}
pub fn parse_u64_be_attr(payload: &[u8]) -> Result<u64> {
if payload.len() < 8 {
return Err(Error::Truncated {
expected: 8,
actual: payload.len(),
});
}
let mut a = [0u8; 8];
a.copy_from_slice(&payload[..8]);
Ok(u64::from_be_bytes(a))
}
pub fn attr_iter(payload: &[u8]) -> AttrIter<'_> {
AttrIter::new(payload)
}
use crate::netlink::{
genl::{CtrlAttr, CtrlAttrMcastGrp, CtrlCmd, GenlMsgHdr, GENL_HDRLEN, GENL_ID_CTRL},
message::{MessageIter, NlMsgError, NLM_F_ACK, NLM_F_REQUEST},
NetlinkSocket,
};
use std::collections::HashMap;
pub async fn resolve_genl_family(
socket: &NetlinkSocket,
name: &str,
) -> Result<u16> {
let mut builder = MessageBuilder::new(GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK);
let genl_hdr = GenlMsgHdr::new(CtrlCmd::GetFamily as u8, 1);
builder.append(&genl_hdr);
builder.append_attr_str(CtrlAttr::FamilyName as u16, name);
let seq = socket.next_seq();
builder.set_seq(seq);
builder.set_pid(socket.pid());
let msg = builder.finish();
socket.send(&msg).await?;
let response: Vec<u8> = socket.recv_msg().await?;
for result in MessageIter::new(&response) {
let (header, payload) = result?;
if header.nlmsg_seq != seq {
continue;
}
if header.is_error() {
let err = NlMsgError::from_bytes(payload)?;
if !err.is_ack() {
if err.error == -libc::ENOENT {
return Err(Error::FamilyNotFound {
name: name.to_string(),
});
}
return Err(err.into_error(payload));
}
continue;
}
if header.is_done() {
continue;
}
if payload.len() < GENL_HDRLEN {
return Err(Error::InvalidMessage(
"GENL header too short in CTRL_CMD_GETFAMILY response".into(),
));
}
let attrs_data = &payload[GENL_HDRLEN..];
for (attr_type, attr_payload) in AttrIter::new(attrs_data) {
if attr_type == CtrlAttr::FamilyId as u16 {
return parse_u16_attr(attr_payload);
}
}
}
Err(Error::FamilyNotFound {
name: name.to_string(),
})
}
pub async fn resolve_genl_family_with_groups(
socket: &NetlinkSocket,
name: &str,
) -> Result<(u16, HashMap<String, u32>)> {
let mut builder = MessageBuilder::new(GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK);
let genl_hdr = GenlMsgHdr::new(CtrlCmd::GetFamily as u8, 1);
builder.append(&genl_hdr);
builder.append_attr_str(CtrlAttr::FamilyName as u16, name);
let seq = socket.next_seq();
builder.set_seq(seq);
builder.set_pid(socket.pid());
let msg = builder.finish();
socket.send(&msg).await?;
let response: Vec<u8> = socket.recv_msg().await?;
let mut family_id: Option<u16> = None;
let mut mcast_groups: HashMap<String, u32> = HashMap::new();
for result in MessageIter::new(&response) {
let (header, payload) = result?;
if header.nlmsg_seq != seq {
continue;
}
if header.is_error() {
let err = NlMsgError::from_bytes(payload)?;
if !err.is_ack() {
if err.error == -libc::ENOENT {
return Err(Error::FamilyNotFound {
name: name.to_string(),
});
}
return Err(err.into_error(payload));
}
continue;
}
if header.is_done() {
continue;
}
if payload.len() < GENL_HDRLEN {
return Err(Error::InvalidMessage(
"GENL header too short in CTRL_CMD_GETFAMILY response".into(),
));
}
let attrs_data = &payload[GENL_HDRLEN..];
for (attr_type, attr_payload) in AttrIter::new(attrs_data) {
if attr_type == CtrlAttr::FamilyId as u16 {
family_id = Some(parse_u16_attr(attr_payload)?);
} else if attr_type == CtrlAttr::McastGroups as u16 {
for (_idx, grp_data) in AttrIter::new(attr_payload) {
let mut grp_name: Option<String> = None;
let mut grp_id: Option<u32> = None;
for (grp_attr_type, grp_attr_payload) in AttrIter::new(grp_data) {
if grp_attr_type == CtrlAttrMcastGrp::Name as u16 {
grp_name = Some(
::std::str::from_utf8(grp_attr_payload)
.unwrap_or("")
.trim_end_matches('\0')
.to_string(),
);
} else if grp_attr_type == CtrlAttrMcastGrp::Id as u16
&& grp_attr_payload.len() >= 4
{
grp_id = Some(u32::from_ne_bytes(
grp_attr_payload[..4].try_into().unwrap(),
));
}
}
if let (Some(n), Some(i)) = (grp_name, grp_id) {
mcast_groups.insert(n, i);
}
}
}
}
}
match family_id {
Some(id) => Ok((id, mcast_groups)),
None => Err(Error::FamilyNotFound {
name: name.to_string(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
struct TinyMsg {
id: u32,
label: String,
}
const ATTR_ID: u16 = 1;
const ATTR_LABEL: u16 = 2;
impl GenlMessage for TinyMsg {
const CMD: u8 = 7;
fn to_bytes(&self, builder: &mut MessageBuilder) -> Result<()> {
__rt::emit_u32_attr(builder, ATTR_ID, self.id);
__rt::emit_str_attr(builder, ATTR_LABEL, &self.label);
Ok(())
}
fn from_bytes(payload: &[u8]) -> Result<Self> {
let mut id: u32 = 0;
let mut label = String::new();
for (ty, p) in __rt::attr_iter(payload) {
match ty {
ATTR_ID => id = __rt::parse_u32_attr(p)?,
ATTR_LABEL => label = __rt::parse_str_attr(p)?,
_ => {}
}
}
Ok(TinyMsg { id, label })
}
}
#[test]
fn hand_rolled_message_round_trips_via_runtime_helpers() {
let original = TinyMsg {
id: 0xDEADBEEF,
label: "hello".to_string(),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let bytes = builder.as_bytes();
let parsed = TinyMsg::from_bytes(&bytes[body_start..]).expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn parse_helpers_reject_truncated_payloads() {
let short_u32: &[u8] = &[0, 1, 2];
let err = __rt::parse_u32_attr(short_u32).unwrap_err();
assert!(matches!(err, Error::Truncated { expected: 4, actual: 3 }));
let short_u64: &[u8] = &[0, 0, 0, 0];
let err = __rt::parse_u64_attr(short_u64).unwrap_err();
assert!(matches!(err, Error::Truncated { expected: 8, actual: 4 }));
let empty: &[u8] = &[];
let err = __rt::parse_u8_attr(empty).unwrap_err();
assert!(matches!(err, Error::Truncated { expected: 1, actual: 0 }));
}
#[test]
fn parse_str_handles_nul_termination_and_invalid_utf8() {
let s = __rt::parse_str_attr(b"hello\0").unwrap();
assert_eq!(s, "hello");
let s = __rt::parse_str_attr(b"abc").unwrap();
assert_eq!(s, "abc");
let s = __rt::parse_str_attr(b"\xFF\xFE\0").unwrap();
assert!(!s.is_empty());
}
#[test]
fn big_endian_helpers_round_trip_against_native_endian_layout() {
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
__rt::emit_u32_be_attr(&mut b, 99, 0x1234_5678);
let bytes = &b.as_bytes()[start..];
let mut found = None;
for (ty, payload) in __rt::attr_iter(bytes) {
if ty == 99 {
found = Some(__rt::parse_u32_be_attr(payload).unwrap());
}
}
assert_eq!(found, Some(0x1234_5678));
}
use crate::macros::GenlMessage as GenlMessageDerive;
#[derive(GenlMessageDerive, Debug, Clone, PartialEq, Eq)]
#[genl_message(cmd = 7u8)]
struct DerivedSimple {
#[genl_attr(1u16)]
id: u32,
#[genl_attr(2u16)]
label: String,
}
#[test]
fn derived_simple_round_trips() {
let original = DerivedSimple {
id: 0xCAFEBABE,
label: "world".to_string(),
};
assert_eq!(DerivedSimple::CMD, 7);
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let bytes = builder.as_bytes();
let parsed = DerivedSimple::from_bytes(&bytes[body_start..]).expect("parse");
assert_eq!(parsed, original);
}
#[derive(GenlMessageDerive, Debug, Clone, PartialEq, Eq)]
#[genl_message(cmd = 9u8)]
struct DerivedOptional {
#[genl_attr(1u16)]
id: u32,
#[genl_attr(2u16)]
description: Option<String>,
#[genl_attr(3u16)]
priority: Option<u16>,
}
#[test]
fn derived_optional_omits_none_on_emit() {
let with_none = DerivedOptional {
id: 1,
description: None,
priority: None,
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
with_none.to_bytes(&mut builder).expect("emit");
let bytes = &builder.as_bytes()[body_start..];
let mut attrs_seen: Vec<u16> = Vec::new();
for (ty, _) in __rt::attr_iter(bytes) {
attrs_seen.push(ty);
}
assert_eq!(attrs_seen, vec![1u16]);
let parsed = DerivedOptional::from_bytes(bytes).expect("parse");
assert_eq!(parsed, with_none);
}
#[test]
fn derived_optional_round_trips_some() {
let original = DerivedOptional {
id: 42,
description: Some("hello".to_string()),
priority: Some(99),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed = DerivedOptional::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed, original);
}
use crate::macros::{GenlAttribute, GenlCommand};
#[derive(GenlCommand, Debug, Clone, Copy, PartialEq, Eq)]
#[genl_command(repr = "u8")]
enum TestCmd {
Unspec = 0,
Get = 2,
}
#[derive(GenlAttribute, Debug, Clone, Copy, PartialEq, Eq)]
#[genl_attribute(repr = "u16")]
enum TestAttr {
Id = 1,
Name = 2,
}
#[derive(GenlMessageDerive, Debug, Clone, PartialEq, Eq)]
#[genl_message(cmd = TestCmd::Get)]
struct TypedGetReq {
#[genl_attr(TestAttr::Id)]
id: u32,
#[genl_attr(TestAttr::Name)]
name: String,
}
use crate::macros::GenlEnum;
#[derive(GenlEnum, Debug, Clone, Copy, PartialEq, Eq)]
#[genl_enum(repr = "u32")]
enum TestMode {
Manual = 1,
Automatic = 2,
}
#[derive(GenlMessageDerive, Debug, Default, Clone, PartialEq, Eq)]
#[genl_message(cmd = 13u8)]
struct DerivedEnumField {
#[genl_attr(1u16)]
id: u32,
#[genl_attr(2u16, repr = "u32")]
mode: Option<TestMode>,
}
#[test]
fn derived_genl_enum_field_round_trips() {
let original = DerivedEnumField {
id: 7,
mode: Some(TestMode::Automatic),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed = DerivedEnumField::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn derived_genl_enum_missing_attr_yields_none() {
let parsed = DerivedEnumField::from_bytes(&[]).expect("parse");
assert_eq!(parsed.id, 0);
assert_eq!(parsed.mode, None);
}
use crate::macros::NetlinkAttrs as NetlinkAttrsDerive;
#[derive(NetlinkAttrsDerive, Debug, Default, Clone, PartialEq, Eq)]
struct DerivedNestedBlock {
#[genl_attr(1u16)]
device_id: u32,
#[genl_attr(2u16)]
label: String,
}
#[derive(GenlMessageDerive, Debug, Default, Clone, PartialEq, Eq)]
#[genl_message(cmd = 17u8)]
struct DerivedWithNested {
#[genl_attr(1u16)]
id: u32,
#[genl_attr(2u16, nested)]
parent: Option<DerivedNestedBlock>,
}
#[test]
fn derived_nested_group_round_trips() {
let original = DerivedWithNested {
id: 100,
parent: Some(DerivedNestedBlock {
device_id: 0xDEAD_BEEF,
label: "wg0".to_string(),
}),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed = DerivedWithNested::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn derived_nested_group_missing_attr_yields_none() {
let parsed = DerivedWithNested::from_bytes(&[]).expect("parse");
assert_eq!(parsed.id, 0);
assert_eq!(parsed.parent, None);
}
#[test]
fn nested_attrs_carry_nla_f_nested_flag_on_wire() {
let original = DerivedWithNested {
id: 1,
parent: Some(DerivedNestedBlock {
device_id: 7,
label: String::new(),
}),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let bytes = &builder.as_bytes()[body_start..];
let first_len = u16::from_ne_bytes([bytes[0], bytes[1]]) as usize;
let aligned_first = (first_len + 3) & !3;
let second_type =
u16::from_ne_bytes([bytes[aligned_first + 2], bytes[aligned_first + 3]]);
assert_eq!(
second_type & 0x8000,
0x8000,
"NLA_F_NESTED flag missing on wire (got 0x{second_type:04x})"
);
assert_eq!(
second_type & 0x7FFF,
2,
"wrong masked-off attr type (got 0x{second_type:04x})"
);
}
#[derive(GenlMessageDerive, Debug, Default, Clone, PartialEq, Eq)]
#[genl_message(cmd = 14u8)]
struct DerivedRepeatedEnum {
#[genl_attr(1u16)]
id: u32,
#[genl_attr(2u16, repr = "u32")]
modes_supported: Vec<TestMode>,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct TestCapabilities: u32 {
const READ = 0b0000_0001;
const WRITE = 0b0000_0010;
const EXECUTE = 0b0000_0100;
}
}
#[derive(GenlMessageDerive, Debug, Clone, PartialEq, Eq)]
#[genl_message(cmd = 15u8)]
struct DerivedBitflagsField {
#[genl_attr(1u16)]
id: u32,
#[genl_attr(2u16, bitflags = "u32")]
caps: TestCapabilities,
}
#[test]
fn derived_bitflags_field_round_trips_combined_bits() {
let original = DerivedBitflagsField {
id: 11,
caps: TestCapabilities::READ | TestCapabilities::EXECUTE,
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed = DerivedBitflagsField::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn derived_bitflags_field_missing_attr_yields_empty_flags() {
let parsed = DerivedBitflagsField::from_bytes(&[]).expect("parse");
assert_eq!(parsed.id, 0);
assert_eq!(parsed.caps, TestCapabilities::empty());
}
#[test]
fn derived_bitflags_field_preserves_unknown_bits() {
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
__rt::emit_u32_attr(&mut builder, 2, 0x80 | TestCapabilities::READ.bits());
let parsed = DerivedBitflagsField::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed.caps.bits(), 0x80 | TestCapabilities::READ.bits());
assert!(parsed.caps.contains(TestCapabilities::READ));
}
#[test]
fn derived_repeated_enum_round_trips_multiple_elements() {
let original = DerivedRepeatedEnum {
id: 42,
modes_supported: vec![TestMode::Manual, TestMode::Automatic],
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed =
DerivedRepeatedEnum::from_bytes(&builder.as_bytes()[body_start..]).expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn derived_repeated_enum_empty_vec_emits_no_attrs() {
let original = DerivedRepeatedEnum {
id: 1,
modes_supported: Vec::new(),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let bytes = &builder.as_bytes()[body_start..];
let mut attrs_seen: Vec<u16> = Vec::new();
for (ty, _) in __rt::attr_iter(bytes) {
attrs_seen.push(ty);
}
assert_eq!(attrs_seen, vec![1u16]);
let parsed = DerivedRepeatedEnum::from_bytes(bytes).expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn derived_genl_enum_unknown_value_surfaces_as_error() {
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
__rt::emit_u32_attr(&mut builder, 2, 99); let result = DerivedEnumField::from_bytes(&builder.as_bytes()[body_start..]);
let err = result.expect_err("parse should fail on unknown enum value");
let s = format!("{err}");
assert!(s.contains("99"), "expected error text to include 99: {s}");
}
#[test]
fn typed_enums_compose_with_message_derive() {
assert_eq!(TypedGetReq::CMD, 2);
let original = TypedGetReq {
id: 7,
name: "device-0".to_string(),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed = TypedGetReq::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed, original);
}
#[derive(GenlMessageDerive, Debug, Clone, PartialEq, Eq)]
#[genl_message(cmd = 5u8)]
struct DerivedBytes {
#[genl_attr(1u16)]
payload: Vec<u8>,
}
#[derive(GenlMessageDerive, Debug, Default, Clone, PartialEq, Eq)]
#[genl_message(cmd = 11u8)]
struct DerivedSigned {
#[genl_attr(1u16)]
temp_mdeg: i32,
#[genl_attr(2u16)]
delta: Option<i32>,
}
#[test]
fn derived_i32_round_trips_positive_and_negative() {
let original = DerivedSigned {
temp_mdeg: 47_500,
delta: Some(-12_345),
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed = DerivedSigned::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed, original);
let parsed = DerivedSigned::from_bytes(&[]).expect("parse");
assert_eq!(parsed.temp_mdeg, 0);
assert_eq!(parsed.delta, None);
}
#[test]
fn signed_int_runtime_helpers_round_trip_negatives() {
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
__rt::emit_i32_attr(&mut b, 50, -98_765);
let bytes = &b.as_bytes()[start..];
let mut found = None;
for (ty, payload) in __rt::attr_iter(bytes) {
if ty == 50 {
found = Some(__rt::parse_i32_attr(payload).unwrap());
}
}
assert_eq!(found, Some(-98_765));
}
#[test]
fn derived_vec_u8_round_trips() {
let original = DerivedBytes {
payload: vec![0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01, 0x02],
};
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
original.to_bytes(&mut builder).expect("emit");
let parsed = DerivedBytes::from_bytes(&builder.as_bytes()[body_start..])
.expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn derived_from_bytes_fills_defaults_for_missing_attrs() {
let parsed = DerivedSimple::from_bytes(&[]).expect("parse");
assert_eq!(parsed.id, 0);
assert_eq!(parsed.label, "");
}
#[test]
fn derived_from_bytes_skips_unknown_attrs() {
let mut builder = MessageBuilder::new(0, 0);
let body_start = builder.len();
__rt::emit_u32_attr(&mut builder, 1, 42);
__rt::emit_u32_attr(&mut builder, 99, 0xDEAD_BEEF);
let parsed =
DerivedSimple::from_bytes(&builder.as_bytes()[body_start..]).expect("parse");
assert_eq!(parsed.id, 42);
assert_eq!(parsed.label, "");
}
use crate::macros::genl_family;
use crate::netlink::construction::AsyncConstructible;
use crate::netlink::{AsyncProtocolInit, Protocol, ProtocolState};
#[genl_family(name = "my_family", version = 1)]
pub struct MyFamily;
#[test]
fn genl_family_macro_generates_constants() {
assert_eq!(MyFamily::NAME, "my_family");
assert_eq!(MyFamily::VERSION, 1);
}
#[test]
fn genl_family_macro_default_constructs_with_zero_family_id() {
let f = MyFamily::default();
assert_eq!(f.family_id(), 0);
}
#[test]
fn genl_family_macro_implements_protocol_state() {
const _: () = {
assert!(matches!(MyFamily::PROTOCOL, Protocol::Generic));
};
}
fn assert_async_constructible<P: AsyncConstructible>() {}
fn assert_async_protocol_init<P: AsyncProtocolInit>() {}
#[test]
fn genl_family_macro_satisfies_trait_bounds_required_by_connection() {
assert_async_constructible::<MyFamily>();
assert_async_protocol_init::<MyFamily>();
}
#[test]
fn genl_family_macro_provides_debug() {
let f = MyFamily::default();
let s = format!("{f:?}");
assert!(s.contains("MyFamily"));
assert!(s.contains("my_family"));
assert!(s.contains("version"));
assert!(s.contains("family_id"));
}
#[genl_family(name = "second_family", version = 2)]
pub struct SecondFamily;
#[test]
fn two_macro_defined_families_coexist() {
assert_eq!(SecondFamily::NAME, "second_family");
assert_eq!(SecondFamily::VERSION, 2);
assert_async_constructible::<SecondFamily>();
}
}