Collect signal strength and timing advances. LTE serving cell measurements (0xb17f) and RACH Timing Advance (0xb062)

This commit is contained in:
Carlos Guerra
2026-05-31 14:46:05 +02:00
committed by Will Greenberg
parent a3cf7ac1ee
commit 8fe516c2a6
5 changed files with 241 additions and 4 deletions
+115
View File
@@ -153,6 +153,19 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
payload: msg,
}))
}
LogBody::LteMl1ServingCellMeas { packet, .. } => {
// frame_number reused for PCI (normally SFN in RRC frames) so all three
// serving-cell fields are accessible in Wireshark as gsmtap.* columns.
let mut header = GsmtapHeader::new(GsmtapType::QcDiag);
header.signal_dbm = packet.get_rsrp_dbm();
header.arfcn = packet.get_earfcn().try_into().unwrap_or(0);
header.frame_number = packet.get_pci() as u32;
Ok(Some(GsmtapMessage {
header,
payload: vec![],
}))
}
LogBody::LteMacRachResponse { payload } => Ok(parse_rach_response(&payload)),
_ => {
error!("gsmtap_sink: ignoring unhandled log type: {value:?}");
Ok(None)
@@ -160,6 +173,108 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
}
}
// Parses a 0xb062 RACH response log and reconstructs a 7-byte MAC RAR PDU for Wireshark.
// Returns None if the log contains no MSG2 (no Timing Advance was received).
fn parse_rach_response(payload: &[u8]) -> Option<GsmtapMessage> {
// Outer header: version(u8) + num_subpackets(u8) + reserved(u16)
if payload.len() < 4 || payload[0] != 0x01 {
return None;
}
let num_subpackets = payload[1] as usize;
let mut offset = 4;
for _ in 0..num_subpackets {
// Subpacket header: id(u8) + version(u8) + size(u16 LE)
if offset + 4 > payload.len() {
break;
}
let sp_id = payload[offset];
let sp_version = payload[offset + 1];
let sp_size = u16::from_le_bytes([payload[offset + 2], payload[offset + 3]]) as usize;
if sp_size < 4 {
break;
}
let sp_end = offset + sp_size;
if sp_end > payload.len() {
break;
}
if sp_id == 0x06 {
// RACH Attempt subpacket
if let Some(msg) = extract_rach_attempt_gsmtap(&payload[offset + 4..sp_end], sp_version) {
return Some(msg);
}
}
offset = sp_end;
}
None
}
fn extract_rach_attempt_gsmtap(body: &[u8], version: u8) -> Option<GsmtapMessage> {
// Per SCAT diagltelogparser.py, RACH Attempt subpacket layouts:
// v0x02: hdr=4B, msg1=4B(BBh), msg2=7B(HBHh)
// v0x03/0x31: hdr=6B, msg1=4B(BBh), msg2=7B(HBHh)
// v0x32: hdr=6B, msg1=7B(BBhHb), msg2=7B(HBHh)
// rapid_offset is the header byte holding preamble_index & 0x3F (the RAPID)
let (hdr_size, msg1_size, rapid_offset, bitmask_offset) = match version {
0x02 => (4usize, 4usize, 0usize, 3usize),
0x03 | 0x31 => (6, 4, 2, 5),
0x32 => (6, 7, 2, 5),
_ => return None,
};
if body.len() < hdr_size {
return None;
}
let msg_bitmask = body[bitmask_offset];
let rapid = body[rapid_offset] & 0x3F;
let msg1_present = msg_bitmask & 0x01 != 0;
let msg2_present = msg_bitmask & 0x02 != 0;
if !msg2_present {
return None;
}
// MSG2: backoff(u16) + result(u8) + tc_rnti(u16) + ta(u16) = 7 bytes
let msg2_offset = hdr_size + if msg1_present { msg1_size } else { 0 };
if body.len() < msg2_offset + 7 {
return None;
}
let tc_rnti = u16::from_le_bytes([body[msg2_offset + 3], body[msg2_offset + 4]]);
let ta_raw = u16::from_le_bytes([body[msg2_offset + 5], body[msg2_offset + 6]]);
// 0xFFFF is a Qualcomm sentinel meaning the RAR was received but TA was not valid
if ta_raw == 0xFFFF {
return None;
}
let ta = ta_raw & 0x7FF;
// Reconstruct 7-byte MAC RAR PDU (3GPP TS 36.321 §6.1.5):
// subheader: E=0, T=0, RAPID[5:0]
// payload: R(1)|TA[10:3](8) | TA[2:0](3)|ULGrant[19:15](5) | ULGrant[14:7](8) |
// ULGrant[6:0](7)|TC-RNTI[15](1) | TC-RNTI[14:7](8) | TC-RNTI[6:0](7)|0(1)
//
// Use LteMacFramed (0x0f) so Wireshark's mac-lte dissector knows the RNTI type is
// RA-RNTI (type=2) and applies the RAR PDU format. The 4-byte framing prefix is:
// [RadioType=1(FDD)][Direction=1(DL)][RNTIType=2(RA-RNTI)][0x01=payload-marker]
let payload = vec![
0x01u8, 0x01, 0x02, 0x01, // framing: FDD, DL, RA-RNTI, payload-marker
rapid & 0x3F,
((ta >> 3) & 0xFF) as u8,
((ta & 0x07) as u8) << 5,
0u8, // UL grant zeroed; Wireshark only needs TA and TC-RNTI to decode the RAR
((tc_rnti >> 15) & 0x01) as u8,
((tc_rnti >> 7) & 0xFF) as u8,
((tc_rnti & 0x7F) as u8) << 1,
];
let mut header = GsmtapHeader::new(GsmtapType::LteMacFramed);
// Wireshark 4.x does not dispatch GSMTAP type 0x0f to its mac-lte dissector, so
// mac-lte.rar.ta is unavailable. TA is also stored in frame_number (gsmtap.frame_nr).
header.frame_number = ta as u32;
Some(GsmtapMessage { header, payload })
}
#[cfg(test)]
mod tests {
use super::*;