1//! The Unicode Collation Protocol.
2//!
3//! This protocol is used in the boot services environment to perform
4//! lexical comparison functions on Unicode strings for given languages.
5
6use crate::proto::unsafe_protocol;
7use core::cmp::Ordering;
8use uefi::data_types::{CStr16, CStr8, Char16, Char8};
9
10/// The Unicode Collation Protocol.
11///
12/// Used to perform case-insensitive comparisons of strings.
13#[repr(C)]
14#[unsafe_protocol("a4c751fc-23ae-4c3e-92e9-4964cf63f349")]
15pub struct UnicodeCollation {
16 stri_coll: extern "efiapi" fn(this: &Self, s1: *const Char16, s2: *const Char16) -> isize,
17 metai_match:
18 extern "efiapi" fn(this: &Self, string: *const Char16, pattern: *const Char16) -> bool,
19 str_lwr: extern "efiapi" fn(this: &Self, s: *mut Char16),
20 str_upr: extern "efiapi" fn(this: &Self, s: *mut Char16),
21 fat_to_str: extern "efiapi" fn(this: &Self, fat_size: usize, fat: *const Char8, s: *mut Char16),
22 str_to_fat:
23 extern "efiapi" fn(this: &Self, s: *const Char16, fat_size: usize, fat: *mut Char8) -> bool,
24}
25
26impl UnicodeCollation {
27 /// Performs a case insensitive comparison of two
28 /// null-terminated strings.
29 #[must_use]
30 pub fn stri_coll(&self, s1: &CStr16, s2: &CStr16) -> Ordering {
31 let order = (self.stri_coll)(self, s1.as_ptr(), s2.as_ptr());
32 order.cmp(&0)
33 }
34
35 /// Performs a case insensitive comparison between a null terminated
36 /// pattern string and a null terminated string.
37 ///
38 /// This function checks if character pattern described in `pattern`
39 /// is found in `string`. If the pattern match succeeds, true is returned.
40 /// Otherwise, false is returned.
41 ///
42 /// The following syntax can be used to build the string `pattern`:
43 ///
44 /// |Pattern Character |Meaning |
45 /// |-----------------------------|--------------------------------------------------|
46 /// |* | Match 0 or more characters |
47 /// |? | Match any one character |
48 /// |[`char1` `char2`...`charN`]| Match any character in the set |
49 /// |[`char1`-`char2`] | Match any character between `char1` and `char2`|
50 /// |`char` | Match the character `char` |
51 ///
52 /// For example, the pattern "*.Fw" will match all strings that end
53 /// in ".FW", ".fw", ".Fw" or ".fW". The pattern "[a-z]" will match any
54 /// letter in the alphabet. The pattern "z" will match the letter "z".
55 /// The pattern "d?.*" will match the character "D" or "d" followed by
56 /// any single character followed by a "." followed by any string.
57 #[must_use]
58 pub fn metai_match(&self, s: &CStr16, pattern: &CStr16) -> bool {
59 (self.metai_match)(self, s.as_ptr(), pattern.as_ptr())
60 }
61
62 /// Converts the characters in `s` to lower case characters.
63 pub fn str_lwr<'a>(
64 &self,
65 s: &CStr16,
66 buf: &'a mut [u16],
67 ) -> Result<&'a CStr16, StrConversionError> {
68 let mut last_index = 0;
69 for (i, c) in s.iter().enumerate() {
70 *buf.get_mut(i).ok_or(StrConversionError::BufferTooSmall)? = (*c).into();
71 last_index = i;
72 }
73 *buf.get_mut(last_index + 1)
74 .ok_or(StrConversionError::BufferTooSmall)? = 0;
75
76 (self.str_lwr)(self, buf.as_ptr() as *mut _);
77
78 Ok(unsafe { CStr16::from_u16_with_nul_unchecked(buf) })
79 }
80
81 /// Converts the characters in `s` to upper case characters.
82 pub fn str_upr<'a>(
83 &self,
84 s: &CStr16,
85 buf: &'a mut [u16],
86 ) -> Result<&'a CStr16, StrConversionError> {
87 let mut last_index = 0;
88 for (i, c) in s.iter().enumerate() {
89 *buf.get_mut(i).ok_or(StrConversionError::BufferTooSmall)? = (*c).into();
90 last_index = i;
91 }
92 *buf.get_mut(last_index + 1)
93 .ok_or(StrConversionError::BufferTooSmall)? = 0;
94
95 (self.str_upr)(self, buf.as_ptr() as *mut _);
96
97 Ok(unsafe { CStr16::from_u16_with_nul_unchecked(buf) })
98 }
99
100 /// Converts the 8.3 FAT file name `fat` to a null terminated string.
101 pub fn fat_to_str<'a>(
102 &self,
103 fat: &CStr8,
104 buf: &'a mut [u16],
105 ) -> Result<&'a CStr16, StrConversionError> {
106 if buf.len() < fat.to_bytes_with_nul().len() {
107 return Err(StrConversionError::BufferTooSmall);
108 }
109 (self.fat_to_str)(
110 self,
111 fat.to_bytes_with_nul().len(),
112 fat.as_ptr(),
113 buf.as_ptr() as *mut _,
114 );
115 Ok(unsafe { CStr16::from_u16_with_nul_unchecked(buf) })
116 }
117
118 /// Converts the null terminated string `s` to legal characters in a FAT file name.
119 pub fn str_to_fat<'a>(
120 &self,
121 s: &CStr16,
122 buf: &'a mut [u8],
123 ) -> Result<&'a CStr8, StrConversionError> {
124 if s.as_slice_with_nul().len() > buf.len() {
125 return Err(StrConversionError::BufferTooSmall);
126 }
127 let failed = (self.str_to_fat)(
128 self,
129 s.as_ptr(),
130 s.as_slice_with_nul().len(),
131 buf.as_ptr() as *mut _,
132 );
133 if failed {
134 Err(StrConversionError::ConversionFailed)
135 } else {
136 // After the conversion, there is a possibility that the converted string
137 // is smaller than the original `s` string.
138 // When the converted string is smaller, there will be a bunch of trailing
139 // nulls.
140 // To remove all those trailing nulls:
141 let mut last_null_index = buf.len() - 1;
142 for i in (0..buf.len()).rev() {
143 if buf[i] != 0 {
144 last_null_index = i + 1;
145 break;
146 }
147 }
148 let buf = unsafe { core::slice::from_raw_parts(buf.as_ptr(), last_null_index + 1) };
149 Ok(unsafe { CStr8::from_bytes_with_nul_unchecked(buf) })
150 }
151 }
152}
153
154/// Errors returned by [`UnicodeCollation::str_lwr`] and [`UnicodeCollation::str_upr`].
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156pub enum StrConversionError {
157 /// The conversion failed.
158 ConversionFailed,
159 /// The buffer given is too small to hold the string.
160 BufferTooSmall,
161}
162