brlapi 0.4.1

Safe Rust bindings for the BrlAPI library
// SPDX-License-Identifier: LGPL-2.1

//! Raw mode operations for direct braille device communication
//!
//! **DANGER: Raw mode provides direct hardware access to braille devices**
//!
//! Raw mode allows applications to communicate directly with braille hardware,
//! bypassing BRLTTY's normal text processing and translation. This is primarily
//! intended for:
//!
//! - **Firmware updates** and device flashing
//! - **Hardware diagnostics** and testing
//! - **Driver development** and debugging
//! - **File transfers** to/from device storage
//!
//! ## Safety Warnings
//!
//! **CRITICAL**: Misuse of raw mode can:
//! - **Permanently damage** your braille device
//! - **Lock the device** requiring hard reset
//! - **Corrupt device firmware** making it unusable
//! - **Void your warranty** through unauthorized access
//!
//! **Always**:
//! - Know your device's protocol before using raw mode
//! - Test with a development/backup device first
//! - Have recovery procedures ready
//! - Verify driver support before entering raw mode
//!
//! ## Basic Usage
//!
//! ```no_run
//! use brlapi::{Connection, raw::RawMode};
//! use std::time::Duration;
//!
//! fn firmware_update_example() -> Result<(), brlapi::BrlApiError> {
//!     let connection = Connection::open()?;
//!
//!     // Get and verify driver supports raw mode
//!     let driver = connection.display_driver()?;
//!     if !RawMode::is_driver_supported(&driver) {
//!         return Err(brlapi::BrlApiError::custom("Driver doesn't support raw mode"));
//!     }
//!
//!     // Enter raw mode (RAII ensures automatic exit)
//!     let raw_mode = RawMode::enter(&connection, &driver)?;
//!
//!     // Send firmware chunk
//!     let firmware_data = b"\x01\x02\x03\x04"; // Example data
//!     let bytes_sent = raw_mode.send_data(firmware_data)?;
//!     println!("Sent {} bytes to device", bytes_sent);
//!
//!     // Receive acknowledgment (blocking)
//!     let mut response = [0u8; 64];
//!     let bytes_received = raw_mode.receive_data(&mut response)?;
//!     println!("Received {} bytes: {:?}", bytes_received, &response[..bytes_received]);
//!
//!     // Raw mode automatically exited when raw_mode is dropped
//!     Ok(())
//! }
//! ```
//!
//! ## Supported Drivers
//!
//! Not all braille device drivers support raw mode. Common drivers that do:
//! - Most USB-connected devices
//! - Serial/Bluetooth devices with direct protocols
//! - Development/testing drivers
//!
//! Drivers that typically don't support raw mode:
//! - Network-based drivers
//! - Virtual/dummy drivers
//! - Some older legacy drivers

use crate::{Result, brlapi_call, connection::Connection, error::BrlApiError};
use brlapi_sys::*;
use std::ffi::CString;

/// Raw mode session providing direct hardware access to braille devices
///
/// This struct uses RAII to ensure raw mode is properly entered and exited.
/// Raw mode gives exclusive access to the braille device hardware, bypassing
/// BRLTTY's normal text processing.
///
/// **WARNING**: Raw mode can permanently damage devices if used incorrectly.
/// Only use this for firmware updates, diagnostics, or driver development.
///
/// # Example
/// ```no_run
/// use brlapi::{Connection, raw::RawMode};
///
/// let connection = Connection::open()?;
/// let driver = connection.display_driver()?;
///
/// // Verify driver support before entering raw mode
/// if RawMode::is_driver_supported(&driver) {
///     let raw_mode = RawMode::enter(&connection, &driver)?;
///     // ... perform raw operations
///     // Raw mode automatically exited when dropped
/// }
/// # Ok::<(), brlapi::BrlApiError>(())
/// ```
#[derive(Debug)]
pub struct RawMode<'a> {
    connection: &'a Connection,
    driver_name: String,
}

