carrier 0.12.2

carrier is a generic secure message system for IoT
use endpoint;
use headers;
use headers::Headers;
use identity;
use nix::sys::statvfs::statvfs;
use nix::sys::utsname::uname;
use proto;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use std::time::{SystemTime, UNIX_EPOCH};

macro_rules! tryo {
    ($i:expr, $x:expr) => {
        match $x {
            Some(v) => v,
            None => {
                warn!("sysinfo: {}, unwrapped None", $i);
                return Default::default();
            }
        }
    };
}

macro_rules! trye {
    ($i:expr, $x:expr) => {
        match $x {
            Ok(v) => v,
            Err(e) => {
                warn!("sysinfo: {}, {}", $i, e);
                return Default::default();
            }
        }
    };
}

fn loadavg() -> Option<proto::Load> {
    let mut f = trye!(1, File::open("/proc/loadavg"));
    let mut s = String::new();
    trye!(1, f.read_to_string(&mut s));
    let mut s = s.split_whitespace();

    let avg_1 = trye!(1, tryo!(1, s.next()).parse());
    let avg_5 = trye!(1, tryo!(1, s.next()).parse());
    let avg_15 = trye!(1, tryo!(1, s.next()).parse());
    let mut proc = tryo!(1, s.next()).split("/");

    let mut f = trye!(1, File::open("/proc/uptime"));
    let mut s = String::new();
    trye!(1, f.read_to_string(&mut s));
    let mut s = s.split_whitespace();

    let uptime = trye!(1, tryo!(1, s.next()).parse::<f64>()) as u64;
    let idletime = trye!(1, tryo!(1, s.next()).parse::<f64>()) as u64;

    Some(proto::Load {
        avg_1,
        avg_5,
        avg_15,
        clock: trye!(1, SystemTime::now().duration_since(UNIX_EPOCH)).as_secs(),
        uptime,
        idletime,
        proc_active: trye!(1, tryo!(1, proc.next()).parse()),
        proc_total: trye!(1, tryo!(1, proc.next()).parse()),
        cores: num_cpus::get_physical() as u32,
        threads: num_cpus::get() as u32,
    })
}

fn mem() -> Option<proto::Mem> {
    let f = trye!(3, File::open("/proc/meminfo"));
    let mut total = 0;
    let mut free = 0;
    let mut available = 0;
    let f = BufReader::new(f);
    for line in f.lines() {
        let line = trye!(3, line);
        let mut line = line.split(":");
        match line.next().map(|v| v.trim()) {
            Some("MemTotal") => {
                total = trye!(3, tryo!(3, tryo!(3, line.next()).split_whitespace().next()).parse());
            }
            Some("MemFree") => {
                free = trye!(3, tryo!(3, tryo!(3, line.next()).split_whitespace().next()).parse());
            }
            Some("MemAvailable") => {
                available = trye!(3, tryo!(3, tryo!(3, line.next()).split_whitespace().next()).parse());
            }
            _ => (),
        }
    }

    Some(proto::Mem { total, free, available })
}



fn fs_(r: &mut Vec<proto::FileSystem>, fname: &str) {
    if let Ok(s) = statvfs(fname) {
        r.push(proto::FileSystem {
            path:      fname.into(),
            blocksize: s.block_size().into(),
            total:     s.blocks().into(),
            free:      s.blocks_free().into(),
        });
    }
}

fn fs() -> Option<Vec<proto::FileSystem>> {
    let mut r = Vec::new();
    fs_(&mut r, "/");

    #[cfg(feature = "uefi")]
    for fname in ["/home/user"].into_iter() {
        fs_(&mut r, *fname);
    }

    #[cfg(target_os = "android")]
    for fname in ["/system", "/cache", "/data"].into_iter() {
        fs_(&mut r, *fname);
    }

    Some(r)
}



fn fromsysfile<E: std::fmt::Display, T : std::str::FromStr<Err=E>>(f: String) -> Result<T, String> {
    let mut f = File::open(f).map_err(|e|format!("{}", e))?;
    let mut buf = String::new();
    f.read_to_string(&mut buf).map_err(|e|format!("{}", e))?;
    buf.trim().parse().map_err(|e|format!("{}", e))
}

