From 9326e1e87c765c91c639cfc943ebea64db845d1b Mon Sep 17 00:00:00 2001 From: Henrik Date: Tue, 17 Mar 2026 10:55:28 +0100 Subject: [PATCH 1/9] feat: add MediaTek NR trace log import (MNR) Parse MediaTek 5G modem trace messages to extract NR-CA, EN-DC, and NR-DC combos with MIMO, modulation, bandwidth, and SCS details. Supports both new and old trace formats, with per-component SCS variant selection (SCS15 for FDD, SCS30 for TDD bands). --- .../importer/ImportMtkNr.kt | 855 ++++++++++++++++++ .../uecapabilityparser/model/LogType.kt | 5 +- .../importer/ImportMtkNrTest.kt | 17 + src/test/resources/mtkNr/input/mtkNrTrace.txt | 196 ++++ .../resources/mtkNr/input/mtkNrTraceOld.txt | 188 ++++ .../resources/mtkNr/oracle/mtkNrTrace.json | 470 ++++++++++ .../resources/mtkNr/oracle/mtkNrTraceOld.json | 334 +++++++ 7 files changed, 2064 insertions(+), 1 deletion(-) create mode 100644 src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt create mode 100644 src/test/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNrTest.kt create mode 100644 src/test/resources/mtkNr/input/mtkNrTrace.txt create mode 100644 src/test/resources/mtkNr/input/mtkNrTraceOld.txt create mode 100644 src/test/resources/mtkNr/oracle/mtkNrTrace.json create mode 100644 src/test/resources/mtkNr/oracle/mtkNrTraceOld.json diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt new file mode 100644 index 00000000..0961aacc --- /dev/null +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -0,0 +1,855 @@ +package it.smartphonecombo.uecapabilityparser.importer + +import it.smartphonecombo.uecapabilityparser.extension.mutableListWithCapacity +import it.smartphonecombo.uecapabilityparser.io.InputSource +import it.smartphonecombo.uecapabilityparser.model.BwClass +import it.smartphonecombo.uecapabilityparser.model.Capabilities +import it.smartphonecombo.uecapabilityparser.model.EmptyMimo +import it.smartphonecombo.uecapabilityparser.model.LinkDirection +import it.smartphonecombo.uecapabilityparser.model.combo.ComboEnDc +import it.smartphonecombo.uecapabilityparser.model.combo.ComboNr +import it.smartphonecombo.uecapabilityparser.model.combo.ComboNrDc +import it.smartphonecombo.uecapabilityparser.model.combo.ICombo +import it.smartphonecombo.uecapabilityparser.model.component.ComponentLte +import it.smartphonecombo.uecapabilityparser.model.component.ComponentNr +import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCLte +import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCNr +import it.smartphonecombo.uecapabilityparser.model.feature.FeatureSet +import it.smartphonecombo.uecapabilityparser.model.feature.IFeaturePerCC +import it.smartphonecombo.uecapabilityparser.model.modulation.ModulationOrder +import it.smartphonecombo.uecapabilityparser.model.toMimo + +/** + * A parser for MediaTek 5G modem trace messages logs. + * + * Parses NR-CA and EN-DC combo information from NRRC capability trace messages, including NR DL/UL + * FSpCC definitions, EUTRA DL/UL FSpCC definitions, Feature Set to FSpCC mappings, Feature Set + * Combinations (FSC), and combo band combinations. + * + * The output is a [Capabilities] with NR-CA combos in [nrCombos][Capabilities.nrCombos], EN-DC + * combos in [enDcCombos][Capabilities.enDcCombos], and NR-DC combos in + * [nrDcCombos][Capabilities.nrDcCombos]. + */ +object ImportMtkNr : ImportCapabilities { + + // Optional [MAIN] prefix found in older MTK trace formats + private const val CAP = """\[CAP\]\s*""" + private const val MAIN_CAP = """(?:\[MAIN\])?\[CAP\]\s*""" + + // Regex for NR DL FSpCC definitions + private val reNrDlFspcc = + Regex( + MAIN_CAP + + """NR DL FSpCC\[(\d+)],\s*""" + + """scs\[(\w+)],\s*bw\[(\w+)],\s*bw90m\[(\w+)],\s*""" + + """mimo\[(\w+)],\s*modulation\[(\w+)]""" + ) + + // Regex for NR UL FSpCC definitions + private val reNrUlFspcc = + Regex( + MAIN_CAP + + """NR UL FSpCC\[(\d+)],\s*""" + + """scs\[(\w+)],\s*bw\[(\w+)],\s*bw90m\[(\w+)],\s*""" + + """cb_mimo\[(\w+)],.*?modulation\[(\w+)]""" + ) + + // Regex for EUTRA DL FSpCC definitions + private val reEutraDlFspcc = Regex(MAIN_CAP + """EUTRA DL FSpCC\[(\d+)],.*?mimo\[(\w+)]""") + + // Regex for EUTRA UL FSpCC definitions (may include 256qam field) + private val reEutraUlFspcc = + Regex(MAIN_CAP + """EUTRA UL FSpCC\[(\d+)],\s*mimo\[(\w+)](?:,\s*256qam\[(\w+)])?""") + + // Regex for NR DL Feature Set -> FSpCC ID mappings + // Old format has 2 brackets, new format has 8 brackets + private val reNrDlFs = + Regex( + MAIN_CAP + + """NR DL FS\[(\d+)],\s*FSDLpCC ID""" + + """\[([^\]]*)]""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + ) + + // Regex for NR UL Feature Set -> FSpCC ID mappings + private val reNrUlFs = + Regex( + MAIN_CAP + + """NR UL FS\[(\d+)],\s*FSULpCC ID""" + + """\[([^\]]*)]""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + ) + + // Regex for EUTRA DL Feature Set -> FSpCC ID mappings + private val reEutraDlFs = + Regex( + MAIN_CAP + + """EUTRA DL FS\[(\d+)],\s*FSDLpCC ID""" + + """\[([^\]]*)]""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + ) + + // Regex for EUTRA UL Feature Set -> FSpCC ID mappings + private val reEutraUlFs = + Regex( + MAIN_CAP + + """EUTRA UL FS\[(\d+)],\s*FSULpCC ID""" + + """\[([^\]]*)]""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + + """(?:\[([^\]]*)])?""" + ) + + // Regex for FSC definitions + // Old format: [MAIN][CAP] FSC[1], band num[1], D/U[...][...] + // New format: [CAP] FSC[1], D/U[...][...] + private val reFsc = + Regex(MAIN_CAP + """FSC\[(\d+)],\s*(?:band num\[\d+],\s*)?D/U((?:\[[^\]]+/[^\]]+])+)""") + private val reDuPair = Regex("""\[([^/]+)/([^\]]+)]""") + + // Regex for combo lines - supports both old and new trace formats: + // New: [CAP]CA idx [0] NL1 bc, num[1] DL: ... UL: ... FSC[1], BC info[...] + // Old: [MAIN][CAP] CA idx [1], NL1 CA band comb, band num[1], DL: ..., UL: ..., FSC[2] + private val reComboNew = + Regex( + CAP + + """CA idx \[(\d+)] NL1 bc, num\[(\d+)]\s+""" + + """DL:\s+([\w_]+)\s+UL:\s+([\w_]+)\s+FSC\[(\d+)]""" + ) + private val reComboOld = + Regex( + MAIN_CAP + + """CA idx \[(\d+)],\s*NL1 CA band comb,\s*band num\[(\d+)],\s*""" + + """DL:\s+([\w_]+),\s*UL:\s+([\w_]+),\s*FSC\[(\d+)]""" + ) + + // Regex for individual band component in DL/UL strings + private val reBandComponent = Regex("""(B|N)(\d+)([A-Z])""") + + // Regex for FS reference strings like 'N7', 'E1', '_0' + private val reFsRef = Regex("""([NE])(\d+)""") + + override fun parse(input: InputSource): Capabilities { + val capabilities = Capabilities() + val lines = input.readLines() + + val parsed = parseTraceLog(lines) + val combos = buildCombos(parsed) + + capabilities.enDcCombos = combos.filterIsInstance() + capabilities.nrCombos = combos.filterIsInstance() + capabilities.nrDcCombos = combos.filterIsInstance() + + return capabilities + } + + /** Data class holding a parsed NR DL/UL per-CC feature. */ + private data class NrFspcc( + val scs: Int, + val bw: Int, + val bw90: Boolean, + val mimo: Int, + val mod: ModulationOrder, + ) + + /** Data class holding a parsed EUTRA DL/UL per-CC feature. */ + private data class EutraFspcc(val mimo: Int) + + /** + * Data class holding a parsed FSC entry with SCS variants. + * + * Some devices emit multiple FSC lines with the same FSC number but different FS references, + * corresponding to different sub-carrier spacings (SCS15 for FDD bands, SCS30 for TDD bands). + * Each variant is a list of (dlFsRef, ulFsRef) per component. + */ + private data class FscEntry(val variants: List>>) + + /** Data class holding a parsed combo. */ + private data class MtkCombo(val components: List, val fscNum: Int) + + /** Data class holding a parsed component from combo DL/UL strings. */ + private data class MtkComponent( + val isNr: Boolean, + val band: Int, + val bwClassDl: Char, + val bwClassUl: Char?, // null if no UL + ) + + /** Container for all parsed trace data. */ + private data class ParsedTrace( + val nrDlFspcc: Map, + val nrUlFspcc: Map, + val eutraDlFspcc: Map, + val eutraUlFspcc: Map, + val nrDlFs: Map>, + val nrUlFs: Map>, + val eutraDlFs: Map>, + val eutraUlFs: Map>, + val fscDefs: Map, + val combos: List, + ) + + /** Parse all capability structures from the trace log lines. */ + private fun parseTraceLog(lines: List): ParsedTrace { + val nrDlFspcc = mutableMapOf() + val nrUlFspcc = mutableMapOf() + val eutraDlFspcc = mutableMapOf() + val eutraUlFspcc = mutableMapOf() + val nrDlFs = mutableMapOf>() + val nrUlFs = mutableMapOf>() + val eutraDlFs = mutableMapOf>() + val eutraUlFs = mutableMapOf>() + val fscDefs = mutableMapOf() + val combos = mutableListOf() + + for (line in lines) { + // Try each pattern - at most one will match per line + parseNrDlFspccLine(line, nrDlFspcc) + ?: parseNrUlFspccLine(line, nrUlFspcc) + ?: parseEutraDlFspccLine(line, eutraDlFspcc) + ?: parseEutraUlFspccLine(line, eutraUlFspcc) + ?: parseNrDlFsLine(line, nrDlFs) + ?: parseNrUlFsLine(line, nrUlFs) + ?: parseEutraDlFsLine(line, eutraDlFs) + ?: parseEutraUlFsLine(line, eutraUlFs) + ?: parseFscLine(line, fscDefs) + ?: parseComboLine(line, combos) + } + + return ParsedTrace( + nrDlFspcc, + nrUlFspcc, + eutraDlFspcc, + eutraUlFspcc, + nrDlFs, + nrUlFs, + eutraDlFs, + eutraUlFs, + fscDefs, + combos, + ) + } + + /** Parse a NR DL FSpCC definition line. */ + private fun parseNrDlFspccLine(line: String, map: MutableMap): Unit? { + val m = reNrDlFspcc.find(line) ?: return null + val idx = m.groupValues[1].toInt() + map[idx] = + NrFspcc( + scs = parseMtkScs(m.groupValues[2]), + bw = parseMtkBw(m.groupValues[3]), + bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT", + mimo = parseMtkDlMimo(m.groupValues[5]), + mod = parseMtkModulation(m.groupValues[6]), + ) + return Unit + } + + /** Parse a NR UL FSpCC definition line. */ + private fun parseNrUlFspccLine(line: String, map: MutableMap): Unit? { + val m = reNrUlFspcc.find(line) ?: return null + val idx = m.groupValues[1].toInt() + map[idx] = + NrFspcc( + scs = parseMtkScs(m.groupValues[2]), + bw = parseMtkBw(m.groupValues[3]), + bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT", + mimo = parseMtkUlMimo(m.groupValues[5]), + mod = parseMtkModulation(m.groupValues[6]), + ) + return Unit + } + + /** Parse a EUTRA DL FSpCC definition line. */ + private fun parseEutraDlFspccLine(line: String, map: MutableMap): Unit? { + val m = reEutraDlFspcc.find(line) ?: return null + val idx = m.groupValues[1].toInt() + val mimoStr = m.groupValues[2] + val mimo = + when { + "FOUR_LAYER" in mimoStr -> 4 + "TWO_LAYER" in mimoStr -> 2 + else -> 0 + } + map[idx] = EutraFspcc(mimo) + return Unit + } + + /** Parse a EUTRA UL FSpCC definition line. */ + private fun parseEutraUlFspccLine(line: String, map: MutableMap): Unit? { + val m = reEutraUlFspcc.find(line) ?: return null + val idx = m.groupValues[1].toInt() + val mimoStr = m.groupValues[2] + val mimo = + when { + "TWO_LAYER" in mimoStr -> 2 + "ONE_LAYER" in mimoStr -> 1 + else -> 0 + } + map[idx] = EutraFspcc(mimo) + return Unit + } + + /** Parse a NR DL FS -> FSpCC ID mapping line. */ + private fun parseNrDlFsLine(line: String, map: MutableMap>): Unit? { + val m = reNrDlFs.find(line) ?: return null + val fsNum = m.groupValues[1].toInt() + val ids = extractFspccIds(m, startGroup = 2, endGroup = 9) + map[fsNum] = ids + return Unit + } + + /** Parse a NR UL FS -> FSpCC ID mapping line. */ + private fun parseNrUlFsLine(line: String, map: MutableMap>): Unit? { + val m = reNrUlFs.find(line) ?: return null + val fsNum = m.groupValues[1].toInt() + val ids = extractFspccIds(m, startGroup = 2, endGroup = 5) + map[fsNum] = ids + return Unit + } + + /** Parse an EUTRA DL FS -> FSpCC ID mapping line. */ + private fun parseEutraDlFsLine(line: String, map: MutableMap>): Unit? { + val m = reEutraDlFs.find(line) ?: return null + val fsNum = m.groupValues[1].toInt() + val ids = extractFspccIds(m, startGroup = 2, endGroup = 6) + map[fsNum] = ids + return Unit + } + + /** Parse an EUTRA UL FS -> FSpCC ID mapping line. */ + private fun parseEutraUlFsLine(line: String, map: MutableMap>): Unit? { + val m = reEutraUlFs.find(line) ?: return null + val fsNum = m.groupValues[1].toInt() + val ids = extractFspccIds(m, startGroup = 2, endGroup = 6) + map[fsNum] = ids + return Unit + } + + /** Parse an FSC definition line. Collects all SCS variants for each FSC number. */ + private fun parseFscLine(line: String, map: MutableMap): Unit? { + val m = reFsc.find(line) ?: return null + val fscNum = m.groupValues[1].toInt() + + val pairsStr = m.groupValues[2] + val pairs = mutableListOf>() + for (pm in reDuPair.findAll(pairsStr)) { + val dl = pm.groupValues[1].trim() + val ul = pm.groupValues[2].trim() + if (dl == "_0" && ul == "_0") continue // Skip empty slots + pairs.add(Pair(dl, ul)) + } + + val existing = map[fscNum] + if (existing != null) { + // Add as another SCS variant + map[fscNum] = FscEntry(existing.variants + listOf(pairs)) + } else { + map[fscNum] = FscEntry(listOf(pairs)) + } + return Unit + } + + /** Parse a combo definition line. Tries new format first, then old format. */ + private fun parseComboLine(line: String, list: MutableList): Unit? { + val m = reComboNew.find(line) ?: reComboOld.find(line) ?: return null + val dlStr = m.groupValues[3] + val ulStr = m.groupValues[4] + val fscNum = m.groupValues[5].toInt() + + val components = parseComboComponents(dlStr, ulStr) + if (components.isNotEmpty()) { + list.add(MtkCombo(components, fscNum)) + } + return Unit + } + + /** + * Parse DL and UL component strings into a merged list of components. + * + * DL and UL strings are underscore-separated with positional matching. For example: DL: + * B1A_B3A_N78A__0_... UL: B1A__0__N78A__0_... + */ + private fun parseComboComponents(dlStr: String, ulStr: String): List { + val dlParts = dlStr.split("_") + val ulParts = ulStr.split("_") + + // Parse DL components with their positions + data class DlEntry(val isNr: Boolean, val band: Int, val bwClass: Char, val pos: Int) + + val dlEntries = mutableListOf() + for ((i, part) in dlParts.withIndex()) { + if (part.isEmpty() || part == "0") continue + val bm = reBandComponent.matchEntire(part) ?: continue + dlEntries.add( + DlEntry( + isNr = bm.groupValues[1] == "N", + band = bm.groupValues[2].toInt(), + bwClass = bm.groupValues[3][0], + pos = i, + ) + ) + } + + // Parse UL components with their positions + data class UlEntry(val isNr: Boolean, val band: Int, val bwClass: Char, val pos: Int) + + val ulEntries = mutableListOf() + for ((i, part) in ulParts.withIndex()) { + if (part.isEmpty() || part == "0") continue + val bm = reBandComponent.matchEntire(part) ?: continue + ulEntries.add( + UlEntry( + isNr = bm.groupValues[1] == "N", + band = bm.groupValues[2].toInt(), + bwClass = bm.groupValues[3][0], + pos = i, + ) + ) + } + + // Merge: for each DL component, find matching UL by position first, then by band + val result = mutableListOf() + val usedUl = mutableSetOf() // indices into ulEntries that have been matched + + for (dl in dlEntries) { + // Try positional match first + var ulBwClass: Char? = null + val posMatch = + ulEntries.indexOfFirst { + it.pos == dl.pos && it !in usedUl.map { idx -> ulEntries[idx] } + } + if ( + posMatch >= 0 && + ulEntries[posMatch].band == dl.band && + ulEntries[posMatch].isNr == dl.isNr + ) { + ulBwClass = ulEntries[posMatch].bwClass + usedUl.add(posMatch) + } else { + // Try matching by band (first unmatched UL with same band and type) + val bandMatch = + ulEntries.withIndex().firstOrNull { (idx, ul) -> + idx !in usedUl && ul.band == dl.band && ul.isNr == dl.isNr + } + if (bandMatch != null) { + ulBwClass = bandMatch.value.bwClass + usedUl.add(bandMatch.index) + } + } + + result.add(MtkComponent(dl.isNr, dl.band, dl.bwClass, ulBwClass)) + } + + return result + } + + /** Extract FSpCC IDs from a regex match, filtering out zeros. */ + private fun extractFspccIds(match: MatchResult, startGroup: Int, endGroup: Int): List { + val ids = mutableListOf() + for (i in startGroup..endGroup) { + val g = match.groupValues.getOrNull(i) + if (!g.isNullOrEmpty()) { + val value = g.toIntOrNull() ?: continue + if (value > 0) ids.add(value) + } + } + return ids + } + + /** Convert MTK SCS enum string to SCS value in kHz. */ + private fun parseMtkScs(scsStr: String): Int { + return when (scsStr) { + "NL1_CAP_SCS_15KHZ" -> 15 + "NL1_CAP_SCS_30KHZ" -> 30 + "NL1_CAP_SCS_60KHZ" -> 60 + "NL1_CAP_SCS_120KHZ" -> 120 + else -> 15 + } + } + + /** Extract bandwidth integer from strings like 'NL1_CAP_BW50'. */ + private fun parseMtkBw(bwStr: String): Int { + val m = Regex("""NL1_CAP_BW(\d+)""").find(bwStr) + return m?.groupValues?.get(1)?.toInt() ?: 0 + } + + /** Convert MTK DL MIMO enum string to layer count. */ + private fun parseMtkDlMimo(mimoStr: String): Int { + return when (mimoStr) { + "NL1_CAP_DL_TWO_LAYER" -> 2 + "NL1_CAP_DL_FOUR_LAYER" -> 4 + "NL1_CAP_DL_EIGHT_LAYER" -> 8 + else -> 0 + } + } + + /** Convert MTK UL MIMO enum string to layer count. */ + private fun parseMtkUlMimo(mimoStr: String): Int { + return when (mimoStr) { + "NL1_CAP_UL_ONE_LAYER" -> 1 + "NL1_CAP_UL_TWO_LAYER" -> 2 + "NL1_CAP_UL_FOUR_LAYER" -> 4 + else -> 0 + } + } + + /** Convert MTK modulation enum string to ModulationOrder. */ + private fun parseMtkModulation(modStr: String): ModulationOrder { + return when (modStr) { + "NL1_CAP_64QAM" -> ModulationOrder.QAM64 + "NL1_CAP_256QAM" -> ModulationOrder.QAM256 + "NL1_CAP_1024QAM" -> ModulationOrder.QAM1024 + else -> ModulationOrder.NONE + } + } + + /** Parse an FS reference string like 'N7', 'E1', '_0' into type and number. */ + private fun parseFsRef(fsStr: String): Pair { + if (fsStr == "_0" || fsStr == "0" || fsStr == "N0") return Pair(null, 0) + val m = reFsRef.matchEntire(fsStr) ?: return Pair(null, 0) + return Pair(m.groupValues[1][0], m.groupValues[2].toInt()) + } + + /** NR FDD band numbers — use SCS 15 kHz features for these. */ + private val nrFddBands = + setOf( + 1, + 2, + 3, + 5, + 7, + 8, + 12, + 13, + 14, + 18, + 20, + 24, + 25, + 26, + 28, + 30, + 31, + 65, + 66, + 68, + 70, + 71, + 72, + 74, + 75, + 85, + 87, + 88, + 91, + 92, + 93, + 94, + 100, + 105, + 106, + 109, + 110, + ) + + /** NR TDD band numbers — use SCS 30 kHz features for these. */ + private val nrTddBands = + setOf(34, 38, 39, 40, 41, 46, 47, 48, 50, 53, 77, 78, 79, 90, 96, 101, 102, 104) + + /** Return the preferred SCS (15 or 30) for the given NR band number. */ + private fun preferredScsForBand(band: Int): Int { + return if (band in nrTddBands) 30 else 15 + } + + /** + * Resolve the SCS of a specific NR DL FS reference. + * + * Returns 0 if the SCS cannot be determined. + */ + private fun resolveNrFsScs(dlFsStr: String, parsed: ParsedTrace): Int { + val (fsType, fsNum) = parseFsRef(dlFsStr) + if (fsType != 'N' || fsNum <= 0) return 0 + val fspccIds = parsed.nrDlFs[fsNum] ?: return 0 + val firstFspccId = fspccIds.firstOrNull() ?: return 0 + val fspcc = parsed.nrDlFspcc[firstFspccId] ?: return 0 + return fspcc.scs + } + + /** + * Build the best FSC pair list for the given combo components by selecting, for each component + * position, the FS reference pair whose SCS matches that band's duplex mode (FDD → SCS 15 kHz, + * TDD → SCS 30 kHz). + * + * FSC variants are a cartesian product of per-component SCS options. For example FSC[12] with + * N3(FDD)+N78(TDD) may have 4 variants: (SCS15,SCS15), (SCS15,SCS30), (SCS30,SCS15), + * (SCS30,SCS30). The correct pick is (SCS15, SCS30) → N3 at SCS15, N78 at SCS30. + * + * Falls back to the first variant if only one variant exists. + */ + private fun selectFscPairs( + fscEntry: FscEntry, + components: List, + parsed: ParsedTrace, + ): List> { + if (fscEntry.variants.size <= 1) return fscEntry.variants.first() + + val pairCount = fscEntry.variants.first().size + val result = mutableListWithCapacity>(pairCount) + + for (i in 0 until pairCount) { + // Collect all unique (dl, ul) candidates at position i across all variants + val candidates = fscEntry.variants.map { it[i] }.distinct() + + if (candidates.size <= 1 || i >= components.size) { + result.add(candidates.first()) + continue + } + + val comp = components[i] + if (!comp.isNr) { + // EUTRA components don't vary by SCS + result.add(candidates.first()) + continue + } + + // Pick the candidate whose DL FS resolves to the preferred SCS for this band + val preferredScs = preferredScsForBand(comp.band) + val best = + candidates.firstOrNull { resolveNrFsScs(it.first, parsed) == preferredScs } + ?: candidates.first() + result.add(best) + } + + return result + } + + /** Build Capabilities combos from parsed trace data. */ + private fun buildCombos(parsed: ParsedTrace): List { + val combos = mutableListWithCapacity(parsed.combos.size) + + // Build NR per-CC feature lists (indexed directly by FSpCC ID) + val nrFeaturesPerCCDl = buildNrFeaturesPerCC(parsed.nrDlFspcc, LinkDirection.DOWNLINK) + val nrFeaturesPerCCUl = buildNrFeaturesPerCC(parsed.nrUlFspcc, LinkDirection.UPLINK) + + // Build EUTRA per-CC feature lists (indexed directly by FSpCC ID) + val eutraFeaturesPerCCDl = + buildEutraFeaturesPerCC(parsed.eutraDlFspcc, LinkDirection.DOWNLINK) + val eutraFeaturesPerCCUl = + buildEutraFeaturesPerCC(parsed.eutraUlFspcc, LinkDirection.UPLINK) + + // Build EUTRA Feature Sets: FS number -> FeatureSet (list of per-CC features) + val eutraDlFeatureSets = + buildEutraFeatureSets(parsed.eutraDlFs, eutraFeaturesPerCCDl, LinkDirection.DOWNLINK) + val eutraUlFeatureSets = + buildEutraFeatureSets(parsed.eutraUlFs, eutraFeaturesPerCCUl, LinkDirection.UPLINK) + + for (combo in parsed.combos) { + val fscEntry = parsed.fscDefs[combo.fscNum] ?: continue + val components = combo.components + + // Select the correct SCS-matched FS pairs for this combo's bands + val fscPairs = selectFscPairs(fscEntry, components, parsed) + + // FSC pairs might not exactly match component count; align by min + val count = minOf(fscPairs.size, components.size) + if (count == 0) continue + + val nrComponents = mutableListOf() + val lteComponents = mutableListOf() + + for (i in 0 until count) { + val comp = components[i] + val (dlFsStr, ulFsStr) = fscPairs[i] + val (dlFsType, dlFsNum) = parseFsRef(dlFsStr) + val (ulFsType, ulFsNum) = parseFsRef(ulFsStr) + + if (comp.isNr) { + val nrComp = + buildNrComponent( + comp, + dlFsType, + dlFsNum, + ulFsType, + ulFsNum, + parsed, + nrFeaturesPerCCDl, + nrFeaturesPerCCUl, + ) + nrComponents.add(nrComp) + } else { + val lteComp = + buildLteComponent( + comp, + dlFsType, + dlFsNum, + ulFsType, + ulFsNum, + eutraDlFeatureSets, + eutraUlFeatureSets, + ) + lteComponents.add(lteComp) + } + } + + val icombo = assembleCombo(lteComponents, nrComponents) + if (icombo != null) combos.add(icombo) + } + + return combos + } + + /** Build a map of FSpCC ID -> FeaturePerCCNr for NR features. */ + private fun buildNrFeaturesPerCC( + fspccMap: Map, + direction: LinkDirection, + ): Map { + return fspccMap.mapValues { (_, fspcc) -> + val bw = if (fspcc.bw90 && fspcc.bw == 80) 90 else fspcc.bw + FeaturePerCCNr( + type = direction, + mimo = fspcc.mimo.toMimo(), + qam = fspcc.mod, + bw = bw, + scs = fspcc.scs, + channelBW90mhz = fspcc.bw90, + ) + } + } + + /** Build a map of FSpCC ID -> FeaturePerCCLte for EUTRA features. */ + private fun buildEutraFeaturesPerCC( + fspccMap: Map, + direction: LinkDirection, + ): Map { + return fspccMap.mapValues { (_, fspcc) -> + FeaturePerCCLte(type = direction, mimo = fspcc.mimo.toMimo()) + } + } + + /** + * Build EUTRA Feature Sets: map from FS number to FeatureSet, resolving FSpCC IDs to actual + * per-CC features. + */ + private fun buildEutraFeatureSets( + fsMap: Map>, + fspccFeatures: Map, + direction: LinkDirection, + ): Map { + return fsMap.mapValues { (_, fspccIds) -> + val perCCList = fspccIds.mapNotNull { fspccFeatures[it] } + FeatureSet(perCCList, direction) + } + } + + /** Build an NR ComponentNr from parsed component data, resolving features via FSC. */ + private fun buildNrComponent( + comp: MtkComponent, + dlFsType: Char?, + dlFsNum: Int, + ulFsType: Char?, + ulFsNum: Int, + parsed: ParsedTrace, + nrFeaturesPerCCDl: Map, + nrFeaturesPerCCUl: Map, + ): ComponentNr { + val classDl = BwClass.valueOf(comp.bwClassDl.toString()) + val classUl = + if (comp.bwClassUl != null) BwClass.valueOf(comp.bwClassUl.toString()) else BwClass.NONE + val baseComponent = ComponentNr(comp.band, classDl, classUl) + + // Resolve DL per-CC features: FS -> FSpCC IDs -> FeaturePerCCNr + val dlFeature: List = + if (dlFsType == 'N' && dlFsNum > 0) { + val fspccIds = parsed.nrDlFs[dlFsNum] ?: emptyList() + fspccIds.mapNotNull { nrFeaturesPerCCDl[it] } + } else { + emptyList() + } + + // Resolve UL per-CC features + val ulFeature: List = + if (ulFsType == 'N' && ulFsNum > 0) { + val fspccIds = parsed.nrUlFs[ulFsNum] ?: emptyList() + fspccIds.mapNotNull { nrFeaturesPerCCUl[it] } + } else { + emptyList() + } + + return mergeComponentAndFeaturePerCC(baseComponent, dlFeature, ulFeature, null) + as ComponentNr + } + + /** Build an LTE ComponentLte from parsed component data, resolving features via FSC. */ + private fun buildLteComponent( + comp: MtkComponent, + dlFsType: Char?, + dlFsNum: Int, + ulFsType: Char?, + ulFsNum: Int, + eutraDlFeatureSets: Map, + eutraUlFeatureSets: Map, + ): ComponentLte { + val classDl = BwClass.valueOf(comp.bwClassDl.toString()) + val classUl = + if (comp.bwClassUl != null) BwClass.valueOf(comp.bwClassUl.toString()) else BwClass.NONE + val mimoUl = if (classUl != BwClass.NONE) 1.toMimo() else EmptyMimo + val baseComponent = ComponentLte(comp.band, classDl, classUl, mimoUL = mimoUl) + + // Resolve DL features from EUTRA FS + val dlFeature: List? = + if (dlFsType == 'E' && dlFsNum > 0) { + eutraDlFeatureSets[dlFsNum]?.featureSetsPerCC + } else { + null + } + + // Resolve UL features from EUTRA FS + val ulFeature: List? = + if (ulFsType == 'E' && ulFsNum > 0) { + eutraUlFeatureSets[ulFsNum]?.featureSetsPerCC + } else { + null + } + + return mergeComponentAndFeaturePerCC(baseComponent, dlFeature, ulFeature, null) + as ComponentLte + } + + /** Assemble the final combo (NR-CA, NR-DC, or EN-DC) from component lists. */ + private fun assembleCombo( + lteComponents: List, + nrComponents: List, + ): ICombo? { + if (nrComponents.isEmpty() && lteComponents.isEmpty()) return null + + val sortedNr = nrComponents.sortedDescending() + val sortedLte = lteComponents.sortedDescending() + + return if (sortedLte.isEmpty()) { + // NR-CA or NR-DC + if (sortedNr.none { it.isFR2 } || sortedNr.none { !it.isFR2 }) { + ComboNr(sortedNr) + } else { + // Both FR1 and FR2 present -> NR-DC + val (fr2, fr1) = sortedNr.partition { it.isFR2 } + ComboNrDc(fr1, fr2) + } + } else { + // EN-DC + ComboEnDc(sortedLte, sortedNr) + } + } +} diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/model/LogType.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/model/LogType.kt index e2716abc..2804765e 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/model/LogType.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/model/LogType.kt @@ -6,6 +6,7 @@ import it.smartphonecombo.uecapabilityparser.importer.Import0xB826 import it.smartphonecombo.uecapabilityparser.importer.ImportCapabilityInformation import it.smartphonecombo.uecapabilityparser.importer.ImportLteCarrierPolicy import it.smartphonecombo.uecapabilityparser.importer.ImportMTKLte +import it.smartphonecombo.uecapabilityparser.importer.ImportMtkNr import it.smartphonecombo.uecapabilityparser.importer.ImportNrCapPrune import it.smartphonecombo.uecapabilityparser.importer.ImportNvItem import it.smartphonecombo.uecapabilityparser.importer.ImportQctModemCap @@ -28,6 +29,7 @@ enum class LogType(val description: String) { QLTE("0xB0CD hexdump"), QNR("0xB826 hexdump"), M("MEDIATEK CA_COMB_INFO"), + MNR("MEDIATEK NR Trace Log"), O("OSIX UE Capability Information"), QC("QCAT UE Capability Information"), T("TEMS UE Capability Information"), @@ -64,7 +66,7 @@ enum class LogType(val description: String) { val multiImporter = validEntries.intersect(listOf(P, DLF, QMDL, HDF, SDM, NSG)) /** One input -> multi or single capability * */ val singleInput = - validEntries.intersect(listOf(E, SHNR, SHLTE, P, DLF, QMDL, HDF, SDM, NSG)) + validEntries.intersect(listOf(E, SHNR, SHLTE, MNR, P, DLF, QMDL, HDF, SDM, NSG)) /** Return [INVALID] if conversion fails * */ fun of(string: String?): LogType { @@ -83,6 +85,7 @@ enum class LogType(val description: String) { Q -> Import0xB0CD QLTE -> Import0xB0CDBin M -> ImportMTKLte + MNR -> ImportMtkNr QNR -> Import0xB826 RF -> ImportQctModemCap SHLTE -> ImportShannonLteUeCap diff --git a/src/test/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNrTest.kt b/src/test/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNrTest.kt new file mode 100644 index 00000000..15b07e6f --- /dev/null +++ b/src/test/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNrTest.kt @@ -0,0 +1,17 @@ +package it.smartphonecombo.uecapabilityparser.importer + +import org.junit.jupiter.api.Test + +internal class ImportMtkNrTest : + AbstractImportCapabilities(ImportMtkNr, "src/test/resources/mtkNr/") { + + @Test + fun parseMtkNrTrace() { + parse("mtkNrTrace.txt", "mtkNrTrace.json") + } + + @Test + fun parseMtkNrTraceOld() { + parse("mtkNrTraceOld.txt", "mtkNrTraceOld.json") + } +} diff --git a/src/test/resources/mtkNr/input/mtkNrTrace.txt b/src/test/resources/mtkNr/input/mtkNrTrace.txt new file mode 100644 index 00000000..8b6eb5e2 --- /dev/null +++ b/src/test/resources/mtkNr/input/mtkNrTrace.txt @@ -0,0 +1,196 @@ +804157, 2534757, 1362300529, 10:54:57:084 2026/03/15, 10:54:57:523731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [0] NL1 bc, num[1] DL: N1A__0___0___0___0___0___0___0___0___0___0___0_ UL: N1A__0___0___0___0___0_ FSC[1], BC info[NRRC_UE_CAP_BC_RPT_INFO_MRS_BAND_CHK_FAIL] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_1 +804159, 2534757, 1362300529, 10:54:57:084 2026/03/15, 10:54:57:523731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [1] NL1 bc, num[1] DL: N78A__0___0___0___0___0___0___0___0___0___0___0_ UL: N78A__0___0___0___0___0_ FSC[7], BC info[NRRC_UE_CAP_BC_RPT_INFO_MRS_BAND_CHK_FAIL] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_1 +804160, 2534757, 1362300529, 10:54:57:084 2026/03/15, 10:54:57:523731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [2] NL1 bc, num[1] DL: N78C__0___0___0___0___0___0___0___0___0___0___0_ UL: N78A__0___0___0___0___0_ FSC[13], BC info[NRRC_UE_CAP_BC_RPT_INFO_MRS_BAND_CHK_FAIL] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_1 +804161, 2534757, 1362300529, 10:54:57:084 2026/03/15, 10:54:57:523731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [3] NL1 bc, num[1] DL: N75A__0___0___0___0___0___0___0___0___0___0___0_ UL: _0___0___0___0___0___0_ FSC[9], BC info[NRRC_UE_CAP_BC_RPT_INFO_NOT_IN_FREQBANDLIST] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_1 +804162, 2534758, 1362300530, 10:54:57:084 2026/03/15, 10:54:57:523795 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [4] NL1 bc, num[2] DL: N1A_N3A__0___0___0___0___0___0___0___0___0___0_ UL: N1A__0___0___0___0___0_ FSC[14], BC info[NRRC_UE_CAP_BC_RPT_INFO_MRS_BAND_CHK_FAIL] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_2 +804163, 2534758, 1362300530, 10:54:57:084 2026/03/15, 10:54:57:523795 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [5] NL1 bc, num[2] DL: N1A_N78A__0___0___0___0___0___0___0___0___0___0_ UL: N1A__0___0___0___0___0_ FSC[27], BC info[NRRC_UE_CAP_BC_RPT_INFO_MRS_BAND_CHK_FAIL] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_2 +804164, 2534758, 1362300530, 10:54:57:084 2026/03/15, 10:54:57:523795 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [6] NL1 bc, num[2] DL: N1A_N78C__0___0___0___0___0___0___0___0___0___0_ UL: N1A_N78A__0___0___0___0_ FSC[35], BC info[NRRC_UE_CAP_BC_RPT_INFO_MRS_BAND_CHK_FAIL] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_2 +804206, 2534760, 1362300532, 10:54:57:084 2026/03/15, 10:54:57:523923 2026/03/15, MOD_NRRC, MOD_NRRC_BASELINE_TRACE_INFO_M, [NRRC] NR RAT supported:[KAL_TRUE], SIM[NRRC_SIM1] +Raw Buffer + +TRACE_NAME, NRRC_TRACE_NR_RAT_SUPPORT_INFO +804207, 2534760, 1362300532, 10:54:57:084 2026/03/15, 10:54:57:523923 2026/03/15, MOD_NRRC, MOD_NRRC_BASELINE_TRACE_INFO_M, [NRRC] 5G option[VG_OPTION1_OPTION3] +Raw Buffer + +TRACE_NAME, NRRC_TRACE_VG_OPTION +804426, 2534786, 1362300558, 10:54:57:084 2026/03/15, 10:54:57:525587 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [7] NL1 bc, num[2] DL: B1A_N78A__0___0___0___0___0___0___0___0___0___0_ UL: B1A_N78A__0___0___0___0_ FSC[135], BC info[NRRC_UE_CAP_BC_RPT_INFO_CANDIDATE] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_2 +804427, 2534787, 1362300559, 10:54:57:084 2026/03/15, 10:54:57:525651 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [8] NL1 bc, num[3] DL: B1A_B3A_N78A__0___0___0___0___0___0___0___0___0_ UL: B1A__0__N78A__0___0___0_ FSC[136], BC info[NRRC_UE_CAP_BC_RPT_INFO_CANDIDATE] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_3 +804428, 2534787, 1362300559, 10:54:57:084 2026/03/15, 10:54:57:525651 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP]CA idx [9] NL1 bc, num[3] DL: B1A_B3A_N78C__0___0___0___0___0___0___0___0___0_ UL: B1A__0__N78A__0___0___0_ FSC[137], BC info[NRRC_UE_CAP_BC_RPT_INFO_CANDIDATE] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO_3 +806373, 2535126, 1362300898, 10:54:57:084 2026/03/15, 10:54:57:547347 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[1], D/U[N1/N1][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_1 +806374, 2535126, 1362300898, 10:54:57:084 2026/03/15, 10:54:57:547347 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[2], D/U[N2/N2][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_1 +806384, 2535127, 1362300899, 10:54:57:084 2026/03/15, 10:54:57:547411 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[7], D/U[N7/N7][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_1 +806389, 2535128, 1362300900, 10:54:57:084 2026/03/15, 10:54:57:547475 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[9], D/U[N9/N0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_1 +806391, 2535128, 1362300900, 10:54:57:084 2026/03/15, 10:54:57:547475 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[13], D/U[N11/N12][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_1 +806392, 2535128, 1362300900, 10:54:57:084 2026/03/15, 10:54:57:547475 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[14], D/U[N1/N1][N2/N0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_2 +806393, 2535128, 1362300900, 10:54:57:084 2026/03/15, 10:54:57:547475 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[27], D/U[N1/N1][N7/N0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_2 +806394, 2535128, 1362300900, 10:54:57:084 2026/03/15, 10:54:57:547475 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[35], D/U[N1/N1][N11/N12][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_2 +807100, 2535200, 1362300972, 10:54:57:084 2026/03/15, 10:54:57:551667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[135], D/U[E1/E1][N7/N7][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_2 +807101, 2535200, 1362300972, 10:54:57:084 2026/03/15, 10:54:57:551667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[136], D/U[E1/E1][E2/E0][N7/N7][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_3 +807102, 2535200, 1362300972, 10:54:57:084 2026/03/15, 10:54:57:551667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] FSC[137], D/U[E1/E1][E2/E0][N11/N12][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC_3 +807297, 2535251, 1362301023, 10:54:57:084 2026/03/15, 10:54:57:555347 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FS[1], FSDLpCC ID[1][0][0][0][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +807298, 2535251, 1362301023, 10:54:57:084 2026/03/15, 10:54:57:555347 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FS[2], FSDLpCC ID[2][0][0][0][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +807303, 2535251, 1362301023, 10:54:57:084 2026/03/15, 10:54:57:555347 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FS[7], FSDLpCC ID[7][0][0][0][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +807304, 2535251, 1362301023, 10:54:57:084 2026/03/15, 10:54:57:555347 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FS[9], FSDLpCC ID[9][0][0][0][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +807305, 2535252, 1362301024, 10:54:57:084 2026/03/15, 10:54:57:555411 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FS[11], FSDLpCC ID[7][7][0][0][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +807306, 2535252, 1362301024, 10:54:57:084 2026/03/15, 10:54:57:555411 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FS[12], FSDLpCC ID[9][0][0][0][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +807316, 2535252, 1362301024, 10:54:57:084 2026/03/15, 10:54:57:555411 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FS[1], FSULpCC ID[1][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +807317, 2535252, 1362301024, 10:54:57:084 2026/03/15, 10:54:57:555411 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FS[2], FSULpCC ID[2][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +807318, 2535252, 1362301024, 10:54:57:084 2026/03/15, 10:54:57:555411 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FS[7], FSULpCC ID[7][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +807319, 2535252, 1362301024, 10:54:57:084 2026/03/15, 10:54:57:555411 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FS[12], FSULpCC ID[9][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +807355, 2535255, 1362301027, 10:54:57:084 2026/03/15, 10:54:57:555603 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FSpCC[1], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW50], bw90m[NL1_CAP_NOT_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM], CORESET[255], CORESETPerPoolIndex[255], UnicastPDSCH_PerPool[255], FDM_SchemeB[NL1_CAP_INVALID] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +807356, 2535255, 1362301027, 10:54:57:084 2026/03/15, 10:54:57:555603 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FSpCC[2], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW40], bw90m[NL1_CAP_NOT_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM], CORESET[255], CORESETPerPoolIndex[255], UnicastPDSCH_PerPool[255], FDM_SchemeB[NL1_CAP_INVALID] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +807361, 2535255, 1362301027, 10:54:57:084 2026/03/15, 10:54:57:555603 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FSpCC[7], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW100], bw90m[NL1_CAP_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM], CORESET[255], CORESETPerPoolIndex[255], UnicastPDSCH_PerPool[255], FDM_SchemeB[NL1_CAP_INVALID] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +807363, 2535255, 1362301027, 10:54:57:084 2026/03/15, 10:54:57:555603 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FSpCC[9], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW50], bw90m[NL1_CAP_NOT_SUPPORT], mimo[NL1_CAP_DL_TWO_LAYER], modulation[NL1_CAP_256QAM], CORESET[255], CORESETPerPoolIndex[255], UnicastPDSCH_PerPool[255], FDM_SchemeB[NL1_CAP_INVALID] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +807365, 2535255, 1362301027, 10:54:57:084 2026/03/15, 10:54:57:555603 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR DL FSpCC[11], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW100], bw90m[NL1_CAP_SUPPORT], mimo[NL1_CAP_DL_TWO_LAYER], modulation[NL1_CAP_256QAM], CORESET[255], CORESETPerPoolIndex[255], UnicastPDSCH_PerPool[255], FDM_SchemeB[NL1_CAP_INVALID] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +807375, 2535256, 1362301028, 10:54:57:084 2026/03/15, 10:54:57:555667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FSpCC[1], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW50], bw90m[NL1_CAP_NOT_SUPPORT], cb_mimo[NL1_CAP_UL_TWO_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[0], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +807376, 2535256, 1362301028, 10:54:57:084 2026/03/15, 10:54:57:555667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FSpCC[2], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW40], bw90m[NL1_CAP_NOT_SUPPORT], cb_mimo[NL1_CAP_UL_TWO_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[0], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +807381, 2535256, 1362301028, 10:54:57:084 2026/03/15, 10:54:57:555667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FSpCC[7], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW100], bw90m[NL1_CAP_SUPPORT], cb_mimo[NL1_CAP_UL_TWO_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[0], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +807383, 2535256, 1362301028, 10:54:57:084 2026/03/15, 10:54:57:555667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FSpCC[9], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW100], bw90m[NL1_CAP_SUPPORT], cb_mimo[NL1_CAP_UL_ONE_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[0], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +807385, 2535256, 1362301028, 10:54:57:084 2026/03/15, 10:54:57:555667 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] NR UL FSpCC[12], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW20], bw90m[NL1_CAP_NOT_SUPPORT], cb_mimo[NL1_CAP_UL_TWO_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[0], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +807392, 2535257, 1362301029, 10:54:57:084 2026/03/15, 10:54:57:555731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] EUTRA DL FS[1], FSDLpCC ID[1][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_DL_FS +807393, 2535257, 1362301029, 10:54:57:084 2026/03/15, 10:54:57:555731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] EUTRA DL FS[2], FSDLpCC ID[2][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_DL_FS +807395, 2535257, 1362301029, 10:54:57:084 2026/03/15, 10:54:57:555731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] EUTRA UL FS[1], FSULpCC ID[1][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_UL_FS +807402, 2535257, 1362301029, 10:54:57:084 2026/03/15, 10:54:57:555731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] EUTRA DL FSpCC[1], fourLayerTM3_TM4[NL1_CAP_SUPPORT], mimo[NL1_EUTRA_CAP_DL_FOUR_LAYER], CSI_Proc[255] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_DL_FS_PER_CC +807403, 2535257, 1362301029, 10:54:57:084 2026/03/15, 10:54:57:555731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] EUTRA DL FSpCC[2], fourLayerTM3_TM4[NL1_CAP_NOT_SUPPORT], mimo[NL1_EUTRA_CAP_DL_TWO_LAYER], CSI_Proc[255] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_DL_FS_PER_CC +807406, 2535257, 1362301029, 10:54:57:084 2026/03/15, 10:54:57:555731 2026/03/15, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [CAP] EUTRA UL FSpCC[1], mimo[NL1_EUTRA_CAP_UL_ONE_LAYER], 256qam[NL1_CAP_SUPPORT] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_UL_FS_PER_CC diff --git a/src/test/resources/mtkNr/input/mtkNrTraceOld.txt b/src/test/resources/mtkNr/input/mtkNrTraceOld.txt new file mode 100644 index 00000000..e8348317 --- /dev/null +++ b/src/test/resources/mtkNr/input/mtkNrTraceOld.txt @@ -0,0 +1,188 @@ +211973, 156010, 9956507, 16:33:37:615 2026/03/14, 16:33:37:716480 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] CA idx [1], NL1 CA band comb, band num[1], DL: N3A__0___0___0___0_, UL: N3A__0___0___0___0_, FSC[2] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO +211984, 156011, 9956508, 16:33:37:615 2026/03/14, 16:33:37:716544 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] CA idx [12], NL1 CA band comb, band num[1], DL: N78A__0___0___0___0_, UL: N78A__0___0___0___0_, FSC[7] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO +211985, 156011, 9956508, 16:33:37:615 2026/03/14, 16:33:37:716544 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] CA idx [13], NL1 CA band comb, band num[2], DL: N1A_N78A__0___0___0_, UL: N1A__0___0___0___0_, FSC[8] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO +211987, 156011, 9956508, 16:33:37:615 2026/03/14, 16:33:37:716544 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] CA idx [17], NL1 CA band comb, band num[2], DL: N3A_N78A__0___0___0_, UL: _0__N78A__0___0___0_, FSC[12] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO +211999, 156011, 9956508, 16:33:37:615 2026/03/14, 16:33:37:716544 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] CA idx [27], NL1 CA band comb, band num[2], DL: N1A_N3A__0___0___0_, UL: N1A__0___0___0___0_, FSC[19] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO +212023, 156012, 9956509, 16:33:37:615 2026/03/14, 16:33:37:716608 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] CA idx [51], NL1 CA band comb, band num[2], DL: B1A_N3A__0___0___0_, UL: B1A_N3A__0___0___0_, FSC[35] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO +212024, 156012, 9956509, 16:33:37:615 2026/03/14, 16:33:37:716608 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] CA idx [52], NL1 CA band comb, band num[2], DL: B1A_N78A__0___0___0_, UL: B1A_N78A__0___0___0_, FSC[41] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_CA_BC_INFO +212781, 156049, 9956546, 16:33:37:615 2026/03/14, 16:33:37:718976 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[2], band num[1], D/U[N2/N2][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212782, 156049, 9956546, 16:33:37:615 2026/03/14, 16:33:37:718976 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[2], band num[1], D/U[N3/N3][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212790, 156049, 9956546, 16:33:37:615 2026/03/14, 16:33:37:718976 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[7], band num[1], D/U[N8/N7][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212791, 156050, 9956547, 16:33:37:615 2026/03/14, 16:33:37:719040 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[7], band num[1], D/U[N10/N9][_0/_0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212792, 156050, 9956547, 16:33:37:615 2026/03/14, 16:33:37:719040 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[8], band num[2], D/U[N1/N1][N8/N0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212793, 156050, 9956547, 16:33:37:615 2026/03/14, 16:33:37:719040 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[8], band num[2], D/U[N1/N1][N10/N0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212803, 156052, 9956549, 16:33:37:615 2026/03/14, 16:33:37:719168 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[12], band num[2], D/U[N2/N0][N8/N7][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212804, 156052, 9956549, 16:33:37:615 2026/03/14, 16:33:37:719168 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[12], band num[2], D/U[N2/N0][N10/N9][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212805, 156052, 9956549, 16:33:37:615 2026/03/14, 16:33:37:719168 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[12], band num[2], D/U[N3/N0][N8/N7][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212806, 156052, 9956549, 16:33:37:615 2026/03/14, 16:33:37:719168 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[12], band num[2], D/U[N3/N0][N10/N9][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212800, 156050, 9956547, 16:33:37:615 2026/03/14, 16:33:37:719040 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[19], band num[2], D/U[N1/N1][N2/N0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212801, 156050, 9956547, 16:33:37:615 2026/03/14, 16:33:37:719040 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[19], band num[2], D/U[N1/N1][N3/N0][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212865, 156055, 9956552, 16:33:37:615 2026/03/14, 16:33:37:719360 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[35], band num[2], D/U[E1/E1][N2/N2][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212866, 156055, 9956552, 16:33:37:615 2026/03/14, 16:33:37:719360 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[35], band num[2], D/U[E1/E1][N3/N3][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212888, 156055, 9956552, 16:33:37:615 2026/03/14, 16:33:37:719360 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[41], band num[2], D/U[E1/E1][N8/N7][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +212889, 156055, 9956552, 16:33:37:615 2026/03/14, 16:33:37:719360 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] FSC[41], band num[2], D/U[E1/E1][N10/N9][_0/_0][_0/_0][_0/_0][_0/_0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_FSC +214064, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FS[1], FSDLpCC ID[1][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +214065, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FS[2], FSDLpCC ID[2][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +214066, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FS[3], FSDLpCC ID[3][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +214071, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FS[8], FSDLpCC ID[8][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +214073, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FS[10], FSDLpCC ID[10][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS +214078, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FS[1], FSULpCC ID[1][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +214079, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FS[2], FSULpCC ID[2][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +214084, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FS[3], FSULpCC ID[3][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +214085, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FS[7], FSULpCC ID[7][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +214090, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FS[9], FSULpCC ID[9][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS +214092, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FSpCC[1], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW20], bw90m[NL1_CAP_NOT_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +214093, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FSpCC[2], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW30], bw90m[NL1_CAP_NOT_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +214094, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FSpCC[3], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW30], bw90m[NL1_CAP_NOT_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +214098, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FSpCC[8], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW50], bw90m[NL1_CAP_NOT_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +214100, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR DL FSpCC[10], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW100], bw90m[NL1_CAP_SUPPORT], mimo[NL1_CAP_DL_FOUR_LAYER], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_DL_FS_PER_CC +214110, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FSpCC[1], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW20], bw90m[NL1_CAP_NOT_SUPPORT], cb_mimo[NL1_CAP_UL_ONE_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[255], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +214111, 156099, 9956596, 16:33:37:615 2026/03/14, 16:33:37:722176 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FSpCC[2], scs[NL1_CAP_SCS_15KHZ], bw[NL1_CAP_BW30], bw90m[NL1_CAP_NOT_SUPPORT], cb_mimo[NL1_CAP_UL_ONE_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[255], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +214112, 156100, 9956597, 16:33:37:615 2026/03/14, 16:33:37:722240 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FSpCC[3], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW30], bw90m[NL1_CAP_NOT_SUPPORT], cb_mimo[NL1_CAP_UL_ONE_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[255], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +214116, 156100, 9956597, 16:33:37:615 2026/03/14, 16:33:37:722240 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FSpCC[7], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW100], bw90m[NL1_CAP_SUPPORT], cb_mimo[NL1_CAP_UL_ONE_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[255], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +214118, 156100, 9956597, 16:33:37:615 2026/03/14, 16:33:37:722240 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] NR UL FSpCC[9], scs[NL1_CAP_SCS_30KHZ], bw[NL1_CAP_BW100], bw90m[NL1_CAP_SUPPORT], cb_mimo[NL1_CAP_UL_ONE_LAYER], cb_SRS_ResourcePerSet[1], non_cb_mimo[NL1_CAP_UL_MIMO_LAYER_TYPE_INVALID], non_cb_SRS_ResourcePerSet[255], non_cb_SimultaneousSRS_ResourceTx[255], modulation[NL1_CAP_256QAM] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_NR_UL_FS_PER_CC +214120, 156100, 9956597, 16:33:37:615 2026/03/14, 16:33:37:722240 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] EUTRA DL FS[1], FSDLpCC ID[1][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_DL_FS +214122, 156100, 9956597, 16:33:37:615 2026/03/14, 16:33:37:722240 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] EUTRA UL FS[1], FSULpCC ID[1][0][0][0][0] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_UL_FS +214128, 156100, 9956597, 16:33:37:615 2026/03/14, 16:33:37:722240 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] EUTRA DL FSpCC[1], fourLayerTM3_TM4[NL1_CAP_SUPPORT], mimo[NL1_EUTRA_CAP_DL_FOUR_LAYER], CSI_Proc[255] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_DL_FS_PER_CC +214130, 156100, 9956597, 16:33:37:615 2026/03/14, 16:33:37:722240 2026/03/14, MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN][CAP] EUTRA UL FSpCC[1], mimo[NL1_EUTRA_CAP_UL_ONE_LAYER], 256qam[NL1_CAP_SUPPORT] +Raw Buffer + +TRACE_NAME, NRRC_MAIN_TRACE_EUTRA_UL_FS_PER_CC diff --git a/src/test/resources/mtkNr/oracle/mtkNrTrace.json b/src/test/resources/mtkNr/oracle/mtkNrTrace.json new file mode 100644 index 00000000..8020e809 --- /dev/null +++ b/src/test/resources/mtkNr/oracle/mtkNrTrace.json @@ -0,0 +1,470 @@ +{ + "endc": [ + { + "componentsLte": [ + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + } + } + ], + "componentsNr": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + }, + { + "componentsLte": [ + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 2 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + } + } + ], + "componentsNr": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + }, + { + "componentsLte": [ + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 2 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + } + } + ], + "componentsNr": [ + { + "band": 78, + "bwClassDl": "C", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + } + ], + "nrca": [ + { + "components": [ + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 50 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "C", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + }, + { + "components": [ + { + "band": 75, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 50 + } + } + ] + }, + { + "components": [ + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 40 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 50 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 50 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "C", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 2 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 50 + } + } + ] + } + ], + "logType": "", + "metadata": {}, + "id": "", + "parserVersion": "staging", + "timestamp": 0 +} diff --git a/src/test/resources/mtkNr/oracle/mtkNrTraceOld.json b/src/test/resources/mtkNr/oracle/mtkNrTraceOld.json new file mode 100644 index 00000000..357952f8 --- /dev/null +++ b/src/test/resources/mtkNr/oracle/mtkNrTraceOld.json @@ -0,0 +1,334 @@ +{ + "endc": [ + { + "componentsLte": [ + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + } + } + ], + "componentsNr": [ + { + "band": 3, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 30 + }, + "maxBwUl": { + "type": "single", + "value": 30 + } + } + ] + }, + { + "componentsLte": [ + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + } + } + ], + "componentsNr": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + } + ], + "nrca": [ + { + "components": [ + { + "band": 3, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 30 + }, + "maxBwUl": { + "type": "single", + "value": 30 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 20 + }, + "maxBwUl": { + "type": "single", + "value": 20 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + }, + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 30 + } + } + ] + }, + { + "components": [ + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 30 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 20 + }, + "maxBwUl": { + "type": "single", + "value": 20 + } + } + ] + } + ], + "logType": "", + "metadata": {}, + "id": "", + "parserVersion": "staging", + "timestamp": 0 +} From 293e86fd4544d9004b04a70652ec864be8b02bba Mon Sep 17 00:00:00 2001 From: Henrik Date: Sat, 4 Apr 2026 23:49:51 +0200 Subject: [PATCH 2/9] Refactor ImportMtkNr: use model classes directly, emit all FSC variants --- .../importer/ImportMtkNr.kt | 373 +++++---------- .../server/ServerModeOthersTest.kt | 1 + .../resources/mtkNr/oracle/mtkNrTraceOld.json | 426 ++++++++++++++++++ 3 files changed, 532 insertions(+), 268 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index 0961aacc..3af61219 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -12,6 +12,7 @@ import it.smartphonecombo.uecapabilityparser.model.combo.ComboNrDc import it.smartphonecombo.uecapabilityparser.model.combo.ICombo import it.smartphonecombo.uecapabilityparser.model.component.ComponentLte import it.smartphonecombo.uecapabilityparser.model.component.ComponentNr +import it.smartphonecombo.uecapabilityparser.model.component.IComponent import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCLte import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCNr import it.smartphonecombo.uecapabilityparser.model.feature.FeatureSet @@ -155,44 +156,23 @@ object ImportMtkNr : ImportCapabilities { return capabilities } - /** Data class holding a parsed NR DL/UL per-CC feature. */ - private data class NrFspcc( - val scs: Int, - val bw: Int, - val bw90: Boolean, - val mimo: Int, - val mod: ModulationOrder, - ) - - /** Data class holding a parsed EUTRA DL/UL per-CC feature. */ - private data class EutraFspcc(val mimo: Int) - /** - * Data class holding a parsed FSC entry with SCS variants. + * Data class holding a parsed FSC entry with variants. * - * Some devices emit multiple FSC lines with the same FSC number but different FS references, - * corresponding to different sub-carrier spacings (SCS15 for FDD bands, SCS30 for TDD bands). + * Some devices emit multiple FSC lines with the same FSC number but different FS references. * Each variant is a list of (dlFsRef, ulFsRef) per component. */ private data class FscEntry(val variants: List>>) - /** Data class holding a parsed combo. */ - private data class MtkCombo(val components: List, val fscNum: Int) - - /** Data class holding a parsed component from combo DL/UL strings. */ - private data class MtkComponent( - val isNr: Boolean, - val band: Int, - val bwClassDl: Char, - val bwClassUl: Char?, // null if no UL - ) + /** Data class holding a parsed combo with its associated FSC number. */ + private data class MtkCombo(val components: List, val fscNum: Int) /** Container for all parsed trace data. */ private data class ParsedTrace( - val nrDlFspcc: Map, - val nrUlFspcc: Map, - val eutraDlFspcc: Map, - val eutraUlFspcc: Map, + val nrDlFspcc: Map, + val nrUlFspcc: Map, + val eutraDlFspcc: Map, + val eutraUlFspcc: Map, val nrDlFs: Map>, val nrUlFs: Map>, val eutraDlFs: Map>, @@ -203,10 +183,10 @@ object ImportMtkNr : ImportCapabilities { /** Parse all capability structures from the trace log lines. */ private fun parseTraceLog(lines: List): ParsedTrace { - val nrDlFspcc = mutableMapOf() - val nrUlFspcc = mutableMapOf() - val eutraDlFspcc = mutableMapOf() - val eutraUlFspcc = mutableMapOf() + val nrDlFspcc = mutableMapOf() + val nrUlFspcc = mutableMapOf() + val eutraDlFspcc = mutableMapOf() + val eutraUlFspcc = mutableMapOf() val nrDlFs = mutableMapOf>() val nrUlFs = mutableMapOf>() val eutraDlFs = mutableMapOf>() @@ -243,37 +223,47 @@ object ImportMtkNr : ImportCapabilities { } /** Parse a NR DL FSpCC definition line. */ - private fun parseNrDlFspccLine(line: String, map: MutableMap): Unit? { + private fun parseNrDlFspccLine(line: String, map: MutableMap): Unit? { val m = reNrDlFspcc.find(line) ?: return null val idx = m.groupValues[1].toInt() + val scs = parseMtkScs(m.groupValues[2]) + val bwRaw = parseMtkBw(m.groupValues[3]) + val bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT" + val bw = if (bw90 && bwRaw == 80) 90 else bwRaw map[idx] = - NrFspcc( - scs = parseMtkScs(m.groupValues[2]), - bw = parseMtkBw(m.groupValues[3]), - bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT", - mimo = parseMtkDlMimo(m.groupValues[5]), - mod = parseMtkModulation(m.groupValues[6]), + FeaturePerCCNr( + type = LinkDirection.DOWNLINK, + mimo = parseMtkDlMimo(m.groupValues[5]).toMimo(), + qam = parseMtkModulation(m.groupValues[6]), + bw = bw, + scs = scs, + channelBW90mhz = bw90, ) return Unit } /** Parse a NR UL FSpCC definition line. */ - private fun parseNrUlFspccLine(line: String, map: MutableMap): Unit? { + private fun parseNrUlFspccLine(line: String, map: MutableMap): Unit? { val m = reNrUlFspcc.find(line) ?: return null val idx = m.groupValues[1].toInt() + val scs = parseMtkScs(m.groupValues[2]) + val bwRaw = parseMtkBw(m.groupValues[3]) + val bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT" + val bw = if (bw90 && bwRaw == 80) 90 else bwRaw map[idx] = - NrFspcc( - scs = parseMtkScs(m.groupValues[2]), - bw = parseMtkBw(m.groupValues[3]), - bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT", - mimo = parseMtkUlMimo(m.groupValues[5]), - mod = parseMtkModulation(m.groupValues[6]), + FeaturePerCCNr( + type = LinkDirection.UPLINK, + mimo = parseMtkUlMimo(m.groupValues[5]).toMimo(), + qam = parseMtkModulation(m.groupValues[6]), + bw = bw, + scs = scs, + channelBW90mhz = bw90, ) return Unit } /** Parse a EUTRA DL FSpCC definition line. */ - private fun parseEutraDlFspccLine(line: String, map: MutableMap): Unit? { + private fun parseEutraDlFspccLine(line: String, map: MutableMap): Unit? { val m = reEutraDlFspcc.find(line) ?: return null val idx = m.groupValues[1].toInt() val mimoStr = m.groupValues[2] @@ -283,12 +273,12 @@ object ImportMtkNr : ImportCapabilities { "TWO_LAYER" in mimoStr -> 2 else -> 0 } - map[idx] = EutraFspcc(mimo) + map[idx] = FeaturePerCCLte(type = LinkDirection.DOWNLINK, mimo = mimo.toMimo()) return Unit } /** Parse a EUTRA UL FSpCC definition line. */ - private fun parseEutraUlFspccLine(line: String, map: MutableMap): Unit? { + private fun parseEutraUlFspccLine(line: String, map: MutableMap): Unit? { val m = reEutraUlFspcc.find(line) ?: return null val idx = m.groupValues[1].toInt() val mimoStr = m.groupValues[2] @@ -298,7 +288,7 @@ object ImportMtkNr : ImportCapabilities { "ONE_LAYER" in mimoStr -> 1 else -> 0 } - map[idx] = EutraFspcc(mimo) + map[idx] = FeaturePerCCLte(type = LinkDirection.UPLINK, mimo = mimo.toMimo()) return Unit } @@ -338,7 +328,7 @@ object ImportMtkNr : ImportCapabilities { return Unit } - /** Parse an FSC definition line. Collects all SCS variants for each FSC number. */ + /** Parse an FSC definition line. Collects all variants for each FSC number. */ private fun parseFscLine(line: String, map: MutableMap): Unit? { val m = reFsc.find(line) ?: return null val fscNum = m.groupValues[1].toInt() @@ -354,7 +344,7 @@ object ImportMtkNr : ImportCapabilities { val existing = map[fscNum] if (existing != null) { - // Add as another SCS variant + // Add as another variant map[fscNum] = FscEntry(existing.variants + listOf(pairs)) } else { map[fscNum] = FscEntry(listOf(pairs)) @@ -382,7 +372,7 @@ object ImportMtkNr : ImportCapabilities { * DL and UL strings are underscore-separated with positional matching. For example: DL: * B1A_B3A_N78A__0_... UL: B1A__0__N78A__0_... */ - private fun parseComboComponents(dlStr: String, ulStr: String): List { + private fun parseComboComponents(dlStr: String, ulStr: String): List { val dlParts = dlStr.split("_") val ulParts = ulStr.split("_") @@ -421,7 +411,7 @@ object ImportMtkNr : ImportCapabilities { } // Merge: for each DL component, find matching UL by position first, then by band - val result = mutableListOf() + val result = mutableListOf() val usedUl = mutableSetOf() // indices into ulEntries that have been matched for (dl in dlEntries) { @@ -450,7 +440,16 @@ object ImportMtkNr : ImportCapabilities { } } - result.add(MtkComponent(dl.isNr, dl.band, dl.bwClass, ulBwClass)) + val classDlBw = BwClass.valueOf(dl.bwClass.toString()) + val classUlBw = + if (ulBwClass != null) BwClass.valueOf(ulBwClass.toString()) else BwClass.NONE + + if (dl.isNr) { + result.add(ComponentNr(dl.band, classDlBw, classUlBw)) + } else { + val mimoUl = if (classUlBw != BwClass.NONE) 1.toMimo() else EmptyMimo + result.add(ComponentLte(dl.band, classDlBw, classUlBw, mimoUL = mimoUl)) + } } return result @@ -523,222 +522,69 @@ object ImportMtkNr : ImportCapabilities { return Pair(m.groupValues[1][0], m.groupValues[2].toInt()) } - /** NR FDD band numbers — use SCS 15 kHz features for these. */ - private val nrFddBands = - setOf( - 1, - 2, - 3, - 5, - 7, - 8, - 12, - 13, - 14, - 18, - 20, - 24, - 25, - 26, - 28, - 30, - 31, - 65, - 66, - 68, - 70, - 71, - 72, - 74, - 75, - 85, - 87, - 88, - 91, - 92, - 93, - 94, - 100, - 105, - 106, - 109, - 110, - ) - - /** NR TDD band numbers — use SCS 30 kHz features for these. */ - private val nrTddBands = - setOf(34, 38, 39, 40, 41, 46, 47, 48, 50, 53, 77, 78, 79, 90, 96, 101, 102, 104) - - /** Return the preferred SCS (15 or 30) for the given NR band number. */ - private fun preferredScsForBand(band: Int): Int { - return if (band in nrTddBands) 30 else 15 - } - - /** - * Resolve the SCS of a specific NR DL FS reference. - * - * Returns 0 if the SCS cannot be determined. - */ - private fun resolveNrFsScs(dlFsStr: String, parsed: ParsedTrace): Int { - val (fsType, fsNum) = parseFsRef(dlFsStr) - if (fsType != 'N' || fsNum <= 0) return 0 - val fspccIds = parsed.nrDlFs[fsNum] ?: return 0 - val firstFspccId = fspccIds.firstOrNull() ?: return 0 - val fspcc = parsed.nrDlFspcc[firstFspccId] ?: return 0 - return fspcc.scs - } - - /** - * Build the best FSC pair list for the given combo components by selecting, for each component - * position, the FS reference pair whose SCS matches that band's duplex mode (FDD → SCS 15 kHz, - * TDD → SCS 30 kHz). - * - * FSC variants are a cartesian product of per-component SCS options. For example FSC[12] with - * N3(FDD)+N78(TDD) may have 4 variants: (SCS15,SCS15), (SCS15,SCS30), (SCS30,SCS15), - * (SCS30,SCS30). The correct pick is (SCS15, SCS30) → N3 at SCS15, N78 at SCS30. - * - * Falls back to the first variant if only one variant exists. - */ - private fun selectFscPairs( - fscEntry: FscEntry, - components: List, - parsed: ParsedTrace, - ): List> { - if (fscEntry.variants.size <= 1) return fscEntry.variants.first() - - val pairCount = fscEntry.variants.first().size - val result = mutableListWithCapacity>(pairCount) - - for (i in 0 until pairCount) { - // Collect all unique (dl, ul) candidates at position i across all variants - val candidates = fscEntry.variants.map { it[i] }.distinct() - - if (candidates.size <= 1 || i >= components.size) { - result.add(candidates.first()) - continue - } - - val comp = components[i] - if (!comp.isNr) { - // EUTRA components don't vary by SCS - result.add(candidates.first()) - continue - } - - // Pick the candidate whose DL FS resolves to the preferred SCS for this band - val preferredScs = preferredScsForBand(comp.band) - val best = - candidates.firstOrNull { resolveNrFsScs(it.first, parsed) == preferredScs } - ?: candidates.first() - result.add(best) - } - - return result - } - /** Build Capabilities combos from parsed trace data. */ private fun buildCombos(parsed: ParsedTrace): List { val combos = mutableListWithCapacity(parsed.combos.size) - // Build NR per-CC feature lists (indexed directly by FSpCC ID) - val nrFeaturesPerCCDl = buildNrFeaturesPerCC(parsed.nrDlFspcc, LinkDirection.DOWNLINK) - val nrFeaturesPerCCUl = buildNrFeaturesPerCC(parsed.nrUlFspcc, LinkDirection.UPLINK) - - // Build EUTRA per-CC feature lists (indexed directly by FSpCC ID) - val eutraFeaturesPerCCDl = - buildEutraFeaturesPerCC(parsed.eutraDlFspcc, LinkDirection.DOWNLINK) - val eutraFeaturesPerCCUl = - buildEutraFeaturesPerCC(parsed.eutraUlFspcc, LinkDirection.UPLINK) - // Build EUTRA Feature Sets: FS number -> FeatureSet (list of per-CC features) val eutraDlFeatureSets = - buildEutraFeatureSets(parsed.eutraDlFs, eutraFeaturesPerCCDl, LinkDirection.DOWNLINK) + buildEutraFeatureSets(parsed.eutraDlFs, parsed.eutraDlFspcc, LinkDirection.DOWNLINK) val eutraUlFeatureSets = - buildEutraFeatureSets(parsed.eutraUlFs, eutraFeaturesPerCCUl, LinkDirection.UPLINK) + buildEutraFeatureSets(parsed.eutraUlFs, parsed.eutraUlFspcc, LinkDirection.UPLINK) for (combo in parsed.combos) { val fscEntry = parsed.fscDefs[combo.fscNum] ?: continue val components = combo.components - // Select the correct SCS-matched FS pairs for this combo's bands - val fscPairs = selectFscPairs(fscEntry, components, parsed) - - // FSC pairs might not exactly match component count; align by min - val count = minOf(fscPairs.size, components.size) - if (count == 0) continue - - val nrComponents = mutableListOf() - val lteComponents = mutableListOf() - - for (i in 0 until count) { - val comp = components[i] - val (dlFsStr, ulFsStr) = fscPairs[i] - val (dlFsType, dlFsNum) = parseFsRef(dlFsStr) - val (ulFsType, ulFsNum) = parseFsRef(ulFsStr) - - if (comp.isNr) { - val nrComp = - buildNrComponent( - comp, - dlFsType, - dlFsNum, - ulFsType, - ulFsNum, - parsed, - nrFeaturesPerCCDl, - nrFeaturesPerCCUl, - ) - nrComponents.add(nrComp) - } else { - val lteComp = - buildLteComponent( - comp, - dlFsType, - dlFsNum, - ulFsType, - ulFsNum, - eutraDlFeatureSets, - eutraUlFeatureSets, - ) - lteComponents.add(lteComp) + // Emit one combo per FSC variant (all variants are listed) + for (variant in fscEntry.variants) { + // FSC pairs might not exactly match component count; align by min + val count = minOf(variant.size, components.size) + if (count == 0) continue + + val nrComponents = mutableListOf() + val lteComponents = mutableListOf() + + for (i in 0 until count) { + val comp = components[i] + val (dlFsStr, ulFsStr) = variant[i] + val (dlFsType, dlFsNum) = parseFsRef(dlFsStr) + val (ulFsType, ulFsNum) = parseFsRef(ulFsStr) + + if (comp is ComponentNr) { + val nrComp = + buildNrComponent( + comp, + dlFsType, + dlFsNum, + ulFsType, + ulFsNum, + parsed, + ) + nrComponents.add(nrComp) + } else if (comp is ComponentLte) { + val lteComp = + buildLteComponent( + comp, + dlFsType, + dlFsNum, + ulFsType, + ulFsNum, + eutraDlFeatureSets, + eutraUlFeatureSets, + ) + lteComponents.add(lteComp) + } } - } - val icombo = assembleCombo(lteComponents, nrComponents) - if (icombo != null) combos.add(icombo) + val icombo = assembleCombo(lteComponents, nrComponents) + if (icombo != null) combos.add(icombo) + } } return combos } - /** Build a map of FSpCC ID -> FeaturePerCCNr for NR features. */ - private fun buildNrFeaturesPerCC( - fspccMap: Map, - direction: LinkDirection, - ): Map { - return fspccMap.mapValues { (_, fspcc) -> - val bw = if (fspcc.bw90 && fspcc.bw == 80) 90 else fspcc.bw - FeaturePerCCNr( - type = direction, - mimo = fspcc.mimo.toMimo(), - qam = fspcc.mod, - bw = bw, - scs = fspcc.scs, - channelBW90mhz = fspcc.bw90, - ) - } - } - - /** Build a map of FSpCC ID -> FeaturePerCCLte for EUTRA features. */ - private fun buildEutraFeaturesPerCC( - fspccMap: Map, - direction: LinkDirection, - ): Map { - return fspccMap.mapValues { (_, fspcc) -> - FeaturePerCCLte(type = direction, mimo = fspcc.mimo.toMimo()) - } - } - /** * Build EUTRA Feature Sets: map from FS number to FeatureSet, resolving FSpCC IDs to actual * per-CC features. @@ -756,25 +602,20 @@ object ImportMtkNr : ImportCapabilities { /** Build an NR ComponentNr from parsed component data, resolving features via FSC. */ private fun buildNrComponent( - comp: MtkComponent, + comp: ComponentNr, dlFsType: Char?, dlFsNum: Int, ulFsType: Char?, ulFsNum: Int, parsed: ParsedTrace, - nrFeaturesPerCCDl: Map, - nrFeaturesPerCCUl: Map, ): ComponentNr { - val classDl = BwClass.valueOf(comp.bwClassDl.toString()) - val classUl = - if (comp.bwClassUl != null) BwClass.valueOf(comp.bwClassUl.toString()) else BwClass.NONE - val baseComponent = ComponentNr(comp.band, classDl, classUl) + val baseComponent = comp.copy() // Resolve DL per-CC features: FS -> FSpCC IDs -> FeaturePerCCNr val dlFeature: List = if (dlFsType == 'N' && dlFsNum > 0) { val fspccIds = parsed.nrDlFs[dlFsNum] ?: emptyList() - fspccIds.mapNotNull { nrFeaturesPerCCDl[it] } + fspccIds.mapNotNull { parsed.nrDlFspcc[it] } } else { emptyList() } @@ -783,7 +624,7 @@ object ImportMtkNr : ImportCapabilities { val ulFeature: List = if (ulFsType == 'N' && ulFsNum > 0) { val fspccIds = parsed.nrUlFs[ulFsNum] ?: emptyList() - fspccIds.mapNotNull { nrFeaturesPerCCUl[it] } + fspccIds.mapNotNull { parsed.nrUlFspcc[it] } } else { emptyList() } @@ -794,7 +635,7 @@ object ImportMtkNr : ImportCapabilities { /** Build an LTE ComponentLte from parsed component data, resolving features via FSC. */ private fun buildLteComponent( - comp: MtkComponent, + comp: ComponentLte, dlFsType: Char?, dlFsNum: Int, ulFsType: Char?, @@ -802,11 +643,7 @@ object ImportMtkNr : ImportCapabilities { eutraDlFeatureSets: Map, eutraUlFeatureSets: Map, ): ComponentLte { - val classDl = BwClass.valueOf(comp.bwClassDl.toString()) - val classUl = - if (comp.bwClassUl != null) BwClass.valueOf(comp.bwClassUl.toString()) else BwClass.NONE - val mimoUl = if (classUl != BwClass.NONE) 1.toMimo() else EmptyMimo - val baseComponent = ComponentLte(comp.band, classDl, classUl, mimoUL = mimoUl) + val baseComponent = comp.copy() // Resolve DL features from EUTRA FS val dlFeature: List? = diff --git a/src/test/java/it/smartphonecombo/uecapabilityparser/server/ServerModeOthersTest.kt b/src/test/java/it/smartphonecombo/uecapabilityparser/server/ServerModeOthersTest.kt index 455ed43a..09135ace 100644 --- a/src/test/java/it/smartphonecombo/uecapabilityparser/server/ServerModeOthersTest.kt +++ b/src/test/java/it/smartphonecombo/uecapabilityparser/server/ServerModeOthersTest.kt @@ -52,6 +52,7 @@ internal class ServerModeOthersTest { "QLTE", "QNR", "M", + "MNR", "O", "QC", "T", diff --git a/src/test/resources/mtkNr/oracle/mtkNrTraceOld.json b/src/test/resources/mtkNr/oracle/mtkNrTraceOld.json index 357952f8..7bbc34ec 100644 --- a/src/test/resources/mtkNr/oracle/mtkNrTraceOld.json +++ b/src/test/resources/mtkNr/oracle/mtkNrTraceOld.json @@ -49,6 +49,105 @@ } ] }, + { + "componentsLte": [ + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + } + } + ], + "componentsNr": [ + { + "band": 3, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 30 + }, + "maxBwUl": { + "type": "single", + "value": 30 + } + } + ] + }, + { + "componentsLte": [ + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + } + } + ], + "componentsNr": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + }, { "componentsLte": [ { @@ -135,6 +234,75 @@ } ] }, + { + "components": [ + { + "band": 3, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 30 + }, + "maxBwUl": { + "type": "single", + "value": 30 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + } + ] + }, { "components": [ { @@ -170,6 +338,57 @@ } ] }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 50 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 20 + }, + "maxBwUl": { + "type": "single", + "value": 20 + } + } + ] + }, { "components": [ { @@ -222,6 +441,58 @@ } ] }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + }, + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 30 + } + } + ] + }, { "components": [ { @@ -276,6 +547,89 @@ }, { "components": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 50 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + }, + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 30 + } + } + ] + }, + { + "components": [ + { + "band": 78, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, + "bw90mhzSupported": true, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 100 + }, + "maxBwUl": { + "type": "single", + "value": 100 + } + }, { "band": 3, "bwClassDl": "A", @@ -287,7 +641,79 @@ "type": "single", "value": "qam256" }, + "maxScs": 30, + "maxBwDl": { + "type": "single", + "value": 30 + } + } + ] + }, + { + "components": [ + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 30 + } + }, + { + "band": 1, + "bwClassDl": "A", + "bwClassUl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "mimoUl": { + "type": "single", + "value": 1 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "modulationUl": { + "type": "single", + "value": "qam256" + }, "maxScs": 15, + "maxBwDl": { + "type": "single", + "value": 20 + }, + "maxBwUl": { + "type": "single", + "value": 20 + } + } + ] + }, + { + "components": [ + { + "band": 3, + "bwClassDl": "A", + "mimoDl": { + "type": "single", + "value": 4 + }, + "modulationDl": { + "type": "single", + "value": "qam256" + }, + "maxScs": 30, "maxBwDl": { "type": "single", "value": 30 From 2ab117cc7b0b797bc41c36b39593f822651ae1d5 Mon Sep 17 00:00:00 2001 From: Andrea Mennillo Date: Mon, 6 Apr 2026 23:33:01 +0200 Subject: [PATCH 3/9] Refactor ImportMtkNr: Remove MTKCombo --- .../importer/ImportMtkNr.kt | 22 +++++++++++-------- .../model/combo/ComboEnDc.kt | 6 ++--- .../uecapabilityparser/model/combo/ComboNr.kt | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index 3af61219..cd780e76 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -164,9 +164,6 @@ object ImportMtkNr : ImportCapabilities { */ private data class FscEntry(val variants: List>>) - /** Data class holding a parsed combo with its associated FSC number. */ - private data class MtkCombo(val components: List, val fscNum: Int) - /** Container for all parsed trace data. */ private data class ParsedTrace( val nrDlFspcc: Map, @@ -178,7 +175,7 @@ object ImportMtkNr : ImportCapabilities { val eutraDlFs: Map>, val eutraUlFs: Map>, val fscDefs: Map, - val combos: List, + val combos: List, ) /** Parse all capability structures from the trace log lines. */ @@ -192,7 +189,7 @@ object ImportMtkNr : ImportCapabilities { val eutraDlFs = mutableMapOf>() val eutraUlFs = mutableMapOf>() val fscDefs = mutableMapOf() - val combos = mutableListOf() + val combos = mutableListOf() for (line in lines) { // Try each pattern - at most one will match per line @@ -353,7 +350,7 @@ object ImportMtkNr : ImportCapabilities { } /** Parse a combo definition line. Tries new format first, then old format. */ - private fun parseComboLine(line: String, list: MutableList): Unit? { + private fun parseComboLine(line: String, list: MutableList): Unit? { val m = reComboNew.find(line) ?: reComboOld.find(line) ?: return null val dlStr = m.groupValues[3] val ulStr = m.groupValues[4] @@ -361,7 +358,14 @@ object ImportMtkNr : ImportCapabilities { val components = parseComboComponents(dlStr, ulStr) if (components.isNotEmpty()) { - list.add(MtkCombo(components, fscNum)) + val lteComponents = components.filterIsInstance() + val nrComponents = components.filterIsInstance() + val combo = if (lteComponents.isEmpty()) { + ComboNr(nrComponents, featureSet = fscNum) + } else { + ComboEnDc(lteComponents, nrComponents, featureSet = fscNum) + } + list.add(combo) } return Unit } @@ -533,8 +537,8 @@ object ImportMtkNr : ImportCapabilities { buildEutraFeatureSets(parsed.eutraUlFs, parsed.eutraUlFspcc, LinkDirection.UPLINK) for (combo in parsed.combos) { - val fscEntry = parsed.fscDefs[combo.fscNum] ?: continue - val components = combo.components + val fscEntry = parsed.fscDefs[combo.featureSet] ?: continue + val components = combo.masterComponents + combo.secondaryComponents // Emit one combo per FSC variant (all variants are listed) for (variant in fscEntry.variants) { diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboEnDc.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboEnDc.kt index 58affad5..ee8c716d 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboEnDc.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboEnDc.kt @@ -38,9 +38,9 @@ data class ComboEnDc( masterComponents: List, secondaryComponents: List, featureSet: Int, - bcsNr: BCS, - bcsEutra: BCS, - bcsIntraEnDc: BCS, + bcsNr: BCS = EmptyBCS, + bcsEutra: BCS = EmptyBCS, + bcsIntraEnDc: BCS = EmptyBCS, ) : this(masterComponents, secondaryComponents, bcsNr, bcsEutra, bcsIntraEnDc) { this.featureSet = featureSet } diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboNr.kt index 670dd28f..d919e290 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/model/combo/ComboNr.kt @@ -23,7 +23,7 @@ data class ComboNr( constructor( masterComponents: List, featureSet: Int, - bcs: BCS, + bcs: BCS = EmptyBCS, uplinkTxSwitch: List = emptyList(), ) : this(masterComponents, bcs, uplinkTxSwitch) { this.featureSet = featureSet From 886f25207439454422ced7c97cb91335ac4f081f Mon Sep 17 00:00:00 2001 From: Andrea Mennillo Date: Sun, 12 Apr 2026 15:17:20 +0200 Subject: [PATCH 4/9] ImportMtkNr: Fix style --- .../importer/ImportMtkNr.kt | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index cd780e76..6a8d0a78 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -360,11 +360,12 @@ object ImportMtkNr : ImportCapabilities { if (components.isNotEmpty()) { val lteComponents = components.filterIsInstance() val nrComponents = components.filterIsInstance() - val combo = if (lteComponents.isEmpty()) { - ComboNr(nrComponents, featureSet = fscNum) - } else { - ComboEnDc(lteComponents, nrComponents, featureSet = fscNum) - } + val combo = + if (lteComponents.isEmpty()) { + ComboNr(nrComponents, featureSet = fscNum) + } else { + ComboEnDc(lteComponents, nrComponents, featureSet = fscNum) + } list.add(combo) } return Unit @@ -557,14 +558,7 @@ object ImportMtkNr : ImportCapabilities { if (comp is ComponentNr) { val nrComp = - buildNrComponent( - comp, - dlFsType, - dlFsNum, - ulFsType, - ulFsNum, - parsed, - ) + buildNrComponent(comp, dlFsType, dlFsNum, ulFsType, ulFsNum, parsed) nrComponents.add(nrComp) } else if (comp is ComponentLte) { val lteComp = From 73ffc52a5448283fa0408fae98931808a97b24bf Mon Sep 17 00:00:00 2001 From: Andrea Mennillo Date: Sun, 12 Apr 2026 16:03:45 +0200 Subject: [PATCH 5/9] ImportMtkNr: Detect the tag of each line --- .../importer/ImportMtkNr.kt | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index 6a8d0a78..42dcb924 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -192,17 +192,23 @@ object ImportMtkNr : ImportCapabilities { val combos = mutableListOf() for (line in lines) { - // Try each pattern - at most one will match per line - parseNrDlFspccLine(line, nrDlFspcc) - ?: parseNrUlFspccLine(line, nrUlFspcc) - ?: parseEutraDlFspccLine(line, eutraDlFspcc) - ?: parseEutraUlFspccLine(line, eutraUlFspcc) - ?: parseNrDlFsLine(line, nrDlFs) - ?: parseNrUlFsLine(line, nrUlFs) - ?: parseEutraDlFsLine(line, eutraDlFs) - ?: parseEutraUlFsLine(line, eutraUlFs) - ?: parseFscLine(line, fscDefs) - ?: parseComboLine(line, combos) + val tag = extractTag(line) + + when (tag) { + "CA idx" -> parseComboLine(line, combos) + "NR DL FSpCC" -> parseNrDlFspccLine(line, nrDlFspcc) + "NR UL FSpCC" -> parseNrUlFspccLine(line, nrUlFspcc) + "EUTRA DL FSpCC" -> parseEutraDlFspccLine(line, eutraDlFspcc) + "EUTRA UL FSpCC" -> parseEutraUlFspccLine(line, eutraUlFspcc) + "NR DL FS" -> parseNrDlFsLine(line, nrDlFs) + "NR UL FS" -> parseNrUlFsLine(line, nrUlFs) + "EUTRA DL FS" -> parseEutraDlFsLine(line, eutraDlFs) + "EUTRA UL FS" -> parseEutraUlFsLine(line, eutraUlFs) + "FSC" -> parseFscLine(line, fscDefs) + else -> { + // do nothing + } + } } return ParsedTrace( @@ -219,6 +225,27 @@ object ImportMtkNr : ImportCapabilities { ) } + /** + * Extract the tag, that is reported in the line after the [CAP] header and before the + * index number. If the line doesn't contain the word [CAP] it returns an empty string. + * + * Example: "211987, 156011, 9956508, 16:33:37:615 2026/03/14, 16:33:37:716544 2026/03/14, + * MOD_NRRC_MAIN, MOD_NRRC_MAIN_BASELINE_TRACE_INFO_H, [MAIN] [CAP] CA idx [17]" + * -> tag CA idx + */ + private fun extractTag(line: String): String { + // Extract text After [MAIN][CAP]/[CAP] + var tag = line.substringAfter("[CAP]", "") + + // remove [index number] and text after index number + tag = tag.split("[").first() + + // trim + tag = tag.trim() + + return tag + } + /** Parse a NR DL FSpCC definition line. */ private fun parseNrDlFspccLine(line: String, map: MutableMap): Unit? { val m = reNrDlFspcc.find(line) ?: return null From 1513ae3a71963b6db886f564a821b017a90b9760 Mon Sep 17 00:00:00 2001 From: Andrea Mennillo Date: Sun, 12 Apr 2026 16:21:44 +0200 Subject: [PATCH 6/9] ImportMtkNr: Merge DL/UL extractors --- .../importer/ImportMtkNr.kt | 132 +++++++----------- 1 file changed, 47 insertions(+), 85 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index 42dcb924..48503130 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -196,14 +196,14 @@ object ImportMtkNr : ImportCapabilities { when (tag) { "CA idx" -> parseComboLine(line, combos) - "NR DL FSpCC" -> parseNrDlFspccLine(line, nrDlFspcc) - "NR UL FSpCC" -> parseNrUlFspccLine(line, nrUlFspcc) - "EUTRA DL FSpCC" -> parseEutraDlFspccLine(line, eutraDlFspcc) - "EUTRA UL FSpCC" -> parseEutraUlFspccLine(line, eutraUlFspcc) - "NR DL FS" -> parseNrDlFsLine(line, nrDlFs) - "NR UL FS" -> parseNrUlFsLine(line, nrUlFs) - "EUTRA DL FS" -> parseEutraDlFsLine(line, eutraDlFs) - "EUTRA UL FS" -> parseEutraUlFsLine(line, eutraUlFs) + "NR DL FSpCC" -> parseNrFspccLine(line, nrDlFspcc, LinkDirection.DOWNLINK) + "NR UL FSpCC" -> parseNrFspccLine(line, nrUlFspcc, LinkDirection.UPLINK) + "EUTRA DL FSpCC" -> parseEutraFspccLine(line, eutraDlFspcc, LinkDirection.DOWNLINK) + "EUTRA UL FSpCC" -> parseEutraFspccLine(line, eutraUlFspcc, LinkDirection.UPLINK) + "NR DL FS" -> parseNrFsLine(line, nrDlFs, LinkDirection.DOWNLINK) + "NR UL FS" -> parseNrFsLine(line, nrUlFs, LinkDirection.UPLINK) + "EUTRA DL FS" -> parseEutraFsLine(line, eutraDlFs, LinkDirection.DOWNLINK) + "EUTRA UL FS" -> parseEutraFsLine(line, eutraUlFs, LinkDirection.UPLINK) "FSC" -> parseFscLine(line, fscDefs) else -> { // do nothing @@ -246,29 +246,14 @@ object ImportMtkNr : ImportCapabilities { return tag } - /** Parse a NR DL FSpCC definition line. */ - private fun parseNrDlFspccLine(line: String, map: MutableMap): Unit? { - val m = reNrDlFspcc.find(line) ?: return null - val idx = m.groupValues[1].toInt() - val scs = parseMtkScs(m.groupValues[2]) - val bwRaw = parseMtkBw(m.groupValues[3]) - val bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT" - val bw = if (bw90 && bwRaw == 80) 90 else bwRaw - map[idx] = - FeaturePerCCNr( - type = LinkDirection.DOWNLINK, - mimo = parseMtkDlMimo(m.groupValues[5]).toMimo(), - qam = parseMtkModulation(m.groupValues[6]), - bw = bw, - scs = scs, - channelBW90mhz = bw90, - ) - return Unit - } - - /** Parse a NR UL FSpCC definition line. */ - private fun parseNrUlFspccLine(line: String, map: MutableMap): Unit? { - val m = reNrUlFspcc.find(line) ?: return null + /** Parse a NR DL/UL FSpCC definition line. */ + private fun parseNrFspccLine( + line: String, + map: MutableMap, + direction: LinkDirection, + ): Unit? { + val regex = if (direction == LinkDirection.DOWNLINK) reNrDlFspcc else reNrUlFspcc + val m = regex.find(line) ?: return null val idx = m.groupValues[1].toInt() val scs = parseMtkScs(m.groupValues[2]) val bwRaw = parseMtkBw(m.groupValues[3]) @@ -276,8 +261,8 @@ object ImportMtkNr : ImportCapabilities { val bw = if (bw90 && bwRaw == 80) 90 else bwRaw map[idx] = FeaturePerCCNr( - type = LinkDirection.UPLINK, - mimo = parseMtkUlMimo(m.groupValues[5]).toMimo(), + type = direction, + mimo = parseMtkMimo(m.groupValues[5]).toMimo(), qam = parseMtkModulation(m.groupValues[6]), bw = bw, scs = scs, @@ -286,66 +271,50 @@ object ImportMtkNr : ImportCapabilities { return Unit } - /** Parse a EUTRA DL FSpCC definition line. */ - private fun parseEutraDlFspccLine(line: String, map: MutableMap): Unit? { - val m = reEutraDlFspcc.find(line) ?: return null + /** Parse a EUTRA DL/UL FSpCC definition line. */ + private fun parseEutraFspccLine( + line: String, + map: MutableMap, + direction: LinkDirection, + ): Unit? { + val regex = if (direction == LinkDirection.DOWNLINK) reEutraDlFspcc else reEutraUlFspcc + val m = regex.find(line) ?: return null val idx = m.groupValues[1].toInt() val mimoStr = m.groupValues[2] val mimo = when { "FOUR_LAYER" in mimoStr -> 4 - "TWO_LAYER" in mimoStr -> 2 - else -> 0 - } - map[idx] = FeaturePerCCLte(type = LinkDirection.DOWNLINK, mimo = mimo.toMimo()) - return Unit - } - - /** Parse a EUTRA UL FSpCC definition line. */ - private fun parseEutraUlFspccLine(line: String, map: MutableMap): Unit? { - val m = reEutraUlFspcc.find(line) ?: return null - val idx = m.groupValues[1].toInt() - val mimoStr = m.groupValues[2] - val mimo = - when { "TWO_LAYER" in mimoStr -> 2 "ONE_LAYER" in mimoStr -> 1 else -> 0 } - map[idx] = FeaturePerCCLte(type = LinkDirection.UPLINK, mimo = mimo.toMimo()) - return Unit - } - - /** Parse a NR DL FS -> FSpCC ID mapping line. */ - private fun parseNrDlFsLine(line: String, map: MutableMap>): Unit? { - val m = reNrDlFs.find(line) ?: return null - val fsNum = m.groupValues[1].toInt() - val ids = extractFspccIds(m, startGroup = 2, endGroup = 9) - map[fsNum] = ids - return Unit - } - - /** Parse a NR UL FS -> FSpCC ID mapping line. */ - private fun parseNrUlFsLine(line: String, map: MutableMap>): Unit? { - val m = reNrUlFs.find(line) ?: return null - val fsNum = m.groupValues[1].toInt() - val ids = extractFspccIds(m, startGroup = 2, endGroup = 5) - map[fsNum] = ids + map[idx] = FeaturePerCCLte(type = direction, mimo = mimo.toMimo()) return Unit } - /** Parse an EUTRA DL FS -> FSpCC ID mapping line. */ - private fun parseEutraDlFsLine(line: String, map: MutableMap>): Unit? { - val m = reEutraDlFs.find(line) ?: return null + /** Parse a NR DL/UL FS -> FSpCC ID mapping line. */ + private fun parseNrFsLine( + line: String, + map: MutableMap>, + direction: LinkDirection, + ): Unit? { + val regex = if (direction == LinkDirection.DOWNLINK) reNrDlFs else reNrUlFs + val endGroup = if (direction == LinkDirection.DOWNLINK) 9 else 5 + val m = regex.find(line) ?: return null val fsNum = m.groupValues[1].toInt() - val ids = extractFspccIds(m, startGroup = 2, endGroup = 6) + val ids = extractFspccIds(m, startGroup = 2, endGroup = endGroup) map[fsNum] = ids return Unit } - /** Parse an EUTRA UL FS -> FSpCC ID mapping line. */ - private fun parseEutraUlFsLine(line: String, map: MutableMap>): Unit? { - val m = reEutraUlFs.find(line) ?: return null + /** Parse an EUTRA DL/UL FS -> FSpCC ID mapping line. */ + private fun parseEutraFsLine( + line: String, + map: MutableMap>, + direction: LinkDirection, + ): Unit? { + val regex = if (direction == LinkDirection.DOWNLINK) reEutraDlFs else reEutraUlFs + val m = regex.find(line) ?: return null val fsNum = m.groupValues[1].toInt() val ids = extractFspccIds(m, startGroup = 2, endGroup = 6) map[fsNum] = ids @@ -517,19 +486,12 @@ object ImportMtkNr : ImportCapabilities { return m?.groupValues?.get(1)?.toInt() ?: 0 } - /** Convert MTK DL MIMO enum string to layer count. */ - private fun parseMtkDlMimo(mimoStr: String): Int { + /** Convert MTK MIMO enum string to layer count. */ + private fun parseMtkMimo(mimoStr: String): Int { return when (mimoStr) { "NL1_CAP_DL_TWO_LAYER" -> 2 "NL1_CAP_DL_FOUR_LAYER" -> 4 "NL1_CAP_DL_EIGHT_LAYER" -> 8 - else -> 0 - } - } - - /** Convert MTK UL MIMO enum string to layer count. */ - private fun parseMtkUlMimo(mimoStr: String): Int { - return when (mimoStr) { "NL1_CAP_UL_ONE_LAYER" -> 1 "NL1_CAP_UL_TWO_LAYER" -> 2 "NL1_CAP_UL_FOUR_LAYER" -> 4 From b610867af634a52d900e98dfaff3cc015c39ee53 Mon Sep 17 00:00:00 2001 From: Andrea Mennillo Date: Sun, 12 Apr 2026 16:28:47 +0200 Subject: [PATCH 7/9] ImportMtkNr: Extract Mimo and Modulation using builtin functions --- .../importer/ImportMtkNr.kt | 41 ++++--------------- .../uecapabilityparser/model/Mimo.kt | 14 ++++--- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index 48503130..83e0ac5f 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -17,6 +17,7 @@ import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCLte import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCNr import it.smartphonecombo.uecapabilityparser.model.feature.FeatureSet import it.smartphonecombo.uecapabilityparser.model.feature.IFeaturePerCC +import it.smartphonecombo.uecapabilityparser.model.fromLiteral import it.smartphonecombo.uecapabilityparser.model.modulation.ModulationOrder import it.smartphonecombo.uecapabilityparser.model.toMimo @@ -262,8 +263,9 @@ object ImportMtkNr : ImportCapabilities { map[idx] = FeaturePerCCNr( type = direction, - mimo = parseMtkMimo(m.groupValues[5]).toMimo(), - qam = parseMtkModulation(m.groupValues[6]), + // XXX_ONE_LAYER -> 1, XXX_TWO_LAYER -> 2... + mimo = Int.fromLiteral(m.groupValues[5]).toMimo(), + qam = ModulationOrder.of(m.groupValues[6]), bw = bw, scs = scs, channelBW90mhz = bw90, @@ -280,15 +282,9 @@ object ImportMtkNr : ImportCapabilities { val regex = if (direction == LinkDirection.DOWNLINK) reEutraDlFspcc else reEutraUlFspcc val m = regex.find(line) ?: return null val idx = m.groupValues[1].toInt() - val mimoStr = m.groupValues[2] - val mimo = - when { - "FOUR_LAYER" in mimoStr -> 4 - "TWO_LAYER" in mimoStr -> 2 - "ONE_LAYER" in mimoStr -> 1 - else -> 0 - } - map[idx] = FeaturePerCCLte(type = direction, mimo = mimo.toMimo()) + // XXX_ONE_LAYER -> 1, XXX_TWO_LAYER -> 2... + val mimo = Int.fromLiteral(m.groupValues[2]).toMimo() + map[idx] = FeaturePerCCLte(type = direction, mimo = mimo) return Unit } @@ -486,29 +482,6 @@ object ImportMtkNr : ImportCapabilities { return m?.groupValues?.get(1)?.toInt() ?: 0 } - /** Convert MTK MIMO enum string to layer count. */ - private fun parseMtkMimo(mimoStr: String): Int { - return when (mimoStr) { - "NL1_CAP_DL_TWO_LAYER" -> 2 - "NL1_CAP_DL_FOUR_LAYER" -> 4 - "NL1_CAP_DL_EIGHT_LAYER" -> 8 - "NL1_CAP_UL_ONE_LAYER" -> 1 - "NL1_CAP_UL_TWO_LAYER" -> 2 - "NL1_CAP_UL_FOUR_LAYER" -> 4 - else -> 0 - } - } - - /** Convert MTK modulation enum string to ModulationOrder. */ - private fun parseMtkModulation(modStr: String): ModulationOrder { - return when (modStr) { - "NL1_CAP_64QAM" -> ModulationOrder.QAM64 - "NL1_CAP_256QAM" -> ModulationOrder.QAM256 - "NL1_CAP_1024QAM" -> ModulationOrder.QAM1024 - else -> ModulationOrder.NONE - } - } - /** Parse an FS reference string like 'N7', 'E1', '_0' into type and number. */ private fun parseFsRef(fsStr: String): Pair { if (fsStr == "_0" || fsStr == "0" || fsStr == "N0") return Pair(null, 0) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/model/Mimo.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/model/Mimo.kt index aad219fc..35f3cf30 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/model/Mimo.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/model/Mimo.kt @@ -137,14 +137,18 @@ fun Int.toMimo(): Mimo = Mimo.from(this) * - 4 if [string] contains "four" * - 8 if [string] contains "eight" * - 0 otherwise + * + * The function is case-insensitive. */ internal fun Int.Companion.fromLiteral(string: String?): Int { + val lowerCaseString = string?.lowercase() + return when { - string == null -> 0 - "one" in string -> 1 - "two" in string -> 2 - "four" in string -> 4 - "eight" in string -> 8 + lowerCaseString == null -> 0 + "one" in lowerCaseString -> 1 + "two" in lowerCaseString -> 2 + "four" in lowerCaseString -> 4 + "eight" in lowerCaseString -> 8 else -> 0 } } From 5a679984bab5dda3e7d080e8f2ec754ed3af3bdc Mon Sep 17 00:00:00 2001 From: Andrea Mennillo Date: Sun, 12 Apr 2026 16:57:17 +0200 Subject: [PATCH 8/9] ImportMtkNr: Remove side effects from simple parsing functions --- .../importer/ImportMtkNr.kt | 96 +++++++++++-------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index 83e0ac5f..b148c890 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -196,15 +196,21 @@ object ImportMtkNr : ImportCapabilities { val tag = extractTag(line) when (tag) { - "CA idx" -> parseComboLine(line, combos) - "NR DL FSpCC" -> parseNrFspccLine(line, nrDlFspcc, LinkDirection.DOWNLINK) - "NR UL FSpCC" -> parseNrFspccLine(line, nrUlFspcc, LinkDirection.UPLINK) - "EUTRA DL FSpCC" -> parseEutraFspccLine(line, eutraDlFspcc, LinkDirection.DOWNLINK) - "EUTRA UL FSpCC" -> parseEutraFspccLine(line, eutraUlFspcc, LinkDirection.UPLINK) - "NR DL FS" -> parseNrFsLine(line, nrDlFs, LinkDirection.DOWNLINK) - "NR UL FS" -> parseNrFsLine(line, nrUlFs, LinkDirection.UPLINK) - "EUTRA DL FS" -> parseEutraFsLine(line, eutraDlFs, LinkDirection.DOWNLINK) - "EUTRA UL FS" -> parseEutraFsLine(line, eutraUlFs, LinkDirection.UPLINK) + "CA idx" -> parseComboLine(line)?.let { combos.add(it) } + "NR DL FSpCC" -> + parseNrFspccLine(line, LinkDirection.DOWNLINK)?.let { nrDlFspcc += it } + "NR UL FSpCC" -> + parseNrFspccLine(line, LinkDirection.UPLINK)?.let { nrUlFspcc += it } + "EUTRA DL FSpCC" -> + parseEutraFspccLine(line, LinkDirection.DOWNLINK)?.let { eutraDlFspcc += it } + "EUTRA UL FSpCC" -> + parseEutraFspccLine(line, LinkDirection.UPLINK)?.let { eutraUlFspcc += it } + "NR DL FS" -> parseNrFsLine(line, LinkDirection.DOWNLINK)?.let { nrDlFs += it } + "NR UL FS" -> parseNrFsLine(line, LinkDirection.UPLINK)?.let { nrUlFs += it } + "EUTRA DL FS" -> + parseEutraFsLine(line, LinkDirection.DOWNLINK)?.let { eutraDlFs += it } + "EUTRA UL FS" -> + parseEutraFsLine(line, LinkDirection.UPLINK)?.let { eutraUlFs += it } "FSC" -> parseFscLine(line, fscDefs) else -> { // do nothing @@ -247,12 +253,15 @@ object ImportMtkNr : ImportCapabilities { return tag } - /** Parse a NR DL/UL FSpCC definition line. */ + /** + * Parse a NR DL/UL FSpCC definition line. + * + * Return the Pair(Idx, FeaturePerCCNr) or null. + */ private fun parseNrFspccLine( line: String, - map: MutableMap, direction: LinkDirection, - ): Unit? { + ): Pair? { val regex = if (direction == LinkDirection.DOWNLINK) reNrDlFspcc else reNrUlFspcc val m = regex.find(line) ?: return null val idx = m.groupValues[1].toInt() @@ -260,7 +269,7 @@ object ImportMtkNr : ImportCapabilities { val bwRaw = parseMtkBw(m.groupValues[3]) val bw90 = m.groupValues[4] == "NL1_CAP_SUPPORT" val bw = if (bw90 && bwRaw == 80) 90 else bwRaw - map[idx] = + val feature = FeaturePerCCNr( type = direction, // XXX_ONE_LAYER -> 1, XXX_TWO_LAYER -> 2... @@ -270,51 +279,56 @@ object ImportMtkNr : ImportCapabilities { scs = scs, channelBW90mhz = bw90, ) - return Unit + + return idx to feature } - /** Parse a EUTRA DL/UL FSpCC definition line. */ + /** + * Parse a EUTRA DL/UL FSpCC definition line. + * + * Return the Pair(Idx, FeaturePerCCLte) or null. + */ private fun parseEutraFspccLine( line: String, - map: MutableMap, direction: LinkDirection, - ): Unit? { + ): Pair? { val regex = if (direction == LinkDirection.DOWNLINK) reEutraDlFspcc else reEutraUlFspcc val m = regex.find(line) ?: return null val idx = m.groupValues[1].toInt() // XXX_ONE_LAYER -> 1, XXX_TWO_LAYER -> 2... val mimo = Int.fromLiteral(m.groupValues[2]).toMimo() - map[idx] = FeaturePerCCLte(type = direction, mimo = mimo) - return Unit + val feature = FeaturePerCCLte(type = direction, mimo = mimo) + + return idx to feature } - /** Parse a NR DL/UL FS -> FSpCC ID mapping line. */ - private fun parseNrFsLine( - line: String, - map: MutableMap>, - direction: LinkDirection, - ): Unit? { + /** + * Parse a NR DL/UL FS -> FSpCC ID mapping line. + * + * Return the Pair(FSNum, id List) or null. + */ + private fun parseNrFsLine(line: String, direction: LinkDirection): Pair>? { val regex = if (direction == LinkDirection.DOWNLINK) reNrDlFs else reNrUlFs val endGroup = if (direction == LinkDirection.DOWNLINK) 9 else 5 val m = regex.find(line) ?: return null val fsNum = m.groupValues[1].toInt() val ids = extractFspccIds(m, startGroup = 2, endGroup = endGroup) - map[fsNum] = ids - return Unit + + return fsNum to ids } - /** Parse an EUTRA DL/UL FS -> FSpCC ID mapping line. */ - private fun parseEutraFsLine( - line: String, - map: MutableMap>, - direction: LinkDirection, - ): Unit? { + /** + * Parse an EUTRA DL/UL FS -> FSpCC ID mapping line. + * + * Return the Pair(FSNum, id List) or null. + */ + private fun parseEutraFsLine(line: String, direction: LinkDirection): Pair>? { val regex = if (direction == LinkDirection.DOWNLINK) reEutraDlFs else reEutraUlFs val m = regex.find(line) ?: return null val fsNum = m.groupValues[1].toInt() val ids = extractFspccIds(m, startGroup = 2, endGroup = 6) - map[fsNum] = ids - return Unit + + return fsNum to ids } /** Parse an FSC definition line. Collects all variants for each FSC number. */ @@ -341,8 +355,12 @@ object ImportMtkNr : ImportCapabilities { return Unit } - /** Parse a combo definition line. Tries new format first, then old format. */ - private fun parseComboLine(line: String, list: MutableList): Unit? { + /** + * Parse a combo definition line. Tries new format first, then old format. + * + * Return the parsed ICombo or null. + */ + private fun parseComboLine(line: String): ICombo? { val m = reComboNew.find(line) ?: reComboOld.find(line) ?: return null val dlStr = m.groupValues[3] val ulStr = m.groupValues[4] @@ -358,9 +376,9 @@ object ImportMtkNr : ImportCapabilities { } else { ComboEnDc(lteComponents, nrComponents, featureSet = fscNum) } - list.add(combo) + return combo } - return Unit + return null } /** From b6a49d57bf27732049dbda13995dd9e36cc0780a Mon Sep 17 00:00:00 2001 From: Andrea Mennillo Date: Sun, 12 Apr 2026 18:21:21 +0200 Subject: [PATCH 9/9] ImportMtkNr: Refactor FSC parsing --- .../importer/ImportMtkNr.kt | 190 ++++++------------ 1 file changed, 62 insertions(+), 128 deletions(-) diff --git a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt index b148c890..ee14e636 100644 --- a/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt +++ b/src/main/java/it/smartphonecombo/uecapabilityparser/importer/ImportMtkNr.kt @@ -13,9 +13,9 @@ import it.smartphonecombo.uecapabilityparser.model.combo.ICombo import it.smartphonecombo.uecapabilityparser.model.component.ComponentLte import it.smartphonecombo.uecapabilityparser.model.component.ComponentNr import it.smartphonecombo.uecapabilityparser.model.component.IComponent +import it.smartphonecombo.uecapabilityparser.model.feature.FeatureIndex import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCLte import it.smartphonecombo.uecapabilityparser.model.feature.FeaturePerCCNr -import it.smartphonecombo.uecapabilityparser.model.feature.FeatureSet import it.smartphonecombo.uecapabilityparser.model.feature.IFeaturePerCC import it.smartphonecombo.uecapabilityparser.model.fromLiteral import it.smartphonecombo.uecapabilityparser.model.modulation.ModulationOrder @@ -161,9 +161,9 @@ object ImportMtkNr : ImportCapabilities { * Data class holding a parsed FSC entry with variants. * * Some devices emit multiple FSC lines with the same FSC number but different FS references. - * Each variant is a list of (dlFsRef, ulFsRef) per component. + * Each variant is a FeatureIndex */ - private data class FscEntry(val variants: List>>) + private data class FscEntry(val variants: List>) /** Container for all parsed trace data. */ private data class ParsedTrace( @@ -211,7 +211,12 @@ object ImportMtkNr : ImportCapabilities { parseEutraFsLine(line, LinkDirection.DOWNLINK)?.let { eutraDlFs += it } "EUTRA UL FS" -> parseEutraFsLine(line, LinkDirection.UPLINK)?.let { eutraUlFs += it } - "FSC" -> parseFscLine(line, fscDefs) + "FSC" -> + parseFscLine(line)?.let { (key, value) -> + // Merge current variants with new variants + val currentVariants = fscDefs[key]?.variants ?: emptyList() + fscDefs[key] = FscEntry(currentVariants + listOf(value)) + } else -> { // do nothing } @@ -331,28 +336,30 @@ object ImportMtkNr : ImportCapabilities { return fsNum to ids } - /** Parse an FSC definition line. Collects all variants for each FSC number. */ - private fun parseFscLine(line: String, map: MutableMap): Unit? { + /** + * Parse an FSC definition line. + * + * Return a Pair FSC Num -> FeatureIndex list. + */ + private fun parseFscLine(line: String): Pair>? { val m = reFsc.find(line) ?: return null val fscNum = m.groupValues[1].toInt() val pairsStr = m.groupValues[2] - val pairs = mutableListOf>() + val indexesList = mutableListOf() for (pm in reDuPair.findAll(pairsStr)) { val dl = pm.groupValues[1].trim() val ul = pm.groupValues[2].trim() if (dl == "_0" && ul == "_0") continue // Skip empty slots - pairs.add(Pair(dl, ul)) - } + val (dlType, dlIndex) = parseFsRef(dl) + val (ulType, ulIndex) = parseFsRef(ul) + val isNr = dlType == 'N' || ulType == 'N' - val existing = map[fscNum] - if (existing != null) { - // Add as another variant - map[fscNum] = FscEntry(existing.variants + listOf(pairs)) - } else { - map[fscNum] = FscEntry(listOf(pairs)) + val featureIndex = FeatureIndex(isNr, dlIndex, ulIndex) + indexesList.add(featureIndex) } - return Unit + + return fscNum to indexesList } /** @@ -511,14 +518,9 @@ object ImportMtkNr : ImportCapabilities { private fun buildCombos(parsed: ParsedTrace): List { val combos = mutableListWithCapacity(parsed.combos.size) - // Build EUTRA Feature Sets: FS number -> FeatureSet (list of per-CC features) - val eutraDlFeatureSets = - buildEutraFeatureSets(parsed.eutraDlFs, parsed.eutraDlFspcc, LinkDirection.DOWNLINK) - val eutraUlFeatureSets = - buildEutraFeatureSets(parsed.eutraUlFs, parsed.eutraUlFspcc, LinkDirection.UPLINK) - for (combo in parsed.combos) { val fscEntry = parsed.fscDefs[combo.featureSet] ?: continue + // Master is before secondary in FSC lines val components = combo.masterComponents + combo.secondaryComponents // Emit one combo per FSC variant (all variants are listed) @@ -527,131 +529,63 @@ object ImportMtkNr : ImportCapabilities { val count = minOf(variant.size, components.size) if (count == 0) continue - val nrComponents = mutableListOf() - val lteComponents = mutableListOf() - + val newComponents = mutableListOf() for (i in 0 until count) { val comp = components[i] - val (dlFsStr, ulFsStr) = variant[i] - val (dlFsType, dlFsNum) = parseFsRef(dlFsStr) - val (ulFsType, ulFsNum) = parseFsRef(ulFsStr) - - if (comp is ComponentNr) { - val nrComp = - buildNrComponent(comp, dlFsType, dlFsNum, ulFsType, ulFsNum, parsed) - nrComponents.add(nrComp) - } else if (comp is ComponentLte) { - val lteComp = - buildLteComponent( - comp, - dlFsType, - dlFsNum, - ulFsType, - ulFsNum, - eutraDlFeatureSets, - eutraUlFeatureSets, - ) - lteComponents.add(lteComp) - } + val featureIndex = variant[i] + val newComponent = mergeFeatureAndComponent(comp, featureIndex, parsed) + newComponents.add(newComponent) } - val icombo = assembleCombo(lteComponents, nrComponents) - if (icombo != null) combos.add(icombo) + val newLteComponents = newComponents.filterIsInstance() + val newNrComponents = newComponents.filterIsInstance() + val newCombo = assembleCombo(newLteComponents, newNrComponents) + + if (newCombo != null) combos.add(newCombo) } } return combos } - /** - * Build EUTRA Feature Sets: map from FS number to FeatureSet, resolving FSpCC IDs to actual - * per-CC features. - */ - private fun buildEutraFeatureSets( - fsMap: Map>, - fspccFeatures: Map, - direction: LinkDirection, - ): Map { - return fsMap.mapValues { (_, fspccIds) -> - val perCCList = fspccIds.mapNotNull { fspccFeatures[it] } - FeatureSet(perCCList, direction) - } - } - - /** Build an NR ComponentNr from parsed component data, resolving features via FSC. */ - private fun buildNrComponent( - comp: ComponentNr, - dlFsType: Char?, - dlFsNum: Int, - ulFsType: Char?, - ulFsNum: Int, + /** Build an IComponent from parsed component data, resolving features via FSC. */ + private fun mergeFeatureAndComponent( + comp: IComponent, + featureIndex: FeatureIndex, parsed: ParsedTrace, - ): ComponentNr { - val baseComponent = comp.copy() - - // Resolve DL per-CC features: FS -> FSpCC IDs -> FeaturePerCCNr - val dlFeature: List = - if (dlFsType == 'N' && dlFsNum > 0) { - val fspccIds = parsed.nrDlFs[dlFsNum] ?: emptyList() - fspccIds.mapNotNull { parsed.nrDlFspcc[it] } - } else { - emptyList() - } - - // Resolve UL per-CC features - val ulFeature: List = - if (ulFsType == 'N' && ulFsNum > 0) { - val fspccIds = parsed.nrUlFs[ulFsNum] ?: emptyList() - fspccIds.mapNotNull { parsed.nrUlFspcc[it] } - } else { - emptyList() - } - - return mergeComponentAndFeaturePerCC(baseComponent, dlFeature, ulFeature, null) - as ComponentNr - } - - /** Build an LTE ComponentLte from parsed component data, resolving features via FSC. */ - private fun buildLteComponent( - comp: ComponentLte, - dlFsType: Char?, - dlFsNum: Int, - ulFsType: Char?, - ulFsNum: Int, - eutraDlFeatureSets: Map, - eutraUlFeatureSets: Map, - ): ComponentLte { - val baseComponent = comp.copy() - - // Resolve DL features from EUTRA FS - val dlFeature: List? = - if (dlFsType == 'E' && dlFsNum > 0) { - eutraDlFeatureSets[dlFsNum]?.featureSetsPerCC - } else { - null - } - - // Resolve UL features from EUTRA FS - val ulFeature: List? = - if (ulFsType == 'E' && ulFsNum > 0) { - eutraUlFeatureSets[ulFsNum]?.featureSetsPerCC - } else { - null - } + ): IComponent { + val downlinkIndex = featureIndex.downlinkIndex + val uplinkIndex = featureIndex.uplinkIndex + + var dlFeatures: List? = null + var ulFeatures: List? = null + + if (featureIndex.isNR && comp is ComponentNr) { + // Resolve DL per-CC features: FS -> FSpCC IDs -> FeaturePerCCNr + dlFeatures = parsed.nrDlFs[downlinkIndex]?.mapNotNull { parsed.nrDlFspcc[it] } + // Resolve UL per-CC features + ulFeatures = parsed.nrUlFs[uplinkIndex]?.mapNotNull { parsed.nrUlFspcc[it] } + } else if (!featureIndex.isNR && comp is ComponentLte) { + // Resolve DL features from EUTRA FS + dlFeatures = parsed.eutraDlFs[downlinkIndex]?.mapNotNull { parsed.eutraDlFspcc[it] } + + // Resolve UL features from EUTRA FS + ulFeatures = parsed.eutraUlFs[uplinkIndex]?.mapNotNull { parsed.eutraUlFspcc[it] } + } - return mergeComponentAndFeaturePerCC(baseComponent, dlFeature, ulFeature, null) - as ComponentLte + // The function return a copy of component + return mergeComponentAndFeaturePerCC(comp, dlFeatures, ulFeatures, null) } /** Assemble the final combo (NR-CA, NR-DC, or EN-DC) from component lists. */ private fun assembleCombo( - lteComponents: List, - nrComponents: List, + lteComponents: List, + nrComponents: List, ): ICombo? { if (nrComponents.isEmpty() && lteComponents.isEmpty()) return null - val sortedNr = nrComponents.sortedDescending() - val sortedLte = lteComponents.sortedDescending() + val sortedNr = nrComponents.sortedDescending().filterIsInstance() + val sortedLte = lteComponents.sortedDescending().filterIsInstance() return if (sortedLte.isEmpty()) { // NR-CA or NR-DC