crdhcpc 0.1.1

Standalone DHCP Client for Linux with DHCPv4, DHCPv6, PXE, and Dynamic DNS support
Documentation
//! Protocol layer for Unix socket communication
//!
//! Implements JSON-RPC 2.0 over Unix sockets with the following components:
//! - JSON-RPC message types (request, response, notification)
//! - Length-prefixed message framing
//! - Unix socket client and server
//! - Peer credential verification (SO_PEERCRED)
//! - Error handling

pub mod jsonrpc;
pub mod framing;
pub mod client;
pub mod server;
pub mod credentials;
pub mod error;
pub mod mock;

// Re-export commonly used types
pub use jsonrpc::{
    JsonRpcRequest, JsonRpcResponse, JsonRpcError, JsonRpcErrorResponse,
    JsonRpcMessage, JsonRpcNotification, JsonRpcBatch, RequestId, error_codes,
};
pub use framing::{
    encode_message, decode_message, send_message, recv_message, write_frame,
    FramedMessage, MessageFraming,
};
pub use client::{UnixClient, ClientConfig};
pub use server::{UnixServer, ServerConfig, ConnectionHandler};
pub use credentials::{PeerCredentials, verify_peer_credentials};
pub use error::{ProtocolError, ProtocolResult};
pub use mock::MockDaemonClient;

use serde_json::Value;

/// Daemon client that can be either real (Unix socket) or mock (in-memory)
pub enum DaemonClient {
    /// Real client communicating over Unix socket
    Real(UnixClient),
    /// Mock client with in-memory state (for development)
    Mock(MockDaemonClient),
}

impl DaemonClient {
    /// Create a real client with the given socket path
    pub fn real(socket_path: impl Into<String>) -> Self {
        Self::Real(UnixClient::with_socket_path(socket_path))
    }

    /// Create a real client with custom configuration
    pub fn real_with_config(config: ClientConfig) -> Self {
        Self::Real(UnixClient::new(config))
    }

    /// Create a mock client for development
    pub fn mock() -> Self {
        Self::Mock(MockDaemonClient::new())
    }

    /// Create a client based on environment or configuration
    /// If MOCK_DAEMON=true, creates a mock client
    /// Otherwise creates a real client with the given socket path
    pub fn from_env(socket_path: impl Into<String>) -> Self {
        if std::env::var("MOCK_DAEMON")
            .unwrap_or_else(|_| "false".to_string())
            .parse::<bool>()
            .unwrap_or(false)
        {
            Self::mock()
        } else {
            Self::real(socket_path)
        }
    }

    /// Connect to the daemon (no-op for mock client)
    pub async fn connect(&self) -> ProtocolResult<()> {
        match self {
            Self::Real(client) => client.connect().await,
            Self::Mock(_) => Ok(()), // Mock is always "connected"
        }
    }

    /// Make a JSON-RPC call
    pub async fn call(
        &self,
        method: impl Into<String>,
        params: Option<Value>,
    ) -> ProtocolResult<Value> {
        match self {
            Self::Real(client) => client.call(method, params).await,
            Self::Mock(client) => client.call(method, params).await,
        }
    }

    /// Send a notification (no response expected)
    pub async fn notify(
        &self,
        method: impl Into<String>,
        params: Option<Value>,
    ) -> ProtocolResult<()> {
        match self {
            Self::Real(client) => client.notify(method, params).await,
            Self::Mock(_) => Ok(()), // Mock accepts notifications
        }
    }

    /// Call with automatic retry (only for real client)
    pub async fn call_with_retry(
        &self,
        method: impl Into<String> + Clone,
        params: Option<Value>,
    ) -> ProtocolResult<Value> {
        match self {
            Self::Real(client) => client.call_with_retry(method, params).await,
            Self::Mock(client) => client.call(method, params).await,
        }
    }

    /// Check if this is a mock client
    pub fn is_mock(&self) -> bool {
        matches!(self, Self::Mock(_))
    }

    /// Disconnect from the daemon (no-op for mock client)
    pub async fn disconnect(&self) -> ProtocolResult<()> {
        match self {
            Self::Real(client) => client.disconnect().await,
            Self::Mock(_) => Ok(()),
        }
    }
}

/// Protocol version constant
pub const PROTOCOL_VERSION: &str = "2.0";

/// Maximum message size (16 MB)
pub const MAX_MESSAGE_SIZE: usize = 16 * 1024 * 1024;

/// Default socket path for daemon
pub const DEFAULT_SOCKET_PATH: &str = "/var/run/crrouterd.sock";