1use crate::format::bytes::read_le_uint as read_uint;
25use crate::format::{FormatContext, FormatError, FormatResult};
26
27const VERSION: u8 = 1;
28
29const FLAG_NAME_LEN_MASK: u8 = 0x03;
30const FLAG_CREATION_ORDER: u8 = 0x04;
31const FLAG_LINK_TYPE: u8 = 0x08;
32const FLAG_CHARSET: u8 = 0x10;
33
34const LINK_TYPE_HARD: u8 = 0;
35const LINK_TYPE_SOFT: u8 = 1;
36const LINK_TYPE_EXTERNAL: u8 = 64;
39
40const EXT_VERSION: u8 = 0;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
54pub enum CharacterSet {
55 #[default]
58 Ascii,
59 Utf8,
61}
62
63impl CharacterSet {
64 pub fn for_name(name: &str) -> Self {
69 if name.is_ascii() {
70 Self::Ascii
71 } else {
72 Self::Utf8
73 }
74 }
75
76 fn code(self) -> u8 {
77 match self {
78 Self::Ascii => 0,
79 Self::Utf8 => 1,
80 }
81 }
82
83 fn from_code(code: u8) -> FormatResult<Self> {
84 match code {
85 0 => Ok(Self::Ascii),
86 1 => Ok(Self::Utf8),
87 other => Err(FormatError::InvalidData(format!(
88 "link name character set {other} is neither ASCII nor UTF-8"
89 ))),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq)]
96pub enum LinkTarget {
97 Hard { address: u64 },
99 Soft { target: String },
101 External { file: String, path: String },
103 UserDefined { link_type: u8, udata: Vec<u8> },
107}
108
109#[derive(Debug, Clone, PartialEq)]
111pub struct LinkMessage {
112 pub name: String,
113 pub target: LinkTarget,
114 pub creation_order: Option<i64>,
118 pub cset: CharacterSet,
120}
121
122impl LinkMessage {
123 pub fn hard(name: &str, address: u64) -> Self {
125 Self {
126 name: name.to_string(),
127 target: LinkTarget::Hard { address },
128 creation_order: None,
129 cset: CharacterSet::for_name(name),
130 }
131 }
132
133 pub fn soft(name: &str, target: &str) -> Self {
135 Self {
136 name: name.to_string(),
137 target: LinkTarget::Soft {
138 target: target.to_string(),
139 },
140 creation_order: None,
141 cset: CharacterSet::for_name(name),
142 }
143 }
144
145 pub fn external(name: &str, file: &str, path: &str) -> Self {
147 Self {
148 name: name.to_string(),
149 target: LinkTarget::External {
150 file: file.to_string(),
151 path: path.to_string(),
152 },
153 creation_order: None,
154 cset: CharacterSet::for_name(name),
155 }
156 }
157
158 pub fn with_creation_order(mut self, corder: i64) -> Self {
160 self.creation_order = Some(corder);
161 self
162 }
163
164 pub fn with_cset(mut self, cset: CharacterSet) -> Self {
173 self.cset = cset;
174 self
175 }
176
177 pub fn fits_symbol_table(&self) -> bool {
191 self.cset == CharacterSet::Ascii
192 && matches!(
193 self.target,
194 LinkTarget::Hard { .. } | LinkTarget::Soft { .. }
195 )
196 }
197
198 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
201 let name_bytes = self.name.as_bytes();
202 let name_len = name_bytes.len();
203 let name_len_size = min_bytes_for_value(name_len as u64);
204 let name_len_code = match name_len_size {
205 1 => 0u8,
206 2 => 1,
207 4 => 2,
208 _ => 3, };
210
211 let link_type = match &self.target {
212 LinkTarget::Hard { .. } => LINK_TYPE_HARD,
213 LinkTarget::Soft { .. } => LINK_TYPE_SOFT,
214 LinkTarget::External { .. } => LINK_TYPE_EXTERNAL,
215 LinkTarget::UserDefined { link_type, .. } => *link_type,
216 };
217
218 let mut flags: u8 = name_len_code & FLAG_NAME_LEN_MASK;
223 if link_type != LINK_TYPE_HARD {
224 flags |= FLAG_LINK_TYPE;
225 }
226 if self.cset != CharacterSet::Ascii {
227 flags |= FLAG_CHARSET;
228 }
229 if self.creation_order.is_some() {
230 flags |= FLAG_CREATION_ORDER;
231 }
232
233 let mut buf = Vec::with_capacity(32);
234 buf.push(VERSION);
235 buf.push(flags);
236
237 if flags & FLAG_LINK_TYPE != 0 {
239 buf.push(link_type);
240 }
241
242 if let Some(corder) = self.creation_order {
244 buf.extend_from_slice(&corder.to_le_bytes());
245 }
246
247 if flags & FLAG_CHARSET != 0 {
248 buf.push(self.cset.code());
249 }
250
251 match name_len_size {
253 1 => buf.push(name_len as u8),
254 2 => buf.extend_from_slice(&(name_len as u16).to_le_bytes()),
255 4 => buf.extend_from_slice(&(name_len as u32).to_le_bytes()),
256 _ => buf.extend_from_slice(&(name_len as u64).to_le_bytes()),
257 }
258
259 buf.extend_from_slice(name_bytes);
261
262 match &self.target {
264 LinkTarget::Hard { address } => {
265 let sa = ctx.sizeof_addr as usize;
266 buf.extend_from_slice(&address.to_le_bytes()[..sa]);
267 }
268 LinkTarget::Soft { target } => {
269 let tbytes = target.as_bytes();
270 buf.extend_from_slice(&(tbytes.len() as u16).to_le_bytes());
271 buf.extend_from_slice(tbytes);
272 }
273 LinkTarget::External { file, path } => {
274 let udata = encode_external_udata(file, path);
275 buf.extend_from_slice(&(udata.len() as u16).to_le_bytes());
276 buf.extend_from_slice(&udata);
277 }
278 LinkTarget::UserDefined { udata, .. } => {
279 buf.extend_from_slice(&(udata.len() as u16).to_le_bytes());
280 buf.extend_from_slice(udata);
281 }
282 }
283
284 buf
285 }
286
287 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
290 if buf.len() < 2 {
291 return Err(FormatError::BufferTooShort {
292 needed: 2,
293 available: buf.len(),
294 });
295 }
296
297 let version = buf[0];
298 if version != VERSION {
299 return Err(FormatError::InvalidVersion(version));
300 }
301
302 let flags = buf[1];
303 let name_len_code = flags & FLAG_NAME_LEN_MASK;
304 let has_creation_order = (flags & FLAG_CREATION_ORDER) != 0;
305 let has_link_type = (flags & FLAG_LINK_TYPE) != 0;
306 let has_charset = (flags & FLAG_CHARSET) != 0;
307
308 let mut pos = 2;
309
310 let link_type = if has_link_type {
312 check_len(buf, pos, 1)?;
313 let lt = buf[pos];
314 pos += 1;
315 lt
316 } else {
317 LINK_TYPE_HARD };
319
320 let creation_order = if has_creation_order {
323 check_len(buf, pos, 8)?;
324 let v = i64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap());
325 pos += 8;
326 Some(v)
327 } else {
328 None
329 };
330
331 let cset = if has_charset {
333 check_len(buf, pos, 1)?;
334 let c = CharacterSet::from_code(buf[pos])?;
335 pos += 1;
336 c
337 } else {
338 CharacterSet::Ascii
339 };
340
341 let name_len_size: usize = match name_len_code {
343 0 => 1,
344 1 => 2,
345 2 => 4,
346 _ => 8,
347 };
348 check_len(buf, pos, name_len_size)?;
349 let name_len = read_uint(&buf[pos..], name_len_size) as usize;
350 pos += name_len_size;
351
352 check_len(buf, pos, name_len)?;
354 let name = std::str::from_utf8(&buf[pos..pos + name_len])
355 .map_err(|e| FormatError::InvalidData(format!("invalid UTF-8 link name: {}", e)))?
356 .to_string();
357 pos += name_len;
358
359 let target = match link_type {
361 LINK_TYPE_HARD => {
362 let sa = ctx.sizeof_addr as usize;
363 check_len(buf, pos, sa)?;
364 let address = read_uint(&buf[pos..], sa);
365 pos += sa;
366 LinkTarget::Hard { address }
367 }
368 LINK_TYPE_SOFT => {
369 check_len(buf, pos, 2)?;
370 let tlen = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
371 pos += 2;
372 check_len(buf, pos, tlen)?;
373 let target = std::str::from_utf8(&buf[pos..pos + tlen])
374 .map_err(|e| {
375 FormatError::InvalidData(format!("invalid UTF-8 soft link target: {}", e))
376 })?
377 .to_string();
378 pos += tlen;
379 LinkTarget::Soft { target }
380 }
381 ud if ud >= LINK_TYPE_EXTERNAL => {
386 check_len(buf, pos, 2)?;
387 let ulen = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
388 pos += 2;
389 check_len(buf, pos, ulen)?;
390 let udata = &buf[pos..pos + ulen];
391 pos += ulen;
392 if ud == LINK_TYPE_EXTERNAL {
393 let (file, path) = decode_external_udata(udata)?;
394 LinkTarget::External { file, path }
395 } else {
396 LinkTarget::UserDefined {
397 link_type: ud,
398 udata: udata.to_vec(),
399 }
400 }
401 }
402 other => {
403 return Err(FormatError::InvalidData(format!(
404 "unknown link type {}",
405 other
406 )));
407 }
408 };
409
410 Ok((
411 Self {
412 name,
413 target,
414 creation_order,
415 cset,
416 },
417 pos,
418 ))
419 }
420}
421
422pub(crate) fn normalize_object_path(path: &str) -> String {
431 let mut out = String::with_capacity(path.len());
432 let mut last_slash = false;
433 for c in path.chars() {
434 if c == '/' && last_slash {
435 continue;
436 }
437 last_slash = c == '/';
438 out.push(c);
439 }
440 if out.len() > 1 && out.ends_with('/') {
442 out.pop();
443 }
444 out
445}
446
447fn encode_external_udata(file: &str, path: &str) -> Vec<u8> {
451 let mut udata = Vec::with_capacity(1 + file.len() + path.len() + 2);
452 udata.push(EXT_VERSION << 4);
453 udata.extend_from_slice(file.as_bytes());
454 udata.push(0);
455 udata.extend_from_slice(path.as_bytes());
456 udata.push(0);
457 udata
458}
459
460fn decode_external_udata(udata: &[u8]) -> FormatResult<(String, String)> {
464 if udata.len() < 3 {
465 return Err(FormatError::InvalidData(format!(
466 "external link value is {} bytes, below the 3-byte minimum",
467 udata.len()
468 )));
469 }
470 let version = udata[0] >> 4;
471 let flags = udata[0] & 0x0f;
472 if version != EXT_VERSION {
473 return Err(FormatError::InvalidVersion(version));
474 }
475 if flags != 0 {
476 return Err(FormatError::InvalidData(format!(
477 "external link flags {flags:#x} are not recognized"
478 )));
479 }
480 let body = &udata[1..];
481 let split = body.iter().position(|&b| b == 0).ok_or_else(|| {
482 FormatError::InvalidData("external link file name is not NUL-terminated".into())
483 })?;
484 let file = str_from_utf8(&body[..split], "external link file name")?;
485 let rest = &body[split + 1..];
486 let end = rest.iter().position(|&b| b == 0).unwrap_or(rest.len());
490 let path = str_from_utf8(&rest[..end], "external link object path")?;
491 Ok((file, path))
492}
493
494fn str_from_utf8(bytes: &[u8], what: &str) -> FormatResult<String> {
495 std::str::from_utf8(bytes)
496 .map(|s| s.to_string())
497 .map_err(|e| FormatError::InvalidData(format!("invalid UTF-8 {what}: {e}")))
498}
499
500fn check_len(buf: &[u8], pos: usize, need: usize) -> FormatResult<()> {
501 match pos.checked_add(need) {
505 Some(end) if end <= buf.len() => Ok(()),
506 _ => Err(FormatError::BufferTooShort {
507 needed: pos.saturating_add(need),
508 available: buf.len(),
509 }),
510 }
511}
512
513fn min_bytes_for_value(v: u64) -> usize {
515 if v <= u8::MAX as u64 {
516 1
517 } else if v <= u16::MAX as u64 {
518 2
519 } else if v <= u32::MAX as u64 {
520 4
521 } else {
522 8
523 }
524}
525
526#[cfg(test)]
529mod tests {
530 use super::*;
531
532 fn ctx8() -> FormatContext {
533 FormatContext {
534 sizeof_addr: 8,
535 sizeof_size: 8,
536 }
537 }
538
539 fn ctx4() -> FormatContext {
540 FormatContext {
541 sizeof_addr: 4,
542 sizeof_size: 4,
543 }
544 }
545
546 #[test]
547 fn roundtrip_hard_link() {
548 let msg = LinkMessage::hard("dataset1", 0x1000);
549 let encoded = msg.encode(&ctx8());
550 let (decoded, consumed) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
551 assert_eq!(consumed, encoded.len());
552 assert_eq!(decoded, msg);
553 }
554
555 #[test]
556 fn roundtrip_hard_link_ctx4() {
557 let msg = LinkMessage::hard("grp", 0x2000);
558 let encoded = msg.encode(&ctx4());
559 let (decoded, consumed) = LinkMessage::decode(&encoded, &ctx4()).unwrap();
560 assert_eq!(consumed, encoded.len());
561 assert_eq!(decoded, msg);
562 }
563
564 #[test]
565 fn roundtrip_soft_link() {
566 let msg = LinkMessage::soft("alias", "/group/dataset");
567 let encoded = msg.encode(&ctx8());
568 let (decoded, consumed) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
569 assert_eq!(consumed, encoded.len());
570 assert_eq!(decoded, msg);
571 }
572
573 #[test]
574 fn roundtrip_empty_name() {
575 let msg = LinkMessage::hard("", 0x100);
577 let encoded = msg.encode(&ctx8());
578 let (decoded, _) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
579 assert_eq!(decoded, msg);
580 }
581
582 #[test]
583 fn roundtrip_long_name() {
584 let long_name: String = "a".repeat(300);
586 let msg = LinkMessage::hard(&long_name, 0xABCD);
587 let encoded = msg.encode(&ctx8());
588 let (decoded, consumed) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
589 assert_eq!(consumed, encoded.len());
590 assert_eq!(decoded, msg);
591 }
592
593 #[test]
594 fn roundtrip_unicode_name() {
595 let msg = LinkMessage::hard("日本語データ", 0x4000);
596 let encoded = msg.encode(&ctx8());
597 let (decoded, _) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
598 assert_eq!(decoded, msg);
599 }
600
601 #[test]
602 fn roundtrip_external_link() {
603 let msg = LinkMessage::external("ext", "sibling.h5", "/payload");
604 let encoded = msg.encode(&ctx8());
605 let (decoded, consumed) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
606 assert_eq!(consumed, encoded.len());
607 assert_eq!(decoded, msg);
608 }
609
610 #[test]
615 fn decode_h5py_external_link_bytes() {
616 let mut buf = vec![1u8, 0x08, 64, 3];
617 buf.extend_from_slice(b"ext");
618 let udata = {
619 let mut u = vec![0u8];
620 u.extend_from_slice(b"link_external_ext.h5\0");
621 u.extend_from_slice(b"/payload\0");
622 u
623 };
624 assert_eq!(udata.len(), 31);
625 buf.extend_from_slice(&(udata.len() as u16).to_le_bytes());
626 buf.extend_from_slice(&udata);
627
628 let (decoded, consumed) = LinkMessage::decode(&buf, &ctx8()).unwrap();
629 assert_eq!(consumed, buf.len());
630 assert_eq!(decoded.name, "ext");
631 assert_eq!(
632 decoded.target,
633 LinkTarget::External {
634 file: "link_external_ext.h5".into(),
635 path: "/payload".into(),
636 }
637 );
638 }
639
640 #[test]
646 fn encoded_external_link_is_the_message_h5lcreate_external_builds() {
647 let encoded =
648 LinkMessage::external("ext", "link_external_ext.h5", "/payload").encode(&ctx8());
649 let mut want = vec![1u8, 0x08, 64, 3];
650 want.extend_from_slice(b"ext");
651 let udata = {
652 let mut u = vec![0u8];
653 u.extend_from_slice(b"link_external_ext.h5\0");
654 u.extend_from_slice(b"/payload\0");
655 u
656 };
657 want.extend_from_slice(&(udata.len() as u16).to_le_bytes());
658 want.extend_from_slice(&udata);
659 assert_eq!(encoded, want);
660 }
661
662 #[test]
665 fn roundtrip_unregistered_user_defined_link() {
666 let msg = LinkMessage {
667 name: "ud".into(),
668 target: LinkTarget::UserDefined {
669 link_type: 200,
670 udata: vec![9, 8, 7],
671 },
672 creation_order: None,
673 cset: CharacterSet::Ascii,
674 };
675 let encoded = msg.encode(&ctx8());
676 let (decoded, consumed) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
677 assert_eq!(consumed, encoded.len());
678 assert_eq!(decoded, msg);
679 }
680
681 #[test]
682 fn decode_unknown_link_type_below_user_defined_range() {
683 let buf = [1u8, 0x08, 7, 1, b'x'];
684 match LinkMessage::decode(&buf, &ctx8()).unwrap_err() {
685 FormatError::InvalidData(ref s) => assert!(s.contains("link type 7"), "{s}"),
686 other => panic!("unexpected error: {other:?}"),
687 }
688 }
689
690 #[test]
691 fn decode_external_value_too_short() {
692 let mut buf = vec![1u8, 0x08, 64, 1, b'e'];
693 buf.extend_from_slice(&2u16.to_le_bytes());
694 buf.extend_from_slice(&[0u8, 0]);
695 match LinkMessage::decode(&buf, &ctx8()).unwrap_err() {
696 FormatError::InvalidData(ref s) => assert!(s.contains("3-byte minimum"), "{s}"),
697 other => panic!("unexpected error: {other:?}"),
698 }
699 }
700
701 #[test]
702 fn decode_external_value_bad_version() {
703 let mut buf = vec![1u8, 0x08, 64, 1, b'e'];
704 let udata = [0x10u8, b'f', 0, b'/', 0];
705 buf.extend_from_slice(&(udata.len() as u16).to_le_bytes());
706 buf.extend_from_slice(&udata);
707 match LinkMessage::decode(&buf, &ctx8()).unwrap_err() {
708 FormatError::InvalidVersion(1) => {}
709 other => panic!("unexpected error: {other:?}"),
710 }
711 }
712
713 #[test]
714 fn decode_bad_version() {
715 let buf = [2u8, 0]; let err = LinkMessage::decode(&buf, &ctx8()).unwrap_err();
717 match err {
718 FormatError::InvalidVersion(2) => {}
719 other => panic!("unexpected error: {:?}", other),
720 }
721 }
722
723 #[test]
724 fn decode_buffer_too_short() {
725 let buf = [1u8];
726 let err = LinkMessage::decode(&buf, &ctx8()).unwrap_err();
727 match err {
728 FormatError::BufferTooShort { .. } => {}
729 other => panic!("unexpected error: {:?}", other),
730 }
731 }
732
733 #[test]
734 fn version_byte() {
735 let encoded = LinkMessage::hard("x", 0).encode(&ctx8());
736 assert_eq!(encoded[0], 1);
737 }
738
739 #[test]
740 fn roundtrip_with_creation_order() {
741 let msg = LinkMessage::hard("d00", 0x1000).with_creation_order(7);
742 let encoded = msg.encode(&ctx8());
743 assert_eq!(encoded[1] & FLAG_CREATION_ORDER, FLAG_CREATION_ORDER);
745 assert_eq!(
746 encoded.len(),
747 LinkMessage::hard("d00", 0x1000).encode(&ctx8()).len() + 8
748 );
749 let (decoded, consumed) = LinkMessage::decode(&encoded, &ctx8()).unwrap();
750 assert_eq!(consumed, encoded.len());
751 assert_eq!(decoded, msg);
752 assert_eq!(decoded.creation_order, Some(7));
753 }
754
755 #[test]
759 fn creation_order_precedes_the_charset_byte() {
760 let msg = LinkMessage::soft("별칭", "/orig").with_creation_order(-3);
761 let encoded = msg.encode(&ctx8());
762 assert_eq!(
763 encoded[1],
764 FLAG_CREATION_ORDER | FLAG_LINK_TYPE | FLAG_CHARSET
765 );
766 assert_eq!(&encoded[3..11], &(-3i64).to_le_bytes());
767 assert_eq!(encoded[11], 1, "charset follows the creation order");
768 assert_eq!(LinkMessage::decode(&encoded, &ctx8()).unwrap().0, msg);
769 }
770
771 #[test]
777 fn ascii_hard_link_is_encoded_the_way_libhdf5_wrote_it() {
778 let encoded = LinkMessage::hard("plain", 800).encode(&ctx8());
779 let mut want = vec![1u8, 0x00, 5];
780 want.extend_from_slice(b"plain");
781 want.extend_from_slice(&800u64.to_le_bytes());
782 assert_eq!(encoded, want);
783 }
784
785 #[test]
788 fn non_ascii_hard_link_carries_the_utf8_charset_byte() {
789 let encoded = LinkMessage::hard("비ascii", 1832).encode(&ctx8());
790 let name = "비ascii".as_bytes();
791 assert_eq!(name.len(), 8);
792 let mut want = vec![1u8, 0x10, 1, 8];
793 want.extend_from_slice(name);
794 want.extend_from_slice(&1832u64.to_le_bytes());
795 assert_eq!(encoded, want);
796 }
797
798 #[test]
801 fn a_utf8_name_does_not_fit_a_symbol_table() {
802 assert!(LinkMessage::hard("plain", 1).fits_symbol_table());
803 assert!(LinkMessage::soft("plain", "/x").fits_symbol_table());
804 assert!(!LinkMessage::hard("비ascii", 1).fits_symbol_table());
805 assert!(!LinkMessage::soft("비ascii", "/x").fits_symbol_table());
806 assert!(!LinkMessage::external("ext", "f.h5", "/p").fits_symbol_table());
807 }
808
809 #[test]
813 fn a_symbol_table_name_keeps_the_default_charset() {
814 let msg = LinkMessage::hard("한글", 0x40).with_cset(CharacterSet::Ascii);
815 assert!(msg.fits_symbol_table());
816 let encoded = msg.encode(&ctx8());
817 assert_eq!(encoded[1] & FLAG_CHARSET, 0);
818 assert_eq!(LinkMessage::decode(&encoded, &ctx8()).unwrap().0, msg);
819 }
820
821 #[test]
824 fn decode_rejects_an_unknown_charset() {
825 let buf = [1u8, 0x10, 7, 1, b'x', 0, 0, 0, 0, 0, 0, 0, 0];
826 match LinkMessage::decode(&buf, &ctx8()).unwrap_err() {
827 FormatError::InvalidData(ref s) => assert!(s.contains("character set 7"), "{s}"),
828 other => panic!("unexpected error: {other:?}"),
829 }
830 }
831
832 #[test]
835 fn object_paths_normalize_like_h5g_normalize() {
836 assert_eq!(normalize_object_path("/payload"), "/payload");
837 assert_eq!(normalize_object_path("//a///b"), "/a/b");
838 assert_eq!(normalize_object_path("/a/b/"), "/a/b");
839 assert_eq!(normalize_object_path("/a/b//"), "/a/b");
840 assert_eq!(normalize_object_path("/"), "/");
841 assert_eq!(normalize_object_path("//"), "/");
842 assert_eq!(normalize_object_path("a/b"), "a/b");
843 assert_eq!(normalize_object_path(""), "");
844 }
845
846 #[test]
847 fn min_bytes_for_value_checks() {
848 assert_eq!(min_bytes_for_value(0), 1);
849 assert_eq!(min_bytes_for_value(255), 1);
850 assert_eq!(min_bytes_for_value(256), 2);
851 assert_eq!(min_bytes_for_value(65535), 2);
852 assert_eq!(min_bytes_for_value(65536), 4);
853 assert_eq!(min_bytes_for_value(u32::MAX as u64), 4);
854 assert_eq!(min_bytes_for_value(u32::MAX as u64 + 1), 8);
855 }
856}