//! ASN.1 `BMPString` support. use crate::{ BytesOwned, DecodeValue, EncodeValue, Error, FixedTag, Header, Length, Reader, Result, Tag, Writer, }; use alloc::{boxed::Box, vec::Vec}; use core::{fmt, str::FromStr}; /// ASN.1 `BMPString` type. /// /// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646), /// a.k.a. UCS-2. #[derive(Clone, Eq, PartialEq, PartialOrd, Ord)] pub struct BmpString { bytes: BytesOwned, } impl BmpString { /// Create a new [`BmpString`] from its UCS-2 encoding. pub fn from_ucs2(bytes: impl Into>) -> Result { let bytes = bytes.into(); if bytes.len() % 2 != 0 { return Err(Tag::BmpString.length_error()); } let ret = Self { bytes: bytes.try_into()?, }; for maybe_char in char::decode_utf16(ret.codepoints()) { match maybe_char { // All surrogates paired and character is in the Basic Multilingual Plane Ok(c) if (c as u64) < u64::from(u16::MAX) => (), // Unpaired surrogates or characters outside Basic Multilingual Plane _ => return Err(Tag::BmpString.value_error()), } } Ok(ret) } /// Create a new [`BmpString`] from a UTF-8 string. pub fn from_utf8(utf8: &str) -> Result { let capacity = utf8 .len() .checked_mul(2) .ok_or_else(|| Tag::BmpString.length_error())?; let mut bytes = Vec::with_capacity(capacity); for code_point in utf8.encode_utf16() { bytes.extend(code_point.to_be_bytes()); } Self::from_ucs2(bytes) } /// Borrow the encoded UCS-2 as bytes. pub fn as_bytes(&self) -> &[u8] { self.bytes.as_ref() } /// Obtain the inner bytes. #[inline] pub fn into_bytes(self) -> Box<[u8]> { self.bytes.into() } /// Get an iterator over characters in the string. pub fn chars(&self) -> impl Iterator + '_ { char::decode_utf16(self.codepoints()) .map(|maybe_char| maybe_char.expect("unpaired surrogates checked in constructor")) } /// Get an iterator over the `u16` codepoints. pub fn codepoints(&self) -> impl Iterator + '_ { // TODO(tarcieri): use `array_chunks` self.as_bytes() .chunks_exact(2) .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])) } } impl AsRef<[u8]> for BmpString { fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl<'a> DecodeValue<'a> for BmpString { fn decode_value>(reader: &mut R, header: Header) -> Result { Self::from_ucs2(reader.read_vec(header.length)?) } } impl EncodeValue for BmpString { fn value_len(&self) -> Result { Ok(self.bytes.len()) } fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { writer.write(self.as_bytes()) } } impl FixedTag for BmpString { const TAG: Tag = Tag::BmpString; } impl FromStr for BmpString { type Err = Error; fn from_str(s: &str) -> Result { Self::from_utf8(s) } } impl fmt::Debug for BmpString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "BmpString(\"{}\")", self) } } impl fmt::Display for BmpString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for c in self.chars() { write!(f, "{}", c)?; } Ok(()) } } #[cfg(test)] mod tests { use super::BmpString; use crate::{Decode, Encode}; use alloc::string::ToString; use hex_literal::hex; const EXAMPLE_BYTES: &[u8] = &hex!( "1e 26 00 43 00 65 00 72 00 74" " 00 69 00 66 00 69 00 63" " 00 61 00 74 00 65 00 54" " 00 65 00 6d 00 70 00 6c" " 00 61 00 74 00 65" ); const EXAMPLE_UTF8: &str = "CertificateTemplate"; #[test] fn decode() { let bmp_string = BmpString::from_der(EXAMPLE_BYTES).unwrap(); assert_eq!(bmp_string.to_string(), EXAMPLE_UTF8); } #[test] fn encode() { let bmp_string = BmpString::from_utf8(EXAMPLE_UTF8).unwrap(); let encoded = bmp_string.to_der().unwrap(); assert_eq!(encoded, EXAMPLE_BYTES); } }