# Unit 01 — Database Layer ## Goal SQLite persistence layer with CRUD operations and MAC-address-based rate limiting. ## Files - `server/database.py` - `server/models.py` --- ## `server/models.py` ```python from dataclasses import dataclass from datetime import datetime from typing import Optional @dataclass class Subscriber: id: Optional[int] imsi: str iccid: str ki: str # hex string opc: str # hex string mac_address: Optional[str] created_at: datetime synced_to_core: bool ``` --- ## `server/database.py` ### Schema ```sql CREATE TABLE IF NOT EXISTS subscribers ( id INTEGER PRIMARY KEY AUTOINCREMENT, imsi TEXT UNIQUE NOT NULL, iccid TEXT UNIQUE NOT NULL, ki TEXT NOT NULL, opc TEXT NOT NULL, mac_address TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, synced_to_core BOOLEAN DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_imsi ON subscribers(imsi); CREATE INDEX IF NOT EXISTS idx_mac_address ON subscribers(mac_address); ``` ### Functions | Function | Signature | Returns | Notes | |----------|-----------|---------|-------| | `init_db` | `(db_path: str) -> None` | — | Creates schema idempotently | | `get_connection` | `(db_path: str) -> sqlite3.Connection` | connection | `row_factory = sqlite3.Row` | | `add_subscriber` | `(conn, imsi, iccid, ki, opc, mac_address) -> int` | row `id` | Raises `ValueError` on duplicate IMSI/ICCID | | `get_subscriber_by_imsi` | `(conn, imsi: str) -> Optional[Subscriber]` | Subscriber or None | | | `get_subscriber_by_id` | `(conn, sub_id: int) -> Optional[Subscriber]` | Subscriber or None | | | `list_unsynced` | `(conn) -> list[Subscriber]` | list | WHERE synced_to_core = 0 | | `mark_synced` | `(conn, imsi: str) -> None` | — | UPDATE synced_to_core = 1 | | `check_rate_limit` | `(conn, mac_address: str, window_hours: int) -> bool` | True = allowed | Counts rows for mac in last N hours | | `export_all` | `(conn) -> list[dict]` | list of dicts | All rows as serializable dicts | ### Rate Limiting Logic ```python def check_rate_limit(conn, mac_address, window_hours): cutoff = datetime.utcnow() - timedelta(hours=window_hours) cur = conn.execute( "SELECT COUNT(*) FROM subscribers WHERE mac_address = ? AND created_at > ?", (mac_address, cutoff.isoformat()) ) count = cur.fetchone()[0] return count == 0 # True = allowed (no prior provisioning in window) ``` ## Verification ```bash python -c " from server.database import init_db, get_connection, add_subscriber, check_rate_limit import tempfile, os db = tempfile.mktemp(suffix='.db') init_db(db) conn = get_connection(db) sid = add_subscriber(conn, '302720000000001', '8910302720000000001', 'aa'*16, 'bb'*16, 'aa:bb:cc:dd:ee:ff') print('inserted id:', sid) print('rate limit (should be False now):', check_rate_limit(conn, 'aa:bb:cc:dd:ee:ff', 1)) conn.close(); os.unlink(db) " ```