zkteco 1.0.0

A pure-Rust client for ZKTeco biometric attendance / access-control terminals (TCP/UDP protocol). A faithful port of the Python `pyzk` library.
Documentation
//! Reachability checks used before opening a session.
//!
//! Mirrors `pyzk`'s `ZK_helper`: an ICMP ping (shelled out to the OS `ping`
//! binary, exactly like `pyzk`) and a TCP connect probe.

use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::time::Duration;

/// Lightweight reachability helper for a device address.
pub struct ZkHelper {
    ip: String,
    port: u16,
}

impl ZkHelper {
    /// Create a helper for `ip:port`.
    pub fn new(ip: impl Into<String>, port: u16) -> Self {
        ZkHelper {
            ip: ip.into(),
            port,
        }
    }

    /// Return `true` if the host answers a single ICMP echo request.
    ///
    /// Like `pyzk`, this shells out to the platform `ping` command so it works
    /// without elevated privileges (raw ICMP sockets usually require root).
    pub fn test_ping(&self) -> bool {
        use std::process::{Command, Stdio};

        let is_windows = cfg!(target_os = "windows");
        let mut cmd = Command::new("ping");
        if is_windows {
            // -n 1 : one echo; -w 5000 : 5s timeout (ms)
            cmd.args(["-n", "1", "-w", "5000", &self.ip]);
        } else {
            // -c 1 : one echo; -W 5 : 5s timeout (s)
            cmd.args(["-c", "1", "-W", "5", &self.ip]);
        }
        cmd.stdout(Stdio::null())
            .stderr(Stdio::null())
            .status()
            .map(|s| s.success())
            .unwrap_or(false)
    }

    /// Attempt a TCP connection. Returns `true` if the port is open.
    ///
    /// Used to auto-detect that a device speaks the newer "ZK8" TCP variant.
    pub fn test_tcp(&self) -> bool {
        match self.address() {
            Some(addr) => TcpStream::connect_timeout(&addr, Duration::from_secs(10)).is_ok(),
            None => false,
        }
    }

    /// Resolve the configured `ip:port` to a single socket address.
    fn address(&self) -> Option<SocketAddr> {
        (self.ip.as_str(), self.port)
            .to_socket_addrs()
            .ok()
            .and_then(|mut it| it.next())
    }
}