tramex_tools/interface/
parse_config.rs

1//! Connection type and file metadata parsing
2
3use std::fmt;
4
5#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
6/// Technology enum
7pub enum Technology {
8    /// LTE (4G) connection
9    LTE,
10    /// NR (5G) connection
11    NR,
12    /// Unknown connection type
13    #[default]
14    Unknown,
15}
16
17impl fmt::Display for Technology {
18    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19        match self {
20            Technology::LTE => write!(f, "LTE (4G)"),
21            Technology::NR => write!(f, "NR (5G)"),
22            Technology::Unknown => write!(f, "--"),
23        }
24    }
25}
26
27#[derive(Debug, Clone, Default)]
28/// File metadata extracted from header comments
29pub struct FileMetadata {
30    /// Technology (LTE or NR)
31    pub technology: Technology,
32
33    /// Cell information (raw line)
34    pub cell_info: Option<String>,
35
36    /// Version information
37    pub version: Option<String>,
38
39    /// Started timestamp
40    pub started_on: Option<String>,
41
42    /// Rotated timestamp
43    pub rotated_on: Option<String>,
44
45    /// Physical Cell ID (PCI)
46    pub pci: Option<u16>,
47
48    /// Mode (TDD or FDD)
49    pub mode: Option<String>,
50
51    /// Frequency (ARFCN - nr_arfcn or earfcn)
52    pub arfcn: Option<u32>,
53
54    /// IO mode ("SISO" if dl_mu=1, "MIMO" otherwise)
55    pub io_mode: Option<String>,
56
57    /// SSB configuration lines from header comments (multiple SSBs possible)
58    pub ssb_info: Vec<String>,
59}
60
61impl FileMetadata {
62    /// Parse file metadata from header lines (lines starting with #)
63    pub fn parse_from_lines(lines: &[String]) -> Self {
64        let mut metadata = FileMetadata::default();
65
66        for line in lines {
67            let trimmed = line.trim();
68
69            // Stop parsing when we hit a non-comment line
70            if !trimmed.starts_with('#') {
71                break;
72            }
73
74            // Parse connection type from Cell line
75            if trimmed.starts_with("# Cell") {
76                metadata.cell_info = Some(trimmed.to_string());
77
78                // Parse technology
79                if trimmed.contains("nr_arfcn") {
80                    metadata.technology = Technology::NR;
81                } else if trimmed.contains("earfcn") {
82                    metadata.technology = Technology::LTE;
83                }
84
85                // Parse PCI
86                if let Some(pci_value) = Self::extract_value(trimmed, "pci=") {
87                    metadata.pci = pci_value.parse().ok();
88                }
89
90                // Parse mode (TDD/FDD)
91                if let Some(mode_value) = Self::extract_value(trimmed, "mode=") {
92                    metadata.mode = Some(mode_value.to_uppercase());
93                }
94
95                // Parse ARFCN (try nr_arfcn first, then earfcn)
96                if let Some(arfcn_value) = Self::extract_value(trimmed, "nr_arfcn=") {
97                    metadata.arfcn = arfcn_value.parse().ok();
98                } else if let Some(arfcn_value) = Self::extract_value(trimmed, "earfcn=") {
99                    metadata.arfcn = arfcn_value.parse().ok();
100                }
101
102                // Parse IO mode
103                if let Some(dl_mu_value) = Self::extract_value(trimmed, "dl_mu=")
104                    && let Some(ul_mu_value) = Self::extract_value(trimmed, "ul_mu=")
105                {
106                    let input = if dl_mu_value == "1" { "SI" } else { "MI" };
107                    let output = if ul_mu_value == "1" { "SO" } else { "MO" };
108                    metadata.io_mode = Some(format!("{}{}", input, output));
109                }
110            }
111
112            // Parse SSB header lines (collect all, not just first)
113            if trimmed.starts_with("# SSB:") {
114                metadata.ssb_info.push(trimmed.to_string());
115            }
116
117            // Parse version
118            if trimmed.starts_with("# lteenb version") || trimmed.starts_with("# mme version") {
119                metadata.version = Some(trimmed.trim_start_matches('#').trim().to_string());
120            }
121
122            // Parse started timestamp
123            if trimmed.starts_with("# Started on") {
124                metadata.started_on = Some(trimmed.trim_start_matches("# Started on").trim().to_string());
125            }
126
127            // Parse started timestamp
128            if trimmed.starts_with("# Rotated on") {
129                metadata.rotated_on = Some(trimmed.trim_start_matches("# Rotated on").trim().to_string());
130            }
131        }
132        metadata
133    }
134
135    /// Extract a value from a key=value pair in a string
136    fn extract_value<'a>(line: &'a str, key: &str) -> Option<&'a str> {
137        line.find(key).map(|start| {
138            let value_start = start + key.len();
139            let rest = &line[value_start..];
140            // Find the end of the value (space or end of string)
141            let end = rest.find(' ').unwrap_or(rest.len());
142            &rest[..end]
143        })
144    }
145
146    /// Get a specific header value by key
147    pub fn get_header_value(&self, key: &str) -> Option<String> {
148        match key {
149            "technology" => Some(self.technology.to_string()),
150            "version" => self.version.clone(),
151            "started_on" => self.started_on.clone(),
152            "rotated_on" => self.rotated_on.clone(),
153            "cell_info" => self.cell_info.clone(),
154            "pci" => self.pci.map(|v| v.to_string()),
155            "mode" => self.mode.clone(),
156            "arfcn" => self.arfcn.map(|v| v.to_string()),
157            "io_mode" => self.io_mode.clone(),
158            "ssb_info" => {
159                if self.ssb_info.is_empty() {
160                    None
161                } else {
162                    Some(self.ssb_info.join("; "))
163                }
164            }
165            _ => None,
166        }
167    }
168}