Coverage Report

Created: 2025-12-28 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rcgen-0.14.2/src/string.rs
Line
Count
Source
1
//! ASN.1 string types
2
3
use std::{fmt, str::FromStr};
4
5
use crate::{Error, InvalidAsn1String};
6
7
/// ASN.1 `PrintableString` type.
8
///
9
/// Supports a subset of the ASCII printable characters (described below).
10
///
11
/// For the full ASCII character set, use
12
/// [`Ia5String`][`crate::Ia5String`].
13
///
14
/// # Examples
15
///
16
/// You can create a `PrintableString` from [a literal string][`&str`] with [`PrintableString::try_from`]:
17
///
18
/// ```
19
/// use rcgen::string::PrintableString;
20
/// let hello = PrintableString::try_from("hello").unwrap();
21
/// ```
22
///
23
/// # Supported characters
24
///
25
/// PrintableString is a subset of the [ASCII printable characters].
26
/// For instance, `'@'` is a printable character as per ASCII but can't be part of [ASN.1's `PrintableString`].
27
///
28
/// The following ASCII characters/ranges are supported:
29
///
30
/// - `A..Z`
31
/// - `a..z`
32
/// - `0..9`
33
/// - "` `" (i.e. space)
34
/// - `\`
35
/// - `(`
36
/// - `)`
37
/// - `+`
38
/// - `,`
39
/// - `-`
40
/// - `.`
41
/// - `/`
42
/// - `:`
43
/// - `=`
44
/// - `?`
45
///
46
/// [ASCII printable characters]: https://en.wikipedia.org/wiki/ASCII#Printable_characters
47
/// [ASN.1's `PrintableString`]: https://en.wikipedia.org/wiki/PrintableString
48
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
49
pub struct PrintableString(String);
50
51
impl PrintableString {
52
  /// Extracts a string slice containing the entire `PrintableString`.
53
0
  pub fn as_str(&self) -> &str {
54
0
    &self.0
55
0
  }
56
}
57
58
impl TryFrom<&str> for PrintableString {
59
  type Error = Error;
60
61
  /// Converts a `&str` to a [`PrintableString`].
62
  ///
63
  /// Any character not in the [`PrintableString`] charset will be rejected.
64
  /// See [`PrintableString`] documentation for more information.
65
  ///
66
  /// The result is allocated on the heap.
67
0
  fn try_from(input: &str) -> Result<Self, Error> {
68
0
    input.to_string().try_into()
69
0
  }
70
}
71
72
impl TryFrom<String> for PrintableString {
73
  type Error = Error;
74
75
  /// Converts a [`String`][`std::string::String`] into a [`PrintableString`]
76
  ///
77
  /// Any character not in the [`PrintableString`] charset will be rejected.
78
  /// See [`PrintableString`] documentation for more information.
79
  ///
80
  /// This conversion does not allocate or copy memory.
81
0
  fn try_from(value: String) -> Result<Self, Self::Error> {
82
0
    for &c in value.as_bytes() {
83
0
      match c {
84
0
        b'A'..=b'Z'
85
0
        | b'a'..=b'z'
86
0
        | b'0'..=b'9'
87
        | b' '
88
        | b'\''
89
        | b'('
90
        | b')'
91
        | b'+'
92
        | b','
93
        | b'-'
94
        | b'.'
95
        | b'/'
96
        | b':'
97
        | b'='
98
0
        | b'?' => (),
99
        _ => {
100
0
          return Err(Error::InvalidAsn1String(
101
0
            InvalidAsn1String::PrintableString(value),
102
0
          ))
103
        },
104
      }
105
    }
106
0
    Ok(Self(value))
107
0
  }
108
}
109
110
impl FromStr for PrintableString {
111
  type Err = Error;
112
113
0
  fn from_str(s: &str) -> Result<Self, Self::Err> {
114
0
    s.try_into()
115
0
  }
116
}
117
118
impl AsRef<str> for PrintableString {
119
0
  fn as_ref(&self) -> &str {
120
0
    &self.0
121
0
  }
122
}
123
124
impl fmt::Display for PrintableString {
125
0
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126
0
    fmt::Display::fmt(self.as_str(), f)
127
0
  }
