1use core::fmt::Write;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(u8)]
12pub enum SizeUnit {
13 Auto = 0, Bytes = b'B',
15 KiB = b'K',
16 MiB = b'M',
17 GiB = b'G',
18 TiB = b'T',
19 PiB = b'P',
20 EiB = b'E',
21}
22
23impl TryFrom<u8> for SizeUnit {
24 type Error = ();
25 fn try_from(value: u8) -> Result<Self, Self::Error> {
26 match value.to_ascii_uppercase() {
27 b'B' => Ok(SizeUnit::Bytes),
28 b'K' => Ok(SizeUnit::KiB),
29 b'M' => Ok(SizeUnit::MiB),
30 b'G' => Ok(SizeUnit::GiB),
31 b'T' => Ok(SizeUnit::TiB),
32 b'P' => Ok(SizeUnit::PiB),
33 b'E' => Ok(SizeUnit::EiB),
34 0 => Ok(SizeUnit::Auto),
35 _ => Err(()),
36 }
37 }
38}
39
40impl TryFrom<char> for SizeUnit {
41 type Error = ();
42 fn try_from(value: char) -> Result<Self, Self::Error> {
43 if value.is_ascii() { Self::try_from(value as u8) } else { Err(()) }
44 }
45}
46
47impl SizeUnit {
48 pub fn to_str(self) -> &'static str {
49 match self {
50 SizeUnit::Auto => "",
51 SizeUnit::Bytes => "B",
52 SizeUnit::KiB => "K",
53 SizeUnit::MiB => "M",
54 SizeUnit::GiB => "G",
55 SizeUnit::TiB => "T",
56 SizeUnit::PiB => "P",
57 SizeUnit::EiB => "E",
58 }
59 }
60}
61
62pub const MAX_FORMAT_SIZE_LEN: usize = 22;
66
67struct SliceWriter<'a> {
68 slice: &'a mut [u8],
69 cursor: usize,
70}
71
72impl<'a> SliceWriter<'a> {
73 fn new(slice: &'a mut [u8]) -> Self {
74 Self { slice, cursor: 0 }
75 }
76
77 fn into_str(self) -> &'a str {
78 core::str::from_utf8(&self.slice[..self.cursor]).unwrap_or("")
79 }
80}
81
82impl<'a> core::fmt::Write for SliceWriter<'a> {
83 fn write_str(&mut self, s: &str) -> core::fmt::Result {
84 let bytes = s.as_bytes();
85 let keep = core::cmp::min(bytes.len(), self.slice.len() - self.cursor);
86 self.slice[self.cursor..self.cursor + keep].copy_from_slice(&bytes[..keep]);
87 self.cursor += keep;
88 Ok(())
89 }
90}
91
92fn rounding_divide(n: usize, d: usize) -> usize {
97 let round_up = (n % d) >= (d / 2);
100 n / d + if round_up { 1 } else { 0 }
101}
102
103pub fn format_size_fixed_rs(buf: &mut [u8], bytes: usize, mut unit: u8) -> &str {
118 if buf.is_empty() {
119 return "";
120 }
121
122 let mut writer = SliceWriter::new(buf);
123 let units = b"BKMGTPE";
124 let num_units = units.len();
125
126 let orig_bytes = bytes;
127 let mut prepended_question = false;
128
129 let mut bytes = bytes;
130
131 loop {
132 let mut ui = 0;
133 let mut divisor = 1;
134
135 while if unit != 0 {
145 ui < num_units && units[ui] != unit
146 } else {
147 bytes >= 10000 || (bytes != 0 && (bytes & 1023) == 0)
148 } {
149 ui += 1;
150 if ui >= num_units {
151 if !prepended_question {
154 let _ = writer.write_char('?');
155 prepended_question = true;
156 }
157 unit = 0;
158 bytes = orig_bytes;
159 break;
160 }
161 bytes /= 1024;
162 divisor *= 1024;
163 }
164
165 if ui < num_units {
166 if orig_bytes.is_multiple_of(divisor) {
169 let _ = core::write!(&mut writer, "{}{}", bytes, units[ui] as char);
170 } else {
171 let mut int_part = orig_bytes / divisor;
180 let mut fractional_part = rounding_divide((orig_bytes % divisor) * 10, divisor);
181 if fractional_part >= 10 {
182 fractional_part = 0;
184 int_part += 1;
185 }
186 let _ = core::write!(
187 &mut writer,
188 "{}.{}{}",
189 int_part,
190 fractional_part,
191 units[ui] as char
192 );
193 }
194 break;
195 }
196 }
197
198 let s = writer.into_str();
199 assert!(s.len() <= MAX_FORMAT_SIZE_LEN);
200 s
201}
202
203pub fn format_size_rs(buf: &mut [u8], bytes: usize) -> &str {
205 format_size_fixed_rs(buf, bytes, 0)
206}
207
208#[derive(Clone, Copy)]
212pub struct FormattedBytes {
213 buf: [u8; MAX_FORMAT_SIZE_LEN],
214 len: usize,
215}
216
217impl Default for FormattedBytes {
218 fn default() -> Self {
219 Self::empty()
220 }
221}
222
223impl FormattedBytes {
224 pub const fn empty() -> Self {
226 Self { buf: [0u8; MAX_FORMAT_SIZE_LEN], len: 0 }
227 }
228
229 pub fn new(bytes: usize) -> Self {
231 let mut res = Self::empty();
232 res.set_size(bytes);
233 res
234 }
235
236 pub fn with_unit(bytes: usize, unit: SizeUnit) -> Self {
238 let mut res = Self::empty();
239 res.set_size_with_unit(bytes, unit);
240 res
241 }
242
243 pub fn set_size(&mut self, bytes: usize) -> &mut Self {
245 let s = format_size_rs(&mut self.buf, bytes);
246 self.len = s.len();
247 self
248 }
249
250 pub fn set_size_with_unit(&mut self, bytes: usize, unit: SizeUnit) -> &mut Self {
252 let s = format_size_fixed_rs(&mut self.buf, bytes, unit as u8);
253 self.len = s.len();
254 self
255 }
256
257 pub fn as_str(&self) -> &str {
259 core::str::from_utf8(&self.buf[..self.len]).unwrap_or("")
260 }
261
262 pub fn magnitude(&self) -> &str {
264 let s = self.as_str();
265 if s.is_empty() { s } else { &s[..s.len() - 1] }
266 }
267
268 pub fn unit(&self) -> SizeUnit {
270 let s = self.as_str().as_bytes();
271 match s.last() {
272 Some(&last) => SizeUnit::try_from(last).unwrap_or(SizeUnit::Auto),
273 None => SizeUnit::Auto,
274 }
275 }
276}
277
278impl core::ops::Deref for FormattedBytes {
279 type Target = str;
280
281 fn deref(&self) -> &str {
282 self.as_str()
283 }
284}
285
286impl AsRef<str> for FormattedBytes {
287 fn as_ref(&self) -> &str {
288 self.as_str()
289 }
290}
291
292impl core::fmt::Display for FormattedBytes {
293 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
294 f.write_str(self.as_str())
295 }
296}
297
298impl core::fmt::Debug for FormattedBytes {
299 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
300 core::fmt::Debug::fmt(self.as_str(), f)
301 }
302}
303
304impl kprint::backend::AsKPrintStr for FormattedBytes {
305 #[inline(always)]
306 fn kprint_len(&self) -> core::ffi::c_int {
307 self.len as core::ffi::c_int
308 }
309
310 #[inline(always)]
311 fn kprint_ptr(&self) -> *const core::ffi::c_char {
312 self.buf.as_ptr() as *const core::ffi::c_char
313 }
314}
315
316impl kprint::backend::AsKPrintStr for &FormattedBytes {
317 #[inline(always)]
318 fn kprint_len(&self) -> core::ffi::c_int {
319 self.len as core::ffi::c_int
320 }
321
322 #[inline(always)]
323 fn kprint_ptr(&self) -> *const core::ffi::c_char {
324 self.buf.as_ptr() as *const core::ffi::c_char
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329struct EncodedSize<'a> {
330 integral: &'a str,
332
333 fractional: Option<&'a str>,
335
336 unit: SizeUnit,
337
338 scale: u64,
339}
340
341fn process_formatted_string(mut formatted: &str) -> Option<EncodedSize<'_>> {
342 if formatted.is_empty() {
343 return None;
344 }
345
346 let mut unit = SizeUnit::Bytes;
347 let mut scale = 1u64;
348
349 if let Some(last_char) = formatted.chars().next_back() {
350 if !last_char.is_ascii_digit() {
351 unit = SizeUnit::try_from(last_char).ok()?;
352 formatted = &formatted[..formatted.len() - last_char.len_utf8()];
353
354 match unit {
356 SizeUnit::Bytes => scale = 1,
357 SizeUnit::KiB => scale = 1 << 10,
358 SizeUnit::MiB => scale = 1 << 20,
359 SizeUnit::GiB => scale = 1 << 30,
360 SizeUnit::TiB => scale = 1 << 40,
361 SizeUnit::PiB => scale = 1 << 50,
362 SizeUnit::EiB => scale = 1 << 60,
363 _ => return None,
364 }
365 }
366 }
367
368 let split_at = formatted.find('.');
369 let (integral, fractional) = if let Some(split_at) = split_at {
370 let integral = &formatted[..split_at];
371 let fractional = &formatted[split_at + 1..];
372 if fractional.is_empty() {
374 return None;
375 }
376 (integral, Some(fractional))
377 } else {
378 (formatted, None)
379 };
380
381 if integral.is_empty() {
382 return None;
383 }
384
385 Some(EncodedSize { integral, fractional, unit, scale })
386}
387
388pub fn parse_size_bytes(formatted_bytes: &str) -> Option<u64> {
396 let encoded_size = process_formatted_string(formatted_bytes)?;
397
398 let mut integral: u64 = 0;
399 let mut base_10: u64 = 1;
400
401 for c in encoded_size.integral.chars().rev() {
402 if !c.is_ascii_digit() {
403 return None;
404 }
405 let val = c.to_digit(10)? as u64;
406 let scaled_val = val.checked_mul(base_10)?.checked_mul(encoded_size.scale)?;
407
408 integral = integral.checked_add(scaled_val)?;
409 base_10 = base_10.checked_mul(10)?;
410 }
411
412 let mut fractional: u64 = 0;
413 if let Some(frac_str) = encoded_size.fractional {
414 let mut frac_base_10: u64 = 1;
415 let mut carry: u64 = 0;
416
417 for c in frac_str.chars() {
429 if !c.is_ascii_digit() {
430 return None;
431 }
432 let val = c.to_digit(10)? as u64;
433 frac_base_10 = frac_base_10.checked_mul(10)?;
434 let scaled_value = val.checked_mul(encoded_size.scale)?;
435
436 let contrib = scaled_value / frac_base_10;
438 fractional = fractional.checked_add(contrib)?;
439
440 carry = carry.checked_mul(10)?.checked_add(scaled_value % frac_base_10)?;
442
443 let consumed_carry = carry / frac_base_10;
445 fractional = fractional.checked_add(consumed_carry)?;
446
447 carry %= frac_base_10;
449 }
450
451 if carry != 0 {
455 return None;
456 }
457 }
458
459 integral.checked_add(fractional)
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 const KILO: usize = 1024;
467 const MEGA: usize = KILO * 1024;
468 const GIGA: usize = MEGA * 1024;
469 const TERA: usize = GIGA * 1024;
470 const PETA: usize = TERA * 1024;
471 const EXA: usize = PETA * 1024;
472
473 struct FormatSizeTestCase {
474 input: usize,
475 unit: u8,
476 expected_output: &'static str,
477 }
478
479 const FORMAT_SIZE_TEST_CASES: &[FormatSizeTestCase] = &[
480 FormatSizeTestCase { input: 0, unit: 0, expected_output: "0B" },
483 FormatSizeTestCase { input: 1, unit: 0, expected_output: "1B" },
484 FormatSizeTestCase { input: KILO - 1, unit: 0, expected_output: "1023B" },
489 FormatSizeTestCase { input: KILO, unit: 0, expected_output: "1K" },
490 FormatSizeTestCase { input: KILO + 1, unit: 0, expected_output: "1025B" },
491 FormatSizeTestCase { input: KILO * 9, unit: 0, expected_output: "9K" },
492 FormatSizeTestCase { input: KILO * 9 + 1, unit: 0, expected_output: "9217B" },
493 FormatSizeTestCase { input: KILO * 10, unit: 0, expected_output: "10K" },
494 FormatSizeTestCase { input: MEGA - KILO, unit: 0, expected_output: "1023K" },
496 FormatSizeTestCase { input: MEGA, unit: 0, expected_output: "1M" },
497 FormatSizeTestCase { input: MEGA + KILO, unit: 0, expected_output: "1025K" },
498 FormatSizeTestCase { input: MEGA * 9, unit: 0, expected_output: "9M" },
499 FormatSizeTestCase { input: MEGA * 9 + KILO, unit: 0, expected_output: "9217K" },
500 FormatSizeTestCase { input: MEGA * 10, unit: 0, expected_output: "10M" },
501 FormatSizeTestCase { input: MEGA, unit: 0, expected_output: "1M" },
503 FormatSizeTestCase { input: GIGA, unit: 0, expected_output: "1G" },
504 FormatSizeTestCase { input: TERA, unit: 0, expected_output: "1T" },
505 FormatSizeTestCase { input: PETA, unit: 0, expected_output: "1P" },
506 FormatSizeTestCase { input: EXA, unit: 0, expected_output: "1E" },
507 FormatSizeTestCase { input: MEGA - 1, unit: 0, expected_output: "1024.0K" },
510 FormatSizeTestCase { input: MEGA + MEGA / 3, unit: 0, expected_output: "1365.3K" },
511 FormatSizeTestCase { input: GIGA - 1, unit: 0, expected_output: "1024.0M" },
512 FormatSizeTestCase { input: TERA - 1, unit: 0, expected_output: "1024.0G" },
513 FormatSizeTestCase { input: PETA - 1, unit: 0, expected_output: "1024.0T" },
514 FormatSizeTestCase { input: EXA - 1, unit: 0, expected_output: "1024.0P" },
515 FormatSizeTestCase { input: usize::MAX, unit: 0, expected_output: "16.0E" },
516 FormatSizeTestCase { input: 9999, unit: 0, expected_output: "9999B" },
519 FormatSizeTestCase { input: 10000, unit: 0, expected_output: "9.8K" },
520 FormatSizeTestCase { input: KILO * 9999, unit: 0, expected_output: "9999K" },
521 FormatSizeTestCase { input: KILO * 9999 + 1, unit: 0, expected_output: "9999.0K" },
522 FormatSizeTestCase { input: KILO * 10000, unit: 0, expected_output: "9.8M" },
523 FormatSizeTestCase { input: 10700, unit: 0, expected_output: "10.4K" },
525 FormatSizeTestCase { input: 10701, unit: 0, expected_output: "10.5K" },
526 FormatSizeTestCase { input: 69887590, unit: 0, expected_output: "66.6M" },
527 FormatSizeTestCase { input: 69887591, unit: 0, expected_output: "66.7M" },
528 FormatSizeTestCase { input: 18389097998479209267, unit: 0, expected_output: "15.9E" },
529 FormatSizeTestCase { input: 18389097998479209268, unit: 0, expected_output: "16.0E" },
530 FormatSizeTestCase {
532 input: usize::MAX,
533 unit: b'B',
534 expected_output: "18446744073709551615B",
535 },
536 FormatSizeTestCase {
537 input: usize::MAX,
538 unit: b'K',
539 expected_output: "18014398509481984.0K",
540 },
541 FormatSizeTestCase { input: usize::MAX, unit: b'M', expected_output: "17592186044416.0M" },
542 FormatSizeTestCase { input: usize::MAX, unit: b'G', expected_output: "17179869184.0G" },
543 FormatSizeTestCase { input: usize::MAX, unit: b'T', expected_output: "16777216.0T" },
544 FormatSizeTestCase { input: usize::MAX, unit: b'P', expected_output: "16384.0P" },
545 FormatSizeTestCase { input: usize::MAX, unit: b'E', expected_output: "16.0E" },
546 FormatSizeTestCase { input: GIGA, unit: b'K', expected_output: "1048576K" },
548 FormatSizeTestCase { input: MEGA / 10, unit: b'M', expected_output: "0.1M" },
550 FormatSizeTestCase { input: GIGA, unit: b'q', expected_output: "?1G" },
552 FormatSizeTestCase { input: KILO, unit: b'q', expected_output: "?1K" },
553 FormatSizeTestCase { input: GIGA + 1, unit: b'#', expected_output: "?1.0G" },
554 FormatSizeTestCase { input: KILO + 1, unit: b'#', expected_output: "?1025B" },
555 ];
556
557 #[test]
558 fn test_format_size_fixed() {
559 let mut buf = [0u8; MAX_FORMAT_SIZE_LEN];
560 for (i, tc) in FORMAT_SIZE_TEST_CASES.iter().enumerate() {
561 buf.fill(0);
562 let res = format_size_fixed_rs(&mut buf, tc.input, tc.unit);
563 assert_eq!(
564 res, tc.expected_output,
565 "case {}, input={}, unit={}",
566 i, tc.input, tc.unit as char
567 );
568 }
569 }
570
571 #[test]
572 fn test_format_size_short_buf_truncates() {
573 let input = 1023 * KILO + 1;
574 let expected_output = "1023.0K";
575
576 let mut buf = [0x55u8; MAX_FORMAT_SIZE_LEN * 2];
577 for str_size in 0..=expected_output.len() {
578 buf.fill(0x55);
579 let res = format_size_rs(&mut buf[..str_size], input);
580 assert_eq!(res, &expected_output[..str_size]);
581 assert_eq!(buf[str_size], 0x55);
582 }
583 }
584
585 #[test]
586 fn test_format_size_bad_unit_short_buf_truncates() {
587 let mut buf = [0x55u8; MAX_FORMAT_SIZE_LEN];
588
589 buf.fill(0x55);
591 let res = format_size_fixed_rs(&mut buf[..0], GIGA, b'q');
592 assert_eq!(res, "");
593 assert_eq!(buf[0], 0x55);
594
595 buf.fill(0x55);
597 let res = format_size_fixed_rs(&mut buf[..1], GIGA, b'q');
598 assert_eq!(res, "?");
599 assert_eq!(buf[1], 0x55);
600
601 buf.fill(0x55);
603 let res = format_size_fixed_rs(&mut buf[..2], GIGA, b'q');
604 assert_eq!(res, "?1");
605 assert_eq!(buf[2], 0x55);
606
607 buf.fill(0x55);
609 let res = format_size_fixed_rs(&mut buf[..3], GIGA, b'q');
610 assert_eq!(res, "?1G");
611 assert_eq!(buf[3], 0x55);
612 }
613
614 #[test]
615 fn test_cpp_to_string() {
616 assert_eq!(SizeUnit::Auto.to_str(), "");
617 assert_eq!(SizeUnit::Bytes.to_str(), "B");
618 assert_eq!(SizeUnit::KiB.to_str(), "K");
619 assert_eq!(SizeUnit::MiB.to_str(), "M");
620 assert_eq!(SizeUnit::GiB.to_str(), "G");
621 assert_eq!(SizeUnit::TiB.to_str(), "T");
622 assert_eq!(SizeUnit::PiB.to_str(), "P");
623 assert_eq!(SizeUnit::EiB.to_str(), "E");
624 }
625
626 struct ParseTestCase {
627 expected_bytes: u64,
628 input: &'static str,
629 }
630
631 const PARSE_TEST_CASES: &[ParseTestCase] = &[
632 ParseTestCase { expected_bytes: 1234, input: "1234" },
634 ParseTestCase { expected_bytes: 1234, input: "1234b" },
635 ParseTestCase { expected_bytes: 1234, input: "1234B" },
636 ParseTestCase { expected_bytes: 1234 * 1024, input: "1234k" },
637 ParseTestCase { expected_bytes: 1234 * 1024, input: "1234K" },
638 ParseTestCase { expected_bytes: 1234 * 1024 * 1024, input: "1234m" },
639 ParseTestCase { expected_bytes: 1234 * 1024 * 1024, input: "1234M" },
640 ParseTestCase { expected_bytes: 1234 * 1024 * 1024 * 1024, input: "1234g" },
641 ParseTestCase { expected_bytes: 1234 * 1024 * 1024 * 1024, input: "1234G" },
642 ParseTestCase { expected_bytes: 1234 * 1024 * 1024 * 1024 * 1024, input: "1234t" },
643 ParseTestCase { expected_bytes: 1234 * 1024 * 1024 * 1024 * 1024, input: "1234T" },
644 ParseTestCase { expected_bytes: 5 * 1024 * 1024 * 1024 * 1024 * 1024, input: "5p" },
645 ParseTestCase { expected_bytes: 5 * 1024 * 1024 * 1024 * 1024 * 1024, input: "5P" },
646 ParseTestCase { expected_bytes: 2 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, input: "2e" },
647 ParseTestCase { expected_bytes: 2 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, input: "2E" },
648 ParseTestCase { expected_bytes: 10700, input: "10.4492187500k" },
650 ParseTestCase { expected_bytes: 10700, input: "10.4492187500K" },
651 ParseTestCase { expected_bytes: 10700 * 1024, input: "10.4492187500m" },
652 ParseTestCase { expected_bytes: 10700 * 1024, input: "10.4492187500M" },
653 ParseTestCase { expected_bytes: 10700 * 1024 * 1024, input: "10.4492187500g" },
654 ParseTestCase { expected_bytes: 10700 * 1024 * 1024, input: "10.4492187500G" },
655 ParseTestCase { expected_bytes: 10700 * 1024 * 1024 * 1024, input: "10.4492187500t" },
656 ParseTestCase { expected_bytes: 10700 * 1024 * 1024 * 1024, input: "10.4492187500T" },
657 ParseTestCase {
658 expected_bytes: 10700 * 1024 * 1024 * 1024 * 1024,
659 input: "10.4492187500p",
660 },
661 ParseTestCase {
662 expected_bytes: 10700 * 1024 * 1024 * 1024 * 1024,
663 input: "10.4492187500P",
664 },
665 ParseTestCase { expected_bytes: 1441151880758558720, input: "1.25e" },
666 ParseTestCase { expected_bytes: 1441151880758558720, input: "1.25E" },
667 ];
668
669 #[test]
670 fn test_parse_size_bytes() {
671 for tc in PARSE_TEST_CASES {
672 let res = parse_size_bytes(tc.input);
673 assert_eq!(res, Some(tc.expected_bytes), "input: {}", tc.input);
674 }
675 }
676
677 const INVALID_INPUTS: &[&str] = &["", "1..1", "1w", "b", "AM", "1.AM", "A.1M"];
678
679 #[test]
680 fn test_parse_size_bytes_invalid() {
681 for input in INVALID_INPUTS {
682 let res = parse_size_bytes(input);
683 assert_eq!(res, None, "input: {}", input);
684 }
685 }
686
687 #[test]
688 fn test_formatted_bytes() {
689 let empty = FormattedBytes::empty();
690 assert_eq!(empty.as_str(), "");
691 assert_eq!(empty.magnitude(), "");
692 assert_eq!(empty.unit(), SizeUnit::Auto);
693
694 let fb = FormattedBytes::new(1024 * 1024);
695 assert_eq!(fb.as_str(), "1M");
696 assert_eq!(fb.magnitude(), "1");
697 assert_eq!(fb.unit(), SizeUnit::MiB);
698
699 let fb_unit = FormattedBytes::with_unit(1024 * 1024, SizeUnit::KiB);
700 assert_eq!(fb_unit.as_str(), "1024K");
701 assert_eq!(fb_unit.magnitude(), "1024");
702 assert_eq!(fb_unit.unit(), SizeUnit::KiB);
703 }
704}