impl<'a> RawMode<'a> {
    /// Enter raw mode for direct hardware communication
    ///
    /// This gives the application exclusive access to the braille device hardware,
    /// bypassing all of BRLTTY's normal processing. Only one client can be in
    /// raw mode at a time.
    ///
    /// **DANGER**: Raw mode can damage your device. See module documentation.
    ///
    /// # Arguments
    /// * `connection` - Active BrlAPI connection
    /// * `driver` - Driver name (should match `connection.display_driver()`)
    ///
    /// # Errors
    /// * `Custom` - Driver doesn't support raw mode, or raw mode already in use
    /// * `DeviceBusy` - Another client is already in raw mode
    /// * `ConnectionRefused` - Device communication failed
    ///
    /// # Example
    /// ```no_run
    /// use brlapi::{Connection, raw::RawMode};
    ///
    /// let connection = Connection::open()?;
    /// let driver = connection.display_driver()?;
    /// let raw_mode = RawMode::enter(&connection, &driver)?;
    /// // Raw mode active - can send/receive raw data
    /// # Ok::<(), brlapi::BrlApiError>(())
    /// ```
    pub fn enter(connection: &'a Connection, driver: &str) -> Result<Self> {
        // Validate driver name
        if driver.is_empty() {
            return Err(BrlApiError::custom("Driver name cannot be empty"));
        }

        // Convert driver name to C string
        let c_driver = CString::new(driver).map_err(|e| BrlApiError::NullByteInString(e))?;

        // Enter raw mode using handle-based API
        // SAFETY: connection.handle_ptr() returns a valid handle from successful connection.
        // c_driver.as_ptr() points to valid null-terminated C string from CString::new().
        brlapi_call!(unsafe { brlapi__enterRawMode(connection.handle_ptr(), c_driver.as_ptr()) })?;

        Ok(RawMode {
            connection,
            driver_name: driver.to_string(),
        })
    }

    /// Check if a driver supports raw mode operations
    ///
    /// This provides a best-effort check based on known driver capabilities.
    /// Even if this returns `true`, raw mode entry might still fail if the
    /// specific device doesn't support it.
    ///
    /// # Arguments
    /// * `driver_name` - Name of the braille driver to check
    ///
    /// # Returns
    /// `true` if the driver is known to support raw mode, `false` otherwise
    ///
    /// # Example
    /// ```no_run
    /// use brlapi::{Connection, raw::RawMode};
    ///
    /// let connection = Connection::open()?;
    /// let driver = connection.display_driver()?;
    ///
    /// if RawMode::is_driver_supported(&driver) {
    ///     println!("Driver {} supports raw mode", driver);
    ///     let raw_mode = RawMode::enter(&connection, &driver)?;
    /// } else {
    ///     println!("Driver {} does not support raw mode", driver);
    /// }
    /// # Ok::<(), brlapi::BrlApiError>(())
    /// ```
    pub fn is_driver_supported(driver_name: &str) -> bool {
        if driver_name.is_empty() {
            return false;
        }

        match driver_name.to_lowercase().as_str() {
            // Known drivers that support raw mode
            "alva" | "baum" | "braillepen" | "braillelite" | "braillememo" | "braillenote"
            | "canbraille" | "cebra" | "ec" | "eureka" | "freedomscientific" | "handy" | "hedo"
            | "hims" | "ht" | "humanware" | "iris" | "logtext" | "md" | "mn" | "mt" | "pegasus"
            | "pm" | "seika" | "voyager" | "vr" | "vs" => true,

            // Drivers that typically don't support raw mode
            "noblraille" | "fake" | "dummy" | "test" => false,

            // For unknown drivers, assume they might support it
            // User will get a proper error if they don't
            _ => true,
        }
    }

    /// Send raw data directly to the braille device
    ///
    /// This bypasses all BRLTTY processing and sends data directly to the
    /// hardware. The exact format depends on the device protocol.
    ///
    /// **WARNING**: Sending incorrect data can damage the device.
    ///
    /// # Arguments
    /// * `data` - Raw bytes to send to the device
    ///
    /// # Returns
    /// Number of bytes successfully sent
    ///
    /// # Errors
    /// * `Custom` - Device rejected the data or communication failed
    /// * `IllegalInstruction` - Not currently in raw mode
    ///
    /// # Example
    /// ```no_run
    /// use brlapi::{Connection, raw::RawMode};
    ///
    /// let connection = Connection::open()?;
    /// let driver = connection.display_driver()?;
    /// let raw_mode = RawMode::enter(&connection, &driver)?;
    ///
    /// // Send device-specific command (example)
    /// let command = b"\x01\x05\x10"; // Hypothetical command
    /// let bytes_sent = raw_mode.send_data(command)?;
    /// println!("Sent {} bytes", bytes_sent);
    /// # Ok::<(), brlapi::BrlApiError>(())
    /// ```
    pub fn send_data(&self, data: &[u8]) -> Result<usize> {
        if data.is_empty() {
            return Ok(0);
        }

        // SAFETY: self.connection.handle_ptr() is valid (raw mode successfully entered).
        // data.as_ptr() points to valid slice memory, data.len() provides correct size.
        // Cast to c_void is safe for transmitting raw bytes.
        let result = brlapi_call!(unsafe {
            brlapi__sendRaw(
                self.connection.handle_ptr(),
                data.as_ptr() as *const std::ffi::c_void,
                data.len(),
            )
        })?;

        Ok(result as usize)
    }

