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