128
}
129
130
impl PartialEq<str> for PrintableString {
131
0
  fn eq(&self, other: &str) -> bool {
132
0
    self.as_str() == other
133
0
  }
134
}
135
136
impl PartialEq<String> for PrintableString {
137
0
  fn eq(&self, other: &String) -> bool {
138
0
    self.as_str() == other.as_str()
139
0
  }
140
}
141
142
impl PartialEq<&str> for PrintableString {
143
0
  fn eq(&self, other: &&str) -> bool {
144
0
    self.as_str() == *other
145
0
  }
146
}
147
148
impl PartialEq<&String> for PrintableString {
149
0
  fn eq(&self, other: &&String) -> bool {
150
0
    self.as_str() == other.as_str()
151
0
  }
152
}
153
154
/// ASN.1 `IA5String` type.
155
///
156
/// # Examples
157
///
158
/// You can create a `Ia5String` from [a literal string][`&str`] with [`Ia5String::try_from`]:
159
///
160
/// ```
161
/// use rcgen::string::Ia5String;
162
/// let hello = Ia5String::try_from("hello").unwrap();
163
/// ```
164
///
165
/// # Supported characters
166
///
167
/// Supports the [International Alphabet No. 5 (IA5)] character encoding, i.e.
168
/// the 128 characters of the ASCII alphabet. (Note: IA5 is now
169
/// technically known as the International Reference Alphabet or IRA as
170
/// specified in the ITU-T's T.50 recommendation).
171
///
172
/// For UTF-8, use [`String`][`std::string::String`].
173
///
174
/// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_(standard)
175
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
176
pub struct Ia5String(String);
177
178
impl Ia5String {
179
  /// Extracts a string slice containing the entire `Ia5String`.
180
0
  pub fn as_str(&self) -> &str {
181
0
    &self.0
182
0
  }
183
}
184
185
impl TryFrom<&str> for Ia5String {
186
  type Error = Error;
187
188
  /// Converts a `&str` to a [`Ia5String`].
189
  ///
190
  /// Any character not in the [`Ia5String`] charset will be rejected.
191
  /// See [`Ia5String`] documentation for more information.
192
  ///
193
  /// The result is allocated on the heap.
194
0
  fn try_from(input: &str) -> Result<Self, Error> {
195
0
    input.to_string().try_into()
196
0
  }
197
}
198
199
impl TryFrom<String> for Ia5String {
200
  type Error = Error;
201
202
  /// Converts a [`String`][`std::string::String`] into a [`Ia5String`]
203
  ///
204
  /// Any character not in the [`Ia5String`] charset will be rejected.
205
  /// See [`Ia5String`] documentation for more information.
206
0
  fn try_from(input: String) -> Result<Self, Error> {
207
0
    if !input.is_ascii() {
208
0
      return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String(
209
0
        input,
210
0
      )));
211
0
    }
212
0
    Ok(Self(input))
213
0
  }
214
}
215
216
impl FromStr for Ia5String {
217
  type Err = Error;
218
219
0
  fn from_str(s: &str) -> Result<Self, Self::Err> {
220
0
    s.try_into()
221
0
  }
222
}
223
224
impl AsRef<str> for Ia5String {
225
0
  fn as_ref(&self) -> &str {
226
0
    &self.0
227
0
  }
228
}
229
230
impl fmt::Display for Ia5String {
231
0
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232
0
    fmt::Display::fmt(self.as_str(), f)
233
0
  }
