use std::{char, cmp, fmt, hash, ops, str};
use std::borrow::Cow;
use std::marker::PhantomData;
use bytes::Bytes;
use crate::{decode, encode};
use crate::decode::DecodeError;
use crate::tag::Tag;
use super::octet::{OctetString, OctetStringIter, OctetStringOctets};
pub trait CharSet {
const TAG: Tag;
fn next_char<I: Iterator<Item=u8>>(
iter: &mut I
) -> Result<Option<char>, CharSetError>;
fn from_str(s: &str) -> Result<Cow<[u8]>, CharSetError>;
fn check<I: Iterator<Item=u8>>(iter: &mut I) -> Result<(), CharSetError> {
while Self::next_char(iter)?.is_some() { }
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct RestrictedString<L: CharSet> {
octets: OctetString,
marker: PhantomData<L>
}
impl<L: CharSet> RestrictedString<L> {
unsafe fn new_unchecked(octets: OctetString) -> Self {
RestrictedString {
octets,
marker: PhantomData
}
}
pub fn new(os: OctetString) -> Result<Self, CharSetError> {
L::check(&mut os.octets())?;
Ok(unsafe { Self::new_unchecked(os) })
}
pub fn from_string(s: String) -> Result<Self, CharSetError> {
let octets = match L::from_str(s.as_ref())? {
Cow::Borrowed(_) => s.into_bytes().into(),
Cow::Owned(owned) => owned.into(),
};
Ok(unsafe { Self::new_unchecked(OctetString::new(octets)) })
}
pub fn chars(&self) -> RestrictedStringChars<L> {
RestrictedStringChars::new(self.octets.octets())
}
pub fn into_bytes(self) -> Bytes {
self.octets.into_bytes()
}
}
impl<L: CharSet> RestrictedString<L> {
pub fn take_from<S: decode::Source>(
cons: &mut decode::Constructed<S>
) -> Result<Self, DecodeError<S::Error>> {
cons.take_value_if(L::TAG, Self::from_content)
}
pub fn from_content<S: decode::Source>(
content: &mut decode::Content<S>
) -> Result<Self, DecodeError<S::Error>> {
let os = OctetString::from_content(content)?;
Self::new(os).map_err(|_| content.content_err("invalid character"))
}
pub fn encode(self) -> impl encode::Values {
self.encode_as(L::TAG)
}
pub fn encode_as(self, tag: Tag) -> impl encode::Values {
self.octets.encode_as(tag)
}
pub fn encode_ref(&self) -> impl encode::Values + '_ {
self.encode_ref_as(L::TAG)
}
pub fn encode_ref_as(&self, tag: Tag) -> impl encode::Values + '_ {
self.octets.encode_ref_as(tag)
}
}
impl<L: CharSet> str::FromStr for RestrictedString<L> {
type Err = CharSetError;
fn from_str(s: &str) -> Result<Self, CharSetError> {
Ok(unsafe { Self::new_unchecked(OctetString::new(
L::from_str(s)?.into_owned().into()
))})
}
}
impl<L: CharSet> ops::Deref for RestrictedString<L> {
type Target = OctetString;
fn deref(&self) -> &OctetString {
self.as_ref()
}
}
impl<L: CharSet> AsRef<OctetString> for RestrictedString<L> {
fn as_ref(&self) -> &OctetString {
&self.octets
}
}
impl<L: CharSet> PartialEq for RestrictedString<L> {
fn eq(&self, other: &Self) -> bool {
self.octets.eq(&other.octets)
}
}
impl<L: CharSet> Eq for RestrictedString<L> { }
impl<L: CharSet> PartialOrd for RestrictedString<L> {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<L: CharSet> Ord for RestrictedString<L> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.octets.cmp(&other.octets)
}
}
impl<L: CharSet> hash::Hash for RestrictedString<L> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.octets.hash(state)
}
}
impl<'a, L: CharSet> IntoIterator for &'a RestrictedString<L> {
type Item = &'a [u8];
type IntoIter = OctetStringIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<L: CharSet> fmt::Display for RestrictedString<L> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.chars().try_for_each(|ch| fmt::Display::fmt(&ch, fmt))
}
}
#[derive(Clone, Debug)]
pub struct RestrictedStringChars<'a, L: CharSet> {
octets: OctetStringOctets<'a>,
marker: PhantomData<L>,
}
impl<'a, L: CharSet> RestrictedStringChars<'a, L> {
fn new(octets: OctetStringOctets<'a>) -> Self {
RestrictedStringChars {
octets,
marker: PhantomData
}
}
}
impl<L: CharSet> Iterator for RestrictedStringChars<'_, L> {
type Item = char;
fn next(&mut self) -> Option<char> {
L::next_char(&mut self.octets).unwrap()
}
}
pub type Utf8String = RestrictedString<Utf8CharSet>;
#[derive(Clone, Copy, Debug)]
pub struct Utf8CharSet;
impl CharSet for Utf8CharSet {
const TAG: Tag = Tag::UTF8_STRING;
fn next_char<I: Iterator<Item=u8>>(
iter: &mut I
) -> Result<Option<char>, CharSetError> {
let first = match iter.next() {
Some(ch) => ch,
None => return Ok(None)
};
if first < 0x80 {
return Ok(Some(char::from(first)))
}
let second = match iter.next() {
Some(ch) => ch,
None => return Err(CharSetError),
};
if first < 0xC0 || second < 0x80 {
return Err(CharSetError)
}
if first < 0xE0 {
return Ok(Some(unsafe {
char::from_u32_unchecked(
((u32::from(first & 0x1F)) << 6) |
u32::from(second & 0x3F)
)
}))
}
let third = match iter.next() {
Some(ch) => ch,
None => return Err(CharSetError)
};
if third < 0x80 {
return Err(CharSetError)
}
if first < 0xF0 {
return Ok(Some(unsafe {
char::from_u32_unchecked(
((u32::from(first & 0x0F)) << 12) |
((u32::from(second & 0x3F)) << 6) |
u32::from(third & 0x3F)
)
}))
}
let fourth = match iter.next() {
Some(ch) => ch,
None => return Err(CharSetError)
};
if first > 0xF7 || fourth < 0x80 {
return Err(CharSetError)
}
Ok(Some(unsafe {
char::from_u32_unchecked(
((u32::from(first & 0x07)) << 18) |
((u32::from(second & 0x3F)) << 12) |
((u32::from(third & 0x3F)) << 6) |
u32::from(fourth & 0x3F)
)
}))
}
fn from_str(s: &str) -> Result<Cow<[u8]>, CharSetError> {
Ok(Cow::Borrowed(s.as_bytes()))
}
}
pub type NumericString = RestrictedString<NumericCharSet>;
#[derive(Clone, Copy, Debug)]
pub struct NumericCharSet;
impl CharSet for NumericCharSet {
const TAG: Tag = Tag::NUMERIC_STRING;
fn next_char<I: Iterator<Item=u8>>(
iter: &mut I
) -> Result<Option<char>, CharSetError> {
match iter.next() {
Some(ch) if ch == b' ' || ch.is_ascii_digit() => {
Ok(Some(char::from(ch)))
}
Some(_) => Err(CharSetError),
None => Ok(None)
}
}
fn from_str(s: &str) -> Result<Cow<[u8]>, CharSetError> {
Ok(Cow::Borrowed(s.as_bytes()))
}
}
pub type PrintableString = RestrictedString<PrintableCharSet>;
#[derive(Clone, Debug)]
pub struct PrintableCharSet;
impl CharSet for PrintableCharSet {
const TAG: Tag = Tag::PRINTABLE_STRING;
fn next_char<I: Iterator<Item=u8>>(
iter: &mut I
) -> Result<Option<char>, CharSetError> {
match iter.next() {
Some(x) if x.is_ascii_alphanumeric() || x == b' ' || x == b'\'' || x == b'(' || x == b')' ||
x == b'+' || x == b',' || x == b'-' || x == b'.' ||
x == b'/' || x == b':' || x == b'=' || x == b'?' => {
Ok(Some(char::from(x)))
}
Some(_) => Err(CharSetError),
None => Ok(None)
}
}
fn from_str(s: &str) -> Result<Cow<[u8]>, CharSetError> {
Ok(Cow::Borrowed(s.as_bytes()))
}
}
pub type Ia5String = RestrictedString<Ia5CharSet>;
#[derive(Clone, Copy, Debug)]
pub struct Ia5CharSet;
impl CharSet for Ia5CharSet {
const TAG: Tag = Tag::IA5_STRING;
fn next_char<I: Iterator<Item=u8>>(
iter: &mut I
) -> Result<Option<char>, CharSetError> {
match iter.next() {
Some(ch) if ch.is_ascii() => Ok(Some(char::from(ch))),
Some(_) => Err(CharSetError),
None => Ok(None)
}
}
fn from_str(s: &str) -> Result<Cow<[u8]>, CharSetError> {
Ok(Cow::Borrowed(s.as_bytes()))
}
}
#[derive(Debug)]
pub struct CharSetError;
#[cfg(test)]
mod test {
use super::*;
use bytes::Bytes;
use crate::decode::IntoSource;
use crate::mode::Mode;
use crate::encode::Values;
#[test]
fn should_encode_decode_printable_string() {
let os = OctetString::new(Bytes::from_static(b"This is okay"));
let ps = PrintableString::new(os).unwrap();
let mut v = Vec::new();
ps.encode_ref().write_encoded(Mode::Der, &mut v).unwrap();
let decoded = Mode::Der.decode(
v.as_slice().into_source(),
PrintableString::take_from
).unwrap();
assert_eq!(ps, decoded);
}
#[test]
fn should_restrict_printable_string() {
let os = OctetString::new(Bytes::from_static(b"This is wrong!"));
assert!(PrintableString::new(os).is_err());
}
}