Skip to main content

asyn_rs/
port.rs

1//! Port driver base and trait.
2//!
3//! # I/O Model
4//!
5//! Ports are driven by a `PortActor` running on a dedicated thread.
6//! The actor exclusively owns the driver and processes requests from a channel.
7//!
8//! **Cache path** (default `read_*`/`write_*` methods):
9//! - Default implementations operate on the parameter cache (non-blocking).
10//! - Background tasks update cache via `set_*_param()` + `call_param_callbacks()`.
11//!
12//! **Actor path** (requests submitted via [`crate::port_handle::PortHandle`]):
13//! - Each port gets a dedicated actor thread that dispatches requests to driver methods.
14//! - `can_block` indicates the port may perform blocking I/O.
15
16use std::collections::HashMap;
17use std::sync::Arc;
18use std::time::SystemTime;
19
20use std::any::Any;
21
22/// Per-address device state for multi-device ports.
23#[derive(Debug, Clone)]
24pub struct DeviceState {
25    pub connected: bool,
26    pub enabled: bool,
27    pub auto_connect: bool,
28}
29
30impl Default for DeviceState {
31    fn default() -> Self {
32        Self {
33            connected: true,
34            enabled: true,
35            auto_connect: true,
36        }
37    }
38}
39
40use crate::error::{AsynError, AsynResult, AsynStatus};
41use crate::exception::{AsynException, ExceptionEvent, ExceptionManager};
42use crate::interpose::{OctetInterpose, OctetInterposeStack};
43use crate::interrupt::{InterruptManager, InterruptValue};
44use crate::param::{EnumEntry, ParamList, ParamType};
45use crate::trace::TraceManager;
46use crate::user::AsynUser;
47
48/// C asyn `queueRequest` priority. In asyn-rs this exists as compatibility
49/// metadata only — there is no actual request queue or priority-based scheduling.
50/// Drivers manage their own async tasks directly.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
52pub enum QueuePriority {
53    Low = 0,
54    #[default]
55    Medium = 1,
56    High = 2,
57    /// Connect/disconnect operations — processed even when disabled/disconnected.
58    Connect = 3,
59}
60
61/// Port configuration flags.
62#[derive(Debug, Clone, Copy)]
63pub struct PortFlags {
64    /// True if port supports multiple sub-addresses (ASYN_MULTIDEVICE).
65    pub multi_device: bool,
66    /// True if port can block (ASYN_CANBLOCK).
67    ///
68    /// When `true`, the port gets a dedicated worker thread that serializes I/O via a
69    /// priority queue (matching C asyn's per-port thread model).
70    ///
71    /// When `false`, requests execute synchronously inline on the caller's thread
72    /// (no worker thread is spawned). This is appropriate for non-blocking drivers
73    /// whose `io_*` methods return immediately (e.g., cache-based parameter access).
74    pub can_block: bool,
75    /// True if port can be destroyed via shutdown_port (ASYN_DESTRUCTIBLE).
76    pub destructible: bool,
77}
78
79impl Default for PortFlags {
80    fn default() -> Self {
81        // `destructible: false` is the C asyn convention — see
82        // asynDriver.h:97 (`#define ASYN_DESTRUCTIBLE 0x0004`) — the
83        // attribute is opt-in via `pasynManager->registerPort(..., attr)`
84        // and `asynManager::shutdownPort` refuses to act on ports
85        // that did not opt in. Defaulting to `true` here over-applied
86        // shutdown rights to every driver that built PortFlags via
87        // `..PortFlags::default()`.
88        Self {
89            multi_device: false,
90            can_block: false,
91            destructible: false,
92        }
93    }
94}
95
96/// Base state shared by all port drivers.
97/// Contains the parameter library, interrupt manager, and connection state.
98///
99/// # Interpose concurrency
100///
101/// `interpose_octet` requires `&mut self` for all operations (both `push` and
102/// `dispatch_*`). Since `PortDriverBase` is always behind `Arc<Mutex<dyn PortDriver>>`,
103/// any access to `interpose_octet` requires the port lock. This naturally
104/// serializes interpose modifications with I/O dispatch — no additional
105/// synchronization is needed. **Callers must never modify the interpose stack
106/// without holding the port lock.**
107pub struct PortDriverBase {
108    pub port_name: String,
109    pub max_addr: usize,
110    pub flags: PortFlags,
111    pub params: ParamList,
112    pub interrupts: InterruptManager,
113    pub connected: bool,
114    pub enabled: bool,
115    pub auto_connect: bool,
116    /// `defunct` — set by [`Self::shutdown_lifecycle`] when a
117    /// destructible port is torn down via `shutdown_port`. Once true,
118    /// the port refuses every new request through [`Self::check_ready`].
119    /// Mirrors the `dpCommon.defunct` flag at C asynManager.c:2284
120    /// — once defunct, the port cannot be re-enabled.
121    pub defunct: bool,
122    /// Exception sink injected by [`crate::manager::PortManager`] on registration.
123    pub exception_sink: Option<Arc<ExceptionManager>>,
124    pub options: HashMap<String, String>,
125    /// Input EOS sequence (max 2 bytes). Used by EOS interpose and drivers.
126    pub input_eos: Vec<u8>,
127    /// Output EOS sequence (max 2 bytes). Used by EOS interpose and drivers.
128    pub output_eos: Vec<u8>,
129    pub interpose_octet: OctetInterposeStack,
130    pub trace: Option<Arc<TraceManager>>,
131    /// Per-address device state for multi-device ports.
132    pub device_states: HashMap<i32, DeviceState>,
133    /// Timestamp source callback for custom timestamps.
134    pub timestamp_source: Option<Arc<dyn Fn() -> SystemTime + Send + Sync>>,
135}
136
137impl PortDriverBase {
138    pub fn new(port_name: &str, max_addr: usize, flags: PortFlags) -> Self {
139        Self {
140            port_name: port_name.to_string(),
141            max_addr: max_addr.max(1),
142            flags,
143            params: ParamList::new(max_addr, flags.multi_device),
144            interrupts: InterruptManager::new(256),
145            connected: true,
146            enabled: true,
147            auto_connect: true,
148            defunct: false,
149            exception_sink: None,
150            options: HashMap::new(),
151            input_eos: Vec::new(),
152            output_eos: Vec::new(),
153            interpose_octet: OctetInterposeStack::new(),
154            trace: None,
155            device_states: HashMap::new(),
156            timestamp_source: None,
157        }
158    }
159
160    /// Announce an exception through the global exception manager (if injected).
161    pub fn announce_exception(&self, exception: AsynException, addr: i32) {
162        if let Some(ref sink) = self.exception_sink {
163            sink.announce(&ExceptionEvent {
164                port_name: self.port_name.clone(),
165                exception,
166                addr,
167            });
168        }
169    }
170
171    /// Query whether the port is connected.
172    pub fn is_connected(&self) -> bool {
173        self.connected
174    }
175
176    /// Query whether the port is enabled.
177    pub fn is_enabled(&self) -> bool {
178        self.enabled
179    }
180
181    /// Query whether auto-connect is enabled.
182    pub fn is_auto_connect(&self) -> bool {
183        self.auto_connect
184    }
185
186    /// Query whether the port has been marked defunct via
187    /// [`Self::shutdown_lifecycle`] — once true the port is gone for
188    /// good, mirroring C asynManager.c:2266-2269.
189    pub fn is_defunct(&self) -> bool {
190        self.defunct
191    }
192
193    /// Check that the port is enabled, connected, and not defunct.
194    /// Returns `Err(Disabled)`, `Err(Disconnected)`, or `Err(Disabled)`
195    /// (defunct => permanently disabled) otherwise.
196    pub fn check_ready(&self) -> AsynResult<()> {
197        // C asyn parity: a defunct port short-circuits queueRequest
198        // (asynManager.c:2283 comment). Reject *before* the enabled
199        // check so the error message names the lifecycle phase, not
200        // just "disabled".
201        if self.defunct {
202            return Err(AsynError::Status {
203                status: AsynStatus::Disabled,
204                message: format!("port {} has been shut down (defunct)", self.port_name),
205            });
206        }
207        if !self.enabled {
208            return Err(AsynError::Status {
209                status: AsynStatus::Disabled,
210                message: format!("port {} is disabled", self.port_name),
211            });
212        }
213        if !self.connected {
214            return Err(AsynError::Status {
215                status: AsynStatus::Disconnected,
216                message: format!("port {} is disconnected", self.port_name),
217            });
218        }
219        Ok(())
220    }
221
222    /// Run the C `shutdownPort` lifecycle (asynManager.c:2251-2308):
223    ///
224    /// 1. Refuse if the port did not opt into `ASYN_DESTRUCTIBLE`
225    ///    (returns `Err(Status::Error)`).
226    /// 2. Short-circuit if already defunct (idempotent — returns Ok).
227    /// 3. Set `enabled = false`, `defunct = true` — every subsequent
228    ///    request through [`Self::check_ready`] fails.
229    /// 4. Broadcast `AsynException::Shutdown` so registered observers
230    ///    (CA gateways, monitor sinks) tear down their handles.
231    ///
232    /// Drivers should call this from their own shutdown plumbing and
233    /// then release any hardware-owned resources via their
234    /// [`PortDriver::shutdown`] implementation. Callers from outside
235    /// the runtime can drive the same lifecycle via
236    /// [`crate::manager::PortManager::shutdown_port`].
237    pub fn shutdown_lifecycle(&mut self) -> AsynResult<()> {
238        if self.defunct {
239            // Idempotent — C asynManager.c:2266-2269 returns asynSuccess.
240            return Ok(());
241        }
242        if !self.flags.destructible {
243            return Err(AsynError::Status {
244                status: AsynStatus::Error,
245                message: format!(
246                    "port {} does not support shutting down (ASYN_DESTRUCTIBLE not set)",
247                    self.port_name
248                ),
249            });
250        }
251        self.enabled = false;
252        self.defunct = true;
253        self.announce_exception(AsynException::Shutdown, -1);
254        Ok(())
255    }
256
257    /// Check that port + device address are both ready.
258    /// For multi-device ports, checks per-address state in addition to port-level state.
259    pub fn check_ready_addr(&self, addr: i32) -> AsynResult<()> {
260        self.check_ready()?;
261        if self.flags.multi_device {
262            if let Some(ds) = self.device_states.get(&addr) {
263                if !ds.enabled {
264                    return Err(AsynError::Status {
265                        status: AsynStatus::Disabled,
266                        message: format!("port {} addr {} is disabled", self.port_name, addr),
267                    });
268                }
269                if !ds.connected {
270                    return Err(AsynError::Status {
271                        status: AsynStatus::Disconnected,
272                        message: format!("port {} addr {} is disconnected", self.port_name, addr),
273                    });
274                }
275            }
276        }
277        Ok(())
278    }
279
280    /// Get or create a device state for the given address.
281    pub fn device_state(&mut self, addr: i32) -> &mut DeviceState {
282        self.device_states.entry(addr).or_default()
283    }
284
285    /// Check if a specific device address is connected.
286    pub fn is_device_connected(&self, addr: i32) -> bool {
287        self.device_states
288            .get(&addr)
289            .map_or(true, |ds| ds.connected)
290    }
291
292    /// Set a specific device address as connected.
293    pub fn connect_addr(&mut self, addr: i32) {
294        self.device_state(addr).connected = true;
295        self.announce_exception(AsynException::Connect, addr);
296    }
297
298    /// Set a specific device address as disconnected.
299    pub fn disconnect_addr(&mut self, addr: i32) {
300        self.device_state(addr).connected = false;
301        self.announce_exception(AsynException::Connect, addr);
302    }
303
304    /// Enable a specific device address.
305    pub fn enable_addr(&mut self, addr: i32) {
306        self.device_state(addr).enabled = true;
307        self.announce_exception(AsynException::Enable, addr);
308    }
309
310    /// Disable a specific device address.
311    pub fn disable_addr(&mut self, addr: i32) {
312        self.device_state(addr).enabled = false;
313        self.announce_exception(AsynException::Enable, addr);
314    }
315
316    /// Set a custom timestamp source callback.
317    pub fn register_timestamp_source<F>(&mut self, source: F)
318    where
319        F: Fn() -> SystemTime + Send + Sync + 'static,
320    {
321        self.timestamp_source = Some(Arc::new(source));
322    }
323
324    /// Get current timestamp from the registered source, or SystemTime::now().
325    pub fn current_timestamp(&self) -> SystemTime {
326        self.timestamp_source
327            .as_ref()
328            .map_or_else(SystemTime::now, |f| f())
329    }
330
331    pub fn create_param(&mut self, name: &str, param_type: ParamType) -> AsynResult<usize> {
332        self.params.create_param(name, param_type)
333    }
334
335    pub fn find_param(&self, name: &str) -> Option<usize> {
336        self.params.find_param(name)
337    }
338
339    // --- Convenience param accessors ---
340
341    pub fn set_int32_param(&mut self, index: usize, addr: i32, value: i32) -> AsynResult<()> {
342        self.params.set_int32(index, addr, value)
343    }
344
345    pub fn get_int32_param(&self, index: usize, addr: i32) -> AsynResult<i32> {
346        self.params.get_int32(index, addr)
347    }
348
349    pub fn set_int64_param(&mut self, index: usize, addr: i32, value: i64) -> AsynResult<()> {
350        self.params.set_int64(index, addr, value)
351    }
352
353    pub fn get_int64_param(&self, index: usize, addr: i32) -> AsynResult<i64> {
354        self.params.get_int64(index, addr)
355    }
356
357    pub fn set_float64_param(&mut self, index: usize, addr: i32, value: f64) -> AsynResult<()> {
358        self.params.set_float64(index, addr, value)
359    }
360
361    pub fn get_float64_param(&self, index: usize, addr: i32) -> AsynResult<f64> {
362        self.params.get_float64(index, addr)
363    }
364
365    pub fn set_string_param(&mut self, index: usize, addr: i32, value: String) -> AsynResult<()> {
366        self.params.set_string(index, addr, value)
367    }
368
369    pub fn get_string_param(&self, index: usize, addr: i32) -> AsynResult<&str> {
370        self.params.get_string(index, addr)
371    }
372
373    pub fn set_uint32_param(
374        &mut self,
375        index: usize,
376        addr: i32,
377        value: u32,
378        mask: u32,
379    ) -> AsynResult<()> {
380        self.params.set_uint32(index, addr, value, mask)
381    }
382
383    pub fn get_uint32_param(&self, index: usize, addr: i32) -> AsynResult<u32> {
384        self.params.get_uint32(index, addr)
385    }
386
387    pub fn get_enum_param(&self, index: usize, addr: i32) -> AsynResult<(usize, Arc<[EnumEntry]>)> {
388        self.params.get_enum(index, addr)
389    }
390
391    pub fn set_enum_index_param(
392        &mut self,
393        index: usize,
394        addr: i32,
395        value: usize,
396    ) -> AsynResult<()> {
397        self.params.set_enum_index(index, addr, value)
398    }
399
400    pub fn set_enum_choices_param(
401        &mut self,
402        index: usize,
403        addr: i32,
404        choices: Arc<[EnumEntry]>,
405    ) -> AsynResult<()> {
406        self.params.set_enum_choices(index, addr, choices)
407    }
408
409    pub fn get_generic_pointer_param(
410        &self,
411        index: usize,
412        addr: i32,
413    ) -> AsynResult<Arc<dyn Any + Send + Sync>> {
414        self.params.get_generic_pointer(index, addr)
415    }
416
417    pub fn set_generic_pointer_param(
418        &mut self,
419        index: usize,
420        addr: i32,
421        value: Arc<dyn Any + Send + Sync>,
422    ) -> AsynResult<()> {
423        self.params.set_generic_pointer(index, addr, value)
424    }
425
426    pub fn set_param_timestamp(
427        &mut self,
428        index: usize,
429        addr: i32,
430        ts: SystemTime,
431    ) -> AsynResult<()> {
432        self.params.set_timestamp(index, addr, ts)
433    }
434
435    pub fn set_param_status(
436        &mut self,
437        index: usize,
438        addr: i32,
439        status: AsynStatus,
440        alarm_status: u16,
441        alarm_severity: u16,
442    ) -> AsynResult<()> {
443        self.params
444            .set_param_status(index, addr, status, alarm_status, alarm_severity)
445    }
446
447    pub fn get_param_status(&self, index: usize, addr: i32) -> AsynResult<(AsynStatus, u16, u16)> {
448        self.params.get_param_status(index, addr)
449    }
450
451    /// Detailed parameter report matching C asynPortDriver::reportParams.
452    pub fn report_params(&self, level: i32) {
453        eprintln!("  Number of parameters is {}", self.params.len());
454        if level < 1 {
455            return;
456        }
457        for i in 0..self.params.len() {
458            let name = self.params.param_name(i).unwrap_or("?");
459            let ptype = self
460                .params
461                .param_type(i)
462                .map(|t| format!("{t:?}"))
463                .unwrap_or("?".into());
464            if level >= 2 {
465                for addr in 0..self.max_addr.max(1) {
466                    let val = self
467                        .params
468                        .get_value(i, addr as i32)
469                        .map(|v| format!("{v:?}"))
470                        .unwrap_or("undefined".into());
471                    let (status, alarm_st, alarm_sev) = self
472                        .params
473                        .get_param_status(i, addr as i32)
474                        .unwrap_or((AsynStatus::Success, 0, 0));
475                    eprintln!(
476                        "  param[{i}] name={name} type={ptype} addr={addr} val={val} status={status:?} alarm=({alarm_st},{alarm_sev})"
477                    );
478                }
479            } else {
480                eprintln!("  param[{i}] name={name} type={ptype}");
481            }
482        }
483    }
484
485    /// Push an interpose layer onto the octet I/O stack.
486    ///
487    /// **Concurrency**: requires `&mut self`, which means the caller must hold
488    /// the port lock (`Arc<Mutex<dyn PortDriver>>`). This ensures
489    /// interpose modifications are serialized with I/O dispatch.
490    pub fn push_octet_interpose(&mut self, layer: Box<dyn OctetInterpose>) {
491        self.interpose_octet.push(layer);
492    }
493
494    /// Flush changed parameters as interrupt notifications.
495    /// Equivalent to C asyn's callParamCallbacks().
496    pub fn call_param_callbacks(&mut self, addr: i32) -> AsynResult<()> {
497        let changed = self.params.take_changed(addr)?;
498        let now = self.current_timestamp();
499        for reason in changed {
500            let value = self.params.get_value(reason, addr)?.clone();
501            let ts = self.params.get_timestamp(reason, addr)?.unwrap_or(now);
502            let uint32_mask = self
503                .params
504                .get_uint32_interrupt_mask(reason, addr)
505                .unwrap_or(0);
506            self.interrupts.notify(InterruptValue {
507                reason,
508                addr,
509                value,
510                timestamp: ts,
511                uint32_changed_mask: uint32_mask,
512            });
513        }
514        Ok(())
515    }
516
517    /// Flush a single parameter's changed flag and notify if dirty.
518    /// Use this instead of `call_param_callbacks` when you want to avoid
519    /// flushing unrelated parameters (e.g. rapidly-updating CP-linked params).
520    pub fn call_param_callback(&mut self, addr: i32, reason: usize) -> AsynResult<()> {
521        if self.params.take_changed_single(reason, addr)? {
522            let now = self.current_timestamp();
523            let value = self.params.get_value(reason, addr)?.clone();
524            let ts = self.params.get_timestamp(reason, addr)?.unwrap_or(now);
525            let uint32_mask = self
526                .params
527                .get_uint32_interrupt_mask(reason, addr)
528                .unwrap_or(0);
529            self.interrupts.notify(InterruptValue {
530                reason,
531                addr,
532                value,
533                timestamp: ts,
534                uint32_changed_mask: uint32_mask,
535            });
536        }
537        Ok(())
538    }
539
540    /// Mark a parameter as changed without modifying its value.
541    ///
542    /// Use this to trigger I/O Intr on params whose data is served via
543    /// `read_*_array()` overrides rather than the param cache (e.g. pixel data).
544    pub fn mark_param_changed(&mut self, index: usize, addr: i32) -> AsynResult<()> {
545        self.params.mark_changed(index, addr)
546    }
547}
548
549/// Port driver trait. All methods have default implementations that operate
550/// on the parameter cache (no actual I/O).
551///
552/// Drivers performing real hardware I/O should:
553/// 1. Run I/O in a background task (e.g., tokio::spawn)
554/// 2. Update parameters via `base_mut().set_*_param()` + `call_param_callbacks()`
555/// 3. Let the default `read_*` methods return cached values
556///
557/// # LockPort/UnlockPort
558///
559/// C asyn provides `lockPort`/`unlockPort` for direct mutex locking. In asyn-rs,
560/// the port is always behind `Arc<Mutex<dyn PortDriver>>`, so callers hold the
561/// parking_lot mutex directly. For multi-request exclusive access, use
562/// `BlockProcess`/`UnblockProcess` via the worker queue.
563pub trait PortDriver: Send + Sync + 'static {
564    fn base(&self) -> &PortDriverBase;
565    fn base_mut(&mut self) -> &mut PortDriverBase;
566
567    // --- AsynCommon ---
568
569    fn connect(&mut self, _user: &AsynUser) -> AsynResult<()> {
570        self.base_mut().connected = true;
571        self.base().announce_exception(AsynException::Connect, -1);
572        Ok(())
573    }
574
575    fn disconnect(&mut self, _user: &AsynUser) -> AsynResult<()> {
576        self.base_mut().connected = false;
577        self.base().announce_exception(AsynException::Connect, -1);
578        Ok(())
579    }
580
581    fn enable(&mut self, _user: &AsynUser) -> AsynResult<()> {
582        self.base_mut().enabled = true;
583        self.base().announce_exception(AsynException::Enable, -1);
584        Ok(())
585    }
586
587    fn disable(&mut self, _user: &AsynUser) -> AsynResult<()> {
588        self.base_mut().enabled = false;
589        self.base().announce_exception(AsynException::Enable, -1);
590        Ok(())
591    }
592
593    fn connect_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
594        self.base_mut().connect_addr(user.addr);
595        Ok(())
596    }
597
598    fn disconnect_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
599        self.base_mut().disconnect_addr(user.addr);
600        Ok(())
601    }
602
603    fn enable_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
604        self.base_mut().enable_addr(user.addr);
605        Ok(())
606    }
607
608    fn disable_addr(&mut self, user: &AsynUser) -> AsynResult<()> {
609        self.base_mut().disable_addr(user.addr);
610        Ok(())
611    }
612
613    fn get_option(&self, key: &str) -> AsynResult<String> {
614        self.base()
615            .options
616            .get(key)
617            .cloned()
618            .ok_or_else(|| AsynError::OptionNotFound(key.to_string()))
619    }
620
621    fn set_option(&mut self, key: &str, value: &str) -> AsynResult<()> {
622        self.base_mut()
623            .options
624            .insert(key.to_string(), value.to_string());
625        Ok(())
626    }
627
628    fn report(&self, level: i32) {
629        let base = self.base();
630        eprintln!("Port: {}", base.port_name);
631        eprintln!(
632            "  connected: {}, max_addr: {}, params: {}, options: {}",
633            base.connected,
634            base.max_addr,
635            base.params.len(),
636            base.options.len()
637        );
638        if level >= 1 {
639            base.report_params(level.saturating_sub(1));
640        }
641        if level >= 2 {
642            for (k, v) in &base.options {
643                eprintln!("  option: {k} = {v}");
644            }
645        }
646    }
647
648    // --- Scalar I/O (cache-based defaults, timeout not applicable) ---
649
650    // Cache-based defaults do NOT check connection state (C parity).
651    // The port actor checks check_ready_addr() before dispatching, matching
652    // C asyn where asynManager checks connection before calling the driver.
653
654    fn read_int32(&mut self, user: &AsynUser) -> AsynResult<i32> {
655        self.base().params.get_int32(user.reason, user.addr)
656    }
657
658    fn write_int32(&mut self, user: &mut AsynUser, value: i32) -> AsynResult<()> {
659        self.base_mut()
660            .params
661            .set_int32(user.reason, user.addr, value)?;
662        self.base_mut().call_param_callbacks(user.addr)
663    }
664
665    fn read_int64(&mut self, user: &AsynUser) -> AsynResult<i64> {
666        self.base().params.get_int64(user.reason, user.addr)
667    }
668
669    fn write_int64(&mut self, user: &mut AsynUser, value: i64) -> AsynResult<()> {
670        self.base_mut()
671            .params
672            .set_int64(user.reason, user.addr, value)?;
673        self.base_mut().call_param_callbacks(user.addr)
674    }
675
676    fn get_bounds_int32(&self, _user: &AsynUser) -> AsynResult<(i32, i32)> {
677        Ok((i32::MIN, i32::MAX))
678    }
679
680    fn get_bounds_int64(&self, _user: &AsynUser) -> AsynResult<(i64, i64)> {
681        Ok((i64::MIN, i64::MAX))
682    }
683
684    fn read_float64(&mut self, user: &AsynUser) -> AsynResult<f64> {
685        self.base().params.get_float64(user.reason, user.addr)
686    }
687
688    fn write_float64(&mut self, user: &mut AsynUser, value: f64) -> AsynResult<()> {
689        self.base_mut()
690            .params
691            .set_float64(user.reason, user.addr, value)?;
692        self.base_mut().call_param_callbacks(user.addr)
693    }
694
695    fn read_octet(&mut self, user: &AsynUser, buf: &mut [u8]) -> AsynResult<usize> {
696        let s = self.base().params.get_string(user.reason, user.addr)?;
697        let bytes = s.as_bytes();
698        let n = bytes.len().min(buf.len());
699        buf[..n].copy_from_slice(&bytes[..n]);
700        Ok(n)
701    }
702
703    fn write_octet(&mut self, user: &mut AsynUser, data: &[u8]) -> AsynResult<()> {
704        let s = String::from_utf8_lossy(data).into_owned();
705        self.base_mut()
706            .params
707            .set_string(user.reason, user.addr, s)?;
708        self.base_mut().call_param_callbacks(user.addr)
709    }
710
711    fn read_uint32_digital(&mut self, user: &AsynUser, mask: u32) -> AsynResult<u32> {
712        let val = self.base().params.get_uint32(user.reason, user.addr)?;
713        Ok(val & mask)
714    }
715
716    fn write_uint32_digital(
717        &mut self,
718        user: &mut AsynUser,
719        value: u32,
720        mask: u32,
721    ) -> AsynResult<()> {
722        self.base_mut()
723            .params
724            .set_uint32(user.reason, user.addr, value, mask)?;
725        self.base_mut().call_param_callbacks(user.addr)
726    }
727
728    // --- Enum I/O (cache-based defaults) ---
729
730    fn read_enum(&mut self, user: &AsynUser) -> AsynResult<(usize, Arc<[EnumEntry]>)> {
731        self.base().params.get_enum(user.reason, user.addr)
732    }
733
734    fn write_enum(&mut self, user: &mut AsynUser, index: usize) -> AsynResult<()> {
735        self.base_mut()
736            .params
737            .set_enum_index(user.reason, user.addr, index)?;
738        self.base_mut().call_param_callbacks(user.addr)
739    }
740
741    fn write_enum_choices(
742        &mut self,
743        user: &mut AsynUser,
744        choices: Arc<[EnumEntry]>,
745    ) -> AsynResult<()> {
746        self.base_mut()
747            .params
748            .set_enum_choices(user.reason, user.addr, choices)?;
749        self.base_mut().call_param_callbacks(user.addr)
750    }
751
752    // --- GenericPointer I/O (cache-based defaults) ---
753
754    fn read_generic_pointer(&mut self, user: &AsynUser) -> AsynResult<Arc<dyn Any + Send + Sync>> {
755        self.base()
756            .params
757            .get_generic_pointer(user.reason, user.addr)
758    }
759
760    fn write_generic_pointer(
761        &mut self,
762        user: &mut AsynUser,
763        value: Arc<dyn Any + Send + Sync>,
764    ) -> AsynResult<()> {
765        self.base_mut()
766            .params
767            .set_generic_pointer(user.reason, user.addr, value)?;
768        self.base_mut().call_param_callbacks(user.addr)
769    }
770
771    // --- Array I/O (default: not supported) ---
772
773    fn read_float64_array(&mut self, _user: &AsynUser, _buf: &mut [f64]) -> AsynResult<usize> {
774        Err(AsynError::InterfaceNotSupported("asynFloat64Array".into()))
775    }
776
777    fn write_float64_array(&mut self, user: &AsynUser, data: &[f64]) -> AsynResult<()> {
778        self.base_mut()
779            .params
780            .set_float64_array(user.reason, user.addr, data.to_vec())?;
781        self.base_mut().call_param_callbacks(user.addr)
782    }
783
784    fn read_int32_array(&mut self, _user: &AsynUser, _buf: &mut [i32]) -> AsynResult<usize> {
785        Err(AsynError::InterfaceNotSupported("asynInt32Array".into()))
786    }
787
788    fn write_int32_array(&mut self, user: &AsynUser, data: &[i32]) -> AsynResult<()> {
789        self.base_mut()
790            .params
791            .set_int32_array(user.reason, user.addr, data.to_vec())?;
792        self.base_mut().call_param_callbacks(user.addr)
793    }
794
795    fn read_int8_array(&mut self, _user: &AsynUser, _buf: &mut [i8]) -> AsynResult<usize> {
796        Err(AsynError::InterfaceNotSupported("asynInt8Array".into()))
797    }
798
799    fn write_int8_array(&mut self, user: &AsynUser, data: &[i8]) -> AsynResult<()> {
800        self.base_mut()
801            .params
802            .set_int8_array(user.reason, user.addr, data.to_vec())?;
803        self.base_mut().call_param_callbacks(user.addr)
804    }
805
806    fn read_int16_array(&mut self, _user: &AsynUser, _buf: &mut [i16]) -> AsynResult<usize> {
807        Err(AsynError::InterfaceNotSupported("asynInt16Array".into()))
808    }
809
810    fn write_int16_array(&mut self, user: &AsynUser, data: &[i16]) -> AsynResult<()> {
811        self.base_mut()
812            .params
813            .set_int16_array(user.reason, user.addr, data.to_vec())?;
814        self.base_mut().call_param_callbacks(user.addr)
815    }
816
817    fn read_int64_array(&mut self, _user: &AsynUser, _buf: &mut [i64]) -> AsynResult<usize> {
818        Err(AsynError::InterfaceNotSupported("asynInt64Array".into()))
819    }
820
821    fn write_int64_array(&mut self, user: &AsynUser, data: &[i64]) -> AsynResult<()> {
822        self.base_mut()
823            .params
824            .set_int64_array(user.reason, user.addr, data.to_vec())?;
825        self.base_mut().call_param_callbacks(user.addr)
826    }
827
828    fn read_float32_array(&mut self, _user: &AsynUser, _buf: &mut [f32]) -> AsynResult<usize> {
829        Err(AsynError::InterfaceNotSupported("asynFloat32Array".into()))
830    }
831
832    fn write_float32_array(&mut self, user: &AsynUser, data: &[f32]) -> AsynResult<()> {
833        self.base_mut()
834            .params
835            .set_float32_array(user.reason, user.addr, data.to_vec())?;
836        self.base_mut().call_param_callbacks(user.addr)
837    }
838
839    // --- I/O methods (worker thread calls these) ---
840    // Default: delegate to cache-based read_*/write_* for backward compat.
841    // Real I/O drivers override these for actual hardware access.
842
843    fn io_read_octet(&mut self, user: &AsynUser, buf: &mut [u8]) -> AsynResult<usize> {
844        self.read_octet(user, buf)
845    }
846
847    fn io_write_octet(&mut self, user: &mut AsynUser, data: &[u8]) -> AsynResult<()> {
848        self.write_octet(user, data)
849    }
850
851    fn io_read_int32(&mut self, user: &AsynUser) -> AsynResult<i32> {
852        self.read_int32(user)
853    }
854
855    fn io_write_int32(&mut self, user: &mut AsynUser, value: i32) -> AsynResult<()> {
856        self.write_int32(user, value)
857    }
858
859    fn io_read_int64(&mut self, user: &AsynUser) -> AsynResult<i64> {
860        self.read_int64(user)
861    }
862
863    fn io_write_int64(&mut self, user: &mut AsynUser, value: i64) -> AsynResult<()> {
864        self.write_int64(user, value)
865    }
866
867    fn io_read_float64(&mut self, user: &AsynUser) -> AsynResult<f64> {
868        self.read_float64(user)
869    }
870
871    fn io_write_float64(&mut self, user: &mut AsynUser, value: f64) -> AsynResult<()> {
872        self.write_float64(user, value)
873    }
874
875    fn io_read_uint32_digital(&mut self, user: &AsynUser, mask: u32) -> AsynResult<u32> {
876        self.read_uint32_digital(user, mask)
877    }
878
879    fn io_write_uint32_digital(
880        &mut self,
881        user: &mut AsynUser,
882        value: u32,
883        mask: u32,
884    ) -> AsynResult<()> {
885        self.write_uint32_digital(user, value, mask)
886    }
887
888    fn io_flush(&mut self, _user: &mut AsynUser) -> AsynResult<()> {
889        Ok(())
890    }
891
892    // --- Octet EOS (delegates to interpose stack by default) ---
893    //
894    // ## EOS connect-wait policy (C asyn issue #103)
895    //
896    // C asyn `asynOctetSyncIO::setInputEos` / `setOutputEos`
897    // (`asynOctetSyncIO.c:300-321`, 346-367) call `lockPort` ahead of
898    // the actual `setInputEos` — `lockPort` waits up to the user's
899    // timeout for the port to be connected, by `epicsEventWait`-ing
900    // on the connect event published from `connectIt`. On IOC init
901    // and exit this serialises EOS configuration against the connect
902    // task, but it also means a `setInputEos` issued before the port
903    // has ever connected blocks the calling thread (issue #103
904    // captured the symptom: IOC startup pauses for the full asyn
905    // timeout when the device is off-line).
906    //
907    // The Rust path here is purely in-memory: `set_input_eos` and
908    // `set_output_eos` write the bytes into `PortDriverBase` and the
909    // EOS interpose stack reads from those fields at next read/write
910    // time. No connect-wait, no lock contention with the connect
911    // task — so issue #103's symptom cannot reproduce. If a future
912    // refactor introduces a connect-gated EOS path (e.g. a driver
913    // that owns the EOS state inside its connect()-allocated
914    // resource), authors MUST keep the wait optional / bounded so
915    // the connect-wait failure mode doesn't return.
916
917    fn set_input_eos(&mut self, eos: &[u8]) -> AsynResult<()> {
918        if eos.len() > 2 {
919            return Err(AsynError::Status {
920                status: AsynStatus::Error,
921                message: format!("illegal eoslen {}", eos.len()),
922            });
923        }
924        self.base_mut().input_eos = eos.to_vec();
925        Ok(())
926    }
927
928    fn get_input_eos(&self) -> Vec<u8> {
929        self.base().input_eos.clone()
930    }
931
932    fn set_output_eos(&mut self, eos: &[u8]) -> AsynResult<()> {
933        if eos.len() > 2 {
934            return Err(AsynError::Status {
935                status: AsynStatus::Error,
936                message: format!("illegal eoslen {}", eos.len()),
937            });
938        }
939        self.base_mut().output_eos = eos.to_vec();
940        Ok(())
941    }
942
943    fn get_output_eos(&self) -> Vec<u8> {
944        self.base().output_eos.clone()
945    }
946
947    // --- Lifecycle ---
948
949    /// Called when the port is being shut down. Drivers override this
950    /// to release hardware resources. Matches C asynPortDriver::shutdownPortDriver().
951    fn shutdown(&mut self) -> AsynResult<()> {
952        Ok(())
953    }
954
955    // --- drvUser ---
956
957    /// Resolve a driver info string to a parameter index.
958    /// Default: look up by parameter name.
959    fn drv_user_create(&self, drv_info: &str) -> AsynResult<usize> {
960        self.base()
961            .params
962            .find_param(drv_info)
963            .ok_or_else(|| AsynError::ParamNotFound(drv_info.to_string()))
964    }
965
966    // --- Capabilities ---
967
968    /// Declare the capabilities this driver supports.
969    /// Default implementation includes all scalar read/write operations.
970    fn capabilities(&self) -> Vec<crate::interfaces::Capability> {
971        crate::interfaces::default_capabilities()
972    }
973
974    /// Check if this driver supports a specific capability.
975    fn supports(&self, cap: crate::interfaces::Capability) -> bool {
976        self.capabilities().contains(&cap)
977    }
978
979    fn init(&mut self) -> AsynResult<()> {
980        Ok(())
981    }
982}
983
984#[cfg(test)]
985mod tests {
986    use super::*;
987    struct TestDriver {
988        base: PortDriverBase,
989    }
990
991    impl TestDriver {
992        fn new() -> Self {
993            let mut base = PortDriverBase::new("test", 1, PortFlags::default());
994            base.create_param("VAL", ParamType::Int32).unwrap();
995            base.create_param("TEMP", ParamType::Float64).unwrap();
996            base.create_param("MSG", ParamType::Octet).unwrap();
997            base.create_param("BITS", ParamType::UInt32Digital).unwrap();
998            Self { base }
999        }
1000    }
1001
1002    impl PortDriver for TestDriver {
1003        fn base(&self) -> &PortDriverBase {
1004            &self.base
1005        }
1006        fn base_mut(&mut self) -> &mut PortDriverBase {
1007            &mut self.base
1008        }
1009    }
1010
1011    #[test]
1012    fn test_default_read_write_int32() {
1013        let mut drv = TestDriver::new();
1014        let mut user = AsynUser::new(0);
1015        drv.write_int32(&mut user, 42).unwrap();
1016        let user = AsynUser::new(0);
1017        assert_eq!(drv.read_int32(&user).unwrap(), 42);
1018    }
1019
1020    #[test]
1021    fn test_default_read_write_float64() {
1022        let mut drv = TestDriver::new();
1023        let mut user = AsynUser::new(1);
1024        drv.write_float64(&mut user, 3.14).unwrap();
1025        let user = AsynUser::new(1);
1026        assert!((drv.read_float64(&user).unwrap() - 3.14).abs() < 1e-10);
1027    }
1028
1029    #[test]
1030    fn test_default_read_write_octet() {
1031        let mut drv = TestDriver::new();
1032        let mut user = AsynUser::new(2);
1033        drv.write_octet(&mut user, b"hello").unwrap();
1034        let user = AsynUser::new(2);
1035        let mut buf = [0u8; 32];
1036        let n = drv.read_octet(&user, &mut buf).unwrap();
1037        assert_eq!(&buf[..n], b"hello");
1038    }
1039
1040    #[test]
1041    fn test_default_read_write_uint32() {
1042        let mut drv = TestDriver::new();
1043        let mut user = AsynUser::new(3);
1044        drv.write_uint32_digital(&mut user, 0xFF, 0x0F).unwrap();
1045        let user = AsynUser::new(3);
1046        assert_eq!(drv.read_uint32_digital(&user, 0xFF).unwrap(), 0x0F);
1047    }
1048
1049    #[test]
1050    fn test_connect_disconnect() {
1051        let mut drv = TestDriver::new();
1052        let user = AsynUser::default();
1053        assert!(drv.base().connected);
1054        drv.disconnect(&user).unwrap();
1055        assert!(!drv.base().connected);
1056        drv.connect(&user).unwrap();
1057        assert!(drv.base().connected);
1058    }
1059
1060    #[test]
1061    fn test_drv_user_create() {
1062        let drv = TestDriver::new();
1063        assert_eq!(drv.drv_user_create("VAL").unwrap(), 0);
1064        assert_eq!(drv.drv_user_create("TEMP").unwrap(), 1);
1065        assert!(drv.drv_user_create("NOPE").is_err());
1066    }
1067
1068    #[test]
1069    fn test_call_param_callbacks() {
1070        let mut drv = TestDriver::new();
1071        let mut rx = drv.base_mut().interrupts.subscribe_async();
1072
1073        drv.base_mut().set_int32_param(0, 0, 100).unwrap();
1074        drv.base_mut().set_float64_param(1, 0, 2.0).unwrap();
1075        drv.base_mut().call_param_callbacks(0).unwrap();
1076
1077        let v1 = rx.try_recv().unwrap();
1078        assert_eq!(v1.reason, 0);
1079        let v2 = rx.try_recv().unwrap();
1080        assert_eq!(v2.reason, 1);
1081        assert!(rx.try_recv().is_err());
1082    }
1083
1084    #[test]
1085    fn test_no_callback_for_unchanged() {
1086        let mut drv = TestDriver::new();
1087        let mut rx = drv.base_mut().interrupts.subscribe_async();
1088
1089        drv.base_mut().set_int32_param(0, 0, 5).unwrap();
1090        drv.base_mut().call_param_callbacks(0).unwrap();
1091        let _ = rx.try_recv().unwrap(); // consume
1092
1093        // Set same value — no interrupt
1094        drv.base_mut().set_int32_param(0, 0, 5).unwrap();
1095        drv.base_mut().call_param_callbacks(0).unwrap();
1096        assert!(rx.try_recv().is_err());
1097    }
1098
1099    #[test]
1100    fn test_array_not_supported_by_default() {
1101        let mut drv = TestDriver::new();
1102        let user = AsynUser::new(0);
1103        let mut buf = [0f64; 10];
1104        assert!(drv.read_float64_array(&user, &mut buf).is_err());
1105        assert!(drv.write_float64_array(&user, &[1.0]).is_err());
1106    }
1107
1108    #[test]
1109    fn test_option_set_get() {
1110        let mut drv = TestDriver::new();
1111        drv.set_option("baud", "9600").unwrap();
1112        assert_eq!(drv.get_option("baud").unwrap(), "9600");
1113        drv.set_option("baud", "115200").unwrap();
1114        assert_eq!(drv.get_option("baud").unwrap(), "115200");
1115    }
1116
1117    #[test]
1118    fn test_option_not_found() {
1119        let drv = TestDriver::new();
1120        let err = drv.get_option("nonexistent").unwrap_err();
1121        assert!(matches!(err, AsynError::OptionNotFound(_)));
1122    }
1123
1124    #[test]
1125    fn test_report_no_panic() {
1126        let mut drv = TestDriver::new();
1127        drv.set_option("testkey", "testval").unwrap();
1128        drv.base_mut().set_int32_param(0, 0, 42).unwrap();
1129        for level in 0..=3 {
1130            drv.report(level);
1131        }
1132    }
1133
1134    #[test]
1135    fn test_callback_uses_param_timestamp() {
1136        let mut drv = TestDriver::new();
1137        let mut rx = drv.base_mut().interrupts.subscribe_async();
1138
1139        let custom_ts = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000);
1140        drv.base_mut().set_int32_param(0, 0, 77).unwrap();
1141        drv.base_mut().set_param_timestamp(0, 0, custom_ts).unwrap();
1142        drv.base_mut().call_param_callbacks(0).unwrap();
1143
1144        let v = rx.try_recv().unwrap();
1145        assert_eq!(v.reason, 0);
1146        assert_eq!(v.timestamp, custom_ts);
1147    }
1148
1149    #[test]
1150    fn test_default_read_write_enum() {
1151        use crate::param::EnumEntry;
1152
1153        let mut base = PortDriverBase::new("test_enum", 1, PortFlags::default());
1154        base.create_param("MODE", ParamType::Enum).unwrap();
1155
1156        struct EnumDriver {
1157            base: PortDriverBase,
1158        }
1159        impl PortDriver for EnumDriver {
1160            fn base(&self) -> &PortDriverBase {
1161                &self.base
1162            }
1163            fn base_mut(&mut self) -> &mut PortDriverBase {
1164                &mut self.base
1165            }
1166        }
1167
1168        let mut drv = EnumDriver { base };
1169        let choices: Arc<[EnumEntry]> = Arc::from(vec![
1170            EnumEntry {
1171                string: "Off".into(),
1172                value: 0,
1173                severity: 0,
1174            },
1175            EnumEntry {
1176                string: "On".into(),
1177                value: 1,
1178                severity: 0,
1179            },
1180        ]);
1181        let mut user = AsynUser::new(0);
1182        drv.write_enum_choices(&mut user, choices).unwrap();
1183        drv.write_enum(&mut user, 1).unwrap();
1184        let (idx, ch) = drv.read_enum(&AsynUser::new(0)).unwrap();
1185        assert_eq!(idx, 1);
1186        assert_eq!(ch[1].string, "On");
1187    }
1188
1189    #[test]
1190    fn test_enum_callback() {
1191        use crate::param::{EnumEntry, ParamValue};
1192
1193        let mut base = PortDriverBase::new("test_enum_cb", 1, PortFlags::default());
1194        base.create_param("MODE", ParamType::Enum).unwrap();
1195        let mut rx = base.interrupts.subscribe_async();
1196
1197        struct EnumDriver {
1198            base: PortDriverBase,
1199        }
1200        impl PortDriver for EnumDriver {
1201            fn base(&self) -> &PortDriverBase {
1202                &self.base
1203            }
1204            fn base_mut(&mut self) -> &mut PortDriverBase {
1205                &mut self.base
1206            }
1207        }
1208
1209        let mut drv = EnumDriver { base };
1210        let choices: Arc<[EnumEntry]> = Arc::from(vec![
1211            EnumEntry {
1212                string: "A".into(),
1213                value: 0,
1214                severity: 0,
1215            },
1216            EnumEntry {
1217                string: "B".into(),
1218                value: 1,
1219                severity: 0,
1220            },
1221        ]);
1222        drv.base_mut()
1223            .set_enum_choices_param(0, 0, choices)
1224            .unwrap();
1225        drv.base_mut().set_enum_index_param(0, 0, 1).unwrap();
1226        drv.base_mut().call_param_callbacks(0).unwrap();
1227
1228        let v = rx.try_recv().unwrap();
1229        assert_eq!(v.reason, 0);
1230        assert!(matches!(v.value, ParamValue::Enum { index: 1, .. }));
1231    }
1232
1233    #[test]
1234    fn test_default_read_write_generic_pointer() {
1235        let mut base = PortDriverBase::new("test_gp", 1, PortFlags::default());
1236        base.create_param("PTR", ParamType::GenericPointer).unwrap();
1237
1238        struct GpDriver {
1239            base: PortDriverBase,
1240        }
1241        impl PortDriver for GpDriver {
1242            fn base(&self) -> &PortDriverBase {
1243                &self.base
1244            }
1245            fn base_mut(&mut self) -> &mut PortDriverBase {
1246                &mut self.base
1247            }
1248        }
1249
1250        let mut drv = GpDriver { base };
1251        let data: Arc<dyn std::any::Any + Send + Sync> = Arc::new(99i32);
1252        let mut user = AsynUser::new(0);
1253        drv.write_generic_pointer(&mut user, data).unwrap();
1254        let val = drv.read_generic_pointer(&AsynUser::new(0)).unwrap();
1255        assert_eq!(*val.downcast_ref::<i32>().unwrap(), 99);
1256    }
1257
1258    #[test]
1259    fn test_generic_pointer_callback() {
1260        use crate::param::ParamValue;
1261
1262        let mut base = PortDriverBase::new("test_gp_cb", 1, PortFlags::default());
1263        base.create_param("PTR", ParamType::GenericPointer).unwrap();
1264        let mut rx = base.interrupts.subscribe_async();
1265
1266        struct GpDriver {
1267            base: PortDriverBase,
1268        }
1269        impl PortDriver for GpDriver {
1270            fn base(&self) -> &PortDriverBase {
1271                &self.base
1272            }
1273            fn base_mut(&mut self) -> &mut PortDriverBase {
1274                &mut self.base
1275            }
1276        }
1277
1278        let mut drv = GpDriver { base };
1279        let data: Arc<dyn std::any::Any + Send + Sync> = Arc::new(vec![1, 2, 3]);
1280        drv.base_mut()
1281            .set_generic_pointer_param(0, 0, data)
1282            .unwrap();
1283        drv.base_mut().call_param_callbacks(0).unwrap();
1284
1285        let v = rx.try_recv().unwrap();
1286        assert_eq!(v.reason, 0);
1287        assert!(matches!(v.value, ParamValue::GenericPointer(_)));
1288    }
1289
1290    #[test]
1291    fn test_interpose_push_requires_lock() {
1292        use crate::interpose::{OctetInterpose, OctetNext, OctetReadResult};
1293        use parking_lot::Mutex;
1294        use std::sync::Arc;
1295
1296        struct NoopInterpose;
1297        impl OctetInterpose for NoopInterpose {
1298            fn read(
1299                &mut self,
1300                user: &AsynUser,
1301                buf: &mut [u8],
1302                next: &mut dyn OctetNext,
1303            ) -> AsynResult<OctetReadResult> {
1304                next.read(user, buf)
1305            }
1306            fn write(
1307                &mut self,
1308                user: &mut AsynUser,
1309                data: &[u8],
1310                next: &mut dyn OctetNext,
1311            ) -> AsynResult<usize> {
1312                next.write(user, data)
1313            }
1314            fn flush(&mut self, user: &mut AsynUser, next: &mut dyn OctetNext) -> AsynResult<()> {
1315                next.flush(user)
1316            }
1317        }
1318
1319        let port: Arc<Mutex<dyn PortDriver>> = Arc::new(Mutex::new(TestDriver::new()));
1320
1321        {
1322            let mut guard = port.lock();
1323            guard
1324                .base_mut()
1325                .push_octet_interpose(Box::new(NoopInterpose));
1326            assert_eq!(guard.base().interpose_octet.len(), 1);
1327        }
1328    }
1329
1330    #[test]
1331    fn test_default_read_write_int64() {
1332        let mut base = PortDriverBase::new("test_i64", 1, PortFlags::default());
1333        base.create_param("BIG", ParamType::Int64).unwrap();
1334
1335        struct I64Driver {
1336            base: PortDriverBase,
1337        }
1338        impl PortDriver for I64Driver {
1339            fn base(&self) -> &PortDriverBase {
1340                &self.base
1341            }
1342            fn base_mut(&mut self) -> &mut PortDriverBase {
1343                &mut self.base
1344            }
1345        }
1346
1347        let mut drv = I64Driver { base };
1348        let mut user = AsynUser::new(0);
1349        drv.write_int64(&mut user, i64::MAX).unwrap();
1350        assert_eq!(drv.read_int64(&AsynUser::new(0)).unwrap(), i64::MAX);
1351    }
1352
1353    #[test]
1354    fn test_get_bounds_int64_default() {
1355        let base = PortDriverBase::new("test_bounds", 1, PortFlags::default());
1356        struct BoundsDriver {
1357            base: PortDriverBase,
1358        }
1359        impl PortDriver for BoundsDriver {
1360            fn base(&self) -> &PortDriverBase {
1361                &self.base
1362            }
1363            fn base_mut(&mut self) -> &mut PortDriverBase {
1364                &mut self.base
1365            }
1366        }
1367        let drv = BoundsDriver { base };
1368        let (lo, hi) = drv.get_bounds_int64(&AsynUser::default()).unwrap();
1369        assert_eq!(lo, i64::MIN);
1370        assert_eq!(hi, i64::MAX);
1371    }
1372
1373    #[test]
1374    fn test_per_addr_device_state() {
1375        let mut base = PortDriverBase::new(
1376            "multi",
1377            4,
1378            PortFlags {
1379                multi_device: true,
1380                can_block: false,
1381                destructible: true,
1382            },
1383        );
1384        base.create_param("V", ParamType::Int32).unwrap();
1385
1386        // Default: all connected
1387        assert!(base.is_device_connected(0));
1388        assert!(base.is_device_connected(1));
1389
1390        // Disable addr 1
1391        base.device_state(1).enabled = false;
1392        assert!(base.check_ready_addr(0).is_ok());
1393        let err = base.check_ready_addr(1).unwrap_err();
1394        assert!(format!("{err}").contains("disabled"));
1395
1396        // Disconnect addr 2
1397        base.device_state(2).connected = false;
1398        let err = base.check_ready_addr(2).unwrap_err();
1399        assert!(format!("{err}").contains("disconnected"));
1400    }
1401
1402    #[test]
1403    fn test_per_addr_single_device_ignored() {
1404        let mut base = PortDriverBase::new("single", 1, PortFlags::default());
1405        base.create_param("V", ParamType::Int32).unwrap();
1406        // For single-device, per-addr check passes even if no device state
1407        assert!(base.check_ready_addr(0).is_ok());
1408    }
1409
1410    #[test]
1411    fn test_timestamp_source() {
1412        let mut base = PortDriverBase::new("ts_test", 1, PortFlags::default());
1413        base.create_param("V", ParamType::Int32).unwrap();
1414
1415        let fixed_ts = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(999999);
1416        base.register_timestamp_source(move || fixed_ts);
1417
1418        assert_eq!(base.current_timestamp(), fixed_ts);
1419    }
1420
1421    #[test]
1422    fn test_timestamp_source_in_callbacks() {
1423        let mut base = PortDriverBase::new("ts_cb", 1, PortFlags::default());
1424        base.create_param("V", ParamType::Int32).unwrap();
1425        let mut rx = base.interrupts.subscribe_async();
1426
1427        let fixed_ts = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(123456);
1428        base.register_timestamp_source(move || fixed_ts);
1429
1430        struct TsDriver {
1431            base: PortDriverBase,
1432        }
1433        impl PortDriver for TsDriver {
1434            fn base(&self) -> &PortDriverBase {
1435                &self.base
1436            }
1437            fn base_mut(&mut self) -> &mut PortDriverBase {
1438                &mut self.base
1439            }
1440        }
1441        let mut drv = TsDriver { base };
1442        drv.base_mut().set_int32_param(0, 0, 42).unwrap();
1443        drv.base_mut().call_param_callbacks(0).unwrap();
1444
1445        let v = rx.try_recv().unwrap();
1446        // Should use fixed_ts since no per-param timestamp is set
1447        assert_eq!(v.timestamp, fixed_ts);
1448    }
1449
1450    #[test]
1451    fn test_queue_priority_connect() {
1452        assert!(QueuePriority::Connect > QueuePriority::High);
1453    }
1454
1455    #[test]
1456    fn test_port_flags_destructible_default_is_opt_in() {
1457        // C asyn parity: ASYN_DESTRUCTIBLE (0x0004, asynDriver.h:97) is
1458        // a `registerPort` attribute that callers opt into. Default
1459        // must be false so drivers don't accidentally accept a
1460        // shutdownPort call. PortDriver authors that want shutdown
1461        // support set `destructible: true` explicitly.
1462        let flags = PortFlags::default();
1463        assert!(
1464            !flags.destructible,
1465            "destructible must be opt-in (C parity)"
1466        );
1467    }
1468
1469    #[test]
1470    fn shutdown_lifecycle_refuses_non_destructible() {
1471        let mut base = PortDriverBase::new(
1472            "p_nondestr",
1473            1,
1474            PortFlags {
1475                multi_device: false,
1476                can_block: false,
1477                destructible: false,
1478            },
1479        );
1480        match base.shutdown_lifecycle() {
1481            Err(AsynError::Status { message, .. }) => {
1482                assert!(message.contains("ASYN_DESTRUCTIBLE"), "msg={message}");
1483            }
1484            other => panic!("expected ASYN_DESTRUCTIBLE refusal, got {other:?}"),
1485        }
1486        assert!(
1487            !base.is_defunct(),
1488            "non-destructible port must not flip defunct"
1489        );
1490        assert!(base.is_enabled(), "non-destructible port must stay enabled");
1491    }
1492
1493    #[test]
1494    fn shutdown_lifecycle_marks_destructible_defunct_and_idempotent() {
1495        let mut base = PortDriverBase::new(
1496            "p_destr",
1497            1,
1498            PortFlags {
1499                multi_device: false,
1500                can_block: false,
1501                destructible: true,
1502            },
1503        );
1504        assert!(base.is_enabled());
1505        assert!(!base.is_defunct());
1506        base.shutdown_lifecycle().unwrap();
1507        assert!(
1508            !base.is_enabled(),
1509            "shutdown_lifecycle must flip enabled=false"
1510        );
1511        assert!(
1512            base.is_defunct(),
1513            "shutdown_lifecycle must flip defunct=true"
1514        );
1515        // Idempotent — second call is Ok and leaves state unchanged.
1516        base.shutdown_lifecycle().unwrap();
1517        assert!(base.is_defunct());
1518        // check_ready surfaces the defunct state for every request.
1519        match base.check_ready() {
1520            Err(AsynError::Status { message, .. }) => {
1521                assert!(message.contains("defunct"), "msg={message}");
1522            }
1523            other => panic!("expected defunct error, got {other:?}"),
1524        }
1525    }
1526
1527    // --- Phase 2B: per-addr connect/disconnect/enable/disable ---
1528
1529    #[test]
1530    fn test_connect_addr() {
1531        let mut base = PortDriverBase::new(
1532            "multi_conn",
1533            4,
1534            PortFlags {
1535                multi_device: true,
1536                can_block: false,
1537                destructible: true,
1538            },
1539        );
1540        base.create_param("V", ParamType::Int32).unwrap();
1541
1542        base.disconnect_addr(1);
1543        assert!(!base.is_device_connected(1));
1544        assert!(base.check_ready_addr(1).is_err());
1545
1546        base.connect_addr(1);
1547        assert!(base.is_device_connected(1));
1548        assert!(base.check_ready_addr(1).is_ok());
1549    }
1550
1551    #[test]
1552    fn test_enable_disable_addr() {
1553        let mut base = PortDriverBase::new(
1554            "multi_en",
1555            4,
1556            PortFlags {
1557                multi_device: true,
1558                can_block: false,
1559                destructible: true,
1560            },
1561        );
1562        base.create_param("V", ParamType::Int32).unwrap();
1563
1564        base.disable_addr(2);
1565        let err = base.check_ready_addr(2).unwrap_err();
1566        assert!(format!("{err}").contains("disabled"));
1567
1568        base.enable_addr(2);
1569        assert!(base.check_ready_addr(2).is_ok());
1570    }
1571
1572    #[test]
1573    fn test_port_level_overrides_addr() {
1574        let mut base = PortDriverBase::new(
1575            "multi_override",
1576            4,
1577            PortFlags {
1578                multi_device: true,
1579                can_block: false,
1580                destructible: true,
1581            },
1582        );
1583        base.create_param("V", ParamType::Int32).unwrap();
1584
1585        // Port-level disabled overrides addr-level enabled
1586        base.enabled = false;
1587        base.enable_addr(0); // addr 0 is enabled, but port is disabled
1588        let err = base.check_ready_addr(0).unwrap_err();
1589        assert!(format!("{err}").contains("disabled"));
1590    }
1591
1592    #[test]
1593    fn test_per_addr_exception_announced() {
1594        use std::sync::atomic::{AtomicI32, Ordering};
1595
1596        let mut base = PortDriverBase::new(
1597            "multi_exc",
1598            4,
1599            PortFlags {
1600                multi_device: true,
1601                can_block: false,
1602                destructible: true,
1603            },
1604        );
1605        base.create_param("V", ParamType::Int32).unwrap();
1606
1607        let exc_mgr = Arc::new(crate::exception::ExceptionManager::new());
1608        base.exception_sink = Some(exc_mgr.clone());
1609
1610        let last_addr = Arc::new(AtomicI32::new(-99));
1611        let last_addr2 = last_addr.clone();
1612        exc_mgr.add_callback(move |event| {
1613            last_addr2.store(event.addr, Ordering::Relaxed);
1614        });
1615
1616        base.disconnect_addr(3);
1617        assert_eq!(last_addr.load(Ordering::Relaxed), 3);
1618
1619        base.enable_addr(2);
1620        assert_eq!(last_addr.load(Ordering::Relaxed), 2);
1621    }
1622}