1#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum Utf8DecodeError {
39 InvalidByteIndex,
40 InvalidContinuationByte,
41 LoneSurrogate(u32),
42 InvalidUtf8Detected,
43}
44
45impl std::fmt::Display for Utf8DecodeError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Utf8DecodeError::InvalidByteIndex => write!(f, "Invalid byte index"),
49 Utf8DecodeError::InvalidContinuationByte => write!(f, "Invalid continuation byte"),
50 Utf8DecodeError::LoneSurrogate(code) => write!(
51 f,
52 "Lone surrogate U+{} is not a scalar value",
53 format!("{:X}", code)
54 ),
55 Utf8DecodeError::InvalidUtf8Detected => write!(f, "Invalid UTF-8 detected"),
56 }
57 }
58}
59
60impl std::error::Error for Utf8DecodeError {}
61
62fn char_length_in_bytes(code: u32) -> usize {
64 if (code & 0xffff_ff80) == 0 {
65 1
66 } else if (code & 0xffff_f800) == 0 {
67 2
68 } else if (code & 0xffff_0000) == 0 {
69 3
70 } else {
71 4
72 }
73}
74
75pub fn string_length_in_bytes(value: &str) -> usize {
80 let mut result = 0;
81 for c in value.chars() {
82 result += char_length_in_bytes(c as u32);
83 }
84 result
85}
86
87fn write_character(buffer: &mut [u8], offset: usize, code: u32) -> usize {
90 let length = char_length_in_bytes(code);
91
92 match length {
93 1 => {
94 buffer[offset] = code as u8;
95 }
96 2 => {
97 buffer[offset] = (((code >> 6) & 0x1f) | 0xc0) as u8;
98 buffer[offset + 1] = ((code & 0x3f) | 0x80) as u8;
99 }
100 3 => {
101 buffer[offset] = (((code >> 12) & 0x0f) | 0xe0) as u8;
102 buffer[offset + 1] = (((code >> 6) & 0x3f) | 0x80) as u8;
103 buffer[offset + 2] = ((code & 0x3f) | 0x80) as u8;
104 }
105 _ => {
106 buffer[offset] = (((code >> 18) & 0x07) | 0xf0) as u8;
107 buffer[offset + 1] = (((code >> 12) & 0x3f) | 0x80) as u8;
108 buffer[offset + 2] = (((code >> 6) & 0x3f) | 0x80) as u8;
109 buffer[offset + 3] = ((code & 0x3f) | 0x80) as u8;
110 }
111 }
112
113 length
114}
115
116pub fn encode_string_to(buffer: &mut [u8], offset: usize, value: &str) -> usize {
119 let mut offset = offset;
120 for c in value.chars() {
121 offset += write_character(buffer, offset, c as u32);
122 }
123 offset
124}
125
126pub fn encode_string(value: &str) -> Vec<u8> {
132 let mut buffer = vec![0u8; string_length_in_bytes(value)];
133 encode_string_to(&mut buffer, 0, value);
134 buffer
135}
136
137fn continuation_byte(buffer: &[u8], index: usize) -> Result<u32, Utf8DecodeError> {
139 if index >= buffer.len() {
140 return Err(Utf8DecodeError::InvalidByteIndex);
141 }
142
143 let continuation_byte = buffer[index];
144
145 if (continuation_byte & 0xC0) == 0x80 {
146 Ok((continuation_byte & 0x3F) as u32)
147 } else {
148 Err(Utf8DecodeError::InvalidContinuationByte)
149 }
150}
151
152pub fn decode_string(value: &[u8]) -> Result<String, Utf8DecodeError> {
157 let mut result = String::new();
158 let mut i = 0usize;
159
160 while i < value.len() {
161 let byte1 = value[i] as u32;
162 i += 1;
163 let code: u32;
164
165 if (byte1 & 0x80) == 0 {
166 code = byte1;
167 } else if (byte1 & 0xe0) == 0xc0 {
168 let byte2 = continuation_byte(value, i)?;
169 i += 1;
170 code = ((byte1 & 0x1f) << 6) | byte2;
171
172 if code < 0x80 {
173 return Err(Utf8DecodeError::InvalidContinuationByte);
174 }
175 } else if (byte1 & 0xf0) == 0xe0 {
176 let byte2 = continuation_byte(value, i)?;
177 i += 1;
178 let byte3 = continuation_byte(value, i)?;
179 i += 1;
180 code = ((byte1 & 0x0f) << 12) | (byte2 << 6) | byte3;
181
182 if code < 0x0800 {
183 return Err(Utf8DecodeError::InvalidContinuationByte);
184 }
185
186 if (0xd800..=0xdfff).contains(&code) {
187 return Err(Utf8DecodeError::LoneSurrogate(code));
188 }
189 } else if (byte1 & 0xf8) == 0xf0 {
190 let byte2 = continuation_byte(value, i)?;
191 i += 1;
192 let byte3 = continuation_byte(value, i)?;
193 i += 1;
194 let byte4 = continuation_byte(value, i)?;
195 i += 1;
196 code = ((byte1 & 0x0f) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
197
198 if code < 0x01_0000 || code > 0x10_ffff {
199 return Err(Utf8DecodeError::InvalidContinuationByte);
200 }
201 } else {
202 return Err(Utf8DecodeError::InvalidUtf8Detected);
203 }
204
205 if let Some(ch) = char::from_u32(code) {
209 result.push(ch);
210 } else {
211 return Err(Utf8DecodeError::InvalidUtf8Detected);
213 }
214 }
215
216 Ok(result)
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn ascii_round_trip() {
225 let s = "Hello, World!";
226 let bytes = encode_string(s);
227 assert_eq!(bytes, s.as_bytes());
228 assert_eq!(decode_string(&bytes).unwrap(), s);
229 }
230
231 #[test]
232 fn empty_round_trip() {
233 let s = "";
234 let bytes = encode_string(s);
235 assert!(bytes.is_empty());
236 assert_eq!(string_length_in_bytes(s), 0);
237 assert_eq!(decode_string(&bytes).unwrap(), s);
238 }
239
240 #[test]
241 fn cyrillic_round_trip() {
242 let s = "Привет";
243 let bytes = encode_string(s);
244 assert_eq!(bytes.len(), 12);
246 assert_eq!(string_length_in_bytes(s), 12);
247 assert_eq!(&bytes[0..4], &[0xD0, 0x9F, 0xD1, 0x80]);
249 assert_eq!(bytes, s.as_bytes());
250 assert_eq!(decode_string(&bytes).unwrap(), s);
251 }
252
253 #[test]
254 fn three_byte_round_trip() {
255 let s = "あ"; let bytes = encode_string(s);
257 assert_eq!(bytes, vec![0xE3, 0x81, 0x82]);
258 assert_eq!(string_length_in_bytes(s), 3);
259 assert_eq!(decode_string(&bytes).unwrap(), s);
260 }
261
262 #[test]
263 fn emoji_surrogate_pair_round_trip() {
264 let s = "😀"; let bytes = encode_string(s);
266 assert_eq!(bytes, vec![0xF0, 0x9F, 0x98, 0x80]);
267 assert_eq!(string_length_in_bytes(s), 4);
268 assert_eq!(decode_string(&bytes).unwrap(), s);
269 }
270
271 #[test]
272 fn mixed_round_trip() {
273 let s = "aЯ あ😀z";
274 let bytes = encode_string(s);
275 assert_eq!(bytes, s.as_bytes());
276 assert_eq!(string_length_in_bytes(s), s.len());
277 assert_eq!(decode_string(&bytes).unwrap(), s);
278 }
279
280 #[test]
281 fn decode_rejects_lone_surrogate() {
282 let err = decode_string(&[0xED, 0xA0, 0x80]).unwrap_err();
284 assert_eq!(err, Utf8DecodeError::LoneSurrogate(0xD800));
285 }
286
287 #[test]
288 fn decode_rejects_overlong_two_byte() {
289 let err = decode_string(&[0xC0, 0x80]).unwrap_err();
291 assert_eq!(err, Utf8DecodeError::InvalidContinuationByte);
292 }
293
294 #[test]
295 fn decode_rejects_bad_continuation() {
296 let err = decode_string(&[0xC2, 0x20]).unwrap_err();
298 assert_eq!(err, Utf8DecodeError::InvalidContinuationByte);
299 }
300
301 #[test]
302 fn decode_rejects_truncated() {
303 let err = decode_string(&[0xE3, 0x81]).unwrap_err();
305 assert_eq!(err, Utf8DecodeError::InvalidByteIndex);
306 }
307
308 #[test]
309 fn decode_rejects_invalid_lead() {
310 let err = decode_string(&[0xFF]).unwrap_err();
311 assert_eq!(err, Utf8DecodeError::InvalidUtf8Detected);
312 }
313}