562 lines
14 KiB
Markdown
562 lines
14 KiB
Markdown
# Database Schema Design
|
|
|
|
## Overview
|
|
|
|
The GigLez database stores RF signal captures with GPS coordinates, device signatures, and community contributions. The schema supports efficient querying by location, frequency, protocol, and device type.
|
|
|
|
## Core Tables
|
|
|
|
### 1. captures
|
|
|
|
Primary table storing raw RF signal captures with GPS attribution.
|
|
|
|
```sql
|
|
CREATE TABLE captures (
|
|
id SERIAL PRIMARY KEY,
|
|
session_id INTEGER REFERENCES sessions(id) ON DELETE CASCADE,
|
|
|
|
-- GPS Data
|
|
latitude DECIMAL(10, 8) NOT NULL,
|
|
longitude DECIMAL(11, 8) NOT NULL,
|
|
altitude DECIMAL(8, 2),
|
|
gps_accuracy DECIMAL(6, 2),
|
|
|
|
-- Timestamp
|
|
captured_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
|
|
-- RF Signal Data
|
|
frequency INTEGER NOT NULL, -- in Hz
|
|
rssi INTEGER, -- Received Signal Strength Indicator
|
|
modulation VARCHAR(50), -- OOK, 2FSK, etc.
|
|
preset VARCHAR(100),
|
|
|
|
-- Protocol Information (if decoded)
|
|
protocol VARCHAR(100),
|
|
bit_length INTEGER,
|
|
key_data BYTEA,
|
|
timing_element INTEGER, -- TE value in microseconds
|
|
|
|
-- Raw Signal Data
|
|
raw_data TEXT, -- RAW_Data timings
|
|
raw_format VARCHAR(20), -- 'RAW', 'BinRAW', 'KEY'
|
|
|
|
-- File Storage
|
|
file_path VARCHAR(500), -- Path to .sub file if stored separately
|
|
file_hash VARCHAR(64), -- SHA-256 hash for deduplication
|
|
|
|
-- Matching
|
|
device_id INTEGER REFERENCES devices(id),
|
|
match_confidence DECIMAL(5, 4), -- 0.0 to 1.0
|
|
match_method VARCHAR(50), -- 'auto', 'user', 'community'
|
|
|
|
-- Indexes for geospatial queries
|
|
CONSTRAINT valid_latitude CHECK (latitude >= -90 AND latitude <= 90),
|
|
CONSTRAINT valid_longitude CHECK (longitude >= -180 AND longitude <= 180)
|
|
);
|
|
|
|
CREATE INDEX idx_captures_location ON captures USING GIST (ll_to_earth(latitude, longitude));
|
|
CREATE INDEX idx_captures_frequency ON captures(frequency);
|
|
CREATE INDEX idx_captures_protocol ON captures(protocol);
|
|
CREATE INDEX idx_captures_timestamp ON captures(captured_at DESC);
|
|
CREATE INDEX idx_captures_session ON captures(session_id);
|
|
CREATE INDEX idx_captures_device ON captures(device_id);
|
|
CREATE INDEX idx_captures_hash ON captures(file_hash);
|
|
```
|
|
|
|
### 2. sessions
|
|
|
|
Wardriving/capture sessions to group related captures.
|
|
|
|
```sql
|
|
CREATE TABLE sessions (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER REFERENCES users(id),
|
|
|
|
-- Session Metadata
|
|
name VARCHAR(200),
|
|
description TEXT,
|
|
started_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
ended_at TIMESTAMP,
|
|
|
|
-- Privacy Settings
|
|
is_public BOOLEAN DEFAULT true,
|
|
anonymize_gps BOOLEAN DEFAULT false,
|
|
gps_precision_meters INTEGER DEFAULT 10,
|
|
|
|
-- Session Statistics (computed)
|
|
total_captures INTEGER DEFAULT 0,
|
|
unique_devices INTEGER DEFAULT 0,
|
|
distance_km DECIMAL(10, 2),
|
|
|
|
-- Bounding Box (for quick filtering)
|
|
min_latitude DECIMAL(10, 8),
|
|
max_latitude DECIMAL(10, 8),
|
|
min_longitude DECIMAL(11, 8),
|
|
max_longitude DECIMAL(11, 8)
|
|
);
|
|
|
|
CREATE INDEX idx_sessions_user ON sessions(user_id);
|
|
CREATE INDEX idx_sessions_started ON sessions(started_at DESC);
|
|
```
|
|
|
|
### 3. devices
|
|
|
|
Known IoT device types with signature information.
|
|
|
|
```sql
|
|
CREATE TABLE devices (
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
-- Device Identification
|
|
manufacturer VARCHAR(200),
|
|
model VARCHAR(200),
|
|
device_type VARCHAR(100), -- 'garage_door', 'weather_station', 'key_fob', etc.
|
|
description TEXT,
|
|
|
|
-- RF Characteristics
|
|
typical_frequency INTEGER, -- Most common frequency in Hz
|
|
frequency_range_low INTEGER,
|
|
frequency_range_high INTEGER,
|
|
modulation_types TEXT[], -- Array: ['OOK', '2FSK']
|
|
|
|
-- Protocol Information
|
|
protocol VARCHAR(100),
|
|
bit_length INTEGER,
|
|
encoding VARCHAR(50), -- 'PWM', 'PPM', 'Manchester', etc.
|
|
|
|
-- Metadata
|
|
fcc_id VARCHAR(50),
|
|
manufacturer_code VARCHAR(50), -- For protocols like KeeLoq
|
|
|
|
-- Community Data
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
verified BOOLEAN DEFAULT false,
|
|
verification_count INTEGER DEFAULT 0,
|
|
|
|
-- Source
|
|
source VARCHAR(50), -- 'flipper', 'rtl433', 'urh', 'community'
|
|
source_url TEXT
|
|
);
|
|
|
|
CREATE INDEX idx_devices_manufacturer ON devices(manufacturer);
|
|
CREATE INDEX idx_devices_type ON devices(device_type);
|
|
CREATE INDEX idx_devices_frequency ON devices(typical_frequency);
|
|
CREATE INDEX idx_devices_protocol ON devices(protocol);
|
|
```
|
|
|
|
### 4. signatures
|
|
|
|
Protocol signatures for device matching.
|
|
|
|
```sql
|
|
CREATE TABLE signatures (
|
|
id SERIAL PRIMARY KEY,
|
|
device_id INTEGER REFERENCES devices(id) ON DELETE CASCADE,
|
|
|
|
-- Signature Pattern
|
|
protocol VARCHAR(100) NOT NULL,
|
|
frequency INTEGER,
|
|
modulation VARCHAR(50),
|
|
|
|
-- Matching Criteria
|
|
bit_pattern BYTEA,
|
|
bit_mask BYTEA, -- Which bits to match (1=match, 0=ignore)
|
|
timing_min INTEGER, -- TE range in microseconds
|
|
timing_max INTEGER,
|
|
|
|
-- Raw Pattern (for regex-like matching)
|
|
pattern_regex TEXT,
|
|
|
|
-- Confidence Weighting
|
|
weight DECIMAL(4, 3) DEFAULT 1.0, -- Higher = more reliable signature
|
|
|
|
-- Metadata
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
source VARCHAR(50),
|
|
source_file VARCHAR(500), -- Original .sub or config file
|
|
|
|
UNIQUE(device_id, protocol, bit_pattern)
|
|
);
|
|
|
|
CREATE INDEX idx_signatures_device ON signatures(device_id);
|
|
CREATE INDEX idx_signatures_protocol ON signatures(protocol);
|
|
CREATE INDEX idx_signatures_frequency ON signatures(frequency);
|
|
```
|
|
|
|
### 5. users
|
|
|
|
User accounts for community features.
|
|
|
|
```sql
|
|
CREATE TABLE users (
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
-- Authentication
|
|
username VARCHAR(50) UNIQUE NOT NULL,
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
|
|
-- Profile
|
|
display_name VARCHAR(100),
|
|
avatar_url TEXT,
|
|
bio TEXT,
|
|
|
|
-- Statistics
|
|
total_captures INTEGER DEFAULT 0,
|
|
total_identifications INTEGER DEFAULT 0,
|
|
reputation_score INTEGER DEFAULT 0,
|
|
|
|
-- Settings
|
|
api_key VARCHAR(64) UNIQUE,
|
|
email_verified BOOLEAN DEFAULT false,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
last_login TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_users_username ON users(username);
|
|
CREATE INDEX idx_users_email ON users(email);
|
|
CREATE INDEX idx_users_api_key ON users(api_key);
|
|
```
|
|
|
|
### 6. identifications
|
|
|
|
User-submitted device identifications (with photos).
|
|
|
|
```sql
|
|
CREATE TABLE identifications (
|
|
id SERIAL PRIMARY KEY,
|
|
capture_id INTEGER REFERENCES captures(id) ON DELETE CASCADE,
|
|
device_id INTEGER REFERENCES devices(id),
|
|
user_id INTEGER REFERENCES users(id),
|
|
|
|
-- Identification Details
|
|
confidence VARCHAR(20), -- 'certain', 'likely', 'guess'
|
|
notes TEXT,
|
|
|
|
-- Visual Evidence
|
|
photo_urls TEXT[], -- Array of image URLs
|
|
|
|
-- Community Validation
|
|
upvotes INTEGER DEFAULT 0,
|
|
downvotes INTEGER DEFAULT 0,
|
|
verified BOOLEAN DEFAULT false,
|
|
verified_by INTEGER REFERENCES users(id),
|
|
verified_at TIMESTAMP,
|
|
|
|
-- Timestamps
|
|
submitted_at TIMESTAMP DEFAULT NOW(),
|
|
|
|
UNIQUE(capture_id, user_id, device_id)
|
|
);
|
|
|
|
CREATE INDEX idx_identifications_capture ON identifications(capture_id);
|
|
CREATE INDEX idx_identifications_device ON identifications(device_id);
|
|
CREATE INDEX idx_identifications_user ON identifications(user_id);
|
|
```
|
|
|
|
### 7. votes
|
|
|
|
Voting on device identifications.
|
|
|
|
```sql
|
|
CREATE TABLE votes (
|
|
id SERIAL PRIMARY KEY,
|
|
identification_id INTEGER REFERENCES identifications(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id),
|
|
|
|
vote_type INTEGER NOT NULL, -- 1 = upvote, -1 = downvote
|
|
voted_at TIMESTAMP DEFAULT NOW(),
|
|
|
|
UNIQUE(identification_id, user_id)
|
|
);
|
|
|
|
CREATE INDEX idx_votes_identification ON votes(identification_id);
|
|
CREATE INDEX idx_votes_user ON votes(user_id);
|
|
```
|
|
|
|
### 8. flipper_signatures
|
|
|
|
Imported Flipper Zero .sub file signatures.
|
|
|
|
```sql
|
|
CREATE TABLE flipper_signatures (
|
|
id SERIAL PRIMARY KEY,
|
|
signature_id INTEGER REFERENCES signatures(id) ON DELETE CASCADE,
|
|
|
|
-- Flipper-Specific Fields
|
|
filetype VARCHAR(50), -- 'Flipper SubGhz Key File' or 'RAW File'
|
|
version INTEGER,
|
|
preset VARCHAR(100),
|
|
|
|
-- Custom Preset Data
|
|
custom_preset_module VARCHAR(50),
|
|
custom_preset_data BYTEA,
|
|
|
|
-- Protocol Data
|
|
protocol VARCHAR(100),
|
|
bit INTEGER,
|
|
key BYTEA,
|
|
te INTEGER, -- Timing element
|
|
|
|
-- RAW Data
|
|
raw_data TEXT,
|
|
bin_raw_bit INTEGER,
|
|
bin_raw_te INTEGER,
|
|
bin_raw_data BYTEA,
|
|
|
|
-- Source
|
|
source_file VARCHAR(500),
|
|
imported_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_flipper_protocol ON flipper_signatures(protocol);
|
|
CREATE INDEX idx_flipper_preset ON flipper_signatures(preset);
|
|
```
|
|
|
|
### 9. rtl433_protocols
|
|
|
|
Imported RTL_433 protocol definitions.
|
|
|
|
```sql
|
|
CREATE TABLE rtl433_protocols (
|
|
id SERIAL PRIMARY KEY,
|
|
signature_id INTEGER REFERENCES signatures(id) ON DELETE CASCADE,
|
|
|
|
-- Protocol Identification
|
|
protocol_number INTEGER,
|
|
protocol_name VARCHAR(200),
|
|
model VARCHAR(200),
|
|
|
|
-- RF Characteristics
|
|
frequency INTEGER,
|
|
modulation VARCHAR(50), -- 'OOK_PWM', 'FSK_PCM', etc.
|
|
|
|
-- Timing Information (in microseconds)
|
|
short_width INTEGER,
|
|
long_width INTEGER,
|
|
reset_limit INTEGER,
|
|
gap_limit INTEGER,
|
|
|
|
-- Decoding
|
|
decoder_type VARCHAR(50),
|
|
bit_count INTEGER,
|
|
|
|
-- JSON Fields Mapping
|
|
json_fields JSONB, -- Expected output fields
|
|
|
|
-- Source
|
|
source_file VARCHAR(500),
|
|
imported_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_rtl433_protocol_num ON rtl433_protocols(protocol_number);
|
|
CREATE INDEX idx_rtl433_model ON rtl433_protocols(model);
|
|
CREATE INDEX idx_rtl433_modulation ON rtl433_protocols(modulation);
|
|
```
|
|
|
|
## Materialized Views
|
|
|
|
### device_statistics
|
|
|
|
Pre-computed statistics for device types.
|
|
|
|
```sql
|
|
CREATE MATERIALIZED VIEW device_statistics AS
|
|
SELECT
|
|
d.id as device_id,
|
|
d.manufacturer,
|
|
d.model,
|
|
COUNT(c.id) as total_captures,
|
|
COUNT(DISTINCT c.session_id) as total_sessions,
|
|
MIN(c.captured_at) as first_seen,
|
|
MAX(c.captured_at) as last_seen,
|
|
AVG(c.rssi) as avg_rssi,
|
|
ST_Collect(ST_MakePoint(c.longitude, c.latitude)) as capture_locations
|
|
FROM devices d
|
|
LEFT JOIN captures c ON c.device_id = d.id
|
|
GROUP BY d.id, d.manufacturer, d.model;
|
|
|
|
CREATE INDEX idx_device_stats_device ON device_statistics(device_id);
|
|
```
|
|
|
|
### geographic_heatmap
|
|
|
|
Aggregated capture density for mapping.
|
|
|
|
```sql
|
|
CREATE MATERIALIZED VIEW geographic_heatmap AS
|
|
SELECT
|
|
ROUND(latitude::numeric, 3) as lat_bucket,
|
|
ROUND(longitude::numeric, 3) as lon_bucket,
|
|
COUNT(*) as capture_count,
|
|
COUNT(DISTINCT device_id) as unique_devices,
|
|
array_agg(DISTINCT protocol) as protocols_seen
|
|
FROM captures
|
|
WHERE device_id IS NOT NULL
|
|
GROUP BY lat_bucket, lon_bucket;
|
|
|
|
CREATE INDEX idx_heatmap_location ON geographic_heatmap(lat_bucket, lon_bucket);
|
|
```
|
|
|
|
## Functions
|
|
|
|
### calculate_distance
|
|
|
|
Calculate distance between two GPS coordinates.
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION calculate_distance(
|
|
lat1 DECIMAL, lon1 DECIMAL,
|
|
lat2 DECIMAL, lon2 DECIMAL
|
|
) RETURNS DECIMAL AS $$
|
|
DECLARE
|
|
R DECIMAL := 6371.0; -- Earth radius in km
|
|
dLat DECIMAL;
|
|
dLon DECIMAL;
|
|
a DECIMAL;
|
|
c DECIMAL;
|
|
BEGIN
|
|
dLat := radians(lat2 - lat1);
|
|
dLon := radians(lon2 - lon1);
|
|
a := sin(dLat/2) * sin(dLat/2) +
|
|
cos(radians(lat1)) * cos(radians(lat2)) *
|
|
sin(dLon/2) * sin(dLon/2);
|
|
c := 2 * atan2(sqrt(a), sqrt(1-a));
|
|
RETURN R * c;
|
|
END;
|
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
```
|
|
|
|
### match_signature
|
|
|
|
Match a capture against all known signatures.
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION match_signature(
|
|
p_capture_id INTEGER
|
|
) RETURNS TABLE(device_id INTEGER, confidence DECIMAL) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
s.device_id,
|
|
CASE
|
|
WHEN c.protocol = s.protocol
|
|
AND c.frequency = s.frequency
|
|
AND c.bit_length = s.bit_length THEN 1.0
|
|
WHEN c.protocol = s.protocol
|
|
AND c.frequency = s.frequency THEN 0.8
|
|
WHEN c.protocol = s.protocol THEN 0.5
|
|
ELSE 0.0
|
|
END as confidence
|
|
FROM captures c
|
|
CROSS JOIN signatures s
|
|
WHERE c.id = p_capture_id
|
|
AND c.protocol IS NOT NULL
|
|
ORDER BY confidence DESC
|
|
LIMIT 10;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
## Triggers
|
|
|
|
### update_session_statistics
|
|
|
|
Automatically update session statistics when captures are added.
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION update_session_stats()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE sessions SET
|
|
total_captures = (SELECT COUNT(*) FROM captures WHERE session_id = NEW.session_id),
|
|
unique_devices = (SELECT COUNT(DISTINCT device_id) FROM captures WHERE session_id = NEW.session_id),
|
|
min_latitude = LEAST(min_latitude, NEW.latitude),
|
|
max_latitude = GREATEST(max_latitude, NEW.latitude),
|
|
min_longitude = LEAST(min_longitude, NEW.longitude),
|
|
max_longitude = GREATEST(max_longitude, NEW.longitude)
|
|
WHERE id = NEW.session_id;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trigger_update_session_stats
|
|
AFTER INSERT ON captures
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_session_stats();
|
|
```
|
|
|
|
### update_device_verification
|
|
|
|
Update device verification status based on identification votes.
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION update_device_verification()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
net_votes INTEGER;
|
|
BEGIN
|
|
SELECT (upvotes - downvotes) INTO net_votes
|
|
FROM identifications
|
|
WHERE id = NEW.identification_id;
|
|
|
|
IF net_votes >= 5 THEN
|
|
UPDATE identifications SET verified = true
|
|
WHERE id = NEW.identification_id;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trigger_update_verification
|
|
AFTER INSERT OR UPDATE ON votes
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_device_verification();
|
|
```
|
|
|
|
## Sample Queries
|
|
|
|
### Find all captures near a location
|
|
|
|
```sql
|
|
SELECT c.*, d.manufacturer, d.model
|
|
FROM captures c
|
|
LEFT JOIN devices d ON c.device_id = d.id
|
|
WHERE calculate_distance(c.latitude, c.longitude, 40.7128, -74.0060) <= 1.0 -- Within 1km
|
|
ORDER BY c.captured_at DESC;
|
|
```
|
|
|
|
### Get device density heatmap
|
|
|
|
```sql
|
|
SELECT lat_bucket, lon_bucket, capture_count, unique_devices
|
|
FROM geographic_heatmap
|
|
WHERE capture_count > 5
|
|
ORDER BY capture_count DESC;
|
|
```
|
|
|
|
### Find unidentified captures
|
|
|
|
```sql
|
|
SELECT c.id, c.frequency, c.protocol, c.latitude, c.longitude, c.captured_at
|
|
FROM captures c
|
|
WHERE c.device_id IS NULL
|
|
AND c.protocol IS NOT NULL
|
|
ORDER BY c.captured_at DESC
|
|
LIMIT 100;
|
|
```
|
|
|
|
### Top device types by capture count
|
|
|
|
```sql
|
|
SELECT d.manufacturer, d.model, d.device_type, COUNT(c.id) as captures
|
|
FROM devices d
|
|
JOIN captures c ON c.device_id = d.id
|
|
GROUP BY d.id, d.manufacturer, d.model, d.device_type
|
|
ORDER BY captures DESC
|
|
LIMIT 20;
|
|
```
|