1use 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#[allow(unused_imports)]
17use alloc::boxed::Box;
18
19pub type ErlNifEnv = c_void;
21pub type ERL_NIF_TERM = u64;
22
23pub type Message = c_void;
25
26#[repr(C)]
28pub enum PortResult {
29 Continue = 0,
30 Terminate = 1,
31}
32
33pub 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
39type CPortCreateFn = extern "C" fn(*const GlobalContext, ERL_NIF_TERM) -> *mut Context;
41type CPortHandlerFn = extern "C" fn(*mut Context, *const Message) -> PortResult;
42
43#[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
55extern "C" {
57 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 pub fn port_send_message_from_task(
67 global: *mut GlobalContext,
68 pid: u32,
69 message: ERL_NIF_TERM,
70 );
71
72 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#[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 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 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 #[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 #[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 (
172 $port_name:ident,
173 create_port = $create_port_fn:ident,
174 handler = $handler_fn:ident
175 ) => {
176 paste::paste! {
177 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
228pub 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
256pub 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
268pub 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
279pub trait PortData: PlatformData {
281 fn handle_message(&mut self, message: &Message) -> PortResult {
283 let _ = message; PortResult::Continue
285 }
286
287 fn get_owner_pid(&self) -> Option<u32> {
289 None
290 }
291
292 fn set_owner_pid(&mut self, _pid: u32) {}
294
295 fn is_active(&self) -> bool {
297 true
298 }
299
300 fn set_active(&mut self, _active: bool) {}
302}
303
304#[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_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#[derive(Debug, Clone, Copy)]
418pub enum PortError {
419 InvalidMessage,
421 PortInactive,
423 HardwareError,
425 OutOfMemory,
427 Generic,
429}
430
431impl From<PortError> for PortResult {
432 fn from(_error: PortError) -> Self {
433 PortResult::Terminate
434 }
435}
436
437pub type PortOpResult<T> = Result<T, PortError>;
439
440pub fn term_to_pid(term: Term) -> PortOpResult<u32> {
444 Ok(term.raw() as u32) }
448
449pub fn create_error_reply<T: AtomTableOps>(reason: &str, table: &T) -> Result<Term, NifError> {
451 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 let _ = (error_atom, reason_atom);
458 Ok(Term::from_raw(0)) }
460
461pub fn create_ok_reply<T: AtomTableOps>(data: Term, table: &T) -> Result<Term, NifError> {
463 let ok_atom = table.ensure_atom_str("ok").map_err(|_| NifError::BadArg)?;
465
466 let _ = (ok_atom, data);
468 Ok(Term::from_raw(0)) }
470
471pub fn handle_standard_message<T: PortData>(
473 ctx: &mut Context,
474 message: &Message,
475) -> PortResult {
476 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 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 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 port_data.handle_message(message)
532 }
533 } else {
534 PortResult::Terminate
535 }
536}
537
538pub 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
547pub 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
557pub 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
572pub 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#[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; 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; 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}