fn net_line(line: String) -> Option<proto::Netdev> {
    let mut line = line.split_whitespace();
    let name = tryo!(51, line.next());
    let name = name.replace(":", "").trim().to_string();

    tryo!(5, line.next());
    let rx_pkt = match tryo!(52, line.next()).parse() {
        Ok(v) => v,
        Err(_) => return None,
    };
    let rx_err = match tryo!(52, line.next()).parse() {
        Ok(v) => v,
        Err(_) => return None,
    };
    tryo!(5, line.next());
    tryo!(5, line.next());
    tryo!(5, line.next());
    tryo!(5, line.next());
    tryo!(5, line.next());
    tryo!(5, line.next());
    let tx_pkt = trye!(53, tryo!(53, line.next()).parse());
    let tx_err = trye!(53, tryo!(53, line.next()).parse());

    let mtu: u32            = trye!(551,fromsysfile(format!("/sys/class/net/{}/mtu", name)));
    let macaddr: String     = trye!(552,fromsysfile(format!("/sys/class/net/{}/address", name)));
    let operstate: String   = trye!(553,fromsysfile(format!("/sys/class/net/{}/operstate", name)));
    let up: usize           = trye!(554,fromsysfile(format!("/sys/class/net/{}/carrier", name)));

    let link_changes: u64   = fromsysfile(format!("/sys/class/net/{}/carrier_changes", name)).unwrap_or(0);
    let link_speed: u64     = fromsysfile(format!("/sys/class/net/{}/speed", name)).unwrap_or(0);

    let link_duplex: String = fromsysfile(format!("/sys/class/net/{}/duplex", name)).unwrap_or(String::new());
    let link_duplex = match link_duplex.trim() {
        "half" => proto::netdev::Duplex::Half,
        "full" => proto::netdev::Duplex::Full,
        _ => proto::netdev::Duplex::Invalid,
    }
    .into();

    let mut addrs = Vec::new();

    #[cfg(not(target_os = "android"))]
    for ifaddr in trye!(56, nix::ifaddrs::getifaddrs()) {
        if let (
            Some(nix::sys::socket::SockAddr::Inet(addr)),
            Some(nix::sys::socket::SockAddr::Inet(mask)),
            Some(nix::sys::socket::SockAddr::Inet(broadcast)),
        ) = (ifaddr.address, ifaddr.netmask, ifaddr.broadcast)
        {
            addrs.push(proto::NetAddress {
                addr:      format!("{}", addr),
                mask:      format!("{}", mask),
                broadcast: format!("{}", broadcast),
            });
        }
    }

    Some(proto::Netdev {
        name,
        rx_pkt,
        rx_err,
        tx_pkt,
        tx_err,
        up: up > 0,
        macaddr,
        mtu,
        addrs,
        link: operstate == "up",
        link_changes,
        link_speed,
        link_duplex,
    })
}

fn net() -> Option<Vec<proto::Netdev>> {
    let mut r = Vec::new();

    let f = trye!(6, File::open("/proc/net/dev"));
    let f = BufReader::new(f);
    for line in f.lines() {
        let line = trye!(6, line);
        if let Some(d) = net_line(line) {
            r.push(d);
        }
    }

    Some(r)
}