234
}
235
236
impl PartialEq<str> for Ia5String {
237
0
  fn eq(&self, other: &str) -> bool {
238
0
    self.as_str() == other
239
0
  }
240
}
241
242
impl PartialEq<String> for Ia5String {
243
0
  fn eq(&self, other: &String) -> bool {
244
0
    self.as_str() == other.as_str()
245
0
  }
246
}
247
248
impl PartialEq<&str> for Ia5String {
249
0
  fn eq(&self, other: &&str) -> bool {
250
0
    self.as_str() == *other
251
0
  }
252
}
253
254
impl PartialEq<&String> for Ia5String {
255
0
  fn eq(&self, other: &&String) -> bool {
256
0
    self.as_str() == other.as_str()
257
0
  }
258
}
259
260
/// ASN.1 `TeletexString` type.
261
///
262
/// # Examples
263
///
264
/// You can create a `TeletexString` from [a literal string][`&str`] with [`TeletexString::try_from`]:
265
///
266
/// ```
267
/// use rcgen::string::TeletexString;
268
/// let hello = TeletexString::try_from("hello").unwrap();
269
/// ```
270
///
271
/// # Supported characters
272
///
273
/// The standard defines a complex character set allowed in this type. However, quoting the ASN.1
274
/// [mailing list], "a sizable volume of software in the world treats TeletexString (T61String) as a
275
/// simple 8-bit string with mostly Windows Latin 1 (superset of iso-8859-1) encoding".
276
///
277
/// `TeletexString` is included for backward compatibility, [RFC 5280] say it
278
/// SHOULD NOT be used for certificates for new subjects.
279
///
280
/// [mailing list]: https://www.mail-archive.com/asn1@asn1.org/msg00460.html
281
/// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25
282
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
283
pub struct TeletexString(String);
284
285
impl TeletexString {
286
  /// Extracts a string slice containing the entire `TeletexString`.
287
0
  pub fn as_str(&self) -> &str {
288
0
    &self.0
289
0
  }
290
291
  /// Returns a byte slice of this `TeletexString`’s contents.
292
0
  pub fn as_bytes(&self) -> &[u8] {
293
0
    self.0.as_bytes()
294
0
  }
295
}
296
297
impl TryFrom<&str> for TeletexString {
298
  type Error = Error;
299
300
  /// Converts a `&str` to a [`TeletexString`].
301
  ///
302
  /// Any character not in the [`TeletexString`] charset will be rejected.
303
  /// See [`TeletexString`] documentation for more information.
304
  ///
305
  /// The result is allocated on the heap.
306
0
  fn try_from(input: &str) -> Result<Self, Error> {
307
0
    input.to_string().try_into()
308
0
  }
309
}
310
311
impl TryFrom<String> for TeletexString {
312
  type Error = Error;
313
314
  /// Converts a [`String`][`std::string::String`] into a [`TeletexString`]
315
  ///
316
  /// Any character not in the [`TeletexString`] charset will be rejected.
317
  /// See [`TeletexString`] documentation for more information.
318
  ///
319
  /// This conversion does not allocate or copy memory.
320
0
  fn try_from(input: String) -> Result<Self, Error> {
321
    // Check all bytes are visible
322
0
    if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) {
323
0
      return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString(
324
0
        input,
325
0
      )));
326
0
    }
327
0
    Ok(Self(input))
328
0
  }
329
}
330
331
impl FromStr for TeletexString {
332
  type Err = Error;
333
334
0
  fn from_str(s: &str) -> Result<Self, Self::Err> {
335
0
    s.try_into()
336
0
  }
337
}
338
339
impl AsRef<str> for TeletexString {
340
0
  fn as_ref(&self) -> &str {
341
0
    &self.0
342
0
  }
343
}
344
345
impl fmt::Display for TeletexString {
346
0
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347
0
    fmt::Display::fmt(self.as_str(), f)
348
0
  }
349
}
350
351
impl PartialEq<str> for TeletexString {
352
0
  fn eq(&self, other: &str) -> bool {
353
0
    self.as_str() == other
354
0
  }
355
}
356
357
impl PartialEq<String> for TeletexString {
358
0
  fn eq(&self, other: &String) -> bool {
359
0
    self.as_str() == other.as_str()
360
0
  }
361
}
362
363
impl PartialEq<&str> for TeletexString {
364
0
  fn eq(&self, other: &&str) -> bool {
365
0
    self.as_str() == *other
366
0
  }
367
}
368
369
impl PartialEq<&String> for TeletexString {
370
0
  fn eq(&self, other: &&String) -> bool {
371
0
    self.as_str() == other.as_str()
372
0
  }
373
}
374
375
/// ASN.1 `BMPString` type.
376
///
377
/// # Examples
378
///
379
/// You can create a `BmpString` from [a literal string][`&str`] with [`BmpString::try_from`]:
380
///
381
/// ```
382
/// use rcgen::string::BmpString;
383
/// let hello = BmpString::try_from("hello").unwrap();
384
/// ```
385
///
386
/// # Supported characters
387
///
388
/// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646),
389
/// a.k.a. UCS-2.
390
///
391
/// Bytes are encoded as UTF-16 big-endian.
392
///
393
/// `BMPString` is included for backward compatibility, [RFC 5280] say it
394
/// SHOULD NOT be used for certificates for new subjects.
395
///
396
/// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25
397
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
398
pub struct BmpString(Vec<u8>);
399
400
impl BmpString {
401
  /// Returns a byte slice of this `BmpString`'s contents.
402
  ///
403
  /// The inverse of this method is [`from_utf16be`].
404
  ///
405
  /// [`from_utf16be`]: BmpString::from_utf16be
406
  ///
407
  /// # Examples
408
  ///
409
  /// ```
410
  /// use rcgen::string::BmpString;
411
  /// let s = BmpString::try_from("hello").unwrap();
412
  ///
413
  /// assert_eq!(&[0, 104, 0, 101, 0, 108, 0, 108, 0, 111], s.as_bytes());
414
  /// ```
415
0
  pub fn as_bytes(&self) -> &[u8] {
416
0
    &self.0
417
0
  }
418
419
  /// Decode a UTF-16BE–encoded vector `vec` into a `BmpString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data.
420
0
  pub fn from_utf16be(vec: Vec<u8>) -> Result<Self, Error> {
421
0
    if vec.len() % 2 != 0 {
422
0
      return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
423
0
        "Invalid UTF-16 encoding".to_string(),
424
0
      )));
