mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-27 07:59:59 -07:00
Rename directories (again)
This commit is contained in:
16
lib/Cargo.toml
Normal file
16
lib/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "orca"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Orbic Realtime Cellular Analysis"
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.5.0"
|
||||
chrono = "0.4.31"
|
||||
crc = "3.0.1"
|
||||
deku = { version = "0.16.0", features = ["logging"] }
|
||||
env_logger = "0.10.1"
|
||||
libc = "0.2.150"
|
||||
log = "0.4.20"
|
||||
pcap-file = "2.0.0"
|
||||
thiserror = "1.0.50"
|
||||
434
lib/src/diag.rs
Normal file
434
lib/src/diag.rs
Normal file
@@ -0,0 +1,434 @@
|
||||
//! Diag protocol serialization/deserialization
|
||||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use deku::prelude::*;
|
||||
|
||||
pub const MESSAGE_TERMINATOR: u8 = 0x7e;
|
||||
pub const MESSAGE_ESCAPE_CHAR: u8 = 0x7d;
|
||||
|
||||
pub const ESCAPED_MESSAGE_TERMINATOR: u8 = 0x5e;
|
||||
pub const ESCAPED_MESSAGE_ESCAPE_CHAR: u8 = 0x5d;
|
||||
|
||||
#[derive(Debug, Clone, DekuWrite)]
|
||||
pub struct RequestContainer {
|
||||
pub data_type: DataType,
|
||||
#[deku(skip)]
|
||||
pub use_mdm: bool,
|
||||
#[deku(skip, cond = "!*use_mdm")]
|
||||
pub mdm_field: i32,
|
||||
pub hdlc_encapsulated_request: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuWrite)]
|
||||
#[deku(type = "u32")]
|
||||
pub enum Request {
|
||||
#[deku(id = "115")]
|
||||
LogConfig(LogConfigRequest),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuWrite)]
|
||||
#[deku(type = "u32", endian = "little")]
|
||||
pub enum LogConfigRequest {
|
||||
#[deku(id = "1")]
|
||||
RetrieveIdRanges,
|
||||
|
||||
#[deku(id = "3")]
|
||||
SetMask {
|
||||
log_type: u32,
|
||||
log_mask_bitsize: u32,
|
||||
log_mask: Vec<u8>,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
#[deku(type = "u32", endian = "little")]
|
||||
pub enum DataType {
|
||||
#[deku(id = "32")]
|
||||
UserSpace,
|
||||
#[deku(id_pat = "_")]
|
||||
Other(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
pub struct MessagesContainer {
|
||||
pub data_type: DataType,
|
||||
pub num_messages: u32,
|
||||
#[deku(count = "num_messages")]
|
||||
pub messages: Vec<HdlcEncapsulatedMessage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
pub struct HdlcEncapsulatedMessage {
|
||||
pub len: u32,
|
||||
#[deku(count = "len")]
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
#[deku(type = "u8")]
|
||||
pub enum Message {
|
||||
#[deku(id = "16")]
|
||||
Log {
|
||||
pending_msgs: u8,
|
||||
outer_length: u16,
|
||||
inner_length: u16,
|
||||
log_type: u16,
|
||||
timestamp: Timestamp,
|
||||
// pass the log type and log length (inner_length - (sizeof(log_type) + sizeof(timestamp)))
|
||||
#[deku(ctx = "*log_type, *inner_length - 12")]
|
||||
body: LogBody,
|
||||
},
|
||||
|
||||
// kinda unpleasant deku hackery here. deku expects an enum's variant to be
|
||||
// right before its data, but in this case, a status value comes between the
|
||||
// variants and the data. so we need to use deku's context (ctx) feature to
|
||||
// pass those opcodes down to their respective parsers.
|
||||
#[deku(id_pat = "_")]
|
||||
Response {
|
||||
opcode: u32,
|
||||
subopcode: u32,
|
||||
status: u32,
|
||||
#[deku(ctx = "*opcode, *subopcode")]
|
||||
payload: ResponsePayload,
|
||||
},
|
||||
}
|
||||
|
||||
#[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 {
|
||||
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 - 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 - 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 = "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)]
|
||||
#[deku(ctx = "opcode: u32, subopcode: u32", id = "opcode")]
|
||||
pub enum ResponsePayload {
|
||||
#[deku(id = "115")]
|
||||
LogConfig(#[deku(ctx = "subopcode")] LogConfigResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
#[deku(ctx = "subopcode: u32", id = "subopcode")]
|
||||
pub enum LogConfigResponse {
|
||||
#[deku(id = "1")]
|
||||
RetrieveIdRanges {
|
||||
log_mask_sizes: [u32; 16],
|
||||
},
|
||||
|
||||
#[deku(id = "3")]
|
||||
SetMask,
|
||||
}
|
||||
|
||||
pub fn build_log_mask_request(log_type: u32, log_mask_bitsize: u32, accepted_log_codes: &[u32]) -> Request {
|
||||
let mut current_byte: u8 = 0;
|
||||
let mut num_bits_written: u8 = 0;
|
||||
let mut log_mask: Vec<u8> = vec![];
|
||||
for i in 0..log_mask_bitsize {
|
||||
let log_code: u32 = (log_type << 12) | i;
|
||||
if accepted_log_codes.contains(&log_code) {
|
||||
current_byte |= 1 << num_bits_written;
|
||||
}
|
||||
num_bits_written += 1;
|
||||
|
||||
if num_bits_written == 8 || i == log_mask_bitsize - 1 {
|
||||
log_mask.push(current_byte);
|
||||
current_byte = 0;
|
||||
num_bits_written = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Request::LogConfig(LogConfigRequest::SetMask {
|
||||
log_type,
|
||||
log_mask_bitsize,
|
||||
log_mask,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// Just about all of these test cases from manually parsing diag packets w/ QCSuper
|
||||
|
||||
#[test]
|
||||
fn test_request_serialization() {
|
||||
let req = Request::LogConfig(LogConfigRequest::RetrieveIdRanges);
|
||||
assert_eq!(req.to_bytes().unwrap(), vec![115, 0, 0, 0, 1, 0, 0, 0]);
|
||||
|
||||
let req = Request::LogConfig(LogConfigRequest::SetMask {
|
||||
log_type: 0,
|
||||
log_mask_bitsize: 0,
|
||||
log_mask: vec![],
|
||||
});
|
||||
assert_eq!(req.to_bytes().unwrap(), vec![
|
||||
115, 0, 0, 0,
|
||||
3, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_log_mask_request() {
|
||||
let log_type = 11;
|
||||
let bitsize = 513;
|
||||
let req = build_log_mask_request(log_type, bitsize, &crate::diag_device::LOG_CODES_FOR_RAW_PACKET_LOGGING);
|
||||
assert_eq!(req, Request::LogConfig(LogConfigRequest::SetMask {
|
||||
log_type: log_type,
|
||||
log_mask_bitsize: bitsize,
|
||||
log_mask: vec![
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
|
||||
0x0, 0x0, 0xc, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0,
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_container() {
|
||||
let req = RequestContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
use_mdm: false,
|
||||
mdm_field: -1,
|
||||
hdlc_encapsulated_request: vec![1, 2, 3, 4],
|
||||
};
|
||||
assert_eq!(req.to_bytes().unwrap(), vec![
|
||||
32, 0, 0, 0,
|
||||
1, 2, 3, 4,
|
||||
]);
|
||||
let req = RequestContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
use_mdm: true,
|
||||
mdm_field: -1,
|
||||
hdlc_encapsulated_request: vec![1, 2, 3, 4],
|
||||
};
|
||||
assert_eq!(req.to_bytes().unwrap(), vec![
|
||||
32, 0, 0, 0,
|
||||
255, 255, 255, 255,
|
||||
1, 2, 3, 4,
|
||||
]);
|
||||
}
|
||||
|
||||
#[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],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
237
lib/src/diag_device.rs
Normal file
237
lib/src/diag_device.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use crate::hdlc::hdlc_encapsulate;
|
||||
use crate::diag::{Message, ResponsePayload, Request, LogConfigRequest, LogConfigResponse, build_log_mask_request, RequestContainer, DataType, MessagesContainer};
|
||||
use crate::diag_reader::{DiagReader, CRC_CCITT};
|
||||
use crate::qmdl::QmdlWriter;
|
||||
use crate::log_codes;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::os::fd::AsRawFd;
|
||||
use thiserror::Error;
|
||||
use log::{info, warn, error};
|
||||
use deku::prelude::*;
|
||||
|
||||
pub type DiagResult<T> = Result<T, DiagDeviceError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DiagDeviceError {
|
||||
#[error("Failed to initialize /dev/diag: {0}")]
|
||||
InitializationFailed(String),
|
||||
#[error("Failed to read diag device: {0}")]
|
||||
DeviceReadFailed(std::io::Error),
|
||||
#[error("Failed to write diag device: {0}")]
|
||||
DeviceWriteFailed(String),
|
||||
#[error("Nonzero status code {0} for diag request: {1:?}")]
|
||||
RequestFailed(u32, Request),
|
||||
#[error("Didn't receive response for request: {0:?}")]
|
||||
NoResponse(Request),
|
||||
#[error("Failed to open QMDL file: {0}")]
|
||||
OpenQmdlFileError(std::io::Error),
|
||||
#[error("Failed to write to QMDL file: {0}")]
|
||||
QmdlFileWriteError(std::io::Error),
|
||||
#[error("Failed to open diag device: {0}")]
|
||||
OpenDiagDeviceError(std::io::Error),
|
||||
#[error("Failed to parse MessagesContainer: {0}")]
|
||||
ParseMessagesContainerError(deku::DekuError),
|
||||
}
|
||||
|
||||
pub const LOG_CODES_FOR_RAW_PACKET_LOGGING: [u32; 11] = [
|
||||
// Layer 2:
|
||||
log_codes::LOG_GPRS_MAC_SIGNALLING_MESSAGE_C, // 0x5226
|
||||
|
||||
// Layer 3:
|
||||
log_codes::LOG_GSM_RR_SIGNALING_MESSAGE_C, // 0x512f
|
||||
log_codes::WCDMA_SIGNALLING_MESSAGE, // 0x412f
|
||||
log_codes::LOG_LTE_RRC_OTA_MSG_LOG_C, // 0xb0c0
|
||||
log_codes::LOG_NR_RRC_OTA_MSG_LOG_C, // 0xb821
|
||||
|
||||
// NAS:
|
||||
log_codes::LOG_UMTS_NAS_OTA_MESSAGE_LOG_PACKET_C, // 0x713a
|
||||
log_codes::LOG_LTE_NAS_ESM_OTA_IN_MSG_LOG_C, // 0xb0e2
|
||||
log_codes::LOG_LTE_NAS_ESM_OTA_OUT_MSG_LOG_C, // 0xb0e3
|
||||
log_codes::LOG_LTE_NAS_EMM_OTA_IN_MSG_LOG_C, // 0xb0ec
|
||||
log_codes::LOG_LTE_NAS_EMM_OTA_OUT_MSG_LOG_C, // 0xb0ed
|
||||
|
||||
// User IP traffic:
|
||||
log_codes::LOG_DATA_PROTOCOL_LOGGING_C // 0x11eb
|
||||
];
|
||||
|
||||
const BUFFER_LEN: usize = 1024 * 1024 * 10;
|
||||
const MEMORY_DEVICE_MODE: i32 = 2;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
const DIAG_IOCTL_REMOTE_DEV: u32 = 32;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const DIAG_IOCTL_REMOTE_DEV: u64 = 32;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
const DIAG_IOCTL_SWITCH_LOGGING: u32 = 7;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const DIAG_IOCTL_SWITCH_LOGGING: u64 = 7;
|
||||
|
||||
pub struct DiagDevice {
|
||||
file: File,
|
||||
pub qmdl_writer: Option<QmdlWriter<File>>,
|
||||
fully_initialized: bool,
|
||||
read_buf: Vec<u8>,
|
||||
use_mdm: i32,
|
||||
}
|
||||
|
||||
impl DiagReader for DiagDevice {
|
||||
type Err = DiagDeviceError;
|
||||
|
||||
fn get_next_messages_container(&mut self) -> DiagResult<MessagesContainer> {
|
||||
let mut bytes_read = 0;
|
||||
while bytes_read == 0 {
|
||||
bytes_read = self.file.read(&mut self.read_buf)
|
||||
.map_err(DiagDeviceError::DeviceReadFailed)?;
|
||||
}
|
||||
let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0))
|
||||
.map_err(DiagDeviceError::ParseMessagesContainerError)?;
|
||||
if !leftover_bytes.is_empty() {
|
||||
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
|
||||
}
|
||||
|
||||
if let Some(qmdl_writer) = self.qmdl_writer.as_mut() {
|
||||
if self.fully_initialized {
|
||||
qmdl_writer.write_container(&container)
|
||||
.map_err(DiagDeviceError::QmdlFileWriteError)?;
|
||||
}
|
||||
}
|
||||
Ok(container)
|
||||
}
|
||||
}
|
||||
|
||||
impl DiagDevice {
|
||||
pub fn new(qmdl_writer: Option<QmdlWriter<File>>) -> DiagResult<Self> {
|
||||
let diag_file = std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/diag")
|
||||
.map_err(DiagDeviceError::OpenDiagDeviceError)?;
|
||||
let fd = diag_file.as_raw_fd();
|
||||
|
||||
enable_frame_readwrite(fd, MEMORY_DEVICE_MODE)?;
|
||||
let use_mdm = determine_use_mdm(fd)?;
|
||||
|
||||
Ok(DiagDevice {
|
||||
read_buf: vec![0; BUFFER_LEN],
|
||||
file: diag_file,
|
||||
fully_initialized: false,
|
||||
qmdl_writer,
|
||||
use_mdm,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_request(&mut self, req: &Request) -> DiagResult<()> {
|
||||
let req_bytes = &req.to_bytes().expect("Failed to serialize Request");
|
||||
let buf = RequestContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
use_mdm: self.use_mdm > 0,
|
||||
mdm_field: -1,
|
||||
hdlc_encapsulated_request: hdlc_encapsulate(req_bytes, &CRC_CCITT),
|
||||
}.to_bytes().expect("Failed to serialize RequestContainer");
|
||||
unsafe {
|
||||
let fd = self.file.as_raw_fd();
|
||||
let buf_ptr = buf.as_ptr() as *const libc::c_void;
|
||||
let ret = libc::write(fd, buf_ptr, buf.len());
|
||||
if ret < 0 {
|
||||
let msg = format!("write failed with error code {}", ret);
|
||||
return Err(DiagDeviceError::DeviceWriteFailed(msg));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn retrieve_id_ranges(&mut self) -> DiagResult<[u32; 16]> {
|
||||
let req = Request::LogConfig(LogConfigRequest::RetrieveIdRanges);
|
||||
self.write_request(&req)?;
|
||||
|
||||
for msg in self.read_response()? {
|
||||
match msg {
|
||||
Ok(Message::Log { .. }) => info!("skipping log response..."),
|
||||
Ok(Message::Response { payload, status, .. }) => match payload {
|
||||
ResponsePayload::LogConfig(LogConfigResponse::RetrieveIdRanges { log_mask_sizes }) => {
|
||||
if status != 0 {
|
||||
return Err(DiagDeviceError::RequestFailed(status, req));
|
||||
}
|
||||
return Ok(log_mask_sizes);
|
||||
},
|
||||
_ => info!("skipping non-LogConfigResponse response..."),
|
||||
},
|
||||
Err(e) => error!("error parsing message: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
Err(DiagDeviceError::NoResponse(req))
|
||||
}
|
||||
|
||||
fn set_log_mask(&mut self, log_type: u32, log_mask_bitsize: u32) -> DiagResult<()> {
|
||||
let req = build_log_mask_request(log_type, log_mask_bitsize, &LOG_CODES_FOR_RAW_PACKET_LOGGING);
|
||||
self.write_request(&req)?;
|
||||
|
||||
for msg in self.read_response()? {
|
||||
match msg {
|
||||
Ok(Message::Log { .. }) => info!("skipping log response..."),
|
||||
Ok(Message::Response { payload, status, .. }) => {
|
||||
if let ResponsePayload::LogConfig(LogConfigResponse::SetMask) = payload {
|
||||
if status != 0 {
|
||||
return Err(DiagDeviceError::RequestFailed(status, req));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
Err(e) => error!("error parsing message: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
Err(DiagDeviceError::NoResponse(req))
|
||||
}
|
||||
|
||||
pub fn config_logs(&mut self) -> DiagResult<()> {
|
||||
info!("retrieving diag logging capabilities...");
|
||||
let log_mask_sizes = self.retrieve_id_ranges()?;
|
||||
|
||||
for (log_type, &log_mask_bitsize) in log_mask_sizes.iter().enumerate() {
|
||||
if log_mask_bitsize > 0 {
|
||||
self.set_log_mask(log_type as u32, log_mask_bitsize)?;
|
||||
info!("enabled logging for log type {}", log_type);
|
||||
}
|
||||
}
|
||||
|
||||
self.fully_initialized = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers the diag device's debug logging mode
|
||||
fn enable_frame_readwrite(fd: i32, mode: i32) -> DiagResult<()> {
|
||||
unsafe {
|
||||
if libc::ioctl(fd, DIAG_IOCTL_SWITCH_LOGGING, mode, 0, 0, 0) < 0 {
|
||||
let ret = libc::ioctl(
|
||||
fd,
|
||||
DIAG_IOCTL_SWITCH_LOGGING,
|
||||
&mut [mode, -1, 0] as *mut _, // diag_logging_mode_param_t
|
||||
std::mem::size_of::<[i32; 3]>(), 0, 0, 0, 0
|
||||
);
|
||||
if ret < 0 {
|
||||
let msg = format!("DIAG_IOCTL_SWITCH_LOGGING ioctl failed with error code {}", ret);
|
||||
return Err(DiagDeviceError::InitializationFailed(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Unsure of what MDM actually stands for, but if `use_mdm` is > 0, then
|
||||
// an additional mask is included in every diag request
|
||||
fn determine_use_mdm(fd: i32) -> DiagResult<i32> {
|
||||
let use_mdm: i32 = 0;
|
||||
unsafe {
|
||||
if libc::ioctl(fd, DIAG_IOCTL_REMOTE_DEV, &use_mdm as *const i32) < 0 {
|
||||
let msg = format!("DIAG_IOCTL_REMOTE_DEV ioctl failed with error code {}", 0);
|
||||
return Err(DiagDeviceError::InitializationFailed(msg))
|
||||
}
|
||||
}
|
||||
Ok(use_mdm)
|
||||
}
|
||||
223
lib/src/diag_reader.rs
Normal file
223
lib/src/diag_reader.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
use crate::diag;
|
||||
use crate::{diag::*, hdlc::hdlc_decapsulate};
|
||||
use crate::hdlc;
|
||||
|
||||
use crc::{Crc, Algorithm};
|
||||
use deku::prelude::*;
|
||||
use log::{info, warn, error};
|
||||
use thiserror::Error;
|
||||
|
||||
// this is sorta based on the params qcsuper uses, plus what seems to be used in
|
||||
// https://github.com/fgsect/scat/blob/f1538b397721df3ab8ba12acd26716abcf21f78b/util.py#L47
|
||||
pub const CRC_CCITT_ALG: Algorithm<u16> = Algorithm {
|
||||
poly: 0x1021,
|
||||
init: 0xffff,
|
||||
refin: true,
|
||||
refout: true,
|
||||
width: 16,
|
||||
xorout: 0xffff,
|
||||
check: 0x2189,
|
||||
residue: 0x0000,
|
||||
};
|
||||
pub const CRC_CCITT: Crc<u16> = Crc::<u16>::new(&CRC_CCITT_ALG);
|
||||
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum DiagParsingError {
|
||||
#[error("Failed to parse Message: {0}, data: {1:?}")]
|
||||
MessageParsingError(deku::DekuError, Vec<u8>),
|
||||
#[error("HDLC decapsulation of message failed: {0}, data: {1:?}")]
|
||||
HdlcDecapsulationError(hdlc::HdlcError, Vec<u8>),
|
||||
}
|
||||
|
||||
type MaybeMessage = Result<Message, DiagParsingError>;
|
||||
|
||||
pub trait DiagReader {
|
||||
type Err;
|
||||
|
||||
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err>;
|
||||
|
||||
fn read_response(&mut self) -> Result<Vec<MaybeMessage>, Self::Err> {
|
||||
loop {
|
||||
let container = self.get_next_messages_container()?;
|
||||
if container.data_type == DataType::UserSpace {
|
||||
return self.parse_response_container(container);
|
||||
} else {
|
||||
info!("skipping non-userspace message...")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_response_container(&self, container: MessagesContainer) -> Result<Vec<MaybeMessage>, Self::Err> {
|
||||
let mut result = Vec::new();
|
||||
for msg in container.messages {
|
||||
for sub_msg in msg.data.split_inclusive(|&b| b == diag::MESSAGE_TERMINATOR) {
|
||||
match hdlc_decapsulate(sub_msg, &CRC_CCITT) {
|
||||
Ok(data) => match Message::from_bytes((&data, 0)) {
|
||||
Ok(((leftover_bytes, _), res)) => {
|
||||
if !leftover_bytes.is_empty() {
|
||||
warn!("warning: {} leftover bytes when parsing Message", leftover_bytes.len());
|
||||
}
|
||||
result.push(Ok(res));
|
||||
},
|
||||
Err(e) => {
|
||||
result.push(Err(DiagParsingError::MessageParsingError(e, data)));
|
||||
},
|
||||
},
|
||||
Err(err) => {
|
||||
result.push(Err(DiagParsingError::HdlcDecapsulationError(err, sub_msg.to_vec())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
struct MockReader {
|
||||
containers: Vec<MessagesContainer>,
|
||||
}
|
||||
|
||||
impl DiagReader for MockReader {
|
||||
type Err = ();
|
||||
|
||||
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err> {
|
||||
Ok(self.containers.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_container(data_type: DataType, message: HdlcEncapsulatedMessage) -> MessagesContainer {
|
||||
MessagesContainer {
|
||||
data_type,
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
}
|
||||
}
|
||||
|
||||
// this log is based on one captured on a real device -- if it fails to
|
||||
// serialize or deserialize, that's probably a problem with this mock, not
|
||||
// the DiagReader implementation
|
||||
fn get_test_message(payload: &[u8]) -> (HdlcEncapsulatedMessage, Message) {
|
||||
let length_with_payload = 31 + payload.len() as u16;
|
||||
let message = Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: length_with_payload,
|
||||
inner_length: length_with_payload,
|
||||
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: payload.len() as u16,
|
||||
packet: payload.to_vec(),
|
||||
},
|
||||
},
|
||||
};
|
||||
let serialized = message.to_bytes().expect("failed to serialize test message");
|
||||
let encapsulated_data = hdlc::hdlc_encapsulate(&serialized, &CRC_CCITT);
|
||||
let encapsulated = HdlcEncapsulatedMessage {
|
||||
len: encapsulated_data.len() as u32,
|
||||
data: encapsulated_data,
|
||||
};
|
||||
(encapsulated, message)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skipping_nonuser_containers() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, _) = get_test_message(&[2]);
|
||||
let (encapsulated3, message3) = get_test_message(&[3]);
|
||||
let mut reader = MockReader {
|
||||
containers: vec![
|
||||
make_container(DataType::UserSpace, encapsulated1),
|
||||
make_container(DataType::Other(0), encapsulated2),
|
||||
make_container(DataType::UserSpace, encapsulated3),
|
||||
],
|
||||
};
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message1)]));
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_containers_with_multiple_messages() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, message2) = get_test_message(&[2]);
|
||||
let mut container1 = make_container(DataType::UserSpace, encapsulated1);
|
||||
container1.messages.push(encapsulated2);
|
||||
container1.num_messages += 1;
|
||||
let (encapsulated3, message3) = get_test_message(&[3]);
|
||||
let mut reader = MockReader {
|
||||
containers: vec![
|
||||
container1,
|
||||
make_container(DataType::UserSpace, encapsulated3),
|
||||
],
|
||||
};
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message1), Ok(message2)]));
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_containers_with_concatenated_message() {
|
||||
let (mut encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, message2) = get_test_message(&[2]);
|
||||
encapsulated1.data.extend(encapsulated2.data);
|
||||
encapsulated1.len += encapsulated2.len;
|
||||
let (encapsulated3, message3) = get_test_message(&[3]);
|
||||
let mut reader = MockReader {
|
||||
containers: vec![
|
||||
make_container(DataType::UserSpace, encapsulated1),
|
||||
make_container(DataType::UserSpace, encapsulated3),
|
||||
],
|
||||
};
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message1), Ok(message2)]));
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handles_parsing_errors() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let bad_message = hdlc::hdlc_encapsulate(&[0x01, 0x02, 0x03, 0x04], &CRC_CCITT);
|
||||
let encapsulated2 = HdlcEncapsulatedMessage {
|
||||
len: bad_message.len() as u32,
|
||||
data: bad_message,
|
||||
};
|
||||
let mut container = make_container(DataType::UserSpace, encapsulated1);
|
||||
container.messages.push(encapsulated2);
|
||||
container.num_messages += 1;
|
||||
let mut reader = MockReader {
|
||||
containers: vec![container],
|
||||
};
|
||||
let result = reader.read_response().unwrap();
|
||||
assert_eq!(result[0], Ok(message1));
|
||||
assert!(matches!(result[1], Err(DiagParsingError::MessageParsingError(_, _))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handles_encapsulation_errors() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let bad_encapsulation = HdlcEncapsulatedMessage {
|
||||
len: 4,
|
||||
data: vec![0x01, 0x02, 0x03, 0x04],
|
||||
};
|
||||
let mut container = make_container(DataType::UserSpace, encapsulated1);
|
||||
container.messages.push(bad_encapsulation);
|
||||
container.num_messages += 1;
|
||||
let mut reader = MockReader {
|
||||
containers: vec![container],
|
||||
};
|
||||
let result = reader.read_response().unwrap();
|
||||
assert_eq!(result[0], Ok(message1));
|
||||
assert!(matches!(result[1], Err(DiagParsingError::HdlcDecapsulationError(_, _))));
|
||||
}
|
||||
}
|
||||
241
lib/src/gsmtap.rs
Normal file
241
lib/src/gsmtap.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
//! The spec for GSMTAP is here: https://github.com/osmocom/libosmocore/blob/master/include/osmocom/core/gsmtap.h
|
||||
|
||||
use deku::prelude::*;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum GsmtapType {
|
||||
Um(UmSubtype),
|
||||
Abis,
|
||||
UmBurst, /* raw burst bits */
|
||||
SIM, /* ISO 7816 smart card interface */
|
||||
TetraI1, /* tetra air interface */
|
||||
TetraI1Burst, /* tetra air interface */
|
||||
WmxBurst, /* WiMAX burst */
|
||||
GbLlc, /* GPRS Gb interface: LLC */
|
||||
GbSndcp, /* GPRS Gb interface: SNDCP */
|
||||
Gmr1Um, /* GMR-1 L2 packets */
|
||||
UmtsRlcMac,
|
||||
UmtsRrc(UmtsRrcSubtype),
|
||||
LteRrc(LteRrcSubtype), /* LTE interface */
|
||||
LteMac, /* LTE MAC interface */
|
||||
LteMacFramed, /* LTE MAC with context hdr */
|
||||
OsmocoreLog, /* libosmocore logging */
|
||||
QcDiag, /* Qualcomm DIAG frame */
|
||||
LteNas(LteNasSubtype), /* LTE Non-Access Stratum */
|
||||
E1T1, /* E1/T1 Lines */
|
||||
GsmRlp, /* GSM RLP frames as per 3GPP TS 24.022 */
|
||||
}
|
||||
|
||||
// based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/src/scat/parsers/qualcomm/diagltelogparser.py#L1337
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum LteNasSubtype {
|
||||
Plain = 0,
|
||||
Secure = 1,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum UmSubtype {
|
||||
Unknown = 0x00,
|
||||
Bcch = 0x01,
|
||||
Ccch = 0x02,
|
||||
Rach = 0x03,
|
||||
Agch = 0x04,
|
||||
Pch = 0x05,
|
||||
Sdcch = 0x06,
|
||||
Sdcch4 = 0x07,
|
||||
Sdcch8 = 0x08,
|
||||
TchF = 0x09,
|
||||
TchH = 0x0a,
|
||||
Pacch = 0x0b,
|
||||
Cbch52 = 0x0c,
|
||||
Pdch = 0x0d,
|
||||
Ptcch = 0x0e,
|
||||
Cbch51 = 0x0f,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum UmtsRrcSubtype {
|
||||
DlDcch = 0,
|
||||
UlDcch = 1,
|
||||
DlCcch = 2,
|
||||
UlCcch = 3,
|
||||
Pcch = 4,
|
||||
DlShcch = 5,
|
||||
UlShcch = 6,
|
||||
BcchFach = 7,
|
||||
BcchBch = 8,
|
||||
Mcch = 9,
|
||||
Msch = 10,
|
||||
HandoverToUTRANCommand = 11,
|
||||
InterRATHandoverInfo = 12,
|
||||
SystemInformationBCH = 13,
|
||||
SystemInformationContainer = 14,
|
||||
UERadioAccessCapabilityInfo = 15,
|
||||
MasterInformationBlock = 16,
|
||||
SysInfoType1 = 17,
|
||||
SysInfoType2 = 18,
|
||||
SysInfoType3 = 19,
|
||||
SysInfoType4 = 20,
|
||||
SysInfoType5 = 21,
|
||||
SysInfoType5bis = 22,
|
||||
SysInfoType6 = 23,
|
||||
SysInfoType7 = 24,
|
||||
SysInfoType8 = 25,
|
||||
SysInfoType9 = 26,
|
||||
SysInfoType10 = 27,
|
||||
SysInfoType11 = 28,
|
||||
SysInfoType11bis = 29,
|
||||
SysInfoType12 = 30,
|
||||
SysInfoType13 = 31,
|
||||
SysInfoType13_1 = 32,
|
||||
SysInfoType13_2 = 33,
|
||||
SysInfoType13_3 = 34,
|
||||
SysInfoType13_4 = 35,
|
||||
SysInfoType14 = 36,
|
||||
SysInfoType15 = 37,
|
||||
SysInfoType15bis = 38,
|
||||
SysInfoType15_1 = 39,
|
||||
SysInfoType15_1bis = 40,
|
||||
SysInfoType15_2 = 41,
|
||||
SysInfoType15_2bis = 42,
|
||||
SysInfoType15_2ter = 43,
|
||||
SysInfoType15_3 = 44,
|
||||
SysInfoType15_3bis = 45,
|
||||
SysInfoType15_4 = 46,
|
||||
SysInfoType15_5 = 47,
|
||||
SysInfoType15_6 = 48,
|
||||
SysInfoType15_7 = 49,
|
||||
SysInfoType15_8 = 50,
|
||||
SysInfoType16 = 51,
|
||||
SysInfoType17 = 52,
|
||||
SysInfoType18 = 53,
|
||||
SysInfoType19 = 54,
|
||||
SysInfoType20 = 55,
|
||||
SysInfoType21 = 56,
|
||||
SysInfoType22 = 57,
|
||||
SysInfoTypeSB1 = 58,
|
||||
SysInfoTypeSB2 = 59,
|
||||
ToTargetRNCContainer = 60,
|
||||
TargetRNCToSourceRNCContainer = 61
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum LteRrcSubtype {
|
||||
DlCcch = 0,
|
||||
DlDcch = 1,
|
||||
UlCcch = 2,
|
||||
UlDcch = 3,
|
||||
BcchBch = 4,
|
||||
BcchDlSch = 5,
|
||||
PCCH = 6,
|
||||
MCCH = 7,
|
||||
BcchBchMbms = 8,
|
||||
BcchDlSchBr = 9,
|
||||
BcchDlSchMbms = 10,
|
||||
ScMcch = 11,
|
||||
SbcchSlBch = 12,
|
||||
SbcchSlBchV2x = 13,
|
||||
DlCcchNb = 14,
|
||||
DlDcchNb = 15,
|
||||
UlCcchNb = 16,
|
||||
UlDcchNb = 17,
|
||||
BcchBchNb = 18,
|
||||
BcchBchTddNb = 19,
|
||||
BcchDlSchNb = 20,
|
||||
PcchNb = 21,
|
||||
ScMcchNb = 22,
|
||||
}
|
||||
|
||||
impl GsmtapType {
|
||||
pub fn get_type(&self) -> u8 {
|
||||
match self {
|
||||
GsmtapType::Um(_) => 0x01,
|
||||
GsmtapType::Abis => 0x02,
|
||||
GsmtapType::UmBurst => 0x03,
|
||||
GsmtapType::SIM => 0x04,
|
||||
GsmtapType::TetraI1 => 0x05,
|
||||
GsmtapType::TetraI1Burst => 0x06,
|
||||
GsmtapType::WmxBurst => 0x07,
|
||||
GsmtapType::GbLlc => 0x08,
|
||||
GsmtapType::GbSndcp => 0x09,
|
||||
GsmtapType::Gmr1Um => 0x0a,
|
||||
GsmtapType::UmtsRlcMac => 0x0b,
|
||||
GsmtapType::UmtsRrc(_) => 0x0c,
|
||||
GsmtapType::LteRrc(_) => 0x0d,
|
||||
GsmtapType::LteMac => 0x0e,
|
||||
GsmtapType::LteMacFramed => 0x0f,
|
||||
GsmtapType::OsmocoreLog => 0x10,
|
||||
GsmtapType::QcDiag => 0x11,
|
||||
GsmtapType::LteNas(_) => 0x12,
|
||||
GsmtapType::E1T1 => 0x13,
|
||||
GsmtapType::GsmRlp => 0x14,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_subtype(&self) -> u8 {
|
||||
match self {
|
||||
GsmtapType::Um(subtype) => *subtype as u8,
|
||||
GsmtapType::UmtsRrc(subtype) => *subtype as u8,
|
||||
GsmtapType::LteRrc(subtype) => *subtype as u8,
|
||||
GsmtapType::LteNas(subtype) => *subtype as u8,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuWrite)]
|
||||
#[deku(endian = "big")]
|
||||
pub struct GsmtapHeader {
|
||||
#[deku(skip)]
|
||||
pub gsmtap_type: GsmtapType,
|
||||
|
||||
#[deku(assert_eq = "2")]
|
||||
pub version: u8,
|
||||
#[deku(assert_eq = "4")]
|
||||
pub header_len: u8, // length in 4-byte words
|
||||
#[deku(update = "self.gsmtap_type.get_type()")]
|
||||
pub packet_type: u8,
|
||||
pub timeslot: u8,
|
||||
pub arfcn: u16,
|
||||
pub signal_dbm: i8,
|
||||
pub signal_noise_ratio_db: u8,
|
||||
pub frame_number: u32,
|
||||
#[deku(update = "self.gsmtap_type.get_subtype()")]
|
||||
pub subtype: u8,
|
||||
pub antenna_number: u8,
|
||||
pub subslot: u8,
|
||||
#[deku(assert_eq = "0")]
|
||||
pub reserved: u8,
|
||||
}
|
||||
|
||||
impl GsmtapHeader {
|
||||
pub fn new(
|
||||
gsmtap_type: GsmtapType,
|
||||
) -> Self {
|
||||
GsmtapHeader {
|
||||
gsmtap_type,
|
||||
version: 2,
|
||||
header_len: 4,
|
||||
packet_type: gsmtap_type.get_type(),
|
||||
timeslot: 0,
|
||||
arfcn: 0,
|
||||
signal_dbm: 0,
|
||||
signal_noise_ratio_db: 0,
|
||||
frame_number: 0,
|
||||
subtype: gsmtap_type.get_subtype(),
|
||||
antenna_number: 0,
|
||||
subslot: 0,
|
||||
reserved: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, DekuWrite)]
|
||||
pub struct GsmtapMessage {
|
||||
pub header: GsmtapHeader,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
139
lib/src/gsmtap_parser.rs
Normal file
139
lib/src/gsmtap_parser.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::diag::*;
|
||||
use crate::gsmtap::*;
|
||||
|
||||
use log::error;
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct GsmtapParser {
|
||||
}
|
||||
|
||||
impl Default for GsmtapParser {
|
||||
fn default() -> Self {
|
||||
GsmtapParser::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GsmtapParserError {
|
||||
#[error("Invalid LteRrcOtaMessage ext header version {0}")]
|
||||
InvalidLteRrcOtaExtHeaderVersion(u8),
|
||||
#[error("Invalid LteRrcOtaMessage header/PDU number combination: {0}/{1}")]
|
||||
InvalidLteRrcOtaHeaderPduNum(u8, u8),
|
||||
}
|
||||
|
||||
impl GsmtapParser {
|
||||
pub fn new() -> Self {
|
||||
GsmtapParser {}
|
||||
}
|
||||
|
||||
pub fn recv_message(&mut self, msg: Message) -> Result<Option<(Timestamp, GsmtapMessage)>, GsmtapParserError> {
|
||||
if let Message::Log { timestamp, body, .. } = msg {
|
||||
match self.log_to_gsmtap(body)? {
|
||||
Some(msg) => Ok(Some((timestamp, msg))),
|
||||
None => Ok(None),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn log_to_gsmtap(&self, value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserError> {
|
||||
match value {
|
||||
LogBody::LteRrcOtaMessage { ext_header_version, packet } => {
|
||||
let gsmtap_type = match ext_header_version {
|
||||
0x02 | 0x03 | 0x04 | 0x06 | 0x07 | 0x08 | 0x0d | 0x16 => match packet.get_pdu_num() {
|
||||
1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
|
||||
2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
|
||||
3 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
|
||||
4 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
|
||||
5 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
|
||||
6 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
|
||||
7 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
|
||||
8 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
|
||||
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
|
||||
},
|
||||
0x09 | 0x0c => match packet.get_pdu_num() {
|
||||
8 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
|
||||
9 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
|
||||
10 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
|
||||
11 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
|
||||
12 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
|
||||
13 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
|
||||
14 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
|
||||
15 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
|
||||
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
|
||||
},
|
||||
0x0e..=0x10 => match packet.get_pdu_num() {
|
||||
1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
|
||||
2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
|
||||
4 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
|
||||
5 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
|
||||
6 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
|
||||
7 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
|
||||
8 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
|
||||
9 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
|
||||
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
|
||||
},
|
||||
0x13 | 0x1a | 0x1b => match packet.get_pdu_num() {
|
||||
1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
|
||||
3 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
|
||||
6 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
|
||||
7 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
|
||||
8 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
|
||||
9 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
|
||||
10 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
|
||||
11 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
|
||||
45 => GsmtapType::LteRrc(LteRrcSubtype::BcchBchNb),
|
||||
46 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSchNb),
|
||||
47 => GsmtapType::LteRrc(LteRrcSubtype::PcchNb),
|
||||
48 => GsmtapType::LteRrc(LteRrcSubtype::DlCcchNb),
|
||||
49 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb),
|
||||
50 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb),
|
||||
52 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb),
|
||||
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
|
||||
}
|
||||
0x14 | 0x18 | 0x19 => match packet.get_pdu_num() {
|
||||
1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
|
||||
2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
|
||||
4 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
|
||||
5 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
|
||||
6 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
|
||||
7 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
|
||||
8 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
|
||||
9 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
|
||||
54 => GsmtapType::LteRrc(LteRrcSubtype::BcchBchNb),
|
||||
55 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSchNb),
|
||||
56 => GsmtapType::LteRrc(LteRrcSubtype::PcchNb),
|
||||
57 => GsmtapType::LteRrc(LteRrcSubtype::DlCcchNb),
|
||||
58 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb),
|
||||
59 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb),
|
||||
61 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb),
|
||||
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
|
||||
},
|
||||
_ => return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(ext_header_version)),
|
||||
};
|
||||
let mut header = GsmtapHeader::new(gsmtap_type);
|
||||
// Wireshark GSMTAP only accepts 14 bits of ARFCN
|
||||
header.arfcn = packet.get_earfcn().try_into().unwrap_or(0);
|
||||
header.frame_number = packet.get_sfn();
|
||||
header.subslot = packet.get_subfn();
|
||||
Ok(Some(GsmtapMessage {
|
||||
header,
|
||||
payload: packet.take_payload(),
|
||||
}))
|
||||
},
|
||||
LogBody::Nas4GMessage { msg, .. } => {
|
||||
// currently we only handle "plain" (i.e. non-secure) NAS messages
|
||||
let header = GsmtapHeader::new(GsmtapType::LteNas(LteNasSubtype::Plain));
|
||||
Ok(Some(GsmtapMessage {
|
||||
header,
|
||||
payload: msg,
|
||||
}))
|
||||
},
|
||||
_ => {
|
||||
error!("gsmtap_sink: ignoring unhandled log type: {:?}", value);
|
||||
Ok(None)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
99
lib/src/hdlc.rs
Normal file
99
lib/src/hdlc.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
//! HDLC stands for "High-level Data Link Control", which the diag protocol uses
|
||||
//! to encapsulate its messages. QCSuper's docs describe this in more detail
|
||||
//! here:
|
||||
//! https://github.com/P1sec/QCSuper/blob/master/docs/The%20Diag%20protocol.md#the-diag-protocol-over-usb
|
||||
|
||||
use crc::Crc;
|
||||
use bytes::Buf;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::diag::{MESSAGE_ESCAPE_CHAR, MESSAGE_TERMINATOR, ESCAPED_MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR};
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum HdlcError {
|
||||
#[error("Invalid checksum (expected {0}, got {1})")]
|
||||
InvalidChecksum(u16, u16),
|
||||
#[error("Invalid HDLC escape sequence: [0x7d, {0}]")]
|
||||
InvalidEscapeSequence(u8),
|
||||
#[error("No trailing character found (expected 0x7e, got {0}))")]
|
||||
NoTrailingCharacter(u8),
|
||||
#[error("Missing checksum")]
|
||||
MissingChecksum,
|
||||
#[error("Data too short to be HDLC encapsulated")]
|
||||
TooShort,
|
||||
}
|
||||
|
||||
pub fn hdlc_encapsulate(data: &[u8], crc: &Crc<u16>) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = Vec::with_capacity(data.len());
|
||||
|
||||
for &b in data {
|
||||
match b {
|
||||
MESSAGE_TERMINATOR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR]),
|
||||
MESSAGE_ESCAPE_CHAR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR]),
|
||||
_ => result.push(b),
|
||||
}
|
||||
}
|
||||
|
||||
for b in crc.checksum(data).to_le_bytes() {
|
||||
match b {
|
||||
MESSAGE_TERMINATOR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR]),
|
||||
MESSAGE_ESCAPE_CHAR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR]),
|
||||
_ => result.push(b),
|
||||
}
|
||||
}
|
||||
|
||||
result.push(MESSAGE_TERMINATOR);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn hdlc_decapsulate(data: &[u8], crc: &Crc<u16>) -> Result<Vec<u8>, HdlcError> {
|
||||
if data.len() < 3 {
|
||||
return Err(HdlcError::TooShort);
|
||||
}
|
||||
|
||||
if data[data.len() - 1] != MESSAGE_TERMINATOR {
|
||||
return Err(HdlcError::NoTrailingCharacter(data[data.len() - 1]));
|
||||
}
|
||||
|
||||
let mut unescaped = Vec::with_capacity(data.len());
|
||||
let mut escaping = false;
|
||||
for &b in &data[..data.len() - 1] {
|
||||
if escaping {
|
||||
match b {
|
||||
ESCAPED_MESSAGE_TERMINATOR => unescaped.push(MESSAGE_TERMINATOR),
|
||||
ESCAPED_MESSAGE_ESCAPE_CHAR => unescaped.push(MESSAGE_ESCAPE_CHAR),
|
||||
_ => return Err(HdlcError::InvalidEscapeSequence(b)),
|
||||
}
|
||||
escaping = false;
|
||||
} else if b == MESSAGE_ESCAPE_CHAR {
|
||||
escaping = true
|
||||
} else {
|
||||
unescaped.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
// pop off the u16 checksum, check it against what we calculated
|
||||
let checksum_hi = unescaped.pop().ok_or(HdlcError::MissingChecksum)?;
|
||||
let checksum_lo = unescaped.pop().ok_or(HdlcError::MissingChecksum)?;
|
||||
let checksum = [checksum_lo, checksum_hi].as_slice().get_u16_le();
|
||||
if checksum != crc.checksum(&unescaped) {
|
||||
return Err(HdlcError::InvalidChecksum(checksum, crc.checksum(&unescaped)));
|
||||
}
|
||||
|
||||
Ok(unescaped)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hdlc_encapsulate() {
|
||||
let crc = Crc::<u16>::new(&crate::diag_reader::CRC_CCITT_ALG);
|
||||
let data = vec![0x01, 0x02, 0x03, 0x04];
|
||||
let expected = vec![1, 2, 3, 4, 145, 57, 126];
|
||||
let encapsulated = hdlc_encapsulate(&data, &crc);
|
||||
assert_eq!(&encapsulated, &expected);
|
||||
assert_eq!(hdlc_decapsulate(&encapsulated, &crc), Ok(data));
|
||||
}
|
||||
}
|
||||
9
lib/src/lib.rs
Normal file
9
lib/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod hdlc;
|
||||
pub mod diag;
|
||||
pub mod diag_device;
|
||||
pub mod diag_reader;
|
||||
pub mod qmdl;
|
||||
pub mod log_codes;
|
||||
pub mod gsmtap;
|
||||
pub mod gsmtap_parser;
|
||||
pub mod pcap;
|
||||
108
lib/src/log_codes.rs
Normal file
108
lib/src/log_codes.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
//! Enumerates some releVant diag log codes. Copied from QCSuper
|
||||
|
||||
|
||||
// These are 2G-related log types.
|
||||
|
||||
pub const LOG_GSM_RR_SIGNALING_MESSAGE_C: u32 = 0x512f;
|
||||
|
||||
pub const DCCH: u32 = 0x00;
|
||||
pub const BCCH: u32 = 0x01;
|
||||
pub const L2_RACH: u32 = 0x02;
|
||||
pub const CCCH: u32 = 0x03;
|
||||
pub const SACCH: u32 = 0x04;
|
||||
pub const SDCCH: u32 = 0x05;
|
||||
pub const FACCH_F: u32 = 0x06;
|
||||
pub const FACCH_H: u32 = 0x07;
|
||||
pub const L2_RACH_WITH_NO_DELAY: u32 = 0x08;
|
||||
|
||||
// These are GPRS-related log types.
|
||||
|
||||
pub const LOG_GPRS_MAC_SIGNALLING_MESSAGE_C: u32 = 0x5226;
|
||||
|
||||
pub const PACCH_RRBP_CHANNEL: u32 = 0x03;
|
||||
pub const UL_PACCH_CHANNEL: u32 = 0x04;
|
||||
pub const DL_PACCH_CHANNEL: u32 = 0x83;
|
||||
|
||||
pub const PACKET_CHANNEL_REQUEST: u32 = 0x20;
|
||||
|
||||
// These are 5G-related log types.
|
||||
|
||||
pub const LOG_NR_RRC_OTA_MSG_LOG_C: u32 = 0xb821;
|
||||
|
||||
// These are 4G-related log types.
|
||||
|
||||
pub const LOG_LTE_RRC_OTA_MSG_LOG_C: u32 = 0xb0c0;
|
||||
pub const LOG_LTE_NAS_ESM_OTA_IN_MSG_LOG_C: u32 = 0xb0e2;
|
||||
pub const LOG_LTE_NAS_ESM_OTA_OUT_MSG_LOG_C: u32 = 0xb0e3;
|
||||
pub const LOG_LTE_NAS_EMM_OTA_IN_MSG_LOG_C: u32 = 0xb0ec;
|
||||
pub const LOG_LTE_NAS_EMM_OTA_OUT_MSG_LOG_C: u32 = 0xb0ed;
|
||||
|
||||
pub const LTE_BCCH_BCH_V0: u32 = 1;
|
||||
pub const LTE_BCCH_DL_SCH_V0: u32 = 2;
|
||||
pub const LTE_MCCH_V0: u32 = 3;
|
||||
pub const LTE_PCCH_V0: u32 = 4;
|
||||
pub const LTE_DL_CCCH_V0: u32 = 5;
|
||||
pub const LTE_DL_DCCH_V0: u32 = 6;
|
||||
pub const LTE_UL_CCCH_V0: u32 = 7;
|
||||
pub const LTE_UL_DCCH_V0: u32 = 8;
|
||||
|
||||
pub const LTE_BCCH_BCH_V14: u32 = 1;
|
||||
pub const LTE_BCCH_DL_SCH_V14: u32 = 2;
|
||||
pub const LTE_MCCH_V14: u32 = 4;
|
||||
pub const LTE_PCCH_V14: u32 = 5;
|
||||
pub const LTE_DL_CCCH_V14: u32 = 6;
|
||||
pub const LTE_DL_DCCH_V14: u32 = 7;
|
||||
pub const LTE_UL_CCCH_V14: u32 = 8;
|
||||
pub const LTE_UL_DCCH_V14: u32 = 9;
|
||||
|
||||
pub const LTE_BCCH_BCH_V9: u32 = 8;
|
||||
pub const LTE_BCCH_DL_SCH_V9: u32 = 9;
|
||||
pub const LTE_MCCH_V9: u32 = 10;
|
||||
pub const LTE_PCCH_V9: u32 = 11;
|
||||
pub const LTE_DL_CCCH_V9: u32 = 12;
|
||||
pub const LTE_DL_DCCH_V9: u32 = 13;
|
||||
pub const LTE_UL_CCCH_V9: u32 = 14;
|
||||
pub const LTE_UL_DCCH_V9: u32 = 15;
|
||||
|
||||
pub const LTE_BCCH_BCH_V19: u32 = 1;
|
||||
pub const LTE_BCCH_DL_SCH_V19: u32 = 3;
|
||||
pub const LTE_MCCH_V19: u32 = 6;
|
||||
pub const LTE_PCCH_V19: u32 = 7;
|
||||
pub const LTE_DL_CCCH_V19: u32 = 8;
|
||||
pub const LTE_DL_DCCH_V19: u32 = 9;
|
||||
pub const LTE_UL_CCCH_V19: u32 = 10;
|
||||
pub const LTE_UL_DCCH_V19: u32 = 11;
|
||||
|
||||
pub const LTE_BCCH_BCH_NB: u32 = 45;
|
||||
pub const LTE_BCCH_DL_SCH_NB: u32 = 46;
|
||||
pub const LTE_PCCH_NB: u32 = 47;
|
||||
pub const LTE_DL_CCCH_NB: u32 = 48;
|
||||
pub const LTE_DL_DCCH_NB: u32 = 49;
|
||||
pub const LTE_UL_CCCH_NB: u32 = 50;
|
||||
pub const LTE_UL_DCCH_NB: u32 = 52;
|
||||
|
||||
// These are 3G-related log types.
|
||||
|
||||
pub const RRCLOG_SIG_UL_CCCH: u32 = 0;
|
||||
pub const RRCLOG_SIG_UL_DCCH: u32 = 1;
|
||||
pub const RRCLOG_SIG_DL_CCCH: u32 = 2;
|
||||
pub const RRCLOG_SIG_DL_DCCH: u32 = 3;
|
||||
pub const RRCLOG_SIG_DL_BCCH_BCH: u32 = 4;
|
||||
pub const RRCLOG_SIG_DL_BCCH_FACH: u32 = 5;
|
||||
pub const RRCLOG_SIG_DL_PCCH: u32 = 6;
|
||||
pub const RRCLOG_SIG_DL_MCCH: u32 = 7;
|
||||
pub const RRCLOG_SIG_DL_MSCH: u32 = 8;
|
||||
pub const RRCLOG_EXTENSION_SIB: u32 = 9;
|
||||
pub const RRCLOG_SIB_CONTAINER: u32 = 10;
|
||||
|
||||
|
||||
// 3G layer 3 packets:
|
||||
|
||||
pub const WCDMA_SIGNALLING_MESSAGE: u32 = 0x412f;
|
||||
|
||||
|
||||
// Upper layers
|
||||
|
||||
pub const LOG_DATA_PROTOCOL_LOGGING_C: u32 = 0x11eb;
|
||||
|
||||
pub const LOG_UMTS_NAS_OTA_MESSAGE_LOG_PACKET_C: u32 = 0x713a;
|
||||
120
lib/src/pcap.rs
Normal file
120
lib/src/pcap.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use crate::gsmtap::GsmtapMessage;
|
||||
use crate::diag::Timestamp;
|
||||
|
||||
use std::io::Write;
|
||||
use std::borrow::Cow;
|
||||
use chrono::prelude::*;
|
||||
use deku::prelude::*;
|
||||
use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
|
||||
use pcap_file::pcapng::blocks::interface_description::InterfaceDescriptionBlock;
|
||||
use pcap_file::pcapng::PcapNgWriter;
|
||||
use pcap_file::PcapError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GsmtapPcapError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Pcap error: {0}")]
|
||||
Pcap(#[from] PcapError),
|
||||
#[error("Timestamp out of range: {0}")]
|
||||
TimestampOutOfRange(#[from] chrono::OutOfRangeError),
|
||||
#[error("Deku error: {0}")]
|
||||
Deku(#[from] DekuError),
|
||||
}
|
||||
|
||||
pub struct GsmtapPcapWriter<T> where T: Write {
|
||||
writer: PcapNgWriter<T>,
|
||||
ip_id: u16,
|
||||
}
|
||||
|
||||
const IP_HEADER_LEN: u16 = 20;
|
||||
#[derive(DekuWrite)]
|
||||
#[deku(endian = "big")]
|
||||
struct IpHeader {
|
||||
version_and_ihl: u8,
|
||||
dscp: u8,
|
||||
total_len: u16,
|
||||
identification: u16,
|
||||
flags_and_frag_offset: u8,
|
||||
idk: u8,
|
||||
ttl: u8,
|
||||
protocol: u8,
|
||||
checksum: u16,
|
||||
src_addr: u32,
|
||||
dst_addr: u32,
|
||||
}
|
||||
|
||||
const UDP_HEADER_LEN: u16 = 8;
|
||||
const GSMTAP_PORT: u16 = 4729;
|
||||
#[derive(DekuWrite)]
|
||||
#[deku(endian = "big")]
|
||||
struct UdpHeader {
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
length: u16,
|
||||
checksum: u16,
|
||||
}
|
||||
|
||||
impl<T> GsmtapPcapWriter<T> where T: Write {
|
||||
pub fn new(writer: T) -> Result<Self, GsmtapPcapError> {
|
||||
let writer = PcapNgWriter::new(writer)?;
|
||||
Ok(GsmtapPcapWriter { writer, ip_id: 0 })
|
||||
}
|
||||
|
||||
pub fn write_iface_header(&mut self) -> Result<(), GsmtapPcapError> {
|
||||
let interface = InterfaceDescriptionBlock {
|
||||
linktype: pcap_file::DataLink::IPV4,
|
||||
snaplen: 0xffff,
|
||||
options: vec![],
|
||||
};
|
||||
self.writer.write_pcapng_block(interface)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_gsmtap_message(&mut self, msg: GsmtapMessage, timestamp: Timestamp) -> Result<(), GsmtapPcapError> {
|
||||
let duration = timestamp.to_datetime()
|
||||
.signed_duration_since(DateTime::UNIX_EPOCH)
|
||||
.to_std()?;
|
||||
|
||||
// despite the timestamp above being correct, we have reduce it by
|
||||
// orders of magnitude due to a bug in pcap_file:
|
||||
// https://github.com/courvoif/pcap-file/pull/32
|
||||
let duration = std::time::Duration::from_nanos(duration.as_micros() as u64);
|
||||
|
||||
let msg_bytes = msg.to_bytes()?;
|
||||
let ip_header = IpHeader {
|
||||
version_and_ihl: 0x45,
|
||||
dscp: 0,
|
||||
total_len: msg_bytes.len() as u16 + IP_HEADER_LEN + UDP_HEADER_LEN,
|
||||
identification: self.ip_id,
|
||||
flags_and_frag_offset: 0x40,
|
||||
idk: 0,
|
||||
ttl: 64,
|
||||
protocol: 0x11, // UDP
|
||||
checksum: 0xffff,
|
||||
src_addr: 0x7f000001,
|
||||
dst_addr: 0x7f000001, // TODO increment by radio_id
|
||||
};
|
||||
let udp_header = UdpHeader {
|
||||
src_port: 13337,
|
||||
dst_port: GSMTAP_PORT,
|
||||
length: msg_bytes.len() as u16 + UDP_HEADER_LEN,
|
||||
checksum: 0xffff,
|
||||
};
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
data.extend(&ip_header.to_bytes()?);
|
||||
data.extend(&udp_header.to_bytes()?);
|
||||
data.extend(&msg_bytes);
|
||||
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)?;
|
||||
self.ip_id = self.ip_id.wrapping_add(1);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
212
lib/src/qmdl.rs
Normal file
212
lib/src/qmdl.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
//! Qualcomm Mobile Diagnostic Log (QMDL) files have a very simple format: just
|
||||
//! a series of of concatenated HDLC encapsulated diag::Message structs.
|
||||
//! QmdlReader and QmdlWriter can read and write MessagesContainers to and from
|
||||
//! QMDL files.
|
||||
|
||||
use crate::diag_reader::DiagReader;
|
||||
use crate::diag::{MessagesContainer, MESSAGE_TERMINATOR, HdlcEncapsulatedMessage, DataType};
|
||||
|
||||
use std::io::{Write, BufReader, BufRead, Read};
|
||||
use thiserror::Error;
|
||||
use log::error;
|
||||
|
||||
pub struct QmdlWriter<T> where T: Write {
|
||||
writer: T,
|
||||
pub total_written: usize,
|
||||
}
|
||||
|
||||
impl<T> QmdlWriter<T> where T: Write {
|
||||
pub fn new(writer: T) -> Self {
|
||||
QmdlWriter::new_with_existing_size(writer, 0)
|
||||
}
|
||||
|
||||
pub fn new_with_existing_size(writer: T, existing_size: usize) -> Self {
|
||||
QmdlWriter {
|
||||
writer,
|
||||
total_written: existing_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_container(&mut self, container: &MessagesContainer) -> std::io::Result<()> {
|
||||
for msg in &container.messages {
|
||||
self.writer.write_all(&msg.data)?;
|
||||
self.total_written += msg.data.len();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QmdlReaderError {
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Reached max_bytes count {0}")]
|
||||
MaxBytesReached(usize),
|
||||
}
|
||||
|
||||
pub struct QmdlReader<T> where T: Read {
|
||||
reader: BufReader<T>,
|
||||
bytes_read: usize,
|
||||
max_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T> QmdlReader<T> where T: Read {
|
||||
pub fn new(reader: T, max_bytes: Option<usize>) -> Self {
|
||||
QmdlReader {
|
||||
reader: BufReader::new(reader),
|
||||
bytes_read: 0,
|
||||
max_bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DiagReader for QmdlReader<T> where T: Read {
|
||||
type Err = QmdlReaderError;
|
||||
|
||||
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err> {
|
||||
if let Some(max_bytes) = self.max_bytes {
|
||||
if self.bytes_read >= max_bytes {
|
||||
if self.bytes_read > max_bytes {
|
||||
error!("warning: {} bytes read, but max_bytes was {}", self.bytes_read, max_bytes);
|
||||
}
|
||||
return Err(QmdlReaderError::MaxBytesReached(max_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let bytes_read = self.reader.read_until(MESSAGE_TERMINATOR, &mut buf)?;
|
||||
self.bytes_read += bytes_read;
|
||||
|
||||
// Since QMDL is just a flat list of messages, we can't actually
|
||||
// reproduce the container structure they came from in the original
|
||||
// read. So we'll just pretend that all containers had exactly one
|
||||
// message. As far as I know, the number of messages per container
|
||||
// doesn't actually affect anything, so this should be fine.
|
||||
Ok(MessagesContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
num_messages: 1,
|
||||
messages: vec![
|
||||
HdlcEncapsulatedMessage {
|
||||
len: bytes_read as u32,
|
||||
data: buf,
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::hdlc::hdlc_encapsulate;
|
||||
use crate::diag_reader::CRC_CCITT;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn get_test_messages() -> Vec<HdlcEncapsulatedMessage> {
|
||||
let messages: Vec<HdlcEncapsulatedMessage> = (10..20).map(|i| {
|
||||
let data = hdlc_encapsulate(&vec![i as u8; i], &CRC_CCITT);
|
||||
HdlcEncapsulatedMessage {
|
||||
len: data.len() as u32,
|
||||
data,
|
||||
}
|
||||
}).collect();
|
||||
messages
|
||||
}
|
||||
|
||||
// returns a byte array consisting of concatenated HDLC encapsulated
|
||||
// test messages
|
||||
fn get_test_message_bytes() -> Vec<u8> {
|
||||
get_test_messages().iter()
|
||||
.flat_map(|msg| msg.data.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_test_containers() -> Vec<MessagesContainer> {
|
||||
let messages = get_test_messages();
|
||||
let (messages1, messages2) = messages.split_at(5);
|
||||
vec![
|
||||
MessagesContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
num_messages: messages1.len() as u32,
|
||||
messages: messages1.to_vec(),
|
||||
},
|
||||
MessagesContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
num_messages: messages2.len() as u32,
|
||||
messages: messages2.to_vec()
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unbounded_qmdl_reader() {
|
||||
let mut buf = Cursor::new(get_test_message_bytes());
|
||||
let mut reader = QmdlReader::new(&mut buf, None);
|
||||
let expected_messages = get_test_messages();
|
||||
for message in expected_messages {
|
||||
let expected_container = MessagesContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
};
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bounded_qmdl_reader() {
|
||||
let mut buf = Cursor::new(get_test_message_bytes());
|
||||
|
||||
// bound the reader to the first two messages
|
||||
let mut expected_messages = get_test_messages();
|
||||
let limit = expected_messages[0].len + expected_messages[1].len;
|
||||
|
||||
let mut reader = QmdlReader::new(&mut buf, Some(limit as usize));
|
||||
for message in expected_messages.drain(0..2) {
|
||||
let expected_container = MessagesContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
};
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
|
||||
}
|
||||
assert!(matches!(reader.get_next_messages_container(), Err(QmdlReaderError::MaxBytesReached(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qmdl_writer() {
|
||||
let mut buf = Vec::new();
|
||||
let mut writer = QmdlWriter::new(&mut buf);
|
||||
let expected_containers = get_test_containers();
|
||||
for container in &expected_containers {
|
||||
writer.write_container(container).unwrap();
|
||||
}
|
||||
assert_eq!(writer.total_written, buf.len());
|
||||
assert_eq!(buf, get_test_message_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_writing_and_reading() {
|
||||
let mut buf = Vec::new();
|
||||
let mut writer = QmdlWriter::new(&mut buf);
|
||||
let expected_containers = get_test_containers();
|
||||
for container in &expected_containers {
|
||||
writer.write_container(container).unwrap();
|
||||
}
|
||||
|
||||
let limit = Some(buf.len());
|
||||
let mut reader = QmdlReader::new(Cursor::new(&mut buf), limit);
|
||||
let expected_messages = get_test_messages();
|
||||
for message in expected_messages {
|
||||
let expected_container = MessagesContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
};
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
|
||||
}
|
||||
assert!(matches!(reader.get_next_messages_container(), Err(QmdlReaderError::MaxBytesReached(_))));
|
||||
}
|
||||
}
|
||||
518
lib/tests/test_lte_parsing.rs
Normal file
518
lib/tests/test_lte_parsing.rs
Normal file
@@ -0,0 +1,518 @@
|
||||
use orca::diag::{
|
||||
Message,
|
||||
LogBody,
|
||||
LteRrcOtaPacket,
|
||||
Timestamp,
|
||||
};
|
||||
use orca::gsmtap_parser::GsmtapParser;
|
||||
use deku::prelude::*;
|
||||
|
||||
// Tests here are based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/tests/test_diagltelogparser.py
|
||||
|
||||
#[test]
|
||||
fn test_lte_rrc_ota() {
|
||||
let mut parser = GsmtapParser::new();
|
||||
let v26_binary = &[
|
||||
0x10, 0x0, 0x23, 0x0, 0x23, 0x0, 0xc0, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x1a, 0xf, 0x40, 0xf, 0x40, 0x1, 0xe, 0x1, 0x13, 0x7,
|
||||
0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x10, 0x15
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v26_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 0x23,
|
||||
inner_length: 0x23,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 0xb0c0,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 26,
|
||||
packet: LteRrcOtaPacket::V25 {
|
||||
rrc_rel_maj: 15,
|
||||
rrc_rel_min: 64,
|
||||
nr_rrc_rel_maj: 15,
|
||||
nr_rrc_rel_min: 64,
|
||||
bearer_id: 1,
|
||||
phy_cell_id: 270,
|
||||
earfcn: 1811,
|
||||
sfn_subfn: 0,
|
||||
pdu_num: 11,
|
||||
sib_mask: 0,
|
||||
len: 2,
|
||||
packet: vec![0x10, 0x15],
|
||||
}
|
||||
}
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[0x10, 0x15]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 1811);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 0);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 3);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 0);
|
||||
|
||||
let v26_binary = &[
|
||||
0x10, 0x00, 0x23, 0x00, 0x23, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x1a, 0x0f, 0x40, 0x0f, 0x40, 0x01, 0x0e, 0x01,
|
||||
0x13, 0x07, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x15,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v26_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 35,
|
||||
inner_length: 35,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 26,
|
||||
packet: LteRrcOtaPacket::V25 {
|
||||
rrc_rel_maj: 15,
|
||||
rrc_rel_min: 64,
|
||||
nr_rrc_rel_maj: 15,
|
||||
nr_rrc_rel_min: 64,
|
||||
bearer_id: 1,
|
||||
phy_cell_id: 270,
|
||||
earfcn: 1811,
|
||||
sfn_subfn: 0,
|
||||
pdu_num: 11,
|
||||
sib_mask: 0,
|
||||
len: 2,
|
||||
packet: vec![0x10, 0x15],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x10, 0x15,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 1811);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 0);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 3);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 0);
|
||||
|
||||
let v24_binary = &[
|
||||
0x10, 0x00, 0x2c, 0x00, 0x2c, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x0f, 0x22, 0x00, 0x68, 0x00, 0xe4, 0x0c,
|
||||
0x00, 0x00, 0x09, 0xdc, 0x05, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0d, 0x00, 0x40, 0x85, 0x8e, 0xc4, 0xe5,
|
||||
0xbf, 0xe0, 0x50, 0xdc, 0x29, 0x15, 0x16, 0x00,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v24_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 44,
|
||||
inner_length: 44,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 24,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 15,
|
||||
rrc_rel_min: 34,
|
||||
bearer_id: 0,
|
||||
phy_cell_id: 104,
|
||||
earfcn: 3300,
|
||||
sfn_subfn: 56329,
|
||||
pdu_num: 5,
|
||||
sib_mask: 0,
|
||||
len: 13,
|
||||
packet: vec![
|
||||
0x40, 0x85, 0x8e, 0xc4, 0xe5, 0xbf, 0xe0, 0x50, 0xdc, 0x29,
|
||||
0x15, 0x16, 0x0
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x40, 0x85, 0x8e, 0xc4, 0xe5, 0xbf, 0xe0, 0x50,
|
||||
0xdc, 0x29, 0x15, 0x16, 0x00,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 3300);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 3520);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 6);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 9);
|
||||
|
||||
let v20_binary = &[
|
||||
0x10, 0x00, 0x37, 0x00, 0x37, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x14, 0x0e, 0x30, 0x01, 0x09, 0x01, 0x9c, 0x18,
|
||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x00, 0x18, 0x00, 0x08, 0x10, 0xa7, 0x14, 0x53,
|
||||
0x59, 0xa6, 0x05, 0x43, 0x68, 0xc0, 0x3b, 0xda,
|
||||
0x30, 0x04, 0xa6, 0x88, 0x02, 0x8d, 0xa2, 0x00,
|
||||
0x9a, 0x68, 0x40,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v20_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 55,
|
||||
inner_length: 55,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 20,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 14,
|
||||
rrc_rel_min: 48,
|
||||
bearer_id: 1,
|
||||
phy_cell_id: 265,
|
||||
earfcn: 6300,
|
||||
sfn_subfn: 0,
|
||||
pdu_num: 9,
|
||||
sib_mask: 0,
|
||||
len: 24,
|
||||
packet: vec![
|
||||
0x8, 0x10, 0xa7, 0x14, 0x53, 0x59, 0xa6, 0x5, 0x43, 0x68,
|
||||
0xc0, 0x3b, 0xda, 0x30, 0x4, 0xa6, 0x88, 0x2, 0x8d, 0xa2,
|
||||
0x0, 0x9a, 0x68, 0x40
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x08, 0x10, 0xa7, 0x14, 0x53, 0x59, 0xa6, 0x05,
|
||||
0x43, 0x68, 0xc0, 0x3b, 0xda, 0x30, 0x04, 0xa6,
|
||||
0x88, 0x02, 0x8d, 0xa2, 0x00, 0x9a, 0x68, 0x40,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 6300);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 0);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 3);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 0);
|
||||
|
||||
let v19_binary = &[
|
||||
0x10, 0x00, 0x28, 0x00, 0x28, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x13, 0x0e, 0x22, 0x00, 0x0b, 0x00, 0xfa, 0x09,
|
||||
0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x00, 0x09, 0x00, 0x28, 0x18, 0x40, 0x16, 0x08,
|
||||
0x08, 0x80, 0x00, 0x00,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v19_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 40,
|
||||
inner_length: 40,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 19,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 14,
|
||||
rrc_rel_min: 34,
|
||||
bearer_id: 0,
|
||||
phy_cell_id: 11,
|
||||
earfcn: 2554,
|
||||
sfn_subfn: 0,
|
||||
pdu_num: 50,
|
||||
sib_mask: 0,
|
||||
len: 9,
|
||||
packet: vec![0x28, 0x18, 0x40, 0x16, 0x8, 0x8, 0x80, 0x0, 0x0],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x28, 0x18, 0x40, 0x16, 0x08, 0x08, 0x80, 0x00,
|
||||
0x00,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 2554);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 0);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 16);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 0);
|
||||
|
||||
let v15_binary = &[
|
||||
0x10, 0x00, 0x26, 0x00, 0x26, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0f, 0x0d, 0x21, 0x00, 0x9e, 0x00, 0x14, 0x05,
|
||||
0x00, 0x00, 0x49, 0x8c, 0x05, 0x00, 0x00, 0x00,
|
||||
0x00, 0x07, 0x00, 0x40, 0x0c, 0x8e, 0xc9, 0x42,
|
||||
0x89, 0xe0,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v15_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 38,
|
||||
inner_length: 38,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 15,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 13,
|
||||
rrc_rel_min: 33,
|
||||
bearer_id: 0,
|
||||
phy_cell_id: 158,
|
||||
earfcn: 1300,
|
||||
sfn_subfn: 35913,
|
||||
pdu_num: 5,
|
||||
sib_mask: 0,
|
||||
len: 7,
|
||||
packet: vec![0x40, 0xc, 0x8e, 0xc9, 0x42, 0x89, 0xe0],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x40, 0x0c, 0x8e, 0xc9, 0x42, 0x89, 0xe0,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 1300);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 2244);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 6);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 9);
|
||||
|
||||
let v15_binary = &[
|
||||
0x10, 0x00, 0x3b, 0x00, 0x3b, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0f, 0x0d, 0x21, 0x01, 0x9e, 0x00, 0x14, 0x05,
|
||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x00, 0x1c, 0x00, 0x08, 0x10, 0xa5, 0x34, 0x61,
|
||||
0x41, 0xa3, 0x1c, 0x31, 0x68, 0x04, 0x40, 0x1a,
|
||||
0x00, 0x49, 0x16, 0x7c, 0x23, 0x15, 0x9f, 0x00,
|
||||
0x10, 0x67, 0xc1, 0x06, 0xd9, 0xe0, 0x00,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v15_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 59,
|
||||
inner_length: 59,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 15,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 13,
|
||||
rrc_rel_min: 33,
|
||||
bearer_id: 1,
|
||||
phy_cell_id: 158,
|
||||
earfcn: 1300,
|
||||
sfn_subfn: 0,
|
||||
pdu_num: 9,
|
||||
sib_mask: 0,
|
||||
len: 28,
|
||||
packet: vec![
|
||||
0x8, 0x10, 0xa5, 0x34, 0x61, 0x41, 0xa3, 0x1c, 0x31, 0x68,
|
||||
0x4, 0x40, 0x1a, 0x0, 0x49, 0x16, 0x7c, 0x23, 0x15, 0x9f,
|
||||
0x0, 0x10, 0x67, 0xc1, 0x6, 0xd9, 0xe0, 0x0
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x08, 0x10, 0xa5, 0x34, 0x61, 0x41, 0xa3, 0x1c,
|
||||
0x31, 0x68, 0x04, 0x40, 0x1a, 0x00, 0x49, 0x16,
|
||||
0x7c, 0x23, 0x15, 0x9f, 0x00, 0x10, 0x67, 0xc1,
|
||||
0x06, 0xd9, 0xe0, 0x00,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 1300);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 0);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 3);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 0);
|
||||
|
||||
let v13_binary = &[
|
||||
0x10, 0x00, 0x21, 0x00, 0x21, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0d, 0x0c, 0x74, 0x01, 0x32, 0x00, 0x38, 0x18,
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x2c, 0x00,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v13_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 33,
|
||||
inner_length: 33,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 13,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 12,
|
||||
rrc_rel_min: 116,
|
||||
bearer_id: 1,
|
||||
phy_cell_id: 50,
|
||||
earfcn: 6200,
|
||||
sfn_subfn: 0,
|
||||
pdu_num: 8,
|
||||
sib_mask: 0,
|
||||
len: 2,
|
||||
packet: vec![0x2c, 0x0],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[0x2c, 0x00]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 6200);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 0);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 3);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 0);
|
||||
|
||||
let v9_binary = &[
|
||||
0x10, 0x00, 0x26, 0x00, 0x26, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x09, 0x0b, 0x70, 0x00, 0x00, 0x01, 0x14, 0x05,
|
||||
0x00, 0x00, 0x09, 0x91, 0x0b, 0x00, 0x00, 0x00,
|
||||
0x00, 0x07, 0x00, 0x40, 0x0b, 0x8e, 0xc1, 0xdd,
|
||||
0x13, 0xb0,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v9_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 38,
|
||||
inner_length: 38,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 9,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 11,
|
||||
rrc_rel_min: 112,
|
||||
bearer_id: 0,
|
||||
phy_cell_id: 256,
|
||||
earfcn: 1300,
|
||||
sfn_subfn: 37129,
|
||||
pdu_num: 11,
|
||||
sib_mask: 0,
|
||||
len: 7,
|
||||
packet: vec![0x40, 0xb, 0x8e, 0xc1, 0xdd, 0x13, 0xb0],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x40, 0x0b, 0x8e, 0xc1, 0xdd, 0x13, 0xb0,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 1300);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 2320);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 6);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 9);
|
||||
|
||||
let v8_binary = &[
|
||||
0x10, 0x00, 0x21, 0x00, 0x21, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x08, 0x0a, 0x72, 0x01, 0x0e, 0x00, 0x9c, 0x18,
|
||||
0x00, 0x00, 0xa9, 0x33, 0x06, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x2e, 0x02,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v8_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 33,
|
||||
inner_length: 33,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 8,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 10,
|
||||
rrc_rel_min: 114,
|
||||
bearer_id: 1,
|
||||
phy_cell_id: 14,
|
||||
earfcn: 6300,
|
||||
sfn_subfn: 13225,
|
||||
pdu_num: 6,
|
||||
sib_mask: 0,
|
||||
len: 2,
|
||||
packet: vec![0x2e, 0x2],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[0x2e, 0x02]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 6300);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 826);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 1);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 9);
|
||||
|
||||
let v6_binary = &[
|
||||
0x10, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0xc0, 0xb0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x06, 0x09, 0xb1, 0x00, 0x07, 0x01, 0x2c, 0x07,
|
||||
0x25, 0x34, 0x02, 0x02, 0x00, 0x00, 0x00, 0x12,
|
||||
0x00, 0x40, 0x49, 0x88, 0x05, 0xc0, 0x97, 0x02,
|
||||
0xd3, 0xb0, 0x98, 0x1c, 0x20, 0xa0, 0x81, 0x8c,
|
||||
0x43, 0x26, 0xd0,
|
||||
];
|
||||
let (_, parsed) = Message::from_bytes((v6_binary, 0)).unwrap();
|
||||
assert_eq!(&parsed, &Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: 47,
|
||||
inner_length: 47,
|
||||
timestamp: Timestamp { ts: 0 },
|
||||
log_type: 45248,
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 6,
|
||||
packet: LteRrcOtaPacket::V5 {
|
||||
rrc_rel_maj: 9,
|
||||
rrc_rel_min: 177,
|
||||
bearer_id: 0,
|
||||
phy_cell_id: 263,
|
||||
earfcn: 1836,
|
||||
sfn_subfn: 13349,
|
||||
pdu_num: 2,
|
||||
sib_mask: 2,
|
||||
len: 18,
|
||||
packet: vec![
|
||||
0x40, 0x49, 0x88, 0x5, 0xc0, 0x97, 0x2, 0xd3, 0xb0, 0x98,
|
||||
0x1c, 0x20, 0xa0, 0x81, 0x8c, 0x43, 0x26, 0xd0
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
let (_, gsmtap_msg) = parser.recv_message(parsed).unwrap().unwrap();
|
||||
assert_eq!(&gsmtap_msg.payload, &[
|
||||
0x40, 0x49, 0x88, 0x05, 0xc0, 0x97, 0x02, 0xd3,
|
||||
0xb0, 0x98, 0x1c, 0x20, 0xa0, 0x81, 0x8c, 0x43,
|
||||
0x26, 0xd0,
|
||||
]);
|
||||
assert_eq!(gsmtap_msg.header.packet_type, 13);
|
||||
assert_eq!(gsmtap_msg.header.timeslot, 0);
|
||||
assert_eq!(gsmtap_msg.header.arfcn, 1836);
|
||||
assert_eq!(gsmtap_msg.header.signal_dbm, 0);
|
||||
assert_eq!(gsmtap_msg.header.signal_noise_ratio_db, 0);
|
||||
assert_eq!(gsmtap_msg.header.frame_number, 834);
|
||||
assert_eq!(gsmtap_msg.header.subtype, 5);
|
||||
assert_eq!(gsmtap_msg.header.subslot, 5);
|
||||
}
|
||||
Reference in New Issue
Block a user