use crate::error::{Error, ErrorCode};
use rqrr::{MetaData, PreparedImage};
use super::QR_PREFIX;
use alloc::string::String;
extern crate alloc;
pub fn scan_luma(width: usize, height: usize, luma: &[u8]) -> Result<String, Error> {
let pixels = width.checked_mul(height).ok_or(ErrorCode::InvalidData)?;
if luma.len() < pixels {
return Err(ErrorCode::InvalidData.into());
}
scan_luma_with(width, height, |x, y| luma[y * width + x])
}
pub fn scan_luma_with<F>(width: usize, height: usize, fill: F) -> Result<String, Error>
where
F: FnMut(usize, usize) -> u8,
{
let mut img = PreparedImage::prepare_from_greyscale(width, height, fill);
let grids = img.detect_grids();
first_matter_qr(grids.into_iter().map(|grid| grid.decode()))
}
pub fn scan_bitmap<F>(width: usize, height: usize, fill: F) -> Result<String, Error>
where
F: FnMut(usize, usize) -> bool,
{
let mut img = PreparedImage::prepare_from_bitmap(width, height, fill);
let grids = img.detect_grids();
first_matter_qr(grids.into_iter().map(|grid| grid.decode()))
}
fn first_matter_qr<E>(
decoded: impl Iterator<Item = Result<(MetaData, String), E>>,
) -> Result<String, Error> {
for res in decoded {
let Ok((_meta, content)) = res else {
continue;
};
if content.starts_with(QR_PREFIX) {
return Ok(content);
}
}
Err(ErrorCode::NotFound.into())
}
#[cfg(test)]
mod tests {
use crate::dm::devices::test::TEST_DEV_DET;
use crate::pairing::qr::{CommFlowType, Qr, QrPayload};
use crate::pairing::DiscoveryCapabilities;
use crate::BasicCommData;
use super::*;
#[test]
fn scan_round_trips_a_rendered_qr() {
const SCALE: usize = 4;
const QUIET: usize = 4;
let comm_data = BasicCommData {
password: 20202021_u32.to_le_bytes().into(),
discriminator: 3840,
};
let payload = QrPayload::new_from_basic_info(
DiscoveryCapabilities::BLE,
CommFlowType::Standard,
comm_data,
&TEST_DEV_DET,
super::super::no_optional_data,
);
let mut str_buf = [0; 256];
let text = unwrap!(payload.as_str(&mut str_buf), "Failed to encode").0;
let mut tmp_buf = [0; 2048];
let mut out_buf = [0; 2048];
let qr = unwrap!(
Qr::compute(text, &mut tmp_buf, &mut out_buf),
"Failed to render"
);
let modules = qr.size() as usize;
let side = (modules + 2 * QUIET) * SCALE;
let scanned = unwrap!(
scan_bitmap(side, side, |x, y| {
let mx = (x / SCALE) as i32 - QUIET as i32;
let my = (y / SCALE) as i32 - QUIET as i32;
qr.get_module(mx, my)
}),
"Failed to scan"
);
assert_eq!(scanned, text);
let mut parse_buf = [0; 128];
let parsed = unwrap!(
QrPayload::parse(&scanned, &mut parse_buf),
"Failed to parse"
);
assert_eq!(parsed.discriminator(), 3840);
assert_eq!(parsed.passcode(), 20202021);
assert_eq!(parsed.vid(), TEST_DEV_DET.vid);
assert_eq!(parsed.pid(), TEST_DEV_DET.pid);
}
#[test]
fn scan_finds_nothing_in_a_blank_image() {
assert!(scan_luma(64, 64, &[255; 64 * 64]).is_err());
}
#[test]
fn scan_luma_rejects_a_short_buffer() {
assert!(scan_luma(64, 64, &[255; 16]).is_err());
}
}