1use qrcode::{EcLevel, QrCode};
2
3#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
4pub struct QrCodeMatrix {
5 pub size: u32,
7 pub modules: String,
10}
11
12#[uniffi::export]
16pub fn encode_text_qr(text: String) -> Option<QrCodeMatrix> {
17 let trimmed = text.trim();
18 if trimmed.is_empty() {
19 return None;
20 }
21 let code = QrCode::with_error_correction_level(trimmed.as_bytes(), EcLevel::M).ok()?;
22 let width = code.width();
23 let cells = code.to_colors();
24 let mut modules = String::with_capacity(width * width);
25 for color in cells {
26 modules.push(if color == qrcode::Color::Dark {
27 '1'
28 } else {
29 '0'
30 });
31 }
32 Some(QrCodeMatrix {
33 size: width as u32,
34 modules,
35 })
36}
37
38#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
39pub struct DeviceApprovalQrPayload {
40 pub owner_input: String,
41 pub device_input: String,
42}
43
44#[uniffi::export]
45pub fn encode_device_approval_qr(_owner_input: String, _device_input: String) -> String {
46 String::new()
47}
48
49#[uniffi::export]
50pub fn decode_device_approval_qr(raw: String) -> Option<DeviceApprovalQrPayload> {
51 nostr_double_ratchet::parse_compact_device_link_request(&raw)
52 .ok()
53 .map(|request| DeviceApprovalQrPayload {
54 owner_input: String::new(),
55 device_input: request.device_app_key_pubkey.to_hex(),
56 })
57}
58
59#[cfg(test)]
60mod tests {
61 use super::{decode_device_approval_qr, encode_device_approval_qr, DeviceApprovalQrPayload};
62 use nostr_sdk::Keys;
63
64 #[test]
65 fn removed_device_approval_qr_encoder_returns_empty() {
66 let encoded = encode_device_approval_qr("npub-owner".into(), "npub-device".into());
67 assert_eq!(encoded, "");
68 }
69
70 #[test]
71 fn compact_device_approval_qr_rejects_wrong_inputs() {
72 assert!(decode_device_approval_qr("".into()).is_none());
73 assert!(decode_device_approval_qr("npub1plainvalue".into()).is_none());
74 assert!(decode_device_approval_qr("https://example.com".into()).is_none());
75 assert!(decode_device_approval_qr("not-a-compact-link-code".into()).is_none());
76 assert!(
77 decode_device_approval_qr(format!("{}.{}.extra", "1".repeat(64), "1".repeat(64)))
78 .is_none()
79 );
80 }
81
82 #[test]
83 fn compact_device_link_qr_decodes_to_device() {
84 let device = Keys::generate();
85 let request = Keys::generate();
86 let encoded = format!(
87 "{}.{}",
88 device.public_key().to_hex(),
89 request.secret_key().to_secret_hex()
90 );
91
92 let decoded = decode_device_approval_qr(encoded.clone()).expect("decode compact request");
93 assert_eq!(
94 decoded,
95 DeviceApprovalQrPayload {
96 owner_input: String::new(),
97 device_input: device.public_key().to_hex(),
98 }
99 );
100
101 let prefixed = format!("prefix:{encoded}");
102 assert!(decode_device_approval_qr(prefixed).is_none());
103 }
104}