From 3f1b9901b525ec9e477562a5e43fd4e54b7bb2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Wed, 17 Jun 2026 09:26:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=A0=BC=E5=BC=8F=E4=BF=A1=E6=81=AF=20B?= =?UTF-8?q?CH=20=E7=BC=96=E7=A0=81=E8=BF=90=E7=AE=97=E7=AC=A6=E4=BC=98?= =?UTF-8?q?=E5=85=88=E7=BA=A7=20=E2=80=94=20|=20=E4=BD=8E=E4=BA=8E=20^=20?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=20XOR=20=E6=9C=AA=E8=A6=86=E7=9B=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: ((data << 10) | (val & 0x3FF)) ^ 0x5412 被解析为 (data << 10) | ((val & 0x3FF) ^ 0x5412) 导致格式信息的 XOR mask 未覆盖前 5 个数据位,扫码器读到错误的 EC 级别和掩码编号 --- core/src/matrix/patterns.rs | 4 +- core/tests/integration_test.rs | 178 +++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 2 deletions(-) diff --git a/core/src/matrix/patterns.rs b/core/src/matrix/patterns.rs index 0d77a06..3c3430f 100644 --- a/core/src/matrix/patterns.rs +++ b/core/src/matrix/patterns.rs @@ -153,8 +153,8 @@ pub fn encode_format_info(ec_bits: u8, mask: u8) -> u16 { } } - // XOR 掩码 - ((data as u16) << 10) | (val & 0x3FF) ^ 0x5412 + // XOR 掩码 — 注意 ^ 优先级高于 |,必须加括号 + (((data as u16) << 10) | (val & 0x3FF)) ^ 0x5412 } /// 将格式信息写入矩阵(两处镜像放置) diff --git a/core/tests/integration_test.rs b/core/tests/integration_test.rs index df8e9b1..ecb6d8c 100644 --- a/core/tests/integration_test.rs +++ b/core/tests/integration_test.rs @@ -1,6 +1,184 @@ use qr_core::qr::{QrCode, QrConfig, VersionMode}; use qr_core::version::EcLevel; +/// 诊断: 验证格式信息编码 + 解码是否正确 +#[test] +fn test_format_info_roundtrip() { + use qr_core::matrix::patterns::{encode_format_info, encode_version_info}; + + // M 级 (00) + mask 0 (000): data = 00000 = 0 + let fmt0 = encode_format_info(0b00, 0); + // L 级 (01) + mask 3 (011): data = 01011 = 11 + let fmt1 = encode_format_info(0b01, 3); + // H 级 (10) + mask 7 (111): data = 10111 = 23 + let fmt2 = encode_format_info(0b10, 7); + + // 不同输入应产生不同输出 + assert_ne!(fmt0, fmt1); + assert_ne!(fmt1, fmt2); + assert_ne!(fmt0, fmt2); + + // 应该在 15-bit 范围内 + assert!(fmt0 < 0x8000); + assert!(fmt1 < 0x8000); + + println!("格式信息 M+mask0: 0x{:04X}", fmt0); + println!("格式信息 L+mask3: 0x{:04X}", fmt1); + println!("格式信息 H+mask7: 0x{:04X}", fmt2); + + // 版本信息编码 + let v7 = encode_version_info(7); + println!("版本信息 v7: 0x{:06X}", v7); + // 前 6 bit 是版本号 + assert_eq!((v7 >> 12) & 0x3F, 7); +} + +/// 诊断: 打印 QR 码的格式信息比特 +#[test] +fn test_dump_format_info() { + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let m = qr.modules(); + + println!("\n=== 格式信息 15 bit (bit14 到 bit0) ==="); + println!("EC 级别: {:?}, 掩码: {}", qr.level, qr.mask); + + // 格式信息位置 (按标准顺序 bit14→bit0) + let coords = [ + (0, 8), (1, 8), (2, 8), (3, 8), (4, 8), (5, 8), (7, 8), (8, 8), + (8, 7), (8, 5), (8, 4), (8, 3), (8, 2), (8, 1), (8, 0), + ]; + + let mut fmt_bits = 0u16; + for (i, &(x, y)) in coords.iter().enumerate() { + let bit = if m[y as usize][x as usize] { 1u16 } else { 0u16 }; + fmt_bits = (fmt_bits << 1) | bit; + print!("{} ", if m[y as usize][x as usize] { '█' } else { '_' }); + } + println!(); + println!("读取的格式信息 (原始, 含 XOR mask 0x5412): 0x{:04X}", fmt_bits); + + // 去掉 XOR mask + let unmasked = fmt_bits ^ 0x5412; + println!("去 XOR mask 后: 0x{:04X}", unmasked); + let ec_bits = (unmasked >> 13) & 0x03; + let mask_bits = (unmasked >> 10) & 0x07; + let bch = unmasked & 0x3FF; + println!("EC bits: {:02b} 掩码 bits: {:03b} BCH: 0x{:03X}", ec_bits, mask_bits, bch); + + // 期望值 + let expected = { + use qr_core::matrix::patterns::encode_format_info; + encode_format_info(qr.level.indicator_bits(), qr.mask) + }; + println!("期望的格式信息: 0x{:04X}", expected); + println!("匹配: {}", if fmt_bits == expected { "✅" } else { "❌ 不匹配!" }); + assert_eq!(fmt_bits, expected, "格式信息不匹配!"); +} +#[test] +fn test_finder_patterns_present() { + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let size = qr.size() as usize; + let m = qr.modules(); + + // 三个定位图案: (0,0), (size-7,0), (0,size-7) + let s = size as u8; + let finders: [(u8, u8); 3] = [(0, 0), (s - 7, 0), (0, s - 7)]; + for (fx, fy) in finders { + assert!(m[fy as usize][fx as usize], "定位({},{}): 左上角应为暗", fx, fy); + assert!(m[fy as usize][(fx + 6) as usize], "定位({},{}): 右上角应为暗", fx, fy); + assert!(m[(fy + 6) as usize][fx as usize], "定位({},{}): 左下角应为暗", fx, fy); + // 内部 3×3 是暗色 + assert!(m[(fy + 2) as usize][(fx + 2) as usize], "定位({},{}): 中心3×3应为暗", fx, fy); + } +} + +#[test] +fn test_timing_pattern_alternates() { + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let m = qr.modules(); + let s = qr.size() as usize; + + // 行6: 从列8到列s-8-1 应交替 + for x in (8..s - 8).step_by(2) { + assert!(m[6][x], "时序 y=6 x={}: 偶数应为暗", x); + if x + 1 < s - 8 { + assert!(!m[6][x + 1], "时序 y=6 x={}: 奇数应为亮", x + 1); + } + } + // 列6: 从行8到行s-8-1 应交替 + for y in (8..s - 8).step_by(2) { + assert!(m[y][6], "时序 x=6 y={}: 偶数应为暗", y); + if y + 1 < s - 8 { + assert!(!m[y + 1][6], "时序 x=6 y={}: 奇数应为亮", y + 1); + } + } +} + +#[test] +fn test_dark_module_present() { + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let m = qr.modules(); + let s = qr.size() as usize; + // 暗模块总是位于 (8, size-8) + assert!(m[s - 8][8], "暗模块 (8,{}) 缺失!", s - 8); +} + +#[test] +fn test_format_info_written() { + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let m = qr.modules(); + // 格式信息在定位图案旁,检查几个位置不是全亮 + assert!(m[8][0] || m[8][1] || m[8][2] || !m[8][0], + "格式信息应已写入"); +} + +#[test] +fn test_svg_valid_structure() { + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let svg = qr.to_svg(); + // SVG 应有正确的结构 + assert!(svg.starts_with("\n") || svg.ends_with(""), + "SVG 应以 结尾"); +} + +#[test] +fn test_quiet_zone_is_white() { + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let m = qr.modules(); + let s = qr.size() as usize; + // 左上角分隔符区域 (7,0..7) 和 (0..7,7) 应为白色 + for i in 0..8usize { + assert!(!m[7][i], "定位分隔符 (7,{}) 应为白色", i); + assert!(!m[i][7], "定位分隔符 ({},7) 应为白色", i); + } +} + +#[test] +fn test_qr_structure_dump() { + // 打印矩阵到 stdout(用于调试) + let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap(); + let size = qr.size() as usize; + let m = qr.modules(); + + println!("\n=== QR Matrix {}x{} v{} mask{} ===", size, size, qr.version.0, qr.mask); + for y in 0..size { + for x in 0..size { + print!("{}", if m[y][x] { "##" } else { " " }); + } + println!(); + } + + // 统计 + let dark: usize = m.iter().flatten().filter(|&&x| x).count(); + let total = size * size; + println!("\n暗/总: {}/{} = {:.1}%", dark, total, dark as f64 / total as f64 * 100.0); + println!("尺寸: {}×{}", size, size); + println!("版本: {} 掩码: {} 纠错: {:?}", qr.version.0, qr.mask, qr.level); +} + #[test] fn test_encode_simple_text() { let config = QrConfig::default();