mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-30 02:19:28 -07:00
requests addressed, better error handling, more logging, small text corrections
This commit is contained in:
committed by
Will Greenberg
parent
ba78c7bd01
commit
5a4a3034be
@@ -167,15 +167,22 @@ impl DiagTask {
|
||||
if self.gps_mode == GpsMode::Fixed
|
||||
&& let Some((lat, lon)) = self.gps_fixed_coords
|
||||
&& let Some((entry_idx, _)) = qmdl_store.get_current_entry()
|
||||
&& let Ok(mut gps_file) = qmdl_store.open_entry_gps_for_append(entry_idx).await
|
||||
{
|
||||
let record = GpsRecord {
|
||||
unix_ts: 0,
|
||||
lat,
|
||||
lon,
|
||||
};
|
||||
if let Ok(json) = serde_json::to_string(&record) {
|
||||
let _ = gps_file.write_all(format!("{json}\n").as_bytes()).await;
|
||||
match qmdl_store.open_entry_gps_for_append(entry_idx).await {
|
||||
Ok(Some(mut gps_file)) => {
|
||||
let record = GpsRecord {
|
||||
unix_ts: 0,
|
||||
lat,
|
||||
lon,
|
||||
};
|
||||
if let Ok(json) = serde_json::to_string(&record) {
|
||||
let _ = gps_file.write_all(format!("{json}\n").as_bytes()).await;
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
error!("GPS sidecar directory not found, cannot write fixed-mode coordinates")
|
||||
}
|
||||
Err(e) => error!("failed to open GPS sidecar for fixed-mode entry: {e}"),
|
||||
}
|
||||
}
|
||||
self.stop_current_recording().await;
|
||||
|
||||
@@ -2,7 +2,7 @@ use axum::Json;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use chrono::Utc;
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
@@ -47,14 +47,22 @@ pub struct GpsRecord {
|
||||
pub lon: f64,
|
||||
}
|
||||
|
||||
/// Reads all GPS records from a sidecar NDJSON file, skipping malformed lines.
|
||||
/// Reads all GPS records from a sidecar NDJSON file, logging and skipping malformed lines.
|
||||
pub async fn load_gps_records(file: tokio::fs::File) -> Vec<GpsRecord> {
|
||||
let reader = BufReader::new(file);
|
||||
let mut lines = reader.lines();
|
||||
let mut records = Vec::new();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
if let Ok(record) = serde_json::from_str::<GpsRecord>(&line) {
|
||||
records.push(record);
|
||||
loop {
|
||||
match lines.next_line().await {
|
||||
Ok(Some(line)) => match serde_json::from_str::<GpsRecord>(&line) {
|
||||
Ok(record) => records.push(record),
|
||||
Err(e) => warn!("skipping malformed GPS sidecar line: {e}"),
|
||||
},
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
error!("error reading GPS sidecar file: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
records
|
||||
@@ -67,7 +75,8 @@ pub async fn post_gps(
|
||||
if state.config.gps_mode != GpsMode::Api {
|
||||
return Err((
|
||||
StatusCode::FORBIDDEN,
|
||||
"GPS API endpoint is disabled. Set gps_mode to 2 in configuration.".to_string(),
|
||||
"GPS API endpoint is disabled. Set gps_mode to API endpoint in configuration."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
let mut gps = state.gps_state.write().await;
|
||||
@@ -75,21 +84,38 @@ pub async fn post_gps(
|
||||
drop(gps);
|
||||
|
||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||
if let Some((entry_idx, _)) = qmdl_store.get_current_entry()
|
||||
&& let Ok(mut file) = qmdl_store.open_entry_gps_for_append(entry_idx).await
|
||||
{
|
||||
let record = GpsRecord {
|
||||
unix_ts: Utc::now().timestamp(),
|
||||
lat: gps_data.latitude,
|
||||
lon: gps_data.longitude,
|
||||
};
|
||||
match serde_json::to_string(&record) {
|
||||
Ok(json) => {
|
||||
if let Err(e) = file.write_all(format!("{json}\n").as_bytes()).await {
|
||||
error!("failed to write GPS record to sidecar: {e}");
|
||||
}
|
||||
if let Some((entry_idx, _)) = qmdl_store.get_current_entry() {
|
||||
match qmdl_store.open_entry_gps_for_append(entry_idx).await {
|
||||
Ok(Some(mut file)) => {
|
||||
let record = GpsRecord {
|
||||
unix_ts: Utc::now().timestamp(),
|
||||
lat: gps_data.latitude,
|
||||
lon: gps_data.longitude,
|
||||
};
|
||||
let json = serde_json::to_string(&record).map_err(|e| {
|
||||
error!("failed to serialize GPS record: {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("failed to serialize GPS record: {e}"),
|
||||
)
|
||||
})?;
|
||||
file.write_all(format!("{json}\n").as_bytes())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("failed to write GPS record to sidecar: {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("failed to write GPS record to sidecar: {e}"),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(None) => error!("GPS sidecar directory not found, cannot write GPS record"),
|
||||
Err(e) => {
|
||||
return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("failed to open GPS sidecar: {e}"),
|
||||
));
|
||||
}
|
||||
Err(e) => error!("failed to serialize GPS record: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -306,6 +306,8 @@ async fn run_with_config(
|
||||
config.webdav.clone().into(),
|
||||
);
|
||||
}
|
||||
// For fixed configuration, we use timestamp 0 to not break other
|
||||
// the GET request for GPS but user won't see the 0 in PCAPs
|
||||
let initial_gps = if config.gps_mode == GpsMode::Fixed {
|
||||
match (config.gps_fixed_latitude, config.gps_fixed_longitude) {
|
||||
(Some(lat), Some(lon)) => Some(gps::GpsData {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::config::GpsMode;
|
||||
use crate::gps::{GpsRecord, load_gps_records};
|
||||
use crate::server::ServerState;
|
||||
|
||||
use crate::config::GpsMode;
|
||||
use anyhow::Error;
|
||||
use axum::body::Body;
|
||||
use axum::extract::{Path, State};
|
||||
@@ -74,41 +74,27 @@ pub(crate) async fn load_gps_records_for_entry(
|
||||
state: &Arc<ServerState>,
|
||||
entry_index: usize,
|
||||
) -> Vec<GpsRecord> {
|
||||
// Always try the per-session sidecar first — it reflects what was actually
|
||||
// recorded regardless of what the current gps_mode config is.
|
||||
let entry_gps_mode;
|
||||
{
|
||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||
if let Ok(file) = qmdl_store.open_entry_gps(entry_index).await {
|
||||
let records = load_gps_records(file).await;
|
||||
if !records.is_empty() {
|
||||
return records;
|
||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||
match qmdl_store.open_entry_gps(entry_index).await {
|
||||
Ok(Some(file)) => load_gps_records(file).await,
|
||||
Ok(None) => {
|
||||
let gps_mode = qmdl_store
|
||||
.manifest
|
||||
.entries
|
||||
.get(entry_index)
|
||||
.and_then(|e| e.gps_mode);
|
||||
if gps_mode.is_some_and(|m| m != GpsMode::Disabled) {
|
||||
error!(
|
||||
"GPS sidecar expected for entry {entry_index} (mode: {gps_mode:?}) but not found"
|
||||
);
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
Err(e) => {
|
||||
error!("failed to open GPS sidecar: {e}");
|
||||
vec![]
|
||||
}
|
||||
// Capture the entry's recorded GPS mode before releasing the lock.
|
||||
entry_gps_mode = qmdl_store
|
||||
.manifest
|
||||
.entries
|
||||
.get(entry_index)
|
||||
.and_then(|e| e.gps_mode);
|
||||
}
|
||||
// Sidecar missing or empty — fall back using the entry's own recorded GPS mode,
|
||||
// not the current config, so old fixed-mode sessions still get coordinates even
|
||||
// if the mode has since been changed. Use the configured fixed coords directly
|
||||
// rather than gps_state, which can be overwritten by API calls or be None.
|
||||
if entry_gps_mode == Some(GpsMode::Fixed)
|
||||
&& let (Some(lat), Some(lon)) = (
|
||||
state.config.gps_fixed_latitude,
|
||||
state.config.gps_fixed_longitude,
|
||||
)
|
||||
{
|
||||
return vec![GpsRecord {
|
||||
unix_ts: 0,
|
||||
lat,
|
||||
lon,
|
||||
}];
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: i64) -> Option<GpsPoint> {
|
||||
|
||||
@@ -304,24 +304,33 @@ impl RecordingStore {
|
||||
.map_err(RecordingStoreError::ReadFileError)
|
||||
}
|
||||
|
||||
pub async fn open_entry_gps(&self, entry_index: usize) -> Result<File, RecordingStoreError> {
|
||||
pub async fn open_entry_gps(
|
||||
&self,
|
||||
entry_index: usize,
|
||||
) -> Result<Option<File>, RecordingStoreError> {
|
||||
let entry = &self.manifest.entries[entry_index];
|
||||
File::open(entry.get_gps_filepath(&self.path))
|
||||
.await
|
||||
.map_err(RecordingStoreError::ReadFileError)
|
||||
match File::open(entry.get_gps_filepath(&self.path)).await {
|
||||
Ok(file) => Ok(Some(file)),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
|
||||
Err(e) => Err(RecordingStoreError::ReadFileError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_entry_gps_for_append(
|
||||
&self,
|
||||
entry_index: usize,
|
||||
) -> Result<File, RecordingStoreError> {
|
||||
) -> Result<Option<File>, RecordingStoreError> {
|
||||
let entry = &self.manifest.entries[entry_index];
|
||||
OpenOptions::new()
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(entry.get_gps_filepath(&self.path))
|
||||
.await
|
||||
.map_err(RecordingStoreError::CreateFileError)
|
||||
{
|
||||
Ok(file) => Ok(Some(file)),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
|
||||
Err(e) => Err(RecordingStoreError::CreateFileError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clear_and_open_entry_analysis(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { type ReportMetadata } from '$lib/analysis.svelte';
|
||||
import type { ManifestEntry } from '$lib/manifest.svelte';
|
||||
import { GpsMode } from '$lib/utils.svelte';
|
||||
import { AnalysisManager } from '$lib/analysisManager.svelte';
|
||||
import AnalysisTable from './AnalysisTable.svelte';
|
||||
import ReAnalyzeButton from './ReAnalyzeButton.svelte';
|
||||
@@ -72,7 +73,7 @@
|
||||
{/if}
|
||||
<p>
|
||||
<b>GPS Mode:</b>
|
||||
{(entry.gps_mode ?? 0) === 0 ? 'Disabled' : entry.gps_mode === 1 ? 'Fixed coordinates' : 'API endpoint'}
|
||||
{(entry.gps_mode ?? GpsMode.Disabled) === GpsMode.Disabled ? 'Disabled' : entry.gps_mode === GpsMode.Fixed ? 'Fixed coordinates' : 'API endpoint'}
|
||||
</p>
|
||||
</div>
|
||||
{#if metadata && metadata.analyzers}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
test_notification,
|
||||
get_wifi_status,
|
||||
scan_wifi_networks,
|
||||
GpsMode,
|
||||
type Config,
|
||||
type WifiStatus,
|
||||
type WifiNetwork,
|
||||
@@ -785,21 +786,21 @@
|
||||
<div>
|
||||
<label for="gps_mode" class="block text-sm font-medium text-gray-700 mb-1">GPS Mode</label>
|
||||
<select id="gps_mode" bind:value={config.gps_mode} class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue">
|
||||
<option value={0}>Disabled</option>
|
||||
<option value={1}>Fixed coordinates</option>
|
||||
<option value={2}>API endpoint</option>
|
||||
<option value={GpsMode.Disabled}>Disabled</option>
|
||||
<option value={GpsMode.Fixed}>Fixed coordinates</option>
|
||||
<option value={GpsMode.Api}>API endpoint</option>
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{#if config.gps_mode === 2}
|
||||
{#if config.gps_mode === GpsMode.Api}
|
||||
POST latitude, longitude, and timestamp to <code>/api/gps</code> from any device on the network.
|
||||
{:else if config.gps_mode === 1}
|
||||
{:else if config.gps_mode === GpsMode.Fixed}
|
||||
GPS coordinates are fixed to the values below.
|
||||
{:else}
|
||||
GPS is disabled; no coordinates will be tracked.
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{#if config.gps_mode === 1}
|
||||
{#if config.gps_mode === GpsMode.Fixed}
|
||||
<div>
|
||||
<label for="gps_fixed_latitude" class="block text-sm font-medium text-gray-700 mb-1">Fixed Latitude</label>
|
||||
<input id="gps_fixed_latitude" type="number" min="-90" max="90" step="any" required
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ManifestEntry } from '$lib/manifest.svelte';
|
||||
import { GpsMode } from '$lib/utils.svelte';
|
||||
import { AnalysisManager } from '$lib/analysisManager.svelte';
|
||||
import DownloadLink from '$lib/components/DownloadLink.svelte';
|
||||
import DeleteButton from '$lib/components/DeleteButton.svelte';
|
||||
@@ -88,7 +89,7 @@
|
||||
{/if}
|
||||
{#if entry.gps_mode !== undefined}
|
||||
<div class="text-sm text-gray-500">
|
||||
GPS: {entry.gps_mode === 0 ? 'Disabled' : entry.gps_mode === 1 ? 'Fixed coordinates' : 'API endpoint'}
|
||||
GPS: {entry.gps_mode === GpsMode.Disabled ? 'Disabled' : entry.gps_mode === GpsMode.Fixed ? 'Fixed coordinates' : 'API endpoint'}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row justify-between lg:justify-end gap-1 mt-2 overflow-x-auto">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { get_report, type AnalysisReport } from './analysis.svelte';
|
||||
import { AnalysisStatus, type AnalysisManager } from './analysisManager.svelte';
|
||||
import { GpsMode } from './utils.svelte';
|
||||
|
||||
interface JsonManifest {
|
||||
entries: JsonManifestEntry[];
|
||||
@@ -13,7 +14,7 @@ interface JsonManifestEntry {
|
||||
qmdl_size_bytes: number;
|
||||
stop_reason: string | null;
|
||||
upload_time: string | null;
|
||||
gps_mode: number | null;
|
||||
gps_mode: GpsMode | null;
|
||||
}
|
||||
|
||||
export class Manifest {
|
||||
@@ -62,7 +63,7 @@ export class ManifestEntry {
|
||||
public analysis_report: AnalysisReport | string | undefined = $state(undefined);
|
||||
public stop_reason: string | undefined = $state(undefined);
|
||||
public upload_time: Date | undefined = $state(undefined);
|
||||
public gps_mode: number | undefined = $state(undefined);
|
||||
public gps_mode: GpsMode | undefined = $state(undefined);
|
||||
|
||||
constructor(json: JsonManifestEntry) {
|
||||
this.name = json.name;
|
||||
|
||||
@@ -28,6 +28,13 @@ export interface WebdavConfig {
|
||||
delete_on_upload: boolean;
|
||||
}
|
||||
|
||||
export enum GpsMode {
|
||||
Disabled = 0,
|
||||
Fixed = 1,
|
||||
Api = 2,
|
||||
}
|
||||
|
||||
|
||||
export interface Config {
|
||||
device: string;
|
||||
ui_level: number;
|
||||
@@ -46,7 +53,7 @@ export interface Config {
|
||||
firewall_restrict_outbound: boolean;
|
||||
firewall_allowed_ports: number[] | null;
|
||||
webdav: WebdavConfig;
|
||||
gps_mode: number;
|
||||
gps_mode: GpsMode;
|
||||
gps_fixed_latitude: number | null;
|
||||
gps_fixed_longitude: number | null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ManifestEntry } from '$lib/manifest.svelte';
|
||||
import { get_manifest, get_system_stats, get_gps, get_config, type GpsData } from '$lib/utils.svelte';
|
||||
import { get_manifest, get_system_stats, get_gps, get_config, GpsMode, type GpsData } from '$lib/utils.svelte';
|
||||
import ManifestTable from '$lib/components/ManifestTable.svelte';
|
||||
import Card from '$lib/components/ManifestCard.svelte';
|
||||
import type { SystemStats } from '$lib/systemStats';
|
||||
@@ -23,7 +23,7 @@
|
||||
let logview_shown: boolean = $state(false);
|
||||
let config_shown: boolean = $state(false);
|
||||
let gps_data: GpsData | null = $state(null);
|
||||
let gps_mode: number = $state(0);
|
||||
let gps_mode: GpsMode = $state(GpsMode.Disabled);
|
||||
$effect(() => {
|
||||
get_config().then((c) => {
|
||||
gps_mode = c.gps_mode;
|
||||
@@ -290,7 +290,7 @@
|
||||
{/if}
|
||||
<SystemStatsTable stats={system_stats!} />
|
||||
</div>
|
||||
{#if gps_mode !== 0}
|
||||
{#if gps_mode !== GpsMode.Disabled}
|
||||
<div class="bg-white border border-gray-200 drop-shadow rounded-md p-4 flex flex-col gap-2">
|
||||
<span class="text-lg font-semibold flex flex-row items-center gap-2">
|
||||
<svg class="w-5 h-5 text-rayhunter-blue" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
Reference in New Issue
Block a user