Skip to content

Commit

Permalink
Merge pull request #489 from handymenny/shannon-lte
Browse files Browse the repository at this point in the history
Add initial support for ShannonLteUeCap
  • Loading branch information
handymenny authored Sep 30, 2024
2 parents d59e9d8 + 142e115 commit 7e9dbd8
Show file tree
Hide file tree
Showing 24 changed files with 530,588 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object HelpMessage {
const val ERROR_SUBTYPES_DUPLICATE =
"A subtype cannot appear multiple times in the same --sub-types."
const val ERROR_MULTIPLE_INPUTS_UNSUPPORTED =
"Type P, E, SHNR, DLF, QMDL, HDF, SDM, NSG don't support multiple inputs in one --input option. Use multiple --input instead."
"Type P, E, SHNR, SHLTE, DLF, QMDL, HDF, SDM, NSG don't support multiple inputs in one --input option. Use multiple --input instead."
const val MAX_REQUEST_SIZE = "Max request size (in Bytes) that this server should accept."
const val NEW_CSV_FORMAT = "Use new CSV format for LTE CA"
const val CUSTOM_CSS = "Inject custom css in Web UI"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@file:OptIn(ExperimentalSerializationApi::class)

package it.smartphonecombo.uecapabilityparser.importer

import it.smartphonecombo.uecapabilityparser.extension.mutableListWithCapacity
import it.smartphonecombo.uecapabilityparser.io.InputSource
import it.smartphonecombo.uecapabilityparser.model.Capabilities
import it.smartphonecombo.uecapabilityparser.model.combo.ComboLte
import it.smartphonecombo.uecapabilityparser.model.component.ComponentLte
import it.smartphonecombo.uecapabilityparser.model.shannon.lte.ShannonComboLte
import it.smartphonecombo.uecapabilityparser.model.shannon.lte.ShannonComponentLte
import it.smartphonecombo.uecapabilityparser.model.shannon.lte.ShannonLteUECap
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.protobuf.ProtoBuf

object ImportShannonLteUeCap : ImportCapabilities {
override fun parse(input: InputSource): Capabilities {
val capabilities = Capabilities()
val byteArray = input.readBytes()
val lteUECap = ProtoBuf.decodeFromByteArray<ShannonLteUECap>(byteArray)

capabilities.setMetadata("shannonUeCapVersion", lteUECap.version)

val list = mutableListWithCapacity<ComboLte>(lteUECap.combos.size)
for (combo in lteUECap.combos) {
list.add(processShannonCombo(combo))
}

capabilities.lteCombos = list

return capabilities
}

private fun processShannonCombo(shCombo: ShannonComboLte): ComboLte {
val lteComponents = shCombo.components
val lte = processShannonComponentsLte(lteComponents)
val bcs = shCombo.bcs

val combo = ComboLte(lte, bcs)
return combo
}

private fun processShannonComponentsLte(
shComponents: List<ShannonComponentLte>
): List<ComponentLte> {
val components = shComponents.map { processShannonComponentLte(it) }

return components.sortedDescending()
}

private fun processShannonComponentLte(shComponent: ShannonComponentLte): ComponentLte {
return shComponent.toComponent()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import it.smartphonecombo.uecapabilityparser.importer.ImportMTKLte
import it.smartphonecombo.uecapabilityparser.importer.ImportNrCapPrune
import it.smartphonecombo.uecapabilityparser.importer.ImportNvItem
import it.smartphonecombo.uecapabilityparser.importer.ImportQctModemCap
import it.smartphonecombo.uecapabilityparser.importer.ImportShannonLteUeCap
import it.smartphonecombo.uecapabilityparser.importer.ImportShannonNrUeCap
import it.smartphonecombo.uecapabilityparser.importer.multi.ImportScat.isScatAvailable
import kotlinx.serialization.SerialName
Expand All @@ -32,6 +33,7 @@ enum class LogType(val description: String) {
T("TEMS UE Capability Information"),
A("Amarisoft UE Capability Information"),
RF("QCT Modem Capabilities"),
SHLTE("Shannon LTE UE Cap Config Protobuf"),
SHNR("Shannon NR UE Cap Config Protobuf"),
P("PCAP"),
NSG("NSG baseband log json"),
Expand Down Expand Up @@ -61,7 +63,8 @@ enum class LogType(val description: String) {
/** One input -> multi capabilities * */
val multiImporter = validEntries.intersect(listOf(P, DLF, QMDL, HDF, SDM, NSG))
/** One input -> multi or single capability * */
val singleInput = validEntries.intersect(listOf(E, SHNR, P, DLF, QMDL, HDF, SDM, NSG))
val singleInput =
validEntries.intersect(listOf(E, SHNR, SHLTE, P, DLF, QMDL, HDF, SDM, NSG))

/** Return [INVALID] if conversion fails * */
fun of(string: String?): LogType {
Expand All @@ -82,6 +85,7 @@ enum class LogType(val description: String) {
M -> ImportMTKLte
QNR -> Import0xB826
RF -> ImportQctModemCap
SHLTE -> ImportShannonLteUeCap
SHNR -> ImportShannonNrUeCap
W,
N,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@file:OptIn(ExperimentalSerializationApi::class)

package it.smartphonecombo.uecapabilityparser.model.shannon.lte

import it.smartphonecombo.uecapabilityparser.model.BCS
import it.smartphonecombo.uecapabilityparser.model.EmptyBCS
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

@Serializable
@SerialName("Combo")
data class ShannonComboLte(
/** List of Components. */
@ProtoNumber(1) val components: List<ShannonComponentLte> = emptyList(),
/**
* The supportedBandwidthCombinationSet of this combo.
*
* It's stored as a 32bit unsigned int, each of its bits has the same value of the corresponding
* bit in the BitString. 0 means default i.e. only BCS 0 supported (if applicable).
*/
@ProtoNumber(2) @SerialName("bcs") private val rawBcs: Long? = null,
@ProtoNumber(3) val unknown1: Long,
@ProtoNumber(4) val unknown2: Long
) {
val bcs
get() =
when (rawBcs) {
0L -> BCS.fromQualcommCP("0")
null -> EmptyBCS
else -> rawBcs.toString(2).padStart(32, '0').let { BCS.fromBinaryString(it) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
@file:OptIn(ExperimentalSerializationApi::class)

package it.smartphonecombo.uecapabilityparser.model.shannon.lte

import it.smartphonecombo.uecapabilityparser.model.BwClass
import it.smartphonecombo.uecapabilityparser.model.LinkDirection
import it.smartphonecombo.uecapabilityparser.model.Mimo
import it.smartphonecombo.uecapabilityparser.model.component.ComponentLte
import it.smartphonecombo.uecapabilityparser.model.toMimo
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

@Serializable
@SerialName("Component")
data class ShannonComponentLte(
/** LTE Bands are stored as int. */
@ProtoNumber(1) @SerialName("band") private val band: Int,
/**
* First 8 bits encode mimo, second 8 bits encode bw class. Bw class is encoded setting to 1 the
* bit representing the bw class index. Ex. 1000 0000 = class A (index 0), 0100 0000 = class B
* (index 1), 00100 0000 = class C (index 2)
*
* Mimo is encoded as enum, 0 -> 2, 1 -> 4
*/
@ProtoNumber(2) @SerialName("bwClassMimoDl") private val rawBwClassMimoDl: Int,
/**
* First 8 bits encode mimo, second 8 bits encode bw class. Bw class is encoded setting to 1 the
* bit representing the bw class index. Ex. 1000 0000 = class A (index 0), 0100 0000 = class B
* (index 1), 00100 0000 = class C (index 2)
*
* Mimo is encoded as enum, 0 -> 1, 1 -> 2
*/
@ProtoNumber(3) @SerialName("bwClassMimoUl") private val rawBwClassMimoUl: Int,
) {

val bwClassDl: BwClass
get() = fromRawToBwClass(rawBwClassMimoDl)

val bwClassUl: BwClass
get() = fromRawToBwClass(rawBwClassMimoUl)

val mimoDl: Mimo
get() = fromRawToMimo(rawBwClassMimoDl, LinkDirection.DOWNLINK)

val mimoUl: Mimo
get() = fromRawToMimo(rawBwClassMimoUl, LinkDirection.UPLINK)

fun toComponent(): ComponentLte {
return ComponentLte(band, bwClassDl, bwClassUl, mimoDl, mimoUl)
}

private fun fromRawToMimo(raw: Int, direction: LinkDirection): Mimo {
val mimoIndex = (raw and 0xF) + 1
val shift = if (direction == LinkDirection.DOWNLINK) 1 else 0

val mimo = mimoIndex shl shift

return mimo.toMimo()
}

private fun fromRawToBwClass(raw: Int): BwClass {
// bits 8 - 15
val upperHalf = raw shr 8 and 0xFF
val bitString = upperHalf.toString(2).padStart(8, '0')

// get position of first 1
val indexOfFirstOne = bitString.indexOf("1") + 1

return BwClass.valueOf(indexOfFirstOne)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@file:OptIn(ExperimentalSerializationApi::class)

package it.smartphonecombo.uecapabilityparser.model.shannon.lte

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber

/**
* This is the result of reverse-engineering Shannon Ue Cap Configs stored as binary protobufs in
* Google Pixel firmware.
*
* See also ShannonLteUeCap.proto in src/main/resources/definition.
*
* This work wouldn't have been possible without the help of @NXij.
*/
@Serializable
@SerialName("ShannonLteUECap")
data class ShannonLteUECap(
/** ShannonUECapLte version. */
@ProtoNumber(1) val version: Long,
/** List of combos. */
@ProtoNumber(2) val combos: List<ShannonComboLte> = emptyList(),
@ProtoNumber(3) val bitmask: Long,
)
44 changes: 44 additions & 0 deletions src/main/resources/definition/ShannonLteUeCap.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
syntax = "proto2";

message ShannonLteUECap {
// ShannonUECapLte version.
required uint32 version = 1;
// List of combos.
repeated Combo combos = 2;
// Unknown bitmask.
required uint32 bitmask = 3;
}

message Combo {
// List of Components.
repeated Component components = 1;
/*
The supportedBandwidthCombinationSet of this combo.
It's stored as a 32bit unsigned int, each of its bits has the same value of the corresponding
bit in the BitString. 0 means default i.e. only BCS 0 supported (if applicable).
*/
optional uint32 bcs = 2;
// Unknown bitmask.
required uint32 unknown1 = 3;
// Unknown bitmask.
required uint32 unknown2 = 4;
}

message Component {
// LTE Bands are stored as int
required int32 band = 1;
/*
First 8 bits encode mimo, second 8 bits encode bw class. Bw class is encoded setting to 1 the
bit representing the bw class index.
Ex. 1000 0000 = class A (index 0), 0100 0000 = class B (index 1), 00100 0000 = class C (index 2)
Mimo is encoded as enum, 0 -> 2, 1 -> 4
*/
required int32 bwClassMimoDl = 2;
/*
First 8 bits encode mimo, second 8 bits encode bw class. Bw class is encoded setting to 1 the
bit representing the bw class index.
Ex. 1000 0000 = class A (index 0), 0100 0000 = class B (index 1), 00100 0000 = class C (index 2)
Mimo is encoded as enum, 0 -> 1, 1 -> 2
*/
required int32 bwClassMimoUl = 3;
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,19 @@ internal class CliCsvNewOutputTest {
)
}

@Test
fun testShannonLteUeCap() {
test(
"-i",
"$path/input/shannonLteUeCap.binarypb",
"-t",
"SHLTE",
"-c",
"-",
oracleFilename = "shannonLteUeCap.csv"
)
}

private fun test(vararg args: String, oracleFilename: String) {
val oraclePath = "$path/oracleCsvNew/$oracleFilename"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,20 @@ internal class CliJsonOutputTest {
)
}

@Test
fun testShannonLteUeCap() {
test(
"-i",
"$path/input/shannonLteUeCap.binarypb",
"-t",
"SHLTE",
"-j",
"-",
"--json-pretty-print",
oracleFilename = "shannonLteUeCap.json"
)
}

@Test
fun testWiresharkEutra() {
test(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package it.smartphonecombo.uecapabilityparser.importer

import it.smartphonecombo.uecapabilityparser.extension.toInputSource
import it.smartphonecombo.uecapabilityparser.model.Capabilities
import java.io.File
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test

internal class ImportShannonLteUeCapTest {
private val path = "src/test/resources/shannon/"

private fun parse(inputFilename: String, oracleFilename: String) {
val filePath = "$path/input/$inputFilename"
val actual = ImportShannonLteUeCap.parse(File(filePath).toInputSource())
val expected =
Json.decodeFromString<Capabilities>(
File("$path/oracleForImport/$oracleFilename").readText()
)

Assertions.assertEquals(expected, actual)
}

@Test
fun parseLte() {
parse("lte.binarypb", "lte.json")
}

@Test
fun parseLte2() {
parse("lte2.binarypb", "lte2.json")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package it.smartphonecombo.uecapabilityparser.model

import it.smartphonecombo.uecapabilityparser.model.shannon.lte.ShannonComponentLte
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

internal class ShannonComponentLteTest {

@Test
fun testBwClass() {
val classDMimo2 = 0b0001_0000_0000_0000
val classAMimo1 = 0b1000_0000_0000_0000
val componentLte = ShannonComponentLte(1, classDMimo2, classAMimo1)

assertEquals("D".toBwClass(), componentLte.bwClassDl)
assertEquals("A".toBwClass(), componentLte.bwClassUl)
}

@Test
fun testMimo() {
val classAMimo4 = 0b0001_0000_0000_0001
val classAMimo2 = 0b1000_0000_0000_0001
val componentLte = ShannonComponentLte(1, classAMimo4, classAMimo2)

assertEquals(4.toMimo(), componentLte.mimoDl)
assertEquals(2.toMimo(), componentLte.mimoUl)
}

@Test
fun testBwClassNone() {
val classBMimo2 = 0b0100_0000_0000_0000

val componentLte = ShannonComponentLte(3, classBMimo2, 0)

assertEquals("B".toBwClass(), componentLte.bwClassDl)
assertEquals(BwClass.NONE, componentLte.bwClassUl)
}
}
Loading

0 comments on commit 7e9dbd8

Please sign in to comment.