mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-09 03:20:11 +08:00
[core] Update login protocol, deprecate DeviceInfo constructor and serializer (#2613)
* [core] update protocol * [core] fix t106 decryption key * [core] handle t543 * [core] revert t106 * [core] revert native test pow data * [core] split build version and apk version name * [core] pass time to t1 instead of time diff * [core] remove unused t106 writer & rename param ip to ipv4 of t1 and t106 * [core] implement qimei for t545 TODO: aes and rsa crypto for nativeMain * [core] fix base64 decode for android in rsa * [core] fix unix timestamp parsing on native and add more tests * [api] DeviceInfo move `androidId` to main constructor and adjust test. * [api] do not request qimei when protocol is neither ANDROID_PHONE nor ANDROID_PAD * [api] implement aes crypto for native target * [api] rsa crypto for multi-platform * [api] crypto test * [api] move freePointer util * [api] openssl api compatibility * [core] add explicit `androidId` param * [core] remove unused `tlvCount` * [core] optimize crypto * [core] move Qimei to network package * [core] move appId to protocol * [core] lazily initialize qimeiLogger * [core] write byte array to BIO mem * [core] move qimei to client, add direct serializer for DeviceInfo * [core] optimize DeviceInfoDelegateSerializer * [core] real user-agent when requesting qimei * [core] use `DeviceInfo.version.release` * [core] remove unused wtlogin packet * [core] do what constructor serializer should do * [core] fix endless cache validation caused by not upgrading device info file. * [core] request qimei before fast-login * [core] tlv order * [core] remove wrong tests and print more detail when deserialize failed. * [core] device info upgrade for native * [core] request qimei after validating cache * [core] DeviceInfo compatibility * [core] DeviceInfo test name * [core] compatibility serializer * [core] disable rsa crypto test on android unit test * [core] move rsa impl to jvmBase * action * import * api dump * api dump * revert wrong api dumps * [core] Deprecate DeviceInfo constructor and serializer, provide `serializeToString` and `deserializeFromString` for replacement * rerun ci * optimize * use serializer directly * optimize test * revert * [core] CacheValidator use `DeviceInfo.serializeToString()` instead of direct serializer * Remove `println` in `DeviceInfoManager` * Add legacy deserialize overload for ABI compatibility * Remove uncompleted docs for DeviceInfo * Suppress DeviceInfo deprecation warnings for internal usages --------- Co-authored-by: Him188 <Him188@mamoe.net>
This commit is contained in:
parent
dafa25d611
commit
60d360baad
@ -5604,8 +5604,9 @@ public final class net/mamoe/mirai/utils/BotConfiguration$MiraiProtocol : java/l
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Companion;
|
||||
public synthetic fun <init> (I[B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[BLkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B)V
|
||||
public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B[B)V
|
||||
public static final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public static final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
@ -5635,26 +5636,26 @@ public final class net/mamoe/mirai/utils/DeviceInfo {
|
||||
public fun hashCode ()I
|
||||
public static final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static final fun write$Self (Lnet/mamoe/mirai/utils/DeviceInfo;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
public static final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo$$serializer : kotlinx/serialization/KSerializer {
|
||||
public static final field INSTANCE Lnet/mamoe/mirai/utils/DeviceInfo$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/utils/DeviceInfo;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo$Companion {
|
||||
public final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static synthetic fun from$default (Lnet/mamoe/mirai/utils/DeviceInfo$Companion;Ljava/io/File;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String;
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
@ -5690,6 +5691,7 @@ public final class net/mamoe/mirai/utils/DeviceInfo$Version$Companion {
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfoKt {
|
||||
public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B
|
||||
public static final synthetic fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests {
|
||||
|
@ -5604,8 +5604,9 @@ public final class net/mamoe/mirai/utils/BotConfiguration$MiraiProtocol : java/l
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Companion;
|
||||
public synthetic fun <init> (I[B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[BLkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B)V
|
||||
public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B[B)V
|
||||
public static final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public static final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
@ -5635,26 +5636,26 @@ public final class net/mamoe/mirai/utils/DeviceInfo {
|
||||
public fun hashCode ()I
|
||||
public static final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static final fun write$Self (Lnet/mamoe/mirai/utils/DeviceInfo;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
public static final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo$$serializer : kotlinx/serialization/KSerializer {
|
||||
public static final field INSTANCE Lnet/mamoe/mirai/utils/DeviceInfo$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/utils/DeviceInfo;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfo$Companion {
|
||||
public final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public static synthetic fun from$default (Lnet/mamoe/mirai/utils/DeviceInfo$Companion;Ljava/io/File;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo;
|
||||
public final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String;
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
@ -5690,6 +5691,7 @@ public final class net/mamoe/mirai/utils/DeviceInfo$Version$Companion {
|
||||
|
||||
public final class net/mamoe/mirai/utils/DeviceInfoKt {
|
||||
public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B
|
||||
public static final synthetic fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests {
|
||||
|
@ -12,20 +12,28 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.serialization.*
|
||||
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.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import net.mamoe.mirai.utils.DeviceInfoManager.Version.Companion.trans
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlin.jvm.JvmStatic
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
import kotlin.random.Random
|
||||
|
||||
public expect class DeviceInfo(
|
||||
internal const val DeviceInfoConstructorDeprecationMessage =
|
||||
"Constructor and serializer of DeviceInfo is deprecated and will be removed in the future." +
|
||||
"This is because new properties can be added and it requires too much effort to maintain public stability." +
|
||||
"Please use DeviceInfo.serializeToString and DeviceInfo.deserializeFromString instead."
|
||||
|
||||
/**
|
||||
* 表示设备信息
|
||||
*/
|
||||
public expect class DeviceInfo
|
||||
@Deprecated(DeviceInfoConstructorDeprecationMessage, level = DeprecationLevel.WARNING)
|
||||
@DeprecatedSinceMirai(warningSince = "2.15") // planned internal
|
||||
public constructor(
|
||||
display: ByteArray,
|
||||
product: ByteArray,
|
||||
device: ByteArray,
|
||||
@ -45,9 +53,9 @@ public expect class DeviceInfo(
|
||||
wifiSSID: ByteArray,
|
||||
imsiMd5: ByteArray,
|
||||
imei: String,
|
||||
apn: ByteArray
|
||||
apn: ByteArray,
|
||||
androidId: ByteArray,
|
||||
) {
|
||||
|
||||
public val display: ByteArray
|
||||
public val product: ByteArray
|
||||
public val device: ByteArray
|
||||
@ -68,15 +76,14 @@ public expect class DeviceInfo(
|
||||
public val imsiMd5: ByteArray
|
||||
public val imei: String
|
||||
public val apn: ByteArray
|
||||
|
||||
public val androidId: ByteArray
|
||||
|
||||
public val ipAddress: ByteArray
|
||||
|
||||
@Transient
|
||||
@MiraiInternalApi
|
||||
public val guid: ByteArray
|
||||
|
||||
|
||||
// @Serializable: use DeviceInfoVersionSerializer in commonMain.
|
||||
public class Version(
|
||||
incremental: ByteArray = "5891938".toByteArray(),
|
||||
@ -98,6 +105,10 @@ public expect class DeviceInfo(
|
||||
* @since 2.9
|
||||
*/
|
||||
override fun hashCode(): Int
|
||||
|
||||
internal companion object {
|
||||
fun serializer(): KSerializer<Version>
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
@ -119,7 +130,27 @@ public expect class DeviceInfo(
|
||||
@JvmStatic
|
||||
public fun random(random: Random): DeviceInfo
|
||||
|
||||
@Deprecated(DeviceInfoConstructorDeprecationMessage, level = DeprecationLevel.WARNING)
|
||||
@DeprecatedSinceMirai(warningSince = "2.15") // planned internal
|
||||
public fun serializer(): KSerializer<DeviceInfo>
|
||||
|
||||
/**
|
||||
* 将此 [DeviceInfo] 序列化为字符串. 序列化的字符串可以在以后通过 [DeviceInfo.deserializeFromString] 反序列化为 [DeviceInfo].
|
||||
*
|
||||
* 序列化的字符串有兼容性保证, 在旧版 mirai 序列化的字符串, 可以在新版 mirai 使用. 但新版 mirai 序列化的字符串不一定能在旧版使用.
|
||||
*
|
||||
* @since 2.15
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun serializeToString(deviceInfo: DeviceInfo): String
|
||||
|
||||
/**
|
||||
* 将通过 [serializeToString] 序列化得到的字符串反序列化为 [DeviceInfo].
|
||||
* 此函数兼容旧版 mirai 序列化的字符串.
|
||||
* @since 2.15
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun deserializeFromString(string: String): DeviceInfo
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +165,74 @@ public expect class DeviceInfo(
|
||||
override fun hashCode(): Int
|
||||
}
|
||||
|
||||
/**
|
||||
* 将此 [DeviceInfo] 序列化为字符串. 序列化的字符串可以在以后通过 [DeviceInfo.deserializeFromString] 反序列化为 [DeviceInfo].
|
||||
*
|
||||
* 序列化的字符串有兼容性保证, 在旧版 mirai 序列化的字符串, 可以在新版 mirai 使用. 但新版 mirai 序列化的字符串不一定能在旧版使用.
|
||||
*
|
||||
* @since 2.15
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public fun DeviceInfo.serializeToString(): String = DeviceInfo.serializeToString(this)
|
||||
|
||||
@Serializable
|
||||
private class DevInfo @OptIn(ExperimentalSerializationApi::class) constructor(
|
||||
@ProtoNumber(1) val bootloader: ByteArray,
|
||||
@ProtoNumber(2) val procVersion: ByteArray,
|
||||
@ProtoNumber(3) val codename: ByteArray,
|
||||
@ProtoNumber(4) val incremental: ByteArray,
|
||||
@ProtoNumber(5) val fingerprint: ByteArray,
|
||||
@ProtoNumber(6) val bootId: ByteArray,
|
||||
@ProtoNumber(7) val androidId: ByteArray,
|
||||
@ProtoNumber(8) val baseBand: ByteArray,
|
||||
@ProtoNumber(9) val innerVersion: ByteArray
|
||||
)
|
||||
|
||||
/**
|
||||
* 不要使用这个 API, 此 API 在未来可能会被删除
|
||||
*/
|
||||
public fun DeviceInfo.generateDeviceInfoData(): ByteArray { // ?? why is this public?
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
return ProtoBuf.encodeToByteArray(
|
||||
DevInfo.serializer(), DevInfo(
|
||||
bootloader,
|
||||
procVersion,
|
||||
version.codename,
|
||||
version.incremental,
|
||||
fingerprint,
|
||||
bootId,
|
||||
androidId,
|
||||
baseBand,
|
||||
version.incremental
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
|
||||
*/
|
||||
internal fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray =
|
||||
(androidId + macAddress).md5()
|
||||
|
||||
|
||||
/*
|
||||
fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo(
|
||||
brand = brand.encodeToString(),
|
||||
model = model.encodeToString(),
|
||||
os = Oidb0x769.OS(
|
||||
version = version.release.encodeToString(),
|
||||
sdk = version.sdk.toString(),
|
||||
kernel = version.kernel
|
||||
)
|
||||
)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see DeviceInfoManager
|
||||
*/
|
||||
internal object DeviceInfoCommonImpl {
|
||||
@Suppress("DEPRECATION")
|
||||
fun randomDeviceInfo(random: Random) = DeviceInfo(
|
||||
display = "MIRAI.${getRandomString(6, '0'..'9', random)}.001".toByteArray(),
|
||||
product = "mirai".toByteArray(),
|
||||
@ -159,7 +257,8 @@ internal object DeviceInfoCommonImpl {
|
||||
wifiSSID = "<unknown ssid>".toByteArray(),
|
||||
imsiMd5 = getRandomByteArray(16, random).md5(),
|
||||
imei = "86${getRandomIntString(12, random)}".let { it + luhn(it) },
|
||||
apn = "wifi".toByteArray()
|
||||
apn = "wifi".toByteArray(),
|
||||
androidId = getRandomByteArray(8, random).toUHexString("").lowercase().encodeToByteArray()
|
||||
)
|
||||
|
||||
/**
|
||||
@ -187,6 +286,8 @@ internal object DeviceInfoCommonImpl {
|
||||
if (deviceInfo === other) return true
|
||||
if (!isSameType(this, other)) return false
|
||||
|
||||
// also remember to add equal compare to JvmDeviceInfoTest.`can read legacy v1`
|
||||
// when adding new field compare here.
|
||||
if (!display.contentEquals(other.display)) return false
|
||||
if (!product.contentEquals(other.product)) return false
|
||||
if (!device.contentEquals(other.device)) return false
|
||||
@ -207,7 +308,10 @@ internal object DeviceInfoCommonImpl {
|
||||
if (!imsiMd5.contentEquals(other.imsiMd5)) return false
|
||||
if (imei != other.imei) return false
|
||||
if (!apn.contentEquals(other.apn)) return false
|
||||
return guid.contentEquals(other.guid)
|
||||
if (!guid.contentEquals(other.guid)) return false
|
||||
if (!androidId.contentEquals(other.androidId)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalApi::class)
|
||||
@ -234,273 +338,7 @@ internal object DeviceInfoCommonImpl {
|
||||
result = 31 * result + imei.hashCode()
|
||||
result = 31 * result + apn.contentHashCode()
|
||||
result = 31 * result + guid.contentHashCode()
|
||||
result = 31 * result + androidId.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private class DevInfo @OptIn(ExperimentalSerializationApi::class) constructor(
|
||||
@ProtoNumber(1) val bootloader: ByteArray,
|
||||
@ProtoNumber(2) val procVersion: ByteArray,
|
||||
@ProtoNumber(3) val codename: ByteArray,
|
||||
@ProtoNumber(4) val incremental: ByteArray,
|
||||
@ProtoNumber(5) val fingerprint: ByteArray,
|
||||
@ProtoNumber(6) val bootId: ByteArray,
|
||||
@ProtoNumber(7) val androidId: ByteArray,
|
||||
@ProtoNumber(8) val baseBand: ByteArray,
|
||||
@ProtoNumber(9) val innerVersion: ByteArray
|
||||
)
|
||||
|
||||
public fun DeviceInfo.generateDeviceInfoData(): ByteArray {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
return ProtoBuf.encodeToByteArray(
|
||||
DevInfo.serializer(), DevInfo(
|
||||
bootloader,
|
||||
procVersion,
|
||||
version.codename,
|
||||
version.incremental,
|
||||
fingerprint,
|
||||
bootId,
|
||||
androidId,
|
||||
baseBand,
|
||||
version.incremental
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
private object DeviceInfoVersionSerializer : KSerializer<DeviceInfo.Version> by SerialData.serializer().map(
|
||||
resultantDescriptor = SerialData.serializer().descriptor,
|
||||
deserialize = {
|
||||
DeviceInfo.Version(incremental, release, codename, sdk)
|
||||
},
|
||||
serialize = {
|
||||
SerialData(incremental, release, codename, sdk)
|
||||
}
|
||||
) {
|
||||
@SerialName("Version")
|
||||
@Serializable
|
||||
private class SerialData(
|
||||
val incremental: ByteArray = "5891938".toByteArray(),
|
||||
val release: ByteArray = "10".toByteArray(),
|
||||
val codename: ByteArray = "REL".toByteArray(),
|
||||
val sdk: Int = 29
|
||||
)
|
||||
}
|
||||
|
||||
@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: @Serializable(DeviceInfoVersionSerializer::class) 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()
|
||||
*/
|
||||
internal fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray =
|
||||
(androidId + macAddress).md5()
|
||||
|
||||
|
||||
/*
|
||||
fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo(
|
||||
brand = brand.encodeToString(),
|
||||
model = model.encodeToString(),
|
||||
os = Oidb0x769.OS(
|
||||
version = version.release.encodeToString(),
|
||||
sdk = version.sdk.toString(),
|
||||
kernel = version.kernel
|
||||
)
|
||||
)
|
||||
*/
|
||||
}
|
319
mirai-core-api/src/commonMain/kotlin/utils/DeviceInfoManager.kt
Normal file
319
mirai-core-api/src/commonMain/kotlin/utils/DeviceInfoManager.kt
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
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 net.mamoe.mirai.utils.DeviceInfoManager.Version.Companion.trans
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
internal object DeviceInfoVersionSerializer : KSerializer<DeviceInfo.Version> by SerialData.serializer().map(
|
||||
resultantDescriptor = SerialData.serializer().descriptor,
|
||||
deserialize = {
|
||||
DeviceInfo.Version(incremental, release, codename, sdk)
|
||||
},
|
||||
serialize = {
|
||||
SerialData(incremental, release, codename, sdk)
|
||||
}
|
||||
) {
|
||||
@SerialName("Version")
|
||||
@Serializable
|
||||
private class SerialData(
|
||||
val incremental: ByteArray = "5891938".toByteArray(),
|
||||
val release: ByteArray = "10".toByteArray(),
|
||||
val codename: ByteArray = "REL".toByteArray(),
|
||||
val sdk: Int = 29
|
||||
)
|
||||
}
|
||||
|
||||
@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: @Serializable(DeviceInfoVersionSerializer::class) 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 {
|
||||
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
||||
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,
|
||||
androidId = getRandomByteArray(8).toUHexString("").lowercase().encodeToByteArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
||||
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(),
|
||||
androidId = getRandomByteArray(8).toUHexString("").lowercase().encodeToByteArray()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
class V3(
|
||||
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,
|
||||
val androidId: String,
|
||||
) : Info {
|
||||
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
||||
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(),
|
||||
this.androidId.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(): V3 = V3(
|
||||
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(),
|
||||
androidId.decodeToString(),
|
||||
)
|
||||
|
||||
internal val format = Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@Deprecated("ABI compatibility for device generator", level = DeprecationLevel.HIDDEN)
|
||||
@JvmName("deserialize")
|
||||
fun deserializeDeprecated(
|
||||
string: String,
|
||||
format: Json = this.format,
|
||||
): DeviceInfo = deserialize(string, format)
|
||||
|
||||
@Throws(IllegalArgumentException::class, NumberFormatException::class) // in case malformed
|
||||
fun deserialize(
|
||||
string: String,
|
||||
format: Json = this.format,
|
||||
onUpgradeVersion: (DeviceInfo) -> Unit = { }
|
||||
): DeviceInfo {
|
||||
val element = format.parseToJsonElement(string)
|
||||
val version = element.jsonObject["deviceInfoVersion"]?.jsonPrimitive?.content?.toInt() ?: 1
|
||||
|
||||
val deviceInfo = when (version) {
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
1 -> format.decodeFromJsonElement(V1.serializer(), element)
|
||||
/**
|
||||
* @since 2.9
|
||||
*/
|
||||
2 -> format.decodeFromJsonElement(Wrapper.serializer(V2.serializer()), element).data
|
||||
/**
|
||||
* @since 2.15
|
||||
*/
|
||||
3 -> format.decodeFromJsonElement(Wrapper.serializer(V3.serializer()), element).data
|
||||
else -> throw IllegalArgumentException("Unsupported deviceInfoVersion: $version")
|
||||
}.toDeviceInfo()
|
||||
|
||||
if (version < 3) onUpgradeVersion(deviceInfo)
|
||||
|
||||
return deviceInfo
|
||||
}
|
||||
|
||||
fun serialize(info: DeviceInfo, format: Json = this.format): String {
|
||||
return format.encodeToString(
|
||||
Wrapper.serializer(V3.serializer()),
|
||||
Wrapper(3, info.toCurrentInfo())
|
||||
)
|
||||
}
|
||||
|
||||
fun toJsonElement(info: DeviceInfo, format: Json = this.format): JsonElement {
|
||||
return format.encodeToJsonElement(
|
||||
Wrapper.serializer(V3.serializer()),
|
||||
Wrapper(3, info.toCurrentInfo())
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
internal class DeviceInfoV1Legacy(
|
||||
val product: ByteArray,
|
||||
val display: 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: DeviceInfoV1LegacyVersion,
|
||||
val simInfo: ByteArray,
|
||||
val osType: ByteArray,
|
||||
val macAddress: ByteArray,
|
||||
val wifiBSSID: ByteArray,
|
||||
val wifiSSID: ByteArray,
|
||||
val imsiMd5: ByteArray,
|
||||
val imei: String,
|
||||
val apn: ByteArray,
|
||||
val androidId: ByteArray? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal class DeviceInfoV1LegacyVersion(
|
||||
val incremental: ByteArray = "5891938".toByteArray(),
|
||||
val release: ByteArray = "10".toByteArray(),
|
||||
val codename: ByteArray = "REL".toByteArray(),
|
||||
val sdk: Int = 29
|
||||
)
|
||||
|
||||
internal object DeviceInfoV1LegacySerializer : KSerializer<DeviceInfo> by DeviceInfoV1Legacy.serializer().map(
|
||||
DeviceInfoV1Legacy.serializer().descriptor.copy("DeviceInfo"),
|
||||
deserialize = {
|
||||
@Suppress("DEPRECATION")
|
||||
DeviceInfo(
|
||||
display,
|
||||
product,
|
||||
device,
|
||||
board,
|
||||
brand,
|
||||
model,
|
||||
bootloader,
|
||||
fingerprint,
|
||||
bootId,
|
||||
procVersion,
|
||||
baseBand,
|
||||
DeviceInfo.Version(version.incremental, version.release, version.codename, version.sdk),
|
||||
simInfo,
|
||||
osType,
|
||||
macAddress,
|
||||
wifiBSSID,
|
||||
wifiSSID,
|
||||
imsiMd5,
|
||||
imei,
|
||||
apn,
|
||||
androidId = display
|
||||
)
|
||||
},
|
||||
serialize = {
|
||||
DeviceInfoV1Legacy(
|
||||
display,
|
||||
product,
|
||||
device,
|
||||
board,
|
||||
brand,
|
||||
model,
|
||||
bootloader,
|
||||
fingerprint,
|
||||
bootId,
|
||||
procVersion,
|
||||
baseBand,
|
||||
DeviceInfoV1LegacyVersion(version.incremental, version.release, version.codename, version.sdk),
|
||||
simInfo,
|
||||
osType,
|
||||
macAddress,
|
||||
wifiBSSID,
|
||||
wifiSSID,
|
||||
imsiMd5,
|
||||
imei,
|
||||
apn
|
||||
)
|
||||
}
|
||||
)
|
@ -42,7 +42,7 @@ class CommonDeviceInfoTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can serialize and deserialize v2`() {
|
||||
fun `can serialize and deserialize v3`() {
|
||||
val device = DeviceInfo.random()
|
||||
assertEquals(device, DeviceInfoManager.deserialize(DeviceInfoManager.serialize(device)))
|
||||
}
|
||||
@ -88,7 +88,7 @@ class CommonDeviceInfoTest {
|
||||
*/
|
||||
|
||||
val element = DeviceInfoManager.toJsonElement(device)
|
||||
assertEquals(2, element.jsonObject["deviceInfoVersion"]!!.jsonPrimitive.content.toInt())
|
||||
assertEquals(3, element.jsonObject["deviceInfoVersion"]!!.jsonPrimitive.content.toInt())
|
||||
|
||||
val imsiMd5 = element.jsonObject["data"]!!.jsonObject["imsiMd5"]!!.jsonPrimitive.content
|
||||
assertEquals(
|
||||
|
@ -1,354 +0,0 @@
|
||||
{
|
||||
"display" : [
|
||||
77,
|
||||
73,
|
||||
82,
|
||||
65,
|
||||
73,
|
||||
46,
|
||||
55,
|
||||
56,
|
||||
49,
|
||||
56,
|
||||
55,
|
||||
57,
|
||||
46,
|
||||
48,
|
||||
48,
|
||||
49
|
||||
],
|
||||
"product" : [
|
||||
109,
|
||||
105,
|
||||
114,
|
||||
97,
|
||||
105
|
||||
],
|
||||
"device" : [
|
||||
109,
|
||||
105,
|
||||
114,
|
||||
97,
|
||||
105
|
||||
],
|
||||
"board" : [
|
||||
109,
|
||||
105,
|
||||
114,
|
||||
97,
|
||||
105
|
||||
],
|
||||
"brand" : [
|
||||
109,
|
||||
97,
|
||||
109,
|
||||
111,
|
||||
101
|
||||
],
|
||||
"model" : [
|
||||
109,
|
||||
105,
|
||||
114,
|
||||
97,
|
||||
105
|
||||
],
|
||||
"bootloader" : [
|
||||
117,
|
||||
110,
|
||||
107,
|
||||
110,
|
||||
111,
|
||||
119,
|
||||
110
|
||||
],
|
||||
"fingerprint" : [
|
||||
109,
|
||||
97,
|
||||
109,
|
||||
111,
|
||||
101,
|
||||
47,
|
||||
109,
|
||||
105,
|
||||
114,
|
||||
97,
|
||||
105,
|
||||
47,
|
||||
109,
|
||||
105,
|
||||
114,
|
||||
97,
|
||||
105,
|
||||
58,
|
||||
49,
|
||||
48,
|
||||
47,
|
||||
77,
|
||||
73,
|
||||
82,
|
||||
65,
|
||||
73,
|
||||
46,
|
||||
50,
|
||||
48,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
50,
|
||||
46,
|
||||
48,
|
||||
48,
|
||||
49,
|
||||
47,
|
||||
53,
|
||||
56,
|
||||
52,
|
||||
54,
|
||||
51,
|
||||
56,
|
||||
49,
|
||||
58,
|
||||
117,
|
||||
115,
|
||||
101,
|
||||
114,
|
||||
47,
|
||||
114,
|
||||
101,
|
||||
108,
|
||||
101,
|
||||
97,
|
||||
115,
|
||||
101,
|
||||
45,
|
||||
107,
|
||||
101,
|
||||
121,
|
||||
115
|
||||
],
|
||||
"bootId" : [
|
||||
56,
|
||||
53,
|
||||
57,
|
||||
67,
|
||||
67,
|
||||
54,
|
||||
52,
|
||||
65,
|
||||
45,
|
||||
57,
|
||||
65,
|
||||
69,
|
||||
57,
|
||||
45,
|
||||
56,
|
||||
48,
|
||||
67,
|
||||
51,
|
||||
45,
|
||||
66,
|
||||
51,
|
||||
68,
|
||||
52,
|
||||
45,
|
||||
51,
|
||||
49,
|
||||
70,
|
||||
49,
|
||||
49,
|
||||
67,
|
||||
56,
|
||||
67,
|
||||
54,
|
||||
66,
|
||||
56,
|
||||
52
|
||||
],
|
||||
"procVersion" : [
|
||||
76,
|
||||
105,
|
||||
110,
|
||||
117,
|
||||
120,
|
||||
32,
|
||||
118,
|
||||
101,
|
||||
114,
|
||||
115,
|
||||
105,
|
||||
111,
|
||||
110,
|
||||
32,
|
||||
51,
|
||||
46,
|
||||
48,
|
||||
46,
|
||||
51,
|
||||
49,
|
||||
45,
|
||||
48,
|
||||
84,
|
||||
102,
|
||||
51,
|
||||
68,
|
||||
50,
|
||||
53,
|
||||
67,
|
||||
32,
|
||||
40,
|
||||
97,
|
||||
110,
|
||||
100,
|
||||
114,
|
||||
111,
|
||||
105,
|
||||
100,
|
||||
45,
|
||||
98,
|
||||
117,
|
||||
105,
|
||||
108,
|
||||
100,
|
||||
64,
|
||||
120,
|
||||
120,
|
||||
120,
|
||||
46,
|
||||
120,
|
||||
120,
|
||||
120,
|
||||
46,
|
||||
120,
|
||||
120,
|
||||
120,
|
||||
46,
|
||||
120,
|
||||
120,
|
||||
120,
|
||||
46,
|
||||
99,
|
||||
111,
|
||||
109,
|
||||
41
|
||||
],
|
||||
"baseBand" : [
|
||||
],
|
||||
"version" : {
|
||||
"incremental" : [
|
||||
53,
|
||||
56,
|
||||
57,
|
||||
49,
|
||||
57,
|
||||
51,
|
||||
56
|
||||
],
|
||||
"release" : [
|
||||
49,
|
||||
48
|
||||
],
|
||||
"codename" : [
|
||||
82,
|
||||
69,
|
||||
76
|
||||
]
|
||||
},
|
||||
"simInfo" : [
|
||||
84,
|
||||
45,
|
||||
77,
|
||||
111,
|
||||
98,
|
||||
105,
|
||||
108,
|
||||
101
|
||||
],
|
||||
"osType" : [
|
||||
97,
|
||||
110,
|
||||
100,
|
||||
114,
|
||||
111,
|
||||
105,
|
||||
100
|
||||
],
|
||||
"macAddress" : [
|
||||
48,
|
||||
50,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48
|
||||
],
|
||||
"wifiBSSID" : [
|
||||
48,
|
||||
50,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48,
|
||||
58,
|
||||
48,
|
||||
48
|
||||
],
|
||||
"wifiSSID" : [
|
||||
60,
|
||||
117,
|
||||
110,
|
||||
107,
|
||||
110,
|
||||
111,
|
||||
119,
|
||||
110,
|
||||
32,
|
||||
115,
|
||||
115,
|
||||
105,
|
||||
100,
|
||||
62
|
||||
],
|
||||
"imsiMd5" : [
|
||||
69,
|
||||
45,
|
||||
31,
|
||||
44,
|
||||
85,
|
||||
103,
|
||||
-19,
|
||||
88,
|
||||
21,
|
||||
-47,
|
||||
94,
|
||||
-128,
|
||||
38,
|
||||
-45,
|
||||
9,
|
||||
50
|
||||
],
|
||||
"imei" : "101633900250935",
|
||||
"apn" : [
|
||||
119,
|
||||
105,
|
||||
102,
|
||||
105
|
||||
]
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
@ -9,14 +9,18 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import kotlin.random.Random
|
||||
|
||||
@Serializable
|
||||
public actual class DeviceInfo actual constructor(
|
||||
@Serializable(DeviceInfoV1LegacySerializer::class)
|
||||
public actual class DeviceInfo
|
||||
@Deprecated(DeviceInfoConstructorDeprecationMessage, level = DeprecationLevel.WARNING)
|
||||
@DeprecatedSinceMirai(warningSince = "2.15") // planned internal
|
||||
public actual constructor(
|
||||
public actual val display: ByteArray,
|
||||
public actual val product: ByteArray,
|
||||
public actual val device: ByteArray,
|
||||
@ -36,9 +40,48 @@ public actual class DeviceInfo actual constructor(
|
||||
public actual val wifiSSID: ByteArray,
|
||||
public actual val imsiMd5: ByteArray,
|
||||
public actual val imei: String,
|
||||
public actual val apn: ByteArray
|
||||
public actual val apn: ByteArray,
|
||||
public actual val androidId: ByteArray,
|
||||
) {
|
||||
public actual val androidId: ByteArray get() = display
|
||||
@Deprecated(
|
||||
DeviceInfoConstructorDeprecationMessage,
|
||||
replaceWith = ReplaceWith(
|
||||
"net.mamoe.mirai.utils.DeviceInfo(display, product, device, board, brand, model, " +
|
||||
"bootloader, fingerprint, bootId, procVersion, baseBand, version, simInfo, osType, " +
|
||||
"macAddress, wifiBSSID, wifiSSID, imsiMd5, imei, apn, androidId)"
|
||||
),
|
||||
level = DeprecationLevel.WARNING
|
||||
)
|
||||
@DeprecatedSinceMirai(warningSince = "2.15")
|
||||
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
||||
public constructor(
|
||||
display: ByteArray,
|
||||
product: ByteArray,
|
||||
device: ByteArray,
|
||||
board: ByteArray,
|
||||
brand: ByteArray,
|
||||
model: ByteArray,
|
||||
bootloader: ByteArray,
|
||||
fingerprint: ByteArray,
|
||||
bootId: ByteArray,
|
||||
procVersion: ByteArray,
|
||||
baseBand: ByteArray,
|
||||
version: Version,
|
||||
simInfo: ByteArray,
|
||||
osType: ByteArray,
|
||||
macAddress: ByteArray,
|
||||
wifiBSSID: ByteArray,
|
||||
wifiSSID: ByteArray,
|
||||
imsiMd5: ByteArray,
|
||||
imei: String,
|
||||
apn: ByteArray
|
||||
) : this(
|
||||
display, product, device, board, brand, model, bootloader,
|
||||
fingerprint, bootId, procVersion, baseBand, version, simInfo,
|
||||
osType, macAddress, wifiBSSID, wifiSSID, imsiMd5, imei, apn,
|
||||
androidId = display
|
||||
)
|
||||
|
||||
public actual val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123)
|
||||
|
||||
init {
|
||||
@ -100,7 +143,9 @@ public actual class DeviceInfo actual constructor(
|
||||
this.writeText(DeviceInfoManager.serialize(it, json))
|
||||
}
|
||||
}
|
||||
return DeviceInfoManager.deserialize(this.readText(), json)
|
||||
return DeviceInfoManager.deserialize(this.readText(), json) { upg ->
|
||||
this.writeText(DeviceInfoManager.serialize(upg, json))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,6 +165,24 @@ public actual class DeviceInfo actual constructor(
|
||||
public actual fun random(random: Random): DeviceInfo {
|
||||
return DeviceInfoCommonImpl.randomDeviceInfo(random)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将此 [DeviceInfo] 序列化为字符串. 序列化的字符串可以在以后通过 [DeviceInfo.deserializeFromString] 反序列化为 [DeviceInfo].
|
||||
*
|
||||
* 序列化的字符串有兼容性保证, 在旧版 mirai 序列化的字符串, 可以在新版 mirai 使用. 但新版 mirai 序列化的字符串不一定能在旧版使用.
|
||||
*
|
||||
* @since 2.15
|
||||
*/
|
||||
@JvmStatic
|
||||
public actual fun serializeToString(deviceInfo: DeviceInfo): String = DeviceInfoManager.serialize(deviceInfo)
|
||||
|
||||
/**
|
||||
* 将通过 [serializeToString] 序列化得到的字符串反序列化为 [DeviceInfo].
|
||||
* 此函数兼容旧版 mirai 序列化的字符串.
|
||||
* @since 2.15
|
||||
*/
|
||||
@JvmStatic
|
||||
public actual fun deserializeFromString(string: String): DeviceInfo = DeviceInfoManager.deserialize(string)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,4 +200,8 @@ public actual class DeviceInfo actual constructor(
|
||||
actual override fun hashCode(): Int {
|
||||
return DeviceInfoCommonImpl.hashCodeImpl(this)
|
||||
}
|
||||
|
||||
@Suppress("ClassName")
|
||||
@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
public object `$serializer` : KSerializer<DeviceInfo> by DeviceInfoV1LegacySerializer
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
@ -11,10 +11,12 @@ package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.utils.DeviceInfo.Companion.loadAsDeviceInfo
|
||||
import net.mamoe.mirai.utils.DeviceInfoManager.Version.Companion.trans
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class JvmDeviceInfoTest {
|
||||
|
||||
@ -22,7 +24,7 @@ class JvmDeviceInfoTest {
|
||||
lateinit var dir: File
|
||||
|
||||
@Test
|
||||
fun `can write and read v2`() {
|
||||
fun `can write and read`() {
|
||||
val device = DeviceInfo.random()
|
||||
val file = dir.resolve("device.json")
|
||||
|
||||
@ -31,11 +33,161 @@ class JvmDeviceInfoTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can read legacy v1`() {
|
||||
fun `can write read legacy v1`() {
|
||||
val device = DeviceInfo.random()
|
||||
val file = dir.resolve("device.json")
|
||||
|
||||
file.writeText(Json.encodeToString(DeviceInfo.serializer(), device))
|
||||
assertEquals(device, file.loadAsDeviceInfo())
|
||||
val encoded = Json.encodeToString(
|
||||
DeviceInfoManager.V1.serializer(), DeviceInfoManager.V1(
|
||||
display = device.display,
|
||||
product = device.product,
|
||||
device = device.device,
|
||||
board = device.board,
|
||||
brand = device.brand,
|
||||
model = device.model,
|
||||
bootloader = device.bootloader,
|
||||
fingerprint = device.fingerprint,
|
||||
bootId = device.bootId,
|
||||
procVersion = device.procVersion,
|
||||
baseBand = device.baseBand,
|
||||
version = device.version,
|
||||
simInfo = device.simInfo,
|
||||
osType = device.osType,
|
||||
macAddress = device.macAddress,
|
||||
wifiBSSID = device.wifiBSSID,
|
||||
wifiSSID = device.wifiSSID,
|
||||
imsiMd5 = device.imsiMd5,
|
||||
imei = device.imei,
|
||||
apn = device.apn,
|
||||
)
|
||||
)
|
||||
|
||||
file.writeText(encoded)
|
||||
val fileDeviceInfo = file.loadAsDeviceInfo()
|
||||
|
||||
assertTrue { isSameType(device, fileDeviceInfo) }
|
||||
|
||||
assertTrue { device.display.contentEquals(fileDeviceInfo.display) }
|
||||
assertTrue { device.product.contentEquals(fileDeviceInfo.product) }
|
||||
assertTrue { device.device.contentEquals(fileDeviceInfo.device) }
|
||||
assertTrue { device.board.contentEquals(fileDeviceInfo.board) }
|
||||
assertTrue { device.brand.contentEquals(fileDeviceInfo.brand) }
|
||||
assertTrue { device.model.contentEquals(fileDeviceInfo.model) }
|
||||
assertTrue { device.bootloader.contentEquals(fileDeviceInfo.bootloader) }
|
||||
assertTrue { device.fingerprint.contentEquals(fileDeviceInfo.fingerprint) }
|
||||
assertTrue { device.bootId.contentEquals(fileDeviceInfo.bootId) }
|
||||
assertTrue { device.procVersion.contentEquals(fileDeviceInfo.procVersion) }
|
||||
assertTrue { device.baseBand.contentEquals(fileDeviceInfo.baseBand) }
|
||||
assertEquals(device.version, fileDeviceInfo.version)
|
||||
assertTrue { device.simInfo.contentEquals(fileDeviceInfo.simInfo) }
|
||||
assertTrue { device.osType.contentEquals(fileDeviceInfo.osType) }
|
||||
assertTrue { device.macAddress.contentEquals(fileDeviceInfo.macAddress) }
|
||||
assertTrue { device.wifiBSSID.contentEquals(fileDeviceInfo.wifiBSSID) }
|
||||
assertTrue { device.wifiSSID.contentEquals(fileDeviceInfo.wifiSSID) }
|
||||
assertTrue { device.imsiMd5.contentEquals(fileDeviceInfo.imsiMd5) }
|
||||
assertEquals(device.imei, fileDeviceInfo.imei)
|
||||
assertTrue { device.apn.contentEquals(fileDeviceInfo.apn) }
|
||||
assertTrue { device.androidId.size == fileDeviceInfo.androidId.size }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can write and read legacy v2`() {
|
||||
val device = DeviceInfo.random()
|
||||
val file = dir.resolve("device.json")
|
||||
|
||||
val encoded = Json.encodeToString(
|
||||
DeviceInfoManager.Wrapper.serializer(DeviceInfoManager.V2.serializer()),
|
||||
DeviceInfoManager.Wrapper(
|
||||
2, DeviceInfoManager.V2(
|
||||
display = device.display.decodeToString(),
|
||||
product = device.product.decodeToString(),
|
||||
device = device.device.decodeToString(),
|
||||
board = device.board.decodeToString(),
|
||||
brand = device.brand.decodeToString(),
|
||||
model = device.model.decodeToString(),
|
||||
bootloader = device.bootloader.decodeToString(),
|
||||
fingerprint = device.fingerprint.decodeToString(),
|
||||
bootId = device.bootId.decodeToString(),
|
||||
procVersion = device.procVersion.decodeToString(),
|
||||
baseBand = DeviceInfoManager.HexString(device.baseBand),
|
||||
version = device.version.trans(),
|
||||
simInfo = device.simInfo.decodeToString(),
|
||||
osType = device.osType.decodeToString(),
|
||||
macAddress = device.macAddress.decodeToString(),
|
||||
wifiBSSID = device.wifiBSSID.decodeToString(),
|
||||
wifiSSID = device.wifiSSID.decodeToString(),
|
||||
imsiMd5 = DeviceInfoManager.HexString(device.imsiMd5),
|
||||
imei = device.imei,
|
||||
apn = device.apn.decodeToString(),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
file.writeText(encoded)
|
||||
val fileDeviceInfo = file.loadAsDeviceInfo()
|
||||
|
||||
assertTrue { isSameType(device, fileDeviceInfo) }
|
||||
|
||||
assertTrue { device.display.contentEquals(fileDeviceInfo.display) }
|
||||
assertTrue { device.product.contentEquals(fileDeviceInfo.product) }
|
||||
assertTrue { device.device.contentEquals(fileDeviceInfo.device) }
|
||||
assertTrue { device.board.contentEquals(fileDeviceInfo.board) }
|
||||
assertTrue { device.brand.contentEquals(fileDeviceInfo.brand) }
|
||||
assertTrue { device.model.contentEquals(fileDeviceInfo.model) }
|
||||
assertTrue { device.bootloader.contentEquals(fileDeviceInfo.bootloader) }
|
||||
assertTrue { device.fingerprint.contentEquals(fileDeviceInfo.fingerprint) }
|
||||
assertTrue { device.bootId.contentEquals(fileDeviceInfo.bootId) }
|
||||
assertTrue { device.procVersion.contentEquals(fileDeviceInfo.procVersion) }
|
||||
assertTrue { device.baseBand.contentEquals(fileDeviceInfo.baseBand) }
|
||||
assertEquals(device.version, fileDeviceInfo.version)
|
||||
assertTrue { device.simInfo.contentEquals(fileDeviceInfo.simInfo) }
|
||||
assertTrue { device.osType.contentEquals(fileDeviceInfo.osType) }
|
||||
assertTrue { device.macAddress.contentEquals(fileDeviceInfo.macAddress) }
|
||||
assertTrue { device.wifiBSSID.contentEquals(fileDeviceInfo.wifiBSSID) }
|
||||
assertTrue { device.wifiSSID.contentEquals(fileDeviceInfo.wifiSSID) }
|
||||
assertTrue { device.imsiMd5.contentEquals(fileDeviceInfo.imsiMd5) }
|
||||
assertEquals(device.imei, fileDeviceInfo.imei)
|
||||
assertTrue { device.apn.contentEquals(fileDeviceInfo.apn) }
|
||||
assertTrue { device.androidId.size == fileDeviceInfo.androidId.size }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can write and read v3`() {
|
||||
val device = DeviceInfo.random()
|
||||
val file = dir.resolve("device.json")
|
||||
|
||||
val encoded = Json.encodeToString(
|
||||
DeviceInfoManager.Wrapper.serializer(DeviceInfoManager.V3.serializer()),
|
||||
DeviceInfoManager.Wrapper(
|
||||
3, DeviceInfoManager.V3(
|
||||
display = device.display.decodeToString(),
|
||||
product = device.product.decodeToString(),
|
||||
device = device.device.decodeToString(),
|
||||
board = device.board.decodeToString(),
|
||||
brand = device.brand.decodeToString(),
|
||||
model = device.model.decodeToString(),
|
||||
bootloader = device.bootloader.decodeToString(),
|
||||
fingerprint = device.fingerprint.decodeToString(),
|
||||
bootId = device.bootId.decodeToString(),
|
||||
procVersion = device.procVersion.decodeToString(),
|
||||
baseBand = DeviceInfoManager.HexString(device.baseBand),
|
||||
version = device.version.trans(),
|
||||
simInfo = device.simInfo.decodeToString(),
|
||||
osType = device.osType.decodeToString(),
|
||||
macAddress = device.macAddress.decodeToString(),
|
||||
wifiBSSID = device.wifiBSSID.decodeToString(),
|
||||
wifiSSID = device.wifiSSID.decodeToString(),
|
||||
imsiMd5 = DeviceInfoManager.HexString(device.imsiMd5),
|
||||
imei = device.imei,
|
||||
apn = device.apn.decodeToString(),
|
||||
androidId = device.androidId.decodeToString()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
file.writeText(encoded)
|
||||
val fileDeviceInfo = file.loadAsDeviceInfo()
|
||||
|
||||
assertEquals(device, fileDeviceInfo)
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlin.test.Test
|
||||
|
||||
class JvmDeviceInfoTestJvm {
|
||||
@Test
|
||||
fun `can deserialize legacy versions before 2_9_0`() {
|
||||
// resources not available on android
|
||||
|
||||
DeviceInfoManager.deserialize(
|
||||
this::class.java.classLoader.getResourceAsStream("device/legacy-device-info-1.json")!!
|
||||
.use { it.readBytes().decodeToString() })
|
||||
}
|
||||
}
|
@ -41,7 +41,10 @@ public actual abstract class AbstractBotConfiguration { // open for Java
|
||||
if (!file.exists()) {
|
||||
file.writeText(DeviceInfoManager.serialize(DeviceInfo.random(), BotConfiguration.json))
|
||||
}
|
||||
DeviceInfoManager.deserialize(file.readText(), BotConfiguration.json)
|
||||
DeviceInfoManager.deserialize(file.readText(), BotConfiguration.json) {
|
||||
file.writeText(DeviceInfoManager.serialize(it, BotConfiguration.json))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
@ -13,8 +13,11 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.random.Random
|
||||
|
||||
@Serializable
|
||||
public actual class DeviceInfo actual constructor(
|
||||
@Serializable(DeviceInfoV1LegacySerializer::class)
|
||||
public actual class DeviceInfo
|
||||
@Deprecated(DeviceInfoConstructorDeprecationMessage, level = DeprecationLevel.WARNING)
|
||||
@DeprecatedSinceMirai(warningSince = "2.15") // planned internal
|
||||
public actual constructor(
|
||||
public actual val display: ByteArray,
|
||||
public actual val product: ByteArray,
|
||||
public actual val device: ByteArray,
|
||||
@ -34,9 +37,9 @@ public actual class DeviceInfo actual constructor(
|
||||
public actual val wifiSSID: ByteArray,
|
||||
public actual val imsiMd5: ByteArray,
|
||||
public actual val imei: String,
|
||||
public actual val apn: ByteArray
|
||||
public actual val apn: ByteArray,
|
||||
public actual val androidId: ByteArray,
|
||||
) {
|
||||
public actual val androidId: ByteArray get() = display
|
||||
public actual val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123)
|
||||
|
||||
init {
|
||||
@ -99,6 +102,22 @@ public actual class DeviceInfo actual constructor(
|
||||
public actual fun random(random: Random): DeviceInfo {
|
||||
return DeviceInfoCommonImpl.randomDeviceInfo(random)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将此 [DeviceInfo] 序列化为字符串. 序列化的字符串可以在以后通过 [DeviceInfo.deserializeFromString] 反序列化为 [DeviceInfo].
|
||||
*
|
||||
* 序列化的字符串有兼容性保证, 在旧版 mirai 序列化的字符串, 可以在新版 mirai 使用. 但新版 mirai 序列化的字符串不一定能在旧版使用.
|
||||
*
|
||||
* @since 2.15
|
||||
*/
|
||||
public actual fun serializeToString(deviceInfo: DeviceInfo): String = DeviceInfoManager.serialize(deviceInfo)
|
||||
|
||||
/**
|
||||
* 将通过 [serializeToString] 序列化得到的字符串反序列化为 [DeviceInfo].
|
||||
* 此函数兼容旧版 mirai 序列化的字符串.
|
||||
* @since 2.15
|
||||
*/
|
||||
public actual fun deserializeFromString(string: String): DeviceInfo = DeviceInfoManager.deserialize(string)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,11 @@ public expect fun currentTimeMillis(): Long
|
||||
*/
|
||||
public fun currentTimeSeconds(): Long = currentTimeMillis() / 1000
|
||||
|
||||
public expect fun currentTimeFormatted(format: String? = null): String
|
||||
public fun currentTimeFormatted(format: String? = null): String {
|
||||
return formatTime(currentTimeMillis(), format)
|
||||
}
|
||||
|
||||
public expect fun formatTime(epochTimeMillis: Long, format: String?): String
|
||||
|
||||
|
||||
// 临时使用, 待 Kotlin Duration 稳定后使用 Duration.
|
||||
|
@ -19,10 +19,10 @@ private val timeFormat: SimpleDateFormat by threadLocal {
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||
}
|
||||
|
||||
public actual fun currentTimeFormatted(format: String?): String {
|
||||
public actual fun formatTime(epochTimeMillis: Long, format: String?): String {
|
||||
return if (format == null) {
|
||||
timeFormat.format(Date())
|
||||
timeFormat.format(Date(epochTimeMillis))
|
||||
} else {
|
||||
SimpleDateFormat(format, Locale.getDefault()).format(Date())
|
||||
SimpleDateFormat(format, Locale.getDefault()).format(Date(epochTimeMillis))
|
||||
}
|
||||
}
|
@ -32,18 +32,26 @@ public actual fun currentTimeMillis(): Long {
|
||||
|
||||
private val timeLock = ReentrantLock()
|
||||
|
||||
@OptIn(UnsafeNumber::class)
|
||||
public actual fun currentTimeFormatted(format: String?): String = timeLock.withLock {
|
||||
public actual fun formatTime(epochTimeMillis: Long, format: String?): String = timeLock.withLock {
|
||||
val strftimeFormat = format
|
||||
?.replace("yyyy", "%Y")
|
||||
?.replace("MM", "%m")
|
||||
?.replace("dd", "%d")
|
||||
?.replace("HH", "%H")
|
||||
?.replace("mm", "%M")
|
||||
?.replace("ss", "%S")
|
||||
?: "%Y-%m-%d %H:%M:%S"
|
||||
memScoped {
|
||||
val timeT = alloc<time_tVar>()
|
||||
time(timeT.ptr)
|
||||
timeT.value = epochTimeMillis / 1000
|
||||
|
||||
// http://www.cplusplus.com/reference/clibrary/ctime/localtime/
|
||||
// tm returns a static pointer which doesn't need to free
|
||||
val tm = localtime(timeT.ptr) // localtime is not thread-safe
|
||||
|
||||
val bb = allocArray<ByteVar>(40)
|
||||
strftime(bb, 40, "%Y-%m-%d %H:%M:%S", tm);
|
||||
|
||||
strftime(bb, 40, strftimeFormat, tm);
|
||||
|
||||
bb.toKString()
|
||||
}
|
||||
|
@ -10,6 +10,8 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal class TimeUtilsTest {
|
||||
@ -23,6 +25,59 @@ internal class TimeUtilsTest {
|
||||
@Test
|
||||
fun `can get currentTimeFormatted`() {
|
||||
// 2022-28-26 18:28:28
|
||||
assertTrue { currentTimeFormatted().matches(Regex("""\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}""")) }
|
||||
assertTrue { currentTimeFormatted().matches(Regex("""^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$""")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can parse explicit timestamp`() {
|
||||
val epochMilli = 1681174590123 // 2023-04-11 00:56:30 GMT
|
||||
val regex = Regex("""^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$""")
|
||||
|
||||
val formatted = regex.find(formatTime(epochMilli, null))
|
||||
assertNotNull(formatted)
|
||||
|
||||
formatted.groupValues.run {
|
||||
assertEquals(get(1), "2023")
|
||||
assertEquals(get(2), "04")
|
||||
assertTrue { get(3) == "11" || get(3) == "10" }
|
||||
assertTrue { get(4).toInt() in 0..23 }
|
||||
assertEquals(get(5), "56")
|
||||
assertEquals(get(6), "30")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can format with custom formatter`() {
|
||||
fun formatTimeAndPrint(formatter: String?): String {
|
||||
return formatTime(currentTimeMillis(), formatter).also { println("custom formatted time: $it") }
|
||||
}
|
||||
|
||||
assertTrue {
|
||||
formatTimeAndPrint("MmMm").matches(Regex("""^MmMm$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint("MM-mm").matches(Regex("""^\d{2}-\d{2}$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint("yyyyMMddHHmmss").matches(Regex("""^\d{14}$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint("yyyyMMddHHmmSS").matches(Regex("""^\d{12}SS$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint(null).matches(Regex("""^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint("yyyy-MM-dd 114514").matches(Regex("""^\d{4}-\d{2}-\d{2} 114514$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint("yyyyMM-114 514--mm-SS").matches(Regex("""^\d{4}\d{2}-114 514--\d{2}-SS$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint("yyyy-MM-dd HH-mm-ss").matches(Regex("""^\d{4}-\d{2}-\d{2} \d{2}-\d{2}-\d{2}$"""))
|
||||
}
|
||||
assertTrue {
|
||||
formatTimeAndPrint("yyyy/MM\\dd HH:mm-ss").matches(Regex("""^\d{4}/\d{2}\\\d{2} \d{2}:\d{2}-\d{2}$"""))
|
||||
}
|
||||
}
|
||||
}
|
@ -102,7 +102,7 @@ internal open class QQAndroidClient(
|
||||
|
||||
|
||||
val apkVersionName: ByteArray get() = protocol.ver.toByteArray() //"8.4.18".toByteArray()
|
||||
val buildVer: String get() = "8.4.18.4810" // 8.2.0.1296 // 8.4.8.4810 // 8.2.7.4410
|
||||
val buildVer: String get() = protocol.buildVer // 8.2.0.1296 // 8.4.8.4810 // 8.2.7.4410
|
||||
|
||||
|
||||
private val sequenceId: AtomicInt = atomic(getRandomUnsignedInt())
|
||||
@ -166,7 +166,15 @@ internal open class QQAndroidClient(
|
||||
var reserveUinInfo: ReserveUinInfo? = null
|
||||
var t402: ByteArray? = null
|
||||
lateinit var t104: ByteArray
|
||||
internal val t104Initialized get() = ::t104.isInitialized
|
||||
var t543: ByteArray? = null
|
||||
var t547: ByteArray? = null
|
||||
|
||||
/**
|
||||
* t545
|
||||
*/
|
||||
var qimei16: String? = null
|
||||
var qimei36: String? = null
|
||||
}
|
||||
|
||||
internal val QQAndroidClient.apkId: ByteArray get() = protocol.apkId.toByteArray()
|
||||
|
@ -10,7 +10,6 @@
|
||||
package net.mamoe.mirai.internal.network.components
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.internal.network.ProtoBufForCache
|
||||
import net.mamoe.mirai.internal.network.component.ComponentKey
|
||||
import net.mamoe.mirai.internal.utils.MiraiProtocolInternal
|
||||
import net.mamoe.mirai.internal.utils.io.writeShortLVString
|
||||
@ -61,7 +60,7 @@ internal class CacheValidatorImpl(
|
||||
val device = ssoProcessorContext.device
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER")
|
||||
writeFully(ProtoBufForCache.encodeToByteArray(DeviceInfo.serializer(), device))
|
||||
writeFully(device.serializeToString().encodeToByteArray())
|
||||
}.let { pkg ->
|
||||
try {
|
||||
pkg.readBytes()
|
||||
|
@ -32,6 +32,8 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.UrlDeviceVerificat
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.*
|
||||
import net.mamoe.mirai.internal.network.qimei.requestQimei
|
||||
import net.mamoe.mirai.internal.utils.subLogger
|
||||
import net.mamoe.mirai.network.*
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||
@ -140,6 +142,8 @@ internal open class SsoProcessorImpl(
|
||||
ssoContext.bot.components[BotClientHolder].client = value
|
||||
}
|
||||
|
||||
private val qimeiLogger by lazy { ssoContext.bot.network.logger.subLogger("QimeiApi") }
|
||||
|
||||
override val ssoSession: SsoSession get() = client
|
||||
private val components get() = ssoContext.bot.components
|
||||
|
||||
@ -199,6 +203,12 @@ internal open class SsoProcessorImpl(
|
||||
|
||||
components[BdhSessionSyncer].loadServerListFromCache()
|
||||
|
||||
try {
|
||||
ssoContext.bot.requestQimei(qimeiLogger)
|
||||
} catch (exception: Throwable) {
|
||||
qimeiLogger.warning("Cannot get qimei from server.", exception)
|
||||
}
|
||||
|
||||
// try fast login
|
||||
if (client.wLoginSigInfoInitialized) {
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
||||
|
@ -18,7 +18,10 @@ import net.mamoe.mirai.internal.utils.GuidSource
|
||||
import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag
|
||||
import net.mamoe.mirai.internal.utils.NetworkType
|
||||
import net.mamoe.mirai.internal.utils.guidFlag
|
||||
import net.mamoe.mirai.internal.utils.io.*
|
||||
import net.mamoe.mirai.internal.utils.io.encryptAndWrite
|
||||
import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray
|
||||
import net.mamoe.mirai.internal.utils.io.writeShortLVByteArrayLimitedLength
|
||||
import net.mamoe.mirai.internal.utils.io.writeShortLVString
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlin.random.Random
|
||||
@ -49,15 +52,15 @@ internal fun TlvMap.smartToString(leadingLineBreak: Boolean = true, sorted: Bool
|
||||
@JvmInline
|
||||
internal value class Tlv(val value: ByteArray)
|
||||
|
||||
internal fun TlvMapWriter.t1(uin: Long, ip: ByteArray) {
|
||||
require(ip.size == 4) { "ip.size must == 4" }
|
||||
internal fun TlvMapWriter.t1(uin: Long, timeSeconds: Int, ipv4: ByteArray) {
|
||||
require(ipv4.size == 4) { "ip.size must == 4" }
|
||||
|
||||
tlv(0x01) {
|
||||
writeShort(1) // _ip_ver
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(uin.toInt())
|
||||
writeInt(currentTimeSeconds().toInt())
|
||||
writeFully(ip)
|
||||
writeInt(timeSeconds)
|
||||
writeFully(ipv4)
|
||||
writeShort(0)
|
||||
}
|
||||
}
|
||||
@ -192,6 +195,7 @@ internal fun TlvMapWriter.t106(
|
||||
client.subAppId /* maybe 1*/,
|
||||
client.appClientVersion,
|
||||
client.uin,
|
||||
client.device.ipAddress,
|
||||
true,
|
||||
passwordMd5,
|
||||
0,
|
||||
@ -220,6 +224,7 @@ internal fun TlvMapWriter.t106(
|
||||
subAppId: Long,
|
||||
appClientVersion: Int = 0,
|
||||
uin: Long,
|
||||
ipv4: ByteArray,
|
||||
isSavePassword: Boolean = true,
|
||||
passwordMd5: ByteArray,
|
||||
salt: Long,
|
||||
@ -233,6 +238,7 @@ internal fun TlvMapWriter.t106(
|
||||
passwordMd5.requireSize(16)
|
||||
tgtgtKey.requireSize(16)
|
||||
guid?.requireSize(16)
|
||||
ipv4.requireSize(4)
|
||||
|
||||
tlv(0x106) {
|
||||
encryptAndWrite(
|
||||
@ -252,7 +258,7 @@ internal fun TlvMapWriter.t106(
|
||||
}
|
||||
|
||||
writeInt(currentTimeSeconds().toInt())
|
||||
writeFully(ByteArray(4)) // ip // no need to write actual ip
|
||||
writeFully(ipv4) //
|
||||
writeByte(isSavePassword.toByte())
|
||||
writeFully(passwordMd5)
|
||||
writeFully(tgtgtKey)
|
||||
@ -368,16 +374,18 @@ internal fun TlvMapWriter.t174(
|
||||
|
||||
|
||||
internal fun TlvMapWriter.t17a(
|
||||
value: Int = 0
|
||||
smsAppId: Int = 0
|
||||
) {
|
||||
tlv(0x17a) {
|
||||
writeInt(value)
|
||||
writeInt(smsAppId)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TlvMapWriter.t197() {
|
||||
internal fun TlvMapWriter.t197(
|
||||
devLockMobileType: Byte = 0
|
||||
) {
|
||||
tlv(0x197) {
|
||||
writeByte(0)
|
||||
writeByte(devLockMobileType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -898,6 +906,61 @@ internal fun TlvMapWriter.t525(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TlvMapWriter.t542(
|
||||
value: ByteArray
|
||||
) {
|
||||
tlv(0x542) {
|
||||
writeFully(value)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TlvMapWriter.t545(
|
||||
qimei: String
|
||||
) {
|
||||
tlv(0x545) {
|
||||
writeFully(qimei.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TlvMapWriter.t548(
|
||||
nativeGetTestData: ByteArray = (
|
||||
"01 02 01 01 00 0A 00 00 00 80 5E C1 1A B0 39 A0 " +
|
||||
"E0 5C 67 DF 44 F8 E5 86 91 A2 A4 5D 92 2B 25 3A " +
|
||||
"B6 6E 2F F1 A1 E3 60 B8 36 1E 2F 6B 6F F7 2D F7 " +
|
||||
"F8 21 F1 0B 75 7D 2A 4F 63 B8 83 9C 41 0B AA C7 " +
|
||||
"C9 69 0D 70 AB F3 0F 46 28 C2 CD DB 81 CC 74 18 " +
|
||||
"ED 97 CD 31 3E 1A 17 F1 94 96 AB 6C 6B 25 4F 83 " +
|
||||
"5B 15 82 B0 8F 53 82 3F 59 FE 6E B5 EA B5 EA 7A " +
|
||||
"0C E7 2B 31 CA 4C FD 43 9A DB 40 7A CA 51 D7 9A " +
|
||||
"3C AD 6D 8F 3C C6 84 A5 4A 5F 00 20 BE FB 91 06 " +
|
||||
"F0 67 42 8B CC 59 27 4E BC 91 78 55 4E E4 5C 98 " +
|
||||
"4B 8B 0F C9 A3 83 56 06 E8 AE 5A 0D 00 AC 01 02 " +
|
||||
"01 02 00 0A 00 00 00 80 5E C1 1A B0 39 A0 E0 5C " +
|
||||
"67 DF 44 F8 E5 86 91 A2 A4 5D 92 2B 25 3A B6 6E " +
|
||||
"2F F1 A1 E3 60 B8 36 1E 2F 6B 6F F7 2D F7 F8 21 " +
|
||||
"F1 0B 75 7D 2A 4F 63 B8 83 9C 41 0B AA C7 C9 69 " +
|
||||
"0D 70 AB F3 0F 46 28 C2 CD DB 81 CC 74 18 ED 97 " +
|
||||
"CD 31 3E 1A 17 F1 94 96 AB 6C 6B 25 4F 83 5B 15 " +
|
||||
"82 B0 8F 53 82 3F 59 FE 6E B5 EA B5 EA 7A 0C E7 " +
|
||||
"2B 31 CA 4C FD 43 9A DB 40 7A CA 51 D7 9A 3C AD " +
|
||||
"6D 8F 3C C6 84 A5 4A 5F 00 20 BE FB 91 06 F0 67 " +
|
||||
"42 8B CC 59 27 4E BC 91 78 55 4E E4 5C 98 4B 8B " +
|
||||
"0F C9 A3 83 56 06 E8 AE 5A 0D 00 80 5E C1 1A B0 " +
|
||||
"39 A0 E0 5C 67 DF 44 F8 E5 86 91 A2 A4 5D 92 2B " +
|
||||
"25 3A B6 6E 2F F1 A1 E3 60 B8 36 1E 2F 6B 6F F7 " +
|
||||
"2D F7 F8 21 F1 0B 75 7D 2A 4F 63 B8 83 9C 41 0B " +
|
||||
"AA C7 C9 69 0D 70 AB F3 0F 46 28 C2 CD DB 81 CC " +
|
||||
"74 18 ED 97 CD 31 3E 1A 17 F1 94 96 AB 6C 6B 25 " +
|
||||
"4F 83 5B 15 82 B0 8F 53 82 3F 59 FE 6E B5 EA B5 " +
|
||||
"EA 7A 0C E7 2B 31 CA 4C FD 43 9A DB 40 7A CA 51 " +
|
||||
"D7 9A 3C AD 6D 8F 3C C6 84 A5 71 6F 00 00 00 1F " +
|
||||
"00 00 27 10").hexToBytes()
|
||||
) {
|
||||
tlv(0x548) {
|
||||
writeFully(nativeGetTestData)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TlvMapWriter.t544( // 1334
|
||||
) {
|
||||
tlv(0x544) {
|
||||
|
@ -216,6 +216,7 @@ internal class WtLogin {
|
||||
tlvMap[0x403]?.let { bot.client.randSeed = it }
|
||||
tlvMap[0x402]?.let { bot.client.t402 = it }
|
||||
tlvMap[0x546]?.let { bot.client.analysisTlv546(it) }
|
||||
tlvMap[0x543]?.let { bot.client.t543 = it }
|
||||
// tlvMap[0x402]?.let { t402 ->
|
||||
// bot.client.G = buildPacket {
|
||||
// writeFully(bot.client.device.guid)
|
||||
|
@ -71,6 +71,7 @@ internal object WtLogin10 : WtLoginExt {
|
||||
)
|
||||
//t112(client.account.phoneNumber.encodeToByteArray())
|
||||
t143(client.wLoginSigInfo.d2.data)
|
||||
t145(client.device.guid)
|
||||
t142(client.apkId)
|
||||
t154(sequenceId)
|
||||
t18(appId, uin = client.uin)
|
||||
|
@ -14,6 +14,8 @@ import net.mamoe.mirai.internal.network.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
|
||||
import net.mamoe.mirai.utils._writeTlvMap
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import net.mamoe.mirai.utils.toByteArray
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
@ -26,7 +28,7 @@ internal object WtLogin15 : WtLoginExt {
|
||||
client: QQAndroidClient,
|
||||
) = WtLogin.ExchangeEmp.buildOutgoingUniPacket(
|
||||
client, bodyType = 2, key = ByteArray(16), remark = "15:refresh-keys"
|
||||
) {
|
||||
) { sequenceId ->
|
||||
// writeSsoPacket(client, client.subAppId, WtLogin.ExchangeEmp.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(
|
||||
client,
|
||||
@ -53,10 +55,9 @@ internal object WtLogin15 : WtLoginExt {
|
||||
// "").hexToBytes())
|
||||
// return@writeOicqRequestPacket
|
||||
|
||||
t18(appId, uin = client.uin)
|
||||
t1(client.uin, ByteArray(4))
|
||||
t18(appId, client.appClientVersion, uin = client.uin)
|
||||
t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress)
|
||||
|
||||
// t106(client = client)
|
||||
t106(client.wLoginSigInfo.encryptA1!!)
|
||||
// kotlin.run {
|
||||
// val key = (client.account.passwordMd5 + ByteArray(4) + client.uin.toInt().toByteArray()).md5()
|
||||
@ -82,7 +83,10 @@ internal object WtLogin15 : WtLoginExt {
|
||||
// }
|
||||
|
||||
t116(client.miscBitMap, client.subSigMap)
|
||||
//t116(0x08F7FF7C, 0x00010400)
|
||||
if (client.miscBitMap and 128 != 0) {
|
||||
t166(1)
|
||||
client.rollbackSig?.let { t172(it) }
|
||||
}
|
||||
|
||||
//t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
|
||||
//t100(appId, 1, client.appClientVersion, client.ssoVersion, mainSigMap = 1048768)
|
||||
@ -90,16 +94,21 @@ internal object WtLogin15 : WtLoginExt {
|
||||
|
||||
t107(0)
|
||||
|
||||
//t108(client.ksid) // 第一次 exchange 没有 108
|
||||
if (client.ksid.isNotEmpty()) {
|
||||
t108(client.ksid)
|
||||
}
|
||||
t144(client)
|
||||
t142(client.apkId)
|
||||
if (client.uin !in 10000L..4000000000L) {
|
||||
t112(client.uin.toByteArray())
|
||||
}
|
||||
t145(client.device.guid)
|
||||
|
||||
val noPicSig =
|
||||
client.wLoginSigInfo.noPicSig ?: error("Internal error: doing exchange emp 15 while noPicSig=null")
|
||||
t16a(noPicSig)
|
||||
|
||||
t154(0)
|
||||
t154(sequenceId)
|
||||
t141(client.device.simInfo, client.networkType, client.device.apn)
|
||||
t8(2052)
|
||||
t511()
|
||||
@ -112,20 +121,22 @@ internal object WtLogin15 : WtLoginExt {
|
||||
uin = client.uin,
|
||||
guid = client.device.guid,
|
||||
dpwd = client.dpwd,
|
||||
appId = 1,
|
||||
subAppId = 16,
|
||||
appId = appId,
|
||||
subAppId = 1,
|
||||
randomSeed = client.randSeed
|
||||
)
|
||||
|
||||
t187(client.device.macAddress)
|
||||
t188(client.device.androidId)
|
||||
t194(client.device.imsiMd5)
|
||||
// ignored t201 cuz SetNeedForPayToken is never called.
|
||||
t202(client.device.wifiBSSID, client.device.wifiSSID)
|
||||
t516()
|
||||
|
||||
t521() // new
|
||||
t525(client.loginExtraData) // new
|
||||
//t544() // new
|
||||
//t544() // new 810_f
|
||||
t545(client.qimei16 ?: client.device.imei)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,13 @@ internal object WtLogin8 : WtLoginExt {
|
||||
t174(t174)
|
||||
t17a(9)
|
||||
t197()
|
||||
// Lcom/tencent/mobileqq/msf/core/auth/l;a(Ljava/lang/String;JLoicq/wlogin_sdk/request/WUserSigInfo;IIILoicq/wlogin_sdk/tools/ErrMsg;)V
|
||||
// a2.addAttribute("smsExtraData", WtloginHelper.getLoginResultData(wUserSigInfo, 1347));
|
||||
// wUserSigInfo.loginResultTLVMap.get(new Integer(1347)).get_data()
|
||||
|
||||
// this.mUserSigInfo.loginResultTLVMap.put(new Integer(1347), async_contextVar._t543);
|
||||
// toServiceMsg.getAttribute("smsExtraData"))
|
||||
client.t543?.let { t542(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import net.mamoe.mirai.internal.network.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
|
||||
import net.mamoe.mirai.utils._writeTlvMap
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import net.mamoe.mirai.utils.toByteArray
|
||||
|
||||
internal object WtLogin9 : WtLoginExt {
|
||||
private const val appId = 16L
|
||||
@ -28,21 +30,16 @@ internal object WtLogin9 : WtLoginExt {
|
||||
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, commandId = 0x0810) {
|
||||
writeShort(9) // subCommand
|
||||
var tlvCount = if (allowSlider) 0x18 else 0x17;
|
||||
val useEncryptA1AndNoPicSig =
|
||||
client.wLoginSigInfoInitialized
|
||||
&& client.wLoginSigInfo.noPicSig != null
|
||||
&& client.wLoginSigInfo.encryptA1 != null
|
||||
if (useEncryptA1AndNoPicSig) {
|
||||
tlvCount++;
|
||||
}
|
||||
// writeShort(tlvCount.toShort()) // count of TLVs, probably ignored by server?
|
||||
//writeShort(LoginType.PASSWORD.value.toShort())
|
||||
|
||||
_writeTlvMap {
|
||||
|
||||
t18(appId, client.appClientVersion, client.uin)
|
||||
t1(client.uin, client.device.ipAddress)
|
||||
t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress)
|
||||
|
||||
if (useEncryptA1AndNoPicSig) {
|
||||
t106(client.wLoginSigInfo.encryptA1!!)
|
||||
@ -63,25 +60,31 @@ internal object WtLogin9 : WtLoginExt {
|
||||
t116(client.miscBitMap, client.subSigMap)
|
||||
t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
|
||||
t107(0)
|
||||
t108(client.device.imei.toByteArray())
|
||||
if (client.ksid.isNotEmpty()) {
|
||||
t108(client.ksid)
|
||||
}
|
||||
|
||||
// t108(byteArrayOf())
|
||||
// ignored: t104()
|
||||
if (client.t104Initialized) {
|
||||
t104(client.t104)
|
||||
}
|
||||
|
||||
t142(client.apkId)
|
||||
|
||||
// if login with non-number uin
|
||||
// t112()
|
||||
if (client.uin !in 10000L..4000000000L) {
|
||||
t112(client.uin.toByteArray())
|
||||
}
|
||||
t144(client)
|
||||
|
||||
//this.build().debugPrint("傻逼")
|
||||
t145(client.device.guid)
|
||||
t147(appId, client.apkVersionName, client.apkSignatureMd5)
|
||||
|
||||
/*
|
||||
if (client.miscBitMap and 0x80 != 0) {
|
||||
t166(1)
|
||||
}
|
||||
*/
|
||||
|
||||
if (client.miscBitMap and 0x80 != 0) {
|
||||
t166(1) // com.tencent.luggage.wxa.me.e.CTRL_INDEX
|
||||
}
|
||||
if (useEncryptA1AndNoPicSig) {
|
||||
t16a(client.wLoginSigInfo.noPicSig!!)
|
||||
}
|
||||
@ -94,7 +97,17 @@ internal object WtLogin9 : WtLoginExt {
|
||||
|
||||
// ignored t172 because rollbackSig is null
|
||||
// ignored t185 because loginType is not SMS
|
||||
// ignored t400 because of first login
|
||||
if (useEncryptA1AndNoPicSig) {
|
||||
t400(
|
||||
g = client.G,
|
||||
uin = client.uin,
|
||||
guid = client.device.guid,
|
||||
dpwd = client.dpwd,
|
||||
appId = appId,
|
||||
subAppId = client.subAppId,
|
||||
randomSeed = client.randSeed,
|
||||
)
|
||||
}
|
||||
|
||||
t187(client.device.macAddress)
|
||||
t188(client.device.androidId)
|
||||
@ -103,8 +116,8 @@ internal object WtLogin9 : WtLoginExt {
|
||||
t191()
|
||||
}
|
||||
|
||||
/*
|
||||
t201(N = byteArrayOf())*/
|
||||
|
||||
//t201(N = byteArrayOf())
|
||||
|
||||
t202(client.device.wifiBSSID, client.device.wifiSSID)
|
||||
|
||||
@ -116,6 +129,8 @@ internal object WtLogin9 : WtLoginExt {
|
||||
t521()
|
||||
|
||||
t525()
|
||||
t545(client.qimei16 ?: client.device.imei)
|
||||
// t548()
|
||||
// this.build().debugPrint("傻逼")
|
||||
|
||||
// ignored t318 because not logging in by QR
|
||||
@ -138,7 +153,7 @@ internal object WtLogin9 : WtLoginExt {
|
||||
|
||||
_writeTlvMap {
|
||||
t18(appId, client.appClientVersion, client.uin)
|
||||
t1(client.uin, client.device.ipAddress)
|
||||
t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress)
|
||||
|
||||
t106(data.tmpPwd)
|
||||
|
||||
|
297
mirai-core/src/commonMain/kotlin/network/qimei/Qimei.kt
Normal file
297
mirai-core/src/commonMain/kotlin/network/qimei/Qimei.kt
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.qimei
|
||||
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.components.BotClientHolder
|
||||
import net.mamoe.mirai.internal.network.components.HttpClientProvider
|
||||
import net.mamoe.mirai.internal.network.components.SsoProcessorContext
|
||||
import net.mamoe.mirai.internal.network.protocol
|
||||
import net.mamoe.mirai.internal.utils.crypto.aesDecrypt
|
||||
import net.mamoe.mirai.internal.utils.crypto.aesEncrypt
|
||||
import net.mamoe.mirai.internal.utils.crypto.rsaEncryptWithX509PubKey
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||
import kotlin.random.Random
|
||||
|
||||
private val secret = "ZdJqM15EeO2zWc08"
|
||||
private val rsaPubKey = """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIxgwoutfwoJxcGQeedgP7FG9
|
||||
qaIuS0qzfR8gWkrkTZKM2iWHn2ajQpBRZjMSoSf6+KJGvar2ORhBfpDXyVtZCKpq
|
||||
LQ+FLkpncClKVIrBwv6PHyUvuCb0rIarmgDnzkfQAqVufEtR64iazGDKatvJ9y6B
|
||||
9NMbHddGSAUmRTCrHQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
""".trimIndent()
|
||||
|
||||
internal suspend fun QQAndroidBot.requestQimei(logger: MiraiLogger) {
|
||||
val protocol = components[BotClientHolder].client.protocol
|
||||
if (protocol.appKey.isEmpty()) return
|
||||
|
||||
val deviceInfo = components[SsoProcessorContext].device
|
||||
val httpClient = components[HttpClientProvider].getHttpClient()
|
||||
|
||||
val seed = deviceInfo.guid.foldRight(0x6f4L) { curr, acc -> acc + curr.toLong() }
|
||||
val random = Random(seed)
|
||||
|
||||
val reservedData = Json.encodeToString(
|
||||
ReservedData(
|
||||
harmony = "0",
|
||||
clone = "0",
|
||||
containe = "",
|
||||
oz = "UhYmelwouA+V2nPWbOvLTgN2/m8jwGB+yUB5v9tysQg=",
|
||||
oo = "Xecjt+9S1+f8Pz2VLSxgpw==",
|
||||
kelong = "0",
|
||||
uptimes = formatTime(currentTimeMillis() - random.nextLong(14_400_000), null),
|
||||
multiUser = "0",
|
||||
bod = deviceInfo.board.decodeToString(),
|
||||
brd = deviceInfo.brand.decodeToString(),
|
||||
dv = deviceInfo.device.decodeToString(),
|
||||
firstLevel = "",
|
||||
manufact = deviceInfo.brand.decodeToString(),
|
||||
name = deviceInfo.model.decodeToString(),
|
||||
host = "se.infra",
|
||||
kernel = deviceInfo.procVersion.decodeToString(),
|
||||
)
|
||||
)
|
||||
|
||||
val yearMonthFormatted = formatTime(currentTimeMillis(), "yyyy-MM-01")
|
||||
val rand1 = random.nextInt(899999) + 100000
|
||||
val rand2 = random.nextInt(899999999) + 100000000
|
||||
|
||||
val beaconId = buildString {
|
||||
(1..40).forEach { i ->
|
||||
when (i) {
|
||||
1, 2, 13, 14, 17, 18, 21, 22, 25, 26, 29, 30, 33, 34, 37, 38 -> {
|
||||
append('k')
|
||||
append(i)
|
||||
append(':')
|
||||
append(yearMonthFormatted)
|
||||
append(rand1)
|
||||
append('.')
|
||||
append(rand2)
|
||||
}
|
||||
|
||||
3 -> append("k3:0000000000000000")
|
||||
4 -> {
|
||||
append("k4:")
|
||||
append(getRandomString(16))
|
||||
}
|
||||
|
||||
else -> {
|
||||
append('k')
|
||||
append(i)
|
||||
append(':')
|
||||
append(random.nextInt(10000))
|
||||
}
|
||||
}
|
||||
append(';')
|
||||
}
|
||||
}
|
||||
|
||||
val payloadParam = Json.encodeToString(
|
||||
DevicePayloadData(
|
||||
androidId = deviceInfo.androidId.decodeToString(),
|
||||
platformId = 1,
|
||||
appKey = protocol.appKey,
|
||||
appVersion = protocol.buildVer,
|
||||
beaconIdSrc = beaconId,
|
||||
brand = deviceInfo.brand.decodeToString(),
|
||||
channelId = "2017",
|
||||
cid = "",
|
||||
imei = deviceInfo.imei,
|
||||
imsi = "",
|
||||
mac = deviceInfo.macAddress.decodeToString(),
|
||||
model = deviceInfo.model.decodeToString(),
|
||||
networkType = "unknown",
|
||||
oaid = "",
|
||||
osVersion = buildString {
|
||||
append("Android ")
|
||||
append(deviceInfo.version.release.toString())
|
||||
append(", level ")
|
||||
append(deviceInfo.version.sdk.toString())
|
||||
},
|
||||
qimei = "",
|
||||
qimei36 = "",
|
||||
sdkVersion = "1.2.13.6",
|
||||
audit = "",
|
||||
userId = "{}",
|
||||
packageId = protocol.apkId,
|
||||
deviceType = if (configuration.protocol == MiraiProtocol.ANDROID_PAD) "Pad" else "Phone",
|
||||
sdkName = "",
|
||||
reserved = reservedData,
|
||||
)
|
||||
).toByteArray()
|
||||
|
||||
val aesKey = getRandomString(16).toByteArray()
|
||||
val nonce = getRandomString(16)
|
||||
val timestamp = currentTimeSeconds() * 1000
|
||||
|
||||
val encodedAESKey = rsaEncryptWithX509PubKey(aesKey, rsaPubKey, timestamp).encodeBase64()
|
||||
val encodedPayloadParam = aesEncrypt(payloadParam, aesKey, aesKey).encodeBase64()
|
||||
|
||||
val payload = Json.encodeToString(
|
||||
PostData(
|
||||
key = encodedAESKey,
|
||||
params = encodedPayloadParam,
|
||||
time = timestamp,
|
||||
nonce = nonce,
|
||||
sign = buildString {
|
||||
append(encodedAESKey)
|
||||
append(encodedPayloadParam)
|
||||
append(timestamp)
|
||||
append(nonce)
|
||||
append(secret)
|
||||
}.md5().toUHexString(""),
|
||||
extra = ""
|
||||
)
|
||||
)
|
||||
|
||||
val resp = Json.decodeFromString(
|
||||
OLAAndroidResp.serializer(),
|
||||
httpClient.post("https://snowflake.qq.com/ola/android") {
|
||||
userAgent(buildString {
|
||||
append("Dalvik/")
|
||||
append(dalvikVersions[deviceInfo.version.sdk] ?: "2.1.0")
|
||||
append(" (Linux; U; Android ")
|
||||
append(deviceInfo.version.release.decodeToString())
|
||||
append("; ")
|
||||
append(deviceInfo.device.decodeToString())
|
||||
append(" Build/")
|
||||
append(deviceInfo.display.decodeToString())
|
||||
append(")")
|
||||
})
|
||||
contentType(ContentType.Application.Json)
|
||||
header("Cookie", "")
|
||||
setBody(payload.toByteArray())
|
||||
timeout {
|
||||
connectTimeoutMillis = 5000
|
||||
requestTimeoutMillis = 5000
|
||||
socketTimeoutMillis = 5000
|
||||
}
|
||||
}.bodyAsText()
|
||||
)
|
||||
|
||||
if (resp.code != 0) {
|
||||
logger.warning { "Cannot get qimei from server, return code = ${resp.code}" }
|
||||
return
|
||||
}
|
||||
|
||||
val decryptedData = aesDecrypt(resp.data.decodeBase64(), aesKey, aesKey)
|
||||
val qimeiData = Json.decodeFromString(QimeiData.serializer(), decryptedData.decodeToString())
|
||||
|
||||
client.qimei36 = qimeiData.q36
|
||||
client.qimei16 = qimeiData.q16
|
||||
}
|
||||
|
||||
private val dalvikVersions = mapOf(
|
||||
14 to "1.6",
|
||||
15 to "1.6",
|
||||
16 to "1.6",
|
||||
17 to "1.6",
|
||||
18 to "1.6",
|
||||
19 to "2.0",
|
||||
20 to "2.0",
|
||||
21 to "2.1.0",
|
||||
22 to "2.1.0",
|
||||
23 to "2.1.0",
|
||||
24 to "2.1.0",
|
||||
25 to "2.1.0",
|
||||
26 to "2.1.0",
|
||||
27 to "2.1.0",
|
||||
28 to "2.1.0",
|
||||
29 to "2.1.0",
|
||||
30 to "2.1.0",
|
||||
31 to "2.1.0",
|
||||
32 to "2.1.0",
|
||||
33 to "2.1.0",
|
||||
34 to "2.1.0",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private class OLAAndroidResp(
|
||||
val code: Int,
|
||||
val data: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private class QimeiData(
|
||||
val q16: String,
|
||||
val q36: String,
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
@Serializable
|
||||
private class ReservedData(
|
||||
val harmony: String,
|
||||
val clone: String,
|
||||
val containe: String,
|
||||
val oz: String,
|
||||
val oo: String,
|
||||
val kelong: String,
|
||||
val uptimes: String,
|
||||
val multiUser: String,
|
||||
val bod: String,
|
||||
val brd: String,
|
||||
val dv: String,
|
||||
val firstLevel: String,
|
||||
val manufact: String,
|
||||
val name: String,
|
||||
val host: String,
|
||||
val kernel: String
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
@Serializable
|
||||
private class DevicePayloadData(
|
||||
val androidId: String,
|
||||
val platformId: Int,
|
||||
val appKey: String,
|
||||
val appVersion: String,
|
||||
val beaconIdSrc: String,
|
||||
val brand: String,
|
||||
val channelId: String,
|
||||
val cid: String,
|
||||
val imei: String,
|
||||
val imsi: String,
|
||||
val mac: String,
|
||||
val model: String,
|
||||
val networkType: String,
|
||||
val oaid: String,
|
||||
val osVersion: String,
|
||||
val qimei: String,
|
||||
val qimei36: String,
|
||||
val sdkVersion: String,
|
||||
val audit: String,
|
||||
val userId: String,
|
||||
val packageId: String,
|
||||
val deviceType: String,
|
||||
val sdkName: String,
|
||||
val reserved: String
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
@Serializable
|
||||
private class PostData(
|
||||
val key: String,
|
||||
val params: String,
|
||||
val time: Long,
|
||||
val nonce: String,
|
||||
val sign: String,
|
||||
val extra: String
|
||||
)
|
@ -18,6 +18,7 @@ internal class MiraiProtocolInternal(
|
||||
var apkId: String,
|
||||
var id: Long,
|
||||
var ver: String,
|
||||
var buildVer: String,
|
||||
var sdkVer: String,
|
||||
var miscBitMap: Int,
|
||||
var subSigMap: Int,
|
||||
@ -25,6 +26,7 @@ internal class MiraiProtocolInternal(
|
||||
var sign: String,
|
||||
var buildTime: Long,
|
||||
var ssoVersion: Int,
|
||||
var appKey: String,
|
||||
var supportsQRLogin: Boolean,
|
||||
|
||||
// don't change property signatures, used externally.
|
||||
@ -38,25 +40,28 @@ internal class MiraiProtocolInternal(
|
||||
protocols[protocol] ?: error("Internal Error: Missing protocol $protocol")
|
||||
|
||||
init {
|
||||
//Updated from MiraiGo (2023/3/7)
|
||||
//Updated from 8.9.35 (2023/4/9)
|
||||
protocols[MiraiProtocol.ANDROID_PHONE] = MiraiProtocolInternal(
|
||||
apkId = "com.tencent.mobileqq",
|
||||
id = 537151682,
|
||||
ver = "8.9.33.10335",
|
||||
sdkVer = "6.0.0.2534",
|
||||
id = 537153295,
|
||||
ver = "8.9.35",
|
||||
buildVer = "8.9.35.10440",
|
||||
sdkVer = "6.0.0.2535",
|
||||
miscBitMap = 150470524,
|
||||
subSigMap = 0x10400,
|
||||
mainSigMap = 16724722,
|
||||
mainSigMap = 34869344 or 192,
|
||||
sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
|
||||
buildTime = 1673599898L,
|
||||
buildTime = 1676531414L,
|
||||
ssoVersion = 19,
|
||||
appKey = "0S200MNJT807V3GE",
|
||||
supportsQRLogin = false,
|
||||
)
|
||||
//Updated from MiraiGo (2023/3/7)
|
||||
protocols[MiraiProtocol.ANDROID_PAD] = MiraiProtocolInternal(
|
||||
apkId = "com.tencent.mobileqq",
|
||||
id = 537151218,
|
||||
ver = "8.9.33.10335",
|
||||
ver = "8.9.33",
|
||||
buildVer = "8.9.33.10335",
|
||||
sdkVer = "6.0.0.2534",
|
||||
miscBitMap = 150470524,
|
||||
subSigMap = 0x10400,
|
||||
@ -64,6 +69,7 @@ internal class MiraiProtocolInternal(
|
||||
sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
|
||||
buildTime = 1673599898L,
|
||||
ssoVersion = 19,
|
||||
appKey = "0S200MNJT807V3GE",
|
||||
supportsQRLogin = false,
|
||||
)
|
||||
//Updated from MiraiGo (2023/3/24)
|
||||
@ -71,6 +77,7 @@ internal class MiraiProtocolInternal(
|
||||
apkId = "com.tencent.qqlite",
|
||||
id = 537065138,
|
||||
ver = "2.0.8",
|
||||
buildVer = "2.0.8",
|
||||
sdkVer = "6.0.0.2365",
|
||||
miscBitMap = 16252796,
|
||||
subSigMap = 0x10400,
|
||||
@ -78,12 +85,14 @@ internal class MiraiProtocolInternal(
|
||||
sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
|
||||
buildTime = 1559564731L,
|
||||
ssoVersion = 5,
|
||||
appKey = "",
|
||||
supportsQRLogin = true,
|
||||
)
|
||||
protocols[MiraiProtocol.IPAD] = MiraiProtocolInternal(
|
||||
apkId = "com.tencent.minihd.qq",
|
||||
id = 537151363,
|
||||
ver = "8.9.33.614",
|
||||
ver = "8.9.33",
|
||||
buildVer = "8.9.33.614",
|
||||
sdkVer = "6.0.0.2433",
|
||||
miscBitMap = 150470524,
|
||||
subSigMap = 66560,
|
||||
@ -91,12 +100,14 @@ internal class MiraiProtocolInternal(
|
||||
sign = "AA 39 78 F4 1F D9 6F F9 91 4A 66 9E 18 64 74 C7",
|
||||
buildTime = 1640921786L,
|
||||
ssoVersion = 12,
|
||||
appKey = "",
|
||||
supportsQRLogin = false,
|
||||
)
|
||||
protocols[MiraiProtocol.MACOS] = MiraiProtocolInternal(
|
||||
apkId = "com.tencent.qq",
|
||||
id = 0x2003ca32,
|
||||
ver = "6.7.9",
|
||||
buildVer = "6.7.9",
|
||||
sdkVer = "6.2.0.1023",
|
||||
miscBitMap = 0x7ffc,
|
||||
subSigMap = 66560,
|
||||
@ -104,6 +115,7 @@ internal class MiraiProtocolInternal(
|
||||
sign = "com.tencent.qq".encodeToByteArray().toUHexString(" "),
|
||||
buildTime = 0L,
|
||||
ssoVersion = 7,
|
||||
appKey = "",
|
||||
supportsQRLogin = true,
|
||||
)
|
||||
}
|
||||
|
14
mirai-core/src/commonMain/kotlin/utils/crypto/AES.kt
Normal file
14
mirai-core/src/commonMain/kotlin/utils/crypto/AES.kt
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
internal expect fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray
|
||||
|
||||
internal expect fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray
|
21
mirai-core/src/commonMain/kotlin/utils/crypto/RSA.kt
Normal file
21
mirai-core/src/commonMain/kotlin/utils/crypto/RSA.kt
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
internal class RSAKeyPair(
|
||||
val plainPubPemKey: String,
|
||||
val plainPrivPemKey: String
|
||||
)
|
||||
|
||||
internal expect fun generateRSAKeyPair(keySize: Int): RSAKeyPair
|
||||
|
||||
internal expect fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray
|
||||
|
||||
internal expect fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray
|
40
mirai-core/src/commonTest/kotlin/utils/crypto/AESTest.kt
Normal file
40
mirai-core/src/commonTest/kotlin/utils/crypto/AESTest.kt
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.utils.currentTimeMillis
|
||||
import net.mamoe.mirai.utils.getRandomString
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class AESTest {
|
||||
@Test
|
||||
fun `can do crypto`() {
|
||||
val random = Random(currentTimeMillis())
|
||||
|
||||
val key = getRandomString(16, random).encodeToByteArray()
|
||||
val iv = getRandomString(16, random).encodeToByteArray()
|
||||
val currentTime = currentTimeMillis()
|
||||
|
||||
val plainText = buildString {
|
||||
append("Use of this source code is governed by the GNU AGPLv3 license ")
|
||||
append("that can be found through the following link. ")
|
||||
append(currentTime)
|
||||
}
|
||||
|
||||
println("AES crypto test: key = ${key.toUHexString()}, iv = ${iv.toUHexString()}, currentTimeMillis = $currentTime")
|
||||
val encrypted = aesEncrypt(plainText.encodeToByteArray(), iv, key)
|
||||
val decrypted = aesDecrypt(encrypted, iv, key)
|
||||
|
||||
assertEquals(plainText, decrypted.decodeToString())
|
||||
}
|
||||
}
|
112
mirai-core/src/commonTest/kotlin/utils/crypto/RSATest.kt
Normal file
112
mirai-core/src/commonTest/kotlin/utils/crypto/RSATest.kt
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.internal.testFramework.*
|
||||
import net.mamoe.mirai.internal.testFramework.rules.DisabledOnJvmLikePlatform
|
||||
import kotlin.math.pow
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@DisabledOnJvmLikePlatform(Platform.AndroidUnitTest::class)
|
||||
class RSATest {
|
||||
@TestFactory
|
||||
fun `can generate key pair`(): DynamicTestsResult {
|
||||
return runDynamicTests(buildList {
|
||||
repeat(4) { exp ->
|
||||
val keySize = 2.0.pow(9 + exp).toInt()
|
||||
add(dynamicTest("RSAKeyGenLength$keySize") {
|
||||
val rsaKeyPair = generateRSAKeyPair(keySize)
|
||||
println("RSA keygen test #${exp + 1}: keySize = $keySize")
|
||||
println(rsaKeyPair.plainPubPemKey)
|
||||
println(rsaKeyPair.plainPrivPemKey)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can do crypto with generated key`() {
|
||||
val keyPair = generateRSAKeyPair(2048)
|
||||
|
||||
val plainText = buildString {
|
||||
append("Use of this source code is governed by the GNU AGPLv3 license ")
|
||||
append("that can be found through the following link. ")
|
||||
}
|
||||
|
||||
println(
|
||||
"RSA crypto test: plainTextLength: ${plainText.length}, " +
|
||||
"pubKey = ${keyPair.plainPubPemKey}, " +
|
||||
"privKey = ${keyPair.plainPrivPemKey}"
|
||||
)
|
||||
val enc = rsaEncryptWithX509PubKey(plainText.encodeToByteArray(), keyPair.plainPubPemKey, 0)
|
||||
println("rsa encrypt: data size=${enc.size}")
|
||||
val decrypted = rsaDecryptWithPKCS8PrivKey(enc, keyPair.plainPrivPemKey, 0)
|
||||
|
||||
assertEquals(plainText, decrypted.decodeToString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can do crypto with specific key`() {
|
||||
val pubKey = """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0KIpsWPW2JA7ShJI18o
|
||||
wPv3Ip3Y6a0OkJOozfVlQDOjjUG6niDcrPIm+OpL7pCAzwc+h8d9sFH5c/7/bY4i
|
||||
wKK6CpSaOYgQQ03P31KhzmXGJ4LVSxUIV0bhuDYQr+sU5Gu97onF8Ko8MELtWTPw
|
||||
KP1dfqZ3PrK8QBH6su0GlB8onYFtzDUckr2wCrrJ1cR4L1Dg5f2egE75l1cliAIM
|
||||
4FH1WFU2musfdEuCo5oPgl8ZPPLrQwp8qm9w7xBvbgbmfPTjPBC0N4gcelVzvdfC
|
||||
eU8vpIlLP/9W5nkdqF6CWzjE3dIx2btOH4QDDyogDSLRAvcKN5/1EIZeu2FTbw9k
|
||||
3QIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
""".trimIndent()
|
||||
|
||||
val privKey = """
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvQoimxY9bYkDt
|
||||
KEkjXyjA+/cindjprQ6Qk6jN9WVAM6ONQbqeINys8ib46kvukIDPBz6Hx32wUflz
|
||||
/v9tjiLAoroKlJo5iBBDTc/fUqHOZcYngtVLFQhXRuG4NhCv6xTka73uicXwqjww
|
||||
Qu1ZM/Ao/V1+pnc+srxAEfqy7QaUHyidgW3MNRySvbAKusnVxHgvUODl/Z6ATvmX
|
||||
VyWIAgzgUfVYVTaa6x90S4Kjmg+CXxk88utDCnyqb3DvEG9uBuZ89OM8ELQ3iBx6
|
||||
VXO918J5Ty+kiUs//1bmeR2oXoJbOMTd0jHZu04fhAMPKiANItEC9wo3n/UQhl67
|
||||
YVNvD2TdAgMBAAECggEAExREqRcfvJyNIeQ9Vg7xclTbuhaB+ypeSAnzGfzJeXxF
|
||||
pUaPCNDeBSvVZ0qmWoG7rA4HViO3AJ9j7ydG6kfLa7orU6SKx5GS56jMZOzrdXsp
|
||||
37pD+wj+n/W08+da2LPYUeeSxSmVdVYq+DwI96mKTwQKDhQULiyqBrWOW7Um/q/Z
|
||||
JC8kJWKEmlNideDQHJxZViRyOdKvJtiwvoBLe1Jvbx7oMbpZnf20gV8C7UU7U38R
|
||||
e0BKT6HBUHXuOOp2tFFpX6dySkJqW7Jijv04B/KnDYaSWD8TtaQfPfAhkiEVA17E
|
||||
Ret04PnPMiYCkSVakO0MEeFpwb01vPca4Z64zgf7EQKBgQDyU6wsO3v8L1OlV7tx
|
||||
7+T0PuOqeo7MWESSn18LeyOP2Y+fDtHKMUFULeYH1UZaGsZJvW+P+c8Mvyitbcvv
|
||||
SZPTR0Dg+1HueXWkNTejs8Z2BKpPmIPaVLz5FxhV7hV2hKgII/yhyRoiWrTiawLg
|
||||
ocOnYSostg+tt6kT8U2QPKhg9QKBgQC5Jho1nZ3pFPVu2rV/o9VvN7bGfn1M2o8k
|
||||
9PQjLZQYiXJvPP0tNlvAOHk9cAqYecHJ3wDVacZWmLicU8xmNSFmmN6Vs9jj6km4
|
||||
CWq0/wuTUO/fiH+oHZb6+JM63RXbASWyNK+WwmZtDryNBGRB5zbeCAK8tFsRCJDw
|
||||
19WQUzljSQKBgCWKkuzTVlTuXA4MdmyjVpwENi8OB5tevVjdudLEg/DgKqDgod2q
|
||||
Hc3VwoJKJzkEVt3LrEHo2IvH/ZxIm0R56J3dtw5jwQCp7nC/EdyZmFBmTqBAJ4Um
|
||||
hZQtYMbHOKoAySthr9y8lADofodpPqvgQ7hllCwTFIC8KER/qJ2E2C0VAoGATLUM
|
||||
hsoWckrMpHDYYVlvQ/TBNNuS7hRe2eDihPCNOt03G/8YpXKv8KN1F48j1KgdMZXC
|
||||
sqhwE9CSK7JMLMw2WltbXIp2gXa/tA+yteo00YPm3aWfvfcEZlY2KV0PgPyosXxC
|
||||
gyNnbCd+1q3LG8K/aJ3JBIV0dUonQqEpSfIxBIECgYArO3Iw+LvoePjq4yHheyEM
|
||||
rz6d6RB+i1Q7ExBK7lbZxN17HmKiOewwI772zEo28IY9sIHugV7rW1vQVs3bnzgk
|
||||
ExDGjYWZSKHfs+3mvrLNRIx/IsVqqwlXt5oO9TspSh68ASvmXN51dmduxRrSuScq
|
||||
8a49uOr675SyFCJTIdF/Ag==
|
||||
-----END PRIVATE KEY-----
|
||||
""".trimIndent()
|
||||
|
||||
val plainText = buildString {
|
||||
append("Use of this source code is governed by the GNU AGPLv3 license ")
|
||||
append("that can be found through the following link. ")
|
||||
}
|
||||
|
||||
val enc = rsaEncryptWithX509PubKey(plainText.encodeToByteArray(), pubKey, 0)
|
||||
val dec = rsaDecryptWithPKCS8PrivKey(enc, privKey, 0)
|
||||
|
||||
assertEquals(plainText, dec.decodeToString())
|
||||
|
||||
}
|
||||
}
|
31
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/AES.kt
Normal file
31
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/AES.kt
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
internal actual fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||
return doAES(input, iv, key, Cipher.ENCRYPT_MODE)
|
||||
}
|
||||
|
||||
internal actual fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||
return doAES(input, iv, key, Cipher.DECRYPT_MODE)
|
||||
}
|
||||
|
||||
private fun doAES(input: ByteArray, iv: ByteArray, key: ByteArray, opMode: Int): ByteArray {
|
||||
val keySpec = SecretKeySpec(key, "AES")
|
||||
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(opMode, keySpec, IvParameterSpec(iv))
|
||||
|
||||
return cipher.doFinal(input)
|
||||
}
|
74
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/RSA.kt
Normal file
74
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/RSA.kt
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.utils.decodeBase64
|
||||
import net.mamoe.mirai.utils.encodeBase64
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import javax.crypto.Cipher
|
||||
|
||||
internal actual fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray {
|
||||
val encodedKey = plainPubPemKey
|
||||
.replace("\n", "")
|
||||
.removePrefix("-----BEGIN PUBLIC KEY-----")
|
||||
.removeSuffix("-----END PUBLIC KEY-----")
|
||||
.trim()
|
||||
.decodeBase64()
|
||||
|
||||
val keyFactory = KeyFactory.getInstance("RSA")
|
||||
val rsaPubKey = keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))
|
||||
|
||||
|
||||
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, rsaPubKey, SecureRandom().apply { setSeed(seed) })
|
||||
|
||||
return cipher.doFinal(input)
|
||||
}
|
||||
|
||||
internal actual fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray {
|
||||
val encodedKey = plainPrivPemKey
|
||||
.replace("\n", "")
|
||||
.removePrefix("-----BEGIN PRIVATE KEY-----")
|
||||
.removeSuffix("-----END PRIVATE KEY-----")
|
||||
.trim()
|
||||
.decodeBase64()
|
||||
|
||||
val keyFactory = KeyFactory.getInstance("RSA")
|
||||
val rsaPubKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
||||
|
||||
|
||||
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, rsaPubKey, SecureRandom().apply { setSeed(seed) })
|
||||
|
||||
return cipher.doFinal(input)
|
||||
}
|
||||
|
||||
internal actual fun generateRSAKeyPair(keySize: Int): RSAKeyPair {
|
||||
val keyGen = KeyPairGenerator.getInstance("RSA")
|
||||
keyGen.initialize(keySize)
|
||||
|
||||
val keyPair = keyGen.generateKeyPair()
|
||||
return RSAKeyPair(
|
||||
plainPubPemKey = buildString {
|
||||
appendLine("-----BEGIN PUBLIC KEY-----")
|
||||
keyPair.public.encoded.encodeBase64().chunked(64).forEach(::appendLine)
|
||||
appendLine("-----END PUBLIC KEY-----")
|
||||
},
|
||||
plainPrivPemKey = buildString {
|
||||
appendLine("-----BEGIN PRIVATE KEY-----")
|
||||
keyPair.private.encoded.encodeBase64().chunked(64).forEach(::appendLine)
|
||||
appendLine("-----END PRIVATE KEY-----")
|
||||
}
|
||||
)
|
||||
}
|
@ -1,4 +1,10 @@
|
||||
headers = openssl/ec.h openssl/ecdh.h openssl/evp.h
|
||||
headers = openssl/ec.h \
|
||||
openssl/ecdh.h \
|
||||
openssl/evp.h \
|
||||
openssl/rsa.h \
|
||||
openssl/pem.h \
|
||||
openssl/x509.h \
|
||||
openssl/err.h
|
||||
|
||||
# -L/usr/local/opt/openssl@1.1/1.1.1o/lib is for GitHub actions. See https://github.com/actions/virtual-environments/blob/main/images/macos/macos-12-Readme.md
|
||||
|
||||
@ -46,3 +52,22 @@ compilerOpts = -I/opt/openssl/include \
|
||||
-I/usr/local/include/ \
|
||||
-IC:/openssl/include/ \
|
||||
-IC:/vcpkg/installed/x64-windows/include \
|
||||
|
||||
---
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
static int _evpCipherCtxGetBlockSize(const EVP_CIPHER_CTX *ctx) {
|
||||
#ifdef EVP_CIPHER_CTX_block_size
|
||||
return EVP_CIPHER_CTX_get_block_size(ctx);
|
||||
#endif
|
||||
return EVP_CIPHER_CTX_block_size(ctx);
|
||||
}
|
||||
|
||||
static int _evpPkeyCtxSetRSAKeygenBits(EVP_PKEY_CTX *ctx, int bits) {
|
||||
#ifdef EVP_PKEY_CTX_set_rsa_keygen_bits
|
||||
return RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_KEYGEN, EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, NULL);
|
||||
#endif
|
||||
return EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits);
|
||||
}
|
101
mirai-core/src/nativeMain/kotlin/utils/crypto/AESNative.kt
Normal file
101
mirai-core/src/nativeMain/kotlin/utils/crypto/AESNative.kt
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import net.mamoe.mirai.internal.utils.free
|
||||
import net.mamoe.mirai.internal.utils.getOpenSSLError
|
||||
import openssl.*
|
||||
|
||||
private val aes256CBC by lazy { EVP_aes_256_cbc() }
|
||||
|
||||
internal actual fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||
return doAES(input, iv, key, true)
|
||||
}
|
||||
|
||||
internal actual fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||
return doAES(input, iv, key, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* reference:
|
||||
* - https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
|
||||
*/
|
||||
private fun doAES(input: ByteArray, iv: ByteArray, key: ByteArray, doEncrypt: Boolean): ByteArray {
|
||||
memScoped {
|
||||
val evpCipherCtx = EVP_CIPHER_CTX_new() ?: error("Failed to create evp cipher context: ${getOpenSSLError()}")
|
||||
|
||||
val pinnedKey = key.pin()
|
||||
val pinnedIv = iv.pin()
|
||||
val pinnedInput = input.pin()
|
||||
|
||||
if (1 != EVP_CipherInit(
|
||||
ctx = evpCipherCtx,
|
||||
cipher = aes256CBC,
|
||||
key = pinnedKey.addressOf(0).reinterpret(),
|
||||
iv = pinnedIv.addressOf(0).reinterpret(),
|
||||
enc = if (doEncrypt) 1 else 0
|
||||
)
|
||||
) {
|
||||
pinnedKey.unpin()
|
||||
pinnedIv.unpin()
|
||||
pinnedInput.unpin()
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to init aes-256-cbc cipher: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
pinnedKey.unpin()
|
||||
pinnedIv.unpin()
|
||||
|
||||
val blockSize = _evpCipherCtxGetBlockSize(evpCipherCtx)
|
||||
val cipherBufferSize = pinnedInput.get().size + blockSize - (pinnedInput.get().size % blockSize)
|
||||
val pinnedCipherBuffer = ByteArray(cipherBufferSize.convert()).pin()
|
||||
|
||||
|
||||
val tempLen = alloc<IntVar>()
|
||||
val cipherSize = alloc<IntVar>()
|
||||
|
||||
if (1 != EVP_CipherUpdate(
|
||||
ctx = evpCipherCtx,
|
||||
out = pinnedCipherBuffer.addressOf(0).reinterpret(),
|
||||
outl = tempLen.ptr,
|
||||
`in` = pinnedInput.addressOf(0).reinterpret(),
|
||||
inl = pinnedInput.get().size.convert()
|
||||
)
|
||||
) {
|
||||
pinnedInput.unpin()
|
||||
pinnedCipherBuffer.unpin()
|
||||
free(tempLen.ptr, cipherSize.ptr)
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed do aes-256-cbc cipher update: ${getOpenSSLError()}")
|
||||
}
|
||||
cipherSize.value = tempLen.value
|
||||
|
||||
if (1 != EVP_CipherFinal(
|
||||
ctx = evpCipherCtx,
|
||||
outm = pinnedCipherBuffer.addressOf(tempLen.value).reinterpret(),
|
||||
outl = tempLen.ptr
|
||||
)
|
||||
) {
|
||||
pinnedInput.unpin()
|
||||
pinnedCipherBuffer.unpin()
|
||||
free(tempLen.ptr, cipherSize.ptr)
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed do aes-256-cbc cipher final: ${getOpenSSLError()}")
|
||||
}
|
||||
cipherSize.value += tempLen.value
|
||||
|
||||
return pinnedCipherBuffer.get().copyOf(cipherSize.value).also {
|
||||
pinnedInput.unpin()
|
||||
pinnedCipherBuffer.unpin()
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
}
|
||||
}
|
||||
}
|
299
mirai-core/src/nativeMain/kotlin/utils/crypto/RSANative.kt
Normal file
299
mirai-core/src/nativeMain/kotlin/utils/crypto/RSANative.kt
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import net.mamoe.mirai.internal.utils.getOpenSSLError
|
||||
import net.mamoe.mirai.internal.utils.ref
|
||||
import openssl.*
|
||||
|
||||
/**
|
||||
* reference:
|
||||
* - https://stackoverflow.com/questions/70535625/openssl-rsa-encryption-decryption-with-evp-methods
|
||||
* - https://www.openssl.org/docs/man3.1/man3/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate RSA key pair with size of [keySize].
|
||||
* The public key pair is encoded with x.509, and the private key pair is encoded with PKCS8
|
||||
*/
|
||||
internal actual fun generateRSAKeyPair(keySize: Int): RSAKeyPair {
|
||||
memScoped {
|
||||
val evpPkeyCtx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, null)
|
||||
?: error("Failed to create evp pkey context: ${getOpenSSLError()}")
|
||||
|
||||
if (EVP_PKEY_keygen_init(evpPkeyCtx) <= 0) {
|
||||
error("Failed to init evp pkey context: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
// libcrypto 3 move EVP_PKEY_CTX_set_rsa_keygen_bits from macro to function
|
||||
if (_evpPkeyCtxSetRSAKeygenBits(evpPkeyCtx, keySize) <= 0) {
|
||||
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||
error("Failed to set key bit for rsa evp pkey: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
val evpPKey = EVP_PKEY_new() ?: kotlin.run {
|
||||
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||
error("Failed to create evp pkey: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
if (EVP_PKEY_keygen(evpPkeyCtx, ref(evpPKey)) <= 0) {
|
||||
EVP_PKEY_free(evpPKey)
|
||||
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||
error("Failed to generate rsa key pair: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
val publicPemKey = dumpPKey(evpPKey) { b, k -> PEM_write_bio_PUBKEY(b, k) }
|
||||
?: kotlin.run {
|
||||
EVP_PKEY_free(evpPKey)
|
||||
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||
error("Failed to dump rsa public key: ${getOpenSSLError()}")
|
||||
}
|
||||
val privatePemKey = dumpPKey(evpPKey) { b, k ->
|
||||
PEM_write_bio_PKCS8PrivateKey(b, k, null, null, 0, null, null)
|
||||
} ?: kotlin.run {
|
||||
EVP_PKEY_free(evpPKey)
|
||||
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||
error("Failed to dump rsa public key: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
EVP_PKEY_free(evpPKey)
|
||||
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||
|
||||
return RSAKeyPair(publicPemKey, privatePemKey)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnsafeNumber::class)
|
||||
private inline fun MemScope.dumpPKey(
|
||||
evpPKey: CPointer<EVP_PKEY>,
|
||||
dumper: (CPointer<BIO>, CPointer<EVP_PKEY>) -> Unit
|
||||
): String? {
|
||||
val bio = BIO_new(BIO_s_mem()) ?: error("Failed to init mem BIO: ${getOpenSSLError()}")
|
||||
|
||||
dumper(bio, evpPKey)
|
||||
BIO_ctrl(bio, BIO_CTRL_FLUSH, 0, null)
|
||||
|
||||
val pKeyBuf = allocPointerTo<ByteVar>()
|
||||
BIO_ctrl(bio, BIO_CTRL_INFO, 0, pKeyBuf.ptr)
|
||||
|
||||
return pKeyBuf.value?.toKString().also { BIO_free(bio) }
|
||||
}
|
||||
|
||||
private fun MemScope.loadPKey(
|
||||
plainPemKey: String,
|
||||
reader: (CPointer<BIO>) -> CPointer<RSA>?
|
||||
): CPointer<RSA>? {
|
||||
val bio = BIO_new(BIO_s_mem()) ?: error("Failed to init mem BIO: ${getOpenSSLError()}")
|
||||
|
||||
return plainPemKey.encodeToByteArray().usePinned {
|
||||
BIO_write(bio, it.addressOf(0), it.get().size)
|
||||
reader(bio)
|
||||
}
|
||||
}
|
||||
|
||||
internal actual fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray {
|
||||
memScoped {
|
||||
val pubPKey = loadPKey(plainPubPemKey) {
|
||||
PEM_read_bio_RSA_PUBKEY(it, null, null, null)
|
||||
} ?: error("Failed to read pem key from BIO: ${getOpenSSLError()}")
|
||||
|
||||
val pinnedInput = input.pin()
|
||||
val encMsg = ByteArray(4096).pin()
|
||||
|
||||
val encMsgLen = RSA_public_encrypt(
|
||||
flen = pinnedInput.get().size,
|
||||
from = pinnedInput.addressOf(0).reinterpret(),
|
||||
to = encMsg.addressOf(0).reinterpret(),
|
||||
rsa = pubPKey,
|
||||
padding = RSA_PKCS1_PADDING
|
||||
)
|
||||
if (encMsgLen <= 0) {
|
||||
pinnedInput.unpin()
|
||||
encMsg.unpin()
|
||||
RSA_free(pubPKey)
|
||||
error("Failed to do rsa decrypt: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
return encMsg.get().copyOf(encMsgLen).also {
|
||||
pinnedInput.unpin()
|
||||
encMsg.unpin()
|
||||
RSA_free(pubPKey)
|
||||
}
|
||||
|
||||
/*if (1 != EVP_SealInit(
|
||||
ctx = evpCipherCtx,
|
||||
type = aes256CBC,
|
||||
ek = encKey.ptr,
|
||||
ekl = encKeyLen.ptr,
|
||||
pubk = ref(pubPKey),
|
||||
iv = iv.ptr,
|
||||
npubk = 1,
|
||||
)
|
||||
) {
|
||||
free(encKey.ptr, encKeyLen.ptr, iv.ptr)
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to init evp seal: ${getOpenSSLError()}")
|
||||
}
|
||||
println("total size: ${pinnedInput.get().size + 1 + EVP_MAX_IV_LENGTH}")
|
||||
val encMsgLen = alloc<size_tVar>().apply { value = 0u }
|
||||
val blockSize = alloc<size_tVar>().apply { value = 0u }
|
||||
|
||||
if (1 != EVP_EncryptUpdate(
|
||||
ctx = evpCipherCtx,
|
||||
out = encMsg.addressOf(encMsgLen.value.convert()).reinterpret(),
|
||||
outl = blockSize.ptr.reinterpret(),
|
||||
`in` = pinnedInput.addressOf(0).reinterpret(),
|
||||
inl = pinnedInput.get().size
|
||||
)
|
||||
) {
|
||||
pinnedInput.unpin()
|
||||
encMsg.unpin()
|
||||
free(encMsgLen.ptr, blockSize.ptr, encKey.ptr, encKeyLen.ptr, iv.ptr)
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to update evp seal: ${getOpenSSLError()}")
|
||||
}
|
||||
println("${encMsgLen.value}, ${blockSize.value}")
|
||||
encMsgLen.value += blockSize.value
|
||||
println("${encMsg.addressOf(0)}, ${encMsg.addressOf(encMsgLen.value.convert())}")
|
||||
|
||||
if (1 != EVP_SealFinal(
|
||||
ctx = evpCipherCtx,
|
||||
out = encMsg.addressOf(encMsgLen.value.convert()).reinterpret(),
|
||||
outl = blockSize.ptr.reinterpret()
|
||||
)
|
||||
) {
|
||||
pinnedInput.unpin()
|
||||
encMsg.unpin()
|
||||
free(encMsgLen.ptr, blockSize.ptr, encKey.ptr, encKeyLen.ptr, iv.ptr)
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to do final evp seal: ${getOpenSSLError()}")
|
||||
}
|
||||
println("${encMsgLen.value}, ${blockSize.value}")
|
||||
encMsgLen.value += blockSize.value
|
||||
|
||||
return encMsg.get().copyOf(encMsgLen.value.convert()).also {
|
||||
encMsg.unpin()
|
||||
pinnedInput.unpin()
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
}.toByteArray()*/
|
||||
}
|
||||
}
|
||||
|
||||
internal actual fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray {
|
||||
memScoped {
|
||||
val evpCipherCtx = EVP_CIPHER_CTX_new()
|
||||
?: error("Failed to create evp cipher context: ${getOpenSSLError()}")
|
||||
|
||||
val privKey = loadPKey(plainPrivPemKey) {
|
||||
PEM_read_bio_RSAPrivateKey(it, null, null, null)
|
||||
} ?: kotlin.run {
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to read pem key from BIO: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
val pinnedInput = input.pin()
|
||||
val encMsg = UByteArray(4096).pin()
|
||||
|
||||
val encMsgLen = RSA_private_decrypt(
|
||||
flen = pinnedInput.get().size,
|
||||
from = pinnedInput.addressOf(0).reinterpret(),
|
||||
to = encMsg.addressOf(0).reinterpret(),
|
||||
rsa = privKey,
|
||||
padding = RSA_PKCS1_PADDING
|
||||
)
|
||||
if (encMsgLen <= 0) {
|
||||
pinnedInput.unpin()
|
||||
encMsg.unpin()
|
||||
RSA_free(privKey)
|
||||
error("Failed to do rsa decrypt: ${getOpenSSLError()}")
|
||||
}
|
||||
|
||||
return encMsg.get().copyOf(encMsgLen).toByteArray().also {
|
||||
pinnedInput.unpin()
|
||||
encMsg.unpin()
|
||||
RSA_free(privKey)
|
||||
}
|
||||
|
||||
/*println(dumpPKey(privKey) { b, k -> PEM_write_bio_PKCS8PrivateKey(b, k, null, null, 0, null, null) })
|
||||
|
||||
val decKeyLen = EVP_PKEY_get_size(privKey)
|
||||
println("evp_pkey_size: $decKeyLen")
|
||||
val decKey = ByteArray(decKeyLen).pin()
|
||||
val pinnedIv = ByteArray(16).pin()
|
||||
|
||||
if (1 != EVP_OpenInit(
|
||||
ctx = evpCipherCtx,
|
||||
type = aes256CBC,
|
||||
ek = decKey.addressOf(0).reinterpret(),
|
||||
ekl = decKeyLen,
|
||||
iv = pinnedIv.addressOf(0).reinterpret(),
|
||||
priv = privKey
|
||||
)
|
||||
) {
|
||||
pinnedIv.unpin()
|
||||
decKey.unpin()
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to init evp open: ${getOpenSSLError()}")
|
||||
}
|
||||
println("init")
|
||||
|
||||
val pinnedInput = input.pin()
|
||||
val decMsg = ByteArray(
|
||||
pinnedInput.get().size + EVP_CIPHER_CTX_get_block_size(evpCipherCtx)
|
||||
).pin()
|
||||
val decMsgLen = alloc<size_tVar>().apply { value = 0u }
|
||||
val blockSize = alloc<size_tVar>().apply { value = 0u }
|
||||
|
||||
if (1 != EVP_DecryptUpdate(
|
||||
ctx = evpCipherCtx,
|
||||
out = decMsg.addressOf(0).reinterpret(),
|
||||
outl = blockSize.ptr.reinterpret(),
|
||||
`in` = pinnedInput.addressOf(0).reinterpret(),
|
||||
inl = pinnedInput.get().size
|
||||
)
|
||||
) {
|
||||
pinnedInput.unpin()
|
||||
decMsg.unpin()
|
||||
pinnedIv.unpin()
|
||||
decKey.unpin()
|
||||
free(decMsgLen.ptr, blockSize.ptr)
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to update evp open: ${getOpenSSLError()}")
|
||||
}
|
||||
decMsgLen.value += blockSize.value
|
||||
println("update")
|
||||
|
||||
if (1 != EVP_OpenFinal(
|
||||
ctx = evpCipherCtx,
|
||||
out = decMsg.addressOf(decMsgLen.value.convert()).reinterpret(),
|
||||
outl = blockSize.ptr.reinterpret()
|
||||
)
|
||||
) {
|
||||
pinnedInput.unpin()
|
||||
decMsg.unpin()
|
||||
pinnedIv.unpin()
|
||||
decKey.unpin()
|
||||
free(decMsgLen.ptr, blockSize.ptr)
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
error("Failed to do final evp open: ${getOpenSSLError()}")
|
||||
}
|
||||
decMsgLen.value += blockSize.value
|
||||
println("final")
|
||||
|
||||
return decMsg.get().copyOf(decMsgLen.value.convert()).also {
|
||||
decMsg.unpin()
|
||||
pinnedInput.unpin()
|
||||
pinnedIv.unpin()
|
||||
decKey.unpin()
|
||||
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||
}*/
|
||||
}
|
||||
}
|
19
mirai-core/src/nativeMain/kotlin/utils/freePointer.kt
Normal file
19
mirai-core/src/nativeMain/kotlin/utils/freePointer.kt
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import kotlinx.cinterop.CValuesRef
|
||||
import platform.posix.free
|
||||
|
||||
internal fun free(
|
||||
vararg refs: CValuesRef<*>
|
||||
) {
|
||||
refs.forEach(::free)
|
||||
}
|
27
mirai-core/src/nativeMain/kotlin/utils/getOpenSSLError.kt
Normal file
27
mirai-core/src/nativeMain/kotlin/utils/getOpenSSLError.kt
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import openssl.*
|
||||
|
||||
@OptIn(UnsafeNumber::class)
|
||||
internal fun getOpenSSLError(): String {
|
||||
memScoped {
|
||||
val bio = BIO_new(BIO_s_mem())
|
||||
val errBuffer = allocPointerTo<ByteVar>()
|
||||
|
||||
ERR_print_errors(bio)
|
||||
BIO_ctrl(bio, BIO_CTRL_FLUSH, 0, null)
|
||||
BIO_ctrl(bio, BIO_CTRL_INFO, 0, errBuffer.ptr)
|
||||
|
||||
return errBuffer.value?.toKString()?.also { BIO_free(bio) } ?: "openssl error: no message"
|
||||
}
|
||||
}
|
19
mirai-core/src/nativeMain/kotlin/utils/ref.kt
Normal file
19
mirai-core/src/nativeMain/kotlin/utils/ref.kt
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
/**
|
||||
* returns reference to a pointer(**variable), equivalent to `my_type **a = &myTypePtr`
|
||||
*/
|
||||
internal inline fun <T : CPointed> NativePlacement.ref(ptr: CPointer<T>): CPointer<CPointerVar<T>> {
|
||||
return allocPointerTo<T>().apply { value = ptr }.ptr
|
||||
}
|
Loading…
Reference in New Issue
Block a user