chore: cargo fmt

This commit is contained in:
oopsbagel
2025-04-13 22:00:54 -07:00
committed by Will Greenberg
parent 151e186ef9
commit 9fe75ac961
37 changed files with 1246 additions and 819 deletions
+9 -3
View File
@@ -18,9 +18,9 @@ use tokio::sync::mpsc::Receiver;
use tokio::sync::{RwLock, RwLockWriteGuard};
use tokio_util::task::TaskTracker;
use crate::dummy_analyzer::TestAnalyzer;
use crate::qmdl_store::RecordingStore;
use crate::server::ServerState;
use crate::dummy_analyzer::TestAnalyzer;
pub struct AnalysisWriter {
writer: BufWriter<File>,
@@ -53,7 +53,10 @@ impl AnalysisWriter {
// Runs the analysis harness on the given container, serializing the results
// to the analysis file and returning the file's new length.
pub async fn analyze(&mut self, container: MessagesContainer) -> Result<(usize, bool), std::io::Error> {
pub async fn analyze(
&mut self,
container: MessagesContainer,
) -> Result<(usize, bool), std::io::Error> {
let row = self.harness.analyze_qmdl_messages(container);
if !row.is_empty() {
self.write(&row).await?;
@@ -182,7 +185,10 @@ pub fn run_analysis_thread(
let count = queued_len(analysis_status_lock.clone()).await;
for _ in 0..count {
let name = dequeue_to_running(analysis_status_lock.clone()).await;
if let Err(err) = perform_analysis(&name, qmdl_store_lock.clone(), enable_dummy_analyzer).await {
if let Err(err) =
perform_analysis(&name, qmdl_store_lock.clone(), enable_dummy_analyzer)
.await
{
error!("failed to analyze {}: {}", name, err);
}
clear_running(analysis_status_lock.clone()).await;
+48 -21
View File
@@ -1,9 +1,15 @@
use std::{collections::HashMap, future, path::PathBuf, pin::pin};
use log::{info, warn};
use rayhunter::{analysis::analyzer::{EventType, Harness}, diag::DataType, gsmtap_parser, pcap::GsmtapPcapWriter, qmdl::QmdlReader};
use tokio::fs::{metadata, read_dir, File};
use clap::Parser;
use futures::TryStreamExt;
use log::{info, warn};
use rayhunter::{
analysis::analyzer::{EventType, Harness},
diag::DataType,
gsmtap_parser,
pcap::GsmtapPcapWriter,
qmdl::QmdlReader,
};
use std::{collections::HashMap, future, path::PathBuf, pin::pin};
use tokio::fs::{metadata, read_dir, File};
mod dummy_analyzer;
@@ -28,15 +34,24 @@ struct Args {
async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool) {
let qmdl_file = &mut File::open(&qmdl_path).await.expect("failed to open file");
let file_size = qmdl_file.metadata().await.expect("failed to get QMDL file metadata").len();
let file_size = qmdl_file
.metadata()
.await
.expect("failed to get QMDL file metadata")
.len();
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(file_size as usize));
let mut qmdl_stream = pin!(qmdl_reader.as_stream()
let mut qmdl_stream = pin!(qmdl_reader
.as_stream()
.try_filter(|container| future::ready(container.data_type == DataType::UserSpace)));
let mut skipped_reasons: HashMap<String, i32> = HashMap::new();
let mut total_messages = 0;
let mut warnings = 0;
let mut skipped = 0;
while let Some(container) = qmdl_stream.try_next().await.expect("failed getting QMDL container") {
while let Some(container) = qmdl_stream
.try_next()
.await
.expect("failed getting QMDL container")
{
let row = harness.analyze_qmdl_messages(container);
total_messages += 1;
for reason in row.skipped_message_reasons {
@@ -50,18 +65,13 @@ async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool
EventType::Informational => {
info!(
"{}: INFO - {} {}",
qmdl_path,
analysis.timestamp,
event.message,
qmdl_path, analysis.timestamp, event.message,
);
}
EventType::QualitativeWarning { severity } => {
warn!(
"{}: WARNING (Severity: {:?}) - {} {}",
qmdl_path,
severity,
analysis.timestamp,
event.message,
qmdl_path, severity, analysis.timestamp, event.message,
);
warnings += 1;
}
@@ -75,22 +85,36 @@ async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool
info!(" - {}: \"{}\"", count, reason);
}
}
info!("{}: {} messages analyzed, {} warnings, {} messages skipped", qmdl_path, total_messages, warnings, skipped);
info!(
"{}: {} messages analyzed, {} warnings, {} messages skipped",
qmdl_path, total_messages, warnings, skipped
);
}
async fn pcapify(qmdl_path: &PathBuf) {
let qmdl_file = &mut File::open(&qmdl_path).await.expect("failed to open qmdl file");
let qmdl_file = &mut File::open(&qmdl_path)
.await
.expect("failed to open qmdl file");
let qmdl_file_size = qmdl_file.metadata().await.unwrap().len();
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(qmdl_file_size as usize));
let mut pcap_path = qmdl_path.clone();
pcap_path.set_extension("pcap");
let pcap_file = &mut File::create(&pcap_path).await.expect("failed to open pcap file");
let pcap_file = &mut File::create(&pcap_path)
.await
.expect("failed to open pcap file");
let mut pcap_writer = GsmtapPcapWriter::new(pcap_file).await.unwrap();
pcap_writer.write_iface_header().await.unwrap();
while let Some(container) = qmdl_reader.get_next_messages_container().await.expect("failed to get container") {
while let Some(container) = qmdl_reader
.get_next_messages_container()
.await
.expect("failed to get container")
{
for msg in container.into_messages().into_iter().flatten() {
if let Ok(Some((timestamp, parsed))) = gsmtap_parser::parse(msg) {
pcap_writer.write_gsmtap_message(parsed, timestamp).await.expect("failed to write");
pcap_writer
.write_gsmtap_message(parsed, timestamp)
.await
.expect("failed to write");
}
}
}
@@ -109,7 +133,8 @@ async fn main() {
.with_colors(true)
.without_timestamps()
.with_level(level)
.init().unwrap();
.init()
.unwrap();
let mut harness = Harness::new_with_all_analyzers();
if args.enable_dummy_analyzer {
@@ -120,7 +145,9 @@ async fn main() {
info!(" - {}: {}", analyzer.name, analyzer.description);
}
let metadata = metadata(&args.qmdl_path).await.expect("failed to get metadata");
let metadata = metadata(&args.qmdl_path)
.await
.expect("failed to get metadata");
if metadata.is_dir() {
let mut dir = read_dir(&args.qmdl_path).await.expect("failed to read dir");
while let Some(entry) = dir.next_entry().await.expect("failed to get entry") {
+5 -3
View File
@@ -2,8 +2,7 @@ use crate::error::RayhunterError;
use serde::Deserialize;
#[derive(Debug)]
#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct Config {
pub qmdl_store_path: String,
@@ -27,7 +26,10 @@ impl Default for Config {
}
}
pub fn parse_config<P>(path: P) -> Result<Config, RayhunterError> where P: AsRef<std::path::Path> {
pub fn parse_config<P>(path: P) -> Result<Config, RayhunterError>
where
P: AsRef<std::path::Path>,
{
if let Ok(config_file) = std::fs::read_to_string(&path) {
Ok(toml::from_str(&config_file).map_err(RayhunterError::ConfigFileParsingError)?)
} else {
+68 -33
View File
@@ -1,38 +1,43 @@
mod analysis;
mod config;
mod error;
mod pcap;
mod server;
mod stats;
mod qmdl_store;
mod diag;
mod display;
mod dummy_analyzer;
mod error;
mod pcap;
mod qmdl_store;
mod server;
mod stats;
use crate::config::{parse_config, parse_args};
use crate::config::{parse_args, parse_config};
use crate::diag::run_diag_read_thread;
use crate::qmdl_store::RecordingStore;
use crate::server::{ServerState, get_qmdl, serve_static};
use crate::pcap::get_pcap;
use crate::stats::get_system_stats;
use crate::error::RayhunterError;
use crate::pcap::get_pcap;
use crate::qmdl_store::RecordingStore;
use crate::server::{get_qmdl, serve_static, ServerState};
use crate::stats::get_system_stats;
use analysis::{get_analysis_status, run_analysis_thread, start_analysis, AnalysisCtrlMessage, AnalysisStatus};
use analysis::{
get_analysis_status, run_analysis_thread, start_analysis, AnalysisCtrlMessage, AnalysisStatus,
};
use axum::response::Redirect;
use diag::{delete_all_recordings, delete_recording, get_analysis_report, start_recording, stop_recording, DiagDeviceCtrlMessage};
use log::{info, error};
use qmdl_store::RecordingStoreError;
use rayhunter::diag_device::DiagDevice;
use axum::routing::{get, post};
use axum::Router;
use diag::{
delete_all_recordings, delete_recording, get_analysis_report, start_recording, stop_recording,
DiagDeviceCtrlMessage,
};
use log::{error, info};
use qmdl_store::RecordingStoreError;
use rayhunter::diag_device::DiagDevice;
use stats::get_qmdl_manifest;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio::sync::mpsc::{self, Sender};
use tokio::sync::{oneshot, RwLock};
use tokio::task::JoinHandle;
use tokio_util::task::TaskTracker;
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tokio::sync::{RwLock, oneshot};
use std::sync::Arc;
type AppRouter = Router<Arc<ServerState>>;
@@ -70,7 +75,8 @@ async fn run_server(
info!("The orca is hunting for stingrays...");
axum::serve(listener, app)
.with_graceful_shutdown(server_shutdown_signal(server_shutdown_rx))
.await.unwrap();
.await
.unwrap();
})
}
@@ -88,7 +94,9 @@ async fn init_qmdl_store(config: &config::Config) -> Result<RecordingStore, Rayh
if store_exists {
Ok(RecordingStore::load(&config.qmdl_store_path).await?)
} else {
Err(RayhunterError::NoStoreDebugMode(config.qmdl_store_path.clone()))
Err(RayhunterError::NoStoreDebugMode(
config.qmdl_store_path.clone(),
))
}
} else if store_exists {
match RecordingStore::load(&config.qmdl_store_path).await {
@@ -97,7 +105,7 @@ async fn init_qmdl_store(config: &config::Config) -> Result<RecordingStore, Rayh
error!("failed to parse QMDL manifest: {}", err);
info!("creating new empty manifest...");
Ok(RecordingStore::create(&config.qmdl_store_path).await?)
},
}
Err(err) => Err(err.into()),
}
} else {
@@ -126,18 +134,24 @@ fn run_ctrl_c_thread(
info!("Done!");
}
server_shutdown_tx.send(())
server_shutdown_tx
.send(())
.expect("couldn't send server shutdown signal");
info!("sending UI shutdown");
if let Some(ui_shutdown_tx) = maybe_ui_shutdown_tx {
ui_shutdown_tx.send(())
ui_shutdown_tx
.send(())
.expect("couldn't send ui shutdown signal");
}
diag_device_sender.send(DiagDeviceCtrlMessage::Exit).await
diag_device_sender
.send(DiagDeviceCtrlMessage::Exit)
.await
.expect("couldn't send Exit message to diag thread");
analysis_tx.send(AnalysisCtrlMessage::Exit).await
analysis_tx
.send(AnalysisCtrlMessage::Exit)
.await
.expect("couldn't send Exit message to analysis thread");
},
}
Err(err) => {
error!("Unable to listen for shutdown signal: {}", err);
}
@@ -146,7 +160,6 @@ fn run_ctrl_c_thread(
})
}
#[tokio::main]
async fn main() -> Result<(), RayhunterError> {
env_logger::init();
@@ -167,21 +180,43 @@ async fn main() -> Result<(), RayhunterError> {
if !config.debug_mode {
let (ui_shutdown_tx, ui_shutdown_rx) = oneshot::channel();
maybe_ui_shutdown_tx = Some(ui_shutdown_tx);
let mut dev = DiagDevice::new().await
let mut dev = DiagDevice::new()
.await
.map_err(RayhunterError::DiagInitError)?;
dev.config_logs().await
dev.config_logs()
.await
.map_err(RayhunterError::DiagInitError)?;
info!("Starting Diag Thread");
run_diag_read_thread(&task_tracker, dev, rx, ui_update_tx.clone(), qmdl_store_lock.clone(), config.enable_dummy_analyzer);
run_diag_read_thread(
&task_tracker,
dev,
rx,
ui_update_tx.clone(),
qmdl_store_lock.clone(),
config.enable_dummy_analyzer,
);
info!("Starting UI");
display::update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx);
}
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
info!("create shutdown thread");
let analysis_status_lock = Arc::new(RwLock::new(AnalysisStatus::default()));
run_analysis_thread(&task_tracker, analysis_rx, qmdl_store_lock.clone(), analysis_status_lock.clone(), config.enable_dummy_analyzer);
run_ctrl_c_thread(&task_tracker, tx.clone(), server_shutdown_tx, maybe_ui_shutdown_tx, qmdl_store_lock.clone(), analysis_tx.clone());
run_analysis_thread(
&task_tracker,
analysis_rx,
qmdl_store_lock.clone(),
analysis_status_lock.clone(),
config.enable_dummy_analyzer,
);
run_ctrl_c_thread(
&task_tracker,
tx.clone(),
server_shutdown_tx,
maybe_ui_shutdown_tx,
qmdl_store_lock.clone(),
analysis_tx.clone(),
);
let state = Arc::new(ServerState {
qmdl_store_lock: qmdl_store_lock.clone(),
diag_device_ctrl_sender: tx,
+137 -37
View File
@@ -6,21 +6,21 @@ use axum::extract::{Path, State};
use axum::http::header::CONTENT_TYPE;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use futures::{StreamExt, TryStreamExt};
use log::{debug, error, info};
use rayhunter::diag::DataType;
use rayhunter::diag_device::DiagDevice;
use tokio::sync::RwLock;
use tokio::sync::mpsc::{Receiver, Sender};
use rayhunter::qmdl::QmdlWriter;
use log::{debug, error, info};
use tokio::fs::File;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::RwLock;
use tokio_util::io::ReaderStream;
use tokio_util::task::TaskTracker;
use futures::{StreamExt, TryStreamExt};
use crate::analysis::AnalysisWriter;
use crate::display;
use crate::qmdl_store::{RecordingStore, RecordingStoreError};
use crate::server::ServerState;
use crate::analysis::AnalysisWriter;
pub enum DiagDeviceCtrlMessage {
StopRecording,
@@ -119,35 +119,82 @@ pub fn run_diag_read_thread(
});
}
pub async fn start_recording(State(state): State<Arc<ServerState>>) -> Result<(StatusCode, String), (StatusCode, String)> {
pub async fn start_recording(
State(state): State<Arc<ServerState>>,
) -> Result<(StatusCode, String), (StatusCode, String)> {
if state.debug_mode {
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
}
let mut qmdl_store = state.qmdl_store_lock.write().await;
let (qmdl_file, analysis_file) = qmdl_store.new_entry().await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't create new qmdl entry: {}", e)))?;
let (qmdl_file, analysis_file) = qmdl_store.new_entry().await.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't create new qmdl entry: {}", e),
)
})?;
let qmdl_writer = QmdlWriter::new(qmdl_file);
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StartRecording((qmdl_writer, analysis_file))).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
state
.diag_device_ctrl_sender
.send(DiagDeviceCtrlMessage::StartRecording((
qmdl_writer,
analysis_file,
)))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send stop recording message: {}", e),
)
})?;
let display_state = display::DisplayState::Recording;
state.ui_update_sender.send(display_state).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?;
state
.ui_update_sender
.send(display_state)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send ui update message: {}", e),
)
})?;
Ok((StatusCode::ACCEPTED, "ok".to_string()))
}
pub async fn stop_recording(State(state): State<Arc<ServerState>>) -> Result<(StatusCode, String), (StatusCode, String)> {
pub async fn stop_recording(
State(state): State<Arc<ServerState>>,
) -> Result<(StatusCode, String), (StatusCode, String)> {
if state.debug_mode {
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
}
let mut qmdl_store = state.qmdl_store_lock.write().await;
qmdl_store.close_current_entry().await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't close current qmdl entry: {}", e)))?;
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
state.ui_update_sender.send(display::DisplayState::Paused).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?;
qmdl_store.close_current_entry().await.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't close current qmdl entry: {}", e),
)
})?;
state
.diag_device_ctrl_sender
.send(DiagDeviceCtrlMessage::StopRecording)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send stop recording message: {}", e),
)
})?;
state
.ui_update_sender
.send(display::DisplayState::Paused)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send ui update message: {}", e),
)
})?;
Ok((StatusCode::ACCEPTED, "ok".to_string()))
}
@@ -160,45 +207,98 @@ pub async fn delete_recording(
}
let mut qmdl_store = state.qmdl_store_lock.write().await;
match qmdl_store.delete_entry(&qmdl_name).await {
Err(RecordingStoreError::NoSuchEntryError) => return Err((StatusCode::BAD_REQUEST, format!("no recording with name {qmdl_name}"))),
Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't delete recording: {e}"))),
Ok(_) => {},
Err(RecordingStoreError::NoSuchEntryError) => {
return Err((
StatusCode::BAD_REQUEST,
format!("no recording with name {qmdl_name}"),
))
}
Err(e) => {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't delete recording: {e}"),
))
}
Ok(_) => {}
}
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
state.ui_update_sender.send(display::DisplayState::Paused).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?;
state
.diag_device_ctrl_sender
.send(DiagDeviceCtrlMessage::StopRecording)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send stop recording message: {}", e),
)
})?;
state
.ui_update_sender
.send(display::DisplayState::Paused)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send ui update message: {}", e),
)
})?;
Ok((StatusCode::ACCEPTED, "ok".to_string()))
}
pub async fn delete_all_recordings(State(state): State<Arc<ServerState>>) -> Result<(StatusCode, String), (StatusCode, String)> {
pub async fn delete_all_recordings(
State(state): State<Arc<ServerState>>,
) -> Result<(StatusCode, String), (StatusCode, String)> {
if state.debug_mode {
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
}
let mut qmdl_store = state.qmdl_store_lock.write().await;
qmdl_store.delete_all_entries().await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't delete all recordings: {}", e)))?;
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
state.ui_update_sender.send(display::DisplayState::Paused).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?;
qmdl_store.delete_all_entries().await.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't delete all recordings: {}", e),
)
})?;
state
.diag_device_ctrl_sender
.send(DiagDeviceCtrlMessage::StopRecording)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send stop recording message: {}", e),
)
})?;
state
.ui_update_sender
.send(display::DisplayState::Paused)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("couldn't send ui update message: {}", e),
)
})?;
Ok((StatusCode::ACCEPTED, "ok".to_string()))
}
pub async fn get_analysis_report(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
pub async fn get_analysis_report(
State(state): State<Arc<ServerState>>,
Path(qmdl_name): Path<String>,
) -> Result<Response, (StatusCode, String)> {
let qmdl_store = state.qmdl_store_lock.read().await;
let (entry_index, _) = if qmdl_name == "live" {
qmdl_store.get_current_entry().ok_or((
StatusCode::SERVICE_UNAVAILABLE,
"No QMDL data's being recorded to analyze, try starting a new recording!".to_string()
"No QMDL data's being recorded to analyze, try starting a new recording!".to_string(),
))?
} else {
qmdl_store.entry_for_name(&qmdl_name).ok_or((
StatusCode::NOT_FOUND,
format!("Couldn't find QMDL entry with name \"{}\"", qmdl_name)
format!("Couldn't find QMDL entry with name \"{}\"", qmdl_name),
))?
};
let analysis_file = qmdl_store.open_entry_analysis(entry_index).await
let analysis_file = qmdl_store
.open_entry_analysis(entry_index)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e)))?;
let analysis_stream = ReaderStream::new(analysis_file);
+4 -7
View File
@@ -1,9 +1,9 @@
use crate::config;
use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer};
use crate::display::DisplayState;
use crate::display::generic_framebuffer::{self, GenericFramebuffer, Dimensions};
use tokio::sync::oneshot;
use tokio::sync::mpsc::Receiver;
use tokio::sync::oneshot;
use tokio_util::task::TaskTracker;
const FB_PATH: &str = "/dev/fb0";
@@ -20,10 +20,7 @@ impl GenericFramebuffer for Framebuffer {
}
}
fn write_buffer(
&mut self,
buffer: &[(u8, u8, u8)],
) {
fn write_buffer(&mut self, buffer: &[(u8, u8, u8)]) {
let mut raw_buffer = Vec::new();
for (r, g, b) in buffer {
let mut rgb565: u16 = (*r as u16 & 0b11111000) << 8;
@@ -40,7 +37,7 @@ pub fn update_ui(
task_tracker: &TaskTracker,
config: &config::Config,
ui_shutdown_rx: oneshot::Receiver<()>,
ui_update_rx: Receiver<DisplayState>
ui_update_rx: Receiver<DisplayState>,
) {
generic_framebuffer::update_ui(
task_tracker,
+3 -13
View File
@@ -4,7 +4,7 @@ use tokio::sync::oneshot;
use tokio_util::task::TaskTracker;
use crate::config;
use crate::display::{DisplayState, tplink_onebit, tplink_framebuffer};
use crate::display::{tplink_framebuffer, tplink_onebit, DisplayState};
use std::fs;
@@ -21,19 +21,9 @@ pub fn update_ui(
if fs::exists(tplink_onebit::OLED_PATH).unwrap_or_default() {
info!("detected one-bit display");
tplink_onebit::update_ui(
task_tracker,
config,
ui_shutdown_rx,
ui_update_rx
)
tplink_onebit::update_ui(task_tracker, config, ui_shutdown_rx, ui_update_rx)
} else {
info!("fallback to framebuffer");
tplink_framebuffer::update_ui(
task_tracker,
config,
ui_shutdown_rx,
ui_update_rx
)
tplink_framebuffer::update_ui(task_tracker, config, ui_shutdown_rx, ui_update_rx)
}
}
+5 -8
View File
@@ -3,11 +3,11 @@ use std::io::Write;
use std::os::fd::AsRawFd;
use crate::config;
use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer};
use crate::display::DisplayState;
use crate::display::generic_framebuffer::{self, GenericFramebuffer, Dimensions};
use tokio::sync::oneshot;
use tokio::sync::mpsc::Receiver;
use tokio::sync::oneshot;
use tokio_util::task::TaskTracker;
const FB_PATH: &str = "/dev/fb0";
@@ -33,10 +33,7 @@ impl GenericFramebuffer for Framebuffer {
}
}
fn write_buffer(
&mut self,
buffer: &[(u8, u8, u8)],
) {
fn write_buffer(&mut self, buffer: &[(u8, u8, u8)]) {
// for how to write to the buffer, consult M7350v5_en_gpl/bootable/recovery/recovery_color_oled.c
let dimensions = self.dimensions();
let width = dimensions.width;
@@ -65,7 +62,7 @@ impl GenericFramebuffer for Framebuffer {
unsafe {
let res = libc::ioctl(
f.as_raw_fd(),
0x4619, // FBIORECT_DISPLAY
0x4619, // FBIORECT_DISPLAY
&mut arg as *mut _,
std::mem::size_of::<fb_fillrect>(),
);
@@ -81,7 +78,7 @@ pub fn update_ui(
task_tracker: &TaskTracker,
config: &config::Config,
ui_shutdown_rx: oneshot::Receiver<()>,
ui_update_rx: Receiver<DisplayState>
ui_update_rx: Receiver<DisplayState>,
) {
generic_framebuffer::update_ui(
task_tracker,
+10 -8
View File
@@ -5,11 +5,11 @@ use rayhunter::telcom_parser::lte_rrc::{PCCH_MessageType, PCCH_MessageType_c1, P
use rayhunter::analysis::analyzer::{Analyzer, Event, EventType, Severity};
use rayhunter::analysis::information_element::{InformationElement, LteInformationElement};
pub struct TestAnalyzer{
pub struct TestAnalyzer {
pub count: i32,
}
impl Analyzer for TestAnalyzer{
impl Analyzer for TestAnalyzer {
fn get_name(&self) -> Cow<str> {
Cow::from("Example Analyzer")
}
@@ -22,15 +22,15 @@ impl Analyzer for TestAnalyzer{
self.count += 1;
if self.count % 100 == 0 {
return Some(Event {
event_type: EventType::Informational ,
event_type: EventType::Informational,
message: "multiple of 100 events processed".to_string(),
})
});
}
let pcch_msg = match ie {
InformationElement::LTE(lte_ie) => match &** lte_ie {
InformationElement::LTE(lte_ie) => match &**lte_ie {
LteInformationElement::PCCH(pcch_msg) => pcch_msg,
_ => return None,
}
},
_ => return None,
};
let PCCH_MessageType::C1(PCCH_MessageType_c1::Paging(paging)) = &pcch_msg.message else {
@@ -39,9 +39,11 @@ impl Analyzer for TestAnalyzer{
for record in &paging.paging_record_list.as_ref()?.0 {
if let PagingUE_Identity::S_TMSI(_) = record.ue_identity {
return Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::Low },
event_type: EventType::QualitativeWarning {
severity: Severity::Low,
},
message: "TMSI was provided to cell".to_string(),
})
});
}
}
None
+2 -2
View File
@@ -1,10 +1,10 @@
use thiserror::Error;
use rayhunter::diag_device::DiagDeviceError;
use thiserror::Error;
use crate::qmdl_store::RecordingStoreError;
#[derive(Error, Debug)]
pub enum RayhunterError{
pub enum RayhunterError {
#[error("Config file parsing error: {0}")]
ConfigFileParsingError(#[from] toml::de::Error),
#[error("Diag intialization error: {0}")]
+34 -20
View File
@@ -1,36 +1,43 @@
use crate::ServerState;
use axum::body::Body;
use axum::extract::{Path, State};
use axum::http::header::CONTENT_TYPE;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use futures::TryStreamExt;
use log::error;
use rayhunter::diag::DataType;
use rayhunter::gsmtap_parser;
use rayhunter::pcap::GsmtapPcapWriter;
use rayhunter::qmdl::QmdlReader;
use axum::body::Body;
use axum::http::header::CONTENT_TYPE;
use axum::extract::{State, Path};
use axum::http::StatusCode;
use axum::response::{Response, IntoResponse};
use std::sync::Arc;
use std::{future, pin::pin};
use tokio::io::duplex;
use tokio_util::io::ReaderStream;
use std::{future, pin::pin};
use std::sync::Arc;
use log::error;
use futures::TryStreamExt;
// Streams a pcap file chunk-by-chunk to the client by reading the QMDL data
// written so far. This is done by spawning a thread which streams chunks of
// pcap data to a channel that's piped to the client.
pub async fn get_pcap(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
pub async fn get_pcap(
State(state): State<Arc<ServerState>>,
Path(qmdl_name): Path<String>,
) -> Result<Response, (StatusCode, String)> {
let qmdl_store = state.qmdl_store_lock.read().await;
let (entry_index, entry) = qmdl_store.entry_for_name(&qmdl_name)
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_name)))?;
let (entry_index, entry) = qmdl_store.entry_for_name(&qmdl_name).ok_or((
StatusCode::NOT_FOUND,
format!("couldn't find qmdl file with name {}", qmdl_name),
))?;
if entry.qmdl_size_bytes == 0 {
return Err((
StatusCode::SERVICE_UNAVAILABLE,
"QMDL file is empty, try again in a bit!".to_string()
"QMDL file is empty, try again in a bit!".to_string(),
));
}
let qmdl_size_bytes = entry.qmdl_size_bytes;
let qmdl_file = qmdl_store.open_entry_qmdl(entry_index).await
let qmdl_file = qmdl_store
.open_entry_qmdl(entry_index)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e)))?;
// the QMDL reader should stop at the last successfully written data chunk
// (entry.size_bytes)
@@ -40,20 +47,27 @@ pub async fn get_pcap(State(state): State<Arc<ServerState>>, Path(qmdl_name): Pa
tokio::spawn(async move {
let mut reader = QmdlReader::new(qmdl_file, Some(qmdl_size_bytes));
let mut messages_stream = pin!(reader.as_stream()
let mut messages_stream = pin!(reader
.as_stream()
.try_filter(|container| future::ready(container.data_type == DataType::UserSpace)));
while let Some(container) = messages_stream.try_next().await.expect("failed getting QMDL container") {
while let Some(container) = messages_stream
.try_next()
.await
.expect("failed getting QMDL container")
{
for maybe_msg in container.into_messages() {
match maybe_msg {
Ok(msg) => {
let maybe_gsmtap_msg = gsmtap_parser::parse(msg)
.expect("error parsing gsmtap message");
let maybe_gsmtap_msg =
gsmtap_parser::parse(msg).expect("error parsing gsmtap message");
if let Some((timestamp, gsmtap_msg)) = maybe_gsmtap_msg {
pcap_writer.write_gsmtap_message(gsmtap_msg, timestamp).await
pcap_writer
.write_gsmtap_message(gsmtap_msg, timestamp)
.await
.expect("error writing pcap packet");
}
},
}
Err(e) => error!("error parsing message: {:?}", e),
}
}
+11 -12
View File
@@ -1,11 +1,11 @@
use rayhunter::util::RuntimeMetadata;
use chrono::{DateTime, Local};
use rayhunter::util::RuntimeMetadata;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use thiserror::Error;
use tokio::{
fs::{self, try_exists, File, OpenOptions},
io::AsyncWriteExt
io::AsyncWriteExt,
};
#[derive(Debug, Error)]
@@ -127,7 +127,7 @@ impl RecordingStore {
let mut store = RecordingStore {
path: path.as_ref().to_owned(),
manifest: Manifest {
entries: Vec::new()
entries: Vec::new(),
},
current_entry: None,
};
@@ -171,10 +171,7 @@ impl RecordingStore {
}
// Returns the corresponding QMDL file for a given entry
pub async fn open_entry_qmdl(
&self,
entry_index: usize,
) -> Result<File, RecordingStoreError> {
pub async fn open_entry_qmdl(&self, entry_index: usize) -> Result<File, RecordingStoreError> {
let entry = &self.manifest.entries[entry_index];
File::open(entry.get_qmdl_filepath(&self.path))
.await
@@ -203,8 +200,7 @@ impl RecordingStore {
.open(entry.get_analysis_filepath(&self.path))
.await
.map_err(RecordingStoreError::ReadFileError)?;
self.update_entry_analysis_size(entry_index, 0)
.await?;
self.update_entry_analysis_size(entry_index, 0).await?;
Ok(file)
}
@@ -253,7 +249,8 @@ impl RecordingStore {
.await
.map_err(RecordingStoreError::WriteManifestError)?;
fs::rename(tmp_path, self.path.join("manifest.toml")).await
fs::rename(tmp_path, self.path.join("manifest.toml"))
.await
.map_err(RecordingStoreError::WriteManifestError)?;
Ok(())
@@ -261,7 +258,8 @@ impl RecordingStore {
// Finds an entry by filename
pub fn entry_for_name(&self, name: &str) -> Option<(usize, &ManifestEntry)> {
let entry_index = self.manifest
let entry_index = self
.manifest
.entries
.iter()
.position(|entry| entry.name == name)?;
@@ -274,7 +272,8 @@ impl RecordingStore {
}
pub async fn delete_entry(&mut self, name: &str) -> Result<ManifestEntry, RecordingStoreError> {
let entry_to_delete_idx = self.manifest
let entry_to_delete_idx = self
.manifest
.entries
.iter()
.position(|entry| entry.name == name)
+30 -16
View File
@@ -1,20 +1,20 @@
use axum::body::Body;
use axum::http::header::{self, CONTENT_LENGTH, CONTENT_TYPE};
use axum::extract::State;
use axum::http::{StatusCode, HeaderValue};
use axum::response::{Response, IntoResponse};
use axum::extract::Path;
use axum::extract::State;
use axum::http::header::{self, CONTENT_LENGTH, CONTENT_TYPE};
use axum::http::{HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use include_dir::{include_dir, Dir};
use std::sync::Arc;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use tokio::sync::mpsc::Sender;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_util::io::ReaderStream;
use include_dir::{include_dir, Dir};
use crate::{display, DiagDeviceCtrlMessage};
use crate::analysis::{AnalysisCtrlMessage, AnalysisStatus};
use crate::qmdl_store::RecordingStore;
use crate::{display, DiagDeviceCtrlMessage};
pub struct ServerState {
pub qmdl_store_lock: Arc<RwLock<RecordingStore>>,
@@ -25,13 +25,22 @@ pub struct ServerState {
pub debug_mode: bool,
}
pub async fn get_qmdl(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
pub async fn get_qmdl(
State(state): State<Arc<ServerState>>,
Path(qmdl_name): Path<String>,
) -> Result<Response, (StatusCode, String)> {
let qmdl_idx = qmdl_name.trim_end_matches(".qmdl");
let qmdl_store = state.qmdl_store_lock.read().await;
let (entry_index, entry) = qmdl_store.entry_for_name(qmdl_idx)
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_idx)))?;
let qmdl_file = qmdl_store.open_entry_qmdl(entry_index).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("error opening QMDL file: {}", e)))?;
let (entry_index, entry) = qmdl_store.entry_for_name(qmdl_idx).ok_or((
StatusCode::NOT_FOUND,
format!("couldn't find qmdl file with name {}", qmdl_idx),
))?;
let qmdl_file = qmdl_store.open_entry_qmdl(entry_index).await.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("error opening QMDL file: {}", e),
)
})?;
let limited_qmdl_file = qmdl_file.take(entry.qmdl_size_bytes as u64);
let qmdl_stream = ReaderStream::new(limited_qmdl_file);
@@ -46,7 +55,10 @@ pub async fn get_qmdl(State(state): State<Arc<ServerState>>, Path(qmdl_name): Pa
// Bundles the server's static files (html/css/js) into the binary for easy distribution
static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static");
pub async fn serve_static(State(state): State<Arc<ServerState>>, Path(path): Path<String>) -> impl IntoResponse {
pub async fn serve_static(
State(state): State<Arc<ServerState>>,
Path(path): Path<String>,
) -> impl IntoResponse {
let path = path.trim_start_matches('/');
let mime_type = mime_guess::from_path(path).first_or_text_plain();
@@ -62,7 +74,9 @@ pub async fn serve_static(State(state): State<Arc<ServerState>>, Path(path): Pat
return match File::open(build_path).await {
Ok(mut file) => {
let mut body = String::new();
file.read_to_string(&mut body).await.expect("failed to read file");
file.read_to_string(&mut body)
.await
.expect("failed to read file");
Response::builder()
.status(StatusCode::OK)
.header(
@@ -71,11 +85,11 @@ pub async fn serve_static(State(state): State<Arc<ServerState>>, Path(path): Pat
)
.body(Body::from(body))
.unwrap()
},
}
Err(_) => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()
.unwrap(),
};
}
+20 -9
View File
@@ -3,9 +3,9 @@ use std::sync::Arc;
use crate::qmdl_store::ManifestEntry;
use crate::server::ServerState;
use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use axum::Json;
use log::error;
use serde::Serialize;
use tokio::process::Command;
@@ -65,10 +65,16 @@ pub struct MemoryStats {
// runs the given command and returns its stdout as a string
async fn get_cmd_output(mut cmd: Command) -> Result<String, String> {
let cmd_str = format!("{:?}", &cmd);
let output = cmd.output().await
let output = cmd
.output()
.await
.map_err(|e| format!("error running command {}: {}", &cmd_str, e))?;
if !output.status.success() {
return Err(format!("command {} failed with exit code {}", &cmd_str, output.status.code().unwrap()));
return Err(format!(
"command {} failed with exit code {}",
&cmd_str,
output.status.code().unwrap()
));
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
@@ -79,7 +85,8 @@ impl MemoryStats {
let mut free_cmd = Command::new("free");
free_cmd.arg("-k");
let stdout = get_cmd_output(free_cmd).await?;
let mut numbers = stdout.split_whitespace()
let mut numbers = stdout
.split_whitespace()
.flat_map(|part| part.parse::<usize>());
Ok(Self {
total: humanize_kb(numbers.next().ok_or("error parsing free output")?),
@@ -91,13 +98,15 @@ impl MemoryStats {
// turns a number of kilobytes (like 28293) into a human-readable string (like "28.3M")
fn humanize_kb(kb: usize) -> String {
if kb < 1000{
if kb < 1000 {
return format!("{}K", kb);
}
format!("{:.1}M", kb as f64 / 1024.0)
}
pub async fn get_system_stats(State(state): State<Arc<ServerState>>) -> Result<Json<SystemStats>, (StatusCode, String)> {
pub async fn get_system_stats(
State(state): State<Arc<ServerState>>,
) -> Result<Json<SystemStats>, (StatusCode, String)> {
let qmdl_store = state.qmdl_store_lock.read().await;
match SystemStats::new(qmdl_store.path.to_str().unwrap()).await {
Ok(stats) => Ok(Json(stats)),
@@ -105,9 +114,9 @@ pub async fn get_system_stats(State(state): State<Arc<ServerState>>) -> Result<J
error!("error getting system stats: {}", err);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"error getting system stats".to_string()
"error getting system stats".to_string(),
))
},
}
}
}
@@ -117,7 +126,9 @@ pub struct ManifestStats {
pub current_entry: Option<ManifestEntry>,
}
pub async fn get_qmdl_manifest(State(state): State<Arc<ServerState>>) -> Result<Json<ManifestStats>, (StatusCode, String)> {
pub async fn get_qmdl_manifest(
State(state): State<Arc<ServerState>>,
) -> Result<Json<ManifestStats>, (StatusCode, String)> {
let qmdl_store = state.qmdl_store_lock.read().await;
let mut entries = qmdl_store.manifest.entries.clone();
let current_entry = qmdl_store.current_entry.map(|index| entries.remove(index));
+14 -10
View File
@@ -1,14 +1,13 @@
use std::borrow::Cow;
use chrono::{DateTime, FixedOffset};
use serde::Serialize;
use std::borrow::Cow;
use crate::{diag::MessagesContainer, gsmtap_parser};
use crate::util::RuntimeMetadata;
use crate::{diag::MessagesContainer, gsmtap_parser};
use super::{
imsi_requested::ImsiRequestedAnalyzer,
information_element::InformationElement,
connection_redirect_downgrade::ConnectionRedirect2GDowngradeAnalyzer,
imsi_requested::ImsiRequestedAnalyzer, information_element::InformationElement,
priority_2g_downgrade::LteSib6And7DowngradeAnalyzer,
};
@@ -118,14 +117,16 @@ impl Default for Harness {
impl Harness {
pub fn new() -> Self {
Self { analyzers: Vec::new() }
Self {
analyzers: Vec::new(),
}
}
pub fn new_with_all_analyzers() -> Self {
let mut harness = Harness::new();
harness.add_analyzer(Box::new(ImsiRequestedAnalyzer::new()));
harness.add_analyzer(Box::new(ConnectionRedirect2GDowngradeAnalyzer{}));
harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{}));
harness.add_analyzer(Box::new(ConnectionRedirect2GDowngradeAnalyzer {}));
harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer {}));
// FIXME: our RRC parser is reporting false positives for this due to an
// upstream hampi bug (https://github.com/ystero-dev/hampi/issues/133).
@@ -186,19 +187,22 @@ impl Harness {
}
fn analyze_information_element(&mut self, ie: &InformationElement) -> Vec<Option<Event>> {
self.analyzers.iter_mut()
self.analyzers
.iter_mut()
.map(|analyzer| analyzer.analyze_information_element(ie))
.collect()
}
pub fn get_names(&self) -> Vec<Cow<'_, str>> {
self.analyzers.iter()
self.analyzers
.iter()
.map(|analyzer| analyzer.get_name())
.collect()
}
pub fn get_descriptions(&self) -> Vec<Cow<'_, str>> {
self.analyzers.iter()
self.analyzers
.iter()
.map(|analyzer| analyzer.get_description())
.collect()
}
@@ -2,13 +2,15 @@ use std::borrow::Cow;
use super::analyzer::{Analyzer, Event, EventType, Severity};
use super::information_element::{InformationElement, LteInformationElement};
use telcom_parser::lte_rrc::{DL_DCCH_MessageType, DL_DCCH_MessageType_c1, RRCConnectionReleaseCriticalExtensions, RRCConnectionReleaseCriticalExtensions_c1, RedirectedCarrierInfo};
use super::util::unpack;
use telcom_parser::lte_rrc::{
DL_DCCH_MessageType, DL_DCCH_MessageType_c1, RRCConnectionReleaseCriticalExtensions,
RRCConnectionReleaseCriticalExtensions_c1, RedirectedCarrierInfo,
};
// Based on HITBSecConf presentation "Forcing a targeted LTE cellphone into an
// eavesdropping network" by Lin Huang
pub struct ConnectionRedirect2GDowngradeAnalyzer {
}
pub struct ConnectionRedirect2GDowngradeAnalyzer {}
// TODO: keep track of SIB state to compare LTE reselection blocks w/ 2g/3g ones
impl Analyzer for ConnectionRedirect2GDowngradeAnalyzer {
@@ -33,7 +35,9 @@ impl Analyzer for ConnectionRedirect2GDowngradeAnalyzer {
unpack!(Some(carrier_info) = &r8_ies.redirected_carrier_info);
match carrier_info {
RedirectedCarrierInfo::Geran(_carrier_freqs_geran) => Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::High },
event_type: EventType::QualitativeWarning {
severity: Severity::High,
},
message: "Detected 2G downgrade".to_owned(),
}),
_ => Some(Event {
+7 -6
View File
@@ -5,8 +5,7 @@ use telcom_parser::lte_rrc::{PCCH_MessageType, PCCH_MessageType_c1, PagingUE_Ide
use super::analyzer::{Analyzer, Event, EventType, Severity};
use super::information_element::{InformationElement, LteInformationElement};
pub struct ImsiProvidedAnalyzer {
}
pub struct ImsiProvidedAnalyzer {}
impl Analyzer for ImsiProvidedAnalyzer {
fn get_name(&self) -> Cow<str> {
@@ -19,10 +18,10 @@ impl Analyzer for ImsiProvidedAnalyzer {
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<Event> {
let pcch_msg = match ie {
InformationElement::LTE(lte_ie) => match &** lte_ie {
InformationElement::LTE(lte_ie) => match &**lte_ie {
LteInformationElement::PCCH(pcch_msg) => pcch_msg,
_ => return None,
}
},
_ => return None,
};
let PCCH_MessageType::C1(PCCH_MessageType_c1::Paging(paging)) = &pcch_msg.message else {
@@ -31,9 +30,11 @@ impl Analyzer for ImsiProvidedAnalyzer {
for record in &paging.paging_record_list.as_ref()?.0 {
if let PagingUE_Identity::Imsi(_) = record.ue_identity {
return Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::High },
event_type: EventType::QualitativeWarning {
severity: Severity::High,
},
message: "IMSI was provided to cell".to_string(),
})
});
}
}
None
+6 -6
View File
@@ -36,7 +36,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
InformationElement::LTE(inner) => match &**inner {
LteInformationElement::NAS(payload) => payload,
_ => return None,
}
},
_ => return None,
};
@@ -45,7 +45,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
if self.packet_num < PACKET_THRESHHOLD {
return Some(Event {
event_type: EventType::QualitativeWarning {
severity: Severity::Medium
severity: Severity::Medium,
},
message: format!(
"NAS IMSI identity request detected, however it was within \
@@ -53,15 +53,15 @@ impl Analyzer for ImsiRequestedAnalyzer {
turned your device on, this is likely a \
false-positive.",
PACKET_THRESHHOLD
)
})
),
});
} else {
return Some(Event {
event_type: EventType::QualitativeWarning {
severity: Severity::High
severity: Severity::High,
},
message: "NAS IMSI identity request detected".to_owned(),
})
});
}
}
None
+14 -9
View File
@@ -3,9 +3,9 @@
//! the term to refer to a structured, fully parsed message in any telcom
//! standard.
use crate::gsmtap::{GsmtapMessage, GsmtapType, LteNasSubtype, LteRrcSubtype};
use telcom_parser::{decode, lte_rrc};
use thiserror::Error;
use crate::gsmtap::{GsmtapMessage, GsmtapType, LteNasSubtype, LteRrcSubtype};
#[derive(Error, Debug)]
pub enum InformationElementError {
@@ -46,7 +46,6 @@ pub enum LteInformationElement {
// FIXME: actually parse NAS messages
NAS(Vec<u8>),
// FIXME: unclear which message these "NB" types map to
//DlCcchNb(),
//DlDcchNb(),
@@ -65,8 +64,8 @@ impl TryFrom<&GsmtapMessage> for InformationElement {
fn try_from(gsmtap_msg: &GsmtapMessage) -> Result<Self, Self::Error> {
match gsmtap_msg.header.gsmtap_type {
GsmtapType::LteRrc(lte_rrc_subtype) => {
use LteRrcSubtype as L;
use LteInformationElement as R;
use LteRrcSubtype as L;
let lte = match lte_rrc_subtype {
L::DlCcch => R::DlCcch(decode(&gsmtap_msg.payload)?),
L::DlDcch => R::DlDcch(Box::new(decode(&gsmtap_msg.payload)?)),
@@ -82,14 +81,20 @@ impl TryFrom<&GsmtapMessage> for InformationElement {
L::BcchDlSchMbms => R::BcchDlSchMbms(decode(&gsmtap_msg.payload)?),
L::SbcchSlBch => R::SbcchSlBch(decode(&gsmtap_msg.payload)?),
L::SbcchSlBchV2x => R::SbcchSlBchV2x(decode(&gsmtap_msg.payload)?),
_ => return Err(InformationElementError::UnsupportedGsmtapType(gsmtap_msg.header.gsmtap_type)),
_ => {
return Err(InformationElementError::UnsupportedGsmtapType(
gsmtap_msg.header.gsmtap_type,
))
}
};
Ok(InformationElement::LTE(Box::new(lte)))
},
GsmtapType::LteNas(LteNasSubtype::Plain) => {
Ok(InformationElement::LTE(Box::new(LteInformationElement::NAS(gsmtap_msg.payload.clone()))))
},
_ => Err(InformationElementError::UnsupportedGsmtapType(gsmtap_msg.header.gsmtap_type)),
}
GsmtapType::LteNas(LteNasSubtype::Plain) => Ok(InformationElement::LTE(Box::new(
LteInformationElement::NAS(gsmtap_msg.payload.clone()),
))),
_ => Err(InformationElementError::UnsupportedGsmtapType(
gsmtap_msg.header.gsmtap_type,
)),
}
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
pub mod analyzer;
pub mod information_element;
pub mod priority_2g_downgrade;
pub mod connection_redirect_downgrade;
pub mod imsi_provided;
pub mod imsi_requested;
pub mod information_element;
pub mod null_cipher;
pub mod priority_2g_downgrade;
pub mod util;
+62 -21
View File
@@ -1,25 +1,41 @@
use std::borrow::Cow;
use telcom_parser::lte_rrc::{CipheringAlgorithm_r12, DL_DCCH_MessageType, DL_DCCH_MessageType_c1, RRCConnectionReconfiguration, RRCConnectionReconfigurationCriticalExtensions, RRCConnectionReconfigurationCriticalExtensions_c1, SCG_Configuration_r12, SecurityConfigHO_v1530HandoverType_v1530, SecurityModeCommand, SecurityModeCommandCriticalExtensions, SecurityModeCommandCriticalExtensions_c1};
use telcom_parser::lte_rrc::{
CipheringAlgorithm_r12, DL_DCCH_MessageType, DL_DCCH_MessageType_c1,
RRCConnectionReconfiguration, RRCConnectionReconfigurationCriticalExtensions,
RRCConnectionReconfigurationCriticalExtensions_c1, SCG_Configuration_r12,
SecurityConfigHO_v1530HandoverType_v1530, SecurityModeCommand,
SecurityModeCommandCriticalExtensions, SecurityModeCommandCriticalExtensions_c1,
};
use super::analyzer::{Analyzer, Event, EventType, Severity};
use super::information_element::{InformationElement, LteInformationElement};
pub struct NullCipherAnalyzer {
}
pub struct NullCipherAnalyzer {}
impl NullCipherAnalyzer {
fn check_rrc_connection_reconfiguration_cipher(&self, reconfiguration: &RRCConnectionReconfiguration) -> bool {
let RRCConnectionReconfigurationCriticalExtensions::C1(c1) = &reconfiguration.critical_extensions else {
fn check_rrc_connection_reconfiguration_cipher(
&self,
reconfiguration: &RRCConnectionReconfiguration,
) -> bool {
let RRCConnectionReconfigurationCriticalExtensions::C1(c1) =
&reconfiguration.critical_extensions
else {
return false;
};
let RRCConnectionReconfigurationCriticalExtensions_c1::RrcConnectionReconfiguration_r8(c1) = c1 else {
let RRCConnectionReconfigurationCriticalExtensions_c1::RrcConnectionReconfiguration_r8(c1) =
c1
else {
return false;
};
if let Some(handover) = &c1.security_config_ho {
let maybe_security_config = match &handover.handover_type {
telcom_parser::lte_rrc::SecurityConfigHOHandoverType::IntraLTE(lte) => lte.security_algorithm_config.as_ref(),
telcom_parser::lte_rrc::SecurityConfigHOHandoverType::InterRAT(rat) => Some(&rat.security_algorithm_config),
telcom_parser::lte_rrc::SecurityConfigHOHandoverType::IntraLTE(lte) => {
lte.security_algorithm_config.as_ref()
}
telcom_parser::lte_rrc::SecurityConfigHOHandoverType::InterRAT(rat) => {
Some(&rat.security_algorithm_config)
}
};
if let Some(security_config) = maybe_security_config {
if security_config.ciphering_algorithm.0 == CipheringAlgorithm_r12::EEA0 {
@@ -28,7 +44,9 @@ impl NullCipherAnalyzer {
}
}
// Use map/flatten to dig into a long chain of nested Option types
let maybe_v1250 = c1.non_critical_extension.as_ref()
let maybe_v1250 = c1
.non_critical_extension
.as_ref()
.and_then(|v890| v890.non_critical_extension.as_ref())
.and_then(|v920| v920.non_critical_extension.as_ref())
.and_then(|v1020| v1020.non_critical_extension.as_ref())
@@ -37,8 +55,11 @@ impl NullCipherAnalyzer {
return false;
};
if let Some(SCG_Configuration_r12::Setup(scg_setup)) = v1250.scg_configuration_r12.as_ref() {
let maybe_cipher = scg_setup.scg_config_part_scg_r12.as_ref()
if let Some(SCG_Configuration_r12::Setup(scg_setup)) = v1250.scg_configuration_r12.as_ref()
{
let maybe_cipher = scg_setup
.scg_config_part_scg_r12
.as_ref()
.and_then(|scg| scg.mobility_control_info_scg_r12.as_ref())
.and_then(|mci| mci.ciphering_algorithm_scg_r12.as_ref());
if let Some(cipher) = maybe_cipher {
@@ -48,7 +69,9 @@ impl NullCipherAnalyzer {
}
}
let maybe_v1530_security_config = v1250.non_critical_extension.as_ref()
let maybe_v1530_security_config = v1250
.non_critical_extension
.as_ref()
.and_then(|v1310| v1310.non_critical_extension.as_ref())
.and_then(|v1430| v1430.non_critical_extension.as_ref())
.and_then(|v1510| v1510.non_critical_extension.as_ref())
@@ -57,9 +80,15 @@ impl NullCipherAnalyzer {
return false;
};
let maybe_security_algorithm = match &v1530_security_config.handover_type_v1530 {
SecurityConfigHO_v1530HandoverType_v1530::Intra5GC(intra_5gc) => intra_5gc.security_algorithm_config_r15.as_ref(),
SecurityConfigHO_v1530HandoverType_v1530::Fivegc_ToEPC(to_epc) => Some(&to_epc.security_algorithm_config_r15),
SecurityConfigHO_v1530HandoverType_v1530::Epc_To5GC(to_5gc) => Some(&to_5gc.security_algorithm_config_r15),
SecurityConfigHO_v1530HandoverType_v1530::Intra5GC(intra_5gc) => {
intra_5gc.security_algorithm_config_r15.as_ref()
}
SecurityConfigHO_v1530HandoverType_v1530::Fivegc_ToEPC(to_epc) => {
Some(&to_epc.security_algorithm_config_r15)
}
SecurityConfigHO_v1530HandoverType_v1530::Epc_To5GC(to_5gc) => {
Some(&to_5gc.security_algorithm_config_r15)
}
};
if let Some(security_algorithm) = maybe_security_algorithm {
if security_algorithm.ciphering_algorithm.0 == CipheringAlgorithm_r12::EEA0 {
@@ -76,7 +105,13 @@ impl NullCipherAnalyzer {
let SecurityModeCommandCriticalExtensions_c1::SecurityModeCommand_r8(r8) = &c1 else {
return false;
};
if r8.security_config_smc.security_algorithm_config.ciphering_algorithm.0 == CipheringAlgorithm_r12::EEA0 {
if r8
.security_config_smc
.security_algorithm_config
.ciphering_algorithm
.0
== CipheringAlgorithm_r12::EEA0
{
return true;
}
false
@@ -94,23 +129,29 @@ impl Analyzer for NullCipherAnalyzer {
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<Event> {
let dcch_msg = match ie {
InformationElement::LTE(lte_ie) => match &** lte_ie {
InformationElement::LTE(lte_ie) => match &**lte_ie {
LteInformationElement::DlDcch(dcch_msg) => dcch_msg,
_ => return None,
}
},
_ => return None,
};
let DL_DCCH_MessageType::C1(c1) = &dcch_msg.message else {
return None;
};
let null_cipher_detected = match c1 {
DL_DCCH_MessageType_c1::RrcConnectionReconfiguration(reconfiguration) => self.check_rrc_connection_reconfiguration_cipher(reconfiguration),
DL_DCCH_MessageType_c1::SecurityModeCommand(command) => self.check_security_mode_command_cipher(command),
DL_DCCH_MessageType_c1::RrcConnectionReconfiguration(reconfiguration) => {
self.check_rrc_connection_reconfiguration_cipher(reconfiguration)
}
DL_DCCH_MessageType_c1::SecurityModeCommand(command) => {
self.check_security_mode_command_cipher(command)
}
_ => return None,
};
if null_cipher_detected {
return Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::High },
event_type: EventType::QualitativeWarning {
severity: Severity::High,
},
message: "Cell suggested use of null cipher".to_string(),
});
}
+45 -16
View File
@@ -2,18 +2,29 @@ use std::borrow::Cow;
use super::analyzer::{Analyzer, Event, EventType, Severity};
use super::information_element::{InformationElement, LteInformationElement};
use telcom_parser::lte_rrc::{BCCH_DL_SCH_MessageType, BCCH_DL_SCH_MessageType_c1, CellReselectionPriority, SystemInformationBlockType7, SystemInformationCriticalExtensions, SystemInformation_r8_IEsSib_TypeAndInfo, SystemInformation_r8_IEsSib_TypeAndInfo_Entry};
use telcom_parser::lte_rrc::{
BCCH_DL_SCH_MessageType, BCCH_DL_SCH_MessageType_c1, CellReselectionPriority,
SystemInformationBlockType7, SystemInformationCriticalExtensions,
SystemInformation_r8_IEsSib_TypeAndInfo, SystemInformation_r8_IEsSib_TypeAndInfo_Entry,
};
/// Based on heuristic T7 from Shinjo Park's "Why We Cannot Win".
pub struct LteSib6And7DowngradeAnalyzer {
}
pub struct LteSib6And7DowngradeAnalyzer {}
impl LteSib6And7DowngradeAnalyzer {
fn unpack_system_information<'a>(&self, ie: &'a InformationElement) -> Option<&'a SystemInformation_r8_IEsSib_TypeAndInfo> {
fn unpack_system_information<'a>(
&self,
ie: &'a InformationElement,
) -> Option<&'a SystemInformation_r8_IEsSib_TypeAndInfo> {
if let InformationElement::LTE(lte_ie) = ie {
if let LteInformationElement::BcchDlSch(bcch_dl_sch_message) = &**lte_ie {
if let BCCH_DL_SCH_MessageType::C1(BCCH_DL_SCH_MessageType_c1::SystemInformation(system_information)) = &bcch_dl_sch_message.message {
if let SystemInformationCriticalExtensions::SystemInformation_r8(sib) = &system_information.critical_extensions {
if let BCCH_DL_SCH_MessageType::C1(BCCH_DL_SCH_MessageType_c1::SystemInformation(
system_information,
)) = &bcch_dl_sch_message.message
{
if let SystemInformationCriticalExtensions::SystemInformation_r8(sib) =
&system_information.critical_extensions
{
return Some(&sib.sib_type_and_info);
}
}
@@ -33,14 +44,19 @@ impl Analyzer for LteSib6And7DowngradeAnalyzer {
Cow::from("Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.")
}
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<super::analyzer::Event> {
fn analyze_information_element(
&mut self,
ie: &InformationElement,
) -> Option<super::analyzer::Event> {
let sibs = &self.unpack_system_information(ie)?.0;
for sib in sibs {
match sib {
SystemInformation_r8_IEsSib_TypeAndInfo_Entry::Sib6(sib6) => {
if let Some(carrier_info_list) = sib6.carrier_freq_list_utra_fdd.as_ref() {
for carrier_info in &carrier_info_list.0 {
if let Some(CellReselectionPriority(p)) = carrier_info.cell_reselection_priority {
if let Some(CellReselectionPriority(p)) =
carrier_info.cell_reselection_priority
{
if p == 0 {
return Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::High },
@@ -52,7 +68,9 @@ impl Analyzer for LteSib6And7DowngradeAnalyzer {
}
if let Some(carrier_info_list) = sib6.carrier_freq_list_utra_tdd.as_ref() {
for carrier_info in &carrier_info_list.0 {
if let Some(CellReselectionPriority(p)) = carrier_info.cell_reselection_priority {
if let Some(CellReselectionPriority(p)) =
carrier_info.cell_reselection_priority
{
if p == 0 {
return Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::High },
@@ -62,20 +80,31 @@ impl Analyzer for LteSib6And7DowngradeAnalyzer {
}
}
}
},
SystemInformation_r8_IEsSib_TypeAndInfo_Entry::Sib7(SystemInformationBlockType7 { carrier_freqs_info_list: Some(carrier_info_list), .. }) => {
}
SystemInformation_r8_IEsSib_TypeAndInfo_Entry::Sib7(
SystemInformationBlockType7 {
carrier_freqs_info_list: Some(carrier_info_list),
..
},
) => {
for carrier_info in &carrier_info_list.0 {
if let Some(CellReselectionPriority(p)) = carrier_info.common_info.cell_reselection_priority {
if let Some(CellReselectionPriority(p)) =
carrier_info.common_info.cell_reselection_priority
{
if p == 0 {
return Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::High },
message: "LTE cell advertised a 2G cell for priority 0 reselection".to_string(),
event_type: EventType::QualitativeWarning {
severity: Severity::High,
},
message:
"LTE cell advertised a 2G cell for priority 0 reselection"
.to_string(),
});
}
}
}
},
_ => {},
}
_ => {}
}
}
None
+3 -2
View File
@@ -1,4 +1,3 @@
// Unpacks a pattern, or returns None.
//
// # Examples
@@ -24,7 +23,9 @@
//
macro_rules! unpack {
($pat:pat = $val:expr) => {
let $pat = $val else { return None; };
let $pat = $val else {
return None;
};
};
}
+88 -66
View File
@@ -5,7 +5,7 @@ use crc::{Algorithm, Crc};
use deku::prelude::*;
use crate::hdlc::{self, hdlc_decapsulate};
use log::{warn, error};
use log::{error, warn};
use thiserror::Error;
pub const MESSAGE_TERMINATOR: u8 = 0x7e;
@@ -42,7 +42,7 @@ pub enum LogConfigRequest {
log_type: u32,
log_mask_bitsize: u32,
log_mask: Vec<u8>,
}
},
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
@@ -93,13 +93,19 @@ impl MessagesContainer {
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());
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()))),
Err(err) => result.push(Err(DiagParsingError::HdlcDecapsulationError(
err,
sub_msg.to_vec(),
))),
}
}
}
@@ -171,7 +177,7 @@ pub enum LogBody {
msg: Vec<u8>,
},
#[deku(id = "0xb0c0")]
LteRrcOtaMessage{
LteRrcOtaMessage {
ext_header_version: u8,
#[deku(ctx = "*ext_header_version")]
packet: LteRrcOtaPacket,
@@ -210,7 +216,7 @@ pub enum LogBody {
NrRrcOtaMessage {
#[deku(count = "hdr_len")]
msg: Vec<u8>,
}
},
}
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
@@ -364,15 +370,17 @@ pub enum ResponsePayload {
#[deku(ctx = "subopcode: u32", id = "subopcode")]
pub enum LogConfigResponse {
#[deku(id = "1")]
RetrieveIdRanges {
log_mask_sizes: [u32; 16],
},
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 {
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![];
@@ -413,31 +421,35 @@ mod test {
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,
]);
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 {
let req = build_log_mask_request(
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,
],
}));
bitsize,
&crate::diag_device::LOG_CODES_FOR_RAW_PACKET_LOGGING,
);
assert_eq!(
req,
Request::LogConfig(LogConfigRequest::SetMask {
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]
@@ -448,53 +460,53 @@ mod test {
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,
]);
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,
]);
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
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],
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 {
@@ -515,7 +527,9 @@ mod test {
outer_length: length_with_payload,
inner_length: length_with_payload,
log_type: 0xb0c0,
timestamp: Timestamp { ts: 72659535985485082 },
timestamp: Timestamp {
ts: 72659535985485082,
},
body: LogBody::LteRrcOtaMessage {
ext_header_version: 20,
packet: LteRrcOtaPacket::V8 {
@@ -532,7 +546,9 @@ mod test {
},
},
};
let serialized = message.to_bytes().expect("failed to serialize test message");
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,
@@ -574,7 +590,10 @@ mod test {
container.num_messages += 1;
let result = container.into_messages();
assert_eq!(result[0], Ok(message1));
assert!(matches!(result[1], Err(DiagParsingError::MessageParsingError(_, _))));
assert!(matches!(
result[1],
Err(DiagParsingError::MessageParsingError(_, _))
));
}
#[test]
@@ -589,6 +608,9 @@ mod test {
container.num_messages += 1;
let result = container.into_messages();
assert_eq!(result[0], Ok(message1));
assert!(matches!(result[1], Err(DiagParsingError::HdlcDecapsulationError(_, _))));
assert!(matches!(
result[1],
Err(DiagParsingError::HdlcDecapsulationError(_, _))
));
}
}
+60 -32
View File
@@ -1,13 +1,16 @@
use crate::diag::{
build_log_mask_request, DataType, DiagParsingError, LogConfigRequest, LogConfigResponse,
Message, MessagesContainer, Request, RequestContainer, ResponsePayload, CRC_CCITT,
};
use crate::hdlc::hdlc_encapsulate;
use crate::diag::{build_log_mask_request, DataType, DiagParsingError, LogConfigRequest, LogConfigResponse, Message, MessagesContainer, Request, RequestContainer, ResponsePayload, CRC_CCITT};
use crate::log_codes;
use deku::prelude::*;
use futures_core::TryStream;
use log::{error, info};
use std::io::ErrorKind;
use std::os::fd::AsRawFd;
use futures_core::TryStream;
use thiserror::Error;
use log::{info, error};
use deku::prelude::*;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -38,22 +41,19 @@ pub enum DiagDeviceError {
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
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
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
log_codes::LOG_DATA_PROTOCOL_LOGGING_C, // 0x11eb
];
const BUFFER_LEN: usize = 1024 * 1024 * 10;
@@ -68,9 +68,9 @@ const DIAG_IOCTL_REMOTE_DEV: u64 = 32;
#[cfg(target_arch = "arm")]
const DIAG_IOCTL_SWITCH_LOGGING: u32 = 7;
#[cfg(target_arch = "x86_64")]
#[cfg(target_arch = "x86_64")]
const DIAG_IOCTL_SWITCH_LOGGING: u64 = 7;
#[cfg(target_arch = "aarch64")]
#[cfg(target_arch = "aarch64")]
const DIAG_IOCTL_SWITCH_LOGGING: u64 = 7;
pub struct DiagDevice {
@@ -99,7 +99,9 @@ impl DiagDevice {
})
}
pub fn as_stream(&mut self) -> impl TryStream<Ok = MessagesContainer, Error = DiagDeviceError> + '_ {
pub fn as_stream(
&mut self,
) -> impl TryStream<Ok = MessagesContainer, Error = DiagDeviceError> + '_ {
futures::stream::try_unfold(self, |dev| async {
let container = dev.get_next_messages_container().await?;
Ok(Some((container, dev)))
@@ -110,11 +112,18 @@ impl DiagDevice {
let mut bytes_read = 0;
// TP-Link M7350 sometimes sends too small messages, we need to be able to deal with short reads.
while bytes_read <= 8 {
bytes_read = self.file.read(&mut self.read_buf).await
bytes_read = self
.file
.read(&mut self.read_buf)
.await
.map_err(DiagDeviceError::DeviceReadFailed)?;
}
info!("Parsing messages container size = {:?} [{:?}]", bytes_read, &self.read_buf[0..bytes_read]);
info!(
"Parsing messages container size = {:?} [{:?}]",
bytes_read,
&self.read_buf[0..bytes_read]
);
match MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0)) {
Ok((_, container)) => return Ok(container),
@@ -129,7 +138,9 @@ impl DiagDevice {
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");
}
.to_bytes()
.expect("Failed to serialize RequestContainer");
if let Err(err) = self.file.write(&buf).await {
// For reasons I don't entirely understand, calls to write(2) on
// /dev/diag always return 0 bytes written, though the written
@@ -164,13 +175,17 @@ impl DiagDevice {
for msg in self.read_response().await? {
match msg {
Ok(Message::Log { .. }) => info!("skipping log response..."),
Ok(Message::Response { payload, status, .. }) => match payload {
ResponsePayload::LogConfig(LogConfigResponse::RetrieveIdRanges { log_mask_sizes }) => {
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),
@@ -181,20 +196,26 @@ impl DiagDevice {
}
async 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);
let req = build_log_mask_request(
log_type,
log_mask_bitsize,
&LOG_CODES_FOR_RAW_PACKET_LOGGING,
);
self.write_request(&req).await?;
for msg in self.read_response().await? {
match msg {
Ok(Message::Log { .. }) => info!("skipping log response..."),
Ok(Message::Response { payload, status, .. }) => {
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),
}
}
@@ -229,7 +250,7 @@ impl DiagDevice {
struct diag_logging_mode_param_t {
req_mode: u32,
peripheral_mask: u32,
mode_param: u8
mode_param: u8,
}
// Triggers the diag device's debug logging mode
@@ -254,11 +275,18 @@ fn enable_frame_readwrite(fd: i32, mode: u32) -> DiagResult<()> {
fd,
DIAG_IOCTL_SWITCH_LOGGING,
&mut params as *mut _,
std::mem::size_of::<diag_logging_mode_param_t>(), 0, 0, 0, 0
std::mem::size_of::<diag_logging_mode_param_t>(),
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))
let msg = format!(
"DIAG_IOCTL_SWITCH_LOGGING ioctl failed with error code {}",
ret
);
return Err(DiagDeviceError::InitializationFailed(msg));
}
}
}
@@ -272,7 +300,7 @@ fn determine_use_mdm(fd: i32) -> DiagResult<i32> {
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))
return Err(DiagDeviceError::InitializationFailed(msg));
}
}
Ok(use_mdm)
+15 -17
View File
@@ -6,24 +6,24 @@ use deku::prelude::*;
pub enum GsmtapType {
Um(UmSubtype),
Abis,
UmBurst, /* raw burst bits */
SIM, /* ISO 7816 smart card interface */
TetraI1, /* tetra air interface */
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 */
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 */
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 */
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
@@ -119,7 +119,7 @@ pub enum UmtsRrcSubtype {
SysInfoTypeSB1 = 58,
SysInfoTypeSB2 = 59,
ToTargetRNCContainer = 60,
TargetRNCToSourceRNCContainer = 61
TargetRNCToSourceRNCContainer = 61,
}
#[repr(u8)]
@@ -218,9 +218,7 @@ pub struct GsmtapHeader {
}
impl GsmtapHeader {
pub fn new(
gsmtap_type: GsmtapType,
) -> Self {
pub fn new(gsmtap_type: GsmtapType) -> Self {
GsmtapHeader {
gsmtap_type,
version: 2,
+50 -14
View File
@@ -13,7 +13,10 @@ pub enum GsmtapParserError {
}
pub fn parse(msg: Message) -> Result<Option<(Timestamp, GsmtapMessage)>, GsmtapParserError> {
if let Message::Log { timestamp, body, .. } = msg {
if let Message::Log {
timestamp, body, ..
} = msg
{
match log_to_gsmtap(body)? {
Some(msg) => Ok(Some((timestamp, msg))),
None => Ok(None),
@@ -25,9 +28,13 @@ pub fn parse(msg: Message) -> Result<Option<(Timestamp, GsmtapMessage)>, GsmtapP
fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserError> {
match value {
LogBody::LteRrcOtaMessage { ext_header_version, packet } => {
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() {
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),
@@ -36,7 +43,12 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
6 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
7 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
8 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
pdu => {
return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
ext_header_version,
pdu,
))
}
},
0x09 | 0x0c => match packet.get_pdu_num() {
8 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
@@ -47,7 +59,12 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
13 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
14 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
15 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
pdu => {
return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
ext_header_version,
pdu,
))
}
},
0x0e..=0x10 => match packet.get_pdu_num() {
1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
@@ -58,7 +75,12 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
7 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
8 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
9 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
pdu => {
return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
ext_header_version,
pdu,
))
}
},
0x13 | 0x1a | 0x1b => match packet.get_pdu_num() {
1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
@@ -76,8 +98,13 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
49 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb),
50 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb),
52 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb),
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
}
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),
@@ -94,9 +121,18 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
58 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb),
59 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb),
61 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb),
pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)),
pdu => {
return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
ext_header_version,
pdu,
))
}
},
_ => return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(ext_header_version)),
_ => {
return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(
ext_header_version,
))
}
};
let mut header = GsmtapHeader::new(gsmtap_type);
header.arfcn = packet.get_earfcn().try_into().unwrap_or(0);
@@ -106,19 +142,19 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
header,
payload: packet.take_payload(),
}))
},
}
LogBody::Nas4GMessage { msg, direction, .. } => {
// currently we only handle "plain" (i.e. non-secure) NAS messages
let mut header = GsmtapHeader::new(GsmtapType::LteNas(LteNasSubtype::Plain));
header.uplink = matches!(direction, Nas4GMessageDirection::Uplink);
header.uplink = matches!(direction, Nas4GMessageDirection::Uplink);
Ok(Some(GsmtapMessage {
header,
payload: msg,
}))
},
}
_ => {
error!("gsmtap_sink: ignoring unhandled log type: {:?}", value);
Ok(None)
},
}
}
}
+15 -5
View File
@@ -3,11 +3,14 @@
//! 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 crc::Crc;
use thiserror::Error;
use crate::diag::{MESSAGE_ESCAPE_CHAR, MESSAGE_TERMINATOR, ESCAPED_MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR};
use crate::diag::{
ESCAPED_MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR, MESSAGE_ESCAPE_CHAR,
MESSAGE_TERMINATOR,
};
#[derive(Debug, Clone, Error, PartialEq)]
pub enum HdlcError {
@@ -29,7 +32,9 @@ pub fn hdlc_encapsulate(data: &[u8], crc: &Crc<u16>) -> Vec<u8> {
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]),
MESSAGE_ESCAPE_CHAR => {
result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR])
}
_ => result.push(b),
}
}
@@ -37,7 +42,9 @@ pub fn hdlc_encapsulate(data: &[u8], crc: &Crc<u16>) -> Vec<u8> {
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]),
MESSAGE_ESCAPE_CHAR => {
result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR])
}
_ => result.push(b),
}
}
@@ -77,7 +84,10 @@ pub fn hdlc_decapsulate(data: &[u8], crc: &Crc<u16>) -> Result<Vec<u8>, HdlcErro
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)));
return Err(HdlcError::InvalidChecksum(
checksum,
crc.checksum(&unescaped),
));
}
Ok(unescaped)
+4 -4
View File
@@ -1,11 +1,11 @@
pub mod hdlc;
pub mod analysis;
pub mod diag;
pub mod qmdl;
pub mod log_codes;
pub mod gsmtap;
pub mod gsmtap_parser;
pub mod hdlc;
pub mod log_codes;
pub mod pcap;
pub mod analysis;
pub mod qmdl;
pub mod util;
// bin/check.rs may target windows and does not use this mod
-3
View File
@@ -1,6 +1,5 @@
//! 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;
@@ -95,12 +94,10 @@ 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;
+25 -10
View File
@@ -1,10 +1,8 @@
//! 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.
use crate::gsmtap::GsmtapMessage;
//! 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.
use crate::diag::Timestamp;
use crate::gsmtap::GsmtapMessage;
use tokio::io::AsyncWrite;
use std::borrow::Cow;
use chrono::prelude::*;
use deku::prelude::*;
use pcap_file_tokio::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
@@ -12,7 +10,9 @@ use pcap_file_tokio::pcapng::blocks::interface_description::InterfaceDescription
use pcap_file_tokio::pcapng::blocks::section_header::{SectionHeaderBlock, SectionHeaderOption};
use pcap_file_tokio::pcapng::PcapNgWriter;
use pcap_file_tokio::{Endianness, PcapError};
use std::borrow::Cow;
use thiserror::Error;
use tokio::io::AsyncWrite;
#[derive(Error, Debug)]
pub enum GsmtapPcapError {
@@ -26,7 +26,10 @@ pub enum GsmtapPcapError {
Deku(#[from] DekuError),
}
pub struct GsmtapPcapWriter<T> where T: AsyncWrite {
pub struct GsmtapPcapWriter<T>
where
T: AsyncWrite,
{
writer: PcapNgWriter<T>,
ip_id: u16,
}
@@ -59,10 +62,17 @@ struct UdpHeader {
checksum: u16,
}
impl<T> GsmtapPcapWriter<T> where T: AsyncWrite + Unpin + Send {
impl<T> GsmtapPcapWriter<T>
where
T: AsyncWrite + Unpin + Send,
{
pub async fn new(writer: T) -> Result<Self, GsmtapPcapError> {
let metadata = crate::util::RuntimeMetadata::new();
let package = format!("{} {}", env!("CARGO_PKG_NAME").to_owned(), metadata.rayhunter_version);
let package = format!(
"{} {}",
env!("CARGO_PKG_NAME").to_owned(),
metadata.rayhunter_version
);
let section = SectionHeaderBlock {
endianness: Endianness::Big,
major_version: 1,
@@ -88,8 +98,13 @@ impl<T> GsmtapPcapWriter<T> where T: AsyncWrite + Unpin + Send {
Ok(())
}
pub async fn write_gsmtap_message(&mut self, msg: GsmtapMessage, timestamp: Timestamp) -> Result<(), GsmtapPcapError> {
let duration = timestamp.to_datetime()
pub async 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()?;
+66 -31
View File
@@ -3,18 +3,24 @@
//! QmdlReader and QmdlWriter can read and write MessagesContainers to and from
//! QMDL files.
use crate::diag::{MessagesContainer, MESSAGE_TERMINATOR, HdlcEncapsulatedMessage, DataType};
use crate::diag::{DataType, HdlcEncapsulatedMessage, MessagesContainer, MESSAGE_TERMINATOR};
use futures::TryStream;
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, AsyncBufReadExt};
use log::error;
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
pub struct QmdlWriter<T> where T: AsyncWrite + Unpin {
pub struct QmdlWriter<T>
where
T: AsyncWrite + Unpin,
{
writer: T,
pub total_written: usize,
}
impl<T> QmdlWriter<T> where T: AsyncWrite + Unpin {
impl<T> QmdlWriter<T>
where
T: AsyncWrite + Unpin,
{
pub fn new(writer: T) -> Self {
QmdlWriter::new_with_existing_size(writer, 0)
}
@@ -35,13 +41,19 @@ impl<T> QmdlWriter<T> where T: AsyncWrite + Unpin {
}
}
pub struct QmdlReader<T> where T: AsyncRead {
pub struct QmdlReader<T>
where
T: AsyncRead,
{
reader: BufReader<T>,
bytes_read: usize,
max_bytes: Option<usize>,
}
impl<T> QmdlReader<T> where T: AsyncRead + Unpin {
impl<T> QmdlReader<T>
where
T: AsyncRead + Unpin,
{
pub fn new(reader: T, max_bytes: Option<usize>) -> Self {
QmdlReader {
reader: BufReader::new(reader),
@@ -50,21 +62,28 @@ impl<T> QmdlReader<T> where T: AsyncRead + Unpin {
}
}
pub fn as_stream(&mut self) -> impl TryStream<Ok = MessagesContainer, Error = std::io::Error> + '_ {
pub fn as_stream(
&mut self,
) -> impl TryStream<Ok = MessagesContainer, Error = std::io::Error> + '_ {
futures::stream::try_unfold(self, |reader| async {
let maybe_container = reader.get_next_messages_container().await?;
match maybe_container {
Some(container) => Ok(Some((container, reader))),
None => Ok(None)
None => Ok(None),
}
})
}
pub async fn get_next_messages_container(&mut self) -> Result<Option<MessagesContainer>, std::io::Error> {
pub async fn get_next_messages_container(
&mut self,
) -> Result<Option<MessagesContainer>, std::io::Error> {
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);
error!(
"warning: {} bytes read, but max_bytes was {}",
self.bytes_read, max_bytes
);
}
return Ok(None);
}
@@ -82,12 +101,10 @@ impl<T> QmdlReader<T> where T: AsyncRead + Unpin {
Ok(Some(MessagesContainer {
data_type: DataType::UserSpace,
num_messages: 1,
messages: vec![
HdlcEncapsulatedMessage {
len: bytes_read as u32,
data: buf,
},
]
messages: vec![HdlcEncapsulatedMessage {
len: bytes_read as u32,
data: buf,
}],
}))
}
}
@@ -96,26 +113,29 @@ impl<T> QmdlReader<T> where T: AsyncRead + Unpin {
mod test {
use std::io::Cursor;
use crate::hdlc::hdlc_encapsulate;
use crate::diag::CRC_CCITT;
use crate::hdlc::hdlc_encapsulate;
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();
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()
get_test_messages()
.iter()
.flat_map(|msg| msg.data.clone())
.collect()
}
@@ -132,7 +152,7 @@ mod test {
MessagesContainer {
data_type: DataType::UserSpace,
num_messages: messages2.len() as u32,
messages: messages2.to_vec()
messages: messages2.to_vec(),
},
]
}
@@ -148,7 +168,10 @@ mod test {
num_messages: 1,
messages: vec![message],
};
assert_eq!(expected_container, reader.get_next_messages_container().await.unwrap().unwrap());
assert_eq!(
expected_container,
reader.get_next_messages_container().await.unwrap().unwrap()
);
}
}
@@ -167,9 +190,15 @@ mod test {
num_messages: 1,
messages: vec![message],
};
assert_eq!(expected_container, reader.get_next_messages_container().await.unwrap().unwrap());
assert_eq!(
expected_container,
reader.get_next_messages_container().await.unwrap().unwrap()
);
}
assert!(matches!(reader.get_next_messages_container().await, Ok(None)));
assert!(matches!(
reader.get_next_messages_container().await,
Ok(None)
));
}
#[tokio::test]
@@ -202,8 +231,14 @@ mod test {
num_messages: 1,
messages: vec![message],
};
assert_eq!(expected_container, reader.get_next_messages_container().await.unwrap().unwrap());
assert_eq!(
expected_container,
reader.get_next_messages_container().await.unwrap().unwrap()
);
}
assert!(matches!(reader.get_next_messages_container().await, Ok(None)));
assert!(matches!(
reader.get_next_messages_container().await,
Ok(None)
));
}
}
+359 -349
View File
@@ -1,42 +1,46 @@
use rayhunter::{diag::{
LogBody, LteRrcOtaPacket, Message, Timestamp
}, gsmtap_parser};
use deku::prelude::*;
use rayhunter::{
diag::{LogBody, LteRrcOtaPacket, Message, Timestamp},
gsmtap_parser,
};
// Tests here are based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/tests/test_diagltelogparser.py
#[test]
fn test_lte_rrc_ota() {
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
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],
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) = gsmtap_parser::parse(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[0x10, 0x15]);
assert_eq!(gsmtap_msg.header.packet_type, 13);
@@ -49,41 +53,40 @@ fn test_lte_rrc_ota() {
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,
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],
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) = gsmtap_parser::parse(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[
0x10, 0x15,
]);
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);
@@ -94,44 +97,44 @@ fn test_lte_rrc_ota() {
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,
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
],
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) = gsmtap_parser::parse(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.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);
@@ -142,48 +145,48 @@ fn test_lte_rrc_ota() {
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,
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
],
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) = gsmtap_parser::parse(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.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);
@@ -194,41 +197,41 @@ fn test_lte_rrc_ota() {
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,
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],
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) = gsmtap_parser::parse(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[
0x28, 0x18, 0x40, 0x16, 0x08, 0x08, 0x80, 0x00,
0x00,
]);
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);
@@ -239,40 +242,41 @@ fn test_lte_rrc_ota() {
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,
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],
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) = gsmtap_parser::parse(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[
0x40, 0x0c, 0x8e, 0xc9, 0x42, 0x89, 0xe0,
]);
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);
@@ -283,49 +287,50 @@ fn test_lte_rrc_ota() {
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,
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
],
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) = gsmtap_parser::parse(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.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);
@@ -336,35 +341,36 @@ fn test_lte_rrc_ota() {
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,
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],
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) = gsmtap_parser::parse(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[0x2c, 0x00]);
assert_eq!(gsmtap_msg.header.packet_type, 13);
@@ -377,40 +383,41 @@ fn test_lte_rrc_ota() {
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,
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],
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) = gsmtap_parser::parse(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[
0x40, 0x0b, 0x8e, 0xc1, 0xdd, 0x13, 0xb0,
]);
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);
@@ -421,35 +428,36 @@ fn test_lte_rrc_ota() {
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,
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],
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) = gsmtap_parser::parse(parsed).unwrap().unwrap();
assert_eq!(&gsmtap_msg.payload, &[0x2e, 0x02]);
assert_eq!(gsmtap_msg.header.packet_type, 13);
@@ -462,46 +470,48 @@ fn test_lte_rrc_ota() {
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,
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
],
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) = gsmtap_parser::parse(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.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);
+11 -14
View File
@@ -1,10 +1,10 @@
//! a simple shell for uploading to the orbic device.
//!
//!
//! It literally just runs bash as UID/GID 0, with special Android GIDs 3003
//! (AID_INET) and 3004 (AID_NET_RAW).
use std::process::Command;
use std::os::unix::process::CommandExt;
use std::env;
use std::os::unix::process::CommandExt;
use std::process::Command;
#[cfg(target_arch = "arm")]
use nix::unistd::Gid;
@@ -15,22 +15,19 @@ fn main() {
// Android's "paranoid network" feature restricts network access to
// processes in specific groups. More info here:
// https://www.elinux.org/Android_Security#Paranoid_network-ing
#[cfg(target_arch = "arm")] {
let gids = &[
Gid::from_raw(3003), // AID_INET
Gid::from_raw(3004), // AID_NET_RAW
];
nix::unistd::setgroups(gids).expect("setgroups failed");
#[cfg(target_arch = "arm")]
{
let gids = &[
Gid::from_raw(3003), // AID_INET
Gid::from_raw(3004), // AID_NET_RAW
];
nix::unistd::setgroups(gids).expect("setgroups failed");
}
// discard argv[0]
let _ = args.next();
// This call will only return if there is an error
let error = Command::new("/bin/bash")
.args(args)
.uid(0)
.gid(0)
.exec();
let error = Command::new("/bin/bash").args(args).uid(0).gid(0).exec();
eprintln!("Error running command: {error}");
std::process::exit(1);
}
+3 -3
View File
@@ -10,9 +10,9 @@ pub enum ParsingError {
}
pub fn decode<T>(data: &[u8]) -> Result<T, ParsingError>
where T: UperCodec<Output = T>
where
T: UperCodec<Output = T>,
{
let mut asn_data = PerCodecData::from_slice_uper(data);
T::uper_decode(&mut asn_data)
.map_err(ParsingError::UperDecodeError)
T::uper_decode(&mut asn_data).map_err(ParsingError::UperDecodeError)
}
+1 -1
View File
@@ -1,4 +1,4 @@
/*
/*
This file was autogenerated using hampi (https://github.com/ystero-dev/hampi), do not modify!
This place is not a place of honor...
+2 -2
View File
@@ -1,10 +1,10 @@
use telcom_parser::lte_rrc::BCCH_DL_SCH_Message;
use asn1_codecs::{uper::UperCodec, PerCodecData};
use telcom_parser::lte_rrc::BCCH_DL_SCH_Message;
fn hex_to_bin(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i+2], 16).unwrap())
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}