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:
adlee-was-taken
2026-05-09 10:44:16 -04:00
parent b9bd152e9d
commit e5d63ab196
6 changed files with 146 additions and 47 deletions

View File

@@ -244,7 +244,7 @@ fn serialize_history_value(value: &FieldValue) -> Result<Zeroizing<String>> {
FieldValue::Concealed(c) => Zeroizing::new(c.as_str().to_owned()),
FieldValue::Totp(cfg) => {
// Store the base32-encoded secret string for human-recognizability.
let s = base32_encode(&cfg.secret);
let s = crate::base32::encode_rfc4648(&cfg.secret);
Zeroizing::new(s)
}
_ => return Err(RelicarioError::Format("not a history-tracked kind".into())),
@@ -252,28 +252,6 @@ fn serialize_history_value(value: &FieldValue) -> Result<Zeroizing<String>> {
Ok(s)
}
/// Minimal RFC 4648 base32 (no padding) for TOTP secret history serialization.
fn base32_encode(bytes: &[u8]) -> String {
const ALPHA: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let mut out = String::new();
let mut buffer: u32 = 0;
let mut bits: u32 = 0;
for &b in bytes {
buffer = (buffer << 8) | (b as u32);
bits += 8;
while bits >= 5 {
let idx = ((buffer >> (bits - 5)) & 0x1f) as usize;
out.push(ALPHA[idx] as char);
bits -= 5;
}
}
if bits > 0 {
let idx = ((buffer << (5 - bits)) & 0x1f) as usize;
out.push(ALPHA[idx] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;