The database module provides all persistence and caching functionality for monocle, organized into three sub-modules.
database/
βββ core/ # Foundation layer
β βββ connection # SQLite DatabaseConn wrapper
β βββ schema # SQLite schema definitions and management
β
βββ session/ # Temporary/session storage
β βββ msg_store # BGP message storage for search results (SQLite)
β
βββ monocle/ # Main monocle database (SQLite)
βββ asinfo # Unified AS information (from bgpkit-commons)
βββ as2rel # AS-level relationships
βββ rpki # RPKI ROA/ASPA data (blob-based prefix storage)
βββ pfx2as # Prefix-to-ASN mappings (blob-based prefix storage)
Monocle uses SQLite as its primary persistence layer:
- ASInfo mappings (unified AS information from bgpkit-commons)
- AS2Rel relationships (AS-level relationships)
- RPKI ROA/ASPA data (cached locally for fast queries and offline validation)
- Pfx2as prefix-to-ASN mappings (cached locally for fast lookups)
- Search result exports / session stores
Blob-based prefix storage: Both RPKI and Pfx2as store IP prefixes as 16-byte start/end address pairs (BLOBs). IPv4 addresses are converted to IPv6-mapped format for uniform storage. This enables efficient range lookups directly in SQLite without native INET/cidr operators.
The foundation layer providing database connections and schema management.
DatabaseConn- SQLite connection wrapper with configurationSchemaManager- SQLite schema managementSchemaStatus- Schema state enumerationSchemaDefinitions- SQL table definitions
Temporary storage for one-time operations like BGP message search results.
MsgStore- SQLite-backed storage for BGP elements
The main persistent database for monocle data.
MonocleDatabase- Main database interfaceAsinfoRepository- Unified AS information queries (from bgpkit-commons)As2relRepository- AS-level relationship queriesRpkiRepository- RPKI ROA/ASPA tables + local validation (blob-based prefix storage)Pfx2asRepository- Prefix-to-ASN mappings (blob-based prefix storage)
use monocle::database::{DatabaseConn, SchemaManager};
// Create in-memory database
let conn = DatabaseConn::open_in_memory()?;
// Or open persistent database
let conn = DatabaseConn::open_path("/path/to/monocle.sqlite3")?;
// Initialize schema
let manager = SchemaManager::new(&conn.conn);
manager.initialize()?;use monocle::database::MonocleDatabase;
// Open the monocle database
let db = MonocleDatabase::open_in_dir("~/.local/share/monocle")?;
// Bootstrap ASInfo data if needed
if db.needs_asinfo_refresh(Duration::from_secs(7 * 24 * 60 * 60)) {
let count = db.refresh_asinfo()?;
println!("Loaded {} ASes", count);
}
// Search for AS information
let results = db.asinfo().search_by_name("cloudflare")?;
for r in results {
println!("AS{}: {}", r.asn, r.name);
}
// Update AS2Rel data
use std::time::Duration;
let ttl = Duration::from_secs(24 * 60 * 60); // 24 hours
if db.needs_as2rel_refresh(ttl) {
let count = db.update_as2rel()?;
println!("Loaded {} relationships", count);
}
// Query relationships
let rels = db.as2rel().search_asn(13335)?;RPKI current data (ROAs and ASPAs) is stored in the monocle SQLite database and can be queried locally.
use monocle::database::{MonocleDatabase, RpkiRepository, DEFAULT_RPKI_CACHE_TTL};
let db = MonocleDatabase::open_in_dir("~/.local/share/monocle")?;
let rpki = db.rpki();
// Check metadata / whether refresh is needed
if rpki.needs_refresh(DEFAULT_RPKI_CACHE_TTL)? {
// Typically refreshed via CLI (`monocle database refresh rpki`) or higher-level lens logic.
// This example intentionally does not fetch from the network directly.
}
// Query ROAs by ASN
let roas = rpki.get_roas_by_asn(13335)?;
println!("Loaded {} ROAs for AS13335", roas.len());
// Validate prefix-ASN pair locally (RFC 6811-style)
let result = rpki.validate_detailed(13335, "1.1.1.0/24")?;
println!("{} {} -> {} ({})", result.prefix, result.asn, result.state, result.reason);use monocle::database::{MonocleDatabase, DEFAULT_PFX2AS_CACHE_TTL};
let db = MonocleDatabase::open_in_dir("~/.local/share/monocle")?;
let pfx2as = db.pfx2as();
// Check if refresh is needed
if pfx2as.needs_refresh(DEFAULT_PFX2AS_CACHE_TTL)? {
// Refresh via CLI: monocle config update --pfx2as
// Or via WebSocket: database.refresh with source: "pfx2as"
}
// Exact prefix match
let results = pfx2as.lookup_exact("1.1.1.0/24")?;
// Longest prefix match (most specific covering prefix)
let results = pfx2as.lookup_longest("1.1.1.1")?;
// Find all supernets (prefixes that cover the query)
let results = pfx2as.lookup_covering("1.1.1.0/24")?;
// Find all subnets (prefixes covered by the query)
let results = pfx2as.lookup_covered("1.0.0.0/8")?;
// Get all prefixes for an ASN
let results = pfx2as.get_prefixes_by_asn(13335)?;CREATE TABLE monocle_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
)CREATE TABLE asinfo (
asn INTEGER PRIMARY KEY,
name TEXT NOT NULL,
country TEXT,
source TEXT NOT NULL
)CREATE TABLE as2rel_meta (
id INTEGER PRIMARY KEY CHECK (id = 1),
file_url TEXT NOT NULL,
last_updated INTEGER NOT NULL,
max_peers_count INTEGER NOT NULL DEFAULT 0
)
CREATE TABLE as2rel (
asn1 INTEGER NOT NULL,
asn2 INTEGER NOT NULL,
paths_count INTEGER NOT NULL,
peers_count INTEGER NOT NULL,
rel INTEGER NOT NULL,
PRIMARY KEY (asn1, asn2, rel)
)CREATE TABLE pfx2as (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prefix TEXT NOT NULL,
prefix_len INTEGER NOT NULL,
prefix_start BLOB NOT NULL, -- 16-byte start address
prefix_end BLOB NOT NULL, -- 16-byte end address
asn INTEGER NOT NULL
)
CREATE INDEX idx_pfx2as_range ON pfx2as(prefix_start, prefix_end);
CREATE INDEX idx_pfx2as_asn ON pfx2as(asn);
CREATE INDEX idx_pfx2as_prefix ON pfx2as(prefix);CREATE TABLE rpki_roas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prefix TEXT NOT NULL,
prefix_len INTEGER NOT NULL,
prefix_start BLOB NOT NULL, -- 16-byte start address
prefix_end BLOB NOT NULL, -- 16-byte end address
max_length INTEGER NOT NULL,
origin_asn INTEGER NOT NULL,
ta TEXT NOT NULL
)
CREATE TABLE rpki_aspas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_asn INTEGER NOT NULL,
provider_asn INTEGER NOT NULL
)
CREATE INDEX idx_rpki_roas_range ON rpki_roas(prefix_start, prefix_end);
CREATE INDEX idx_rpki_roas_asn ON rpki_roas(origin_asn);Default TTL values:
| Cache Type | Default TTL | Constant |
|---|---|---|
| ASInfo | 7 days | DEFAULT_ASINFO_TTL |
| AS2Rel | 7 days | DEFAULT_AS2REL_TTL |
| RPKI (SQLite) | 24 hours | DEFAULT_RPKI_CACHE_TTL |
| Pfx2as (SQLite) | 24 hours | DEFAULT_PFX2AS_CACHE_TTL |
# Run all database tests
cargo test database::
# Run specific module tests
cargo test asinfo
cargo test as2rel
cargo test rpki
cargo test pfx2asIn-memory databases are useful for testing:
#[test]
fn test_my_feature() {
let db = MonocleDatabase::open_in_memory().unwrap();
// Test with in-memory database
}For temporary directory tests, use tempfile:
#[test]
fn test_with_temp_db() {
let temp_dir = tempfile::tempdir().unwrap();
let db = MonocleDatabase::open_in_dir(temp_dir.path().to_str().unwrap()).unwrap();
// Test with temporary database
}- Architecture Overview - System architecture
- Lens Module - Lens patterns and conventions
- DEVELOPMENT.md - Contributor guide
- Server README - WebSocket API (database.status, database.refresh)