mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-30 08:29:27 -07:00
added documentation and polishing UI around GPS mode
This commit is contained in:
committed by
Will Greenberg
parent
66f0c2a336
commit
5451e23293
@@ -162,49 +162,6 @@ fn resolve_bin(name: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn wifi_config(&self) -> wifi_station::WifiConfig {
|
||||
let (wpa_bin, hostapd_conf, ctrl_interface) = match self.device {
|
||||
Device::Tmobile | Device::Wingtech => (
|
||||
Some("/usr/sbin/wpa_supplicant".into()),
|
||||
Some("/data/configs/hostapd.conf".into()),
|
||||
None,
|
||||
),
|
||||
Device::Uz801 => (
|
||||
Some("/system/bin/wpa_supplicant".into()),
|
||||
Some("/data/misc/wifi/hostapd.conf".into()),
|
||||
Some("/data/misc/wifi/sockets".into()),
|
||||
),
|
||||
_ => (None, None, None),
|
||||
};
|
||||
wifi_station::WifiConfig {
|
||||
wifi_enabled: self.wifi_enabled,
|
||||
dns_servers: self.dns_servers.clone(),
|
||||
wifi_ssid: self.wifi_ssid.clone(),
|
||||
wifi_password: self.wifi_password.clone(),
|
||||
security_type: self.wifi_security,
|
||||
wpa_supplicant_bin: wpa_bin.or_else(|| resolve_bin("wpa_supplicant")),
|
||||
hostapd_conf,
|
||||
ctrl_interface,
|
||||
udhcpc_hook_path: Some("/data/rayhunter/udhcpc-hook.sh".into()),
|
||||
dhcp_lease_path: Some("/data/rayhunter/dhcp_lease".into()),
|
||||
wpa_conf_path: Some("/data/rayhunter/wpa_sta.conf".into()),
|
||||
iw_bin: resolve_bin("iw"),
|
||||
udhcpc_bin: resolve_bin("udhcpc"),
|
||||
crash_log_dir: Some("/data/rayhunter/crash-logs".into()),
|
||||
wakelock_name: Some("rayhunter".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_bin(name: &str) -> Option<String> {
|
||||
let local = format!("/data/rayhunter/bin/{name}");
|
||||
if std::path::Path::new(&local).exists() {
|
||||
return Some(local);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn parse_config<P>(path: P) -> Result<Config, RayhunterError>
|
||||
where
|
||||
P: AsRef<std::path::Path>,
|
||||
|
||||
@@ -56,6 +56,7 @@ pub struct DiagTask {
|
||||
notification_channel: tokio::sync::mpsc::Sender<Notification>,
|
||||
min_space_to_start_mb: u64,
|
||||
min_space_to_continue_mb: u64,
|
||||
gps_mode: u8,
|
||||
state: DiagState,
|
||||
max_type_seen: EventType,
|
||||
bytes_since_space_check: usize,
|
||||
@@ -104,6 +105,7 @@ impl DiagTask {
|
||||
notification_channel: tokio::sync::mpsc::Sender<Notification>,
|
||||
min_space_to_start_mb: u64,
|
||||
min_space_to_continue_mb: u64,
|
||||
gps_mode: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
ui_update_sender,
|
||||
@@ -112,6 +114,7 @@ impl DiagTask {
|
||||
notification_channel,
|
||||
min_space_to_start_mb,
|
||||
min_space_to_continue_mb,
|
||||
gps_mode,
|
||||
state: DiagState::Stopped,
|
||||
max_type_seen: EventType::Informational,
|
||||
bytes_since_space_check: 0,
|
||||
@@ -144,7 +147,7 @@ impl DiagTask {
|
||||
DiskSpaceCheck::Failed => {}
|
||||
}
|
||||
|
||||
let (qmdl_file, analysis_file) = match qmdl_store.new_entry().await {
|
||||
let (qmdl_file, analysis_file) = match qmdl_store.new_entry(self.gps_mode).await {
|
||||
Ok(files) => files,
|
||||
Err(e) => {
|
||||
let msg = format!("failed creating QMDL file entry: {e}");
|
||||
@@ -381,6 +384,7 @@ pub fn run_diag_read_thread(
|
||||
notification_channel: tokio::sync::mpsc::Sender<Notification>,
|
||||
min_space_to_start_mb: u64,
|
||||
min_space_to_continue_mb: u64,
|
||||
gps_mode: u8,
|
||||
) {
|
||||
task_tracker.spawn(async move {
|
||||
info!("Using configuration for device: {0:?}", device);
|
||||
@@ -396,7 +400,8 @@ pub fn run_diag_read_thread(
|
||||
analyzer_config,
|
||||
notification_channel,
|
||||
min_space_to_start_mb,
|
||||
min_space_to_continue_mb
|
||||
min_space_to_continue_mb,
|
||||
gps_mode,
|
||||
);
|
||||
qmdl_file_tx
|
||||
.send(DiagDeviceCtrlMessage::StartRecording { response_tx: None })
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
use axum::Json;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
|
||||
use crate::server::ServerState;
|
||||
|
||||
/// Accepts both a JSON number and a numeric string (e.g. `"1234567890"` or `1234567890`).
|
||||
/// Truncates floats to seconds. Returns an error for non-numeric values.
|
||||
fn deserialize_unix_ts<'de, D>(deserializer: D) -> Result<i64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de;
|
||||
use serde_json::Value;
|
||||
match Value::deserialize(deserializer)? {
|
||||
Value::Number(n) => n.as_i64()
|
||||
.or_else(|| n.as_f64().map(|f| f as i64))
|
||||
.ok_or_else(|| de::Error::custom("timestamp out of range")),
|
||||
Value::String(s) => s.trim().parse::<f64>()
|
||||
.map(|f| f as i64)
|
||||
.map_err(|_| de::Error::custom("timestamp must be a numeric value")),
|
||||
_ => Err(de::Error::custom("timestamp must be a number or numeric string")),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct GpsData {
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub timestamp: String,
|
||||
#[serde(deserialize_with = "deserialize_unix_ts")]
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
||||
@@ -232,6 +232,7 @@ async fn run_with_config(
|
||||
notification_service.new_handler(),
|
||||
config.min_space_to_start_recording_mb,
|
||||
config.min_space_to_continue_recording_mb,
|
||||
config.gps_mode,
|
||||
);
|
||||
info!("Starting UI");
|
||||
|
||||
@@ -305,7 +306,7 @@ async fn run_with_config(
|
||||
(Some(lat), Some(lon)) => Some(gps::GpsData {
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
timestamp: "fixed".to_string(),
|
||||
timestamp: 0,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -70,10 +70,12 @@ pub struct ManifestEntry {
|
||||
/// When the manifest was uploaded to a WebDAV server
|
||||
#[cfg_attr(feature = "apidocs", schema(value_type = String))]
|
||||
pub upload_time: Option<DateTime<Local>>,
|
||||
#[serde(default)]
|
||||
pub gps_mode: Option<u8>,
|
||||
}
|
||||
|
||||
impl ManifestEntry {
|
||||
fn new() -> Self {
|
||||
fn new(gps_mode: u8) -> Self {
|
||||
let now = rayhunter::clock::get_adjusted_now();
|
||||
let metadata = RuntimeMetadata::new();
|
||||
ManifestEntry {
|
||||
@@ -86,6 +88,7 @@ impl ManifestEntry {
|
||||
arch: Some(metadata.arch),
|
||||
stop_reason: None,
|
||||
upload_time: None,
|
||||
gps_mode: Some(gps_mode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +226,7 @@ impl RecordingStore {
|
||||
arch: None,
|
||||
stop_reason: None,
|
||||
upload_time: None,
|
||||
gps_mode: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -255,12 +259,12 @@ impl RecordingStore {
|
||||
// Closes the current entry (if needed), creates a new entry based on the
|
||||
// current time, and updates the manifest. Returns a tuple of the entry's
|
||||
// newly created QMDL file and analysis file.
|
||||
pub async fn new_entry(&mut self) -> Result<(File, File), RecordingStoreError> {
|
||||
pub async fn new_entry(&mut self, gps_mode: u8) -> Result<(File, File), RecordingStoreError> {
|
||||
// if we've already got an entry open, close it
|
||||
if self.current_entry.is_some() {
|
||||
self.close_current_entry().await?;
|
||||
}
|
||||
let new_entry = ManifestEntry::new();
|
||||
let new_entry = ManifestEntry::new(gps_mode);
|
||||
let qmdl_filepath = new_entry.get_qmdl_filepath(&self.path);
|
||||
let qmdl_file = File::create(&qmdl_filepath)
|
||||
.await
|
||||
@@ -545,7 +549,7 @@ mod tests {
|
||||
async fn test_creating_updating_and_closing_entries() {
|
||||
let dir = make_temp_dir();
|
||||
let mut store = RecordingStore::create(dir.path()).await.unwrap();
|
||||
let _ = store.new_entry().await.unwrap();
|
||||
let _ = store.new_entry(0).await.unwrap();
|
||||
let entry_index = store.current_entry.unwrap();
|
||||
assert_eq!(
|
||||
RecordingStore::read_manifest(dir.path()).await.unwrap(),
|
||||
@@ -582,7 +586,7 @@ mod tests {
|
||||
async fn test_create_on_existing_store() {
|
||||
let dir = make_temp_dir();
|
||||
let mut store = RecordingStore::create(dir.path()).await.unwrap();
|
||||
let _ = store.new_entry().await.unwrap();
|
||||
let _ = store.new_entry(0).await.unwrap();
|
||||
let entry_index = store.current_entry.unwrap();
|
||||
store
|
||||
.update_entry_qmdl_size(entry_index, 1000)
|
||||
@@ -596,9 +600,9 @@ mod tests {
|
||||
async fn test_repeated_new_entries() {
|
||||
let dir = make_temp_dir();
|
||||
let mut store = RecordingStore::create(dir.path()).await.unwrap();
|
||||
let _ = store.new_entry().await.unwrap();
|
||||
let _ = store.new_entry(0).await.unwrap();
|
||||
let entry_index = store.current_entry.unwrap();
|
||||
let _ = store.new_entry().await.unwrap();
|
||||
let _ = store.new_entry(0).await.unwrap();
|
||||
let new_entry_index = store.current_entry.unwrap();
|
||||
assert_ne!(entry_index, new_entry_index);
|
||||
assert_eq!(store.manifest.entries.len(), 2);
|
||||
@@ -608,7 +612,7 @@ mod tests {
|
||||
async fn test_delete_all_entries() {
|
||||
let dir = make_temp_dir();
|
||||
let mut store = RecordingStore::create(dir.path()).await.unwrap();
|
||||
let _ = store.new_entry().await.unwrap();
|
||||
let _ = store.new_entry(0).await.unwrap();
|
||||
assert!(store.current_entry.is_some());
|
||||
|
||||
store.delete_all_entries().await.unwrap();
|
||||
|
||||
@@ -523,7 +523,7 @@ mod tests {
|
||||
) -> String {
|
||||
let entry_name = {
|
||||
let mut store = store_lock.write().await;
|
||||
let (mut qmdl_file, _analysis_file) = store.new_entry().await.unwrap();
|
||||
let (mut qmdl_file, _analysis_file) = store.new_entry(0).await.unwrap();
|
||||
|
||||
if !test_data.is_empty() {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
@@ -70,6 +70,12 @@
|
||||
>
|
||||
</p>
|
||||
{/if}
|
||||
{#if entry.gps_mode !== undefined}
|
||||
<p>
|
||||
<b>GPS Mode:</b>
|
||||
{entry.gps_mode === 0 ? 'Disabled' : entry.gps_mode === 1 ? 'Fixed coordinates' : 'API endpoint'}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if metadata && metadata.analyzers}
|
||||
<div>
|
||||
|
||||
@@ -86,6 +86,11 @@
|
||||
{entry.stop_reason}
|
||||
</div>
|
||||
{/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'}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row justify-between lg:justify-end gap-1 mt-2 overflow-x-auto">
|
||||
<DownloadLink url={entry.get_pcap_url()} text="pcap" full_button />
|
||||
<DownloadLink url={entry.get_qmdl_url()} text="qmdl" full_button />
|
||||
|
||||
@@ -13,6 +13,7 @@ interface JsonManifestEntry {
|
||||
qmdl_size_bytes: number;
|
||||
stop_reason: string | null;
|
||||
upload_time: string | null;
|
||||
gps_mode: number | null;
|
||||
}
|
||||
|
||||
export class Manifest {
|
||||
@@ -61,6 +62,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);
|
||||
|
||||
constructor(json: JsonManifestEntry) {
|
||||
this.name = json.name;
|
||||
@@ -75,6 +77,9 @@ export class ManifestEntry {
|
||||
if (json.upload_time) {
|
||||
this.upload_time = new Date(json.upload_time);
|
||||
}
|
||||
if (json.gps_mode !== null) {
|
||||
this.gps_mode = json.gps_mode;
|
||||
}
|
||||
}
|
||||
|
||||
get_readable_qmdl_size(): string {
|
||||
|
||||
@@ -160,7 +160,8 @@ export async function get_daemon_time(): Promise<TimeResponse> {
|
||||
export interface GpsData {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
timestamp: string;
|
||||
/** Unix timestamp in seconds (0 = fixed/no real time). */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export async function get_gps(): Promise<GpsData | null> {
|
||||
|
||||
@@ -299,6 +299,7 @@
|
||||
GPS Status
|
||||
</span>
|
||||
{#if gps_data}
|
||||
{@const gps_date_formatter = new Intl.DateTimeFormat(undefined, { timeStyle: 'long', dateStyle: 'short' })}
|
||||
<table class="w-full text-sm">
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-100">
|
||||
@@ -311,7 +312,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-1 pr-4 text-gray-500 font-medium">GPS Timestamp</td>
|
||||
<td class="py-1 font-mono">{gps_data.timestamp}</td>
|
||||
<td class="py-1 font-mono">
|
||||
{gps_data.timestamp > 0
|
||||
? gps_date_formatter.format(new Date(gps_data.timestamp * 1000))
|
||||
: 'Fixed'}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user