Skip to main content

typed_store/
util.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2026 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::ops::{Bound, RangeBounds};
6
7use bincode::Options;
8use serde::Serialize;
9
10#[inline]
11pub fn be_fix_int_ser<S>(t: &S) -> Vec<u8>
12where
13    S: ?Sized + serde::Serialize,
14{
15    bincode::DefaultOptions::new()
16        .with_big_endian()
17        .with_fixint_encoding()
18        .serialize(t)
19        .expect("failed to serialize via be_fix_int_ser method")
20}
21
22pub(crate) fn iterator_bounds<K>(
23    lower_bound: Option<K>,
24    upper_bound: Option<K>,
25) -> (Option<Vec<u8>>, Option<Vec<u8>>)
26where
27    K: Serialize,
28{
29    (
30        lower_bound.map(|b| be_fix_int_ser(&b)),
31        upper_bound.map(|b| be_fix_int_ser(&b)),
32    )
33}
34
35pub(crate) fn iterator_bounds_with_range<K>(
36    range: impl RangeBounds<K>,
37) -> (Option<Vec<u8>>, Option<Vec<u8>>)
38where
39    K: Serialize,
40{
41    let iterator_lower_bound = match range.start_bound() {
42        Bound::Included(lower_bound) => {
43            // Rocksdb lower bound is inclusive by default so nothing to do
44            Some(be_fix_int_ser(&lower_bound))
45        }
46        Bound::Excluded(lower_bound) => {
47            let mut key_buf = be_fix_int_ser(&lower_bound);
48
49            if is_max(&key_buf) {
50                // No representable key strictly greater than the maximum at this byte
51                // length. Append a zero byte so the lower bound is lexicographically
52                // greater than any same-length key, ensuring the iterator yields
53                // nothing — matching the user's intent of excluding the max key.
54                key_buf.push(0);
55            } else {
56                // Since we want exclusive, we need to increment the key to exclude the previous
57                big_endian_saturating_add_one(&mut key_buf);
58            }
59            Some(key_buf)
60        }
61        Bound::Unbounded => None,
62    };
63    let iterator_upper_bound = match range.end_bound() {
64        Bound::Included(upper_bound) => {
65            let mut key_buf = be_fix_int_ser(&upper_bound);
66
67            // If the key is already at the limit, there's nowhere else to go, so no upper
68            // bound
69            if !is_max(&key_buf) {
70                // Since we want exclusive, we need to increment the key to get the upper bound
71                big_endian_saturating_add_one(&mut key_buf);
72                Some(key_buf)
73            } else {
74                None
75            }
76        }
77        Bound::Excluded(upper_bound) => {
78            // Rocksdb upper bound is exclusive by default so nothing to do
79            Some(be_fix_int_ser(&upper_bound))
80        }
81        Bound::Unbounded => None,
82    };
83    (iterator_lower_bound, iterator_upper_bound)
84}
85
86/// Given a vec<u8>, find the value which is one more than the vector
87/// if the vector was a big endian number.
88/// If the vector is already minimum, don't change it.
89fn big_endian_saturating_add_one(v: &mut [u8]) {
90    if is_max(v) {
91        return;
92    }
93    for i in (0..v.len()).rev() {
94        if v[i] == u8::MAX {
95            v[i] = 0;
96        } else {
97            v[i] += 1;
98            break;
99        }
100    }
101}
102
103/// Check if all the bytes in the vector are 0xFF
104fn is_max(v: &[u8]) -> bool {
105    v.iter().all(|&x| x == u8::MAX)
106}
107
108#[expect(clippy::assign_op_pattern, clippy::manual_div_ceil)]
109#[test]
110fn test_helpers() {
111    let v = vec![];
112    assert!(is_max(&v));
113
114    fn check_add(v: Vec<u8>) {
115        let mut v = v;
116        let num = Num32::from_big_endian(&v);
117        big_endian_saturating_add_one(&mut v);
118        assert!(num + 1 == Num32::from_big_endian(&v));
119    }
120
121    uint::construct_uint! {
122        // 32 byte number
123        struct Num32(4);
124    }
125
126    let mut v = vec![255; 32];
127    big_endian_saturating_add_one(&mut v);
128    assert!(Num32::MAX == Num32::from_big_endian(&v));
129
130    check_add(vec![1; 32]);
131    check_add(vec![6; 32]);
132    check_add(vec![254; 32]);
133
134    // TBD: More tests coming with randomized arrays
135}