Skip to main content

der/asn1/
bmp_string.rs

1//! ASN.1 `BMPString` support.
2
3use crate::{
4    BytesOwned, DecodeValue, EncodeValue, Error, FixedTag, Header, Length, Reader, Result, Tag,
5    Writer, ord::OrdIsValueOrd,
6};
7use alloc::{boxed::Box, vec::Vec};
8use core::{fmt, str::FromStr};
9
10/// ASN.1 `BMPString` type.
11///
12/// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646),
13/// a.k.a. UCS-2.
14#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
15pub struct BmpString {
16    bytes: BytesOwned,
17}
18
19impl BmpString {
20    /// Create a new [`BmpString`] from its UCS-2 encoding.
21    ///
22    /// # Errors
23    /// If `bytes` contains out-of-range characters.
24    pub fn from_ucs2(bytes: impl Into<Box<[u8]>>) -> Result<Self> {
25        let bytes = bytes.into();
26
27        if bytes.len() % 2 != 0 {
28            return Err(Tag::BmpString.length_error().into());
29        }
30
31        let ret = Self {
32            bytes: bytes.try_into()?,
33        };
34
35        for maybe_char in char::decode_utf16(ret.codepoints()) {
36            match maybe_char {
37                // Character is in the Basic Multilingual Plane
38                Ok(c) if (c as u64) < u64::from(u16::MAX) => (),
39                // Characters outside Basic Multilingual Plane or unpaired surrogates
40                _ => return Err(Tag::BmpString.value_error().into()),
41            }
42        }
43
44        Ok(ret)
45    }
46
47    /// Create a new [`BmpString`] from a UTF-8 string.
48    ///
49    /// # Errors
50    /// If a length calculation overflowed or an internal conversion failed.
51    pub fn from_utf8(utf8: &str) -> Result<Self> {
52        let capacity = utf8
53            .len()
54            .checked_mul(2)
55            .ok_or_else(|| Tag::BmpString.length_error())?;
56
57        let mut bytes = Vec::with_capacity(capacity);
58
59        for code_point in utf8.encode_utf16() {
60            bytes.extend(code_point.to_be_bytes());
61        }
62
63        Self::from_ucs2(bytes)
64    }
65
66    /// Borrow the encoded UCS-2 as bytes.
67    #[must_use]
68    pub fn as_bytes(&self) -> &[u8] {
69        self.bytes.as_ref()
70    }
71
72    /// Obtain the inner bytes.
73    #[inline]
74    #[must_use]
75    pub fn into_bytes(self) -> Box<[u8]> {
76        self.bytes.into()
77    }
78
79    /// Get an iterator over characters in the string.
80    #[allow(clippy::missing_panics_doc)]
81    pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
82        char::decode_utf16(self.codepoints())
83            .map(|maybe_char| maybe_char.expect("unpaired surrogates checked in constructor"))
84    }
85
86    /// Get an iterator over the `u16` codepoints.
87    pub fn codepoints(&self) -> impl Iterator<Item = u16> + '_ {
88        // TODO(tarcieri): use `as_chunks`
89        self.as_bytes()
90            .chunks_exact(2)
91            .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
92    }
93}
94
95impl AsRef<[u8]> for BmpString {
96    fn as_ref(&self) -> &[u8] {
97        self.as_bytes()
98    }
99}
100
101impl<'a> DecodeValue<'a> for BmpString {
102    type Error = Error;
103
104    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
105        Self::from_ucs2(reader.read_vec(header.length())?)
106    }
107}
108
109impl EncodeValue for BmpString {
110    fn value_len(&self) -> Result<Length> {
111        Ok(self.bytes.len())
112    }
113
114    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
115        writer.write(self.as_bytes())
116    }
117}
118
119impl FixedTag for BmpString {
120    const TAG: Tag = Tag::BmpString;
121}
122
123impl FromStr for BmpString {
124    type Err = Error;
125
126    fn from_str(s: &str) -> Result<Self> {
127        Self::from_utf8(s)
128    }
129}
130
131impl OrdIsValueOrd for BmpString {}
132
133/// Hack for simplifying the custom derive use case,
134/// as there is no `BmpStringRef` yet.
135impl From<&BmpString> for BmpString {
136    fn from(value: &BmpString) -> Self {
137        BmpString {
138            bytes: value.bytes.clone(),
139        }
140    }
141}
142
143impl fmt::Debug for BmpString {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        write!(f, "BmpString(\"{self}\")")
146    }
147}
148
149impl fmt::Display for BmpString {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        for c in self.chars() {
152            write!(f, "{c}")?;
153        }
154        Ok(())
155    }
156}
157
158#[cfg(test)]
159#[allow(clippy::unwrap_used)]
160mod tests {
161    use super::BmpString;
162    use crate::{Decode, Encode};
163    use alloc::string::ToString;
164    use hex_literal::hex;
165
166    const EXAMPLE_BYTES: &[u8] = &hex!(
167        "1e 26 00 43 00 65 00 72 00 74"
168        "      00 69 00 66 00 69 00 63"
169        "      00 61 00 74 00 65 00 54"
170        "      00 65 00 6d 00 70 00 6c"
171        "      00 61 00 74 00 65"
172    );
173
174    const EXAMPLE_UTF8: &str = "CertificateTemplate";
175
176    #[test]
177    fn decode() {
178        let bmp_string = BmpString::from_der(EXAMPLE_BYTES).unwrap();
179        assert_eq!(bmp_string.to_string(), EXAMPLE_UTF8);
180    }
181
182    #[test]
183    fn encode() {
184        let bmp_string = BmpString::from_utf8(EXAMPLE_UTF8).unwrap();
185        let encoded = bmp_string.to_der().unwrap();
186        assert_eq!(encoded, EXAMPLE_BYTES);
187    }
188}