mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-09 11:30:11 +08:00
Implement multi versioned DeviceInfo
, implement DeviceInfo v2 which stores properties as String and hex strings instead of ByteArrays.
This commit is contained in:
parent
d5d0b35806
commit
8b99cc45fb
@ -10,11 +10,17 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.io.core.toByteArray
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import net.mamoe.mirai.utils.DeviceInfoManager.Version.Companion.trans
|
||||
import java.io.File
|
||||
import kotlin.random.Random
|
||||
|
||||
@ -98,14 +104,14 @@ public class DeviceInfo(
|
||||
@JvmStatic
|
||||
@JvmName("from")
|
||||
public fun File.loadAsDeviceInfo(
|
||||
json: Json = Json
|
||||
json: Json = DeviceInfoManager.format
|
||||
): DeviceInfo {
|
||||
if (!this.exists() || this.length() == 0L) {
|
||||
return random().also {
|
||||
this.writeText(json.encodeToString(serializer(), it))
|
||||
this.writeText(DeviceInfoManager.serialize(it, json))
|
||||
}
|
||||
}
|
||||
return json.decodeFromString(serializer(), this.readText())
|
||||
return DeviceInfoManager.deserialize(this.readText(), json)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,6 +251,205 @@ public fun DeviceInfo.generateDeviceInfoData(): ByteArray {
|
||||
)
|
||||
}
|
||||
|
||||
internal object DeviceInfoManager {
|
||||
sealed interface Info {
|
||||
fun toDeviceInfo(): DeviceInfo
|
||||
}
|
||||
|
||||
@Serializable(HexStringSerializer::class)
|
||||
@JvmInline
|
||||
value class HexString(
|
||||
val data: ByteArray
|
||||
)
|
||||
|
||||
object HexStringSerializer : KSerializer<HexString> by String.serializer().map(
|
||||
String.serializer().descriptor.copy("HexString"),
|
||||
deserialize = { HexString(it.hexToBytes()) },
|
||||
serialize = { it.data.toUHexString("").lowercase() }
|
||||
)
|
||||
|
||||
// Note: property names must be kept intact during obfuscation process if applied.
|
||||
@Serializable
|
||||
class Wrapper<T : Info>(
|
||||
@Suppress("unused") val deviceInfoVersion: Int, // used by plain jsonObject
|
||||
val data: T
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class V1(
|
||||
val display: ByteArray,
|
||||
val product: ByteArray,
|
||||
val device: ByteArray,
|
||||
val board: ByteArray,
|
||||
val brand: ByteArray,
|
||||
val model: ByteArray,
|
||||
val bootloader: ByteArray,
|
||||
val fingerprint: ByteArray,
|
||||
val bootId: ByteArray,
|
||||
val procVersion: ByteArray,
|
||||
val baseBand: ByteArray,
|
||||
val version: DeviceInfo.Version,
|
||||
val simInfo: ByteArray,
|
||||
val osType: ByteArray,
|
||||
val macAddress: ByteArray,
|
||||
val wifiBSSID: ByteArray,
|
||||
val wifiSSID: ByteArray,
|
||||
val imsiMd5: ByteArray,
|
||||
val imei: String,
|
||||
val apn: ByteArray
|
||||
) : Info {
|
||||
override fun toDeviceInfo(): DeviceInfo {
|
||||
return DeviceInfo(
|
||||
display = display,
|
||||
product = product,
|
||||
device = device,
|
||||
board = board,
|
||||
brand = brand,
|
||||
model = model,
|
||||
bootloader = bootloader,
|
||||
fingerprint = fingerprint,
|
||||
bootId = bootId,
|
||||
procVersion = procVersion,
|
||||
baseBand = baseBand,
|
||||
version = version,
|
||||
simInfo = simInfo,
|
||||
osType = osType,
|
||||
macAddress = macAddress,
|
||||
wifiBSSID = wifiBSSID,
|
||||
wifiSSID = wifiSSID,
|
||||
imsiMd5 = imsiMd5,
|
||||
imei = imei,
|
||||
apn = apn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
class V2(
|
||||
val display: String,
|
||||
val product: String,
|
||||
val device: String,
|
||||
val board: String,
|
||||
val brand: String,
|
||||
val model: String,
|
||||
val bootloader: String,
|
||||
val fingerprint: String,
|
||||
val bootId: String,
|
||||
val procVersion: String,
|
||||
val baseBand: HexString,
|
||||
val version: Version,
|
||||
val simInfo: String,
|
||||
val osType: String,
|
||||
val macAddress: String,
|
||||
val wifiBSSID: String,
|
||||
val wifiSSID: String,
|
||||
val imsiMd5: HexString,
|
||||
val imei: String,
|
||||
val apn: String
|
||||
) : Info {
|
||||
override fun toDeviceInfo(): DeviceInfo = DeviceInfo(
|
||||
this.display.toByteArray(),
|
||||
this.product.toByteArray(),
|
||||
this.device.toByteArray(),
|
||||
this.board.toByteArray(),
|
||||
this.brand.toByteArray(),
|
||||
this.model.toByteArray(),
|
||||
this.bootloader.toByteArray(),
|
||||
this.fingerprint.toByteArray(),
|
||||
this.bootId.toByteArray(),
|
||||
this.procVersion.toByteArray(),
|
||||
this.baseBand.data,
|
||||
this.version.trans(),
|
||||
this.simInfo.toByteArray(),
|
||||
this.osType.toByteArray(),
|
||||
this.macAddress.toByteArray(),
|
||||
this.wifiBSSID.toByteArray(),
|
||||
this.wifiSSID.toByteArray(),
|
||||
this.imsiMd5.data,
|
||||
this.imei,
|
||||
this.apn.toByteArray()
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class Version(
|
||||
val incremental: String,
|
||||
val release: String,
|
||||
val codename: String,
|
||||
val sdk: Int = 29
|
||||
) {
|
||||
companion object {
|
||||
fun DeviceInfo.Version.trans(): Version {
|
||||
return Version(incremental.decodeToString(), release.decodeToString(), codename.decodeToString(), sdk)
|
||||
}
|
||||
|
||||
fun Version.trans(): DeviceInfo.Version {
|
||||
return DeviceInfo.Version(incremental.toByteArray(), release.toByteArray(), codename.toByteArray(), sdk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun DeviceInfo.toCurrentInfo(): V2 = V2(
|
||||
display.decodeToString(),
|
||||
product.decodeToString(),
|
||||
device.decodeToString(),
|
||||
board.decodeToString(),
|
||||
brand.decodeToString(),
|
||||
model.decodeToString(),
|
||||
bootloader.decodeToString(),
|
||||
fingerprint.decodeToString(),
|
||||
bootId.decodeToString(),
|
||||
procVersion.decodeToString(),
|
||||
HexString(baseBand),
|
||||
version.trans(),
|
||||
simInfo.decodeToString(),
|
||||
osType.decodeToString(),
|
||||
macAddress.decodeToString(),
|
||||
wifiBSSID.decodeToString(),
|
||||
wifiSSID.decodeToString(),
|
||||
HexString(imsiMd5),
|
||||
imei,
|
||||
apn.decodeToString()
|
||||
)
|
||||
|
||||
internal val format = Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, NumberFormatException::class) // in case malformed
|
||||
fun deserialize(string: String, format: Json = this.format): DeviceInfo {
|
||||
val element = format.parseToJsonElement(string)
|
||||
|
||||
return when (val version = element.jsonObject["deviceInfoVersion"]?.jsonPrimitive?.content?.toInt() ?: 1) {
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
1 -> format.decodeFromJsonElement(V1.serializer(), element)
|
||||
/**
|
||||
* @since 2.9
|
||||
*/
|
||||
2 -> format.decodeFromJsonElement(Wrapper.serializer(V2.serializer()), element).data
|
||||
else -> throw IllegalArgumentException("Unsupported deviceInfoVersion: $version")
|
||||
}.toDeviceInfo()
|
||||
}
|
||||
|
||||
fun serialize(info: DeviceInfo, format: Json = this.format): String {
|
||||
return format.encodeToString(
|
||||
Wrapper.serializer(V2.serializer()),
|
||||
Wrapper(2, info.toCurrentInfo())
|
||||
)
|
||||
}
|
||||
|
||||
fun toJsonElement(info: DeviceInfo, format: Json = this.format): JsonElement {
|
||||
return format.encodeToJsonElement(
|
||||
Wrapper.serializer(V2.serializer()),
|
||||
Wrapper(2, info.toCurrentInfo())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
|
||||
*/
|
||||
|
@ -9,9 +9,17 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import net.mamoe.mirai.utils.DeviceInfo.Companion.loadAsDeviceInfo
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class DeviceInfoTest {
|
||||
@Test
|
||||
@ -19,4 +27,97 @@ class DeviceInfoTest {
|
||||
val time = System.currentTimeMillis()
|
||||
assertEquals(DeviceInfo.random(Random(time)), DeviceInfo.random(Random(time)))
|
||||
}
|
||||
|
||||
class HexStringTest {
|
||||
@Test
|
||||
fun `can serialize as String`() {
|
||||
val hexString = DeviceInfoManager.HexString(byteArrayOf(1, 2))
|
||||
val string = Json.encodeToString(DeviceInfoManager.HexString.serializer(), hexString)
|
||||
assertEquals("\"0102\"", string)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can deserialize from String`() {
|
||||
val hex = Json.decodeFromString(DeviceInfoManager.HexString.serializer(), "\"0102\"")
|
||||
assertContentEquals(byteArrayOf(1, 2), hex.data)
|
||||
}
|
||||
}
|
||||
|
||||
@TempDir
|
||||
lateinit var dir: File
|
||||
|
||||
@Test
|
||||
fun `can serialize and deserialize v2`() {
|
||||
val device = DeviceInfo.random()
|
||||
assertEquals(device, DeviceInfoManager.deserialize(DeviceInfoManager.serialize(device)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can write and read v2`() {
|
||||
val device = DeviceInfo.random()
|
||||
val file = dir.resolve("device.json")
|
||||
|
||||
file.writeText(DeviceInfoManager.serialize(device))
|
||||
assertEquals(device, file.loadAsDeviceInfo())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `current version pretty print preview`() {
|
||||
val device = DeviceInfo.random()
|
||||
|
||||
val text = DeviceInfoManager.serialize(device, Json {
|
||||
prettyPrint = true
|
||||
})
|
||||
println(text)
|
||||
/*
|
||||
{
|
||||
"deviceInfoVersion": 2,
|
||||
"data": {
|
||||
"display": "MIRAI.868912.001",
|
||||
"product": "mirai",
|
||||
"device": "mirai",
|
||||
"board": "mirai",
|
||||
"brand": "mamoe",
|
||||
"model": "mirai",
|
||||
"bootloader": "unknown",
|
||||
"fingerprint": "mamoe/mirai/mirai:10/MIRAI.200122.001/6174518:user/release-keys",
|
||||
"bootId": "500E9D6F-1A76-4ED0-20F3-66A5B20C7049",
|
||||
"procVersion": "Linux version 3.0.31-r35YRB94 (android-build@xxx.xxx.xxx.xxx.com)",
|
||||
"baseBand": "",
|
||||
"version": {
|
||||
"incremental": "5891938",
|
||||
"release": "10",
|
||||
"codename": "REL"
|
||||
},
|
||||
"simInfo": "T-Mobile",
|
||||
"osType": "android",
|
||||
"macAddress": "02:00:00:00:00:00",
|
||||
"wifiBSSID": "02:00:00:00:00:00",
|
||||
"wifiSSID": "<unknown ssid>",
|
||||
"imsiMd5": "d1ead821747a3ad3f8f3784fafa3b954",
|
||||
"imei": "155970036849035",
|
||||
"apn": "wifi"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
val element = DeviceInfoManager.toJsonElement(device)
|
||||
assertEquals(2, element.jsonObject["deviceInfoVersion"]!!.jsonPrimitive.content.toInt())
|
||||
|
||||
val imsiMd5 = element.jsonObject["data"]!!.jsonObject["imsiMd5"]!!.jsonPrimitive.content
|
||||
assertEquals(
|
||||
device.imsiMd5.toUHexString("").lowercase(),
|
||||
imsiMd5
|
||||
)
|
||||
assertTrue { imsiMd5 matches Regex("""[a-z0-9]+""") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can read legacy v1`() {
|
||||
val device = DeviceInfo.random()
|
||||
val file = dir.resolve("device.json")
|
||||
|
||||
file.writeText(Json.encodeToString(DeviceInfo.serializer(), device))
|
||||
assertEquals(device, file.loadAsDeviceInfo())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user