Skip to main content

serial_codec/sms/
ucs2.rs

1//! UCS2 user data (23.038 §6.2.3). Encoded as UTF-16BE, so characters outside the BMP
2//! travel as surrogate pairs, as every current handset expects.
3
4/// Text → UTF-16BE bytes.
5pub fn encode(text: &str) -> Vec<u8> {
6    text.encode_utf16().flat_map(u16::to_be_bytes).collect()
7}
8
9/// Number of UTF-16 code units (2 octets each) for `c`.
10pub fn units(c: char) -> usize {
11    c.len_utf16()
12}
13
14/// UTF-16BE bytes → text. Unpaired surrogates and a trailing odd octet become U+FFFD.
15pub fn decode(bytes: &[u8]) -> String {
16    let mut out: String = char::decode_utf16(code_units(bytes))
17        .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
18        .collect();
19    if bytes.len() % 2 == 1 {
20        out.push(char::REPLACEMENT_CHARACTER);
21    }
22    out
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Ucs2Error {
27    /// Odd number of octets.
28    OddLength,
29    /// Unpaired surrogate at this code-unit index.
30    UnpairedSurrogate(usize),
31}
32
33impl std::fmt::Display for Ucs2Error {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::OddLength => f.write_str("UCS2 data has an odd number of octets"),
37            Self::UnpairedSurrogate(i) => write!(f, "unpaired UTF-16 surrogate at unit {i}"),
38        }
39    }
40}
41
42impl std::error::Error for Ucs2Error {}
43
44/// Strict variant of [`decode`].
45pub fn decode_strict(bytes: &[u8]) -> Result<String, Ucs2Error> {
46    if bytes.len() % 2 == 1 {
47        return Err(Ucs2Error::OddLength);
48    }
49    let mut out = String::with_capacity(bytes.len() / 2);
50    let mut idx = 0;
51    for r in char::decode_utf16(code_units(bytes)) {
52        let c = r.map_err(|_| Ucs2Error::UnpairedSurrogate(idx))?;
53        idx += c.len_utf16();
54        out.push(c);
55    }
56    Ok(out)
57}
58
59fn code_units(bytes: &[u8]) -> impl Iterator<Item = u16> + '_ {
60    bytes
61        .chunks_exact(2)
62        .map(|p| u16::from_be_bytes([p[0], p[1]]))
63}