Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

der: add BmpString #1164

Merged
merged 2 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions der/src/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod internal_macros;

mod any;
mod bit_string;
#[cfg(feature = "alloc")]
mod bmp_string;
mod boolean;
mod choice;
mod context_specific;
Expand Down Expand Up @@ -52,6 +54,7 @@ pub use self::{
pub use self::{
any::Any,
bit_string::BitString,
bmp_string::BmpString,
ia5_string::Ia5String,
integer::{int::Int, uint::Uint},
octet_string::OctetString,
Expand Down
160 changes: 160 additions & 0 deletions der/src/asn1/bmp_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! 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<Box<[u8]>>) -> Result<Self> {
let bytes = bytes.into();

if bytes.len() % 2 != 0 {
return Err(Tag::BmpString.length_error());
}

let ret = Self {
bytes: bytes.try_into()?,
};

// Ensure resulting string is free of unpaired surrogates
for maybe_char in char::decode_utf16(ret.codepoints()) {
if maybe_char.is_err() {
return Err(Tag::BmpString.value_error());
}
}

Ok(ret)
}

/// Create a new [`BmpString`] from a UTF-8 string.
pub fn from_utf8(utf8: &str) -> Result<Self> {
#[allow(clippy::integer_arithmetic)]
let mut bytes = Vec::with_capacity(utf8.len() * 2);

for code_point in utf8.encode_utf16() {
bytes.extend(code_point.to_be_bytes());
}

Ok(Self {
bytes: bytes.try_into()?,
})
}

/// 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<Item = char> + '_ {
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<Item = u16> + '_ {
// TODO(tarcieri): use `array_chunks`
self.as_bytes()
.chunks_exact(2)
.map(|chunk| u16::from_be_bytes(chunk.try_into().expect("two bytes")))
}
}

impl AsRef<[u8]> for BmpString {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}

impl<'a> DecodeValue<'a> for BmpString {
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
Self::from_ucs2(reader.read_vec(header.length)?)
}
}

impl EncodeValue for BmpString {
fn value_len(&self) -> Result<Length> {
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> {
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);
}
}
6 changes: 6 additions & 0 deletions der/src/bytes_owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ impl DerOrd for BytesOwned {
}
}

impl From<BytesOwned> for Box<[u8]> {
fn from(bytes: BytesOwned) -> Box<[u8]> {
bytes.inner
}
}

impl From<StrRef<'_>> for BytesOwned {
fn from(s: StrRef<'_>) -> BytesOwned {
let bytes = s.as_bytes();
Expand Down