avmnif_rs/
port.rs

1//! Port collection macros for AtomVM
2//! 
3//! Provides safe Rust wrappers around AtomVM's port driver API
4//!
5//! # Design Philosophy
6//!
7//! All operations work with any AtomTableOps implementation through dependency injection.
8//! No global state, no hardcoded dependencies.
9
10use crate::term::{Term, NifError, TermValue};
11use crate::context::{Context, GlobalContext, ContextExt, PlatformData, PortBuilder};
12use crate::atom::{AtomTableOps, AtomTable};
13use core::ffi::{c_void, c_char, c_int};
14
15// Suppress warnings for unused items since this is a library
16#[allow(unused_imports)]
17use alloc::boxed::Box;
18
19// AtomVM port types (reuse from context module)
20pub type ErlNifEnv = c_void;
21pub type ERL_NIF_TERM = u64;
22
23/// Port message type
24pub type Message = c_void;
25
26/// Port result enum
27#[repr(C)]
28pub enum PortResult {
29    Continue = 0,
30    Terminate = 1,
31}
32
33/// Port driver function type signatures
34pub type PortInitFn = fn(&mut GlobalContext);
35pub type PortDestroyFn = fn(&mut GlobalContext);  
36pub type PortCreateFn = fn(&GlobalContext, Term) -> *mut Context;
37pub type PortHandlerFn = fn(&mut Context, &Message) -> PortResult;
38
39/// C-compatible function types for FFI boundary
40type CPortCreateFn = extern "C" fn(*const GlobalContext, ERL_NIF_TERM) -> *mut Context;
41type CPortHandlerFn = extern "C" fn(*mut Context, *const Message) -> PortResult;
42
43/// Port driver registration structure
44#[repr(C)]
45pub struct AtomVMPortDriver {
46    pub name: *const c_char,
47    pub init: Option<PortInitFn>,
48    pub destroy: Option<PortDestroyFn>,
49    pub create_port: CPortCreateFn,
50    pub message_handler: CPortHandlerFn,
51}
52
53unsafe impl Sync for AtomVMPortDriver {}
54
55// AtomVM Port API FFI declarations
56extern "C" {
57    /// Send a reply to an Erlang process from port context
58    pub fn port_send_reply(
59        ctx: *mut Context,
60        pid: ERL_NIF_TERM,
61        reference: ERL_NIF_TERM,
62        reply: ERL_NIF_TERM,
63    );
64    
65    /// Send an async message to an Erlang process from any context (ISR-safe)
66    pub fn port_send_message_from_task(
67        global: *mut GlobalContext,
68        pid: u32,
69        message: ERL_NIF_TERM,
70    );
71    
72    /// Parse a generic port message into components
73    pub fn parse_port_message(
74        message: *const Message,
75        pid: *mut ERL_NIF_TERM,
76        reference: *mut ERL_NIF_TERM,
77        command: *mut ERL_NIF_TERM,
78    ) -> c_int;
79}
80
81/// Register a port collection with AtomVM
82/// 
83/// # Usage
84/// ```rust,ignore
85/// use avmnif_rs::port_collection;
86/// 
87/// port_collection!(
88///     my_port,
89///     init = my_port_init,
90///     destroy = my_port_destroy,
91///     create_port = my_port_create,
92///     handler = my_port_handler
93/// );
94/// ```
95#[macro_export]
96macro_rules! port_collection {
97    (
98        $port_name:ident,
99        init = $init_fn:ident,
100        destroy = $destroy_fn:ident,
101        create_port = $create_port_fn:ident,
102        handler = $handler_fn:ident
103    ) => {
104        paste::paste! {
105            // Wrapper functions that convert between C and Rust types
106            extern "C" fn [<$create_port_fn _wrapper>](
107                global: *const $crate::context::GlobalContext,
108                opts: $crate::port::ERL_NIF_TERM
109            ) -> *mut $crate::context::Context {
110                let global_ref = unsafe { &*global };
111                let opts_term = $crate::term::Term::from_raw(opts.try_into().unwrap());
112                $create_port_fn(global_ref, opts_term)
113            }
114            
115            extern "C" fn [<$handler_fn _wrapper>](
116                ctx: *mut $crate::context::Context,
117                message: *const $crate::port::Message
118            ) -> $crate::port::PortResult {
119                let ctx_ref = unsafe { &mut *ctx };
120                let message_ref = unsafe { &*message };
121                $handler_fn(ctx_ref, message_ref)
122            }
123            
124            // Create the port driver structure using wrapper functions
125            static [<$port_name:upper _PORT_DRIVER>]: $crate::port::AtomVMPortDriver = $crate::port::AtomVMPortDriver {
126                name: concat!(stringify!($port_name), "\0").as_ptr() as *const core::ffi::c_char,
127                init: Some($init_fn),
128                destroy: Some($destroy_fn),
129                create_port: [<$create_port_fn _wrapper>],
130                message_handler: [<$handler_fn _wrapper>],
131            };
132            
133            // Export the port driver registration function
134            #[no_mangle]
135            pub extern "C" fn [<$port_name _port_driver_init>]() -> *const $crate::port::AtomVMPortDriver {
136                &[<$port_name:upper _PORT_DRIVER>]
137            }
138            
139            // Export individual functions for debugging/testing
140            #[no_mangle]
141            pub extern "C" fn [<$port_name _init>](global: *mut $crate::context::GlobalContext) {
142                let global_ref = unsafe { &mut *global };
143                $init_fn(global_ref);
144            }
145            
146            #[no_mangle]
147            pub extern "C" fn [<$port_name _destroy>](global: *mut $crate::context::GlobalContext) {
148                let global_ref = unsafe { &mut *global };
149                $destroy_fn(global_ref);
150            }
151            
152            #[no_mangle]
153            pub extern "C" fn [<$port_name _create_port>](
154                global: *const $crate::context::GlobalContext,
155                opts: $crate::port::ERL_NIF_TERM
156            ) -> *mut $crate::context::Context {
157                [<$create_port_fn _wrapper>](global, opts)
158            }
159            
160            #[no_mangle]
161            pub extern "C" fn [<$port_name _message_handler>](
162                ctx: *mut $crate::context::Context,
163                message: *const $crate::port::Message
164            ) -> $crate::port::PortResult {
165                [<$handler_fn _wrapper>](ctx, message)
166            }
167        }
168    };
169    
170    // Version without init/destroy functions
171    (
172        $port_name:ident,
173        create_port = $create_port_fn:ident,
174        handler = $handler_fn:ident
175    ) => {
176        paste::paste! {
177            // Wrapper functions that convert between C and Rust types
178            extern "C" fn [<$create_port_fn _wrapper>](
179                global: *const $crate::context::GlobalContext,
180                opts: $crate::port::ERL_NIF_TERM
181            ) -> *mut $crate::context::Context {
182                let global_ref = unsafe { &*global };
183                let opts_term = $crate::term::Term::from_raw(opts.try_into().unwrap());
184                $create_port_fn(global_ref, opts_term)
185            }
186            
187            extern "C" fn [<$handler_fn _wrapper>](
188                ctx: *mut $crate::context::Context,
189                message: *const $crate::port::Message
190            ) -> $crate::port::PortResult {
191                let ctx_ref = unsafe { &mut *ctx };
192                let message_ref = unsafe { &*message };
193                $handler_fn(ctx_ref, message_ref)
194            }
195            
196            static [<$port_name:upper _PORT_DRIVER>]: $crate::port::AtomVMPortDriver = $crate::port::AtomVMPortDriver {
197                name: concat!(stringify!($port_name), "\0").as_ptr() as *const core::ffi::c_char,
198                init: None,
199                destroy: None,
200                create_port: [<$create_port_fn _wrapper>],
201                message_handler: [<$handler_fn _wrapper>],
202            };
203            
204            #[no_mangle]
205            pub extern "C" fn [<$port_name _port_driver_init>]() -> *const $crate::port::AtomVMPortDriver {
206                &[<$port_name:upper _PORT_DRIVER>]
207            }
208            
209            #[no_mangle]
210            pub extern "C" fn [<$port_name _create_port>](
211                global: *const $crate::context::GlobalContext,
212                opts: $crate::port::ERL_NIF_TERM
213            ) -> *mut $crate::context::Context {
214                [<$create_port_fn _wrapper>](global, opts)
215            }
216            
217            #[no_mangle]
218            pub extern "C" fn [<$port_name _message_handler>](
219                ctx: *mut $crate::context::Context,
220                message: *const $crate::port::Message
221            ) -> $crate::port::PortResult {
222                [<$handler_fn _wrapper>](ctx, message)
223            }
224        }
225    };
226}
227
228/// Helper functions for port message handling
229
230/// Parse a generic port message into its components
231pub fn parse_gen_message(message: &Message) -> Result<(Term, Term, Term), NifError> {
232    let mut pid: u64 = 0;
233    let mut reference: u64 = 0;
234    let mut command: u64 = 0;
235    
236    let result = unsafe {
237        parse_port_message(
238            message as *const _ as *const c_void,
239            &mut pid,
240            &mut reference,
241            &mut command,
242        )
243    };
244    
245    if result != 0 {
246        Ok((
247            Term::from_raw(pid.try_into().unwrap()),
248            Term::from_raw(reference.try_into().unwrap()),
249            Term::from_raw(command.try_into().unwrap()),
250        ))
251    } else {
252        Err(NifError::BadArg)
253    }
254}
255
256/// Send a reply to an Erlang process
257pub fn send_reply(ctx: &Context, pid: Term, reference: Term, reply: Term) {
258    unsafe {
259        port_send_reply(
260            ctx as *const _ as *mut Context,
261            pid.raw().try_into().unwrap(),
262            reference.raw().try_into().unwrap(),
263            reply.raw().try_into().unwrap(),
264        );
265    }
266}
267
268/// Send an async message to an Erlang process (ISR-safe)
269pub fn send_async_message(pid: u32, message: Term) {
270    unsafe {
271        port_send_message_from_task(
272            crate::context::get_global_context(),
273            pid,
274            message.raw().try_into().unwrap(),
275        );
276    }
277}
278
279/// Trait for port data types to implement cleanup and message handling
280pub trait PortData: PlatformData {
281    /// Called when the port receives a message
282    fn handle_message(&mut self, message: &Message) -> PortResult {
283        let _ = message; // Suppress unused warning
284        PortResult::Continue
285    }
286    
287    /// Get the owner PID for this port (if any)
288    fn get_owner_pid(&self) -> Option<u32> {
289        None
290    }
291    
292    /// Set the owner PID for this port
293    fn set_owner_pid(&mut self, _pid: u32) {}
294    
295    /// Check if the port is active
296    fn is_active(&self) -> bool {
297        true
298    }
299    
300    /// Activate/deactivate the port
301    fn set_active(&mut self, _active: bool) {}
302}
303
304/// Generic port data wrapper with standard functionality
305#[repr(C)]
306pub struct GenericPortData<T: PortData> {
307    pub inner: T,
308    pub owner_pid: u32,
309    pub active: bool,
310}
311
312impl<T: PortData> GenericPortData<T> {
313    pub fn new(inner: T) -> Self {
314        Self {
315            inner,
316            owner_pid: 0,
317            active: false,
318        }
319    }
320    
321    pub fn set_owner(&mut self, pid: u32) {
322        self.owner_pid = pid;
323        self.active = true;
324        self.inner.set_owner_pid(pid);
325    }
326    
327    pub fn deactivate(&mut self) {
328        self.active = false;
329        self.inner.set_active(false);
330        self.inner.cleanup();
331    }
332    
333    pub fn get_inner(&self) -> &T {
334        &self.inner
335    }
336    
337    pub fn get_inner_mut(&mut self) -> &mut T {
338        &mut self.inner
339    }
340}
341
342impl<T: PortData> PlatformData for GenericPortData<T> {
343    fn cleanup(&mut self) {
344        self.deactivate();
345    }
346}
347
348impl<T: PortData> PortData for GenericPortData<T> {
349    fn handle_message(&mut self, message: &Message) -> PortResult {
350        if self.active {
351            self.inner.handle_message(message)
352        } else {
353            PortResult::Terminate
354        }
355    }
356    
357    fn get_owner_pid(&self) -> Option<u32> {
358        if self.owner_pid != 0 {
359            Some(self.owner_pid)
360        } else {
361            None
362        }
363    }
364    
365    fn set_owner_pid(&mut self, pid: u32) {
366        self.owner_pid = pid;
367    }
368    
369    fn is_active(&self) -> bool {
370        self.active
371    }
372    
373    fn set_active(&mut self, active: bool) {
374        self.active = active;
375    }
376}
377
378/// Macro for creating simple port data structures
379#[macro_export]
380macro_rules! port_data {
381    (
382        $name:ident {
383            $(
384                $field:ident: $field_type:ty
385            ),* $(,)?
386        }
387    ) => {
388        #[repr(C)]
389        pub struct $name {
390            $(
391                pub $field: $field_type,
392            )*
393        }
394        
395        impl $crate::context::PlatformData for $name {}
396        impl $crate::port::PortData for $name {}
397        
398        impl $name {
399            pub fn new() -> Self {
400                Self {
401                    $(
402                        $field: Default::default(),
403                    )*
404                }
405            }
406        }
407        
408        impl Default for $name {
409            fn default() -> Self {
410                Self::new()
411            }
412        }
413    };
414}
415
416/// Error handling for port operations
417#[derive(Debug, Clone, Copy)]
418pub enum PortError {
419    /// Invalid message format
420    InvalidMessage,
421    /// Port not active
422    PortInactive,
423    /// Hardware error
424    HardwareError,
425    /// Out of memory
426    OutOfMemory,
427    /// Generic error
428    Generic,
429}
430
431impl From<PortError> for PortResult {
432    fn from(_error: PortError) -> Self {
433        PortResult::Terminate
434    }
435}
436
437/// Result type for port operations
438pub type PortOpResult<T> = Result<T, PortError>;
439
440/// Utility functions for common port operations
441
442/// Extract PID as u32 from Term (for use in async messaging)
443pub fn term_to_pid(term: Term) -> PortOpResult<u32> {
444    // This would need to be implemented based on actual Term structure
445    // For now, return a placeholder
446    Ok(term.raw() as u32) // This is obviously wrong, but demonstrates the interface
447}
448
449/// Create a standard error reply using any atom table
450pub fn create_error_reply<T: AtomTableOps>(reason: &str, table: &T) -> Result<Term, NifError> {
451    // Create an error tuple: {error, Reason}
452    let error_atom = table.ensure_atom_str("error").map_err(|_| NifError::BadArg)?;
453    let reason_atom = table.ensure_atom_str(reason).map_err(|_| NifError::BadArg)?;
454    
455    // This would use the actual term construction API
456    // For now, return a placeholder that shows the pattern
457    let _ = (error_atom, reason_atom);
458    Ok(Term::from_raw(0)) // Obviously wrong, but demonstrates interface
459}
460
461/// Create a standard success reply using any atom table
462pub fn create_ok_reply<T: AtomTableOps>(data: Term, table: &T) -> Result<Term, NifError> {
463    // Create an ok tuple: {ok, Data}
464    let ok_atom = table.ensure_atom_str("ok").map_err(|_| NifError::BadArg)?;
465    
466    // This would use the actual term construction API
467    let _ = (ok_atom, data);
468    Ok(Term::from_raw(0)) // Obviously wrong, but demonstrates interface
469}
470
471/// Generic standard message handler template
472pub fn handle_standard_message<T: PortData>(
473    ctx: &mut Context,
474    message: &Message,
475) -> PortResult {
476    // Get the atom table from the global context
477    let table = AtomTable::from_global();
478    
479    let port_data = unsafe {
480        let data_ptr = ctx.get_platform_data_as::<GenericPortData<T>>();
481        if data_ptr.is_null() {
482            return PortResult::Terminate;
483        }
484        &mut *data_ptr
485    };
486    
487    if let Ok((pid, reference, command)) = parse_gen_message(message) {
488        // Convert command to TermValue for pattern matching
489        let command_value = match command.to_value() {
490            Ok(val) => val,
491            Err(_) => {
492                if let Ok(reply) = create_error_reply("invalid_command", &table) {
493                    send_reply(ctx, pid, reference, reply);
494                }
495                return PortResult::Continue;
496            }
497        };
498        
499        // Handle standard commands using TermValue pattern matching with the table
500        if command_value.is_atom_str("start", &table) {
501            if let Ok(pid_u32) = term_to_pid(pid) {
502                port_data.set_owner(pid_u32);
503                if let Ok(reply) = create_ok_reply(Term::from_raw(0), &table) {
504                    send_reply(ctx, pid, reference, reply);
505                }
506                PortResult::Continue
507            } else {
508                if let Ok(reply) = create_error_reply("invalid_pid", &table) {
509                    send_reply(ctx, pid, reference, reply);
510                }
511                PortResult::Continue
512            }
513        } else if command_value.is_atom_str("stop", &table) {
514            port_data.deactivate();
515            if let Ok(reply) = create_ok_reply(Term::from_raw(0), &table) {
516                send_reply(ctx, pid, reference, reply);
517            }
518            PortResult::Terminate
519        } else if command_value.is_atom_str("status", &table) {
520            let _status = if port_data.is_active() {
521                "active"
522            } else {
523                "inactive"
524            };
525            if let Ok(reply) = create_ok_reply(Term::from_raw(0), &table) {
526                send_reply(ctx, pid, reference, reply);
527            }
528            PortResult::Continue
529        } else {
530            // Delegate to the port data's message handler
531            port_data.handle_message(message)
532        }
533    } else {
534        PortResult::Terminate
535    }
536}
537
538/// Create a port with automatic platform data setup
539pub fn create_port_with_data<T: PortData>(
540    global: &GlobalContext,
541    data: T,
542) -> *mut Context {
543    let wrapped_data = GenericPortData::new(data);
544    PortBuilder::new(wrapped_data).build(global)
545}
546
547/// Create a port with data and user term
548pub fn create_port_with_data_and_term<T: PortData>(
549    global: &GlobalContext,
550    data: T,
551    user_term: Term,
552) -> *mut Context {
553    let wrapped_data = GenericPortData::new(data);
554    PortBuilder::new(wrapped_data).build_with_user_term(global, user_term)
555}
556
557/// Safely execute a function with port data
558pub fn with_port_data<T: PortData, R, F>(ctx: &Context, f: F) -> Option<R>
559where
560    F: FnOnce(&GenericPortData<T>) -> R,
561{
562    unsafe {
563        let data_ptr = ctx.get_platform_data_as::<GenericPortData<T>>();
564        if data_ptr.is_null() {
565            None
566        } else {
567            Some(f(&*data_ptr))
568        }
569    }
570}
571
572/// Safely execute a function with mutable port data
573pub fn with_port_data_mut<T: PortData, R, F>(ctx: &mut Context, f: F) -> Option<R>
574where
575    F: FnOnce(&mut GenericPortData<T>) -> R,
576{
577    unsafe {
578        let data_ptr = ctx.get_platform_data_as::<GenericPortData<T>>();
579        if data_ptr.is_null() {
580            None
581        } else {
582            Some(f(&mut *data_ptr))
583        }
584    }
585}
586
587/// High-level port creation macro that handles common patterns
588#[macro_export]
589macro_rules! simple_port {
590    (
591        $port_name:ident,
592        data = $data_type:ty,
593        init_data = $init_expr:expr
594    ) => {
595        fn [<$port_name _create>](global: &$crate::context::GlobalContext, opts: $crate::term::Term) -> *mut $crate::context::Context {
596            let _ = opts; // suppress unused warning
597            let data: $data_type = $init_expr;
598            $crate::port::create_port_with_data(global, data)
599        }
600        
601        fn [<$port_name _handler>](ctx: &mut $crate::context::Context, message: &$crate::port::Message) -> $crate::port::PortResult {
602            $crate::port::handle_standard_message::<$data_type>(ctx, message)
603        }
604        
605        $crate::port_collection!(
606            $port_name,
607            create_port = [<$port_name _create>],
608            handler = [<$port_name _handler>]
609        );
610    };
611    
612    (
613        $port_name:ident,
614        data = $data_type:ty,
615        init_data = $init_expr:expr,
616        init = $init_fn:ident,
617        destroy = $destroy_fn:ident
618    ) => {
619        fn [<$port_name _create>](global: &$crate::context::GlobalContext, opts: $crate::term::Term) -> *mut $crate::context::Context {
620            let _ = opts; // suppress unused warning
621            let data: $data_type = $init_expr;
622            $crate::port::create_port_with_data(global, data)
623        }
624        
625        fn [<$port_name _handler>](ctx: &mut $crate::context::Context, message: &$crate::port::Message) -> $crate::port::PortResult {
626            $crate::port::handle_standard_message::<$data_type>(ctx, message)
627        }
628        
629        $crate::port_collection!(
630            $port_name,
631            init = $init_fn,
632            destroy = $destroy_fn,
633            create_port = [<$port_name _create>],
634            handler = [<$port_name _handler>]
635        );
636    };
637}