mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-30 09:59:27 -07:00
Kismet GPS option section per packet dcriped, refactoring to reduce loc
This commit is contained in:
committed by
Will Greenberg
parent
adb316e2d7
commit
66f0c2a336
@@ -14,10 +14,8 @@ pub struct GpsData {
|
||||
pub timestamp: String,
|
||||
}
|
||||
|
||||
/// A single GPS fix recorded in the sidecar file alongside a QMDL recording.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GpsRecord {
|
||||
/// Unix timestamp (seconds) of when this fix was received by the server.
|
||||
pub unix_ts: u32,
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
|
||||
@@ -10,15 +10,12 @@ use axum::response::{IntoResponse, Response};
|
||||
use log::error;
|
||||
use rayhunter::diag::DataType;
|
||||
use rayhunter::gsmtap_parser;
|
||||
use rayhunter::pcap::{GsmtapPcapWriter, KismetGpsPoint};
|
||||
use rayhunter::pcap::{GpsPoint, GsmtapPcapWriter};
|
||||
use rayhunter::qmdl::QmdlReader;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, duplex};
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
// Streams a pcap file chunk-by-chunk to the client by reading the QMDL data
|
||||
// written so far. This is done by spawning a thread which streams chunks of
|
||||
// pcap data to a channel that's piped to the client.
|
||||
#[cfg_attr(feature = "apidocs", utoipa::path(
|
||||
get,
|
||||
path = "/api/pcap/{name}",
|
||||
@@ -72,42 +69,33 @@ pub async fn get_pcap(
|
||||
Ok((headers, body).into_response())
|
||||
}
|
||||
|
||||
/// Loads GPS records for a recording entry.
|
||||
///
|
||||
/// - `gps_mode == 0`: returns empty vec (no GPS)
|
||||
/// - `gps_mode == 1`: returns a single synthetic record with `unix_ts = 0` (fixed coordinates)
|
||||
/// - `gps_mode == 2`: loads per-fix records from the GPS sidecar file
|
||||
pub(crate) async fn load_gps_records_for_entry(
|
||||
state: &Arc<ServerState>,
|
||||
entry_index: usize,
|
||||
) -> Vec<GpsRecord> {
|
||||
if state.config.gps_mode == 0 {
|
||||
return vec![];
|
||||
// Always try the per-session sidecar first — it reflects what was actually
|
||||
// recorded regardless of what the current gps_mode config is.
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sidecar missing or empty — fall back to current config.
|
||||
if state.config.gps_mode == 1 {
|
||||
let guard = state.gps_state.read().await;
|
||||
return guard
|
||||
.as_ref()
|
||||
.map(|g| {
|
||||
vec![GpsRecord {
|
||||
unix_ts: 0, // 0 signals fixed/synthetic to the Kismet option builder
|
||||
lat: g.latitude,
|
||||
lon: g.longitude,
|
||||
}]
|
||||
})
|
||||
.map(|g| vec![GpsRecord { unix_ts: 0, lat: g.latitude, lon: g.longitude }])
|
||||
.unwrap_or_default();
|
||||
}
|
||||
// gps_mode == 2: load from sidecar
|
||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||
match qmdl_store.open_entry_gps(entry_index).await {
|
||||
Ok(file) => load_gps_records(file).await,
|
||||
Err(_) => vec![],
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Returns the GPS fix from `records` whose `unix_ts` is closest to `packet_unix_ts`.
|
||||
/// Returns `None` if `records` is empty.
|
||||
fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: u32) -> Option<KismetGpsPoint> {
|
||||
fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: u32) -> Option<GpsPoint> {
|
||||
if records.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -117,19 +105,10 @@ fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: u32) -> Option<Kismet
|
||||
} else if idx >= records.len() {
|
||||
&records[records.len() - 1]
|
||||
} else {
|
||||
let before = &records[idx - 1];
|
||||
let after = &records[idx];
|
||||
if packet_unix_ts - before.unix_ts <= after.unix_ts - packet_unix_ts {
|
||||
before
|
||||
} else {
|
||||
after
|
||||
}
|
||||
let (before, after) = (&records[idx - 1], &records[idx]);
|
||||
if packet_unix_ts - before.unix_ts <= after.unix_ts - packet_unix_ts { before } else { after }
|
||||
};
|
||||
Some(KismetGpsPoint {
|
||||
latitude: record.lat,
|
||||
longitude: record.lon,
|
||||
timestamp_unix_secs: record.unix_ts,
|
||||
})
|
||||
Some(GpsPoint { latitude: record.lat, longitude: record.lon, unix_ts: record.unix_ts })
|
||||
}
|
||||
|
||||
pub async fn generate_pcap_data<R, W>(
|
||||
|
||||
@@ -783,24 +783,16 @@
|
||||
|
||||
<div class="border-t pt-4 mt-6 space-y-3">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">GPS Settings</h3>
|
||||
|
||||
<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"
|
||||
>
|
||||
<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}>0 - Disabled</option>
|
||||
<option value={1}>1 - Fixed coordinates</option>
|
||||
<option value={2}>2 - API Endpoint</option>
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{#if config.gps_mode === 2}
|
||||
POST latitude, longitude, and timestamp to <code>/api/gps</code> from
|
||||
any device on the network.
|
||||
POST latitude, longitude, and timestamp to <code>/api/gps</code> from any device on the network.
|
||||
{:else if config.gps_mode === 1}
|
||||
GPS coordinates are fixed to the values below.
|
||||
{:else}
|
||||
@@ -808,47 +800,19 @@
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if config.gps_mode === 1}
|
||||
<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
|
||||
bind:value={config.gps_fixed_latitude}
|
||||
placeholder="e.g. 37.7749"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||
/>
|
||||
<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
|
||||
bind:value={config.gps_fixed_latitude} placeholder="e.g. 37.7749"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue" />
|
||||
<p class="text-xs text-gray-500 mt-1">Decimal degrees, -90 to 90</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="gps_fixed_longitude"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Fixed Longitude
|
||||
</label>
|
||||
<input
|
||||
id="gps_fixed_longitude"
|
||||
type="number"
|
||||
min="-180"
|
||||
max="180"
|
||||
step="any"
|
||||
required
|
||||
bind:value={config.gps_fixed_longitude}
|
||||
placeholder="e.g. -122.4194"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||
/>
|
||||
<label for="gps_fixed_longitude" class="block text-sm font-medium text-gray-700 mb-1">Fixed Longitude</label>
|
||||
<input id="gps_fixed_longitude" type="number" min="-180" max="180" step="any" required
|
||||
bind:value={config.gps_fixed_longitude} placeholder="e.g. -122.4194"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue" />
|
||||
<p class="text-xs text-gray-500 mt-1">Decimal degrees, -180 to 180</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -291,46 +291,34 @@
|
||||
<SystemStatsTable stats={system_stats!} />
|
||||
</div>
|
||||
{#if gps_mode !== 0}
|
||||
<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"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M11.906 1.994a8.002 8.002 0 0 1 8.09 8.421 7.996 7.996 0 0 1-1.297 3.957.996.996 0 0 1-.133.204l-.108.129c-.178.243-.37.477-.573.699l-5.112 6.224a1 1 0 0 1-1.545 0L5.982 15.26l-.002-.002a18.146 18.146 0 0 1-.309-.38l-.133-.163a.999.999 0 0 1-.13-.202 7.995 7.995 0 0 1 6.498-12.518ZM15 9.997a3 3 0 1 1-5.999 0 3 3 0 0 1 5.999 0Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
GPS Status
|
||||
</span>
|
||||
{#if gps_data}
|
||||
<table class="w-full text-sm">
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-100">
|
||||
<td class="py-1 pr-4 text-gray-500 font-medium">Latitude</td>
|
||||
<td class="py-1 font-mono">{gps_data.latitude.toFixed(6)}</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-100">
|
||||
<td class="py-1 pr-4 text-gray-500 font-medium">Longitude</td>
|
||||
<td class="py-1 font-mono">{gps_data.longitude.toFixed(6)}</td>
|
||||
</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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{:else}
|
||||
<span class="text-gray-400 text-sm">Awaiting GPS data...</span>
|
||||
{/if}
|
||||
</div>
|
||||
<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">
|
||||
<path fill-rule="evenodd" d="M11.906 1.994a8.002 8.002 0 0 1 8.09 8.421 7.996 7.996 0 0 1-1.297 3.957.996.996 0 0 1-.133.204l-.108.129c-.178.243-.37.477-.573.699l-5.112 6.224a1 1 0 0 1-1.545 0L5.982 15.26l-.002-.002a18.146 18.146 0 0 1-.309-.38l-.133-.163a.999.999 0 0 1-.13-.202 7.995 7.995 0 0 1 6.498-12.518ZM15 9.997a3 3 0 1 1-5.999 0 3 3 0 0 1 5.999 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
GPS Status
|
||||
</span>
|
||||
{#if gps_data}
|
||||
<table class="w-full text-sm">
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-100">
|
||||
<td class="py-1 pr-4 text-gray-500 font-medium">Latitude</td>
|
||||
<td class="py-1 font-mono">{gps_data.latitude.toFixed(6)}</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-100">
|
||||
<td class="py-1 pr-4 text-gray-500 font-medium">Longitude</td>
|
||||
<td class="py-1 font-mono">{gps_data.longitude.toFixed(6)}</td>
|
||||
</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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{:else}
|
||||
<span class="text-gray-400 text-sm">Awaiting GPS data...</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
|
||||
152
lib/src/pcap.rs
152
lib/src/pcap.rs
@@ -6,8 +6,7 @@ use crate::gsmtap::GsmtapMessage;
|
||||
use chrono::prelude::*;
|
||||
use deku::prelude::*;
|
||||
use pcap_file_tokio::pcapng::PcapNgWriter;
|
||||
use pcap_file_tokio::pcapng::RawBlock;
|
||||
use pcap_file_tokio::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
|
||||
use pcap_file_tokio::pcapng::blocks::enhanced_packet::{EnhancedPacketBlock, EnhancedPacketOption};
|
||||
use pcap_file_tokio::pcapng::blocks::interface_description::InterfaceDescriptionBlock;
|
||||
use pcap_file_tokio::pcapng::blocks::section_header::{SectionHeaderBlock, SectionHeaderOption};
|
||||
use pcap_file_tokio::{Endianness, PcapError};
|
||||
@@ -27,87 +26,11 @@ pub enum GsmtapPcapError {
|
||||
Deku(#[from] DekuError),
|
||||
}
|
||||
|
||||
/// A GPS fix to embed in each PCAP packet as a Kismet-compatible custom option.
|
||||
///
|
||||
/// Set `timestamp_unix_secs = 0` to signal a fixed/synthetic coordinate (no real GPS time).
|
||||
pub struct KismetGpsPoint {
|
||||
pub struct GpsPoint {
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub timestamp_unix_secs: u32,
|
||||
}
|
||||
|
||||
// Block type constant for Enhanced Packet Block (pcapng spec §4.3)
|
||||
const ENHANCED_PACKET_BLOCK: u32 = 0x00000006;
|
||||
|
||||
/// Serialises a Kismet GPS custom option into a byte buffer suitable for
|
||||
/// appending directly to an EPB body.
|
||||
///
|
||||
/// Wire layout (section is big-endian; GPS custom payload is little-endian per
|
||||
/// Kismet convention):
|
||||
///
|
||||
/// option_code u16 BE = 2989 (custom binary, non-copyable)
|
||||
/// option_len u16 BE = 24 (PEN 4 + payload 20)
|
||||
/// pen u32 BE = 55922 (Kismet IANA PEN)
|
||||
/// --- custom payload (20 bytes, all fields little-endian) ---
|
||||
/// magic u8 = 0x47
|
||||
/// version u8 = 0x01
|
||||
/// fields_len u16 LE = 16 (bitmask + lon + lat + ts)
|
||||
/// bitmask u32 LE = 0x26 (lon=0x2, lat=0x4, gps_time=0x20)
|
||||
/// longitude i32 LE fixed37 (degrees × 1e7)
|
||||
/// latitude i32 LE fixed37 (degrees × 1e7)
|
||||
/// gps_time u32 LE unix seconds (0 = fixed/unknown)
|
||||
/// --- end-of-options marker ---
|
||||
/// end_code u16 BE = 0
|
||||
/// end_len u16 BE = 0
|
||||
fn build_gps_option_bytes(gps: &KismetGpsPoint) -> Vec<u8> {
|
||||
// --- opt_comment (code 1): human-readable GPS for Wireshark ---
|
||||
// Format chosen to match what Kismet writes so tools see a consistent label.
|
||||
let comment = if gps.timestamp_unix_secs == 0 {
|
||||
format!("GPS fixed lat={:.7} lon={:.7}", gps.latitude, gps.longitude)
|
||||
} else {
|
||||
format!("GPS lat={:.7} lon={:.7} ts={}", gps.latitude, gps.longitude, gps.timestamp_unix_secs)
|
||||
};
|
||||
let comment_bytes = comment.as_bytes();
|
||||
let comment_pad = (4 - (comment_bytes.len() % 4)) % 4;
|
||||
|
||||
// --- Kismet GPS custom option (code 2989) ---
|
||||
let lon_fixed: i32 = (gps.longitude * 1e7) as i32;
|
||||
let lat_fixed: i32 = (gps.latitude * 1e7) as i32;
|
||||
|
||||
// Custom payload: 20 bytes, all little-endian (Kismet convention)
|
||||
let fields_len: u16 = 16; // bitmask(4) + lon(4) + lat(4) + ts(4)
|
||||
let bitmask: u32 = 0x2 | 0x4 | 0x20; // lon | lat | gps_time
|
||||
let mut payload = Vec::<u8>::with_capacity(20);
|
||||
payload.push(0x47); // magic
|
||||
payload.push(0x01); // version
|
||||
payload.extend_from_slice(&fields_len.to_le_bytes());
|
||||
payload.extend_from_slice(&bitmask.to_le_bytes());
|
||||
payload.extend_from_slice(&lon_fixed.to_le_bytes());
|
||||
payload.extend_from_slice(&lat_fixed.to_le_bytes());
|
||||
payload.extend_from_slice(&gps.timestamp_unix_secs.to_le_bytes());
|
||||
// payload is exactly 20 bytes, already 4-byte aligned
|
||||
|
||||
// option_len = PEN (4) + payload (20) = 24, also 4-byte aligned
|
||||
let gps_opt_len: u16 = 24;
|
||||
|
||||
let mut out = Vec::new();
|
||||
|
||||
// opt_comment header + value + padding (big-endian section)
|
||||
out.extend_from_slice(&1u16.to_be_bytes()); // option_code = 1
|
||||
out.extend_from_slice(&(comment_bytes.len() as u16).to_be_bytes()); // option_len
|
||||
out.extend_from_slice(comment_bytes);
|
||||
out.extend_from_slice(&[0u8; 3][..comment_pad]); // padding to 4-byte boundary
|
||||
|
||||
// Kismet GPS option header + PEN + custom payload (big-endian section, LE payload)
|
||||
out.extend_from_slice(&2989u16.to_be_bytes()); // option_code
|
||||
out.extend_from_slice(&gps_opt_len.to_be_bytes());
|
||||
out.extend_from_slice(&55922u32.to_be_bytes()); // PEN
|
||||
out.extend_from_slice(&payload);
|
||||
|
||||
// end-of-options marker (big-endian)
|
||||
out.extend_from_slice(&0u16.to_be_bytes());
|
||||
out.extend_from_slice(&0u16.to_be_bytes());
|
||||
out
|
||||
/// Unix timestamp of the GPS fix. 0 means fixed/synthetic (no real GPS time).
|
||||
pub unix_ts: u32,
|
||||
}
|
||||
|
||||
pub struct GsmtapPcapWriter<T>
|
||||
@@ -186,7 +109,7 @@ where
|
||||
&mut self,
|
||||
msg: GsmtapMessage,
|
||||
timestamp: Timestamp,
|
||||
gps: Option<&KismetGpsPoint>,
|
||||
gps: Option<&GpsPoint>,
|
||||
) -> Result<(), GsmtapPcapError> {
|
||||
let duration = timestamp
|
||||
.to_datetime()
|
||||
@@ -223,56 +146,23 @@ where
|
||||
data.extend(&udp_header.to_bytes()?);
|
||||
data.extend(&msg_bytes);
|
||||
|
||||
match gps {
|
||||
None => {
|
||||
// Fast path: delegate to the library's standard EPB writer.
|
||||
let packet = EnhancedPacketBlock {
|
||||
interface_id: 0,
|
||||
timestamp: duration,
|
||||
original_len: data.len() as u32,
|
||||
data: Cow::Owned(data),
|
||||
options: vec![],
|
||||
};
|
||||
self.writer.write_pcapng_block(packet).await?;
|
||||
}
|
||||
Some(gps_point) => {
|
||||
// GPS path: build a raw EPB body so we can append the Kismet
|
||||
// custom option directly. The pcap-file-tokio crate does not
|
||||
// expose the inner option types publicly, so we must write the
|
||||
// option bytes manually and wrap them in a RawBlock.
|
||||
//
|
||||
// All standard pcapng multi-byte fields are big-endian (section
|
||||
// header declares Endianness::Big); the GPS custom payload uses
|
||||
// little-endian per the Kismet convention.
|
||||
let pad_len = (4 - (data.len() % 4)) % 4;
|
||||
let ts_nanos = duration.as_nanos();
|
||||
let ts_high = (ts_nanos >> 32) as u32;
|
||||
let ts_low = (ts_nanos & 0xFFFFFFFF) as u32;
|
||||
|
||||
let gps_bytes = build_gps_option_bytes(gps_point);
|
||||
|
||||
// Body = EPB fixed header (20 B) + data + padding + gps options
|
||||
let body_len = 20 + data.len() + pad_len + gps_bytes.len();
|
||||
let mut body = Vec::<u8>::with_capacity(body_len);
|
||||
body.extend_from_slice(&0u32.to_be_bytes()); // interface_id
|
||||
body.extend_from_slice(&ts_high.to_be_bytes()); // timestamp high
|
||||
body.extend_from_slice(&ts_low.to_be_bytes()); // timestamp low
|
||||
body.extend_from_slice(&(data.len() as u32).to_be_bytes()); // captured_len
|
||||
body.extend_from_slice(&(data.len() as u32).to_be_bytes()); // original_len
|
||||
body.extend_from_slice(&data);
|
||||
body.extend_from_slice(&[0u8; 3][..pad_len]); // padding
|
||||
body.extend_from_slice(&gps_bytes);
|
||||
|
||||
let block_total_len = (12 + body.len()) as u32;
|
||||
let raw = RawBlock {
|
||||
type_: ENHANCED_PACKET_BLOCK,
|
||||
initial_len: block_total_len,
|
||||
body: Cow::Owned(body),
|
||||
trailer_len: block_total_len,
|
||||
};
|
||||
self.writer.write_raw_block(&raw).await?;
|
||||
}
|
||||
let mut options = vec![];
|
||||
if let Some(p) = gps {
|
||||
let comment = if p.unix_ts == 0 {
|
||||
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)
|
||||
};
|
||||
options.push(EnhancedPacketOption::Comment(Cow::Owned(comment)));
|
||||
}
|
||||
let packet = EnhancedPacketBlock {
|
||||
interface_id: 0,
|
||||
timestamp: duration,
|
||||
original_len: data.len() as u32,
|
||||
data: Cow::Owned(data),
|
||||
options,
|
||||
};
|
||||
self.writer.write_pcapng_block(packet).await?;
|
||||
|
||||
self.ip_id = self.ip_id.wrapping_add(1);
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user