Skip to main content

nfc/
lib.rs

1//! Safe Rust bindings for `libnfc` built on top of [`nfc-sys`].
2//!
3//! The [`ffi`] module re-exports the raw `nfc-sys` API for advanced use cases.
4//! The top-level API in this crate focuses on ownership, resource cleanup, and
5//! slice-based wrappers for the parts of libnfc that can be expressed safely.
6//!
7//! Some libnfc capabilities remain raw-only because the C API itself requires
8//! unchecked callbacks or driver pointers. In particular, custom driver
9//! registration and target emulation state machines still live in [`ffi`].
10//!
11//! # Native Dependency
12//!
13//! This crate links against a system installation of `libnfc`. You must install
14//! the native library before building or running code that depends on `nfc`.
15//! The underlying `nfc-sys` build script handles the actual linking step.
16//!
17//! Common setups:
18//!
19//! - macOS: `brew install libnfc`
20//! - Debian/Ubuntu: install the `libnfc` development package
21//! - Custom installs: configure your linker environment as needed for `libnfc`
22//!
23//! If you need a libnfc capability that is not exposed through the safe API,
24//! use [`ffi`] directly and keep the native library installed as above.
25//!
26//! # Example
27//!
28//! ```no_run
29//! use nfc::{version, Context};
30//!
31//! fn main() -> nfc::Result<()> {
32//!     let context = Context::new()?;
33//!     println!("libnfc version: {}", version());
34//!
35//!     for connstring in context.list_devices(8)? {
36//!         println!("found device: {connstring}");
37//!     }
38//!
39//!     Ok(())
40//! }
41//! ```
42
43pub mod error;
44pub mod ffi;
45
46pub use crate::error::{Error, Result};
47
48use std::borrow::Cow;
49use std::ffi::{CStr, CString};
50use std::marker::PhantomData;
51use std::mem::MaybeUninit;
52use std::os::raw::c_char;
53use std::ptr::{self, NonNull};
54use std::rc::Rc;
55
56/// Owns a libnfc context and releases it with `nfc_exit` on drop.
57///
58/// Create this with [`Context::new`] and keep it alive for as long as any
59/// [`Device`] values borrowed from it remain open.
60pub struct Context {
61    raw: NonNull<ffi::nfc_context>,
62    _not_send_sync: PhantomData<Rc<()>>,
63}
64
65/// Owns an open NFC device and closes it with `nfc_close` on drop.
66///
67/// A `Device` borrows its parent [`Context`], which keeps the lifetime relation
68/// between the native libnfc handles explicit in Rust.
69pub struct Device<'ctx> {
70    raw: NonNull<ffi::nfc_device>,
71    _context: PhantomData<&'ctx Context>,
72    _not_send_sync: PhantomData<Rc<()>>,
73}
74
75/// Result of a timed transceive operation.
76///
77/// libnfc reports both the amount of received data and the number of hardware
78/// cycles spent on the exchange.
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub struct TimedResponse {
81    /// Number of bytes or bits reported by libnfc, depending on the call used.
82    pub received: usize,
83    /// Number of cycles reported by libnfc.
84    pub cycles: u32,
85}
86
87impl Context {
88    /// Initializes libnfc and returns an owned context.
89    ///
90    /// This requires a working native `libnfc` installation at runtime. If
91    /// libnfc cannot initialize, this returns an [`Error`].
92    pub fn new() -> Result<Self> {
93        let mut raw = ptr::null_mut();
94        unsafe {
95            ffi::nfc_init(&mut raw);
96        }
97
98        let raw = NonNull::new(raw).ok_or_else(|| Error::new("libnfc returned a null context"))?;
99
100        Ok(Self {
101            raw,
102            _not_send_sync: PhantomData,
103        })
104    }
105
106    /// Returns the borrowed raw libnfc context pointer.
107    ///
108    /// This is mainly useful when integrating with [`ffi`].
109    pub fn as_ptr(&self) -> *mut ffi::nfc_context {
110        self.raw.as_ptr()
111    }
112
113    /// Opens an NFC device.
114    ///
115    /// Pass `None` to let libnfc choose the default device, or a connection
116    /// string previously returned by [`Context::list_devices`].
117    ///
118    /// The returned [`Device`] closes itself automatically when dropped.
119    pub fn open(&self, connstring: Option<&str>) -> Result<Device<'_>> {
120        let connstring = match connstring {
121            Some(value) => Some(
122                CString::new(value)
123                    .map_err(|_| Error::new("connection string contains an interior NUL byte"))?,
124            ),
125            None => None,
126        };
127
128        let raw = unsafe {
129            ffi::nfc_open(
130                self.raw.as_ptr(),
131                connstring.as_ref().map_or(ptr::null(), |value| value.as_ptr()),
132            )
133        };
134
135        let raw = NonNull::new(raw).ok_or_else(|| Error::new("libnfc could not open an NFC device"))?;
136
137        Ok(Device {
138            raw,
139            _context: PhantomData,
140            _not_send_sync: PhantomData,
141        })
142    }
143
144    /// Lists up to `max` discoverable device connection strings.
145    ///
146    /// The strings returned here can be passed back into [`Context::open`] to
147    /// target a specific reader.
148    pub fn list_devices(&self, max: usize) -> Result<Vec<String>> {
149        if max == 0 {
150            return Ok(Vec::new());
151        }
152
153        let mut connstrings = vec![[0 as c_char; ffi::NFC_BUFSIZE_CONNSTRING]; max];
154        let count = unsafe {
155            ffi::nfc_list_devices(self.raw.as_ptr(), connstrings.as_mut_ptr(), connstrings.len())
156        };
157
158        Ok(connstrings
159            .into_iter()
160            .take(count)
161            .map(|connstring| c_char_array_to_string(&connstring))
162            .collect())
163    }
164}
165
166impl Drop for Context {
167    fn drop(&mut self) {
168        unsafe {
169            ffi::nfc_exit(self.raw.as_ptr());
170        }
171    }
172}
173
174impl Device<'_> {
175    /// Returns the borrowed raw libnfc device pointer.
176    ///
177    /// This is mainly useful when integrating with [`ffi`].
178    pub fn as_ptr(&self) -> *mut ffi::nfc_device {
179        self.raw.as_ptr()
180    }
181
182    /// Returns the device name as a borrowed C string.
183    ///
184    /// This avoids allocation and is useful when interoperating with other
185    /// C-oriented APIs.
186    pub fn name_cstr(&self) -> &CStr {
187        unsafe { CStr::from_ptr(ffi::nfc_device_get_name(self.raw.as_ptr())) }
188    }
189
190    /// Returns the device name as UTF-8, replacing invalid bytes if needed.
191    pub fn name(&self) -> Cow<'_, str> {
192        self.name_cstr().to_string_lossy()
193    }
194
195    /// Returns the device connection string as a borrowed C string.
196    pub fn connstring_cstr(&self) -> &CStr {
197        unsafe { CStr::from_ptr(ffi::nfc_device_get_connstring(self.raw.as_ptr())) }
198    }
199
200    /// Returns the device connection string as UTF-8, replacing invalid bytes if needed.
201    pub fn connstring(&self) -> Cow<'_, str> {
202        self.connstring_cstr().to_string_lossy()
203    }
204
205    /// Returns the last libnfc error code recorded for this device.
206    ///
207    /// This is mostly helpful for diagnostics; normal safe API calls already
208    /// convert failures into [`Error`].
209    pub fn last_error_code(&self) -> i32 {
210        unsafe { ffi::nfc_device_get_last_error(self.raw.as_ptr()) }
211    }
212
213    /// Returns the last libnfc error message recorded for this device.
214    pub fn last_error_message(&self) -> Cow<'_, str> {
215        unsafe { CStr::from_ptr(ffi::nfc_strerror(self.raw.as_ptr())).to_string_lossy() }
216    }
217
218    /// Returns the human-readable information string for this device.
219    ///
220    /// libnfc allocates the underlying C string and this wrapper frees it for
221    /// you before returning an owned [`String`].
222    pub fn information_about(&self) -> Result<String> {
223        let mut buffer = ptr::null_mut();
224        check_code(
225            self.raw.as_ptr(),
226            unsafe { ffi::nfc_device_get_information_about(self.raw.as_ptr(), &mut buffer) },
227        )?;
228
229        unsafe { owned_libnfc_string(buffer) }
230    }
231
232    /// Returns the supported modulation types for the requested mode.
233    ///
234    /// Use [`ffi::nfc_mode::N_INITIATOR`] for reader mode and
235    /// [`ffi::nfc_mode::N_TARGET`] for emulation mode.
236    pub fn supported_modulations(
237        &self,
238        mode: ffi::nfc_mode,
239    ) -> Result<Vec<ffi::nfc_modulation_type>> {
240        let mut values = ptr::null();
241        check_code(
242            self.raw.as_ptr(),
243            unsafe { ffi::nfc_device_get_supported_modulation(self.raw.as_ptr(), mode, &mut values) },
244        )?;
245
246        decode_modulation_types(values)
247    }
248
249    /// Returns the supported baud rates for a modulation type.
250    pub fn supported_baud_rates(
251        &self,
252        modulation_type: ffi::nfc_modulation_type,
253    ) -> Result<Vec<ffi::nfc_baud_rate>> {
254        let mut values = ptr::null();
255        check_code(
256            self.raw.as_ptr(),
257            unsafe {
258                ffi::nfc_device_get_supported_baud_rate(self.raw.as_ptr(), modulation_type, &mut values)
259            },
260        )?;
261
262        decode_baud_rates(values)
263    }
264
265    /// Returns the supported baud rates for target mode.
266    pub fn supported_target_baud_rates(
267        &self,
268        modulation_type: ffi::nfc_modulation_type,
269    ) -> Result<Vec<ffi::nfc_baud_rate>> {
270        let mut values = ptr::null();
271        check_code(
272            self.raw.as_ptr(),
273            unsafe {
274                ffi::nfc_device_get_supported_baud_rate_target_mode(
275                    self.raw.as_ptr(),
276                    modulation_type,
277                    &mut values,
278                )
279            },
280        )?;
281
282        decode_baud_rates(values)
283    }
284
285    /// Sets an integer device property.
286    ///
287    /// This is the safe wrapper over `nfc_device_set_property_int`.
288    pub fn set_property_int(&mut self, property: ffi::nfc_property, value: i32) -> Result<()> {
289        check_code(
290            self.raw.as_ptr(),
291            unsafe { ffi::nfc_device_set_property_int(self.raw.as_ptr(), property, value) },
292        )?;
293        Ok(())
294    }
295
296    /// Sets a boolean device property.
297    ///
298    /// This is the safe wrapper over `nfc_device_set_property_bool`.
299    pub fn set_property_bool(&mut self, property: ffi::nfc_property, enabled: bool) -> Result<()> {
300        check_code(
301            self.raw.as_ptr(),
302            unsafe { ffi::nfc_device_set_property_bool(self.raw.as_ptr(), property, enabled) },
303        )?;
304        Ok(())
305    }
306
307    /// Initializes this device in initiator mode.
308    ///
309    /// Call this before reader-style operations such as
310    /// [`Device::select_passive_target`] or [`Device::transceive_bytes`].
311    pub fn initiator_init(&mut self) -> Result<()> {
312        check_code(self.raw.as_ptr(), unsafe { ffi::nfc_initiator_init(self.raw.as_ptr()) })?;
313        Ok(())
314    }
315
316    /// Initializes this device's secure element in initiator mode.
317    pub fn initiator_init_secure_element(&mut self) -> Result<()> {
318        check_code(
319            self.raw.as_ptr(),
320            unsafe { ffi::nfc_initiator_init_secure_element(self.raw.as_ptr()) },
321        )?;
322        Ok(())
323    }
324
325    /// Selects a passive target and returns it if one was found.
326    ///
327    /// `init_data` is the optional initialization payload expected by libnfc
328    /// for the selected modulation. An empty slice means "no init data".
329    pub fn select_passive_target(
330        &mut self,
331        modulation: ffi::nfc_modulation,
332        init_data: &[u8],
333    ) -> Result<Option<ffi::nfc_target>> {
334        let mut target = MaybeUninit::<ffi::nfc_target>::uninit();
335        let result = unsafe {
336            ffi::nfc_initiator_select_passive_target(
337                self.raw.as_ptr(),
338                modulation,
339                ptr_or_null(init_data),
340                init_data.len(),
341                target.as_mut_ptr(),
342            )
343        };
344
345        if result < 0 {
346            return Err(Error::from_device(self.raw.as_ptr(), result));
347        }
348
349        if result == 0 {
350            return Ok(None);
351        }
352
353        Ok(Some(unsafe { target.assume_init() }))
354    }
355
356    /// Lists passive targets matching the requested modulation.
357    ///
358    /// `max_targets` controls the maximum number of targets libnfc will write
359    /// into the returned vector.
360    pub fn list_passive_targets(
361        &mut self,
362        modulation: ffi::nfc_modulation,
363        max_targets: usize,
364    ) -> Result<Vec<ffi::nfc_target>> {
365        let mut targets = Vec::<MaybeUninit<ffi::nfc_target>>::with_capacity(max_targets);
366        let result = unsafe {
367            ffi::nfc_initiator_list_passive_targets(
368                self.raw.as_ptr(),
369                modulation,
370                targets.as_mut_ptr().cast(),
371                max_targets,
372            )
373        };
374
375        if result < 0 {
376            return Err(Error::from_device(self.raw.as_ptr(), result));
377        }
378
379        let count = result as usize;
380        unsafe {
381            targets.set_len(count);
382        }
383
384        Ok(targets
385            .into_iter()
386            .map(|target| unsafe { target.assume_init() })
387            .collect())
388    }
389
390    /// Polls for a target using one or more modulations.
391    ///
392    /// `poll_count` and `period` are passed directly to libnfc and therefore
393    /// use libnfc's native polling semantics.
394    pub fn poll_target(
395        &mut self,
396        modulations: &[ffi::nfc_modulation],
397        poll_count: u8,
398        period: u8,
399    ) -> Result<Option<ffi::nfc_target>> {
400        let mut target = MaybeUninit::<ffi::nfc_target>::uninit();
401        let result = unsafe {
402            ffi::nfc_initiator_poll_target(
403                self.raw.as_ptr(),
404                ptr_or_null(modulations),
405                modulations.len(),
406                poll_count,
407                period,
408                target.as_mut_ptr(),
409            )
410        };
411
412        if result < 0 {
413            return Err(Error::from_device(self.raw.as_ptr(), result));
414        }
415
416        if result == 0 {
417            return Ok(None);
418        }
419
420        Ok(Some(unsafe { target.assume_init() }))
421    }
422
423    /// Selects a DEP target and returns it if one was found.
424    ///
425    /// Pass `None` for `initiator_info` when you do not need to provide custom
426    /// DEP negotiation data.
427    pub fn select_dep_target(
428        &mut self,
429        mode: ffi::nfc_dep_mode,
430        baud_rate: ffi::nfc_baud_rate,
431        initiator_info: Option<&ffi::nfc_dep_info>,
432        timeout: i32,
433    ) -> Result<Option<ffi::nfc_target>> {
434        let mut target = MaybeUninit::<ffi::nfc_target>::uninit();
435        let result = unsafe {
436            ffi::nfc_initiator_select_dep_target(
437                self.raw.as_ptr(),
438                mode,
439                baud_rate,
440                initiator_info.map_or(ptr::null(), |value| value),
441                target.as_mut_ptr(),
442                timeout,
443            )
444        };
445
446        if result < 0 {
447            return Err(Error::from_device(self.raw.as_ptr(), result));
448        }
449
450        if result == 0 {
451            return Ok(None);
452        }
453
454        Ok(Some(unsafe { target.assume_init() }))
455    }
456
457    /// Polls for a DEP target and returns it if one was found.
458    pub fn poll_dep_target(
459        &mut self,
460        mode: ffi::nfc_dep_mode,
461        baud_rate: ffi::nfc_baud_rate,
462        initiator_info: Option<&ffi::nfc_dep_info>,
463        timeout: i32,
464    ) -> Result<Option<ffi::nfc_target>> {
465        let mut target = MaybeUninit::<ffi::nfc_target>::uninit();
466        let result = unsafe {
467            ffi::nfc_initiator_poll_dep_target(
468                self.raw.as_ptr(),
469                mode,
470                baud_rate,
471                initiator_info.map_or(ptr::null(), |value| value),
472                target.as_mut_ptr(),
473                timeout,
474            )
475        };
476
477        if result < 0 {
478            return Err(Error::from_device(self.raw.as_ptr(), result));
479        }
480
481        if result == 0 {
482            return Ok(None);
483        }
484
485        Ok(Some(unsafe { target.assume_init() }))
486    }
487
488    /// Deselects the currently selected target.
489    pub fn deselect_target(&mut self) -> Result<()> {
490        check_code(
491            self.raw.as_ptr(),
492            unsafe { ffi::nfc_initiator_deselect_target(self.raw.as_ptr()) },
493        )?;
494        Ok(())
495    }
496
497    /// Sends bytes to the current target and writes the reply into `rx`.
498    ///
499    /// The returned value is the number of bytes written into `rx`.
500    ///
501    /// If `rx` is too small, libnfc reports an error and this method returns
502    /// that error instead of truncating the response.
503    pub fn transceive_bytes(&mut self, tx: &[u8], rx: &mut [u8], timeout: i32) -> Result<usize> {
504        let result = unsafe {
505            ffi::nfc_initiator_transceive_bytes(
506                self.raw.as_ptr(),
507                ptr_or_null(tx),
508                tx.len(),
509                mut_ptr_or_null(rx),
510                rx.len(),
511                timeout,
512            )
513        };
514
515        Ok(check_code(self.raw.as_ptr(), result)? as usize)
516    }
517
518    /// Sends bit frames to the current target and writes the reply into `rx`.
519    ///
520    /// The returned value is the number of bits reported by libnfc.
521    ///
522    /// `tx_bits` lets you send a partial final byte. Parity buffers are passed
523    /// through to libnfc unchanged when provided.
524    pub fn transceive_bits(
525        &mut self,
526        tx: &[u8],
527        tx_bits: usize,
528        tx_parity: Option<&[u8]>,
529        rx: &mut [u8],
530        rx_parity: Option<&mut [u8]>,
531    ) -> Result<usize> {
532        validate_bit_frame_args(tx, tx_bits, tx_parity, rx, rx_parity.as_deref())?;
533
534        let result = unsafe {
535            ffi::nfc_initiator_transceive_bits(
536                self.raw.as_ptr(),
537                ptr_or_null(tx),
538                tx_bits,
539                tx_parity.map_or(ptr::null(), ptr_or_null),
540                mut_ptr_or_null(rx),
541                rx.len(),
542                rx_parity.map_or(ptr::null_mut(), mut_ptr_or_null),
543            )
544        };
545
546        Ok(check_code(self.raw.as_ptr(), result)? as usize)
547    }
548
549    /// Sends bytes and returns both the received length and libnfc cycle count.
550    pub fn transceive_bytes_timed(
551        &mut self,
552        tx: &[u8],
553        rx: &mut [u8],
554    ) -> Result<TimedResponse> {
555        let mut cycles = 0;
556        let result = unsafe {
557            ffi::nfc_initiator_transceive_bytes_timed(
558                self.raw.as_ptr(),
559                ptr_or_null(tx),
560                tx.len(),
561                mut_ptr_or_null(rx),
562                rx.len(),
563                &mut cycles,
564            )
565        };
566
567        Ok(TimedResponse {
568            received: check_code(self.raw.as_ptr(), result)? as usize,
569            cycles,
570        })
571    }
572
573    /// Sends bit frames and returns both the received length and libnfc cycle count.
574    pub fn transceive_bits_timed(
575        &mut self,
576        tx: &[u8],
577        tx_bits: usize,
578        tx_parity: Option<&[u8]>,
579        rx: &mut [u8],
580        rx_parity: Option<&mut [u8]>,
581    ) -> Result<TimedResponse> {
582        validate_bit_frame_args(tx, tx_bits, tx_parity, rx, rx_parity.as_deref())?;
583
584        let mut cycles = 0;
585        let result = unsafe {
586            ffi::nfc_initiator_transceive_bits_timed(
587                self.raw.as_ptr(),
588                ptr_or_null(tx),
589                tx_bits,
590                tx_parity.map_or(ptr::null(), ptr_or_null),
591                mut_ptr_or_null(rx),
592                rx.len(),
593                rx_parity.map_or(ptr::null_mut(), mut_ptr_or_null),
594                &mut cycles,
595            )
596        };
597
598        Ok(TimedResponse {
599            received: check_code(self.raw.as_ptr(), result)? as usize,
600            cycles,
601        })
602    }
603
604    /// Checks whether the provided target is still present.
605    ///
606    /// Returns `Ok(false)` when libnfc reports that the target has been
607    /// released, and `Err` for other libnfc failures.
608    pub fn target_is_present(&mut self, target: &ffi::nfc_target) -> Result<bool> {
609        let result = unsafe { ffi::nfc_initiator_target_is_present(self.raw.as_ptr(), target) };
610        if result == ffi::NFC_ETGRELEASED {
611            return Ok(false);
612        }
613
614        check_code(self.raw.as_ptr(), result)?;
615        Ok(true)
616    }
617
618    /// Initializes this device in target emulation mode.
619    ///
620    /// The returned value is the number of bytes written into `rx`.
621    ///
622    /// This covers libnfc's basic target mode setup. More advanced emulator
623    /// callback state machines are still available only through [`ffi`].
624    pub fn target_init(
625        &mut self,
626        target: &mut ffi::nfc_target,
627        rx: &mut [u8],
628        timeout: i32,
629    ) -> Result<usize> {
630        let result = unsafe {
631            ffi::nfc_target_init(
632                self.raw.as_ptr(),
633                target,
634                mut_ptr_or_null(rx),
635                rx.len(),
636                timeout,
637            )
638        };
639
640        Ok(check_code(self.raw.as_ptr(), result)? as usize)
641    }
642
643    /// Sends bytes while acting as a target.
644    pub fn target_send_bytes(&mut self, tx: &[u8], timeout: i32) -> Result<usize> {
645        let result = unsafe {
646            ffi::nfc_target_send_bytes(
647                self.raw.as_ptr(),
648                ptr_or_null(tx),
649                tx.len(),
650                timeout,
651            )
652        };
653
654        Ok(check_code(self.raw.as_ptr(), result)? as usize)
655    }
656
657    /// Receives bytes while acting as a target.
658    pub fn target_receive_bytes(&mut self, rx: &mut [u8], timeout: i32) -> Result<usize> {
659        let result = unsafe {
660            ffi::nfc_target_receive_bytes(
661                self.raw.as_ptr(),
662                mut_ptr_or_null(rx),
663                rx.len(),
664                timeout,
665            )
666        };
667
668        Ok(check_code(self.raw.as_ptr(), result)? as usize)
669    }
670
671    /// Sends raw bit frames while acting as a target.
672    pub fn target_send_bits(
673        &mut self,
674        tx: &[u8],
675        tx_bits: usize,
676        tx_parity: Option<&[u8]>,
677    ) -> Result<usize> {
678        validate_tx_bits_args(tx, tx_bits, tx_parity)?;
679
680        let result = unsafe {
681            ffi::nfc_target_send_bits(
682                self.raw.as_ptr(),
683                ptr_or_null(tx),
684                tx_bits,
685                tx_parity.map_or(ptr::null(), ptr_or_null),
686            )
687        };
688
689        Ok(check_code(self.raw.as_ptr(), result)? as usize)
690    }
691
692    /// Receives raw bit frames while acting as a target.
693    pub fn target_receive_bits(
694        &mut self,
695        rx: &mut [u8],
696        rx_parity: Option<&mut [u8]>,
697    ) -> Result<usize> {
698        validate_rx_parity_args(rx, rx_parity.as_deref())?;
699
700        let result = unsafe {
701            ffi::nfc_target_receive_bits(
702                self.raw.as_ptr(),
703                mut_ptr_or_null(rx),
704                rx.len(),
705                rx_parity.map_or(ptr::null_mut(), mut_ptr_or_null),
706            )
707        };
708
709        Ok(check_code(self.raw.as_ptr(), result)? as usize)
710    }
711
712    /// Switches the device to idle mode.
713    pub fn idle(&mut self) -> Result<()> {
714        check_code(self.raw.as_ptr(), unsafe { ffi::nfc_idle(self.raw.as_ptr()) })?;
715        Ok(())
716    }
717
718    /// Aborts the currently running command.
719    pub fn abort_command(&mut self) -> Result<()> {
720        check_code(
721            self.raw.as_ptr(),
722            unsafe { ffi::nfc_abort_command(self.raw.as_ptr()) },
723        )?;
724        Ok(())
725    }
726}
727
728impl Drop for Device<'_> {
729    fn drop(&mut self) {
730        unsafe {
731            ffi::nfc_close(self.raw.as_ptr());
732        }
733    }
734}
735
736/// Returns the linked libnfc version string.
737///
738/// This requires the native `libnfc` library to be available at runtime.
739pub fn version_cstr() -> &'static CStr {
740    unsafe { CStr::from_ptr(ffi::nfc_version()) }
741}
742
743/// Returns the linked libnfc version string as UTF-8.
744pub fn version() -> Cow<'static, str> {
745    version_cstr().to_string_lossy()
746}
747
748/// Converts a modulation type to the corresponding libnfc display string.
749pub fn modulation_type_name(value: ffi::nfc_modulation_type) -> Cow<'static, str> {
750    unsafe { CStr::from_ptr(ffi::str_nfc_modulation_type(value)).to_string_lossy() }
751}
752
753/// Converts a baud rate to the corresponding libnfc display string.
754pub fn baud_rate_name(value: ffi::nfc_baud_rate) -> Cow<'static, str> {
755    unsafe { CStr::from_ptr(ffi::str_nfc_baud_rate(value)).to_string_lossy() }
756}
757
758/// Formats an NFC target using libnfc's built-in target formatter.
759///
760/// For lower-level formatting control, use the raw formatter in [`ffi`].
761pub fn target_to_string(target: &ffi::nfc_target, verbose: bool) -> Result<String> {
762    let mut buffer = ptr::null_mut();
763    let result = unsafe { ffi::str_nfc_target(&mut buffer, target, verbose) };
764    if result < 0 {
765        return Err(Error::new(format!("libnfc could not format target: error code {result}")));
766    }
767
768    unsafe { owned_libnfc_string(buffer) }
769}
770
771/// Computes the ISO14443-A CRC for the provided data.
772///
773/// This allocates a temporary owned buffer because libnfc expects a mutable
774/// pointer even though the payload itself is not modified.
775pub fn iso14443a_crc(data: &[u8]) -> [u8; 2] {
776    let mut owned = data.to_vec();
777    let mut crc = [0u8; 2];
778    unsafe {
779        ffi::iso14443a_crc(owned.as_mut_ptr(), data.len(), crc.as_mut_ptr());
780    }
781    crc
782}
783
784/// Appends an ISO14443-A CRC to the provided buffer.
785///
786/// The buffer is extended by exactly two bytes.
787pub fn append_iso14443a_crc(data: &mut Vec<u8>) {
788    let payload_len = data.len();
789    data.resize(payload_len + 2, 0);
790    unsafe {
791        ffi::iso14443a_crc_append(data.as_mut_ptr(), payload_len);
792    }
793}
794
795/// Computes the ISO14443-B CRC for the provided data.
796pub fn iso14443b_crc(data: &[u8]) -> [u8; 2] {
797    let mut owned = data.to_vec();
798    let mut crc = [0u8; 2];
799    unsafe {
800        ffi::iso14443b_crc(owned.as_mut_ptr(), data.len(), crc.as_mut_ptr());
801    }
802    crc
803}
804
805/// Appends an ISO14443-B CRC to the provided buffer.
806///
807/// The buffer is extended by exactly two bytes.
808pub fn append_iso14443b_crc(data: &mut Vec<u8>) {
809    let payload_len = data.len();
810    data.resize(payload_len + 2, 0);
811    unsafe {
812        ffi::iso14443b_crc_append(data.as_mut_ptr(), payload_len);
813    }
814}
815
816/// Locates the ISO14443-A historical bytes inside an ATS buffer.
817///
818/// Returns a subslice of `ats` when libnfc can identify the historical bytes,
819/// or `None` if no such region is present.
820pub fn locate_iso14443a_historical_bytes(ats: &mut [u8]) -> Option<&mut [u8]> {
821    let mut historical_len = 0;
822    let ptr = unsafe {
823        ffi::iso14443a_locate_historical_bytes(ats.as_mut_ptr(), ats.len(), &mut historical_len)
824    };
825
826    if ptr.is_null() {
827        return None;
828    }
829
830    let start = unsafe { ptr.offset_from(ats.as_ptr()) };
831    if start < 0 {
832        return None;
833    }
834
835    let start = start as usize;
836    let end = start.checked_add(historical_len)?;
837    if end > ats.len() {
838        return None;
839    }
840
841    Some(&mut ats[start..end])
842}
843
844fn check_code(device: *mut ffi::nfc_device, code: i32) -> Result<i32> {
845    if code < 0 {
846        Err(Error::from_device(device, code))
847    } else {
848        Ok(code)
849    }
850}
851
852fn c_char_array_to_string(connstring: &ffi::nfc_connstring) -> String {
853    unsafe { CStr::from_ptr(connstring.as_ptr()).to_string_lossy().into_owned() }
854}
855
856unsafe fn owned_libnfc_string(buffer: *mut c_char) -> Result<String> {
857    if buffer.is_null() {
858        return Ok(String::new());
859    }
860
861    struct OwnedString(*mut c_char);
862
863    impl Drop for OwnedString {
864        fn drop(&mut self) {
865            unsafe {
866                ffi::nfc_free(self.0.cast());
867            }
868        }
869    }
870
871    let buffer = OwnedString(buffer);
872    Ok(CStr::from_ptr(buffer.0).to_string_lossy().into_owned())
873}
874
875fn decode_modulation_types(
876    mut values: *const ffi::nfc_modulation_type,
877) -> Result<Vec<ffi::nfc_modulation_type>> {
878    let mut out = Vec::new();
879    while !values.is_null() {
880        let raw = unsafe { *(values as *const i32) };
881        let value = match raw {
882            0 => break,
883            1 => ffi::nfc_modulation_type::NMT_ISO14443A,
884            2 => ffi::nfc_modulation_type::NMT_JEWEL,
885            3 => ffi::nfc_modulation_type::NMT_ISO14443B,
886            4 => ffi::nfc_modulation_type::NMT_ISO14443BI,
887            5 => ffi::nfc_modulation_type::NMT_ISO14443B2SR,
888            6 => ffi::nfc_modulation_type::NMT_ISO14443B2CT,
889            7 => ffi::nfc_modulation_type::NMT_FELICA,
890            8 => ffi::nfc_modulation_type::NMT_DEP,
891            9 => ffi::nfc_modulation_type::NMT_BARCODE,
892            10 => ffi::nfc_modulation_type::NMT_ISO14443BICLASS,
893            _ => return Err(Error::new(format!("libnfc returned an unknown modulation type: {raw}"))),
894        };
895        out.push(value);
896        values = unsafe { values.add(1) };
897    }
898    Ok(out)
899}
900
901fn decode_baud_rates(mut values: *const ffi::nfc_baud_rate) -> Result<Vec<ffi::nfc_baud_rate>> {
902    let mut out = Vec::new();
903    while !values.is_null() {
904        let raw = unsafe { *(values as *const i32) };
905        let value = match raw {
906            0 => break,
907            1 => ffi::nfc_baud_rate::NBR_106,
908            2 => ffi::nfc_baud_rate::NBR_212,
909            3 => ffi::nfc_baud_rate::NBR_424,
910            4 => ffi::nfc_baud_rate::NBR_847,
911            _ => return Err(Error::new(format!("libnfc returned an unknown baud rate: {raw}"))),
912        };
913        out.push(value);
914        values = unsafe { values.add(1) };
915    }
916    Ok(out)
917}
918
919fn ptr_or_null<T>(slice: &[T]) -> *const T {
920    if slice.is_empty() {
921        ptr::null()
922    } else {
923        slice.as_ptr()
924    }
925}
926
927fn mut_ptr_or_null<T>(slice: &mut [T]) -> *mut T {
928    if slice.is_empty() {
929        ptr::null_mut()
930    } else {
931        slice.as_mut_ptr()
932    }
933}
934
935fn validate_bit_frame_args(
936    tx: &[u8],
937    tx_bits: usize,
938    tx_parity: Option<&[u8]>,
939    rx: &[u8],
940    rx_parity: Option<&[u8]>,
941) -> Result<()> {
942    validate_tx_bits_args(tx, tx_bits, tx_parity)?;
943    validate_rx_parity_args(rx, rx_parity)
944}
945
946fn validate_tx_bits_args(tx: &[u8], tx_bits: usize, tx_parity: Option<&[u8]>) -> Result<()> {
947    let required_tx_bytes = tx_bits.div_ceil(8);
948    if required_tx_bytes > tx.len() {
949        return Err(Error::new(format!(
950            "tx_bits ({tx_bits}) requires at least {required_tx_bytes} bytes, but tx only has {}",
951            tx.len()
952        )));
953    }
954
955    if let Some(parity) = tx_parity {
956        if parity.len() < required_tx_bytes {
957            return Err(Error::new(format!(
958                "tx_parity must contain at least {required_tx_bytes} bytes, but only {} were provided",
959                parity.len()
960            )));
961        }
962    }
963
964    Ok(())
965}
966
967fn validate_rx_parity_args(rx: &[u8], rx_parity: Option<&[u8]>) -> Result<()> {
968    if let Some(parity) = rx_parity {
969        if parity.len() < rx.len() {
970            return Err(Error::new(format!(
971                "rx_parity must contain at least {} bytes to match rx, but only {} were provided",
972                rx.len(),
973                parity.len()
974            )));
975        }
976    }
977
978    Ok(())
979}
980
981#[cfg(test)]
982mod tests {
983    use super::*;
984
985    #[test]
986    fn version_is_non_empty() {
987        assert!(!version().is_empty());
988    }
989
990    #[test]
991    fn append_iso14443a_crc_matches_direct_crc() {
992        let mut payload = vec![0x01, 0x02, 0x03];
993        let crc = iso14443a_crc(&payload);
994        append_iso14443a_crc(&mut payload);
995
996        assert_eq!(&payload[3..], &crc);
997    }
998
999    #[test]
1000    fn append_iso14443b_crc_matches_direct_crc() {
1001        let mut payload = vec![0x04, 0x05, 0x06];
1002        let crc = iso14443b_crc(&payload);
1003        append_iso14443b_crc(&mut payload);
1004
1005        assert_eq!(&payload[3..], &crc);
1006    }
1007}