use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
pub const MAXIMUM_CLIENT_ID: usize = 32;
#[must_use]
pub fn random_string(len: usize) -> String {
String::from_utf8(
thread_rng()
.sample_iter(&Alphanumeric)
.take(len)
.collect::<Vec<u8>>(),
)
.expect("Invalid random string")
}
#[must_use]
pub fn random_client_id() -> String {
let mut rng = rand::thread_rng();
let len = rng.gen_range(14..22);
String::from_utf8(
rng.sample_iter(&Alphanumeric)
.take(len)
.collect::<Vec<u8>>(),
)
.expect("Invalid random string")
}
#[derive(Debug, PartialEq, Eq)]
pub enum StringError {
TooManyData,
InvalidLength,
InvalidChar,
SeriousError,
}
impl From<std::string::FromUtf8Error> for StringError {
fn from(_e: std::string::FromUtf8Error) -> Self {
Self::SeriousError
}
}
#[inline]
pub const fn validate_two_bytes_data(data: &[u8]) -> Result<(), StringError> {
if data.len() > u16::MAX as usize {
Err(StringError::TooManyData)
} else {
Ok(())
}
}
pub fn validate_utf8_string(s: &str) -> Result<(), StringError> {
if s.len() > u16::MAX as usize {
return Err(StringError::TooManyData);
}
for c in s.chars() {
if c == '\u{0000}' {
return Err(StringError::SeriousError);
}
if ('\u{0001}'..='\u{001f}').contains(&c) || ('\u{007f}'..='\u{009f}').contains(&c) {
return Err(StringError::InvalidChar);
}
}
Ok(())
}
pub fn to_utf8_string(buf: &[u8]) -> Result<String, StringError> {
let s = String::from_utf8(buf.to_vec())?;
validate_utf8_string(&s)?;
Ok(s)
}
pub fn validate_client_id(id: &str) -> Result<(), StringError> {
if id.is_empty() {
log::error!("client id is empty");
return Err(StringError::InvalidLength);
}
if id.len() > MAXIMUM_CLIENT_ID {
log::error!("client id has too many charas: {}", id.len());
return Err(StringError::InvalidLength);
}
for byte in id.bytes() {
if !((b'0'..=b'9').contains(&byte)
|| (b'a'..=b'z').contains(&byte)
|| (b'A'..=b'Z').contains(&byte)
|| b'-' == byte
|| b'_' == byte
|| b'.' == byte)
{
return Err(StringError::InvalidChar);
}
}
Ok(())
}