1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SPDX-License-Identifier: LGPL-2.1
//! BrlAPI connection management
use crate::{error::BrlApiError, settings::ConnectionSettings};
use brlapi_sys::*;
use std::ptr;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
/// A thread-safe BrlAPI connection with automatic cleanup
///
/// This struct wraps a BrlAPI handle and file descriptor, ensuring that
/// `brlapi__closeConnection()` is called when the connection is dropped.
/// Uses the handle-based API for thread safety.
#[derive(Debug)]
pub struct Connection {
handle: Box<brlapi_handle_t>,
fd: brlapi_fileDescriptor,
}
impl Connection {
/// Open a new BrlAPI connection with default settings
///
/// # Errors
///
/// Returns `BrlApiError::ConnectionTimeout` if the connection times out (default 10 seconds).
/// Returns `BrlApiError::ConnectionRefused` if the BrlAPI daemon is not running or connection fails.
pub fn open() -> Result<Self, BrlApiError> {
Self::open_with_settings(None)
}
/// Open a new BrlAPI connection with custom settings
///
/// # Errors
///
/// Returns `BrlApiError::ConnectionTimeout` if the connection times out.
/// Returns `BrlApiError::InvalidParameter` if the settings contain invalid parameters.
/// Returns `BrlApiError::ConnectionRefused` if the BrlAPI daemon is not running or connection fails.
pub fn open_with_settings(settings: Option<&ConnectionSettings>) -> Result<Self, BrlApiError> {
let timeout = settings
.map(|s| s.timeout())
.unwrap_or(Duration::from_secs(10));
// Use timeout-aware connection
Self::open_with_timeout(settings, timeout)
}
/// Open a BrlAPI connection with a specific timeout
fn open_with_timeout(
settings: Option<&ConnectionSettings>,
timeout: Duration,
) -> Result<Self, BrlApiError> {
// Create channel for thread communication
let (sender, receiver) = mpsc::channel();
// Clone settings data to send to thread (avoiding raw pointer issues)
let settings_clone = settings.cloned();
// Spawn connection attempt in a separate thread
let connection_thread = thread::spawn(move || {
let result = Self::attempt_connection(settings_clone.as_ref());
let _ = sender.send(result); // Ignore send errors (timeout might have occurred)
});
// Wait for connection result or timeout
match receiver.recv_timeout(timeout) {
Ok(result) => {
// Connection thread completed, get the result
connection_thread.join().unwrap_or(()); // Best effort to clean up thread
result
}
Err(mpsc::RecvTimeoutError::Timeout) => {
// Connection timed out - thread may still be running but we can't wait
// The thread will be cleaned up when the process exits
Err(BrlApiError::ConnectionTimeout)
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
// Thread panicked or channel was closed
connection_thread.join().unwrap_or(());
Err(BrlApiError::ChannelError)
}
}
}
/// Internal function to attempt BrlAPI connection (runs in separate thread)
fn attempt_connection(settings: Option<&ConnectionSettings>) -> Result<Self, BrlApiError> {
// Allocate handle storage
// SAFETY: brlapi_getHandleSize() is a pure function that returns the required
// size for a BrlAPI handle allocation. No parameters needed, always safe to call.
let handle_size = unsafe { brlapi_getHandleSize() };
let mut handle = vec![0u8; handle_size].into_boxed_slice();
let handle_ptr = handle.as_mut_ptr() as *mut brlapi_handle_t;
// Prepare C-compatible settings inside this thread
let c_settings_data = match settings {
Some(s) => Some(s.to_c_settings()?),
None => None,
};
let c_settings_ptr = c_settings_data
.as_ref()
.map_or(ptr::null(), |(c_settings, _, _)| c_settings as *const _);
// SAFETY: handle_ptr points to valid handle memory allocated with correct size from brlapi_getHandleSize().
// c_settings_ptr is either null or points to valid brlapi_connectionSettings_t from to_c_settings().
// Third parameter is null_mut() as we don't need the actual settings back.
let fd = crate::brlapi_call!(unsafe { brlapi__openConnection(handle_ptr, c_settings_ptr, ptr::null_mut()) })?;
// Convert boxed slice to Box<brlapi_handle_t>
// SAFETY: handle was allocated with correct size from brlapi_getHandleSize() and contains
// a valid BrlAPI handle after successful brlapi__openConnection(). The cast from
// *mut [u8] to *mut brlapi_handle_t is safe because both have the same memory layout.
let handle = unsafe { Box::from_raw(Box::into_raw(handle) as *mut brlapi_handle_t) };
Ok(Connection { handle, fd })
}
/// Get the file descriptor for this connection
///
/// This can be used for select/poll operations or other low-level operations.
pub fn file_descriptor(&self) -> i32 {
self.fd
}
/// Get a raw pointer to the internal handle (for advanced usage)
///
/// # Safety
/// The returned pointer is valid only as long as this Connection exists.
/// Do not use this pointer after the Connection is dropped.
/// Caller must ensure that all BrlAPI calls using this handle are synchronized properly.
/// This function ensures the returned pointer is never null.
pub unsafe fn handle_ptr(&self) -> *mut brlapi_handle_t {
let ptr = self.handle.as_ref() as *const brlapi_handle_t as *mut brlapi_handle_t;
debug_assert!(!ptr.is_null(), "Connection handle should never be null");
ptr
}
}
impl Drop for Connection {
fn drop(&mut self) {
// SAFETY: self.handle contains a valid BrlAPI handle from successful brlapi__openConnection().
// brlapi__closeConnection is safe to call once per handle and we ensure it's only called once in Drop.
unsafe {
brlapi__closeConnection(self.handle.as_mut());
}
}
}