synta-python-mtc 0.3.0

Python extension module for synta Merkle Tree Certificates types
Documentation
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;

// ── RevokedRanges ─────────────────────────────────────────────────────────────

/// A mutable, sorted collection of non-overlapping inclusive ``(start, end)``
/// ranges representing revoked leaf indices in an MTC log (draft-04 §7.5).
///
/// Ranges are automatically merged on insertion: overlapping and adjacent
/// ranges are coalesced.  Lookups use binary search for O(log n) performance.
///
/// ```python,ignore
/// import synta.mtc as mtc
///
/// revoked = mtc.RevokedRanges()
/// revoked.add(1, 5)
/// revoked.add(3, 8)   # overlaps — merged into [1, 8]
/// revoked.add(9, 12)  # adjacent — merged into [1, 12]
///
/// assert revoked.is_revoked(6)
/// assert not revoked.is_revoked(0)
/// assert len(revoked) == 1
/// ```
#[pyclass(name = "RevokedRanges")]
pub struct PyRevokedRanges {
    inner: synta_mtc::revocation::RevokedRanges,
}

#[pymethods]
impl PyRevokedRanges {
    /// Create an empty revocation range collection.
    #[new]
    fn new() -> Self {
        Self {
            inner: synta_mtc::revocation::RevokedRanges::new(),
        }
    }

    /// Insert an inclusive range ``[start, end]`` and merge with any
    /// overlapping or adjacent existing ranges.
    ///
    /// :param start: Start of the revoked range (inclusive).
    /// :param end: End of the revoked range (inclusive).
    /// :raises ValueError: if *start* > *end*.
    fn add(&mut self, start: u64, end: u64) -> PyResult<()> {
        self.inner
            .add(start, end)
            .map_err(|e| PyValueError::new_err(e.to_string()))
    }

    /// Check whether *index* falls within any revoked range.
    ///
    /// Uses binary search — O(log n) where n is the number of ranges.
    ///
    /// :param index: Leaf index to check.
    /// :returns: ``True`` if *index* is revoked, ``False`` otherwise.
    fn is_revoked(&self, index: u64) -> bool {
        self.inner.is_revoked(index)
    }

    /// Check whether every index in ``[start, end]`` (inclusive) is revoked.
    ///
    /// :param start: Start of the range to check (inclusive).
    /// :param end: End of the range to check (inclusive).
    /// :returns: ``True`` if the entire range is covered by a single revoked range.
    fn contains_range(&self, start: u64, end: u64) -> bool {
        self.inner.contains_range(start, end)
    }

    /// Return the number of distinct (non-overlapping) ranges.
    fn len(&self) -> usize {
        self.inner.len()
    }

    /// Return ``True`` if no ranges have been added.
    fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    /// Return a list of all ``(start, end)`` inclusive ranges in sorted order.
    ///
    /// :returns: List of ``(int, int)`` tuples, each representing an inclusive
    ///     revoked range.
    fn ranges<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, pyo3::types::PyList>> {
        let items: Vec<Bound<'_, pyo3::types::PyTuple>> = self
            .inner
            .iter()
            .map(|(s, e)| pyo3::types::PyTuple::new(py, [s, e]))
            .collect::<PyResult<Vec<_>>>()?;
        pyo3::types::PyList::new(py, items)
    }

    fn __len__(&self) -> usize {
        self.inner.len()
    }

    fn __repr__(&self) -> String {
        format!("RevokedRanges(len={})", self.inner.len())
    }
}