diff --git a/daemon/src/main.rs b/daemon/src/main.rs index eb77054..0dcfb88 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -22,8 +22,8 @@ use crate::notifications::{NotificationService, run_notification_worker}; use crate::pcap::get_pcap; use crate::qmdl_store::RecordingStore; use crate::server::{ - ServerState, debug_set_display_state, get_config, get_qmdl, get_zip, serve_static, set_config, - test_notification, + ServerState, debug_set_display_state, get_config, get_qmdl, get_time, get_zip, serve_static, + set_config, set_time_offset, test_notification, }; use crate::stats::{get_qmdl_manifest, get_route_status, get_system_stats}; @@ -71,6 +71,8 @@ fn get_router() -> AppRouter { .route("/api/config", get(get_config)) .route("/api/config", post(set_config)) .route("/api/test-notification", post(test_notification)) + .route("/api/time", get(get_time)) + .route("/api/time-offset", post(set_time_offset)) .route("/api/debug/display-state", post(debug_set_display_state)) .route("/", get(|| async { Redirect::permanent("/index.html") })) .route("/{*path}", get(serve_static)) diff --git a/daemon/src/qmdl_store.rs b/daemon/src/qmdl_store.rs index 0c6d036..fd3785c 100644 --- a/daemon/src/qmdl_store.rs +++ b/daemon/src/qmdl_store.rs @@ -58,7 +58,7 @@ pub struct ManifestEntry { impl ManifestEntry { fn new() -> Self { - let now = Local::now(); + let now = rayhunter::clock::get_adjusted_now(); let metadata = RuntimeMetadata::new(); ManifestEntry { name: format!("{}", now.timestamp()), @@ -300,7 +300,8 @@ impl RecordingStore { size_bytes: usize, ) -> Result<(), RecordingStoreError> { self.manifest.entries[entry_index].qmdl_size_bytes = size_bytes; - self.manifest.entries[entry_index].last_message_time = Some(Local::now()); + self.manifest.entries[entry_index].last_message_time = + Some(rayhunter::clock::get_adjusted_now()); self.write_manifest().await } diff --git a/daemon/src/server.rs b/daemon/src/server.rs index 043b479..50f028e 100644 --- a/daemon/src/server.rs +++ b/daemon/src/server.rs @@ -9,7 +9,9 @@ use axum::extract::State; use axum::http::header::{self, CONTENT_LENGTH, CONTENT_TYPE}; use axum::http::{HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; +use chrono::{DateTime, Local}; use log::{error, warn}; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::fs::write; use tokio::io::{AsyncReadExt, copy, duplex}; @@ -170,6 +172,37 @@ pub async fn test_notification( }) } +/// Response for GET /api/time +#[derive(Serialize)] +pub struct TimeResponse { + /// The raw system time (without clock offset) + pub system_time: DateTime, + /// The adjusted time (system time + offset) + pub adjusted_time: DateTime, + /// The current offset in seconds + pub offset_seconds: i64, +} + +/// Request for POST /api/time-offset +#[derive(Deserialize)] +pub struct SetTimeOffsetRequest { + /// The offset to set, in seconds + pub offset_seconds: i64, +} + +pub async fn get_time() -> Json { + Json(TimeResponse { + system_time: Local::now(), + adjusted_time: rayhunter::clock::get_adjusted_now(), + offset_seconds: rayhunter::clock::get_offset().num_seconds(), + }) +} + +pub async fn set_time_offset(Json(req): Json) -> StatusCode { + rayhunter::clock::set_offset(chrono::TimeDelta::seconds(req.offset_seconds)); + StatusCode::OK +} + pub async fn get_zip( State(state): State>, Path(entry_name): Path, diff --git a/daemon/web/src/lib/components/ApiRequestButton.svelte b/daemon/web/src/lib/components/ApiRequestButton.svelte index c7d683f..18fcb1b 100644 --- a/daemon/web/src/lib/components/ApiRequestButton.svelte +++ b/daemon/web/src/lib/components/ApiRequestButton.svelte @@ -12,6 +12,7 @@ onclick, ariaLabel, errorMessage, + jsonBody, }: { url: string; method?: string; @@ -23,6 +24,7 @@ onclick?: () => void | Promise; ariaLabel?: string; errorMessage?: string; + jsonBody?: unknown; } = $props(); let is_requesting = $state(false); @@ -51,7 +53,8 @@ await user_action_req( method, url, - errorMessage ? errorMessage : 'Error performing action' + errorMessage ? errorMessage : 'Error performing action', + jsonBody ); if (onclick) { await onclick(); diff --git a/daemon/web/src/lib/components/ClockDriftAlert.svelte b/daemon/web/src/lib/components/ClockDriftAlert.svelte new file mode 100644 index 0000000..03cf831 --- /dev/null +++ b/daemon/web/src/lib/components/ClockDriftAlert.svelte @@ -0,0 +1,119 @@ + + +{#if show_alert} +
+ + + Clock Mismatch Detected + +

+ Normally the device should get the correct time from the network. Consider using another + SIM card for better results. +

+ + + + + + + {#if has_offset} + + + + + {/if} + + + + + +
Device (system):{device_system_time}
Device (adjusted):{device_adjusted_time}
Browser:{browser_time}
+

Copy browser clock to device?

+
+ + +
+
+{/if} diff --git a/daemon/web/src/lib/utils.svelte.ts b/daemon/web/src/lib/utils.svelte.ts index e4488f7..84e6793 100644 --- a/daemon/web/src/lib/utils.svelte.ts +++ b/daemon/web/src/lib/utils.svelte.ts @@ -26,15 +26,18 @@ export interface Config { analyzers: AnalyzerConfig; } -export async function req(method: string, url: string): Promise { - const response = await fetch(url, { - method: method, - }); - const body = await response.text(); +export async function req(method: string, url: string, json_body?: unknown): Promise { + const options: RequestInit = { method }; + if (json_body !== undefined) { + options.body = JSON.stringify(json_body); + options.headers = { 'Content-Type': 'application/json' }; + } + const response = await fetch(url, options); + const responseBody = await response.text(); if (response.status >= 200 && response.status < 300) { - return body; + return responseBody; } else { - throw new Error(body); + throw new Error(responseBody); } } @@ -42,13 +45,13 @@ export async function req(method: string, url: string): Promise { export async function user_action_req( method: string, url: string, - error_msg: string + error_msg: string, + json_body?: unknown ): Promise { try { - return await req(method, url); + return await req(method, url, json_body); } catch (error) { if (error instanceof Error) { - console.log('beeeo'); add_error(error, error_msg); } return undefined; @@ -105,3 +108,13 @@ export interface RouteStatus { export async function get_route_status(): Promise { return JSON.parse(await req('GET', '/api/route-status')); } + +export interface TimeResponse { + system_time: string; + adjusted_time: string; + offset_seconds: number; +} + +export async function get_daemon_time(): Promise { + return JSON.parse(await req('GET', '/api/time')); +} diff --git a/daemon/web/src/routes/+page.svelte b/daemon/web/src/routes/+page.svelte index 6feea0c..960dce0 100644 --- a/daemon/web/src/routes/+page.svelte +++ b/daemon/web/src/routes/+page.svelte @@ -11,6 +11,7 @@ import ConfigForm from '$lib/components/ConfigForm.svelte'; import ActionErrors from '$lib/components/ActionErrors.svelte'; import IPRouteAlert from '$lib/components/IPRouteAlert.svelte'; + import ClockDriftAlert from '$lib/components/ClockDriftAlert.svelte'; import LogView from '$lib/components/LogView.svelte'; import WarningIcon from '$lib/components/WarningIcon.svelte'; @@ -196,6 +197,7 @@ {/if} + {#if loaded}
{#if current_entry} diff --git a/lib/src/clock.rs b/lib/src/clock.rs new file mode 100644 index 0000000..723b92a --- /dev/null +++ b/lib/src/clock.rs @@ -0,0 +1,25 @@ +//! Global clock offset for adjusting timestamps. +//! +//! This module provides a global clock offset that can be used to adjust +//! timestamps when the device's system clock is incorrect. The offset is +//! stored in memory and is not persisted across restarts. + +use chrono::{DateTime, Local, TimeDelta}; +use std::sync::RwLock; + +static CLOCK_OFFSET: RwLock = RwLock::new(TimeDelta::zero()); + +/// Get the current clock offset. +pub fn get_offset() -> TimeDelta { + *CLOCK_OFFSET.read().unwrap() +} + +/// Set the clock offset. +pub fn set_offset(offset: TimeDelta) { + *CLOCK_OFFSET.write().unwrap() = offset; +} + +/// Get the current adjusted time (system time + offset). +pub fn get_adjusted_now() -> DateTime { + Local::now() + get_offset() +} diff --git a/lib/src/diag.rs b/lib/src/diag.rs index fcdb33b..eb8d396 100644 --- a/lib/src/diag.rs +++ b/lib/src/diag.rs @@ -360,7 +360,8 @@ impl Timestamp { let mut delta_seconds = ts_upper as f64 * 1.25; delta_seconds += ts_lower as f64 / 40960.0; let ts_delta = chrono::Duration::milliseconds(delta_seconds as i64); - epoch + ts_delta + // Apply global clock offset to adjust for incorrect device time + epoch + ts_delta + crate::clock::get_offset() } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 65331f2..bcb51be 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -12,6 +12,7 @@ pub fn init_logging(default_level: log::LevelFilter) { } pub mod analysis; +pub mod clock; pub mod diag; pub mod gsmtap; pub mod gsmtap_parser;