formatting_nostd/
format_buffer.rs1use core::ffi::CStr;
2use core::mem::MaybeUninit;
3
4pub struct FormatBuffer<const N: usize> {
28 buffer: [MaybeUninit<u8>; N],
29 used: usize,
31 truncated: usize,
32}
33
34impl<const N: usize> FormatBuffer<N> {
35 const CAPACITY_INCLUDING_NULL: usize = N;
36 const CAPACITY: usize = N - 1;
37
38 pub fn new() -> Self {
39 assert!(Self::CAPACITY_INCLUDING_NULL >= 1);
40 let mut res = Self {
41 buffer: [MaybeUninit::uninit(); N],
42 used: 0,
43 truncated: 0,
44 };
45 res.null_terminate();
46 res
47 }
48
49 pub fn capacity_remaining(&self) -> usize {
51 Self::CAPACITY - self.used
52 }
53
54 pub fn capacity_remaining_including_null(&self) -> usize {
55 Self::CAPACITY_INCLUDING_NULL - self.used
56 }
57
58 pub fn truncated(&self) -> usize {
63 self.truncated
64 }
65
66 fn null_terminate(&mut self) {
67 self.buffer[self.used].write(0);
68 }
69
70 pub fn reset(&mut self) {
75 self.used = 0;
76 self.truncated = 0;
77 self.null_terminate();
78 }
79
80 fn initd_buffer_including_null(&self) -> &[u8] {
82 let buffer: *const MaybeUninit<u8> = self.buffer.as_ptr();
83 let buffer: *const u8 = buffer as *const u8;
85 let rv = unsafe { core::slice::from_raw_parts(buffer, self.used + 1) };
87 assert_eq!(rv.last(), Some(&0));
88 rv
89 }
90
91 fn initd_buffer_excluding_null(&self) -> &[u8] {
92 let res = self.initd_buffer_including_null();
93 &res[..(res.len() - 1)]
94 }
95
96 pub fn as_str(&self) -> &str {
101 unsafe { core::str::from_utf8_unchecked(self.initd_buffer_excluding_null()) }
103 }
104
105 pub fn as_cstr(&self) -> Option<&CStr> {
107 CStr::from_bytes_with_nul(self.initd_buffer_including_null()).ok()
108 }
109
110 pub unsafe fn sprintf(&mut self, fmt: &CStr, args: va_list::VaList) {
125 let mut buf = [MaybeUninit::<i8>::uninit(); N];
139
140 let rv = unsafe { vsnprintf(buf.as_mut_ptr() as *mut i8, buf.len(), fmt.as_ptr(), args) };
141
142 let formatted_len = match usize::try_from(rv) {
144 Ok(n) => n,
145 Err(_) => {
146 panic!("vsnprintf returned {rv}");
147 }
148 };
149
150 unsafe fn transmute_to_u8(buf: &[MaybeUninit<i8>]) -> &[u8] {
153 unsafe { core::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()) }
154 }
155
156 let non_null_bytes_written = core::cmp::min(buf.len() - 1, formatted_len);
160 let initd_buf = unsafe { transmute_to_u8(&buf[..non_null_bytes_written]) };
161
162 for decoded_char in crate::utf8::decode_lossy(initd_buf) {
163 if self.truncated > 0 || decoded_char.len() > self.capacity_remaining() {
164 self.truncated += decoded_char.len()
165 } else {
166 self.write_fitting_str(decoded_char)
167 }
168 }
169
170 self.truncated += formatted_len - non_null_bytes_written;
174 self.null_terminate();
175 }
176
177 fn write_fitting_str(&mut self, src: &str) {
179 assert!(src.len() <= self.capacity_remaining());
180
181 let dst: *mut MaybeUninit<u8> = unsafe { self.buffer.as_mut_ptr().add(self.used) };
183
184 let dst: *mut u8 = dst as *mut u8;
187
188 unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len()) };
189 self.used += src.len();
190 self.null_terminate();
191 }
192}
193
194impl<const N: usize> core::fmt::Write for FormatBuffer<N> {
195 fn write_str(&mut self, src: &str) -> Result<(), core::fmt::Error> {
196 if self.truncated() > 0 {
197 self.truncated += src.len();
199 return Ok(());
200 }
201
202 if src.len() <= self.capacity_remaining() {
203 self.write_fitting_str(src);
204 return Ok(());
205 }
206
207 let mut nbytes = self.capacity_remaining();
210 while !src.is_char_boundary(nbytes) {
211 nbytes -= 1;
212 }
213 self.truncated += src.len() - nbytes;
214
215 self.write_fitting_str(&src[..nbytes]);
216 Ok(())
217 }
218}
219
220impl<const N: usize> core::fmt::Display for FormatBuffer<N> {
221 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
222 if self.truncated == 0 {
223 write!(f, "{}", self.as_str())
224 } else {
225 write!(f, "{}...<truncated {}>", self.as_str(), self.truncated())
226 }
227 }
228}
229
230impl<const N: usize> Default for FormatBuffer<N> {
231 fn default() -> Self {
232 Self::new()
233 }
234}
235
236extern crate libc;
238
239unsafe extern "C" {
240 fn vsnprintf(
248 str: *mut core::ffi::c_char,
249 size: usize,
250 fmt: *const core::ffi::c_char,
251 ap: va_list::VaList,
252 ) -> i32;
253}
254
255#[cfg(test)]
256mod test {
257 use core::fmt::Write;
258
259 use std::ffi::CString;
260
261 use super::*;
262
263 #[test]
264 fn test_format_buffer_write_str_exact() {
265 let mut buf = FormatBuffer::<4>::new();
266 assert!(buf.write_str("123").is_ok());
267 assert_eq!(buf.as_str(), "123");
268 assert_eq!(buf.truncated(), 0);
269 }
270
271 #[test]
272 fn test_format_buffer_write_str_truncated() {
273 let mut buf = FormatBuffer::<3>::new();
274 assert!(buf.write_str("123").is_ok());
275 assert_eq!(buf.as_str(), "12");
276 assert_eq!(buf.truncated(), 1);
277 }
278
279 #[test]
280 fn test_format_buffer_write_str_truncated_unicode() {
281 let mut buf = FormatBuffer::<3>::new();
282 assert!(buf.write_str("1¡").is_ok());
286 assert_eq!(buf.as_str(), "1");
287 assert_eq!(buf.truncated(), 2);
288
289 assert_eq!(buf.capacity_remaining(), 1);
292 assert!(buf.write_str("2").is_ok());
293 assert_eq!(buf.capacity_remaining(), 1);
294 assert_eq!(buf.truncated(), 3);
295 }
296
297 #[test]
298 fn test_format_buffer_display_truncated() {
299 let mut buf = FormatBuffer::<3>::new();
300 assert!(buf.write_str("123").is_ok());
301 assert_eq!(format!("{buf}"), "12...<truncated 1>");
302 }
303
304 #[test]
305 fn test_format_buffer_write_str_multiple() {
306 let mut buf = FormatBuffer::<7>::new();
307 assert!(buf.write_str("123").is_ok());
308 assert_eq!(buf.as_str(), "123");
309 assert!(buf.write_str("456").is_ok());
310 assert_eq!(buf.as_str(), "123456");
311 }
312
313 #[test]
314 fn test_cstr_ok() {
315 let mut buf = FormatBuffer::<7>::new();
316 assert!(buf.write_str("123").is_ok());
317 let expected = CString::new("123").unwrap();
318 assert_eq!(buf.as_cstr(), Some(expected.as_c_str()));
319 }
320}
321
322#[cfg(all(test, not(miri)))]
324mod sprintf_test {
325 use std::ffi::CString;
326
327 use super::*;
328
329 #[unsafe(no_mangle)]
331 unsafe extern "C-unwind" fn test_format_buffer_valist(
332 format_buffer: *mut FormatBuffer<10>,
333 fmt: *const core::ffi::c_char,
334 args: va_list::VaList,
335 ) {
336 let fmt = unsafe { CStr::from_ptr(fmt) };
337 let format_buffer = unsafe { format_buffer.as_mut().unwrap() };
338 unsafe { format_buffer.sprintf(fmt, args) };
339 }
340
341 unsafe extern "C-unwind" {
342 #[allow(improper_ctypes)]
348 fn test_format_buffer_vararg(
349 format_buffer: *mut FormatBuffer<10>,
350 fmt: *const core::ffi::c_char,
351 ...
352 );
353 }
354
355 #[test]
356 fn test_sprintf_noargs() {
357 let mut buf = FormatBuffer::<10>::new();
358 let fmt = CString::new("hello").unwrap();
359 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr()) };
360 assert_eq!(buf.as_str(), "hello");
361 assert_eq!(buf.truncated(), 0);
362 }
363
364 #[test]
365 fn test_sprintf_args() {
366 let mut buf = FormatBuffer::<10>::new();
367 let fmt = CString::new("x %d y").unwrap();
368 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr(), 42i32) };
369 assert_eq!(buf.as_str(), "x 42 y");
370 assert_eq!(buf.truncated(), 0);
371 }
372
373 #[test]
374 fn test_sprintf_truncated() {
375 let mut buf = FormatBuffer::<10>::new();
376 let fmt = CString::new("1234567890123").unwrap();
377
378 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr()) };
381 assert_eq!(buf.as_str(), "123456789");
382 assert_eq!(buf.truncated(), 4);
383 }
384
385 #[test]
386 fn test_sprintf_truncated_partly_full() {
387 let mut buf = FormatBuffer::<10>::new();
388 let fmt = CString::new("12345678").unwrap();
389 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr()) };
390 assert_eq!(buf.as_str(), "12345678");
391 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr()) };
392 assert_eq!(buf.as_str(), "123456781");
393 assert_eq!(buf.truncated(), 7);
394 }
395
396 #[test]
397 fn test_sprintf_truncated_unicode() {
398 let mut buf = FormatBuffer::<10>::new();
399 let fmt = CString::new("123456789¡").unwrap();
403 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr()) };
404 assert_eq!(buf.as_str(), "123456789");
405 assert_eq!(buf.truncated(), 2);
406 }
407
408 #[test]
409 fn test_sprintf_unicode_replacement() {
410 let mut buf = FormatBuffer::<10>::new();
411 let fmt = CString::new("x%cy").unwrap();
415 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr(), 0x80 as core::ffi::c_int) };
416 assert_eq!(buf.as_str(), "x�y");
417 assert_eq!(buf.truncated(), 0);
418 }
419
420 #[test]
421 fn test_sprintf_unicode_replacement_truncation() {
422 let mut buf = FormatBuffer::<10>::new();
423 let fmt = CString::new("12345678%c").unwrap();
427 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr(), 0x80 as core::ffi::c_int) };
428 assert_eq!(buf.as_str(), "12345678");
430 assert!(buf.truncated() > 0);
433 }
434
435 #[test]
436 fn test_sprintf_multiple() {
437 let mut buf = FormatBuffer::<10>::new();
438 let fmt = CString::new("123").unwrap();
439 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr()) };
440 let fmt = CString::new("456").unwrap();
441 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr()) };
442 assert_eq!(buf.as_str(), "123456");
443 assert_eq!(buf.truncated(), 0);
444 }
445
446 #[test]
447 fn test_sprintf_cstr_fail() {
448 let mut buf = FormatBuffer::<10>::new();
449 let fmt = CString::new("1234%c56").unwrap();
451
452 unsafe { test_format_buffer_vararg(&mut buf, fmt.as_ptr(), 0 as core::ffi::c_int) };
455 assert_eq!(buf.as_cstr(), None);
456 }
457}