1use crate::error::{DxError, Result};
7
8pub fn validate_utf8(bytes: &[u8]) -> Result<&str> {
28 match std::str::from_utf8(bytes) {
29 Ok(s) => Ok(s),
30 Err(e) => Err(DxError::Utf8Error {
31 offset: e.valid_up_to(),
32 }),
33 }
34}
35
36pub fn validate_utf8_owned(bytes: Vec<u8>) -> Result<String> {
41 match String::from_utf8(bytes) {
42 Ok(s) => Ok(s),
43 Err(e) => Err(DxError::Utf8Error {
44 offset: e.utf8_error().valid_up_to(),
45 }),
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct Utf8ValidationError {
55 pub offset: usize,
57 pub invalid_byte: Option<u8>,
59 pub description: String,
61}
62
63impl Utf8ValidationError {
64 pub fn new(offset: usize, invalid_byte: Option<u8>, description: impl Into<String>) -> Self {
66 Self {
67 offset,
68 invalid_byte,
69 description: description.into(),
70 }
71 }
72}
73
74#[allow(unsafe_code)]
79pub fn validate_utf8_detailed(bytes: &[u8]) -> std::result::Result<&str, Utf8ValidationError> {
80 let mut i = 0;
81 while i < bytes.len() {
82 let byte = bytes[i];
83
84 let seq_len = if byte & 0x80 == 0 {
86 1
88 } else if byte & 0xE0 == 0xC0 {
89 2
91 } else if byte & 0xF0 == 0xE0 {
92 3
94 } else if byte & 0xF8 == 0xF0 {
95 4
97 } else if byte & 0xC0 == 0x80 {
98 return Err(Utf8ValidationError::new(
100 i,
101 Some(byte),
102 format!(
103 "Unexpected continuation byte 0x{:02X} at position {}",
104 byte, i
105 ),
106 ));
107 } else {
108 return Err(Utf8ValidationError::new(
110 i,
111 Some(byte),
112 format!("Invalid UTF-8 start byte 0x{:02X} at position {}", byte, i),
113 ));
114 };
115
116 if i + seq_len > bytes.len() {
118 return Err(Utf8ValidationError::new(
119 i,
120 Some(byte),
121 format!(
122 "Incomplete UTF-8 sequence at position {}: expected {} bytes, got {}",
123 i,
124 seq_len,
125 bytes.len() - i
126 ),
127 ));
128 }
129
130 for j in 1..seq_len {
132 let cont_byte = bytes[i + j];
133 if cont_byte & 0xC0 != 0x80 {
134 return Err(Utf8ValidationError::new(
135 i + j,
136 Some(cont_byte),
137 format!(
138 "Invalid continuation byte 0x{:02X} at position {} (expected 10xxxxxx)",
139 cont_byte,
140 i + j
141 ),
142 ));
143 }
144 }
145
146 if seq_len == 2 {
148 let code_point = ((byte as u32 & 0x1F) << 6) | (bytes[i + 1] as u32 & 0x3F);
150 if code_point < 0x80 {
151 return Err(Utf8ValidationError::new(
152 i,
153 Some(byte),
154 format!(
155 "Overlong encoding at position {}: 2-byte sequence for code point U+{:04X}",
156 i, code_point
157 ),
158 ));
159 }
160 } else if seq_len == 3 {
161 let code_point = ((byte as u32 & 0x0F) << 12)
163 | ((bytes[i + 1] as u32 & 0x3F) << 6)
164 | (bytes[i + 2] as u32 & 0x3F);
165 if code_point < 0x800 {
166 return Err(Utf8ValidationError::new(
167 i,
168 Some(byte),
169 format!(
170 "Overlong encoding at position {}: 3-byte sequence for code point U+{:04X}",
171 i, code_point
172 ),
173 ));
174 }
175 if (0xD800..=0xDFFF).contains(&code_point) {
176 return Err(Utf8ValidationError::new(
177 i,
178 Some(byte),
179 format!(
180 "Invalid surrogate code point U+{:04X} at position {}",
181 code_point, i
182 ),
183 ));
184 }
185 } else if seq_len == 4 {
186 let code_point = ((byte as u32 & 0x07) << 18)
188 | ((bytes[i + 1] as u32 & 0x3F) << 12)
189 | ((bytes[i + 2] as u32 & 0x3F) << 6)
190 | (bytes[i + 3] as u32 & 0x3F);
191 if code_point < 0x10000 {
192 return Err(Utf8ValidationError::new(
193 i,
194 Some(byte),
195 format!(
196 "Overlong encoding at position {}: 4-byte sequence for code point U+{:04X}",
197 i, code_point
198 ),
199 ));
200 }
201 if code_point > 0x10FFFF {
202 return Err(Utf8ValidationError::new(
203 i,
204 Some(byte),
205 format!(
206 "Code point U+{:04X} exceeds maximum U+10FFFF at position {}",
207 code_point, i
208 ),
209 ));
210 }
211 }
212
213 i += seq_len;
214 }
215
216 Ok(unsafe { std::str::from_utf8_unchecked(bytes) })
220}
221
222pub fn validate_string_input(bytes: &[u8], base_offset: usize) -> Result<&str> {
227 match std::str::from_utf8(bytes) {
228 Ok(s) => Ok(s),
229 Err(e) => Err(DxError::Utf8Error {
230 offset: base_offset + e.valid_up_to(),
231 }),
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_valid_ascii() {
241 let input = b"Hello, World!";
242 assert_eq!(validate_utf8(input).unwrap(), "Hello, World!");
243 }
244
245 #[test]
246 fn test_valid_utf8_multibyte() {
247 let input = "Hello, 世界!".as_bytes();
249 assert_eq!(validate_utf8(input).unwrap(), "Hello, 世界!");
250 }
251
252 #[test]
253 fn test_valid_utf8_emoji() {
254 let input = "Hello 🌍!".as_bytes();
256 assert_eq!(validate_utf8(input).unwrap(), "Hello 🌍!");
257 }
258
259 #[test]
260 fn test_invalid_continuation_byte() {
261 let input = &[0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x80]; let err = validate_utf8(input).unwrap_err();
264 if let DxError::Utf8Error { offset } = err {
265 assert_eq!(offset, 5);
266 } else {
267 panic!("Expected Utf8Error");
268 }
269 }
270
271 #[test]
272 fn test_invalid_start_byte() {
273 let input = &[0x48, 0x65, 0xFF, 0x6c, 0x6f]; let err = validate_utf8(input).unwrap_err();
276 if let DxError::Utf8Error { offset } = err {
277 assert_eq!(offset, 2);
278 } else {
279 panic!("Expected Utf8Error");
280 }
281 }
282
283 #[test]
284 fn test_incomplete_sequence() {
285 let input = &[0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xC2]; let err = validate_utf8(input).unwrap_err();
288 if let DxError::Utf8Error { offset } = err {
289 assert_eq!(offset, 5);
290 } else {
291 panic!("Expected Utf8Error");
292 }
293 }
294
295 #[test]
296 fn test_detailed_validation_overlong() {
297 let input = &[0xC1, 0x81]; let err = validate_utf8_detailed(input).unwrap_err();
300 assert_eq!(err.offset, 0);
301 assert!(err.description.contains("Overlong"));
302 }
303
304 #[test]
305 fn test_detailed_validation_surrogate() {
306 let input = &[0xED, 0xA0, 0x80]; let err = validate_utf8_detailed(input).unwrap_err();
309 assert_eq!(err.offset, 0);
310 assert!(err.description.contains("surrogate"));
311 }
312
313 #[test]
314 fn test_detailed_validation_too_large() {
315 let input = &[0xF4, 0x90, 0x80, 0x80]; let err = validate_utf8_detailed(input).unwrap_err();
318 assert_eq!(err.offset, 0);
319 assert!(err.description.contains("exceeds"));
320 }
321
322 #[test]
323 fn test_validate_string_input_with_offset() {
324 let input = &[0x48, 0x65, 0xFF]; let err = validate_string_input(input, 10).unwrap_err();
327 if let DxError::Utf8Error { offset } = err {
328 assert_eq!(offset, 12); } else {
330 panic!("Expected Utf8Error");
331 }
332 }
333
334 #[test]
335 fn test_empty_input() {
336 assert_eq!(validate_utf8(b"").unwrap(), "");
337 }
338
339 #[test]
340 fn test_validate_utf8_owned() {
341 let valid = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; assert_eq!(validate_utf8_owned(valid).unwrap(), "Hello");
343
344 let invalid = vec![0x48, 0x65, 0xFF]; let err = validate_utf8_owned(invalid).unwrap_err();
346 if let DxError::Utf8Error { offset } = err {
347 assert_eq!(offset, 2);
348 } else {
349 panic!("Expected Utf8Error");
350 }
351 }
352}