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    /// Number of resource blocks
55    pub n_rb: Option<u16>,
56
57    /// IO mode ("SISO" if dl_mu=1, "MIMO" otherwise)
58    pub io_mode: Option<String>,
59
60    /// SSB configuration lines from header comments (multiple SSBs possible)
61    pub ssb_info: Vec<String>,
62}
63
64impl FileMetadata {
65    /// Parse file metadata from header lines (lines starting with #)
66    pub fn parse_from_lines(lines: &[String]) -> Self {
67        let mut metadata = FileMetadata::default();
68
69        for line in lines {
70            // println!("Line: {}", line);
71            let trimmed = line.trim();
72
73            // Stop parsing when we hit a non-comment line
74            if !trimmed.starts_with('#') {
75                break;
76            }
77
78            // Parse connection type from Cell line
79            if trimmed.starts_with("# Cell") {
80                metadata.cell_info = Some(trimmed.to_string());
81
82                // Parse technology
83                if trimmed.contains("nr_arfcn") {
84                    metadata.technology = Technology::NR;
85                } else if trimmed.contains("earfcn") {
86                    metadata.technology = Technology::LTE;
87                }
88
89                // Parse PCI
90                if let Some(pci_value) = Self::extract_value(trimmed, "pci=") {
91                    metadata.pci = pci_value.parse().ok();
92                }
93
94                // Parse mode (TDD/FDD)
95                if let Some(mode_value) = Self::extract_value(trimmed, "mode=") {
96                    metadata.mode = Some(mode_value.to_uppercase());
97                }
98
99                // Parse ARFCN (try nr_arfcn first, then earfcn)
100                if let Some(arfcn_value) = Self::extract_value(trimmed, "nr_arfcn=") {
101                    metadata.arfcn = arfcn_value.parse().ok();
102                } else if let Some(arfcn_value) = Self::extract_value(trimmed, "earfcn=") {
103                    metadata.arfcn = arfcn_value.parse().ok();
104                }
105
106                // Parse n_rb
107                if let Some(n_rb_value) = Self::extract_value(trimmed, "n_rb_dl=") {
108                    metadata.n_rb = n_rb_value.parse().ok();
109                    if let Some(n_rb_ul_value) = Self::extract_value(trimmed, "n_rb_ul=")
110                        && n_rb_ul_value != n_rb_value
111                    {
112                        log::error!(
113                            "n_rb_dl and n_rb_ul are different: {} and {}, this is not supported n_rb_dl has been used",
114                            n_rb_value,
115                            n_rb_ul_value
116                        );
117                    }
118                }
119
120                // Parse IO mode
121                if let Some(dl_mu_value) = Self::extract_value(trimmed, "dl_mu=")
122                    && let Some(ul_mu_value) = Self::extract_value(trimmed, "ul_mu=")
123                {
124                    let input = if dl_mu_value == "1" { "SI" } else { "MI" };
125                    let output = if ul_mu_value == "1" { "SO" } else { "MO" };
126                    metadata.io_mode = Some(format!("{}{}", input, output));
127                }
128            }
129
130            // Parse SSB header lines (collect all, not just first)
131            if trimmed.starts_with("# SSB:") {
132                metadata.ssb_info.push(trimmed.to_string());
133            }
134
135            // Parse version
136            if trimmed.starts_with("# lteenb version") || trimmed.starts_with("# mme version") {
137                metadata.version = Some(trimmed.trim_start_matches('#').trim().to_string());
138            }
139
140            // Parse started timestamp
141            if trimmed.starts_with("# Started on") {
142                metadata.started_on = Some(trimmed.trim_start_matches("# Started on").trim().to_string());
143            }
144
145            // Parse started timestamp
146            if trimmed.starts_with("# Rotated on") {
147                metadata.rotated_on = Some(trimmed.trim_start_matches("# Rotated on").trim().to_string());
148            }
149        }
150        log::debug!("{:?}", metadata);
151        metadata
152    }
153
154    /// Extract a value from a key=value pair in a string
155    fn extract_value<'a>(line: &'a str, key: &str) -> Option<&'a str> {
156        line.find(key).map(|start| {
157            let value_start = start + key.len();
158            let rest = &line[value_start..];
159            // Find the end of the value (space or end of string)
160            let end = rest.find(' ').unwrap_or(rest.len());
161            &rest[..end]
162        })
163    }
164
165    /// Get a specific header value by key
166    pub fn get_header_value(&self, key: &str) -> Option<String> {
167        match key {
168            "technology" => Some(self.technology.to_string()),
169            "version" => self.version.clone(),
170            "started_on" => self.started_on.clone(),
171            "rotated_on" => self.rotated_on.clone(),
172            "cell_info" => self.cell_info.clone(),
173            "pci" => self.pci.map(|v| v.to_string()),
174            "mode" => self.mode.clone(),
175            "arfcn" => self.arfcn.map(|v| v.to_string()),
176            "io_mode" => self.io_mode.clone(),
177            "ssb_info" => {
178                if self.ssb_info.is_empty() {
179                    None
180                } else {
181                    Some(self.ssb_info.join("; "))
182                }
183            }
184            _ => None,
185        }
186    }
187}