GPS logging capability#971
Conversation
untitaker
left a comment
There was a problem hiding this comment.
the code seems perfectly sound. Note that we're adding a separate ndjson file for all the GPS data, and the PCAP download now joins QMDL and this additional file into one format.
I wonder if we should compress this file in a similar fashion as we plan to do with the QMDL: #970 but I think this would require a larger refactor, and we can do it later.
Alternatively we could write GPS data straight into the QMDL?
But I think this is a good first approximation, and whether these files become too large entirely depends on how often the user submits GPS position updates.
There was a problem hiding this comment.
thanks so much for writing this @CGurity!
i have a couple comments ranging from minor nitpicks to moderate refactors, but i agree w/ @untitaker that the general approach here is sound.
| (Some(lat), Some(lon)) => Some(gps::GpsData { | ||
| latitude: lat, | ||
| longitude: lon, | ||
| timestamp: 0, |
There was a problem hiding this comment.
hmm, i think it's misleading to give a 0 unix timestamp for the fixed coordinate. instead, what would you think about using the rayhunter startup time (i.e. the current time when this code runs) here?
There was a problem hiding this comment.
or perhaps better yet, maybe we should omit a timestamp entirely for the fixed/synthetic data points
| format!("GPS fixed lat={:.7} lon={:.7}", p.latitude, p.longitude) | ||
| } else { | ||
| format!("GPS lat={:.7} lon={:.7} ts={}", p.latitude, p.longitude, p.unix_ts) |
There was a problem hiding this comment.
what do you think of using the same serialized JSON format used above here? it's slightly less user-friendly, but is definitely still legible to a human, and would make this much more easy to parse if we wanted to write automated tools to process this data later
|
@CGurity currently CI fails. appears to be mostly formatting related |
|
Sorry, the new review request was sent by accident, last time I went through this process, GitHub wasn't even MS property. Apologies, reviewing everything. |
|
All requests addressed, most important considerations are:
Again apologies for any process hiccups, I'm still rusty on this, and thank you for the review :) |
|
@CGurity sorry there is now a merge conflict to be resolved. Also the to_datetime method is unused which is causing a linter failure. |
|
No problem! I'll address both things the best I can. I'll let you know if something is too difficult for me. |
|
The issues were addressed, also the following testing commands were run to anticipate further issues: cargo clippy --package rayhunter-daemon 2>&1 | head -80 Some refactoring to address outputs for these commands. Again, the resulting code was just tested in TP-Link M7350 hardware and everything GPS related works as expected. |
|
thanks! i'm hoping to get back to reviewing this today or tomorrow |
wgreenberg
left a comment
There was a problem hiding this comment.
thanks @CGurity. there's still some outstanding issues from my last review, and i have a couple more comments, but it's looking closer!
|
|
||
| export async function get_gps(): Promise<GpsData | null> { | ||
| const response = await fetch('/api/gps', { cache: 'no-store' }); | ||
| if (response.status === 404) { |
There was a problem hiding this comment.
i don't think the request handler for /api/gps ever returns 404 right?
There was a problem hiding this comment.
From my testing, I have seen 404 in two scenarios:
- Trying to get GPS coordinates when in disabled mode (expected)
- Trying to get GPS coordinates in API mode before it actually received coordinated for the first time (also expected?)
| lon, | ||
| }]; | ||
| } | ||
| vec![] |
There was a problem hiding this comment.
if we didn't find GPS data, i think returning 404 would be appropriate here
There was a problem hiding this comment.
I propose generating the pcap and logging the lack of GPS data (given the way everything is generated, I don't see this happening except in a couple of border cases, so I'm a bit wary of adding too much more code to cover this). Let me know what do you think.
BeigeBox
left a comment
There was a problem hiding this comment.
Took a pass looking specifically for things that'd affect users in the field, since you and wgreenberg have been through the shape of the code already. Found half a dozen worth flagging, in rough order of impact:
- Client-supplied timestamp gets thrown away on write to the sidecar and
Utc::now()goes in instead, which is the opposite of what the PR description says ("especially useful for analysis to check how close in time the coordinates were logged"). - Fixed mode silently no-ops if one of the two coords is missing from config. No error, UI just sits on "Awaiting GPS data..." forever.
- POST returns 200 but drops the record entirely when no recording is active. Companion app sees success, data goes nowhere.
gps_fixed_latitude/_longitudepersist in config.toml after the user switches mode to Disabled.- No server-side range validation on posted lat/lon. The HTML5 min/max on the form is cosmetic.
- Frontend's
gps_modeis read once and doesn't refresh after config changes, so the GPS card visibility lags until hard reload.
Details inline. None of them look hard to address.
|
Code updated with corrections, again thank you for catching these issues. |
| } | ||
| } | ||
| } | ||
| records |
There was a problem hiding this comment.
Quick follow up on my earlier timestamp comment. Now that we're storing the client supplied timestamp (good), out of order records are actually more likely than before — a phone app can queue fixes while offline and POST them late, or retry. partition_point at pcap.rs:104 returns whatever on unsorted input, so worth adding a sort here before returning:
records.sort_by_key(|r| r.unix_ts);
recordsCheap, robust.
|
Last fix incorporated. Also rebased with main for the recent changes incorporated. GPS features tested again for this round. |
|
just flagging that while we are waiting for will's time to free up to finish reviewing this there are some merge conflicts and failing tests that could be addressed @CGurity |
|
Thank you very much for the update and the support! No pressure on my side. While I'm comfortable enough with the additions I'm proposing in this PR, I'm a bit worried I make a mistake rebasing with the recent merged code given I'm less familiar with those features and I'm less confident in testing them effectively. I'll be alert in case there is anything else I can help with, and again thank you :) |
…nique commit to make easier to debug or rollback
…efactoring to appease tests
…order conditions covered
… error handling in DiagTask::start
|
Rebased the PR, and addressed all remaining comments. I think the remaining point is, do we want to silently drop GPS data if it's malformed, when the user tries to download PCAP? I think yes, this feature is a lot of new potentially buggy code, and we don't want to break other functionality while we still figure things out. |
wgreenberg
left a comment
There was a problem hiding this comment.
this is looking good, thanks @untitaker. i have one proposal which, if you agree, hopefully won't be too much extra work
| match qmdl_store.open_entry_gps_for_append(entry_idx).await { | ||
| Ok(Some(mut file)) => { | ||
| let record = GpsRecord { | ||
| unix_ts: gps_data.timestamp, |
There was a problem hiding this comment.
what do you think about just using Rayhunter's current time for this, rather than accepting a timestamp from the client? we already have an API endpoint to sync Rayhunter's clock, and are using it for pcap data anyhow, so IMO using it removes the chance for a client to send us bogus time data that doesn't gel with the corresponding packet data.
There was a problem hiding this comment.
right. i mean, our use of timestamps is getting messy overall:
- system timestamp/manifest timestamp can be synced with the browser
- packet timestamp is just whatever the modem returns (i.e. not corrected at all)
- then GPS timestamps are user-submitted
i think it's probably best to grab the most recent packet timestamp and use that as GPS timestamp?
that the packet timestamp is not corrected is another issue, which is nontrivial to fix
There was a problem hiding this comment.
we already have an API endpoint to sync Rayhunter's clock, and are using it for pcap data anyhow,
that's actually not how it works, we only use it for manifest/metadata, and don't correct anything in the pcap. this was scoped out because the modem and the overall linux system have two different clocks with different drift, and messing with the pcap was deemed too magical.
There was a problem hiding this comment.
right. i mean, our use of timestamps is getting messy overall:
1. system timestamp/manifest timestamp can be synced with the browser 2. packet timestamp is just whatever the modem returns (i.e. not corrected at all) 3. then GPS timestamps are user-submittedi think it's probably best to grab the most recent packet timestamp and use that as GPS timestamp?
agreed, that seems like a fine compromise
that's actually not how it works, we only use it for manifest/metadata, and don't correct anything in the pcap. this was scoped out because the modem and the overall linux system have two different clocks with different drift, and messing with the pcap was deemed too magical.
ah right, i'd forgotten how cursed the timing situation was for diag data. thanks for the refresher
There was a problem hiding this comment.
I've updated the code to record both the current system timestamp AND the latest packet timestamp for every GPS update, probably useful for debugging.
It would be kind of neat to be able to write the current system time with every QMDL message into .qmdl, not sure if the format allows for a good place for that.
Getting the latest packet timestamp was a bit hairy to do without RwLock, so I moved the code for writing gps data into DiagTask.
There was a problem hiding this comment.
afaik a QMDL message can only store a single timestamp, but theoretically we could decide to rely on the system clock + rayhunter offset and override that timestamp
…o eliminate RwLocks, remove "sidecar" word from codebase
wgreenberg
left a comment
There was a problem hiding this comment.
nice, thanks @untitaker, and thanks @CGurity for making this happen!

This PR adds the relevant code to support the inclusion of coordinates into the generated PCAP files. There are 3 modes of GPS acquisition/logging:
The code was extensively tested on the TP-Link M7350, including GPS mode changes, downloading old session information, etc.
Some visual elements were added, like the configuration fields, GPS modes in the session panels, and a GPS div with the last received coordinates, if applicable.
There was the suggestion to use the Kismet GPS format (https://www.kismetwireless.net/docs/dev/pcapng_gps/#gps-custom-option ), however, while completely possible by currently used libraries, the result is not parsed by Wireshark and requires an extra layer of code to process. So I decided to keep this information in the packet comments for now.
Please be mindful that this will generate PCAP files with the location of the sensor operator, while this is super valuable to answer the when and where parts of a detection, this is highly sensitive information. A comprehensive threat model should include how to process this information. For instance, it would be a very good practice to only publish PCAPs that are anonymized or at least with truncated decimals in the coordinates to reduce precision, and explore informed consent processes for operators (this risk consideration also applies to collecting IMSIs, but it is definitely intensified by collecting GPS data)
The API endpoint might be tested with this very simple python script:
Claude Code was used to speed up the process; however, every modification was reviewed, approved, and tested personally on device.
Pull Request Checklist
cargo fmt.You must check one of: