1use super::ParsingError;
4use super::hex_extractor::extract_binary_from_lines;
5use crate::data::{AdditionalInfos, Trace};
6use crate::interface::association::TraceRelation;
7use crate::interface::{layer::Layer, types::Direction};
8
9use super::FileParser;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum PHYChannelType {
14 PDSCH,
16 PUSCH,
18 PUCCH,
20 PDCCH,
22 PRACH,
24 Other,
26}
27
28#[derive(Debug, Clone)]
30pub enum PHYChannelData {
31 Pdcch {
33 dci: String,
35 harq_process: Option<u8>,
37 ndi: Option<u8>,
39 rv_idx: Option<u8>,
41 harq_feedback_timing: Option<u8>,
43 },
44 Pdsch {
46 retx: Option<u8>,
48 rv_idx: Option<u8>,
50 },
51 Pusch {
53 retx: Option<u8>,
55 rv_idx: Option<u8>,
57 crc: Option<bool>,
59 },
60 Pucch {
62 format: Option<u8>,
64 ack: Option<bool>,
66 },
67 None,
69}
70
71#[derive(Debug, Clone)]
73pub struct PHYInfos {
74 pub direction: Direction,
76 pub channel_type: PHYChannelType,
78 pub frame: u16,
80 pub slot: u8,
82 pub prb_start: u16,
84 pub prb_length: u16,
86 pub symb_start: u8,
88 pub symb_length: u8,
90 pub harq: Option<u8>,
92 pub harq_si: bool,
94 pub channel_data: PHYChannelData,
96}
97
98pub struct PHYParser;
100
101impl PHYParser {
102 fn parse_lines(lines: &[String]) -> Vec<String> {
104 lines.to_vec()
105 }
106}
107
108impl FileParser for PHYParser {
109 fn parse_additional_infos(lines: &[String]) -> Result<AdditionalInfos, ParsingError> {
110 if lines.is_empty() {
111 return Ok(AdditionalInfos::None);
112 }
113
114 let first_line = &lines[0];
115
116 let direction = if first_line.contains(" DL ") {
118 Direction::DL
119 } else if first_line.contains(" UL ") {
120 Direction::UL
121 } else {
122 Direction::DL };
124
125 match parse_phy_lines(lines, direction) {
127 Some(phy_infos) => {
128 if matches!(
130 phy_infos.channel_type,
131 PHYChannelType::PDCCH
132 | PHYChannelType::PDSCH
133 | PHYChannelType::PUSCH
134 | PHYChannelType::PUCCH
135 | PHYChannelType::PRACH
136 ) {
137 Ok(AdditionalInfos::PHYInfos(phy_infos))
138 } else {
139 Ok(AdditionalInfos::None)
140 }
141 }
142 None => Ok(AdditionalInfos::None),
143 }
144 }
145
146 fn parse(lines: &[String]) -> Result<Trace, ParsingError> {
147 let additional_infos = match Self::parse_additional_infos(lines) {
148 Ok(m) => m,
149 Err(e) => {
150 return Err(e);
151 }
152 };
153
154 let text = Self::parse_lines(lines);
155
156 let binary = extract_binary_from_lines(lines);
157
158 let timestamp = if let Some(first_line) = lines.first() {
160 super::parse_timestamp(first_line)?
161 } else {
162 0
163 };
164
165 let trace = Trace {
166 timestamp,
167 layer: Layer::PHY,
168 additional_infos,
169 text: Some(text),
170 binary,
171 relation: TraceRelation::default(),
172 };
173 Ok(trace)
174 }
175}
176
177pub fn parse_phy_lines(lines: &[String], direction: Direction) -> Option<PHYInfos> {
193 if lines.is_empty() {
194 return None;
195 }
196 let first_line = &lines[0];
197
198 let channel_type = if first_line.contains("PDSCH:") {
200 PHYChannelType::PDSCH
201 } else if first_line.contains("PUSCH:") {
202 PHYChannelType::PUSCH
203 } else if first_line.contains("PUCCH:") {
204 PHYChannelType::PUCCH
205 } else if first_line.contains("PDCCH:") {
206 PHYChannelType::PDCCH
207 } else if first_line.contains("PRACH:") {
208 PHYChannelType::PRACH
209 } else {
210 PHYChannelType::Other
211 };
212
213 let (frame, slot) = parse_frame_slot(first_line)?;
215
216 let (prb_start, prb_length) = if matches!(channel_type, PHYChannelType::PDCCH) {
218 (0, 0)
219 } else {
220 parse_prb(first_line)?
221 };
222
223 let (symb_start, symb_length) = parse_symb(first_line).unwrap_or((0, 14));
225
226 let (harq, harq_si) = parse_harq(first_line);
228
229 let channel_data = match channel_type {
231 PHYChannelType::PDCCH => parse_pdcch_data(lines),
232 PHYChannelType::PDSCH => parse_pdsch_data(first_line),
233 PHYChannelType::PUSCH => parse_pusch_data(first_line),
234 PHYChannelType::PUCCH => parse_pucch_data(first_line),
235 _ => PHYChannelData::None,
236 };
237
238 Some(PHYInfos {
239 direction,
240 channel_type,
241 frame,
242 slot,
243 prb_start,
244 prb_length,
245 symb_start,
246 symb_length,
247 harq,
248 harq_si,
249 channel_data,
250 })
251}
252
253pub fn parse_phy_line(line: &str, direction: Direction) -> Option<PHYInfos> {
255 parse_phy_lines(&[line.to_string()], direction)
256}
257
258fn parse_frame_slot(line: &str) -> Option<(u16, u8)> {
264 let parts: Vec<&str> = line.split_whitespace().collect();
269
270 for part in &parts {
271 if let Some(dot_pos) = part.find('.') {
273 let frame_part = &part[..dot_pos];
274 let slot_part = &part[dot_pos + 1..];
275
276 let slot_part: String = slot_part.chars().take_while(|c| c.is_ascii_digit()).collect();
278
279 if let (Ok(frame), Ok(slot)) = (frame_part.parse::<u16>(), slot_part.parse::<u8>()) {
280 if slot < 20 {
282 return Some((frame, slot));
283 }
284 }
285 }
286 }
287
288 None
289}
290
291fn parse_harq(line: &str) -> (Option<u8>, bool) {
296 let harq_start_idx = match line.find("harq=") {
297 Some(idx) => idx,
298 None => return (None, false),
299 };
300 let harq_value_start = harq_start_idx + 5; let value_part: String = line[harq_value_start..]
303 .chars()
304 .take_while(|c| c.is_ascii_alphanumeric())
305 .collect();
306
307 if value_part == "si" {
308 return (None, true);
309 }
310
311 (value_part.parse::<u8>().ok(), false)
312}
313
314fn extract_field_u8(line: &str, prefix: &str) -> Option<u8> {
316 let start = line.find(prefix)? + prefix.len();
317 let value: String = line[start..].chars().take_while(|c| c.is_ascii_digit()).collect();
318 value.parse::<u8>().ok()
319}
320
321fn extract_field_str(line: &str, prefix: &str) -> Option<String> {
323 let start = line.find(prefix)? + prefix.len();
324 let value: String = line[start..].chars().take_while(|c| !c.is_whitespace()).collect();
325 if value.is_empty() { None } else { Some(value) }
326}
327
328fn parse_pdcch_data(lines: &[String]) -> PHYChannelData {
333 let first_line = &lines[0];
334 let dci = extract_field_str(first_line, "dci=").unwrap_or_default();
335 let is_dci_1_1 = dci == "1_1";
336
337 let mut harq_process = None;
338 let mut ndi = None;
339 let mut rv_idx = None;
340 let mut harq_feedback_timing = None;
341
342 for line in lines.iter().skip(1) {
343 let trimmed = line.trim();
344 if let Some(trimmed_prefix) = trimmed.strip_prefix("harq_process=") {
345 harq_process = trimmed_prefix.split_whitespace().next().and_then(|v| v.parse::<u8>().ok());
346 } else if is_dci_1_1 {
347 if let Some(trimmed_prefix) = trimmed.strip_prefix("ndi1=") {
349 ndi = trimmed_prefix.split_whitespace().next().and_then(|v| v.parse::<u8>().ok());
350 } else if let Some(trimmed_prefix) = trimmed.strip_prefix("rv_idx1=") {
351 rv_idx = trimmed_prefix.split_whitespace().next().and_then(|v| v.parse::<u8>().ok());
352 } else if let Some(trimmed_prefix) = trimmed.strip_prefix("harq_feedback_timing=") {
353 harq_feedback_timing = trimmed_prefix.split_whitespace().next().and_then(|v| v.parse::<u8>().ok());
354 }
355 } else {
356 if let Some(trimmed_prefix) = trimmed.strip_prefix("ndi=") {
358 ndi = trimmed_prefix.split_whitespace().next().and_then(|v| v.parse::<u8>().ok());
359 } else if let Some(trimmed_prefix) = trimmed.strip_prefix("rv_idx=") {
360 rv_idx = trimmed_prefix.split_whitespace().next().and_then(|v| v.parse::<u8>().ok());
361 }
362 }
363 }
364
365 PHYChannelData::Pdcch {
366 dci,
367 harq_process,
368 ndi,
369 rv_idx,
370 harq_feedback_timing,
371 }
372}
373
374fn parse_pdsch_data(line: &str) -> PHYChannelData {
376 let retx = extract_field_u8(line, "retx=");
377 let rv_idx = extract_field_u8(line, "rv_idx=");
378 PHYChannelData::Pdsch { retx, rv_idx }
379}
380
381fn parse_pusch_data(line: &str) -> PHYChannelData {
383 let retx = extract_field_u8(line, "retx=");
384 let rv_idx = extract_field_u8(line, "rv_idx=");
385 let crc = extract_field_str(line, "crc=").map(|s| s != "KO");
386 PHYChannelData::Pusch { retx, rv_idx, crc }
387}
388
389fn parse_pucch_data(line: &str) -> PHYChannelData {
394 let format = extract_field_u8(line, "format=");
395 let ack = extract_field_str(line, "ack=").map(|s| s != "0");
397 PHYChannelData::Pucch { format, ack }
398}
399
400fn parse_prb(line: &str) -> Option<(u16, u16)> {
407 let prb_start_idx = line.find("prb=")?;
409 let prb_value_start = prb_start_idx + 4; let value_part: String = line[prb_value_start..]
413 .chars()
414 .take_while(|c| c.is_ascii_digit() || *c == ':' || *c == ' ')
415 .collect();
416
417 let value_trimmed = value_part.trim();
418
419 if let Some(colon_pos) = value_trimmed.find(':') {
420 let start = value_trimmed[..colon_pos].parse::<u16>().ok()?;
422 let length = value_trimmed[colon_pos + 1..].parse::<u16>().ok()?;
423 Some((start, length))
424 } else {
425 let start = value_trimmed.parse::<u16>().ok()?;
427 Some((start, 1))
428 }
429}
430
431fn parse_symb(line: &str) -> Option<(u8, u8)> {
440 let symb_start_idx = line.find("symb=")?;
442 let symb_value_start = symb_start_idx + 5; let value_part: String = line[symb_value_start..]
446 .chars()
447 .take_while(|c| c.is_ascii_digit() || *c == ':' || *c == ' ')
448 .collect();
449
450 let value_trimmed = value_part.trim();
451
452 if let Some(colon_pos) = value_trimmed.find(':') {
453 let start = value_trimmed[..colon_pos].parse::<u8>().ok()?;
455 let length = value_trimmed[colon_pos + 1..].parse::<u8>().ok()?;
456 Some((start, length))
457 } else {
458 let start = value_trimmed.parse::<u8>().ok()?;
460 Some((start, 1))
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn test_parse_5g_pdsch() {
470 let line = "10:32:34.715 [PHY] DL 0001 01 4601 431.16 PDSCH: harq=0 prb=50 symb=1:13 k1=12 CW0: tb_len=133 mod=8 rv_idx=0 cr=0.94 retx=0";
471 let info = parse_phy_line(line, Direction::DL).unwrap();
472
473 assert_eq!(info.frame, 431);
474 assert_eq!(info.slot, 16);
475 assert_eq!(info.prb_start, 50);
476 assert_eq!(info.prb_length, 1);
477 assert_eq!(info.symb_start, 1);
478 assert_eq!(info.symb_length, 13);
479 assert!(matches!(info.channel_type, PHYChannelType::PDSCH));
480 assert_eq!(info.harq, Some(0));
481 assert!(!info.harq_si);
482 assert!(matches!(
483 info.channel_data,
484 PHYChannelData::Pdsch {
485 retx: Some(0),
486 rv_idx: Some(0)
487 }
488 ));
489 }
490
491 #[test]
492 fn test_parse_4g_pdsch_no_symb() {
493 let line = "10:37:50.654 [PHY] DL 0001 01 003d 421.0 PDSCH: harq=0 k1=4 prb=23:2 tx=port0 CW0: tb_len=185 mod=6 rv_idx=0 retx=0";
494 let info = parse_phy_line(line, Direction::DL).unwrap();
495
496 assert_eq!(info.frame, 421);
497 assert_eq!(info.slot, 0);
498 assert_eq!(info.prb_start, 23);
499 assert_eq!(info.prb_length, 2);
500 assert_eq!(info.symb_start, 0);
501 assert_eq!(info.symb_length, 14); assert!(matches!(info.channel_type, PHYChannelType::PDSCH));
503 assert_eq!(info.harq, Some(0));
504 }
505
506 #[test]
507 fn test_parse_pusch() {
508 let line = "10:37:38.884 [PHY] UL 0001 01 003d 267.5 PUSCH: harq=3 prb=2:4 symb=0:13 CW0: tb_len=11 mod=2 rv_idx=0 retx=0 crc=OK snr=26.2 epre=-35.6 ta=0.1";
509 let info = parse_phy_line(line, Direction::UL).unwrap();
510
511 assert_eq!(info.frame, 267);
512 assert_eq!(info.slot, 5);
513 assert_eq!(info.prb_start, 2);
514 assert_eq!(info.prb_length, 4);
515 assert_eq!(info.symb_start, 0);
516 assert_eq!(info.symb_length, 13);
517 assert!(matches!(info.channel_type, PHYChannelType::PUSCH));
518 assert_eq!(info.harq, Some(3));
519 assert!(matches!(
520 info.channel_data,
521 PHYChannelData::Pusch {
522 retx: Some(0),
523 rv_idx: Some(0),
524 crc: Some(true)
525 }
526 ));
527 }
528
529 #[test]
530 fn test_parse_pusch_crc_ko() {
531 let line = "10:32:34.569 [PHY] UL 0001 01 4601 416.19 PUSCH: harq=0 prb=2 symb=0:14 CW0: tb_len=145 mod=8 rv_idx=0 cr=0.94 retx=0 crc=KO snr=37.1 epre=-86.9 ta=-0.5";
532 let info = parse_phy_line(line, Direction::UL).unwrap();
533 assert!(matches!(info.channel_data, PHYChannelData::Pusch { crc: Some(false), .. }));
534 }
535
536 #[test]
537 fn test_parse_prb_single() {
538 let line = "10:37:50.654 [PHY] DL 0001 01 003d 421.0 PDSCH: prb=50 symb=1:13";
539 let info = parse_phy_line(line, Direction::DL).unwrap();
540 assert_eq!(info.prb_start, 50);
541 assert_eq!(info.prb_length, 1);
542 }
543
544 #[test]
545 fn test_parse_pdcch_dci_0_1() {
546 let lines: Vec<String> = vec![
547 "10:32:29.584 [PHY] DL 0001 01 4601 942.15 PDCCH: ss_id=2 cce_index=6 al=2 dci=0_1 k2=4".into(),
548 "\t\trb_alloc=0x30".into(),
549 "\t\ttime_domain_rsc=1".into(),
550 "\t\tmcs=27".into(),
551 "\t\tndi=1".into(),
552 "\t\trv_idx=0".into(),
553 "\t\tharq_process=0".into(),
554 "\t\tdai=3".into(),
555 "\t\ttpc_command=1".into(),
556 "\t\tantenna_ports=0".into(),
557 "\t\tsrs_request=0".into(),
558 "\t\tdmrs_seq_init=0".into(),
559 "\t\tul_sch_indicator=1".into(),
560 ];
561 let info = parse_phy_lines(&lines, Direction::DL).unwrap();
562 assert!(matches!(info.channel_type, PHYChannelType::PDCCH));
563 assert_eq!(info.frame, 942);
564 assert_eq!(info.slot, 15);
565 assert_eq!(info.prb_start, 0); match &info.channel_data {
567 PHYChannelData::Pdcch {
568 dci,
569 harq_process,
570 ndi,
571 rv_idx,
572 harq_feedback_timing,
573 } => {
574 assert_eq!(dci, "0_1");
575 assert_eq!(*harq_process, Some(0));
576 assert_eq!(*ndi, Some(1));
577 assert_eq!(*rv_idx, Some(0));
578 assert_eq!(*harq_feedback_timing, None);
579 }
580 _ => panic!("Expected Pdcch channel data"),
581 }
582 }
583
584 #[test]
585 fn test_parse_pdcch_dci_1_1() {
586 let lines: Vec<String> = vec![
587 "10:32:22.293 [PHY] DL 0001 01 4601 213.13 PDCCH: ss_id=2 cce_index=4 al=2 dci=1_1".into(),
588 "\t\trb_alloc=0x32".into(),
589 "\t\tmcs1=27".into(),
590 "\t\tndi1=0".into(),
591 "\t\trv_idx1=0".into(),
592 "\t\tharq_process=0".into(),
593 "\t\tharq_feedback_timing=2".into(),
594 ];
595 let info = parse_phy_lines(&lines, Direction::DL).unwrap();
596 match &info.channel_data {
597 PHYChannelData::Pdcch {
598 dci,
599 harq_process,
600 ndi,
601 rv_idx,
602 harq_feedback_timing,
603 } => {
604 assert_eq!(dci, "1_1");
605 assert_eq!(*harq_process, Some(0));
606 assert_eq!(*ndi, Some(0));
607 assert_eq!(*rv_idx, Some(0));
608 assert_eq!(*harq_feedback_timing, Some(2));
609 }
610 _ => panic!("Expected Pdcch channel data"),
611 }
612 }
613
614 #[test]
615 fn test_parse_pucch_ack() {
616 let line = "10:32:22.299 [PHY] UL 0001 01 4601 213.19 PUCCH: format=1 prb=50 prb2=0 symb=0:14 cs=1 occ=0 ack=1 snr=35.7 epre=-88.5";
617 let info = parse_phy_line(line, Direction::UL).unwrap();
618 assert!(matches!(info.channel_type, PHYChannelType::PUCCH));
619 match &info.channel_data {
620 PHYChannelData::Pucch { format, ack } => {
621 assert_eq!(*format, Some(1));
622 assert_eq!(*ack, Some(true));
623 }
624 _ => panic!("Expected Pucch channel data"),
625 }
626 }
627
628 #[test]
629 fn test_parse_pucch_sr() {
630 let line = "13:20:47.884 [PHY] UL 003d 01 4644 960.8 PUCCH: format=1 prb=50 prb2=0 symb=0:14 cs=9 occ=2 sr=1 snr=18.9 epre=-53.8";
631 let info = parse_phy_line(line, Direction::UL).unwrap();
632 match &info.channel_data {
633 PHYChannelData::Pucch { format, ack } => {
634 assert_eq!(*format, Some(1));
635 assert_eq!(*ack, None); }
637 _ => panic!("Expected Pucch channel data"),
638 }
639 }
640
641 #[test]
642 fn test_parse_harq_si() {
643 let line = "13:20:47.877 [PHY] DL - 01 ffff 960.0 PDSCH: harq=si prb=41:7 symb=2:12 CW0: tb_len=84 mod=2 rv_idx=0 cr=0.44";
644 let info = parse_phy_line(line, Direction::DL).unwrap();
645 assert_eq!(info.harq, None);
646 assert!(info.harq_si);
647 assert!(matches!(info.channel_type, PHYChannelType::PDSCH));
648 }
649
650 #[test]
651 fn test_parse_pucch_format2_no_ack() {
652 let line = "13:20:47.885 [PHY] UL 003d 01 4644 960.9 PUCCH: format=2 prb=1 prb2=49 symb=8:2 csi=0101 epre=-51.3";
653 let info = parse_phy_line(line, Direction::UL).unwrap();
654 match &info.channel_data {
655 PHYChannelData::Pucch { format, ack } => {
656 assert_eq!(*format, Some(2));
657 assert_eq!(*ack, None); }
659 _ => panic!("Expected Pucch channel data"),
660 }
661 }
662}