Skip to main content

mapping/
extents.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::BLOCK_SIZE;
6use anyhow::{Error, anyhow, ensure};
7use std::borrow::Borrow;
8use std::ops::Range;
9
10const TYPE_MASK: u64 = 0xc0000000_00000000;
11const REGULAR: u64 = 0x00000000_00000000;
12const INLINE_TAIL: u64 = 0x40000000_00000000;
13const SPARSE: u64 = 0x80000000_00000000;
14
15// Regular extents are densely bit-packed into a single 64-bit hardware command (LSB 0):
16//   Bits 62-63 (2 most significant bits): Type identifier (`REGULAR`)
17//   Bits 32-61 (30 bits): Extent length in `BLOCK_SIZE` units (4096 bytes)
18//   Bits 0-31  (32 least significant bits): Target device offset block address (in `BLOCK_SIZE`
19//               units), relative to `base_device_offset`
20// Therefore, the maximum contiguous chunk that can fit into a single regular command is 30 bits.
21const MAX_REGULAR_EXTENT_BLOCKS: u64 = 0x3fff_ffff;
22
23// Inline tail extents pack their relative byte offset (up to 16 TiB, matching REGULAR's 32-bit
24// block index * 4096 bytes) into the low 44 bits.
25const MAX_INLINE_TAIL_OFFSET: u64 = 0xfff_ffff_ffff;
26
27// Sparse extents pack their length into the remaining 62 bits not occupied by the type header.
28const MAX_SPARSE_EXTENT_BLOCKS: u64 = !TYPE_MASK;
29
30/// Physical backing for a logical [`Extent`].
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum DeviceExtent {
33    /// Unbacked (sparse) extent that reads as zeros.
34    Sparse,
35    /// Block-aligned physical extent starting at the given device byte offset.
36    Block(u64),
37    /// Single-block inline tail extent starting at a sub-block device byte offset.
38    InlineTail(u64),
39}
40
41impl From<Option<u64>> for DeviceExtent {
42    fn from(offset: Option<u64>) -> Self {
43        match offset {
44            Some(offset) => Self::Block(offset),
45            None => Self::Sparse,
46        }
47    }
48}
49
50/// Represents a logical extent and its physical backing (`DeviceExtent`).
51/// `logical_range` boundaries must always be a multiple of `BLOCK_SIZE` (4096 bytes).
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct Extent {
54    logical_range: Range<u64>,
55    device_extent: DeviceExtent,
56}
57
58impl Extent {
59    /// Returns the logical range of this extent.
60    pub fn logical_range(&self) -> Range<u64> {
61        self.logical_range.clone()
62    }
63
64    /// Returns the [`DeviceExtent`] backing this logical extent.
65    pub fn device_extent(&self) -> DeviceExtent {
66        self.device_extent
67    }
68
69    /// Returns the optional physical device offset of this extent. If None, this extent is sparse.
70    pub fn device_offset(&self) -> Option<u64> {
71        match self.device_extent {
72            DeviceExtent::Sparse => None,
73            DeviceExtent::Block(offset) | DeviceExtent::InlineTail(offset) => Some(offset),
74        }
75    }
76
77    /// Returns true if this extent is sparse (unbacked by physical storage).
78    pub fn is_sparse(&self) -> bool {
79        matches!(self.device_extent, DeviceExtent::Sparse)
80    }
81
82    /// Returns the logical length of this extent in bytes.
83    pub fn len(&self) -> u64 {
84        self.logical_range.end - self.logical_range.start
85    }
86
87    /// Creates a new `Extent`.
88    ///
89    /// # Panics
90    ///
91    /// Panics if `logical_range.start` or `logical_range.end` is not a multiple of
92    /// `BLOCK_SIZE` (4096 bytes), if `logical_range.start > logical_range.end`, or if an
93    /// `InlineTail` extent is not a single block.
94    pub fn new(logical_range: Range<u64>, device_extent: impl Into<DeviceExtent>) -> Self {
95        Self::try_new(logical_range, device_extent).unwrap()
96    }
97
98    /// Creates a new `Extent`, returning an `Error` if the alignment or length is invalid.
99    pub fn try_new(
100        logical_range: Range<u64>,
101        device_extent: impl Into<DeviceExtent>,
102    ) -> Result<Self, Error> {
103        let device_extent = device_extent.into();
104        ensure!(
105            logical_range.start % BLOCK_SIZE == 0 && logical_range.end % BLOCK_SIZE == 0,
106            "logical_range boundaries must be a multiple of BLOCK_SIZE (4096 bytes), got {:?}",
107            logical_range
108        );
109        ensure!(
110            logical_range.start <= logical_range.end,
111            "logical_range.start must be <= logical_range.end, got {:?}",
112            logical_range
113        );
114        let len = logical_range.end - logical_range.start;
115        match device_extent {
116            DeviceExtent::Sparse => {}
117            DeviceExtent::Block(dev_offset) => {
118                ensure!(
119                    dev_offset.checked_add(len).is_some(),
120                    "device_offset + extent length overflows u64"
121                );
122            }
123            DeviceExtent::InlineTail(dev_offset) => {
124                ensure!(
125                    len == BLOCK_SIZE,
126                    "Inline tail extent must be exactly 1 block ({BLOCK_SIZE} bytes), got {len}"
127                );
128                ensure!(
129                    dev_offset.checked_add(BLOCK_SIZE).is_some(),
130                    "device_offset + BLOCK_SIZE overflows u64"
131                );
132            }
133        }
134        Ok(Self { logical_range, device_extent })
135    }
136}
137
138/// Compact in-memory representation of a single extent boundary (16 bytes).
139/// By storing the ending logical offset (`end_logical_offset`), the start of extent `i`
140/// is `0` for `i == 0` or `entries[i - 1].end_logical_offset` for `i > 0`.
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142struct ExtentEntry {
143    end_logical_offset: u64,
144    device_offset: u64, // u64::MAX sentinel indicates a SPARSE (unbacked) extent.
145}
146
147impl ExtentEntry {
148    const SPARSE_DEVICE_OFFSET: u64 = u64::MAX;
149
150    fn is_sparse(&self) -> bool {
151        self.device_offset == Self::SPARSE_DEVICE_OFFSET
152    }
153}
154
155fn encode_regular(length_blocks: u32, target_block: u32) -> u64 {
156    REGULAR | ((length_blocks as u64 & MAX_REGULAR_EXTENT_BLOCKS) << 32) | (target_block as u64)
157}
158
159fn encode_inline_tail(relative_offset: u64) -> u64 {
160    INLINE_TAIL | (relative_offset & MAX_INLINE_TAIL_OFFSET)
161}
162
163fn encode_sparse(length_blocks: u64) -> u64 {
164    SPARSE | (length_blocks & !TYPE_MASK)
165}
166
167/// An iterator over a subset of `Extent`s in an `Extents` container.
168#[derive(Debug, Clone)]
169pub struct ExtentsIterator<'a> {
170    extents: &'a Extents,
171    index: usize,
172}
173
174impl<'a> Iterator for ExtentsIterator<'a> {
175    type Item = Extent;
176
177    fn next(&mut self) -> Option<Self::Item> {
178        if self.index < self.extents.entries.len() {
179            let res = self.extents.entry_to_result(self.index);
180            self.index += 1;
181            Some(res)
182        } else {
183            None
184        }
185    }
186}
187
188/// Container for active mappings associated with a session.
189/// Extents are stored in compact form (`ExtentEntry`, 16 bytes) in a boxed slice
190/// sorted by ascending `end_logical_offset` to support clean O(log N) binary search lookups.
191#[derive(Debug, Clone, PartialEq, Eq, Default)]
192pub struct Extents {
193    base_device_offset: u64,
194    entries: Box<[ExtentEntry]>,
195}
196
197impl Extents {
198    /// Returns the base device offset of this container.
199    pub fn base_device_offset(&self) -> u64 {
200        self.base_device_offset
201    }
202
203    /// Returns the number of encoded extent entries in this container.
204    pub fn len(&self) -> usize {
205        self.entries.len()
206    }
207
208    /// Returns true if this container has no extent entries.
209    pub fn is_empty(&self) -> bool {
210        self.entries.is_empty()
211    }
212
213    /// Creates an `Extents` container from an iterator of `Extent`s, returning an `Error`
214    /// if validation fails.
215    pub fn try_new(
216        extents: impl IntoIterator<Item = impl Borrow<Extent>>,
217        base_device_offset: u64,
218    ) -> Result<Self, Error> {
219        let iter = extents.into_iter();
220        let (lower_bound, _) = iter.size_hint();
221        let mut entries = Vec::with_capacity(lower_bound);
222        let mut current_logical_offset = 0u64;
223
224        for extent in iter {
225            let extent = extent.borrow();
226            ensure!(
227                extent.logical_range.start == current_logical_offset,
228                "Extents must be contiguous and start at 0: expected start \
229                 {current_logical_offset}, got {}",
230                extent.logical_range.start
231            );
232            ensure!(
233                extent.logical_range.start < extent.logical_range.end,
234                "Extent logical range must be non-empty, got {:?}",
235                extent.logical_range
236            );
237            ensure!(
238                extent.logical_range.start % BLOCK_SIZE == 0
239                    && extent.logical_range.end % BLOCK_SIZE == 0,
240                "logical_range boundaries must be a multiple of BLOCK_SIZE ({BLOCK_SIZE} bytes), \
241                 got {:?}",
242                extent.logical_range
243            );
244            let length_blocks = extent.len() / BLOCK_SIZE;
245            match extent.device_extent {
246                DeviceExtent::InlineTail(dev_offset) => {
247                    ensure!(
248                        dev_offset >= base_device_offset,
249                        "device_offset ({dev_offset}) must be >= base_device_offset \
250                         ({base_device_offset})"
251                    );
252                    ensure!(
253                        length_blocks == 1,
254                        "Inline tail extent must be exactly 1 block ({BLOCK_SIZE} bytes)"
255                    );
256                    let relative_offset = dev_offset - base_device_offset;
257                    ensure!(
258                        relative_offset <= MAX_INLINE_TAIL_OFFSET,
259                        "Relative device byte offset exceeds 44-bit maximum"
260                    );
261                    current_logical_offset += BLOCK_SIZE;
262                    entries.push(ExtentEntry {
263                        end_logical_offset: current_logical_offset,
264                        device_offset: dev_offset,
265                    });
266                }
267                DeviceExtent::Block(dev_offset) => {
268                    ensure!(
269                        dev_offset >= base_device_offset,
270                        "device_offset ({dev_offset}) must be >= base_device_offset \
271                         ({base_device_offset})"
272                    );
273                    let relative_offset = dev_offset - base_device_offset;
274                    ensure!(
275                        relative_offset % BLOCK_SIZE == 0,
276                        "Relative device offset ({dev_offset} - {base_device_offset} = \
277                         {relative_offset}) must be a multiple of BLOCK_SIZE ({BLOCK_SIZE} bytes)"
278                    );
279                    let mut remaining_blocks = length_blocks;
280                    let mut current_dev_offset = dev_offset;
281                    while remaining_blocks > 0 {
282                        let entry_blocks =
283                            std::cmp::min(remaining_blocks, MAX_REGULAR_EXTENT_BLOCKS);
284                        let entry_bytes = entry_blocks * BLOCK_SIZE;
285                        let entry_relative_offset = current_dev_offset - base_device_offset;
286                        let target_block = entry_relative_offset / BLOCK_SIZE;
287                        ensure!(
288                            target_block <= u32::MAX as u64,
289                            "Relative device offset block index exceeds u32::MAX"
290                        );
291                        current_logical_offset += entry_bytes;
292                        entries.push(ExtentEntry {
293                            end_logical_offset: current_logical_offset,
294                            device_offset: current_dev_offset,
295                        });
296                        current_dev_offset =
297                            current_dev_offset.checked_add(entry_bytes).ok_or_else(|| {
298                                anyhow!("device_offset + extent length overflows u64")
299                            })?;
300                        remaining_blocks -= entry_blocks;
301                    }
302                }
303                DeviceExtent::Sparse => {
304                    ensure!(
305                        length_blocks <= MAX_SPARSE_EXTENT_BLOCKS,
306                        "Extent length bounds exceed maximum encodeable length"
307                    );
308                    current_logical_offset = extent.logical_range.end;
309                    entries.push(ExtentEntry {
310                        end_logical_offset: current_logical_offset,
311                        device_offset: ExtentEntry::SPARSE_DEVICE_OFFSET,
312                    });
313                }
314            }
315        }
316
317        Ok(Self { base_device_offset, entries: entries.into_boxed_slice() })
318    }
319
320    /// Encodes this `Extents` container into 64-bit mapping descriptors relative to its base
321    /// device offset.
322    pub fn encode(&self) -> impl Iterator<Item = u64> + '_ {
323        let mut prev_logical = 0u64;
324        self.entries.iter().map(move |entry| {
325            let length_blocks = (entry.end_logical_offset - prev_logical) / BLOCK_SIZE;
326            prev_logical = entry.end_logical_offset;
327            if entry.is_sparse() {
328                encode_sparse(length_blocks)
329            } else {
330                let relative_offset = entry.device_offset - self.base_device_offset;
331                if relative_offset % BLOCK_SIZE != 0 {
332                    encode_inline_tail(relative_offset)
333                } else {
334                    let target_block = (relative_offset / BLOCK_SIZE) as u32;
335                    encode_regular(length_blocks as u32, target_block)
336                }
337            }
338        })
339    }
340
341    /// Encodes an `Extents` container into 64-bit mapping descriptors.
342    pub fn encode_extents(extents: &Extents) -> impl Iterator<Item = u64> + '_ {
343        extents.encode()
344    }
345
346    /// Encodes an `Extents` container into 64-bit mapping descriptors relative to its base
347    /// device offset.
348    pub fn encode_extents_with_base_offset(extents: &Extents) -> impl Iterator<Item = u64> + '_ {
349        extents.encode()
350    }
351
352    /// Decodes a sequence of 64-bit mapping descriptors into a compact `Extents` container,
353    /// offsetting regular extents by `base_device_offset`.
354    /// Returns `None` if an unknown mapping descriptor type is encountered or if an arithmetic
355    /// overflow occurs while decoding.
356    pub fn from_encoded(
357        encoded: impl IntoIterator<Item = u64>,
358        base_device_offset: u64,
359    ) -> Option<Self> {
360        let iter = encoded.into_iter();
361        let (lower_bound, _) = iter.size_hint();
362        let mut entries = Vec::with_capacity(lower_bound);
363        let mut current_logical_offset = 0u64;
364
365        for val in iter {
366            let kind = val & TYPE_MASK;
367            if kind == REGULAR {
368                let length_blocks = ((val & !TYPE_MASK) >> 32) as u64;
369                let target_block = (val & 0xffff_ffff) as u64;
370                let length_bytes = length_blocks.checked_mul(BLOCK_SIZE)?;
371                current_logical_offset = current_logical_offset.checked_add(length_bytes)?;
372                let device_offset =
373                    base_device_offset.checked_add(target_block.checked_mul(BLOCK_SIZE)?)?;
374                entries.push(ExtentEntry {
375                    end_logical_offset: current_logical_offset,
376                    device_offset,
377                });
378            } else if kind == INLINE_TAIL {
379                let relative_offset = val & !TYPE_MASK;
380                if relative_offset % BLOCK_SIZE == 0 || relative_offset > MAX_INLINE_TAIL_OFFSET {
381                    return None;
382                }
383                current_logical_offset = current_logical_offset.checked_add(BLOCK_SIZE)?;
384                let device_offset = base_device_offset.checked_add(relative_offset)?;
385                entries.push(ExtentEntry {
386                    end_logical_offset: current_logical_offset,
387                    device_offset,
388                });
389            } else if kind == SPARSE {
390                let length_blocks = (val & !TYPE_MASK) as u64;
391                let length_bytes = length_blocks.checked_mul(BLOCK_SIZE)?;
392                current_logical_offset = current_logical_offset.checked_add(length_bytes)?;
393                entries.push(ExtentEntry {
394                    end_logical_offset: current_logical_offset,
395                    device_offset: ExtentEntry::SPARSE_DEVICE_OFFSET,
396                });
397            } else {
398                return None;
399            }
400        }
401
402        Some(Self { base_device_offset, entries: entries.into_boxed_slice() })
403    }
404
405    /// Returns an iterator over all extents whose logical range ends after `start_offset`,
406    /// jumping directly to the first overlapping extent in O(log N) time via binary search.
407    pub fn iter_extents(&self, start_offset: u64) -> ExtentsIterator<'_> {
408        let index = self.entries.partition_point(|e| e.end_logical_offset <= start_offset);
409        ExtentsIterator { extents: self, index }
410    }
411
412    /// Maps a logical byte offset to the corresponding `Extent` in O(log N) time
413    /// using binary search, translating logical and physical ranges to start at `offset`.
414    ///
415    /// # Panics
416    ///
417    /// Panics if `offset` is not a multiple of `BLOCK_SIZE` (4096 bytes).
418    pub fn map(&self, offset: u64) -> Option<Extent> {
419        assert!(
420            offset % BLOCK_SIZE == 0,
421            "offset must be a multiple of BLOCK_SIZE (4096 bytes), got {offset}"
422        );
423        let idx = self.entries.partition_point(|e| e.end_logical_offset <= offset);
424        if idx >= self.entries.len() {
425            return None;
426        }
427
428        let entry = &self.entries[idx];
429        let start_logical = self.entry_start_offset(idx);
430        let end_logical = entry.end_logical_offset;
431
432        let offset_within = offset.checked_sub(start_logical)?;
433        let device_extent = if entry.is_sparse() {
434            DeviceExtent::Sparse
435        } else {
436            let dev_offset = entry.device_offset.checked_add(offset_within)?;
437            if (dev_offset - self.base_device_offset) % BLOCK_SIZE != 0 {
438                DeviceExtent::InlineTail(dev_offset)
439            } else {
440                DeviceExtent::Block(dev_offset)
441            }
442        };
443
444        Some(Extent { logical_range: offset..end_logical, device_extent })
445    }
446
447    /// Returns all mappings as full `Extent` structs.
448    pub fn mappings(&self) -> Vec<Extent> {
449        (0..self.entries.len()).map(|i| self.entry_to_result(i)).collect()
450    }
451
452    fn entry_start_offset(&self, idx: usize) -> u64 {
453        if idx == 0 { 0 } else { self.entries[idx - 1].end_logical_offset }
454    }
455
456    fn entry_to_result(&self, idx: usize) -> Extent {
457        let entry = &self.entries[idx];
458        let start_logical = self.entry_start_offset(idx);
459        let end_logical = entry.end_logical_offset;
460        let device_extent = if entry.is_sparse() {
461            DeviceExtent::Sparse
462        } else if (entry.device_offset - self.base_device_offset) % BLOCK_SIZE != 0 {
463            DeviceExtent::InlineTail(entry.device_offset)
464        } else {
465            DeviceExtent::Block(entry.device_offset)
466        };
467        Extent { logical_range: start_logical..end_logical, device_extent }
468    }
469}
470
471#[cfg(test)]
472mod tests {
473    use super::*;
474
475    #[test]
476    fn test_encode_decode_regular() {
477        let extents = Extents::try_new(
478            [
479                Extent::new(0..(4 * BLOCK_SIZE), Some(10 * BLOCK_SIZE)),
480                Extent::new((4 * BLOCK_SIZE)..(6 * BLOCK_SIZE), Some(100 * BLOCK_SIZE)),
481            ],
482            0,
483        )
484        .unwrap();
485        let encoded = Extents::encode_extents(&extents);
486        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
487
488        let decoded = extents_container.mappings();
489        assert_eq!(decoded.len(), 2);
490
491        assert_eq!(decoded[0].logical_range, 0..(4 * BLOCK_SIZE));
492        assert_eq!(decoded[0].device_offset(), Some(10 * BLOCK_SIZE));
493
494        assert_eq!(decoded[1].logical_range, (4 * BLOCK_SIZE)..(6 * BLOCK_SIZE));
495        assert_eq!(decoded[1].device_offset(), Some(100 * BLOCK_SIZE));
496    }
497
498    #[test]
499    fn test_encode_decode_sparse() {
500        let extents = Extents::try_new(
501            [
502                Extent::new(0..(2 * BLOCK_SIZE), Some(50 * BLOCK_SIZE)),
503                Extent::new((2 * BLOCK_SIZE)..(5 * BLOCK_SIZE), None),
504                Extent::new((5 * BLOCK_SIZE)..(6 * BLOCK_SIZE), Some(200 * BLOCK_SIZE)),
505            ],
506            0,
507        )
508        .unwrap();
509        let encoded = Extents::encode_extents(&extents);
510        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
511
512        let decoded = extents_container.mappings();
513        assert_eq!(decoded.len(), 3);
514
515        assert_eq!(decoded[0].logical_range, 0..(2 * BLOCK_SIZE));
516        assert_eq!(decoded[0].device_offset(), Some(50 * BLOCK_SIZE));
517
518        assert_eq!(decoded[1].logical_range, (2 * BLOCK_SIZE)..(5 * BLOCK_SIZE));
519        assert_eq!(decoded[1].device_offset(), None);
520
521        assert_eq!(decoded[2].logical_range, (5 * BLOCK_SIZE)..(6 * BLOCK_SIZE));
522        assert_eq!(decoded[2].device_offset(), Some(200 * BLOCK_SIZE));
523    }
524
525    #[test]
526    fn test_encode_decode_non_aligned_start() {
527        let base_device_offset = 17408u64; // e.g. LBA 34 on 512-byte sector disk
528        let extents = Extents::try_new(
529            [
530                Extent::new(0..(4 * BLOCK_SIZE), Some(base_device_offset)),
531                Extent::new(
532                    (4 * BLOCK_SIZE)..(6 * BLOCK_SIZE),
533                    Some(base_device_offset + 100 * BLOCK_SIZE),
534                ),
535            ],
536            base_device_offset,
537        )
538        .unwrap();
539        let encoded = Extents::encode_extents_with_base_offset(&extents);
540        let extents_container =
541            Extents::from_encoded(encoded, base_device_offset).expect("from_encoded failed");
542
543        let decoded = extents_container.mappings();
544        assert_eq!(decoded.len(), 2);
545        assert_eq!(decoded[0].logical_range, 0..(4 * BLOCK_SIZE));
546        assert_eq!(decoded[0].device_offset(), Some(base_device_offset));
547        assert_eq!(decoded[1].logical_range, (4 * BLOCK_SIZE)..(6 * BLOCK_SIZE));
548        assert_eq!(decoded[1].device_offset(), Some(base_device_offset + 100 * BLOCK_SIZE));
549
550        let mapped = extents_container.map(0).expect("should map at 0");
551        assert_eq!(mapped.device_offset(), Some(base_device_offset));
552
553        let mapped_next = extents_container.map(4 * BLOCK_SIZE).expect("should map next");
554        assert_eq!(mapped_next.device_offset(), Some(base_device_offset + 100 * BLOCK_SIZE));
555    }
556
557    #[test]
558    fn test_extents_validation_relative_offset_unaligned_fails() {
559        let base_device_offset = 17408u64;
560        // 17408 + 500 is not aligned to BLOCK_SIZE relative to base_device_offset
561        let result = Extents::try_new(
562            [Extent::new(0..BLOCK_SIZE, Some(base_device_offset + 500))],
563            base_device_offset,
564        );
565        assert!(result.is_err());
566        assert!(
567            result.unwrap_err().to_string().contains("Relative device offset"),
568            "Error should mention relative device offset"
569        );
570    }
571
572    #[test]
573    fn test_extents_validation_device_offset_smaller_than_base_fails() {
574        let base_device_offset = 17408u64;
575        let result = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], base_device_offset);
576        assert!(result.is_err());
577        assert!(
578            result.unwrap_err().to_string().contains("must be >= base_device_offset"),
579            "Error should mention dev_offset >= base_device_offset"
580        );
581    }
582
583    #[test]
584    fn test_extents_validation_non_contiguous_logical_fails() {
585        let result = Extents::try_new(
586            [
587                Extent::new(0..BLOCK_SIZE, Some(0)),
588                Extent::new((2 * BLOCK_SIZE)..(3 * BLOCK_SIZE), Some(BLOCK_SIZE)),
589            ],
590            0,
591        );
592        assert!(result.is_err());
593        assert!(
594            result.unwrap_err().to_string().contains("must be contiguous and start at 0"),
595            "Error should mention non-contiguous start"
596        );
597    }
598
599    #[test]
600    fn test_binary_search_map_logical_offset() {
601        let extents = Extents::try_new(
602            [
603                Extent::new(0..(10 * BLOCK_SIZE), Some(100 * BLOCK_SIZE)),
604                Extent::new((10 * BLOCK_SIZE)..(20 * BLOCK_SIZE), Some(200 * BLOCK_SIZE)),
605                Extent::new((20 * BLOCK_SIZE)..(30 * BLOCK_SIZE), Some(300 * BLOCK_SIZE)),
606            ],
607            0,
608        )
609        .unwrap();
610        let encoded = Extents::encode_extents(&extents);
611        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
612
613        let mapped = extents_container.map(0).expect("should map at offset 0");
614        assert_eq!(mapped.logical_range, 0..(10 * BLOCK_SIZE));
615        assert_eq!(mapped.device_offset(), Some(100 * BLOCK_SIZE));
616
617        let mapped_mid = extents_container
618            .map(12 * BLOCK_SIZE)
619            .expect("should map inside second extent via binary search");
620        assert_eq!(mapped_mid.logical_range, (12 * BLOCK_SIZE)..(20 * BLOCK_SIZE));
621        assert_eq!(mapped_mid.device_offset(), Some(202 * BLOCK_SIZE));
622    }
623
624    #[test]
625    fn test_map_out_of_bounds() {
626        let extents =
627            Extents::try_new([Extent::new(0..(2 * BLOCK_SIZE), Some(10 * BLOCK_SIZE))], 0).unwrap();
628        let encoded = Extents::encode_extents(&extents);
629        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
630
631        assert!(extents_container.map(2 * BLOCK_SIZE).is_none());
632        assert!(extents_container.map(100 * BLOCK_SIZE).is_none());
633    }
634
635    #[test]
636    fn test_binary_search_iter_extents() {
637        let extents = Extents::try_new(
638            [
639                Extent::new(0..(2 * BLOCK_SIZE), Some(10 * BLOCK_SIZE)),
640                Extent::new((2 * BLOCK_SIZE)..(4 * BLOCK_SIZE), Some(20 * BLOCK_SIZE)),
641                Extent::new((4 * BLOCK_SIZE)..(6 * BLOCK_SIZE), Some(30 * BLOCK_SIZE)),
642            ],
643            0,
644        )
645        .unwrap();
646        let encoded = Extents::encode_extents(&extents);
647        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
648
649        let results: Vec<_> = extents_container.iter_extents(3 * BLOCK_SIZE).collect();
650        assert_eq!(results.len(), 2);
651        assert_eq!(results[0].logical_range, (2 * BLOCK_SIZE)..(4 * BLOCK_SIZE));
652        assert_eq!(results[1].logical_range, (4 * BLOCK_SIZE)..(6 * BLOCK_SIZE));
653    }
654
655    #[test]
656    fn test_exact_boundary_queries() {
657        let extents = Extents::try_new(
658            [
659                Extent::new(0..(10 * BLOCK_SIZE), Some(100 * BLOCK_SIZE)),
660                Extent::new((10 * BLOCK_SIZE)..(20 * BLOCK_SIZE), Some(200 * BLOCK_SIZE)),
661            ],
662            0,
663        )
664        .unwrap();
665        let encoded = Extents::encode_extents(&extents);
666        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
667
668        let mapped = extents_container.map(10 * BLOCK_SIZE).expect("should map at exact boundary");
669        assert_eq!(mapped.logical_range, (10 * BLOCK_SIZE)..(20 * BLOCK_SIZE));
670        assert_eq!(mapped.device_offset(), Some(200 * BLOCK_SIZE));
671
672        let results: Vec<_> = extents_container.iter_extents(10 * BLOCK_SIZE).collect();
673        assert_eq!(results.len(), 1);
674        assert_eq!(results[0].logical_range, (10 * BLOCK_SIZE)..(20 * BLOCK_SIZE));
675    }
676
677    #[test]
678    #[should_panic(expected = "multiple of BLOCK_SIZE")]
679    fn test_extent_new_unaligned_logical_start_panics() {
680        Extent::new(1..(2 * BLOCK_SIZE), Some(10 * BLOCK_SIZE));
681    }
682
683    #[test]
684    #[should_panic(expected = "multiple of BLOCK_SIZE")]
685    fn test_extent_new_unaligned_logical_end_panics() {
686        Extent::new(0..(2 * BLOCK_SIZE + 1), Some(10 * BLOCK_SIZE));
687    }
688
689    #[test]
690    #[should_panic(expected = "multiple of BLOCK_SIZE")]
691    fn test_map_unaligned_offset_panics() {
692        Extents::default().map(500);
693    }
694
695    #[test]
696    fn test_iter_extents_unaligned_start_offset() {
697        let extents = Extents::try_new(
698            [
699                Extent::new(0..(10 * BLOCK_SIZE), Some(100 * BLOCK_SIZE)),
700                Extent::new((10 * BLOCK_SIZE)..(20 * BLOCK_SIZE), Some(200 * BLOCK_SIZE)),
701            ],
702            0,
703        )
704        .unwrap();
705        let encoded = Extents::encode_extents(&extents);
706        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
707        let results: Vec<_> = extents_container.iter_extents(500).collect();
708        assert_eq!(results.len(), 2);
709        assert_eq!(results[0].logical_range, 0..(10 * BLOCK_SIZE));
710    }
711
712    #[test]
713    fn test_encode_decode_large_regular_extent() {
714        let entry_bytes = MAX_REGULAR_EXTENT_BLOCKS * BLOCK_SIZE;
715        let large_len = 2 * entry_bytes + 5 * BLOCK_SIZE;
716        let start_dev = 10 * BLOCK_SIZE;
717        let next_len = 3 * BLOCK_SIZE;
718        let next_dev = 50 * BLOCK_SIZE;
719
720        let extents = Extents::try_new(
721            [
722                Extent::new(0..large_len, Some(start_dev)),
723                Extent::new(large_len..(large_len + next_len), Some(next_dev)),
724            ],
725            0,
726        )
727        .expect("Extents::try_new should split large regular extent");
728
729        let encoded: Vec<u64> = Extents::encode_extents(&extents).collect();
730        assert_eq!(encoded.len(), 4);
731
732        let extents_container = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
733        assert_eq!(extents_container, extents);
734
735        let decoded = extents_container.mappings();
736        assert_eq!(
737            decoded,
738            vec![
739                Extent::new(0..entry_bytes, Some(start_dev)),
740                Extent::new(entry_bytes..(2 * entry_bytes), Some(start_dev + entry_bytes)),
741                Extent::new((2 * entry_bytes)..large_len, Some(start_dev + 2 * entry_bytes)),
742                Extent::new(large_len..(large_len + next_len), Some(next_dev)),
743            ]
744        );
745
746        let mapped_second_entry =
747            extents_container.map(entry_bytes + BLOCK_SIZE).expect("should map in second entry");
748        assert_eq!(
749            mapped_second_entry.logical_range,
750            (entry_bytes + BLOCK_SIZE)..(2 * entry_bytes)
751        );
752        assert_eq!(mapped_second_entry.device_offset(), Some(start_dev + entry_bytes + BLOCK_SIZE));
753    }
754
755    #[test]
756    fn test_large_regular_extent_target_block_overflow_errors() {
757        let large_len = (MAX_REGULAR_EXTENT_BLOCKS + 1) * BLOCK_SIZE;
758        let start_dev = (u32::MAX as u64 - 10) * BLOCK_SIZE;
759        let extent = Extent::try_new(0..large_len, Some(start_dev))
760            .expect("Extent::try_new should allow large length");
761        let result = Extents::try_new([extent], 0);
762        assert!(result.is_err());
763        assert!(
764            result
765                .unwrap_err()
766                .to_string()
767                .contains("Relative device offset block index exceeds u32::MAX"),
768            "Second split entry exceeding u32::MAX target_block should fail"
769        );
770    }
771
772    #[test]
773    fn test_from_encoded_unknown_kind_returns_none() {
774        let unknown_descriptor = 0x40000000_00000000;
775        assert!(Extents::from_encoded([unknown_descriptor], 0).is_none());
776        let unknown_descriptor_2 = 0xc0000000_00000000;
777        assert!(Extents::from_encoded([unknown_descriptor_2], 0).is_none());
778    }
779
780    #[test]
781    fn test_encode_decode_inline_tail() {
782        let extents = Extents::try_new(
783            [
784                Extent::new(0..(2 * BLOCK_SIZE), DeviceExtent::Block(10 * BLOCK_SIZE)),
785                Extent::new(
786                    (2 * BLOCK_SIZE)..(3 * BLOCK_SIZE),
787                    DeviceExtent::InlineTail(3 * BLOCK_SIZE + 64),
788                ),
789            ],
790            0,
791        )
792        .unwrap();
793        let encoded: Vec<u64> = Extents::encode_extents(&extents).collect();
794        assert_eq!(encoded.len(), 2);
795        let decoded = Extents::from_encoded(encoded, 0).expect("from_encoded failed");
796        assert_eq!(decoded, extents);
797        let mapped = decoded.map(2 * BLOCK_SIZE).expect("should map inline tail");
798        assert_eq!(mapped.logical_range, (2 * BLOCK_SIZE)..(3 * BLOCK_SIZE));
799        assert_eq!(mapped.device_extent(), DeviceExtent::InlineTail(3 * BLOCK_SIZE + 64));
800        assert_eq!(mapped.device_offset(), Some(3 * BLOCK_SIZE + 64));
801    }
802}