Skip to main content

fission_shell_winit/
nfc.rs

1use fission_core::{
2    NfcAvailability, NfcEmulationRequest, NfcError, NfcRecord, NfcScanRequest, NfcSessionReceipt,
3    NfcTag, NfcTechnology, NfcWriteRequest, CANCEL_NFC_SESSION, EMULATE_NFC_TAG,
4    GET_NFC_AVAILABILITY, SCAN_NFC_TAG, WRITE_NFC_TAG,
5};
6use fission_shell::async_host::AsyncRegistry;
7use std::sync::Arc;
8
9/// Host-side NFC provider used by shell capability registration.
10pub trait NfcHost: Send + Sync + 'static {
11    /// Returns NFC support, enabled state, and supported operation modes.
12    fn availability(&self) -> Result<NfcAvailability, NfcError>;
13    /// Starts a read session and returns the first matching NFC tag.
14    ///
15    /// `request` carries technology filters, prompt text, timeout, and record
16    /// collection behavior.
17    fn scan_tag(&self, request: NfcScanRequest) -> Result<NfcTag, NfcError>;
18    /// Starts a write session for the supplied NFC records.
19    fn write_tag(&self, request: NfcWriteRequest) -> Result<NfcSessionReceipt, NfcError>;
20    /// Starts card emulation for hosts and hardware that support it.
21    fn emulate_tag(&self, request: NfcEmulationRequest) -> Result<NfcSessionReceipt, NfcError>;
22    /// Cancels the active NFC scan, write, or emulation session.
23    fn cancel_session(&self) -> Result<(), NfcError>;
24}
25
26/// Default provider used when the active shell has no NFC integration.
27#[derive(Debug, Default)]
28pub struct UnsupportedNfcHost;
29
30impl NfcHost for UnsupportedNfcHost {
31    fn availability(&self) -> Result<NfcAvailability, NfcError> {
32        Ok(NfcAvailability::default())
33    }
34
35    fn scan_tag(&self, _request: NfcScanRequest) -> Result<NfcTag, NfcError> {
36        Err(NfcError::unsupported("scan_tag"))
37    }
38
39    fn write_tag(&self, _request: NfcWriteRequest) -> Result<NfcSessionReceipt, NfcError> {
40        Err(NfcError::unsupported("write_tag"))
41    }
42
43    fn emulate_tag(&self, _request: NfcEmulationRequest) -> Result<NfcSessionReceipt, NfcError> {
44        Err(NfcError::unsupported("emulate_tag"))
45    }
46
47    fn cancel_session(&self) -> Result<(), NfcError> {
48        Err(NfcError::unsupported("cancel_session"))
49    }
50}
51
52/// In-process NFC host for smoke tests and non-OS environments.
53#[derive(Debug, Clone)]
54pub struct MemoryNfcHost {
55    tag: NfcTag,
56}
57
58impl MemoryNfcHost {
59    pub fn new(tag: NfcTag) -> Self {
60        Self { tag }
61    }
62}
63
64impl Default for MemoryNfcHost {
65    fn default() -> Self {
66        Self {
67            tag: NfcTag {
68                id: Some(vec![0xF1, 0x55, 0x10, 0x01]),
69                technologies: vec![NfcTechnology::Ndef],
70                records: vec![NfcRecord::uri("fission://memory-nfc")],
71                raw_payload: None,
72            },
73        }
74    }
75}
76
77impl NfcHost for MemoryNfcHost {
78    fn availability(&self) -> Result<NfcAvailability, NfcError> {
79        Ok(NfcAvailability {
80            supported: true,
81            enabled: true,
82            read: true,
83            write: true,
84            card_emulation: true,
85        })
86    }
87
88    fn scan_tag(&self, _request: NfcScanRequest) -> Result<NfcTag, NfcError> {
89        Ok(self.tag.clone())
90    }
91
92    fn write_tag(&self, request: NfcWriteRequest) -> Result<NfcSessionReceipt, NfcError> {
93        Ok(NfcSessionReceipt {
94            session_id: Some(format!("memory-nfc-write-{}", request.records.len())),
95            completed: true,
96        })
97    }
98
99    fn emulate_tag(&self, request: NfcEmulationRequest) -> Result<NfcSessionReceipt, NfcError> {
100        Ok(NfcSessionReceipt {
101            session_id: Some(format!("memory-nfc-emulate-{}", request.records.len())),
102            completed: true,
103        })
104    }
105
106    fn cancel_session(&self) -> Result<(), NfcError> {
107        Ok(())
108    }
109}
110
111pub(crate) fn register_nfc_capabilities(
112    async_registry: &mut AsyncRegistry,
113    host: Arc<dyn NfcHost>,
114) {
115    let availability_host = host.clone();
116    async_registry.register_operation_capability(GET_NFC_AVAILABILITY, move |(), _| {
117        let host = availability_host.clone();
118        async move { host.availability() }
119    });
120
121    let scan_host = host.clone();
122    async_registry.register_operation_capability(SCAN_NFC_TAG, move |request, _| {
123        let host = scan_host.clone();
124        async move { host.scan_tag(request) }
125    });
126
127    let write_host = host.clone();
128    async_registry.register_operation_capability(WRITE_NFC_TAG, move |request, _| {
129        let host = write_host.clone();
130        async move { host.write_tag(request) }
131    });
132
133    let emulate_host = host.clone();
134    async_registry.register_operation_capability(EMULATE_NFC_TAG, move |request, _| {
135        let host = emulate_host.clone();
136        async move { host.emulate_tag(request) }
137    });
138
139    async_registry.register_operation_capability(CANCEL_NFC_SESSION, move |(), _| {
140        let host = host.clone();
141        async move { host.cancel_session() }
142    });
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn unsupported_host_reports_unavailable() {
151        let host = UnsupportedNfcHost;
152        let availability = host.availability().unwrap();
153
154        assert!(!availability.supported);
155        assert!(host.scan_tag(NfcScanRequest::default()).is_err());
156    }
157
158    #[test]
159    fn memory_host_scans_and_writes() {
160        let host = MemoryNfcHost::default();
161        let tag = host.scan_tag(NfcScanRequest::default()).unwrap();
162        assert_eq!(tag.technologies, vec![NfcTechnology::Ndef]);
163
164        let receipt = host
165            .write_tag(NfcWriteRequest {
166                records: vec![NfcRecord::text("en", "ok")],
167                ..Default::default()
168            })
169            .unwrap();
170        assert!(receipt.completed);
171    }
172}