// SPDX-License-Identifier: MIT

//! Example of listing generic families based on `netlink_proto`
//!
//! This example's functionality is same as the identical name example in
//! `netlink_packet_generic`. But this example shows you the usage of this crate
//! to run generic netlink protocol asynchronously.

use anyhow::{bail, Error};
use futures::StreamExt;
use genetlink::new_connection;
use netlink_packet_core::{
    NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST,
};
use netlink_packet_generic::{
    ctrl::{nlas::GenlCtrlAttrs, GenlCtrl, GenlCtrlCmd},
    GenlMessage,
};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let mut nl_hdr = NetlinkHeader::default();
    nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP;
    let nlmsg = NetlinkMessage::new(
        nl_hdr,
        GenlMessage::from_payload(GenlCtrl {
            cmd: GenlCtrlCmd::GetFamily,
            nlas: vec![],
        })
        .into(),
    );
    let (conn, mut handle, _) = new_connection()?;
    tokio::spawn(conn);

    let mut responses = handle.request(nlmsg).await?;

    while let Some(result) = responses.next().await {
        let resp = result?;
        match resp.payload {
            NetlinkPayload::InnerMessage(genlmsg) => {
                if genlmsg.payload.cmd == GenlCtrlCmd::NewFamily {
                    print_entry(genlmsg.payload.nlas);
                }
            }
            NetlinkPayload::Error(err) => {
                eprintln!("Received a netlink error message: {err:?}");
                bail!(err);
            }
            _ => {}
        }
    }

    Ok(())
}

fn print_entry(entry: Vec<GenlCtrlAttrs>) {
    let family_id = entry
        .iter()
        .find_map(|nla| {
            if let GenlCtrlAttrs::FamilyId(id) = nla {
                Some(*id)
            } else {
                None
            }
        })
        .expect("Cannot find FamilyId attribute");
    let family_name = entry
        .iter()
        .find_map(|nla| {
            if let GenlCtrlAttrs::FamilyName(name) = nla {
                Some(name.as_str())
            } else {
                None
            }
        })
        .expect("Cannot find FamilyName attribute");
    let version = entry
        .iter()
        .find_map(|nla| {
            if let GenlCtrlAttrs::Version(ver) = nla {
                Some(*ver)
            } else {
                None
            }
        })
        .expect("Cannot find Version attribute");
    let hdrsize = entry
        .iter()
        .find_map(|nla| {
            if let GenlCtrlAttrs::HdrSize(hdr) = nla {
                Some(*hdr)
            } else {
                None
            }
        })
        .expect("Cannot find HdrSize attribute");

    if hdrsize == 0 {
        println!("0x{family_id:04x} {family_name} [Version {version}]");
    } else {
        println!(
            "0x{family_id:04x} {family_name} [Version {version}] \
            [Header {hdrsize} bytes]"
        );
    }
}
