pub const VERIFIER_MIN_LENGTH: usize = 43;
pub const VERIFIER_MAX_LENGTH: usize = 128;
#[must_use]
#[inline]
pub const fn is_unreserved_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'.' || byte == b'_' || byte == b'~'
}
#[must_use]
#[inline]
pub const fn is_valid_verifier_length(len: usize) -> bool {
len >= VERIFIER_MIN_LENGTH && len <= VERIFIER_MAX_LENGTH
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerifierByteError {
InvalidLength(usize),
InvalidCharacter {
byte: u8,
position: usize,
},
}
pub fn validate_verifier_bytes(verifier: &[u8]) -> Result<(), VerifierByteError> {
let len = verifier.len();
if !is_valid_verifier_length(len) {
return Err(VerifierByteError::InvalidLength(len));
}
for (position, byte) in verifier.iter().copied().enumerate() {
if !is_unreserved_byte(byte) {
return Err(VerifierByteError::InvalidCharacter { byte, position });
}
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, missing_docs)]
mod tests {
use super::*;
#[test]
fn length_bounds() {
assert!(is_valid_verifier_length(43));
assert!(is_valid_verifier_length(128));
assert!(!is_valid_verifier_length(42));
assert!(!is_valid_verifier_length(129));
assert!(!is_valid_verifier_length(0));
}
#[test]
fn byte_domain() {
for b in b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" {
assert!(is_unreserved_byte(*b), "byte {b:#x} must be unreserved");
}
for b in [b' ', b'+', b'=', b'/', b'?', 0x00, 0x80, 0xff] {
assert!(!is_unreserved_byte(b), "byte {b:#x} must be reserved");
}
}
#[test]
fn validate_bytes_agrees_with_str_validator() {
for len in [0usize, 1, 42, 43, 64, 128, 129, 200] {
let v = "a".repeat(len);
assert_eq!(
validate_verifier_bytes(v.as_bytes()).is_ok(),
crate::pkce::validate_verifier(&v).is_ok(),
"divergence at length {len}"
);
}
let mut bad = "a".repeat(42);
bad.push('+');
assert!(validate_verifier_bytes(bad.as_bytes()).is_err());
assert!(crate::pkce::validate_verifier(&bad).is_err());
}
#[test]
fn error_position_is_reported() {
let mut v = "a".repeat(42);
v.push('+');
v.push('b');
assert_eq!(v.len(), 44);
let err = validate_verifier_bytes(v.as_bytes()).unwrap_err();
assert_eq!(
err,
VerifierByteError::InvalidCharacter {
byte: b'+',
position: 42
}
);
}
#[test]
fn nul_byte_is_character_violation_not_length_sentinel() {
let mut v = vec![b'a'; 43];
v[20] = 0x00;
assert_eq!(
validate_verifier_bytes(&v).unwrap_err(),
VerifierByteError::InvalidCharacter {
byte: 0,
position: 20
}
);
}
}