    /// Receive raw data from the braille device
    ///
    /// This receives data directly from the hardware without any BRLTTY
    /// processing. The data format depends on the device protocol.
    ///
    /// **Note**: This function may block indefinitely if no data is available.
    /// Consider implementing external timeout handling if needed.
    ///
    /// # Arguments
    /// * `buffer` - Buffer to store received data
    ///
    /// # Returns
    /// Number of bytes actually received (may be less than buffer size)
    ///
    /// # Errors
    /// * `Custom` - Device communication failed
    /// * `IllegalInstruction` - Not currently in raw mode
    ///
    /// # Example
    /// ```no_run
    /// use brlapi::{Connection, raw::RawMode};
    ///
    /// let connection = Connection::open()?;
    /// let driver = connection.display_driver()?;
    /// let raw_mode = RawMode::enter(&connection, &driver)?;
    ///
    /// let mut buffer = [0u8; 256];
    /// let bytes_received = raw_mode.receive_data(&mut buffer)?;
    /// println!("Received {} bytes: {:?}", bytes_received, &buffer[..bytes_received]);
    /// # Ok::<(), brlapi::BrlApiError>(())
    /// ```
    pub fn receive_data(&self, buffer: &mut [u8]) -> Result<usize> {
        if buffer.is_empty() {
            return Ok(0);
        }

        // SAFETY: self.connection.handle_ptr() is valid (raw mode successfully entered).
        // buffer.as_mut_ptr() points to valid mutable slice memory, buffer.len() provides correct size.
        // Cast to c_void is safe for receiving raw bytes into the buffer.
        let result = brlapi_call!(unsafe {
            brlapi__recvRaw(
                self.connection.handle_ptr(),
                buffer.as_mut_ptr() as *mut std::ffi::c_void,
                buffer.len(),
            )
        })?;

        Ok(result as usize)
    }

    /// Get the driver name this raw mode session is using
    pub fn driver_name(&self) -> &str {
        &self.driver_name
    }

    /// Get the underlying connection
    pub fn connection(&self) -> &Connection {
        self.connection
    }
}

impl<'a> Drop for RawMode<'a> {
    /// Automatically exit raw mode when the struct is dropped
    ///
    /// This ensures raw mode is always properly exited, even if an error
    /// occurs or the function returns early.
    fn drop(&mut self) {
        // Exit raw mode - ignore errors since we're in a destructor
        // SAFETY: self.connection.handle_ptr() is valid (raw mode was successfully entered).
        // brlapi__leaveRawMode is safe to call once per raw mode session.
        let _ = crate::brlapi_call!(unsafe { brlapi__leaveRawMode(self.connection.handle_ptr()) });
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_driver_support_detection() {
        // Known supported drivers
        assert!(RawMode::is_driver_supported("alva"));
        assert!(RawMode::is_driver_supported("baum"));
        assert!(RawMode::is_driver_supported("hims"));

        // Known unsupported drivers
        assert!(!RawMode::is_driver_supported("noblraille"));
        assert!(!RawMode::is_driver_supported("dummy"));

        // Case insensitive
        assert!(RawMode::is_driver_supported("ALVA"));
        assert!(RawMode::is_driver_supported("Baum"));

        // Unknown drivers (assume supported)
        assert!(RawMode::is_driver_supported("unknown_driver"));
    }

    #[test]
    fn test_empty_driver_validation() {
        // We can't test actual raw mode without a connection,
        // but we can test validation logic when we implement it
        assert!(!RawMode::is_driver_supported(""));
    }
}