1use bytes::BufMut;
4use commonware_codec::{Buf, BufsMut, EncodeSize, Error as CodecError, MaxEncodeSize, Read, Write};
5use commonware_macros::stability;
6#[stability(ALPHA)]
7use core::ops::{
8 Bound::{Excluded, Included, Unbounded},
9 RangeBounds,
10};
11use core::{fmt, ops::Range};
12
13#[stability(ALPHA)]
32pub fn contains_cyclic<K: Ord + ?Sized>(span: impl RangeBounds<K>, key: &K) -> bool {
33 let start = span.start_bound();
34 let end = span.end_bound();
35 let after_start = match start {
36 Included(start) => key >= start,
37 Excluded(start) => key > start,
38 Unbounded => true,
39 };
40 let before_end = match end {
41 Included(end) => key <= end,
42 Excluded(end) => key < end,
43 Unbounded => true,
44 };
45 match (start, end) {
46 (Included(start) | Excluded(start), Included(end) | Excluded(end)) if start >= end => {
47 after_start || before_end
48 }
49 _ => after_start && before_end,
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
55#[error("range is empty")]
56pub struct EmptyRange;
57
58#[derive(Clone, PartialEq, Eq, Hash)]
60pub struct NonEmptyRange<Idx>(Range<Idx>);
61
62impl<Idx: fmt::Debug> fmt::Debug for NonEmptyRange<Idx> {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 self.0.fmt(f)
65 }
66}
67
68impl<Idx: PartialOrd> NonEmptyRange<Idx> {
69 pub fn new(range: Range<Idx>) -> Result<Self, EmptyRange> {
71 (range.start < range.end)
72 .then_some(Self(range))
73 .ok_or(EmptyRange)
74 }
75}
76
77impl<Idx: Copy> NonEmptyRange<Idx> {
78 pub const fn start(&self) -> Idx {
80 self.0.start
81 }
82
83 pub const fn end(&self) -> Idx {
85 self.0.end
86 }
87}
88
89impl<Idx: PartialOrd> TryFrom<Range<Idx>> for NonEmptyRange<Idx> {
90 type Error = EmptyRange;
91
92 fn try_from(range: Range<Idx>) -> Result<Self, Self::Error> {
93 Self::new(range)
94 }
95}
96
97impl<Idx> From<NonEmptyRange<Idx>> for Range<Idx> {
98 fn from(r: NonEmptyRange<Idx>) -> Self {
99 r.0
100 }
101}
102
103impl<Idx> IntoIterator for NonEmptyRange<Idx>
104where
105 Range<Idx>: Iterator,
106{
107 type Item = <Range<Idx> as Iterator>::Item;
108 type IntoIter = Range<Idx>;
109
110 fn into_iter(self) -> Self::IntoIter {
111 self.0
112 }
113}
114
115impl<Idx: Write> Write for NonEmptyRange<Idx> {
116 #[inline]
117 fn write(&self, buf: &mut impl BufMut) {
118 self.0.start.write(buf);
119 self.0.end.write(buf);
120 }
121
122 #[inline]
123 fn write_bufs(&self, buf: &mut impl BufsMut) {
124 self.0.start.write_bufs(buf);
125 self.0.end.write_bufs(buf);
126 }
127}
128
129impl<Idx: EncodeSize> EncodeSize for NonEmptyRange<Idx> {
130 #[inline]
131 fn encode_size(&self) -> usize {
132 self.0.start.encode_size() + self.0.end.encode_size()
133 }
134
135 #[inline]
136 fn encode_inline_size(&self) -> usize {
137 self.0.start.encode_inline_size() + self.0.end.encode_inline_size()
138 }
139}
140
141impl<Idx: MaxEncodeSize> MaxEncodeSize for NonEmptyRange<Idx> {
142 const MAX_ENCODE_SIZE: usize = 2 * Idx::MAX_ENCODE_SIZE;
143}
144
145impl<Idx: Read + PartialOrd> Read for NonEmptyRange<Idx> {
146 type Cfg = Idx::Cfg;
147
148 #[inline]
149 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
150 let start = Idx::read_cfg(buf, cfg)?;
151 let end = Idx::read_cfg(buf, cfg)?;
152 if !start.partial_cmp(&end).is_some_and(|o| o.is_lt()) {
153 return Err(CodecError::Invalid("NonEmptyRange", "start must be < end"));
154 }
155 Ok(Self(start..end))
156 }
157}
158
159#[cfg(feature = "arbitrary")]
160impl<'a, Idx: arbitrary::Arbitrary<'a> + Ord> arbitrary::Arbitrary<'a> for NonEmptyRange<Idx> {
161 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
162 let a = Idx::arbitrary(u)?;
163 let b = Idx::arbitrary(u)?;
164 let (start, end) = if a < b {
165 (a, b)
166 } else if b < a {
167 (b, a)
168 } else {
169 return Err(arbitrary::Error::IncorrectFormat);
170 };
171 Ok(Self(start..end))
172 }
173}
174
175#[macro_export]
177macro_rules! non_empty_range {
178 ($start:expr, $end:expr) => {
179 $crate::range::NonEmptyRange::new($start..$end).expect("range must be non-empty")
180 };
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use commonware_codec::{DecodeExt, Encode};
187
188 #[test]
189 fn test_contains_cyclic_boundaries() {
190 let cases: &[(_, &[u8])] = &[
191 ((Included(2), Excluded(6)), &[2, 3, 4, 5]),
192 ((Excluded(2), Included(6)), &[3, 4, 5, 6]),
193 ((Included(2), Included(6)), &[2, 3, 4, 5, 6]),
194 ((Excluded(2), Excluded(6)), &[3, 4, 5]),
195 ((Included(6), Excluded(2)), &[0, 1, 6, 7]),
196 ((Excluded(6), Included(2)), &[0, 1, 2, 7]),
197 ((Included(6), Included(2)), &[0, 1, 2, 6, 7]),
198 ((Excluded(6), Excluded(2)), &[0, 1, 7]),
199 ((Included(3), Excluded(3)), &[0, 1, 2, 3, 4, 5, 6, 7]),
200 ((Excluded(3), Included(3)), &[0, 1, 2, 3, 4, 5, 6, 7]),
201 ((Included(3), Included(3)), &[0, 1, 2, 3, 4, 5, 6, 7]),
202 ((Excluded(3), Excluded(3)), &[0, 1, 2, 4, 5, 6, 7]),
203 ((Unbounded, Included(3)), &[0, 1, 2, 3]),
204 ((Unbounded, Excluded(3)), &[0, 1, 2]),
205 ((Included(3), Unbounded), &[3, 4, 5, 6, 7]),
206 ((Excluded(3), Unbounded), &[4, 5, 6, 7]),
207 ((Unbounded, Unbounded), &[0, 1, 2, 3, 4, 5, 6, 7]),
208 ];
209 for &(bounds, expected) in cases {
210 let actual: Vec<_> = (0..=7).filter(|key| contains_cyclic(bounds, key)).collect();
211 assert_eq!(actual, expected, "bounds: {bounds:?}");
212 }
213 }
214
215 #[test]
216 #[allow(clippy::reversed_empty_ranges)]
217 fn test_contains_cyclic_extremes() {
218 for key in u8::MIN..=u8::MAX {
219 assert_eq!(contains_cyclic(u8::MAX..u8::MIN, &key), key == u8::MAX);
220 assert_eq!(
221 contains_cyclic((Excluded(u8::MAX), Included(u8::MIN)), &key),
222 key == u8::MIN
223 );
224 assert!(contains_cyclic(u8::MIN..u8::MIN, &key));
225 assert!(contains_cyclic(u8::MAX..u8::MAX, &key));
226 assert!(!contains_cyclic(..u8::MIN, &key));
227 assert!(!contains_cyclic((Excluded(u8::MAX), Unbounded), &key));
228 }
229 }
230
231 #[test]
232 fn test_contains_cyclic_range_syntax() {
233 let start = String::from("a");
234 let end = String::from("c");
235 let key = String::from("b");
236 assert!(contains_cyclic(&start..&end, &key));
237 assert!(!contains_cyclic(&start..&end, &end));
238 assert!(contains_cyclic(&start..=&end, &end));
239 assert!(contains_cyclic(&start.., &key));
240 assert!(!contains_cyclic(..&end, &end));
241 assert!(contains_cyclic(..=&end, &end));
242 assert!(contains_cyclic(.., &key));
243 assert!(contains_cyclic((Excluded(&start), Included(&end)), &end));
244 assert!(contains_cyclic(
245 (Included(start.as_str()), Excluded(end.as_str())),
246 key.as_str()
247 ));
248 }
249
250 #[test]
251 fn test_contains_cyclic_current_bounds() {
252 let mut span = 2..=6;
253 assert_eq!(span.next(), Some(2));
254 assert_eq!(span.next_back(), Some(6));
255 for key in 0..=7 {
256 assert_eq!(contains_cyclic(span.clone(), &key), (3..=5).contains(&key));
257 }
258 }
259
260 #[test]
261 fn test_non_empty_range_valid() {
262 let r = NonEmptyRange::new(0u32..5).unwrap();
263 assert_eq!(r.start(), 0);
264 assert_eq!(r.end(), 5);
265 assert_eq!(Range::from(r), 0..5);
266 }
267
268 #[test]
269 fn test_non_empty_range_single_element() {
270 let r = NonEmptyRange::new(3u32..4).unwrap();
271 assert_eq!(r.start(), 3);
272 assert_eq!(r.end(), 4);
273 }
274
275 #[test]
276 fn test_non_empty_range_empty() {
277 assert_eq!(NonEmptyRange::new(5u32..5), Err(EmptyRange));
278 #[allow(clippy::reversed_empty_ranges)]
279 let reversed = NonEmptyRange::new(5u32..3);
280 assert_eq!(reversed, Err(EmptyRange));
281 }
282
283 #[test]
284 fn test_non_empty_range_into() {
285 let r = NonEmptyRange::new(1u32..10).unwrap();
286 let range: Range<u32> = r.into();
287 assert_eq!(range, 1..10);
288 }
289
290 #[test]
291 fn test_non_empty_range_debug() {
292 let r = NonEmptyRange::new(1u32..5).unwrap();
293 assert_eq!(format!("{r:?}"), "1..5");
294 }
295
296 #[test]
297 fn test_non_empty_range_iter() {
298 let r = NonEmptyRange::new(0u32..4).unwrap();
299 let items: Vec<_> = r.into_iter().collect();
300 assert_eq!(items, vec![0, 1, 2, 3]);
301 }
302
303 #[test]
304 fn test_non_empty_range_encode_decode() {
305 let r = NonEmptyRange::new(10u32..20).unwrap();
306 let encoded = r.encode();
307 let decoded = NonEmptyRange::<u32>::decode(encoded).unwrap();
308 assert_eq!(r, decoded);
309 }
310
311 #[test]
312 fn test_non_empty_range_decode_invalid() {
313 for (start, end) in [(20u32, 10u32), (5, 5)] {
314 let mut buf = Vec::new();
315 buf.extend_from_slice(&start.to_be_bytes());
316 buf.extend_from_slice(&end.to_be_bytes());
317 assert!(matches!(
318 NonEmptyRange::<u32>::decode(buf),
319 Err(CodecError::Invalid("NonEmptyRange", "start must be < end"))
320 ));
321 }
322 }
323
324 #[cfg(feature = "arbitrary")]
325 mod conformance {
326 use super::*;
327 use commonware_codec::conformance::CodecConformance;
328
329 commonware_conformance::conformance_tests! {
330 CodecConformance<NonEmptyRange<u32>>,
331 CodecConformance<NonEmptyRange<u64>>,
332 }
333 }
334}