refactor(core): extract base32 module, dedupe two RFC 4648 impls
New crates/relicario-core/src/base32.rs hosts encode_rfc4648 + decode_rfc4648_lenient (case-insensitive, optional padding, whitespace stripped). Folds inline base32_encode (item.rs:255-275) and decode_base32_totp (import_lastpass.rs:202-220) into the shared module; both call sites updated. - New RelicarioError::InvalidBase32(String) variant for the decoder error path - Module is pub(crate); public API surface unchanged - Steam alphabet (item_types/totp.rs:13) intentionally separate with neighbour comment pointing at crate::base32 Plan B Phase 7 sub-step 1 (DEV-A P2 base32 dedup half). docs/superpowers/specs/2026-05-04-cli-restructure-design.md. cargo test --workspace: green cargo clippy --workspace: silent Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -158,8 +158,8 @@ fn map_row(
|
||||
let totp = if totp_raw.is_empty() {
|
||||
None
|
||||
} else {
|
||||
match decode_base32_totp(totp_raw) {
|
||||
Some(bytes) if !bytes.is_empty() => Some(crate::item_types::TotpConfig {
|
||||
match crate::base32::decode_rfc4648_lenient(totp_raw) {
|
||||
Ok(bytes) if !bytes.is_empty() => Some(crate::item_types::TotpConfig {
|
||||
secret: Zeroizing::new(bytes),
|
||||
algorithm: crate::item_types::TotpAlgorithm::Sha1,
|
||||
digits: 6,
|
||||
@@ -196,25 +196,3 @@ fn map_row(
|
||||
(Some(item), warning)
|
||||
}
|
||||
|
||||
/// Decode a base32-encoded TOTP secret per RFC 4648, case-insensitive,
|
||||
/// padding optional. Returns None if the input contains any non-alphabet
|
||||
/// character (after upper-casing). Used by the LastPass importer.
|
||||
fn decode_base32_totp(secret: &str) -> Option<Vec<u8>> {
|
||||
const ALPHA: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
let upper = secret.trim().trim_end_matches('=').to_ascii_uppercase();
|
||||
if upper.is_empty() { return None; }
|
||||
|
||||
let mut out = Vec::with_capacity(upper.len() * 5 / 8);
|
||||
let mut buffer: u32 = 0;
|
||||
let mut bits: u32 = 0;
|
||||
for ch in upper.bytes() {
|
||||
let idx = ALPHA.iter().position(|&a| a == ch)?;
|
||||
buffer = (buffer << 5) | (idx as u32);
|
||||
bits += 5;
|
||||
if bits >= 8 {
|
||||
bits -= 8;
|
||||
out.push(((buffer >> bits) & 0xFF) as u8);
|
||||
}
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user