OpenPay (1 crate, 1801 LOC, 57 tests)
op-bin BIN/IIN database + range matching + card-network
classification. 8-digit normalized prefix Bin +
BinRange. Sorted-vec interval tree with O(log N) point
lookup + overlap rejection. CardNetwork classify()
with structural rules (Visa 4, MC 51-55/2221-2720,
Amex 34/37, Discover, JCB, Diners, UnionPay, RuPay,
Mir, Troy, Maestro, Elo). Static prefix tables +
build_default_tree(). ISO/IEC 7812-1 Annex B Luhn
(validate / is_valid / check_digit). Card-type +
issuer-country + Durbin regulated-debit. PCI-safe:
8-digit cap, no full-PAN APIs.
Smart Byte (1 crate, ~1500 LOC, 20 tests)
smart-byte-tendermint BFT consensus. Step enum + Round + propose/prevote/
precommit/commit FSM with voting-power tallies.
ValidatorSet with 2/3+1 quorum, 1/3+1 trust
threshold, round-robin proposer. Block / Header /
Commit / CommitSig with Blake3 hashing. Light client
(sequential + skipping). ABCI Application trait +
EchoApp. DuplicateVoteEvidence + LightClientAttackEvidence.
IBC ClientState + ConsensusState + light-client
update.
EOC (1 crate, 2084 LOC, 23 tests)
eoc-safety AI safety primitives. RegexSet over 16 prompt-injection
signatures. 13-family jailbreak detector (DAN/DUDE/
Grandma/STAN/AIM/dev-mode/etc.). Presidio-style PII
redactor with Luhn-validated CC. Toxicity / bias /
NSFW classifier traits + heuristic baselines (5-axis
bias, 4-category NSFW with hard-reject for minors).
Constitutional AI critique→revise loop. Composable
InputGuard / OutputGuard pipelines. JSON-Schema-subset
output validator. Token-bucket rate limiter +
abuse-window detection. Red-team replay harness.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
167 lines
5 KiB
Rust
167 lines
5 KiB
Rust
//! Sorted-vector interval tree for BIN range lookup.
|
|
//!
|
|
//! Card-network ranges form a set of **disjoint** half-open
|
|
//! intervals over `0..=99_999_999` (8-digit prefix space). A
|
|
//! plain sorted `Vec<BinRange>` with binary search by `low` gives
|
|
//! `O(log N)` point lookups, which is the right complexity for
|
|
//! the ~few-thousand published ranges. We do not need a full
|
|
//! augmented interval tree because BIN ranges do not overlap by
|
|
//! construction (ISO/IEC 7812 assigns them exclusively).
|
|
//!
|
|
//! ## Invariant
|
|
//!
|
|
//! The `ranges` vector is **sorted by `low`** and contains no
|
|
//! overlapping intervals. [`RangeTree::insert`] enforces both.
|
|
|
|
use crate::bin::{Bin, BinRange};
|
|
use crate::error::{Error, Result};
|
|
|
|
/// Sorted, non-overlapping collection of [`BinRange`] entries
|
|
/// optimized for point lookups.
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct RangeTree {
|
|
ranges: Vec<BinRange>,
|
|
}
|
|
|
|
impl RangeTree {
|
|
/// Empty tree.
|
|
pub const fn new() -> Self {
|
|
Self { ranges: Vec::new() }
|
|
}
|
|
|
|
/// Build from an iterator of ranges. Ranges are inserted in
|
|
/// order; later insertions that overlap an earlier range are
|
|
/// rejected.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// - [`Error::InvalidRange`] if any pair overlaps.
|
|
pub fn from_ranges<I: IntoIterator<Item = BinRange>>(iter: I) -> Result<Self> {
|
|
let mut tree = Self::new();
|
|
for r in iter {
|
|
tree.insert(r)?;
|
|
}
|
|
Ok(tree)
|
|
}
|
|
|
|
/// Insert a range, maintaining sorted order and disjointness.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// - [`Error::InvalidRange`] if the new range overlaps an
|
|
/// existing one.
|
|
pub fn insert(&mut self, r: BinRange) -> Result<()> {
|
|
// Find insertion point by `low`.
|
|
let pos = self
|
|
.ranges
|
|
.binary_search_by(|probe| probe.low.cmp(&r.low))
|
|
.unwrap_or_else(|i| i);
|
|
|
|
// Check overlap with predecessor.
|
|
if pos > 0 {
|
|
let prev = &self.ranges[pos - 1];
|
|
if prev.high > r.low {
|
|
return Err(Error::InvalidRange {
|
|
low: r.low,
|
|
high: r.high,
|
|
});
|
|
}
|
|
}
|
|
// Check overlap with successor.
|
|
if pos < self.ranges.len() {
|
|
let next = &self.ranges[pos];
|
|
if r.high > next.low {
|
|
return Err(Error::InvalidRange {
|
|
low: r.low,
|
|
high: r.high,
|
|
});
|
|
}
|
|
}
|
|
self.ranges.insert(pos, r);
|
|
Ok(())
|
|
}
|
|
|
|
/// `O(log N)` point lookup. Returns the unique range
|
|
/// containing this BIN, or `None`.
|
|
pub fn lookup(&self, bin: &Bin) -> Option<&BinRange> {
|
|
let p = bin.prefix_8();
|
|
// Find rightmost range with `low <= p`.
|
|
let idx = match self.ranges.binary_search_by(|probe| probe.low.cmp(&p)) {
|
|
Ok(i) => i,
|
|
Err(0) => return None,
|
|
Err(i) => i - 1,
|
|
};
|
|
let candidate = &self.ranges[idx];
|
|
if candidate.contains(bin) {
|
|
Some(candidate)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Total number of ranges.
|
|
pub fn len(&self) -> usize {
|
|
self.ranges.len()
|
|
}
|
|
|
|
/// True iff empty.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.ranges.is_empty()
|
|
}
|
|
|
|
/// Read-only access to the underlying sorted slice.
|
|
pub fn ranges(&self) -> &[BinRange] {
|
|
&self.ranges
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::card_type::CardType;
|
|
use crate::network::CardNetwork;
|
|
|
|
fn mk(low: u32, high: u32, net: CardNetwork) -> BinRange {
|
|
BinRange::new(low, high, net, CardType::Credit, None, false).expect("valid")
|
|
}
|
|
|
|
#[test]
|
|
fn insert_and_lookup() {
|
|
let mut t = RangeTree::new();
|
|
t.insert(mk(40_000_000, 50_000_000, CardNetwork::Visa))
|
|
.expect("ok");
|
|
t.insert(mk(51_000_000, 56_000_000, CardNetwork::Mastercard))
|
|
.expect("ok");
|
|
|
|
let v = Bin::parse("411111").expect("ok");
|
|
let m = Bin::parse("520000").expect("ok");
|
|
let n = Bin::parse("600000").expect("ok");
|
|
|
|
assert_eq!(t.lookup(&v).map(|r| r.network), Some(CardNetwork::Visa));
|
|
assert_eq!(
|
|
t.lookup(&m).map(|r| r.network),
|
|
Some(CardNetwork::Mastercard)
|
|
);
|
|
assert!(t.lookup(&n).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn overlap_rejected() {
|
|
let mut t = RangeTree::new();
|
|
t.insert(mk(40_000_000, 50_000_000, CardNetwork::Visa))
|
|
.expect("ok");
|
|
let err = t.insert(mk(45_000_000, 55_000_000, CardNetwork::Mastercard));
|
|
assert!(matches!(err, Err(Error::InvalidRange { .. })));
|
|
}
|
|
|
|
#[test]
|
|
fn adjacent_ranges_ok() {
|
|
// Half-open: [40M, 50M) and [50M, 60M) do NOT overlap.
|
|
let mut t = RangeTree::new();
|
|
t.insert(mk(40_000_000, 50_000_000, CardNetwork::Visa))
|
|
.expect("ok");
|
|
t.insert(mk(50_000_000, 60_000_000, CardNetwork::Mastercard))
|
|
.expect("ok");
|
|
assert_eq!(t.len(), 2);
|
|
}
|
|
}
|