From 0120d9b17e473e4a536ca1d50b3833706e28f168 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:29:11 +0200 Subject: [PATCH 01/13] Add Samsung SDM collector to workspace --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index cef266e..8c52e75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/6grok-agent", "crates/6grok-api", "crates/6grok-qcsuper", + "crates/6grok-samsung-sdm", ] resolver = "2" From 3c239cdefaa8979d8748faa2b35c3181e7a0dbc6 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:29:19 +0200 Subject: [PATCH 02/13] Add Samsung SDM collector package --- crates/6grok-samsung-sdm/Cargo.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 crates/6grok-samsung-sdm/Cargo.toml diff --git a/crates/6grok-samsung-sdm/Cargo.toml b/crates/6grok-samsung-sdm/Cargo.toml new file mode 100644 index 0000000..6961646 --- /dev/null +++ b/crates/6grok-samsung-sdm/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "sixgrok-samsung-sdm" +version = "0.1.0" +edition.workspace = true +license = "GPL-2.0-or-later" +repository.workspace = true +rust-version.workspace = true + +[[bin]] +name = "6grok-samsung-sdm" +path = "src/main.rs" + +[dependencies] +sixgrok-core = { path = "../6grok-core" } +anyhow.workspace = true +clap.workspace = true +serde_json.workspace = true From 96c43ec513fc725cf9aef679df65bda19867cf56 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:30:22 +0200 Subject: [PATCH 03/13] Port SCAT Samsung SDM framing and control --- crates/6grok-samsung-sdm/src/sdm.rs | 464 ++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 crates/6grok-samsung-sdm/src/sdm.rs diff --git a/crates/6grok-samsung-sdm/src/sdm.rs b/crates/6grok-samsung-sdm/src/sdm.rs new file mode 100644 index 0000000..bdc4911 --- /dev/null +++ b/crates/6grok-samsung-sdm/src/sdm.rs @@ -0,0 +1,464 @@ +// SPDX-FileCopyrightText: 2026 mbound +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Samsung SDM framing/control adapted and translated from fgsect/scat: +// repository: https://github.com/fgsect/scat +// commit: 361ff551a4fbb30789c46750c00586682a7a9b26 +// paths: +// src/scat/parsers/samsung/sdmcmd.py +// src/scat/parsers/samsung/samsungparser.py +// Modified/translated to Rust for 6grok on 2026-09-05. + +use clap::ValueEnum; +use std::fmt; + +pub const SDM_START: u8 = 0x7f; +pub const SDM_END: u8 = 0x7e; +pub const SDM_HEADER_LEN: usize = 14; +pub const SDM_DIRECTION_DM: u8 = 0xa0; +pub const DEFAULT_START_MAGIC: u32 = 0x4141_4141; +pub const MAX_SDM_PACKET: usize = 8 * 1024 * 1024; + +pub const GROUP_CONTROL: u8 = 0x00; +pub const GROUP_COMMON: u8 = 0x01; +pub const GROUP_LTE: u8 = 0x02; +pub const GROUP_EDGE: u8 = 0x03; +pub const GROUP_HSPA: u8 = 0x04; +pub const GROUP_TRACE: u8 = 0x05; +pub const GROUP_IP: u8 = 0x07; + +pub const CONTROL_START: u8 = 0x00; +pub const CONTROL_STOP: u8 = 0x02; +pub const CHANGE_UPDATE_PERIOD_REQUEST: u8 = 0x06; +pub const COMMON_ITEM_SELECT_REQUEST: u8 = 0x10; +pub const LTE_ITEM_SELECT_REQUEST: u8 = 0x20; +pub const EDGE_ITEM_SELECT_REQUEST: u8 = 0x30; +pub const HSPA_ITEM_SELECT_REQUEST: u8 = 0x40; +pub const CDMA_ITEM_SELECT_REQUEST: u8 = 0x44; + +// Common-data item IDs from SCAT sdmcmd.py. +pub const COMMON_BASIC_INFO: u8 = 0x00; +pub const COMMON_CELL_INFO: u8 = 0x01; +pub const COMMON_DATA_INFO: u8 = 0x02; +pub const COMMON_SIGNALING_INFO: u8 = 0x03; +pub const COMMON_MULTI_SIGNALING_INFO: u8 = 0x06; +pub const COMMON_NR_RRC_SIGNALING_INFO: u8 = 0x08; +pub const COMMON_NR_NAS_SIGNALING_INFO: u8 = 0x09; + +// LTE-data item IDs from SCAT sdmcmd.py. +pub const LTE_PHY_STATUS: u8 = 0x00; +pub const LTE_PHY_CELL_SEARCH_MEAS: u8 = 0x01; +pub const LTE_PHY_NCELL_INFO: u8 = 0x02; +pub const LTE_L1_RF: u8 = 0x10; +pub const LTE_RRC_SERVING_CELL: u8 = 0x50; +pub const LTE_RRC_STATUS: u8 = 0x51; +pub const LTE_RRC_OTA_PACKET: u8 = 0x52; +pub const LTE_NAS_EMM_MESSAGE: u8 = 0x5a; +pub const LTE_NAS_ESM_MESSAGE: u8 = 0x5f; + +/// Native Shannon SDM frames occupy a separate 6grok namespace rather than +/// masquerading as the surviving parser's synthetic 0x20xx..0x23xx MIPC IDs. +pub const SDM_SYNTHETIC_BASE: u16 = 0x2400; + +#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)] +pub enum SdmProfile { + /// RRC/NAS signaling plus basic serving-cell context. + Signaling, + /// Serving/neighbor PHY measurements plus basic serving-cell context. + Radio, + /// Union of signaling and radio selections. + Full, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SdmHeader { + pub length1: u16, + pub zero: u8, + pub length2: u16, + pub stamp: u16, + pub direction: u8, + pub radio_id: u8, + pub group: u8, + pub command: u8, + pub timestamp: u32, +} + +impl SdmHeader { + pub fn synthetic_log_code(self) -> u16 { + SDM_SYNTHETIC_BASE | u16::from(self.group & 0x1f) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SdmFrame { + pub header: SdmHeader, + /// Complete wire packet, including 0x7f start byte and 0x7e terminator. + pub packet: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SdmError { + HeaderTooShort, + InvalidLengthRelation { length1: u16, length2: u16 }, + PacketTooLarge(usize), + BadTerminator(u8), +} + +impl fmt::Display for SdmError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::HeaderTooShort => write!(f, "Samsung SDM header is shorter than 14 bytes"), + Self::InvalidLengthRelation { length1, length2 } => write!( + f, + "Samsung SDM inner/outer lengths disagree (length1={length1}, length2={length2})" + ), + Self::PacketTooLarge(size) => write!(f, "Samsung SDM packet length {size} exceeds limit"), + Self::BadTerminator(value) => { + write!(f, "Samsung SDM packet has bad terminator 0x{value:02x}") + } + } + } +} + +impl std::error::Error for SdmError {} + +pub fn parse_header(header: &[u8]) -> Result { + if header.len() < SDM_HEADER_LEN { + return Err(SdmError::HeaderTooShort); + } + let length1 = u16::from_le_bytes([header[0], header[1]]); + let zero = header[2]; + let length2 = u16::from_le_bytes([header[3], header[4]]); + let stamp = u16::from_le_bytes([header[5], header[6]]); + let direction = header[7]; + let group_raw = header[8]; + let command = header[9]; + let timestamp = u32::from_le_bytes([header[10], header[11], header[12], header[13]]); + Ok(SdmHeader { + length1, + zero, + length2, + stamp, + direction, + radio_id: group_raw >> 5, + group: group_raw & 0x1f, + command, + timestamp, + }) +} + +/// Generate the SDM packet shape used by SCAT's `generate_sdm_packet`. +pub fn generate_packet( + direction: u8, + group: u8, + command: u8, + payload: &[u8], + timestamp: u32, +) -> Result, SdmError> { + // SCAT: pkt_len = 2 + 3 + 4 + payload + 2; length1 = pkt_len + 3. + let length2 = 11usize + .checked_add(payload.len()) + .ok_or(SdmError::PacketTooLarge(usize::MAX))?; + let length1 = length2 + 3; + let total = length1 + 2; + if total > MAX_SDM_PACKET || length1 > u16::MAX as usize { + return Err(SdmError::PacketTooLarge(total)); + } + + let mut out = Vec::with_capacity(total); + out.push(SDM_START); + out.extend_from_slice(&(length1 as u16).to_le_bytes()); + out.push(0); + out.extend_from_slice(&(length2 as u16).to_le_bytes()); + out.extend_from_slice(&0_u16.to_le_bytes()); // stamp + out.push(direction); + out.push(group); + out.push(command); + out.extend_from_slice(×tamp.to_le_bytes()); + out.extend_from_slice(payload); + out.push(SDM_END); + Ok(out) +} + +pub fn item_selection(items: &[u8]) -> Vec { + let mut out = Vec::with_capacity(1 + items.len() * 2); + out.push(items.len().min(0xfe) as u8); + for &item in items.iter().take(0xfe) { + out.push(item); + out.push(1); + } + out +} + +pub fn select_all() -> Vec { + vec![0xff] +} + +pub fn deselect_all() -> Vec { + vec![0x00] +} + +pub fn stop_packet() -> Result, SdmError> { + generate_packet(SDM_DIRECTION_DM, GROUP_CONTROL, CONTROL_STOP, &[], 0) +} + +/// Build the initialization transaction adapted from SCAT `SamsungParser.init_diag`. +pub fn init_packets( + start_magic: u32, + profile: SdmProfile, + all_items: bool, +) -> Result>, SdmError> { + let mut packets = Vec::new(); + packets.push(generate_packet( + SDM_DIRECTION_DM, + GROUP_CONTROL, + CONTROL_START, + &start_magic.to_be_bytes(), + 0, + )?); + packets.push(generate_packet( + SDM_DIRECTION_DM, + GROUP_CONTROL, + CHANGE_UPDATE_PERIOD_REQUEST, + &[0x05], + 0, + )?); + + for command in [ + COMMON_ITEM_SELECT_REQUEST, + LTE_ITEM_SELECT_REQUEST, + EDGE_ITEM_SELECT_REQUEST, + HSPA_ITEM_SELECT_REQUEST, + CDMA_ITEM_SELECT_REQUEST, + ] { + packets.push(generate_packet( + SDM_DIRECTION_DM, + GROUP_CONTROL, + command, + &deselect_all(), + 0, + )?); + } + + if all_items { + for command in [ + COMMON_ITEM_SELECT_REQUEST, + LTE_ITEM_SELECT_REQUEST, + EDGE_ITEM_SELECT_REQUEST, + HSPA_ITEM_SELECT_REQUEST, + CDMA_ITEM_SELECT_REQUEST, + ] { + packets.push(generate_packet( + SDM_DIRECTION_DM, + GROUP_CONTROL, + command, + &select_all(), + 0, + )?); + } + return Ok(packets); + } + + let (common, lte) = profile_items(profile); + packets.push(generate_packet( + SDM_DIRECTION_DM, + GROUP_CONTROL, + COMMON_ITEM_SELECT_REQUEST, + &item_selection(&common), + 0, + )?); + packets.push(generate_packet( + SDM_DIRECTION_DM, + GROUP_CONTROL, + LTE_ITEM_SELECT_REQUEST, + &item_selection(<e), + 0, + )?); + Ok(packets) +} + +fn profile_items(profile: SdmProfile) -> (Vec, Vec) { + let common_context = [COMMON_BASIC_INFO, COMMON_CELL_INFO, COMMON_DATA_INFO]; + let common_signaling = [ + COMMON_SIGNALING_INFO, + COMMON_MULTI_SIGNALING_INFO, + COMMON_NR_RRC_SIGNALING_INFO, + COMMON_NR_NAS_SIGNALING_INFO, + ]; + let lte_context = [LTE_RRC_SERVING_CELL, LTE_RRC_STATUS]; + let lte_signaling = [LTE_RRC_OTA_PACKET, LTE_NAS_EMM_MESSAGE, LTE_NAS_ESM_MESSAGE]; + let lte_radio = [LTE_PHY_STATUS, LTE_PHY_CELL_SEARCH_MEAS, LTE_PHY_NCELL_INFO, LTE_L1_RF]; + + match profile { + SdmProfile::Signaling => ( + common_context + .into_iter() + .chain(common_signaling) + .collect(), + lte_context.into_iter().chain(lte_signaling).collect(), + ), + SdmProfile::Radio => ( + common_context.to_vec(), + lte_context.into_iter().chain(lte_radio).collect(), + ), + SdmProfile::Full => ( + common_context + .into_iter() + .chain(common_signaling) + .collect(), + lte_context + .into_iter() + .chain(lte_signaling) + .chain(lte_radio) + .collect(), + ), + } +} + +#[derive(Debug, Default)] +pub struct SdmDecoder { + buffer: Vec, +} + +impl SdmDecoder { + pub fn push(&mut self, data: &[u8]) -> Vec> { + self.buffer.extend_from_slice(data); + let mut out = Vec::new(); + + loop { + let Some(start) = self.buffer.iter().position(|&b| b == SDM_START) else { + // Keep no arbitrary garbage: a future 0x7f will establish framing. + self.buffer.clear(); + break; + }; + if start > 0 { + self.buffer.drain(..start); + } + if self.buffer.len() < 1 + SDM_HEADER_LEN { + break; + } + + let header = match parse_header(&self.buffer[1..1 + SDM_HEADER_LEN]) { + Ok(header) => header, + Err(err) => { + out.push(Err(err)); + self.buffer.drain(..1); + continue; + } + }; + + if header.length1 != header.length2.saturating_add(3) { + out.push(Err(SdmError::InvalidLengthRelation { + length1: header.length1, + length2: header.length2, + })); + self.buffer.drain(..1); + continue; + } + + let total = usize::from(header.length1) + 2; + if total < 1 + SDM_HEADER_LEN + 1 || total > MAX_SDM_PACKET { + out.push(Err(SdmError::PacketTooLarge(total))); + self.buffer.drain(..1); + continue; + } + if self.buffer.len() < total { + break; + } + let terminator = self.buffer[total - 1]; + if terminator != SDM_END { + out.push(Err(SdmError::BadTerminator(terminator))); + // Match SCAT's resynchronization intent while being conservative: + // discard only this start marker and search again. + self.buffer.drain(..1); + continue; + } + + let packet: Vec = self.buffer.drain(..total).collect(); + out.push(Ok(SdmFrame { header, packet })); + } + + out + } +} + +pub fn group_name(group: u8) -> &'static str { + match group { + GROUP_CONTROL => "control", + GROUP_COMMON => "common", + GROUP_LTE => "lte", + GROUP_EDGE => "edge", + GROUP_HSPA => "hspa", + GROUP_TRACE => "trace", + GROUP_IP => "ip", + _ => "unknown", + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generated_packet_matches_scat_length_relationship() { + let packet = generate_packet(0xa0, GROUP_COMMON, COMMON_BASIC_INFO, &[1, 2, 3], 0x12345678) + .unwrap(); + assert_eq!(packet[0], SDM_START); + assert_eq!(*packet.last().unwrap(), SDM_END); + let header = parse_header(&packet[1..15]).unwrap(); + assert_eq!(header.length1, header.length2 + 3); + assert_eq!(usize::from(header.length1) + 2, packet.len()); + assert_eq!(header.direction, 0xa0); + assert_eq!(header.group, GROUP_COMMON); + assert_eq!(header.command, COMMON_BASIC_INFO); + assert_eq!(header.timestamp, 0x12345678); + } + + #[test] + fn decoder_handles_fragmentation_and_resynchronization() { + let one = generate_packet(0xa0, GROUP_COMMON, COMMON_CELL_INFO, &[0xaa], 0).unwrap(); + let two = generate_packet(0xa0, GROUP_LTE, LTE_RRC_OTA_PACKET, &[0xbb, 0xcc], 1).unwrap(); + let mut stream = vec![0x00, 0x11, 0x22]; + stream.extend_from_slice(&one); + stream.extend_from_slice(&two); + + let mut decoder = SdmDecoder::default(); + let split = 9; + assert!(decoder.push(&stream[..split]).is_empty()); + let frames: Vec<_> = decoder + .push(&stream[split..]) + .into_iter() + .map(Result::unwrap) + .collect(); + assert_eq!(frames.len(), 2); + assert_eq!(frames[0].header.group, GROUP_COMMON); + assert_eq!(frames[1].header.group, GROUP_LTE); + assert_eq!(frames[1].header.command, LTE_RRC_OTA_PACKET); + } + + #[test] + fn native_namespace_does_not_overlap_mipc_synthetic_ranges() { + let header = SdmHeader { + length1: 14, + zero: 0, + length2: 11, + stamp: 0, + direction: 0xa0, + radio_id: 0, + group: GROUP_LTE, + command: LTE_RRC_OTA_PACKET, + timestamp: 0, + }; + assert_eq!(header.synthetic_log_code(), 0x2402); + assert!(!(0x2000..=0x23ff).contains(&header.synthetic_log_code())); + } + + #[test] + fn signaling_profile_selects_lte_and_nr_signaling() { + let (common, lte) = profile_items(SdmProfile::Signaling); + assert!(common.contains(&COMMON_NR_RRC_SIGNALING_INFO)); + assert!(common.contains(&COMMON_NR_NAS_SIGNALING_INFO)); + assert!(lte.contains(<E_RRC_OTA_PACKET)); + assert!(lte.contains(<E_NAS_EMM_MESSAGE)); + } +} From d5fe34fb6982028385fbeca42e5069c1c82a0efa Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:31:00 +0200 Subject: [PATCH 04/13] Add native Samsung Shannon SDM collector --- crates/6grok-samsung-sdm/src/main.rs | 325 +++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 crates/6grok-samsung-sdm/src/main.rs diff --git a/crates/6grok-samsung-sdm/src/main.rs b/crates/6grok-samsung-sdm/src/main.rs new file mode 100644 index 0000000..e9a865a --- /dev/null +++ b/crates/6grok-samsung-sdm/src/main.rs @@ -0,0 +1,325 @@ +// SPDX-FileCopyrightText: 2026 mbound +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Native Samsung Shannon SDM collector derived from the framing/control model +// in fgsect/scat, commit 361ff551a4fbb30789c46750c00586682a7a9b26. +// See sdm.rs and THIRD_PARTY.md for file-level provenance. + +mod sdm; + +use anyhow::{Context, Result}; +use clap::{Parser, Subcommand}; +use sdm::{ + group_name, init_packets, stop_packet, SdmDecoder, SdmProfile, DEFAULT_START_MAGIC, +}; +use sixgrok_core::{encode_wire_frame, parser_payload, CaptureFrame, Vendor}; +use std::fs::{File, OpenOptions}; +use std::io::{self, Read, Write}; +use std::net::TcpStream; +use std::path::{Path, PathBuf}; +use std::time::{Instant, SystemTime, UNIX_EPOCH}; + +const DEFAULT_DEVICE: &str = "/dev/umts_dm0"; + +#[derive(Debug, Parser)] +#[command(name = "6grok-samsung-sdm")] +#[command(about = "Native Samsung Shannon SDM acquisition backend for 6grok")] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Initialize SDM and capture from a Shannon diagnostic character device. + Capture { + /// Shannon diagnostic device, commonly /dev/umts_dm0 on Samsung devices. + #[arg(long, default_value = DEFAULT_DEVICE)] + device: String, + /// Curated SDM item selection. + #[arg(long, value_enum, default_value_t = SdmProfile::Signaling)] + profile: SdmProfile, + /// Ask SDM for every item in the COMMON/LTE/EDGE/HSPA/CDMA groups. + /// This can generate very high log volume. + #[arg(long)] + all_items: bool, + /// Skip CONTROL_START/item-selection writes and only read an already-running SDM stream. + #[arg(long)] + passive: bool, + /// CONTROL_START magic, decimal or 0x-prefixed hexadecimal. + #[arg(long, value_parser = parse_u32_auto, default_value = "0x41414141")] + start_magic: u32, + /// Save the exact incoming SDM byte stream for lossless replay. + #[arg(long)] + raw_capture: Option, + /// Save normalized CaptureFrame objects as JSON Lines. + #[arg(long)] + frame_capture: Option, + /// Stream normalized frames to a 6grok-api ingest listener. + #[arg(long)] + server: Option, + }, + /// Send SDM CONTROL_STOP to a Shannon diagnostic character device. + Stop { + #[arg(long, default_value = DEFAULT_DEVICE)] + device: String, + }, + /// Replay a saved raw native SDM byte stream. + Replay { + path: PathBuf, + #[arg(long)] + frame_capture: Option, + #[arg(long)] + server: Option, + }, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + match cli.command { + Command::Capture { + device, + profile, + all_items, + passive, + start_magic, + raw_capture, + frame_capture, + server, + } => { + let mut io = open_device(&device)?; + if !passive { + configure_sdm(&mut io, start_magic, profile, all_items)?; + } + eprintln!( + "6grok-samsung-sdm: capturing {} SDM stream from {}{}", + if all_items { "all-item" } else { profile_name(profile) }, + device, + if passive { " (passive)" } else { "" } + ); + run_stream( + io, + true, + open_optional(raw_capture.as_deref())?, + open_optional(frame_capture.as_deref())?, + WireSink::connect_optional(server.as_deref())?, + ) + } + Command::Stop { device } => { + let mut io = open_device(&device)?; + io.write_all(&stop_packet()?) + .with_context(|| format!("writing SDM CONTROL_STOP to {device}"))?; + io.flush().context("flushing SDM CONTROL_STOP")?; + eprintln!("6grok-samsung-sdm: CONTROL_STOP sent to {device}"); + Ok(()) + } + Command::Replay { + path, + frame_capture, + server, + } => { + let input = File::open(&path) + .with_context(|| format!("opening SDM capture {}", path.display()))?; + run_stream( + input, + false, + None, + open_optional(frame_capture.as_deref())?, + WireSink::connect_optional(server.as_deref())?, + ) + } + } +} + +fn open_device(path: &str) -> Result { + OpenOptions::new() + .read(true) + .write(true) + .open(path) + .with_context(|| format!("opening Samsung Shannon SDM device {path}")) +} + +fn configure_sdm( + io: &mut File, + start_magic: u32, + profile: SdmProfile, + all_items: bool, +) -> Result<()> { + for packet in init_packets(start_magic, profile, all_items)? { + io.write_all(&packet).context("writing Samsung SDM control packet")?; + } + io.flush().context("flushing Samsung SDM initialization")?; + eprintln!( + "6grok-samsung-sdm: SDM initialized (start_magic=0x{start_magic:08x}, selection={})", + if all_items { "all" } else { profile_name(profile) } + ); + Ok(()) +} + +fn run_stream( + mut reader: R, + live: bool, + mut raw_capture: Option, + mut frame_capture: Option, + mut wire_sink: Option, +) -> Result<()> { + let start = Instant::now(); + let mut sequence = 0_u64; + let mut decoder = SdmDecoder::default(); + let mut buf = [0_u8; 64 * 1024]; + + loop { + let n = match reader.read(&mut buf) { + Ok(0) if live => continue, + Ok(0) => break, + Ok(n) => n, + Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, + Err(err) => return Err(err).context("reading Samsung SDM stream"), + }; + + if let Some(file) = raw_capture.as_mut() { + file.write_all(&buf[..n])?; + } + + for result in decoder.push(&buf[..n]) { + let sdm = match result { + Ok(frame) => frame, + Err(err) => { + eprintln!("6grok-samsung-sdm: dropping/resynchronizing invalid SDM frame: {err}"); + continue; + } + }; + sequence += 1; + let log_code = sdm.header.synthetic_log_code(); + let frame = CaptureFrame { + sequence, + timestamp_wall: unix_ms(), + timestamp_mono: start.elapsed().as_millis() as u64, + vendor: Vendor::Samsung, + log_code, + payload: parser_payload(log_code, &sdm.packet), + }; + emit_frame(&frame, &sdm.header, &mut frame_capture, &mut wire_sink)?; + } + } + + flush_optional(&mut raw_capture)?; + flush_optional(&mut frame_capture)?; + Ok(()) +} + +fn emit_frame( + frame: &CaptureFrame, + header: &sdm::SdmHeader, + frame_capture: &mut Option, + wire_sink: &mut Option, +) -> Result<()> { + if let Some(file) = frame_capture.as_mut() { + serde_json::to_writer(&mut *file, frame)?; + file.write_all(b"\n")?; + } + if let Some(sink) = wire_sink.as_mut() { + sink.send(frame)?; + } + + // Preserve the standard decoded-packet object while adding native SDM + // envelope metadata that the surviving parser does not yet model. + let output = serde_json::json!({ + "sdm": { + "direction": format!("0x{:02x}", header.direction), + "radio_id": header.radio_id, + "group": header.group, + "group_name": group_name(header.group), + "command": header.command, + "timestamp": header.timestamp, + "native_log_code": format!("0x{:04x}", frame.log_code), + }, + "decoded": frame.decode(), + }); + println!("{}", serde_json::to_string(&output)?); + Ok(()) +} + +struct WireSink { + stream: TcpStream, +} + +impl WireSink { + fn connect_optional(address: Option<&str>) -> Result> { + address + .map(|address| { + let stream = TcpStream::connect(address) + .with_context(|| format!("connecting to 6grok-api ingest at {address}"))?; + stream + .set_nodelay(true) + .context("enabling TCP_NODELAY for Samsung SDM uplink")?; + eprintln!("6grok-samsung-sdm: streaming frames to {address}"); + Ok(Self { stream }) + }) + .transpose() + } + + fn send(&mut self, frame: &CaptureFrame) -> Result<()> { + let payload = encode_wire_frame(frame).context("encoding MessagePack agent frame")?; + let len = u32::try_from(payload.len()).context("agent frame exceeds u32 wire length")?; + self.stream + .write_all(&len.to_be_bytes()) + .context("writing agent frame length")?; + self.stream + .write_all(&payload) + .context("writing agent MessagePack frame")?; + Ok(()) + } +} + +fn open_optional(path: Option<&Path>) -> Result> { + path.map(|path| { + File::create(path).with_context(|| format!("creating capture {}", path.display())) + }) + .transpose() +} + +fn flush_optional(file: &mut Option) -> Result<()> { + if let Some(file) = file.as_mut() { + file.flush()?; + } + Ok(()) +} + +fn parse_u32_auto(value: &str) -> std::result::Result { + let value = value.trim(); + if let Some(hex) = value + .strip_prefix("0x") + .or_else(|| value.strip_prefix("0X")) + { + u32::from_str_radix(hex, 16).map_err(|err| err.to_string()) + } else { + value.parse::().map_err(|err| err.to_string()) + } +} + +fn profile_name(profile: SdmProfile) -> &'static str { + match profile { + SdmProfile::Signaling => "signaling", + SdmProfile::Radio => "radio", + SdmProfile::Full => "full", + } +} + +fn unix_ms() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_start_magic() { + assert_eq!(parse_u32_auto("0x41414141").unwrap(), DEFAULT_START_MAGIC); + assert_eq!(parse_u32_auto("1094795585").unwrap(), DEFAULT_START_MAGIC); + } +} From 25777742fe22178c6f45eba017da6069d2ac9445 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:31:14 +0200 Subject: [PATCH 05/13] Temporarily install canonical SCAT GPLv2 text --- .github/workflows/canonical-gpl2.yml | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/canonical-gpl2.yml diff --git a/.github/workflows/canonical-gpl2.yml b/.github/workflows/canonical-gpl2.yml new file mode 100644 index 0000000..92f1168 --- /dev/null +++ b/.github/workflows/canonical-gpl2.yml @@ -0,0 +1,37 @@ +name: Canonicalize SCAT GPLv2 license + +on: + push: + branches: + - feature/scat-samsung-sdm + +permissions: + contents: write + +jobs: + canonicalize: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: feature/scat-samsung-sdm + - name: Install and verify SCAT GPLv2 text + run: | + set -euo pipefail + mkdir -p LICENSES + curl -fsSL \ + https://raw.githubusercontent.com/fgsect/scat/361ff551a4fbb30789c46750c00586682a7a9b26/COPYING \ + -o LICENSES/GPL-2.0-or-later.txt + test "$(git hash-object LICENSES/GPL-2.0-or-later.txt)" = "d159169d1050894d3ea3b98e1c965c4058208fe1" + - name: Commit canonical license + run: | + set -euo pipefail + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add LICENSES/GPL-2.0-or-later.txt + if git diff --cached --quiet; then + exit 0 + fi + git commit -m "Add verified GPL-2.0-or-later license text" + git push origin HEAD:feature/scat-samsung-sdm From 150e2b0c2b52dbb64f86ee8253281893db2eeab9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:31:22 +0000 Subject: [PATCH 06/13] Add verified GPL-2.0-or-later license text --- LICENSES/GPL-2.0-or-later.txt | 339 ++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 LICENSES/GPL-2.0-or-later.txt diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. From 3f6b05e6c99992c004ba6ecdd8c767e304f2b3a3 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:31:46 +0200 Subject: [PATCH 07/13] Remove temporary GPLv2 canonicalization workflow --- .github/workflows/canonical-gpl2.yml | 37 ---------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .github/workflows/canonical-gpl2.yml diff --git a/.github/workflows/canonical-gpl2.yml b/.github/workflows/canonical-gpl2.yml deleted file mode 100644 index 92f1168..0000000 --- a/.github/workflows/canonical-gpl2.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Canonicalize SCAT GPLv2 license - -on: - push: - branches: - - feature/scat-samsung-sdm - -permissions: - contents: write - -jobs: - canonicalize: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: feature/scat-samsung-sdm - - name: Install and verify SCAT GPLv2 text - run: | - set -euo pipefail - mkdir -p LICENSES - curl -fsSL \ - https://raw.githubusercontent.com/fgsect/scat/361ff551a4fbb30789c46750c00586682a7a9b26/COPYING \ - -o LICENSES/GPL-2.0-or-later.txt - test "$(git hash-object LICENSES/GPL-2.0-or-later.txt)" = "d159169d1050894d3ea3b98e1c965c4058208fe1" - - name: Commit canonical license - run: | - set -euo pipefail - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add LICENSES/GPL-2.0-or-later.txt - if git diff --cached --quiet; then - exit 0 - fi - git commit -m "Add verified GPL-2.0-or-later license text" - git push origin HEAD:feature/scat-samsung-sdm From fcafb9a93e03c75bed894cadb0c26796cd334a83 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:32:04 +0200 Subject: [PATCH 08/13] Classify Samsung SDM crate license in REUSE --- REUSE.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/REUSE.toml b/REUSE.toml index 887d5b8..24e081e 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -36,6 +36,11 @@ path = [ SPDX-FileCopyrightText = "2026 mbound" SPDX-License-Identifier = "GPL-3.0-or-later" +[[annotations]] +path = "crates/6grok-samsung-sdm/Cargo.toml" +SPDX-FileCopyrightText = "2026 mbound" +SPDX-License-Identifier = "GPL-2.0-or-later" + [[annotations]] path = "THIRD_PARTY_LICENSES/fivegrok-parser-MIT.txt" SPDX-FileCopyrightText = "2024 5grok Contributors" From b7b08cc9ea917d0d18bd1e67d058ad0f80447abc Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:32:54 +0200 Subject: [PATCH 09/13] Document native Samsung Shannon SDM capture --- docs/MULTI_VENDOR.md | 114 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 16 deletions(-) diff --git a/docs/MULTI_VENDOR.md b/docs/MULTI_VENDOR.md index 28ad7cd..23c5730 100644 --- a/docs/MULTI_VENDOR.md +++ b/docs/MULTI_VENDOR.md @@ -1,6 +1,6 @@ # Multi-vendor capture interfaces -6grok separates **device transport** from **record decoding**. Qualcomm currently has a native serial DIAG transport. MediaTek and Samsung have record-level adapters so device-specific collectors can feed 6grok without being linked into the parser/API. +6grok separates **device transport** from **record decoding**. Qualcomm has native DIAG transports, Samsung now has a native Shannon SDM collector, and MediaTek currently has a record-level adapter while a native Android collector is developed from appropriate MediaTek-specific sources. ## Qualcomm profiles @@ -14,6 +14,8 @@ Capture profiles are curated from log codes already present in the MIT-licensed Profiles are capability-aware: before writing a mask, 6grok queries the modem's equipment-ID ranges. Profile codes beyond a modem's reported range are skipped with a warning. Explicit `--log` codes remain strict and fail if unsupported. +Rooted Qualcomm Android devices can also use the GPL QCSuper interoperability backend; see the repository README. + ## MediaTek records The current parser consumes records with its documented 9-byte MediaTek envelope: @@ -34,9 +36,93 @@ Decode a record stream: The adapter repeats records until EOF and can simultaneously write normalized JSONL frames and stream them to `6grok-api`. -## Samsung records +Native MediaTek `mdlogger`/CCCI acquisition is **not** sourced from SCAT: the pinned SCAT tree used by 6grok has Qualcomm/Samsung and other modem support but no MediaTek collector. Native MediaTek work should instead use MediaTek-specific public interfaces and compatible sources such as MobileInsight where applicable. + +## Native Samsung Shannon SDM + +`6grok-samsung-sdm` is a native collector for Samsung Shannon/Exynos SDM diagnostic streams, adapted from SCAT's GPL-2.0-or-later Samsung implementation at pinned commit `361ff551a4fbb30789c46750c00586682a7a9b26`. + +The default device path is the commonly used: + +```text +/dev/umts_dm0 +``` + +Start a curated signaling capture: + +```bash +6grok-samsung-sdm capture --device /dev/umts_dm0 --profile signaling +``` + +Radio-focused or combined selections are also available: + +```bash +6grok-samsung-sdm capture --profile radio +6grok-samsung-sdm capture --profile full --server 10.0.0.2:5566 +``` + +SCAT's default SDM start magic is retained (`0x41414141`) and can be overridden: + +```bash +6grok-samsung-sdm capture --start-magic 0x41414141 --profile signaling +``` + +To attach to an SDM stream initialized by another process without writing selection commands: + +```bash +6grok-samsung-sdm capture --passive +``` + +Request every item only when the log volume is acceptable: + +```bash +6grok-samsung-sdm capture --all-items +``` + +Lossless native capture/replay is supported: + +```bash +6grok-samsung-sdm capture --raw-capture shannon.sdm --frame-capture shannon.frames.jsonl +6grok-samsung-sdm replay shannon.sdm +``` + +Stop SDM collection explicitly: + +```bash +6grok-samsung-sdm stop --device /dev/umts_dm0 +``` + +### Native SDM framing -The surviving parser understands a 16-byte MIPC-style envelope: +The collector follows SCAT's native SDM framing rather than the surviving parser's synthetic MIPC envelope: + +```text +0x7f +u16_le length1 +u8 zero +u16_le length2 +u16_le stamp +u8 direction +u8 group_with_radio_id +u8 command +u32_le modem_timestamp +bytes payload +0x7e +``` + +The decoder checks `length1 == length2 + 3`, bounds the packet size, requires the final `0x7e`, supports fragmented reads, and resynchronizes on the next `0x7f` after malformed data. + +Native SDM packets are preserved intact in normalized frames under a dedicated synthetic namespace: + +```text +0x2400 + SDM group +``` + +This intentionally does **not** reuse `0x2000..0x23ff`, because those are message-level synthetic IDs assigned by the surviving 5grok parser. Pretending a raw SDM group/command is one of those IDs would produce misleading NAS/RRC labels. Local output includes the actual SDM group, command, radio ID, direction, and modem timestamp while preserving the full wire packet for future semantic decoding. + +## Legacy/synthetic Samsung records + +The surviving parser separately understands a 16-byte MIPC-style envelope: ```text u32_le magic # 0x4d495043 in parser metadata @@ -53,18 +139,15 @@ bytes payload - `0x2200..0x22ff` ML1/PHY - `0x2300..0x23ff` MAC/PDCP/RLC -If the MIPC command is already in that range, it is used as the synthetic code. Otherwise provide the intended parser code explicitly: +Existing record imports remain available: ```bash 6grok-agent records samsung.bin --format samsung --log 0x2150 -``` - -For extracted raw PDUs with no Samsung envelope: - -```bash 6grok-agent records nr-rrc.bin --format raw --log 0x2150 ``` +These synthetic MIPC IDs and native SDM `0x24xx` frames are intentionally kept distinct. + For MediaTek raw PDUs use a parser-supported MediaTek code such as `0x1c01` or `0x1d01`. ## Normalized frame replay @@ -75,14 +158,13 @@ Any vendor can be replayed once converted to `CaptureFrame` JSON Lines: 6grok-agent frames session.frames.jsonl --server 127.0.0.1:5566 ``` -This is also the stable integration boundary for external collectors. A GPL collector such as SCAT may write/export records that are then consumed by 6grok as a **separate program**; its source code is not incorporated into the MIT 6grok binary. +This remains the stable integration boundary for external collectors as well as the native backends in this repository. ## Acquisition roadmap -Record-level support does not imply every device transport is solved. Planned native collectors are: - -1. MediaTek Android `mdlogger`/CCCI and external-modem logging transports, implemented from public interfaces or permissively licensed sources. -2. Samsung Shannon `/dev/umts_dm*` / SDM transports, after validating framing and command mapping across modem generations. -3. AT-command fallback for devices that expose measurements but lock diagnostic ports. +1. Validate native Samsung Shannon SDM selection behavior across additional ICD generations and real hardware, while retaining lossless raw capture as the compatibility fallback. +2. Add native MediaTek Android `mdlogger`/CCCI and external-modem transports using MediaTek-specific public interfaces and appropriately licensed implementations (for example MobileInsight where applicable). +3. Add AT-command fallback for devices that expose measurements but lock diagnostic ports. +4. Extend semantic parsing of native SDM envelopes without conflating them with the historical synthetic MIPC namespace. -Until a transport is validated on hardware, 6grok keeps it as an explicit record adapter rather than guessing framing and silently producing incorrect decodes. +Until a transport or mapping is validated, 6grok keeps the raw envelope intact rather than guessing framing or silently producing incorrect decodes. From 3a2a929c1fdcdfc97e10013816f325d2143bab15 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:33:21 +0200 Subject: [PATCH 10/13] Record native SCAT Shannon SDM derivation --- THIRD_PARTY.md | 58 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md index 9cc5e9d..6be2835 100644 --- a/THIRD_PARTY.md +++ b/THIRD_PARTY.md @@ -8,11 +8,12 @@ 2. Original reusable 6grok files/components may be **MIT OR GPL-3.0-or-later** when their SPDX metadata says so. 3. Third-party files **retain their original license and copyright**. The top-level project license never silently relicenses imported material. 4. A combined executable containing GPL-covered code is distributed under compatible GPL terms even when constituent files are also available under permissive licenses. -5. `GPL-2.0-or-later` code may participate in the GPLv3 application by selecting GPLv3 terms. `GPL-2.0-only` code must not be linked or copied into that combined work. -6. `GPL-3.0-only` is rejected by default because it would remove the project's intended "or later" licensing option. Review explicitly if ever needed. -7. AGPL is not accepted by default. MPL/LGPL/CDDL and other weak/file-level copyleft licenses require explicit architecture and redistribution review before use. -8. Standard SPDX license texts are retained under `LICENSES/`; upstream-specific license/NOTICE copies may be retained under `THIRD_PARTY_LICENSES/` or alongside vendored material. -9. Protocol facts, packet layouts and numeric constants are not treated as source-code imports, but references should still be recorded when useful. +5. `GPL-2.0-or-later` code may participate in a GPLv3 combined work by selecting GPLv3 terms; a separate GPL-2.0-or-later executable may also link dual-licensed 6grok libraries under their MIT option. +6. `GPL-2.0-only` code must not be linked or copied into the GPLv3 combined application without a different compatibility architecture. +7. `GPL-3.0-only` is rejected by default because it would remove the project's intended "or later" licensing option. Review explicitly if ever needed. +8. AGPL is not accepted by default. MPL/LGPL/CDDL and other weak/file-level copyleft licenses require explicit architecture and redistribution review before use. +9. Standard SPDX license texts are retained under `LICENSES/`; upstream-specific license/NOTICE copies may be retained under `THIRD_PARTY_LICENSES/` or alongside vendored material. +10. Protocol facts, packet layouts and numeric constants are not treated as source-code imports, but references should still be recorded when useful. ## Import checklist @@ -22,8 +23,8 @@ Every copied or adapted third-party source file must record: - immutable upstream commit/tag; - original upstream path; - SPDX license identifier; -- original upstream copyright holder(s); -- whether the file is copied verbatim or modified; +- original upstream copyright holder(s), where stated upstream; +- whether the file is copied verbatim or modified/translated; - a short modification/provenance notice when changed; - location of the corresponding license and NOTICE text. @@ -34,8 +35,8 @@ Prefer an SPDX header in the imported file. Do not replace an upstream SPDX iden | Project | Upstream license | Permitted use in 6grok | Provenance requirement | |---|---|---|---| | `mbound/5grok-parser` / `think-evil/5grok-parser` | MIT | Linked parser dependency | Pinned commit + retained MIT text | -| QCSuper (`P1sec/QCSuper`) | GPL-3.0-or-later | **Source reuse/adaptation allowed in GPL application** | Preserve GPL/copyright; record exact commit/path/modifications | -| SCAT (`fgsect/scat`) | GPL-2.0-or-later | **Source reuse/adaptation allowed in GPLv3 application** by choosing GPLv3 terms for the combined work | Preserve original GPL-2.0-or-later notice/copyright; record commit/path/modifications | +| QCSuper (`P1sec/QCSuper`) | GPL-3.0-or-later | Source reuse/adaptation in GPL application components | Preserve GPL/copyright; record exact commit/path/modifications | +| SCAT (`fgsect/scat`) | GPL-2.0-or-later | Source reuse/adaptation in compatible GPL components | Preserve GPL-2.0-or-later identity; record exact commit/path/modifications | | MobileInsight | Apache-2.0 | Source reuse/adaptation allowed | Preserve Apache-2.0 notices and any upstream NOTICE obligations | | FirmWire (`FirmWire/FirmWire`) | BSD-3-Clause | Source reuse/adaptation allowed where useful | Preserve BSD copyright/conditions/disclaimer | | ShannonBaseband (`grant-h/ShannonBaseband`) | Mixed/file-specific | Only files with a clearly compatible license may be reused | File-by-file SPDX/license review required | @@ -51,15 +52,15 @@ It is MIT licensed and states copyright: > Copyright (c) 2024 5grok Contributors -Its upstream-specific MIT copy remains preserved in `THIRD_PARTY_LICENSES/fivegrok-parser-MIT.txt`, while the standard MIT text used by REUSE is in `LICENSES/MIT.txt`. The parser is not relicensed by its use inside a GPL-covered 6grok executable. +Its upstream-specific MIT copy remains preserved in `THIRD_PARTY_LICENSES/fivegrok-parser-MIT.txt`, while the standard MIT text used by REUSE is in `LICENSES/MIT.txt`. The parser is not relicensed by its use inside a GPL-covered executable. ## QCSuper -QCSuper is currently pinned for provenance at: +QCSuper is pinned for provenance at: `aa555b4f7f25f7a8bf4e5afd4dcb884edf2f6735` (QCSuper 2.1.3, 2026-07-23) -QCSuper declares GPL-3.0+ / GPL-3.0-or-later. Its code may be copied or adapted into GPL-covered 6grok application components. The first integration is `crates/6grok-qcsuper`, a Rust interoperability backend for QCSuper's Android `/dev/diag` TCP bridge. +QCSuper declares GPL-3.0+ / GPL-3.0-or-later. The `crates/6grok-qcsuper` integration is a GPL-3.0-or-later Rust interoperability backend for QCSuper's Android `/dev/diag` TCP bridge. Upstream material used for that backend: @@ -72,27 +73,44 @@ Upstream material used for that backend: The bridge client itself is written in Rust for 6grok and carries GPL-3.0-or-later SPDX metadata. QCSuper-derived log selections explicitly record the upstream commit/path in source comments. The standard GPLv3 text is retained under `LICENSES/GPL-3.0-or-later.txt` and as the repository root `LICENSE`. -Do not move QCSuper-derived code into a component advertised as MIT-only. If functionality needs to be shared with a permissive library, isolate an independently written interface/data model from the GPL-derived implementation. - ### Qualcomm log-mask semantics cross-check -QCSuper calls the range value a log-mask bit size in parts of its implementation, but Qualcomm DIAG sources and Osmocom model the protocol field as inclusive `last_item`. 6grok therefore deliberately retains an inclusive mask length of `floor(last_item / 8) + 1` bytes. This is covered by regression tests, including a boundary where `last_item == 8` and bit 8 must occupy a second byte. +QCSuper calls the range value a log-mask bit size in parts of its implementation, but Qualcomm DIAG sources and Osmocom model the protocol field as inclusive `last_item`. 6grok therefore deliberately retains an inclusive mask length of `floor(last_item / 8) + 1` bytes. This is covered by regression tests, including a boundary where `last_item == 8` and bit 8 occupies a second byte. References used for this protocol cross-check include Qualcomm `diaglog.c` implementations and `osmocom/osmo-qcdiag/src/diag_log.c`; no Qualcomm source is copied into the dual-licensed core. -## SCAT +## SCAT / Samsung Shannon SDM -SCAT is currently pinned for provenance at: +SCAT is pinned for provenance at: `361ff551a4fbb30789c46750c00586682a7a9b26` (2026-09-03) -SCAT declares `GPL-2.0-or-later`. This is compatible with the GPLv3 6grok application because the "or later" grant permits selecting GPLv3 terms for the combined work. +SCAT declares `GPL-2.0-or-later`. Its exact `COPYING` file at that commit has Git blob SHA `d159169d1050894d3ea3b98e1c965c4058208fe1`. That exact text is retained as `LICENSES/GPL-2.0-or-later.txt` and was hash-verified before commit. + +The native Samsung collector is: + +`crates/6grok-samsung-sdm` + +and remains **GPL-2.0-or-later** at the crate/source level. It links `sixgrok-core` under the core's independent MIT grant, so the standalone collector does not require relabeling SCAT-derived files as GPLv3. + +Source material adapted/translated from SCAT: + +| Upstream path | 6grok use | +|---|---| +| `src/scat/parsers/samsung/sdmcmd.py` | SDM packet header, command/group constants, item-selection encoding, packet generation | +| `src/scat/parsers/samsung/samsungparser.py` | CONTROL_START/update-period/item-selection initialization and streaming frame extraction/resynchronization behavior | + +The implementation preserves the native SDM wire packet rather than converting it into the surviving 5grok parser's unrelated synthetic MIPC envelope. Native frames use 6grok namespace `0x2400 + SDM group`; the historical parser-assigned `0x2000..0x23ff` message families remain untouched. + +The collector validates both SDM length fields (`length1 == length2 + 3`), bounds packet length, requires `0x7e` termination, supports fragmented reads, resynchronizes on `0x7f`, and retains SCAT's default `CONTROL_START` magic `0x41414141` with an override option. + +### MediaTek correction -SCAT is being used to corroborate Qualcomm DIAG behavior and as the primary implementation reference for native Shannon/MediaTek acquisition work. SCAT-derived files must retain their `GPL-2.0-or-later` identity and copyright. Do not rewrite their file-level SPDX identifier to GPL-3.0 merely because the complete executable is conveyed under compatible GPLv3 terms. +The pinned SCAT tree used here does **not** provide the MediaTek `mdlogger`/CCCI collector previously contemplated in the roadmap. SCAT is therefore not the source for future native MediaTek integration. MediaTek work should use MediaTek-specific public interfaces and separately reviewed compatible sources, such as MobileInsight where applicable. ## Apache/BSD sources -Apache-2.0 and BSD-3-Clause sources can be included in the GPLv3 combined application while retaining their original licenses and notices. Apache NOTICE material, when present and applicable, must be propagated as required. +Apache-2.0 and BSD-3-Clause sources can be included in a GPLv3 combined application while retaining their original licenses and notices. Apache NOTICE material, when present and applicable, must be propagated as required. ## Rust dependencies From 77bd4fc7a499afe1a4216bf55b6b8617e4a5210f Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:34:10 +0200 Subject: [PATCH 11/13] Document native Shannon SDM backend --- README.md | 92 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 246fb3d..1a7e56e 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,11 @@ The working implementation currently provides: - capability-aware `signaling`, `radio` and `full` Qualcomm profiles; - GPL QCSuper interoperability backend for rooted Android `/dev/diag` through QCSuper `adb_bridge`; - QCSuper-derived signaling and IP/DPL capture profiles with pinned provenance; +- native Samsung Shannon/Exynos SDM capture through `/dev/umts_dm0`, adapted from pinned SCAT source; +- lossless native SDM raw capture/replay plus `signaling`, `radio`, `full`, passive and all-item modes; - normalized JSONL capture/replay; - MediaTek 9-byte parser-record ingestion; -- Samsung MIPC-style and raw-PDU ingestion using the parser's synthetic namespaces; +- legacy Samsung MIPC-style and raw-PDU ingestion using the parser's historical synthetic namespaces; - lightweight MessagePack/TCP agent uplink; - `6grok-api` aggregation service; - REST statistics/history and live WebSocket streaming; @@ -28,19 +30,19 @@ The working implementation currently provides: ```text modem / phone | - | DIAG / QCSuper bridge / SDM / vendor trace + | DIAG / QCSuper bridge / Shannon SDM / vendor trace v -+----------------+ +-------------+ +------------------+ -| 6grok-agent / | --> | 6grok-core | ----> | fivegrok-parser | -| 6grok-qcsuper | +-------------+ +------------------+ -+----------------+ | | - | | MessagePack | decoded packets - v v v - raw / JSONL +-----------+ JSON / history - | 6grok-api | - +-----------+ - / | \ - REST WS GSMTAP -> Wireshark ++--------------------+ +-------------+ +------------------+ +| 6grok-agent / | --> | 6grok-core | ----> | fivegrok-parser | +| 6grok-qcsuper / | +-------------+ +------------------+ +| 6grok-samsung-sdm | | | ++--------------------+ | MessagePack | decoded packets + | v v + v +-----------+ JSON / history + raw / JSONL | 6grok-api | + +-----------+ + / | \ + REST WS GSMTAP -> Wireshark ``` ## Build @@ -57,7 +59,7 @@ rustup target add aarch64-unknown-linux-musl cargo build --release --target aarch64-unknown-linux-musl -p sixgrok-agent ``` -The primary edge executable is named `6grok-agent`. The GPL QCSuper interoperability executable is `6grok-qcsuper`. +Primary edge executables are `6grok-agent`, `6grok-qcsuper`, and `6grok-samsung-sdm`. ## Qualcomm serial/USB capture @@ -103,40 +105,54 @@ cargo run -p sixgrok-agent -- replay capture.bin `6grok-qcsuper` interoperates with the TCP endpoint created by QCSuper's GPL `adb_bridge`. This is useful on Qualcomm Android devices where `/dev/diag` requires the diagchar setup logic already implemented and tested by QCSuper. -QCSuper's default bridge port is TCP 43555. Once its bridge is running and forwarded by ADB, probe the modem from 6grok: +QCSuper's default bridge port is TCP 43555. Once its bridge is running and forwarded by ADB: ```bash cargo run -p sixgrok-qcsuper -- probe +cargo run -p sixgrok-qcsuper -- capture --profile signaling +cargo run -p sixgrok-qcsuper -- capture --profile ip +cargo run -p sixgrok-qcsuper -- capture --profile full --server 10.0.0.2:5566 ``` -Capture QCSuper's established signaling selection: +The backend does not vendor QCSuper's Android executable. QCSuper remains the source of the on-device `/dev/diag` bridge; 6grok speaks its HDLC-over-TCP interface and performs DIAG log configuration itself. The integration is pinned to QCSuper commit `aa555b4f7f25f7a8bf4e5afd4dcb884edf2f6735` and its source-level provenance is recorded in [`THIRD_PARTY.md`](THIRD_PARTY.md). + +## Samsung Shannon SDM + +`6grok-samsung-sdm` is a native Samsung Shannon/Exynos diagnostic collector adapted from SCAT's Samsung SDM implementation at pinned commit `361ff551a4fbb30789c46750c00586682a7a9b26`. + +On devices exposing the conventional Shannon diagnostic node: ```bash -cargo run -p sixgrok-qcsuper -- capture --profile signaling +cargo run -p sixgrok-samsung-sdm -- capture --device /dev/umts_dm0 --profile signaling ``` -Capture Qualcomm IP/DPL records: +Other useful modes: ```bash -cargo run -p sixgrok-qcsuper -- capture --profile ip +cargo run -p sixgrok-samsung-sdm -- capture --profile radio +cargo run -p sixgrok-samsung-sdm -- capture --profile full --server 10.0.0.2:5566 +cargo run -p sixgrok-samsung-sdm -- capture --passive +cargo run -p sixgrok-samsung-sdm -- capture --all-items ``` -Or request the union and send normalized frames directly to a remote API service: +Lossless native SDM capture and replay: ```bash -cargo run -p sixgrok-qcsuper -- capture \ - --profile full \ - --frame-capture android.jsonl \ - --server 10.0.0.2:5566 +cargo run -p sixgrok-samsung-sdm -- capture \ + --raw-capture shannon.sdm \ + --frame-capture shannon.frames.jsonl +cargo run -p sixgrok-samsung-sdm -- replay shannon.sdm ``` -For a non-default forwarded endpoint: +Stop an initialized SDM stream: ```bash -cargo run -p sixgrok-qcsuper -- capture --bridge 127.0.0.1:43556 --profile signaling +cargo run -p sixgrok-samsung-sdm -- stop --device /dev/umts_dm0 ``` -The backend does not vendor QCSuper's Android executable. QCSuper remains the source of the on-device `/dev/diag` bridge; 6grok speaks its HDLC-over-TCP interface and performs DIAG log configuration itself. The integration is pinned to QCSuper commit `aa555b4f7f25f7a8bf4e5afd4dcb884edf2f6735` and its source-level provenance is recorded in [`THIRD_PARTY.md`](THIRD_PARTY.md). +Native SDM is kept distinct from the surviving parser's historical synthetic Samsung IDs. Full SDM packets are preserved under `0x2400 + group`, with actual direction/radio-ID/group/command/timestamp exposed in local JSON. This avoids inventing NAS/RRC labels before a native SDM message has been semantically mapped. + +See [`docs/MULTI_VENDOR.md`](docs/MULTI_VENDOR.md) for framing and namespace details. ## Service / remote agents @@ -174,9 +190,9 @@ cargo run -p sixgrok-api -- --gsmtap 127.0.0.1:4729 See [`docs/WIRESHARK.md`](docs/WIRESHARK.md). -## MediaTek and Samsung +## MediaTek and imported records -The current vendor boundary supports parser-compatible MediaTek records, Samsung MIPC-style records and extracted raw vendor PDUs. Native device-specific collection transports are being added behind this boundary rather than coupling them to parser internals. +The current generic vendor boundary supports parser-compatible MediaTek records, historical Samsung MIPC-style records and extracted raw vendor PDUs: ```bash cargo run -p sixgrok-agent -- records capture.bin --format mediatek @@ -184,27 +200,31 @@ cargo run -p sixgrok-agent -- records capture.bin --format samsung cargo run -p sixgrok-agent -- records pdu.bin --format raw --log 0x2060 ``` +Native MediaTek collection is still a separate milestone. The pinned SCAT tree does not provide a MediaTek collector, so future `mdlogger`/CCCI work will use MediaTek-specific interfaces and separately reviewed compatible sources such as MobileInsight where applicable. + See [`docs/MULTI_VENDOR.md`](docs/MULTI_VENDOR.md). ## Licensing 6grok intentionally uses a **multi-license architecture**. -- The combined `6grok-agent` application and `sixgrok-qcsuper` backend are `GPL-3.0-or-later`. +- `6grok-agent` and `6grok-qcsuper` are `GPL-3.0-or-later`. +- The SCAT-derived `6grok-samsung-sdm` collector remains `GPL-2.0-or-later` and links reusable 6grok code under its independent MIT option. - Original reusable `sixgrok-core` and `sixgrok-api` code is available under `MIT OR GPL-3.0-or-later` where indicated by repository metadata. - Third-party files retain their exact upstream license, copyright and notices. -- QCSuper (`GPL-3.0-or-later`) and SCAT (`GPL-2.0-or-later`) source may be reused/adapted in GPL application components with explicit provenance. +- QCSuper (`GPL-3.0-or-later`) and SCAT (`GPL-2.0-or-later`) source reuse is tracked with immutable upstream revision/path provenance. - MIT, Apache-2.0 and compatible BSD material may also be incorporated while retaining its original terms. -- `GPL-2.0-only`, AGPL and other licenses outside the reviewed compatibility policy are not imported into the combined application without explicit review. +- `GPL-2.0-only`, AGPL and other licenses outside the reviewed compatibility policy are not imported into incompatible combined components without explicit review. -The root [`LICENSE`](LICENSE) contains the GPLv3 license text. See [`docs/LICENSING.md`](docs/LICENSING.md) for the component model and [`THIRD_PARTY.md`](THIRD_PARTY.md) for import/provenance requirements. +The root [`LICENSE`](LICENSE) contains GPLv3 for the principal GPLv3 application distribution. Standard SPDX texts for both GPLv3 and GPLv2-or-later material are retained under [`LICENSES/`](LICENSES/). See [`docs/LICENSING.md`](docs/LICENSING.md) and [`THIRD_PARTY.md`](THIRD_PARTY.md). -`cargo-deny` and REUSE metadata are used to make license drift visible in CI. +`cargo-deny` and REUSE metadata make license drift visible in CI. ## Next milestones -- native MediaTek mdlogger/CCCI acquisition; -- validated native Samsung Shannon SDM acquisition; +- hardware validation of native Shannon SDM across more modem/ICD generations; +- semantic native-SDM parsing while preserving the raw envelope and avoiding synthetic-ID conflation; +- native MediaTek `mdlogger`/CCCI acquisition from MediaTek-specific compatible sources; - direct Android diagchar backend where it adds value beyond QCSuper bridge interoperability; - GPS/NMEA/gpsd synchronized location frames; - AT-monitor fallback for DIAG-locked devices; From f738831dc07ce57aeef792b88cfe07306356f2b4 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:34:40 +0200 Subject: [PATCH 12/13] Document standalone GPLv2-or-later Samsung component --- docs/LICENSING.md | 65 ++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/docs/LICENSING.md b/docs/LICENSING.md index c7e55e6..c2c3123 100644 --- a/docs/LICENSING.md +++ b/docs/LICENSING.md @@ -8,7 +8,9 @@ This document describes the repository policy; it is not a substitute for legal | Component/material | License policy | |---|---| -| `sixgrok-agent` combined application | `GPL-3.0-or-later` | +| `sixgrok-agent` application | `GPL-3.0-or-later` | +| `sixgrok-qcsuper` QCSuper interoperability backend | `GPL-3.0-or-later` | +| `sixgrok-samsung-sdm` SCAT-derived Shannon collector | `GPL-2.0-or-later` | | Original reusable `sixgrok-core` | `MIT OR GPL-3.0-or-later` | | Original reusable `sixgrok-api` | `MIT OR GPL-3.0-or-later` | | Original source files identified as dual licensed by SPDX/REUSE metadata | `MIT OR GPL-3.0-or-later` | @@ -17,34 +19,33 @@ This document describes the repository policy; it is not a substitute for legal | SCAT-derived source | upstream `GPL-2.0-or-later`, retained | | Apache/BSD/MIT imports | retain their exact upstream license and notices | -The repository root `LICENSE` contains the GNU GPL version 3 text because the complete 6grok agent/application distribution is GPL-covered once GPL-derived modem components are incorporated. +The repository root `LICENSE` contains GNU GPL version 3 because the principal 6grok agent/application distribution is GPL-3.0-or-later. File/component-specific terms remain authoritative where explicitly assigned, and standard GPLv2-or-later material is also retained under `LICENSES/GPL-2.0-or-later.txt`. ## Dual licensing does not relicense imports `MIT OR GPL-3.0-or-later` applies only where the copyright holder has offered those choices. It does **not** convert an imported GPL, Apache, BSD or MIT file into another license. -For example: +The architecture supports both of these legitimate combinations: ```text -sixgrok-core source MIT OR GPL-3.0-or-later -fivegrok-parser source MIT -SCAT-derived Samsung module GPL-2.0-or-later -QCSuper-derived Qualcomm module GPL-3.0-or-later - - linked into sixgrok-agent - | - v - combined distributed work - GPL-3.0-or-later +sixgrok-core (MIT OR GPL-3+) sixgrok-core (MIT OR GPL-3+) + | | + | choose GPL-3+ | choose MIT + v v +sixgrok-qcsuper / agent sixgrok-samsung-sdm + GPL-3+ GPL-2+ ``` -The original file-level licenses and notices remain applicable to those files inside the combined work. +A SCAT-derived file continues to identify itself as `GPL-2.0-or-later`. It is not relabeled GPLv3 merely because GPL-2.0-or-later could alternatively be used under GPLv3 terms in another combined work. ## GPL version compatibility rule -SCAT declares `GPL-2.0-or-later`. The `or-later` grant permits use under GPLv3 terms when it is combined with a GPLv3 application. +SCAT declares `GPL-2.0-or-later`. The `or-later` grant permits either: -QCSuper declares GPL-3.0+ / GPL-3.0-or-later and therefore fits the same combined GPLv3 application. +- keeping a standalone SCAT-derived component under `GPL-2.0-or-later` while linking separately dual-licensed libraries under a compatible permissive grant; or +- selecting GPLv3 terms when SCAT-derived material is actually combined into a GPLv3 work. + +QCSuper declares GPL-3.0+ / GPL-3.0-or-later and fits the GPLv3 application side directly. The following are **not accepted automatically**: @@ -61,7 +62,7 @@ Every copied or translated/adapted upstream source file must retain or add enoug 2. immutable upstream commit/tag; 3. original source path; 4. upstream SPDX/license; -5. upstream copyright holder(s); +5. upstream copyright holder(s), where stated upstream; 6. whether 6grok changed or translated the file; 7. date/summary of material modifications; 8. retained license and NOTICE location. @@ -71,34 +72,24 @@ A typical adapted QCSuper-derived Rust module should carry a notice similar to: ```text SPDX-License-Identifier: GPL-3.0-or-later Derived from P1sec/QCSuper, commit , -Upstream copyright: Modified for 6grok: ``` -A SCAT-derived file should continue to identify itself as `GPL-2.0-or-later`; it should not be relabeled GPLv3 merely because the complete executable is conveyed under compatible GPLv3 terms. +A SCAT-derived module carries `SPDX-License-Identifier: GPL-2.0-or-later` and records the pinned SCAT commit/source paths. The exact SCAT GPLv2 text used by this repository is hash-verified and retained under `LICENSES/GPL-2.0-or-later.txt`. ## Permissive reusable boundary -GPL-derived acquisition implementations belong in the GPL application side of the architecture, not in `sixgrok-core`. - -`sixgrok-core` should contain neutral data models, wire formats, original decoders/utilities, and permissively licensed code only. This preserves its useful MIT reuse option. +GPL-derived acquisition implementations belong in GPL components, not in `sixgrok-core`. -A good dependency direction is therefore: +`sixgrok-core` contains neutral data models, wire formats, original decoders/utilities, and permissively/dual-licensed code. This preserves its MIT reuse option and allows a GPL-2.0-or-later standalone collector to consume the common `CaptureFrame`/MessagePack model under MIT without a license-version conflict. ```text - sixgrok-core - MIT OR GPL-3.0-or-later - ^ - | - +-------------+-------------+ - | | -sixgrok-api sixgrok-agent -MIT OR GPL-3+ GPL-3+ - | - +--------------+--------------+ - | | - QCSuper-derived SCAT-derived - GPL-3+ GPL-2+ + sixgrok-core + MIT OR GPL-3.0-or-later + / | \ + choose MIT choose GPL3 choose GPL3 + / | \ + samsung-sdm GPL-2+ agent GPL-3+ qcsuper GPL-3+ ``` ## Distribution obligations @@ -107,7 +98,7 @@ When distributing a GPL-covered 6grok binary, make the corresponding source avai For releases, the preferred model is to publish the exact source revision, build scripts/configuration and retained license notices alongside binaries from the same release/tag. -If 6grok is embedded in a consumer product, GPLv3's installation-information provisions may also become relevant depending on how the product is distributed and controlled. +GPLv3 installation-information provisions can become relevant for GPLv3-covered software distributed in a qualifying User Product. Evaluate the obligations of the **actual component/license being conveyed**, rather than assuming every executable in the repository has the same GPL version. ## Repository enforcement From 50196eca643011fcf7efee7e9a0cc51dce7465e7 Mon Sep 17 00:00:00 2001 From: mbound <8779548+mbound@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:35:56 +0200 Subject: [PATCH 13/13] Avoid SPDX example false-positive in REUSE lint --- docs/LICENSING.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/LICENSING.md b/docs/LICENSING.md index c2c3123..2557bf6 100644 --- a/docs/LICENSING.md +++ b/docs/LICENSING.md @@ -67,15 +67,9 @@ Every copied or translated/adapted upstream source file must retain or add enoug 7. date/summary of material modifications; 8. retained license and NOTICE location. -A typical adapted QCSuper-derived Rust module should carry a notice similar to: +A typical adapted QCSuper-derived Rust module should state the GPL-3.0-or-later SPDX identifier, the pinned upstream repository/commit/path, and the date/summary of the 6grok modification. -```text -SPDX-License-Identifier: GPL-3.0-or-later -Derived from P1sec/QCSuper, commit , -Modified for 6grok: -``` - -A SCAT-derived module carries `SPDX-License-Identifier: GPL-2.0-or-later` and records the pinned SCAT commit/source paths. The exact SCAT GPLv2 text used by this repository is hash-verified and retained under `LICENSES/GPL-2.0-or-later.txt`. +A SCAT-derived module uses the SPDX license value `GPL-2.0-or-later` and records the pinned SCAT commit/source paths. The exact SCAT GPLv2 text used by this repository is hash-verified and retained under `LICENSES/GPL-2.0-or-later.txt`. ## Permissive reusable boundary