425
0
    }
426
427
    // FIXME: Update this when `array_chunks` is stabilized.
428
0
    for maybe_char in char::decode_utf16(
429
0
      vec.chunks_exact(2)
430
0
        .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])),
431
    ) {
432
      // We check we only use the BMP subset of Unicode (the first 65 536 code points)
433
0
      match maybe_char {
434
        // Character is in the Basic Multilingual Plane
435
0
        Ok(c) if (c as u64) < u64::from(u16::MAX) => (),
436
        // Characters outside Basic Multilingual Plane or unpaired surrogates
437
        _ => {
438
0
          return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
439
0
            "Invalid UTF-16 encoding".to_string(),
440
0
          )));
441
        },
442
      }
443
    }
444
0
    Ok(Self(vec.to_vec()))
445
0
  }
446
}
447
448
impl TryFrom<&str> for BmpString {
449
  type Error = Error;
450
451
  /// Converts a `&str` to a [`BmpString`].
452
  ///
453
  /// Any character not in the [`BmpString`] charset will be rejected.
454
  /// See [`BmpString`] documentation for more information.
455
  ///
456
  /// The result is allocated on the heap.
457
0
  fn try_from(value: &str) -> Result<Self, Self::Error> {
458
0
    let capacity = value.len().checked_mul(2).ok_or_else(|| {
459
0
      Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string()))
460
0
    })?;
461
462
0
    let mut bytes = Vec::with_capacity(capacity);
463
464
0
    for code_point in value.encode_utf16() {
465
0
      bytes.extend(code_point.to_be_bytes());
466
0
    }
467
468
0
    BmpString::from_utf16be(bytes)
469
0
  }
470
}
471
472
impl TryFrom<String> for BmpString {
473
  type Error = Error;
474
475
  /// Converts a [`String`][`std::string::String`] into a [`BmpString`]
476
  ///
477
  /// Any character not in the [`BmpString`] charset will be rejected.
478
  /// See [`BmpString`] documentation for more information.
479
  ///
480
  /// Parsing a `BmpString` allocates memory since the UTF-8 to UTF-16 conversion requires a memory allocation.
481
0
  fn try_from(value: String) -> Result<Self, Self::Error> {
482
0
    value.as_str().try_into()
483
0
  }
484
}
485
486
impl FromStr for BmpString {
487
  type Err = Error;
488
489
0
  fn from_str(s: &str) -> Result<Self, Self::Err> {
490
0
    s.try_into()
491
0
  }
492
}
493
494
/// ASN.1 `UniversalString` type.
495
///
496
/// # Examples
497
///
498
/// You can create a `UniversalString` from [a literal string][`&str`] with [`UniversalString::try_from`]:
499
///
500
/// ```
501
/// use rcgen::string::UniversalString;
502
/// let hello = UniversalString::try_from("hello").unwrap();
503
/// ```
504
///
505
/// # Supported characters
506
///
507
/// The characters which can appear in the `UniversalString` type are any of the characters allowed by
508
/// ISO/IEC 10646 (Unicode).
509
///
510
/// Bytes are encoded like UTF-32 big-endian.
511
///
512
/// `UniversalString` is included for backward compatibility, [RFC 5280] say it
513
/// SHOULD NOT be used for certificates for new subjects.
514
///
515
/// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25
516
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
517
pub struct UniversalString(Vec<u8>);
518
519
impl UniversalString {
520
  /// Returns a byte slice of this `UniversalString`'s contents.
521
  ///
522
  /// The inverse of this method is [`from_utf32be`].
523
  ///
524
  /// [`from_utf32be`]: UniversalString::from_utf32be
525
  ///
526
  /// # Examples
527
  ///
528
  /// ```
529
  /// use rcgen::string::UniversalString;
530
  /// let s = UniversalString::try_from("hello").unwrap();
531
  ///
532
  /// assert_eq!(&[0, 0, 0, 104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111], s.as_bytes());
533
  /// ```
534
0
  pub fn as_bytes(&self) -> &[u8] {
535
0
    &self.0
536
0
  }
537
538
  /// Decode a UTF-32BE–encoded vector `vec` into a `UniversalString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data.
539
0
  pub fn from_utf32be(vec: Vec<u8>) -> Result<UniversalString, Error> {
540
0
    if vec.len() % 4 != 0 {
541
0
      return Err(Error::InvalidAsn1String(
542
0
        InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
543
0
      ));
544
0
    }
545
546
    // FIXME: Update this when `array_chunks` is stabilized.
547
0
    for maybe_char in vec
548
0
      .chunks_exact(4)
549
0
      .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
550
    {
551
0
      if core::char::from_u32(maybe_char).is_none() {
552
0
        return Err(Error::InvalidAsn1String(
553
0
          InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
554
0
        ));
555
0
      }
556
    }
557
558
0
    Ok(Self(vec))
559
0
  }
560
}
561
562
impl TryFrom<&str> for UniversalString {
563
  type Error = Error;
564
565
  /// Converts a `&str` to a [`UniversalString`].
566
  ///
567
  /// Any character not in the [`UniversalString`] charset will be rejected.
568
  /// See [`UniversalString`] documentation for more information.
569
  ///
570
  /// The result is allocated on the heap.
571
0
  fn try_from(value: &str) -> Result<Self, Self::Error> {
572
0
    let capacity = value.len().checked_mul(4).ok_or_else(|| {
573
0
      Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string()))
574
0
    })?;
