mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-30 17:53:35 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4bfaf07dd | |||
| c08ccda58b | |||
| f33b3baa4f |
@@ -0,0 +1,351 @@
|
|||||||
|
//! Diag ML1 measurement log serialization/deserialization. These are pretty
|
||||||
|
//! much entirely based on Shinjo Park's work in scat, since we couldn't find
|
||||||
|
//! any other documentation for the logs' structure.
|
||||||
|
|
||||||
|
use deku::prelude::*;
|
||||||
|
use deku::ctx::Order;
|
||||||
|
|
||||||
|
fn decode_rsrp(rsrp: u16) -> f32 {
|
||||||
|
rsrp as f32 / 16.0 - 180.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_rssi(rssi: u16) -> f32 {
|
||||||
|
rssi as f32 / 16.0 - 110.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_rsrq(rsrq: u16) -> f32 {
|
||||||
|
rsrq as f32 / 16.0 - 30.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod serving_cell {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||||
|
#[deku(bit_order = "lsb")]
|
||||||
|
pub struct MeasurementAndEvaluation {
|
||||||
|
pub header: MeasurementAndEvaluationHeader,
|
||||||
|
#[deku(bits = 12, pad_bits_after = "20")]
|
||||||
|
meas_rsrp: u16,
|
||||||
|
avg_rsrp: u32,
|
||||||
|
#[deku(bits = 10, pad_bits_after = "22")]
|
||||||
|
meas_rsrq: u16,
|
||||||
|
#[deku(pad_bits_before = "10", bits = 11, pad_bits_after = "11")]
|
||||||
|
meas_rssi: u16,
|
||||||
|
rxlev: u32,
|
||||||
|
s_search: u32,
|
||||||
|
#[deku(cond = "header.get_rrc_rel() == 0x01")]
|
||||||
|
r9_data: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||||
|
#[deku(ctx = "_: Order", id_type = "u8", bit_order = "lsb")]
|
||||||
|
pub enum MeasurementAndEvaluationHeader {
|
||||||
|
#[deku(id = "4")]
|
||||||
|
V4 {
|
||||||
|
rrc_rel: u8,
|
||||||
|
_reserved: u16,
|
||||||
|
earfcn: u16,
|
||||||
|
#[deku(bits = 9)]
|
||||||
|
pci: u16,
|
||||||
|
#[deku(bits = 7)]
|
||||||
|
serv_layer_priority: u8,
|
||||||
|
},
|
||||||
|
#[deku(id = "5")]
|
||||||
|
V5 {
|
||||||
|
rrc_rel: u8,
|
||||||
|
_reserved: u16,
|
||||||
|
earfcn: u32,
|
||||||
|
#[deku(bits = 9)]
|
||||||
|
pci: u16,
|
||||||
|
#[deku(bits = 7, pad_bytes_after = "2")]
|
||||||
|
serv_layer_priority: u8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasurementAndEvaluationHeader {
|
||||||
|
fn get_rrc_rel(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
MeasurementAndEvaluationHeader::V4 { rrc_rel, .. } => *rrc_rel,
|
||||||
|
MeasurementAndEvaluationHeader::V5 { rrc_rel, .. } => *rrc_rel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasurementAndEvaluation {
|
||||||
|
pub fn get_pci(&self) -> u16 {
|
||||||
|
match &self.header {
|
||||||
|
MeasurementAndEvaluationHeader::V4 { pci, .. } => *pci,
|
||||||
|
MeasurementAndEvaluationHeader::V5 { pci, .. } => *pci,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_earfcn(&self) -> u32 {
|
||||||
|
match &self.header {
|
||||||
|
MeasurementAndEvaluationHeader::V4 { earfcn, .. } => *earfcn as u32,
|
||||||
|
MeasurementAndEvaluationHeader::V5 { earfcn, .. } => *earfcn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meas_rsrp(&self) -> f32 {
|
||||||
|
decode_rsrp(self.meas_rsrp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meas_rssi(&self) -> f32 {
|
||||||
|
decode_rssi(self.meas_rssi)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meas_rsrq(&self) -> f32 {
|
||||||
|
decode_rsrq(self.meas_rsrq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod neighbor_cells {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, DekuRead, DekuWrite, PartialEq)]
|
||||||
|
#[deku(id_type = "u8", bit_order = "lsb")]
|
||||||
|
pub enum MeasurementsHeader {
|
||||||
|
#[deku(id = "4")]
|
||||||
|
V4 {
|
||||||
|
rrc_rel: u8,
|
||||||
|
_reserved1: u16,
|
||||||
|
earfcn: u16,
|
||||||
|
#[deku(bits = 6)]
|
||||||
|
q_rxlevmin: u8,
|
||||||
|
#[deku(bits = 10)]
|
||||||
|
n_cells: u16,
|
||||||
|
},
|
||||||
|
#[deku(id = "5")]
|
||||||
|
V5 {
|
||||||
|
rrc_rel: u8,
|
||||||
|
_reserved1: u16,
|
||||||
|
earfcn: u32,
|
||||||
|
#[deku(bits = 6)]
|
||||||
|
q_rxlevmin: u8,
|
||||||
|
#[deku(bits = 26)]
|
||||||
|
n_cells: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasurementsHeader {
|
||||||
|
fn get_n_cells(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
MeasurementsHeader::V4 { n_cells, .. } => *n_cells as usize,
|
||||||
|
MeasurementsHeader::V5 { n_cells, .. } => *n_cells as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, DekuRead, DekuWrite, PartialEq)]
|
||||||
|
pub struct Measurements {
|
||||||
|
pub header: MeasurementsHeader,
|
||||||
|
#[deku(count = "header.get_n_cells()")]
|
||||||
|
pub cells: Vec<MeasurementsCell>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Measurements {
|
||||||
|
pub fn get_earfcn(&self) -> u32 {
|
||||||
|
match &self.header {
|
||||||
|
MeasurementsHeader::V4 { earfcn, .. } => *earfcn as u32,
|
||||||
|
MeasurementsHeader::V5 { earfcn, .. } => *earfcn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, DekuRead, DekuWrite, PartialEq)]
|
||||||
|
#[deku(bit_order = "lsb")]
|
||||||
|
pub struct MeasurementsCell {
|
||||||
|
#[deku(bits = 9)]
|
||||||
|
pub pci: u16,
|
||||||
|
#[deku(bits = 11)]
|
||||||
|
meas_rssi: u16,
|
||||||
|
#[deku(bits = 12)]
|
||||||
|
meas_rsrp: u16,
|
||||||
|
#[deku(pad_bits_before = "12", bits = 12, pad_bits_after = "8")]
|
||||||
|
avg_rsrp: u16,
|
||||||
|
#[deku(pad_bits_before = "12", bits = 10, pad_bits_after = "10")]
|
||||||
|
meas_rsrq: u16,
|
||||||
|
#[deku(bits = 10, pad_bits_after = "10")]
|
||||||
|
avg_rsrq: u16,
|
||||||
|
#[deku(bits = 6, pad_bits_after = "6")]
|
||||||
|
s_rxlev: u16,
|
||||||
|
n_freq_offset: u16,
|
||||||
|
val5: u16,
|
||||||
|
ant0_offset: u32,
|
||||||
|
ant1_offset: u32,
|
||||||
|
unk1: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasurementsCell {
|
||||||
|
pub fn get_meas_rsrp(&self) -> f32 {
|
||||||
|
decode_rsrp(self.meas_rsrp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meas_rssi(&self) -> f32 {
|
||||||
|
decode_rssi(self.meas_rssi)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meas_rsrq(&self) -> f32 {
|
||||||
|
decode_rsrq(self.meas_rsrq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::diag::diaglog::LogBody;
|
||||||
|
use crate::log_codes::{LOG_LTE_ML1_NEIGHBOR_MEAS, LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL};
|
||||||
|
use std::io::{Cursor, Seek};
|
||||||
|
|
||||||
|
fn unhexlify(hexlified_bytes: &str) -> (usize, Reader<Cursor<Vec<u8>>>) {
|
||||||
|
let byte_len = hexlified_bytes.len() / 2;
|
||||||
|
let bytes = (0..hexlified_bytes.len())
|
||||||
|
.step_by(2)
|
||||||
|
.map(|i| u8::from_str_radix(&hexlified_bytes[i..i+2], 16).unwrap())
|
||||||
|
.collect();
|
||||||
|
(byte_len, Reader::new(Cursor::new(bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ncell_measurements(hexlified_bytes: &str) -> (u8, neighbor_cells::Measurements) {
|
||||||
|
let (total_size, mut reader) = unhexlify(hexlified_bytes);
|
||||||
|
match LogBody::from_reader_with_ctx(&mut reader, (LOG_LTE_ML1_NEIGHBOR_MEAS as u16, 0)) {
|
||||||
|
Ok(LogBody::LteMl1NeighborCellsMeasurements { data }) => {
|
||||||
|
if !reader.end() {
|
||||||
|
let leftover_bits = reader.rest();
|
||||||
|
let leftover_bytes = total_size - reader.stream_position().unwrap() as usize;
|
||||||
|
panic!("failed to read entire buffer ({} bytes, {} bits left)", leftover_bytes, leftover_bits.len());
|
||||||
|
}
|
||||||
|
let pkt_version = match data.header {
|
||||||
|
neighbor_cells::MeasurementsHeader::V4 { .. } => 4,
|
||||||
|
neighbor_cells::MeasurementsHeader::V5 { .. } => 5,
|
||||||
|
};
|
||||||
|
(pkt_version, data)
|
||||||
|
},
|
||||||
|
Ok(x) => panic!("expected MeasurementAndEvaluation, but parsed {:?}", x),
|
||||||
|
Err(x) => panic!("failed to parse MeasurementAndEvaluation {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_meas_eval(hexlified_bytes: &str) -> (u8, serving_cell::MeasurementAndEvaluation) {
|
||||||
|
let (total_size, mut reader) = unhexlify(hexlified_bytes);
|
||||||
|
match LogBody::from_reader_with_ctx(&mut reader, (LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL as u16, 0)) {
|
||||||
|
Ok(LogBody::LteMl1ServingCellMeasurementAndEvaluation { data }) => {
|
||||||
|
if !reader.end() {
|
||||||
|
let leftover_bits = reader.rest();
|
||||||
|
let leftover_bytes = total_size - reader.stream_position().unwrap() as usize;
|
||||||
|
panic!("failed to read entire buffer ({} bytes, {} bits left)", leftover_bytes, leftover_bits.len());
|
||||||
|
}
|
||||||
|
let pkt_version = match data.header {
|
||||||
|
serving_cell::MeasurementAndEvaluationHeader::V4 { .. } => 4,
|
||||||
|
serving_cell::MeasurementAndEvaluationHeader::V5 { .. } => 5,
|
||||||
|
};
|
||||||
|
(pkt_version, data)
|
||||||
|
},
|
||||||
|
Ok(x) => panic!("expected MeasurementAndEvaluation, but parsed {:?}", x),
|
||||||
|
Err(x) => panic!("failed to parse MeasurementAndEvaluation {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scell_meas_and_eval_case(
|
||||||
|
hexlified_bytes: &str,
|
||||||
|
pkt_version: u8,
|
||||||
|
pci: u16,
|
||||||
|
earfcn: u32,
|
||||||
|
rsrp: f32,
|
||||||
|
rsrq: f32,
|
||||||
|
rssi: f32
|
||||||
|
) {
|
||||||
|
let (parsed_pkt_version, data) = parse_meas_eval(hexlified_bytes);
|
||||||
|
assert_eq!(parsed_pkt_version, pkt_version);
|
||||||
|
assert_eq!(data.get_pci(), pci, "incorrect pci");
|
||||||
|
assert_eq!(data.get_earfcn(), earfcn, "incorrect earfcn");
|
||||||
|
assert_eq!(data.get_meas_rsrp(), rsrp, "incorrect rsrp");
|
||||||
|
assert_eq!(data.get_meas_rsrq(), rsrq, "incorrect rsrq");
|
||||||
|
assert_eq!(data.get_meas_rssi(), rssi, "incorrect rssi");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from scat's TestDiagLteLogParser::test_parse_lte_ml1_scell_meas,
|
||||||
|
// but edited to print full-precision floats
|
||||||
|
#[test]
|
||||||
|
fn test_scell_meas() {
|
||||||
|
scell_meas_and_eval_case(
|
||||||
|
"040100009C18D60AECC44E00E2244E00FFFCE30FFED80A0047AD56021D310100A2624100",
|
||||||
|
4,
|
||||||
|
214,
|
||||||
|
6300,
|
||||||
|
-101.25,
|
||||||
|
-14.0625,
|
||||||
|
-66.625
|
||||||
|
);
|
||||||
|
scell_meas_and_eval_case(
|
||||||
|
"05010000160d0000d40e00004bb444005444450039e514133149070048adfe019f310100a23f0000",
|
||||||
|
5,
|
||||||
|
212,
|
||||||
|
3350,
|
||||||
|
-111.3125,
|
||||||
|
-10.4375,
|
||||||
|
-80.875,
|
||||||
|
);
|
||||||
|
scell_meas_and_eval_case(
|
||||||
|
"05010000f424000a4d43434d4e434d41524b45527c307c3236327c317c34323330333233347c7c4d43434d4e434d41524b45520a0a434f504d41524b45527c434f504552524f5232363230317c434f504d41524b45520a006306000057755500577555001d75d4111d290b0048ad7e02dd370100a27f4100",
|
||||||
|
5,
|
||||||
|
333,
|
||||||
|
167781620,
|
||||||
|
-127.125,
|
||||||
|
-22.25,
|
||||||
|
2.75,
|
||||||
|
);
|
||||||
|
scell_meas_and_eval_case(
|
||||||
|
"0501000000190000a90d0000d9944d00d9944d006081d5d55d2568bc48ad3e027f314fe0891900e0",
|
||||||
|
5,
|
||||||
|
425,
|
||||||
|
6400,
|
||||||
|
-102.4375,
|
||||||
|
-8.0,
|
||||||
|
-77.4375,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ncell_meas_case(
|
||||||
|
hexlified_bytes: &str,
|
||||||
|
pkt_version: u8,
|
||||||
|
earfcn: u32,
|
||||||
|
cells: Vec<(u16, f32, f32, f32)>,
|
||||||
|
) {
|
||||||
|
let (parsed_pkt_version, data) = parse_ncell_measurements(hexlified_bytes);
|
||||||
|
assert_eq!(parsed_pkt_version, pkt_version, "incorrect pkt_version");
|
||||||
|
assert_eq!(data.cells.len(), cells.len(), "incorrect number of cells");
|
||||||
|
assert_eq!(data.get_earfcn(), earfcn, "incorrect earfcn");
|
||||||
|
for (parsed, (pci, rsrp, rssi, rsrq)) in data.cells.iter().zip(cells) {
|
||||||
|
assert_eq!(parsed.pci, pci, "incorrect pci");
|
||||||
|
assert_eq!(parsed.get_meas_rsrp(), rsrp, "incorrect rsrp");
|
||||||
|
assert_eq!(parsed.get_meas_rssi(), rssi, "incorrect rssi");
|
||||||
|
assert_eq!(parsed.get_meas_rsrq(), rsrq, "incorrect rsrq");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from scat's TestDiagLteLogParser::test_parse_lte_ml1_ncell_meas,
|
||||||
|
// but edited to print full-precision floats
|
||||||
|
#[test]
|
||||||
|
fn test_ncell_meas() {
|
||||||
|
ncell_meas_case(
|
||||||
|
"040100009C1847008348E44DDEA44C00CAB4CC32B6D8420300000000FF773301FF77330122020100",
|
||||||
|
4,
|
||||||
|
6300,
|
||||||
|
vec![
|
||||||
|
(131, -102.125, -75.75, -17.3125),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
ncell_meas_case(
|
||||||
|
"05010000160d0000480000006cea413bb4433b00b4f3cc33cf3c130200000000ffefc00fffefc00f45081600",
|
||||||
|
5,
|
||||||
|
3350,
|
||||||
|
vec![
|
||||||
|
(108, -120.75, -94.6875, -17.0625),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
//! Diag LogBody serialization/deserialization
|
||||||
|
|
||||||
|
use chrono::{DateTime, FixedOffset};
|
||||||
|
use deku::prelude::*;
|
||||||
|
|
||||||
|
pub mod measurement;
|
||||||
|
pub mod rrc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||||
|
#[deku(ctx = "log_type: u16, hdr_len: u16", id = "log_type")]
|
||||||
|
pub enum LogBody {
|
||||||
|
#[deku(id = "0x412f")]
|
||||||
|
WcdmaSignallingMessage {
|
||||||
|
channel_type: u8,
|
||||||
|
radio_bearer: u8,
|
||||||
|
length: u16,
|
||||||
|
#[deku(count = "length")]
|
||||||
|
msg: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id = "0x512f")]
|
||||||
|
GsmRrSignallingMessage {
|
||||||
|
channel_type: u8,
|
||||||
|
message_type: u8,
|
||||||
|
length: u8,
|
||||||
|
#[deku(count = "length")]
|
||||||
|
msg: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id = "0x5226")]
|
||||||
|
GprsMacSignallingMessage {
|
||||||
|
channel_type: u8,
|
||||||
|
message_type: u8,
|
||||||
|
length: u8,
|
||||||
|
#[deku(count = "length")]
|
||||||
|
msg: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id = "0xb0c0")]
|
||||||
|
LteRrcOtaMessage {
|
||||||
|
ext_header_version: u8,
|
||||||
|
#[deku(ctx = "*ext_header_version")]
|
||||||
|
packet: rrc::LteRrcOtaPacket,
|
||||||
|
},
|
||||||
|
// the four NAS command opcodes refer to:
|
||||||
|
// * 0xb0e2: plain ESM NAS message (incoming)
|
||||||
|
// * 0xb0e3: plain ESM NAS message (outgoing)
|
||||||
|
// * 0xb0ec: plain EMM NAS message (incoming)
|
||||||
|
// * 0xb0ed: plain EMM NAS message (outgoing)
|
||||||
|
#[deku(id_pat = "0xb0e2 | 0xb0e3 | 0xb0ec | 0xb0ed")]
|
||||||
|
Nas4GMessage {
|
||||||
|
#[deku(skip, default = "log_type")]
|
||||||
|
log_type: u16,
|
||||||
|
#[deku(ctx = "*log_type")]
|
||||||
|
direction: Nas4GMessageDirection,
|
||||||
|
ext_header_version: u8,
|
||||||
|
rrc_rel: u8,
|
||||||
|
rrc_version_minor: u8,
|
||||||
|
rrc_version_major: u8,
|
||||||
|
// message length = hdr_len - (sizeof(ext_header_version) + sizeof(rrc_rel) + sizeof(rrc_version_minor) + sizeof(rrc_version_major))
|
||||||
|
#[deku(count = "hdr_len.saturating_sub(4)")]
|
||||||
|
msg: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id = "0x11eb")]
|
||||||
|
IpTraffic {
|
||||||
|
// is this right?? based on https://github.com/P1sec/QCSuper/blob/81dbaeee15ec7747e899daa8e3495e27cdcc1264/src/modules/pcap_dump.py#L378
|
||||||
|
#[deku(count = "hdr_len.saturating_sub(8)")]
|
||||||
|
msg: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id = "0x713a")]
|
||||||
|
UmtsNasOtaMessage {
|
||||||
|
is_uplink: u8,
|
||||||
|
length: u32,
|
||||||
|
#[deku(count = "length")]
|
||||||
|
msg: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id = "0xb821")]
|
||||||
|
NrRrcOtaMessage {
|
||||||
|
#[deku(count = "hdr_len")]
|
||||||
|
msg: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id = "0xb17f")]
|
||||||
|
LteMl1ServingCellMeasurementAndEvaluation {
|
||||||
|
data: measurement::serving_cell::MeasurementAndEvaluation,
|
||||||
|
},
|
||||||
|
#[deku(id = "0xb180")]
|
||||||
|
LteMl1NeighborCellsMeasurements {
|
||||||
|
data: measurement::neighbor_cells::Measurements,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||||
|
#[deku(ctx = "log_type: u16", id = "log_type")]
|
||||||
|
pub enum Nas4GMessageDirection {
|
||||||
|
// * 0xb0e2: plain ESM NAS message (incoming)
|
||||||
|
// * 0xb0e3: plain ESM NAS message (outgoing)
|
||||||
|
// * 0xb0ec: plain EMM NAS message (incoming)
|
||||||
|
// * 0xb0ed: plain EMM NAS message (outgoing)
|
||||||
|
#[deku(id_pat = "0xb0e2 | 0xb0ec")]
|
||||||
|
Downlink,
|
||||||
|
#[deku(id_pat = "0xb0e3 | 0xb0ed")]
|
||||||
|
Uplink,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||||
|
#[deku(endian = "little")]
|
||||||
|
pub struct Timestamp {
|
||||||
|
pub ts: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timestamp {
|
||||||
|
pub fn to_datetime(&self) -> DateTime<FixedOffset> {
|
||||||
|
// Upper 48 bits: epoch at 1980-01-06 00:00:00, incremented by 1 for 1/800s
|
||||||
|
// Lower 16 bits: time since last 1/800s tick in 1/32 chip units
|
||||||
|
let ts_upper = self.ts >> 16;
|
||||||
|
let ts_lower = self.ts & 0xffff;
|
||||||
|
let epoch = chrono::DateTime::parse_from_rfc3339("1980-01-06T00:00:00-00:00").unwrap();
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::diag::Message;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_logs() {
|
||||||
|
let data = vec![
|
||||||
|
16, 0, 38, 0, 38, 0, 192, 176, 26, 165, 245, 135, 118, 35, 2, 1, 20, 14, 48, 0, 160, 0,
|
||||||
|
2, 8, 0, 0, 217, 15, 5, 0, 0, 0, 0, 7, 0, 64, 1, 238, 173, 213, 77, 208,
|
||||||
|
];
|
||||||
|
let msg = Message::from_bytes((&data, 0)).unwrap().1;
|
||||||
|
assert_eq!(
|
||||||
|
msg,
|
||||||
|
Message::Log {
|
||||||
|
pending_msgs: 0,
|
||||||
|
outer_length: 38,
|
||||||
|
inner_length: 38,
|
||||||
|
log_type: 0xb0c0,
|
||||||
|
timestamp: Timestamp {
|
||||||
|
ts: 72659535985485082
|
||||||
|
},
|
||||||
|
body: LogBody::LteRrcOtaMessage {
|
||||||
|
ext_header_version: 20,
|
||||||
|
packet: rrc::LteRrcOtaPacket::V8 {
|
||||||
|
rrc_rel_maj: 14,
|
||||||
|
rrc_rel_min: 48,
|
||||||
|
bearer_id: 0,
|
||||||
|
phy_cell_id: 160,
|
||||||
|
earfcn: 2050,
|
||||||
|
sfn_subfn: 4057,
|
||||||
|
pdu_num: 5,
|
||||||
|
sib_mask: 0,
|
||||||
|
len: 7,
|
||||||
|
packet: vec![0x40, 0x1, 0xee, 0xad, 0xd5, 0x4d, 0xd0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fuzz_crash_inner_length_underflow() {
|
||||||
|
// Regression test: inner_length < 12 previously caused panic.
|
||||||
|
// Fixed by using saturating_sub in Message::Log body length calculation.
|
||||||
|
let fuzz_data = b"\x10\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||||
|
let _ = Message::from_bytes((fuzz_data, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fuzz_crash_nas_hdr_len_underflow() {
|
||||||
|
// Regression test for two things:
|
||||||
|
// - hdr_len < 4 previously caused panic in Nas4GMessage.
|
||||||
|
// - Upgrading to deku 0.20 caused incorrect parsing behavior (double-read of discriminant)
|
||||||
|
let nas_msg =
|
||||||
|
b"\x10\x00\x14\x00\x02\x00\xe2\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00";
|
||||||
|
|
||||||
|
let ((rest, _), msg) = Message::from_bytes((nas_msg, 0)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rest.len(), 0);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
msg,
|
||||||
|
Message::Log {
|
||||||
|
log_type: 0xb0e2,
|
||||||
|
body: LogBody::Nas4GMessage {
|
||||||
|
direction: Nas4GMessageDirection::Downlink,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"Unexpected message: {:?}",
|
||||||
|
msg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fuzz_crash_ip_traffic_hdr_len_underflow() {
|
||||||
|
// Regression test: hdr_len < 8 previously caused panic in IpTraffic.
|
||||||
|
// Fixed by using saturating_sub for msg length calculation.
|
||||||
|
let ip_msg = b"\x10\x00\x14\x00\x02\x00\xeb\x11\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00";
|
||||||
|
let _ = Message::from_bytes((ip_msg, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
//! Diag LTE RRC serialization/deserialization
|
||||||
|
|
||||||
|
use deku::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||||
|
#[deku(ctx = "ext_header_version: u8", id = "ext_header_version")]
|
||||||
|
pub enum LteRrcOtaPacket {
|
||||||
|
#[deku(id_pat = "0..=4")]
|
||||||
|
V0 {
|
||||||
|
rrc_rel_maj: u8,
|
||||||
|
rrc_rel_min: u8,
|
||||||
|
bearer_id: u8,
|
||||||
|
phy_cell_id: u16,
|
||||||
|
earfcn: u16,
|
||||||
|
sfn_subfn: u16,
|
||||||
|
pdu_num: u8,
|
||||||
|
len: u16,
|
||||||
|
#[deku(count = "len")]
|
||||||
|
packet: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id_pat = "5..=7")]
|
||||||
|
V5 {
|
||||||
|
rrc_rel_maj: u8,
|
||||||
|
rrc_rel_min: u8,
|
||||||
|
bearer_id: u8,
|
||||||
|
phy_cell_id: u16,
|
||||||
|
earfcn: u16,
|
||||||
|
sfn_subfn: u16,
|
||||||
|
pdu_num: u8,
|
||||||
|
sib_mask: u32,
|
||||||
|
len: u16,
|
||||||
|
#[deku(count = "len")]
|
||||||
|
packet: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id_pat = "8..=24")]
|
||||||
|
V8 {
|
||||||
|
rrc_rel_maj: u8,
|
||||||
|
rrc_rel_min: u8,
|
||||||
|
bearer_id: u8,
|
||||||
|
phy_cell_id: u16,
|
||||||
|
earfcn: u32,
|
||||||
|
sfn_subfn: u16,
|
||||||
|
pdu_num: u8,
|
||||||
|
sib_mask: u32,
|
||||||
|
len: u16,
|
||||||
|
#[deku(count = "len")]
|
||||||
|
packet: Vec<u8>,
|
||||||
|
},
|
||||||
|
#[deku(id_pat = "25..")]
|
||||||
|
V25 {
|
||||||
|
rrc_rel_maj: u8,
|
||||||
|
rrc_rel_min: u8,
|
||||||
|
nr_rrc_rel_maj: u8,
|
||||||
|
nr_rrc_rel_min: u8,
|
||||||
|
bearer_id: u8,
|
||||||
|
phy_cell_id: u16,
|
||||||
|
earfcn: u32,
|
||||||
|
sfn_subfn: u16,
|
||||||
|
pdu_num: u8,
|
||||||
|
sib_mask: u32,
|
||||||
|
len: u16,
|
||||||
|
#[deku(count = "len")]
|
||||||
|
packet: Vec<u8>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LteRrcOtaPacket {
|
||||||
|
fn get_sfn_subfn(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
LteRrcOtaPacket::V0 { sfn_subfn, .. } => *sfn_subfn,
|
||||||
|
LteRrcOtaPacket::V5 { sfn_subfn, .. } => *sfn_subfn,
|
||||||
|
LteRrcOtaPacket::V8 { sfn_subfn, .. } => *sfn_subfn,
|
||||||
|
LteRrcOtaPacket::V25 { sfn_subfn, .. } => *sfn_subfn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_sfn(&self) -> u32 {
|
||||||
|
self.get_sfn_subfn() as u32 >> 4
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_subfn(&self) -> u8 {
|
||||||
|
(self.get_sfn_subfn() & 0xf) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pdu_num(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
LteRrcOtaPacket::V0 { pdu_num, .. } => *pdu_num,
|
||||||
|
LteRrcOtaPacket::V5 { pdu_num, .. } => *pdu_num,
|
||||||
|
LteRrcOtaPacket::V8 { pdu_num, .. } => *pdu_num,
|
||||||
|
LteRrcOtaPacket::V25 { pdu_num, .. } => *pdu_num,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_earfcn(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
LteRrcOtaPacket::V0 { earfcn, .. } => *earfcn as u32,
|
||||||
|
LteRrcOtaPacket::V5 { earfcn, .. } => *earfcn as u32,
|
||||||
|
LteRrcOtaPacket::V8 { earfcn, .. } => *earfcn,
|
||||||
|
LteRrcOtaPacket::V25 { earfcn, .. } => *earfcn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_payload(self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
LteRrcOtaPacket::V0 { packet, .. } => packet,
|
||||||
|
LteRrcOtaPacket::V5 { packet, .. } => packet,
|
||||||
|
LteRrcOtaPacket::V8 { packet, .. } => packet,
|
||||||
|
LteRrcOtaPacket::V25 { packet, .. } => packet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
//! Diag protocol serialization/deserialization
|
//! Diag protocol serialization/deserialization
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset};
|
|
||||||
use crc::{Algorithm, Crc};
|
use crc::{Algorithm, Crc};
|
||||||
use deku::prelude::*;
|
use deku::prelude::*;
|
||||||
|
|
||||||
@@ -8,6 +7,10 @@ use crate::hdlc::{self, hdlc_decapsulate};
|
|||||||
use log::warn;
|
use log::warn;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod diaglog;
|
||||||
|
|
||||||
|
use diaglog::{LogBody, Timestamp};
|
||||||
|
|
||||||
pub const MESSAGE_TERMINATOR: u8 = 0x7e;
|
pub const MESSAGE_TERMINATOR: u8 = 0x7e;
|
||||||
pub const MESSAGE_ESCAPE_CHAR: u8 = 0x7d;
|
pub const MESSAGE_ESCAPE_CHAR: u8 = 0x7d;
|
||||||
|
|
||||||
@@ -152,218 +155,6 @@ pub enum Message {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
|
||||||
#[deku(ctx = "log_type: u16, hdr_len: u16", id = "log_type")]
|
|
||||||
pub enum LogBody {
|
|
||||||
#[deku(id = "0x412f")]
|
|
||||||
WcdmaSignallingMessage {
|
|
||||||
channel_type: u8,
|
|
||||||
radio_bearer: u8,
|
|
||||||
length: u16,
|
|
||||||
#[deku(count = "length")]
|
|
||||||
msg: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id = "0x512f")]
|
|
||||||
GsmRrSignallingMessage {
|
|
||||||
channel_type: u8,
|
|
||||||
message_type: u8,
|
|
||||||
length: u8,
|
|
||||||
#[deku(count = "length")]
|
|
||||||
msg: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id = "0x5226")]
|
|
||||||
GprsMacSignallingMessage {
|
|
||||||
channel_type: u8,
|
|
||||||
message_type: u8,
|
|
||||||
length: u8,
|
|
||||||
#[deku(count = "length")]
|
|
||||||
msg: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id = "0xb0c0")]
|
|
||||||
LteRrcOtaMessage {
|
|
||||||
ext_header_version: u8,
|
|
||||||
#[deku(ctx = "*ext_header_version")]
|
|
||||||
packet: LteRrcOtaPacket,
|
|
||||||
},
|
|
||||||
// the four NAS command opcodes refer to:
|
|
||||||
// * 0xb0e2: plain ESM NAS message (incoming)
|
|
||||||
// * 0xb0e3: plain ESM NAS message (outgoing)
|
|
||||||
// * 0xb0ec: plain EMM NAS message (incoming)
|
|
||||||
// * 0xb0ed: plain EMM NAS message (outgoing)
|
|
||||||
#[deku(id_pat = "0xb0e2 | 0xb0e3 | 0xb0ec | 0xb0ed")]
|
|
||||||
Nas4GMessage {
|
|
||||||
#[deku(skip, default = "log_type")]
|
|
||||||
log_type: u16,
|
|
||||||
#[deku(ctx = "*log_type")]
|
|
||||||
direction: Nas4GMessageDirection,
|
|
||||||
ext_header_version: u8,
|
|
||||||
rrc_rel: u8,
|
|
||||||
rrc_version_minor: u8,
|
|
||||||
rrc_version_major: u8,
|
|
||||||
// message length = hdr_len - (sizeof(ext_header_version) + sizeof(rrc_rel) + sizeof(rrc_version_minor) + sizeof(rrc_version_major))
|
|
||||||
#[deku(count = "hdr_len.saturating_sub(4)")]
|
|
||||||
msg: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id = "0x11eb")]
|
|
||||||
IpTraffic {
|
|
||||||
// is this right?? based on https://github.com/P1sec/QCSuper/blob/81dbaeee15ec7747e899daa8e3495e27cdcc1264/src/modules/pcap_dump.py#L378
|
|
||||||
#[deku(count = "hdr_len.saturating_sub(8)")]
|
|
||||||
msg: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id = "0x713a")]
|
|
||||||
UmtsNasOtaMessage {
|
|
||||||
is_uplink: u8,
|
|
||||||
length: u32,
|
|
||||||
#[deku(count = "length")]
|
|
||||||
msg: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id = "0xb821")]
|
|
||||||
NrRrcOtaMessage {
|
|
||||||
#[deku(count = "hdr_len")]
|
|
||||||
msg: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
|
||||||
#[deku(ctx = "log_type: u16", id = "log_type")]
|
|
||||||
pub enum Nas4GMessageDirection {
|
|
||||||
// * 0xb0e2: plain ESM NAS message (incoming)
|
|
||||||
// * 0xb0e3: plain ESM NAS message (outgoing)
|
|
||||||
// * 0xb0ec: plain EMM NAS message (incoming)
|
|
||||||
// * 0xb0ed: plain EMM NAS message (outgoing)
|
|
||||||
#[deku(id_pat = "0xb0e2 | 0xb0ec")]
|
|
||||||
Downlink,
|
|
||||||
#[deku(id_pat = "0xb0e3 | 0xb0ed")]
|
|
||||||
Uplink,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
|
||||||
#[deku(ctx = "ext_header_version: u8", id = "ext_header_version")]
|
|
||||||
pub enum LteRrcOtaPacket {
|
|
||||||
#[deku(id_pat = "0..=4")]
|
|
||||||
V0 {
|
|
||||||
rrc_rel_maj: u8,
|
|
||||||
rrc_rel_min: u8,
|
|
||||||
bearer_id: u8,
|
|
||||||
phy_cell_id: u16,
|
|
||||||
earfcn: u16,
|
|
||||||
sfn_subfn: u16,
|
|
||||||
pdu_num: u8,
|
|
||||||
len: u16,
|
|
||||||
#[deku(count = "len")]
|
|
||||||
packet: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id_pat = "5..=7")]
|
|
||||||
V5 {
|
|
||||||
rrc_rel_maj: u8,
|
|
||||||
rrc_rel_min: u8,
|
|
||||||
bearer_id: u8,
|
|
||||||
phy_cell_id: u16,
|
|
||||||
earfcn: u16,
|
|
||||||
sfn_subfn: u16,
|
|
||||||
pdu_num: u8,
|
|
||||||
sib_mask: u32,
|
|
||||||
len: u16,
|
|
||||||
#[deku(count = "len")]
|
|
||||||
packet: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id_pat = "8..=24")]
|
|
||||||
V8 {
|
|
||||||
rrc_rel_maj: u8,
|
|
||||||
rrc_rel_min: u8,
|
|
||||||
bearer_id: u8,
|
|
||||||
phy_cell_id: u16,
|
|
||||||
earfcn: u32,
|
|
||||||
sfn_subfn: u16,
|
|
||||||
pdu_num: u8,
|
|
||||||
sib_mask: u32,
|
|
||||||
len: u16,
|
|
||||||
#[deku(count = "len")]
|
|
||||||
packet: Vec<u8>,
|
|
||||||
},
|
|
||||||
#[deku(id_pat = "25..")]
|
|
||||||
V25 {
|
|
||||||
rrc_rel_maj: u8,
|
|
||||||
rrc_rel_min: u8,
|
|
||||||
nr_rrc_rel_maj: u8,
|
|
||||||
nr_rrc_rel_min: u8,
|
|
||||||
bearer_id: u8,
|
|
||||||
phy_cell_id: u16,
|
|
||||||
earfcn: u32,
|
|
||||||
sfn_subfn: u16,
|
|
||||||
pdu_num: u8,
|
|
||||||
sib_mask: u32,
|
|
||||||
len: u16,
|
|
||||||
#[deku(count = "len")]
|
|
||||||
packet: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LteRrcOtaPacket {
|
|
||||||
fn get_sfn_subfn(&self) -> u16 {
|
|
||||||
match self {
|
|
||||||
LteRrcOtaPacket::V0 { sfn_subfn, .. } => *sfn_subfn,
|
|
||||||
LteRrcOtaPacket::V5 { sfn_subfn, .. } => *sfn_subfn,
|
|
||||||
LteRrcOtaPacket::V8 { sfn_subfn, .. } => *sfn_subfn,
|
|
||||||
LteRrcOtaPacket::V25 { sfn_subfn, .. } => *sfn_subfn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_sfn(&self) -> u32 {
|
|
||||||
self.get_sfn_subfn() as u32 >> 4
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_subfn(&self) -> u8 {
|
|
||||||
(self.get_sfn_subfn() & 0xf) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_pdu_num(&self) -> u8 {
|
|
||||||
match self {
|
|
||||||
LteRrcOtaPacket::V0 { pdu_num, .. } => *pdu_num,
|
|
||||||
LteRrcOtaPacket::V5 { pdu_num, .. } => *pdu_num,
|
|
||||||
LteRrcOtaPacket::V8 { pdu_num, .. } => *pdu_num,
|
|
||||||
LteRrcOtaPacket::V25 { pdu_num, .. } => *pdu_num,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_earfcn(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
LteRrcOtaPacket::V0 { earfcn, .. } => *earfcn as u32,
|
|
||||||
LteRrcOtaPacket::V5 { earfcn, .. } => *earfcn as u32,
|
|
||||||
LteRrcOtaPacket::V8 { earfcn, .. } => *earfcn,
|
|
||||||
LteRrcOtaPacket::V25 { earfcn, .. } => *earfcn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take_payload(self) -> Vec<u8> {
|
|
||||||
match self {
|
|
||||||
LteRrcOtaPacket::V0 { packet, .. } => packet,
|
|
||||||
LteRrcOtaPacket::V5 { packet, .. } => packet,
|
|
||||||
LteRrcOtaPacket::V8 { packet, .. } => packet,
|
|
||||||
LteRrcOtaPacket::V25 { packet, .. } => packet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
|
||||||
#[deku(endian = "little")]
|
|
||||||
pub struct Timestamp {
|
|
||||||
pub ts: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Timestamp {
|
|
||||||
pub fn to_datetime(&self) -> DateTime<FixedOffset> {
|
|
||||||
// Upper 48 bits: epoch at 1980-01-06 00:00:00, incremented by 1 for 1/800s
|
|
||||||
// Lower 16 bits: time since last 1/800s tick in 1/32 chip units
|
|
||||||
let ts_upper = self.ts >> 16;
|
|
||||||
let ts_lower = self.ts & 0xffff;
|
|
||||||
let epoch = chrono::DateTime::parse_from_rfc3339("1980-01-06T00:00:00-00:00").unwrap();
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||||
#[deku(ctx = "opcode: u32, subopcode: u32", id = "opcode")]
|
#[deku(ctx = "opcode: u32, subopcode: u32", id = "opcode")]
|
||||||
pub enum ResponsePayload {
|
pub enum ResponsePayload {
|
||||||
@@ -478,42 +269,6 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_logs() {
|
|
||||||
let data = vec![
|
|
||||||
16, 0, 38, 0, 38, 0, 192, 176, 26, 165, 245, 135, 118, 35, 2, 1, 20, 14, 48, 0, 160, 0,
|
|
||||||
2, 8, 0, 0, 217, 15, 5, 0, 0, 0, 0, 7, 0, 64, 1, 238, 173, 213, 77, 208,
|
|
||||||
];
|
|
||||||
let msg = Message::from_bytes((&data, 0)).unwrap().1;
|
|
||||||
assert_eq!(
|
|
||||||
msg,
|
|
||||||
Message::Log {
|
|
||||||
pending_msgs: 0,
|
|
||||||
outer_length: 38,
|
|
||||||
inner_length: 38,
|
|
||||||
log_type: 0xb0c0,
|
|
||||||
timestamp: Timestamp {
|
|
||||||
ts: 72659535985485082
|
|
||||||
},
|
|
||||||
body: LogBody::LteRrcOtaMessage {
|
|
||||||
ext_header_version: 20,
|
|
||||||
packet: LteRrcOtaPacket::V8 {
|
|
||||||
rrc_rel_maj: 14,
|
|
||||||
rrc_rel_min: 48,
|
|
||||||
bearer_id: 0,
|
|
||||||
phy_cell_id: 160,
|
|
||||||
earfcn: 2050,
|
|
||||||
sfn_subfn: 4057,
|
|
||||||
pdu_num: 5,
|
|
||||||
sib_mask: 0,
|
|
||||||
len: 7,
|
|
||||||
packet: vec![0x40, 0x1, 0xee, 0xad, 0xd5, 0x4d, 0xd0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_container(data_type: DataType, message: HdlcEncapsulatedMessage) -> MessagesContainer {
|
fn make_container(data_type: DataType, message: HdlcEncapsulatedMessage) -> MessagesContainer {
|
||||||
MessagesContainer {
|
MessagesContainer {
|
||||||
data_type,
|
data_type,
|
||||||
@@ -537,7 +292,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
body: LogBody::LteRrcOtaMessage {
|
body: LogBody::LteRrcOtaMessage {
|
||||||
ext_header_version: 20,
|
ext_header_version: 20,
|
||||||
packet: LteRrcOtaPacket::V8 {
|
packet: diaglog::rrc::LteRrcOtaPacket::V8 {
|
||||||
rrc_rel_maj: 14,
|
rrc_rel_maj: 14,
|
||||||
rrc_rel_min: 48,
|
rrc_rel_min: 48,
|
||||||
bearer_id: 0,
|
bearer_id: 0,
|
||||||
@@ -619,50 +374,6 @@ mod test {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fuzz_crash_inner_length_underflow() {
|
|
||||||
// Regression test: inner_length < 12 previously caused panic.
|
|
||||||
// Fixed by using saturating_sub in Message::Log body length calculation.
|
|
||||||
let fuzz_data = b"\x10\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
|
||||||
let _ = Message::from_bytes((fuzz_data, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fuzz_crash_nas_hdr_len_underflow() {
|
|
||||||
// Regression test for two things:
|
|
||||||
// - hdr_len < 4 previously caused panic in Nas4GMessage.
|
|
||||||
// - Upgrading to deku 0.20 caused incorrect parsing behavior (double-read of discriminant)
|
|
||||||
let nas_msg =
|
|
||||||
b"\x10\x00\x14\x00\x02\x00\xe2\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00";
|
|
||||||
|
|
||||||
let ((rest, _), msg) = Message::from_bytes((nas_msg, 0)).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rest.len(), 0);
|
|
||||||
assert!(
|
|
||||||
matches!(
|
|
||||||
msg,
|
|
||||||
Message::Log {
|
|
||||||
log_type: 0xb0e2,
|
|
||||||
body: LogBody::Nas4GMessage {
|
|
||||||
direction: Nas4GMessageDirection::Downlink,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"Unexpected message: {:?}",
|
|
||||||
msg
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fuzz_crash_ip_traffic_hdr_len_underflow() {
|
|
||||||
// Regression test: hdr_len < 8 previously caused panic in IpTraffic.
|
|
||||||
// Fixed by using saturating_sub for msg length calculation.
|
|
||||||
let ip_msg = b"\x10\x00\x14\x00\x02\x00\xeb\x11\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00";
|
|
||||||
let _ = Message::from_bytes((ip_msg, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fuzz_crash_response_opcode_parsing() {
|
fn test_fuzz_crash_response_opcode_parsing() {
|
||||||
// Regression test: Upgrading to deku 0.20 caused incorrect parsing of Response messages.
|
// Regression test: Upgrading to deku 0.20 caused incorrect parsing of Response messages.
|
||||||
@@ -40,7 +40,7 @@ pub enum DiagDeviceError {
|
|||||||
ParseMessagesContainerError(deku::DekuError),
|
ParseMessagesContainerError(deku::DekuError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 11] = [
|
pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 14] = [
|
||||||
// Layer 2:
|
// Layer 2:
|
||||||
log_codes::LOG_GPRS_MAC_SIGNALLING_MESSAGE_C, // 0x5226
|
log_codes::LOG_GPRS_MAC_SIGNALLING_MESSAGE_C, // 0x5226
|
||||||
// Layer 3:
|
// Layer 3:
|
||||||
@@ -56,6 +56,10 @@ pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 11] = [
|
|||||||
log_codes::LOG_LTE_NAS_EMM_OTA_OUT_MSG_LOG_C, // 0xb0ed
|
log_codes::LOG_LTE_NAS_EMM_OTA_OUT_MSG_LOG_C, // 0xb0ed
|
||||||
// User IP traffic:
|
// User IP traffic:
|
||||||
log_codes::LOG_DATA_PROTOCOL_LOGGING_C, // 0x11eb
|
log_codes::LOG_DATA_PROTOCOL_LOGGING_C, // 0x11eb
|
||||||
|
// Statistics
|
||||||
|
log_codes::LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL, // 0xb17f
|
||||||
|
log_codes::LOG_LTE_ML1_SERVING_CELL_MEAS_RESPONSE, // 0xb193
|
||||||
|
log_codes::LOG_LTE_ML1_NEIGHBOR_MEAS, // 0xb180
|
||||||
];
|
];
|
||||||
|
|
||||||
const BUFFER_LEN: usize = 1024 * 1024 * 10;
|
const BUFFER_LEN: usize = 1024 * 1024 * 10;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::diag::*;
|
use crate::diag::Message;
|
||||||
use crate::gsmtap::*;
|
use crate::diag::diaglog::{Timestamp, LogBody, Nas4GMessageDirection};
|
||||||
|
use crate::gsmtap::{GsmtapHeader, GsmtapMessage, GsmtapType, LteNasSubtype, LteRrcSubtype};
|
||||||
|
|
||||||
use log::error;
|
use log::debug;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -153,7 +154,7 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("gsmtap_sink: ignoring unhandled log type: {value:?}");
|
debug!("gsmtap_sink: ignoring unhandled log type: {value:?}");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,3 +103,7 @@ pub const WCDMA_SIGNALLING_MESSAGE: u32 = 0x412f;
|
|||||||
pub const LOG_DATA_PROTOCOL_LOGGING_C: u32 = 0x11eb;
|
pub const LOG_DATA_PROTOCOL_LOGGING_C: u32 = 0x11eb;
|
||||||
|
|
||||||
pub const LOG_UMTS_NAS_OTA_MESSAGE_LOG_PACKET_C: u32 = 0x713a;
|
pub const LOG_UMTS_NAS_OTA_MESSAGE_LOG_PACKET_C: u32 = 0x713a;
|
||||||
|
|
||||||
|
pub const LOG_LTE_ML1_SERVING_CELL_MEAS_RESP_AND_EVAL: u32 = 0xb17f;
|
||||||
|
pub const LOG_LTE_ML1_SERVING_CELL_MEAS_RESPONSE: u32 = 0xb193;
|
||||||
|
pub const LOG_LTE_ML1_NEIGHBOR_MEAS: u32 = 0xb180;
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
//! Parse QMDL files and create a pcap file.
|
//! Parse QMDL files and create a pcap file.
|
||||||
//! Creates a plausible IP header and [GSMtap](https://osmocom.org/projects/baseband/wiki/GSMTAP) header and then puts the rest of the data under that for wireshark to parse.
|
//! Creates a plausible IP header and [GSMtap](https://osmocom.org/projects/baseband/wiki/GSMTAP) header and then puts the rest of the data under that for wireshark to parse.
|
||||||
use crate::diag::Timestamp;
|
use crate::diag::diaglog::Timestamp;
|
||||||
use crate::gsmtap::GsmtapMessage;
|
use crate::gsmtap::GsmtapMessage;
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use deku::prelude::*;
|
use deku::prelude::*;
|
||||||
use rayhunter::{
|
use rayhunter::{
|
||||||
diag::{LogBody, LteRrcOtaPacket, Message, Timestamp},
|
diag::Message,
|
||||||
|
diag::diaglog::{LogBody, rrc::LteRrcOtaPacket, Timestamp},
|
||||||
gsmtap_parser,
|
gsmtap_parser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user