pub fn sysinfo(
    _poll: osaka::Poll,
    _headers: headers::Headers,
    _: &identity::Identity,
    mut stream: endpoint::Stream,
) -> Option<osaka::Task<()>> {
    stream.send(Headers::ok().encode());


    let uts = uname();
    let mut sysinfo = proto::Sysinfo {
        uname:            Some(proto::Uname {
            sysname:  uts.sysname().to_string(),
            nodename: uts.nodename().to_string(),
            release:  uts.release().to_string(),
            version:  uts.version().to_string(),
            machine:  uts.machine().to_string(),
        }),
        load:             loadavg(),
        firmware:         None,
        mem:              mem(),
        fs:               fs().unwrap_or(Vec::new()),
        net:              net().unwrap_or(Vec::new()),
        switch:           Vec::new(),
        board_id:         String::new(),
        carrier_build_id: super::super::BUILD_ID.into(),
        belltower:        None,
        dualboot:         None,
    };

    #[cfg(feature = "uefi")]
    {
        use super::super::smbios;
        if let Ok((et, sts)) = smbios::stream() {
            let mut board = String::new();
            for st in sts {
                if st.header.handle == 0x01 {
                    if !st.strings[0].contains("To Be Filled") || st.strings[0].trim().is_empty(){
                        board = st.strings[0].clone();
                        if !st.strings[1].trim().is_empty() {
                            board += "-";
                            board += &st.strings[1].trim();
                        }
                        if !st.strings[2].trim().is_empty() {
                            board += "-";
                            board += &st.strings[2].trim();
                        }
                    }
                }
                if st.header.handle == 0x02 {
                    if board.is_empty() {
                        board = st.strings[0].clone();
                        if !st.strings[1].trim().is_empty() {
                            board += "-";
                            board += &st.strings[1].trim();
                        }
                        if !st.strings[2].trim().is_empty() {
                            board += "-";
                            board += &st.strings[2].trim();
                        }
                    }
                }
            }
            sysinfo.board_id = String::from("pc-") + &board.replace(" ", "_");
        }

    }
    #[cfg(feature = "openwrt")]
    {
        let board           = super::openwrt::load_board_json();
        sysinfo.firmware    = super::openwrt::firmware();
        sysinfo.switch      = super::openwrt::switch(&board).unwrap_or(Vec::new());
        sysinfo.board_id    = board.map(|board| board.model.id.clone()).unwrap_or(String::new());
    }
    #[cfg(target_os = "android")]
    {
        sysinfo.firmware    = super::android::firmware();
        if let Some(fw) = &sysinfo.firmware {
            sysinfo.board_id    = fw.board.clone();
        }
    }

    if sysinfo.firmware.is_none() {
        let filename = "/etc/fusion-release";

        let mut buffer = String::default();
        if let Ok(mut f) = File::open(&filename) {
            f.read_to_string(&mut buffer).expect(&format!("cannot read {:?}", filename));
            if let Ok(dr) = toml::from_str::<DistroRelease>(&buffer) {
                sysinfo.firmware = Some(proto::Firmware{
                    board:      dr.BOARD_ID.unwrap_or(sysinfo.board_id.clone()),
                    distro:     dr.DISTRIB_ID.unwrap_or(String::new()),
                    release:    dr.DISTRIB_RELEASE.unwrap_or(String::new()),
                    revision:   dr.DISTRIB_REVISION.unwrap_or(String::new()),
                    .. proto::Firmware::default()
                });

                #[cfg(feature = "uefi")]
                {
                    if let Some(dualboot) = dr.dualboot {
                        use super::super::cluproccmdline;
                        use cluproccmdline::Cmdline;

                        let mut active_side = String::new();
                        let mut cmdline = cluproccmdline::this_machine().unwrap();
                        for (name, value) in cmdline.iter() {
                            if let Some(b"root") = name {
                                if value == dualboot.a.disk.as_bytes() {
                                    active_side = "a".to_string();
                                } else if value == dualboot.b.disk.as_bytes() {
                                    active_side = "b".to_string();
                                }
                            }
                        }
                        sysinfo.dualboot = Some(proto::Dualboot{
                            active_side,
                        });
                    }
                }

                if let Ok(mut f) = File::open("/home/user/.belltower.hash.current") {
                    let mut bt = proto::Belltower{
                        current: String::new(),
                        previous: String::new(),
                    };
                    f.read_to_string(&mut bt.current).ok();
                    if let Ok(mut f) = File::open("/home/user/.belltower.hash.previous") {
                        f.read_to_string(&mut bt.previous).ok();
                    }
                    sysinfo.belltower = Some(bt);
                }
            }
        }
    }

    stream.message(sysinfo);
    None
}



#[derive(Deserialize)]
pub struct DualbootSide{
    pub disk: String,
}
#[derive(Deserialize)]
pub struct Dualboot{
    pub a: DualbootSide,
    pub b: DualbootSide,
    pub boot: String,
}

#[derive(Deserialize)]
#[allow(non_snake_case)]
pub struct DistroRelease{
    pub DISTRIB_ID:         Option<String>,
    pub DISTRIB_RELEASE:    Option<String>,
    pub DISTRIB_REVISION:   Option<String>,
    pub BOARD_ID:           Option<String>,

    pub dualboot:           Option<Dualboot>,
}