575
576
0
    let mut bytes = Vec::with_capacity(capacity);
577
578
    // A `char` is any ‘Unicode code point’ other than a surrogate code point.
579
    // The code units for UTF-32 correspond exactly to Unicode code points.
580
    // (https://www.unicode.org/reports/tr19/tr19-9.html#Introduction)
581
    // So any `char` is a valid UTF-32, we just cast it to perform the convertion.
582
0
    for char in value.chars().map(|char| char as u32) {
583
0
      bytes.extend(char.to_be_bytes())
584
    }
585
586
0
    UniversalString::from_utf32be(bytes)
587
0
  }
588
}
589
590
impl TryFrom<String> for UniversalString {
591
  type Error = Error;
592
593
  /// Converts a [`String`][`std::string::String`] into a [`UniversalString`]
594
  ///
595
  /// Any character not in the [`UniversalString`] charset will be rejected.
596
  /// See [`UniversalString`] documentation for more information.
597
  ///
598
  /// Parsing a `UniversalString` allocates memory since the UTF-8 to UTF-32 conversion requires a memory allocation.
599
0
  fn try_from(value: String) -> Result<Self, Self::Error> {
600
0
    value.as_str().try_into()
601
0
  }
602
}
603
604
#[cfg(test)]
605
#[allow(clippy::unwrap_used)]
606
mod tests {
607
608
  use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString};
609
610
  #[test]
611
  fn printable_string() {
612
    const EXAMPLE_UTF8: &str = "CertificateTemplate";
613
    let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap();
614
    assert_eq!(printable_string, EXAMPLE_UTF8);
615
    assert!(PrintableString::try_from("@").is_err());
616
    assert!(PrintableString::try_from("*").is_err());
617
  }
618
619
  #[test]
620
  fn ia5_string() {
621
    const EXAMPLE_UTF8: &str = "CertificateTemplate";
622
    let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap();
623
    assert_eq!(ia5_string, EXAMPLE_UTF8);
624
    assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
625
    assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
626
  }
627
628
  #[test]
629
  fn teletext_string() {
630
    const EXAMPLE_UTF8: &str = "CertificateTemplate";
631
    let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap();
632
    assert_eq!(teletext_string, EXAMPLE_UTF8);
633
    assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
634
    assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
635
  }
636
637
  #[test]
638
  fn bmp_string() {
639
    const EXPECTED_BYTES: &[u8] = &[
640
      0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69,
641
      0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d,
642
      0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65,
643
    ];
644
    const EXAMPLE_UTF8: &str = "CertificateTemplate";
645
    let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap();
646
    assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES);
647
    assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok());
648
    assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err());
649
  }
650
651
  #[test]
652
  fn universal_string() {
653
    const EXPECTED_BYTES: &[u8] = &[
654
      0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00,
655
      0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69,
656
      0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00,
657
      0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d,
658
      0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00,
659
      0x00, 0x74, 0x00, 0x00, 0x00, 0x65,
660
    ];
661
    const EXAMPLE_UTF8: &str = "CertificateTemplate";
662
    let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap();
663
    assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES);
664
  }
665
}