[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:
StageGuard 2023-05-21 20:45:46 +08:00 committed by GitHub
parent dafa25d611
commit 60d360baad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2147 additions and 748 deletions

View File

@ -5604,8 +5604,9 @@ public final class net/mamoe/mirai/utils/BotConfiguration$MiraiProtocol : java/l
public final class net/mamoe/mirai/utils/DeviceInfo { public final class net/mamoe/mirai/utils/DeviceInfo {
public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Companion; 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)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 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;)Lnet/mamoe/mirai/utils/DeviceInfo;
public static final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)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 fun hashCode ()I
public static final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo; 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 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 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 synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V 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 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 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;)Lnet/mamoe/mirai/utils/DeviceInfo;
public final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)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 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 ()Lnet/mamoe/mirai/utils/DeviceInfo;
public final fun random (Lkotlin/random/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; 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 final class net/mamoe/mirai/utils/DeviceInfoKt {
public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B 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 { public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests {

View File

@ -5604,8 +5604,9 @@ public final class net/mamoe/mirai/utils/BotConfiguration$MiraiProtocol : java/l
public final class net/mamoe/mirai/utils/DeviceInfo { public final class net/mamoe/mirai/utils/DeviceInfo {
public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Companion; 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)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 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;)Lnet/mamoe/mirai/utils/DeviceInfo;
public static final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)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 fun hashCode ()I
public static final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo; 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 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 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 synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V 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 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 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;)Lnet/mamoe/mirai/utils/DeviceInfo;
public final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)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 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 ()Lnet/mamoe/mirai/utils/DeviceInfo;
public final fun random (Lkotlin/random/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; 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 final class net/mamoe/mirai/utils/DeviceInfoKt {
public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B 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 { public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests {

View File

@ -12,20 +12,28 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.utils.io.core.* import io.ktor.utils.io.core.*
import kotlinx.serialization.* import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.Transient
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.protobuf.ProtoNumber 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.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.random.Random 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, display: ByteArray,
product: ByteArray, product: ByteArray,
device: ByteArray, device: ByteArray,
@ -45,9 +53,9 @@ public expect class DeviceInfo(
wifiSSID: ByteArray, wifiSSID: ByteArray,
imsiMd5: ByteArray, imsiMd5: ByteArray,
imei: String, imei: String,
apn: ByteArray apn: ByteArray,
androidId: ByteArray,
) { ) {
public val display: ByteArray public val display: ByteArray
public val product: ByteArray public val product: ByteArray
public val device: ByteArray public val device: ByteArray
@ -68,15 +76,14 @@ public expect class DeviceInfo(
public val imsiMd5: ByteArray public val imsiMd5: ByteArray
public val imei: String public val imei: String
public val apn: ByteArray public val apn: ByteArray
public val androidId: ByteArray public val androidId: ByteArray
public val ipAddress: ByteArray public val ipAddress: ByteArray
@Transient @Transient
@MiraiInternalApi @MiraiInternalApi
public val guid: ByteArray public val guid: ByteArray
// @Serializable: use DeviceInfoVersionSerializer in commonMain. // @Serializable: use DeviceInfoVersionSerializer in commonMain.
public class Version( public class Version(
incremental: ByteArray = "5891938".toByteArray(), incremental: ByteArray = "5891938".toByteArray(),
@ -98,6 +105,10 @@ public expect class DeviceInfo(
* @since 2.9 * @since 2.9
*/ */
override fun hashCode(): Int override fun hashCode(): Int
internal companion object {
fun serializer(): KSerializer<Version>
}
} }
public companion object { public companion object {
@ -119,7 +130,27 @@ public expect class DeviceInfo(
@JvmStatic @JvmStatic
public fun random(random: Random): DeviceInfo public fun random(random: Random): DeviceInfo
@Deprecated(DeviceInfoConstructorDeprecationMessage, level = DeprecationLevel.WARNING)
@DeprecatedSinceMirai(warningSince = "2.15") // planned internal
public fun serializer(): KSerializer<DeviceInfo> 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 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 { internal object DeviceInfoCommonImpl {
@Suppress("DEPRECATION")
fun randomDeviceInfo(random: Random) = DeviceInfo( fun randomDeviceInfo(random: Random) = DeviceInfo(
display = "MIRAI.${getRandomString(6, '0'..'9', random)}.001".toByteArray(), display = "MIRAI.${getRandomString(6, '0'..'9', random)}.001".toByteArray(),
product = "mirai".toByteArray(), product = "mirai".toByteArray(),
@ -159,7 +257,8 @@ internal object DeviceInfoCommonImpl {
wifiSSID = "<unknown ssid>".toByteArray(), wifiSSID = "<unknown ssid>".toByteArray(),
imsiMd5 = getRandomByteArray(16, random).md5(), imsiMd5 = getRandomByteArray(16, random).md5(),
imei = "86${getRandomIntString(12, random)}".let { it + luhn(it) }, 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 (deviceInfo === other) return true
if (!isSameType(this, other)) return false 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 (!display.contentEquals(other.display)) return false
if (!product.contentEquals(other.product)) return false if (!product.contentEquals(other.product)) return false
if (!device.contentEquals(other.device)) return false if (!device.contentEquals(other.device)) return false
@ -207,7 +308,10 @@ internal object DeviceInfoCommonImpl {
if (!imsiMd5.contentEquals(other.imsiMd5)) return false if (!imsiMd5.contentEquals(other.imsiMd5)) return false
if (imei != other.imei) return false if (imei != other.imei) return false
if (!apn.contentEquals(other.apn)) 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) @OptIn(MiraiInternalApi::class)
@ -234,273 +338,7 @@ internal object DeviceInfoCommonImpl {
result = 31 * result + imei.hashCode() result = 31 * result + imei.hashCode()
result = 31 * result + apn.contentHashCode() result = 31 * result + apn.contentHashCode()
result = 31 * result + guid.contentHashCode() result = 31 * result + guid.contentHashCode()
result = 31 * result + androidId.contentHashCode()
return result 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
)
)
*/

View 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())
)
}
}

View File

@ -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
)
}
)

View File

@ -42,7 +42,7 @@ class CommonDeviceInfoTest {
} }
@Test @Test
fun `can serialize and deserialize v2`() { fun `can serialize and deserialize v3`() {
val device = DeviceInfo.random() val device = DeviceInfo.random()
assertEquals(device, DeviceInfoManager.deserialize(DeviceInfoManager.serialize(device))) assertEquals(device, DeviceInfoManager.deserialize(DeviceInfoManager.serialize(device)))
} }
@ -88,7 +88,7 @@ class CommonDeviceInfoTest {
*/ */
val element = DeviceInfoManager.toJsonElement(device) 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 val imsiMd5 = element.jsonObject["data"]!!.jsonObject["imsiMd5"]!!.jsonPrimitive.content
assertEquals( assertEquals(

View File

@ -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
]
}

View File

@ -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 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * 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 package net.mamoe.mirai.utils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import kotlin.random.Random import kotlin.random.Random
@Serializable @Serializable(DeviceInfoV1LegacySerializer::class)
public actual class DeviceInfo actual constructor( 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 display: ByteArray,
public actual val product: ByteArray, public actual val product: ByteArray,
public actual val device: ByteArray, public actual val device: ByteArray,
@ -36,9 +40,48 @@ public actual class DeviceInfo actual constructor(
public actual val wifiSSID: ByteArray, public actual val wifiSSID: ByteArray,
public actual val imsiMd5: ByteArray, public actual val imsiMd5: ByteArray,
public actual val imei: String, 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) public actual val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123)
init { init {
@ -100,7 +143,9 @@ public actual class DeviceInfo actual constructor(
this.writeText(DeviceInfoManager.serialize(it, json)) 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 { public actual fun random(random: Random): DeviceInfo {
return DeviceInfoCommonImpl.randomDeviceInfo(random) 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 { actual override fun hashCode(): Int {
return DeviceInfoCommonImpl.hashCodeImpl(this) return DeviceInfoCommonImpl.hashCodeImpl(this)
} }
@Suppress("ClassName")
@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
public object `$serializer` : KSerializer<DeviceInfo> by DeviceInfoV1LegacySerializer
} }

View File

@ -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 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * 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 kotlinx.serialization.json.Json
import net.mamoe.mirai.utils.DeviceInfo.Companion.loadAsDeviceInfo 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 org.junit.jupiter.api.io.TempDir
import java.io.File import java.io.File
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue
class JvmDeviceInfoTest { class JvmDeviceInfoTest {
@ -22,7 +24,7 @@ class JvmDeviceInfoTest {
lateinit var dir: File lateinit var dir: File
@Test @Test
fun `can write and read v2`() { fun `can write and read`() {
val device = DeviceInfo.random() val device = DeviceInfo.random()
val file = dir.resolve("device.json") val file = dir.resolve("device.json")
@ -31,11 +33,161 @@ class JvmDeviceInfoTest {
} }
@Test @Test
fun `can read legacy v1`() { fun `can write read legacy v1`() {
val device = DeviceInfo.random() val device = DeviceInfo.random()
val file = dir.resolve("device.json") val file = dir.resolve("device.json")
file.writeText(Json.encodeToString(DeviceInfo.serializer(), device)) val encoded = Json.encodeToString(
assertEquals(device, file.loadAsDeviceInfo()) 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)
} }
} }

View File

@ -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() })
}
}

View File

@ -41,7 +41,10 @@ public actual abstract class AbstractBotConfiguration { // open for Java
if (!file.exists()) { if (!file.exists()) {
file.writeText(DeviceInfoManager.serialize(DeviceInfo.random(), BotConfiguration.json)) 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))
}
} }
} }

View File

@ -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 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * 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 kotlinx.serialization.Transient
import kotlin.random.Random import kotlin.random.Random
@Serializable @Serializable(DeviceInfoV1LegacySerializer::class)
public actual class DeviceInfo actual constructor( 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 display: ByteArray,
public actual val product: ByteArray, public actual val product: ByteArray,
public actual val device: ByteArray, public actual val device: ByteArray,
@ -34,9 +37,9 @@ public actual class DeviceInfo actual constructor(
public actual val wifiSSID: ByteArray, public actual val wifiSSID: ByteArray,
public actual val imsiMd5: ByteArray, public actual val imsiMd5: ByteArray,
public actual val imei: String, 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) public actual val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123)
init { init {
@ -99,6 +102,22 @@ public actual class DeviceInfo actual constructor(
public actual fun random(random: Random): DeviceInfo { public actual fun random(random: Random): DeviceInfo {
return DeviceInfoCommonImpl.randomDeviceInfo(random) 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)
} }
/** /**

View File

@ -25,7 +25,11 @@ public expect fun currentTimeMillis(): Long
*/ */
public fun currentTimeSeconds(): Long = currentTimeMillis() / 1000 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. // 临时使用, 待 Kotlin Duration 稳定后使用 Duration.

View File

@ -19,10 +19,10 @@ private val timeFormat: SimpleDateFormat by threadLocal {
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) 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) { return if (format == null) {
timeFormat.format(Date()) timeFormat.format(Date(epochTimeMillis))
} else { } else {
SimpleDateFormat(format, Locale.getDefault()).format(Date()) SimpleDateFormat(format, Locale.getDefault()).format(Date(epochTimeMillis))
} }
} }

View File

@ -32,18 +32,26 @@ public actual fun currentTimeMillis(): Long {
private val timeLock = ReentrantLock() private val timeLock = ReentrantLock()
@OptIn(UnsafeNumber::class) public actual fun formatTime(epochTimeMillis: Long, format: String?): String = timeLock.withLock {
public actual fun currentTimeFormatted(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 { memScoped {
val timeT = alloc<time_tVar>() val timeT = alloc<time_tVar>()
time(timeT.ptr) timeT.value = epochTimeMillis / 1000
// http://www.cplusplus.com/reference/clibrary/ctime/localtime/ // http://www.cplusplus.com/reference/clibrary/ctime/localtime/
// tm returns a static pointer which doesn't need to free // tm returns a static pointer which doesn't need to free
val tm = localtime(timeT.ptr) // localtime is not thread-safe val tm = localtime(timeT.ptr) // localtime is not thread-safe
val bb = allocArray<ByteVar>(40) val bb = allocArray<ByteVar>(40)
strftime(bb, 40, "%Y-%m-%d %H:%M:%S", tm);
strftime(bb, 40, strftimeFormat, tm);
bb.toKString() bb.toKString()
} }

View File

@ -10,6 +10,8 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
internal class TimeUtilsTest { internal class TimeUtilsTest {
@ -23,6 +25,59 @@ internal class TimeUtilsTest {
@Test @Test
fun `can get currentTimeFormatted`() { fun `can get currentTimeFormatted`() {
// 2022-28-26 18:28:28 // 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}$"""))
}
} }
} }

View File

@ -102,7 +102,7 @@ internal open class QQAndroidClient(
val apkVersionName: ByteArray get() = protocol.ver.toByteArray() //"8.4.18".toByteArray() 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()) private val sequenceId: AtomicInt = atomic(getRandomUnsignedInt())
@ -166,7 +166,15 @@ internal open class QQAndroidClient(
var reserveUinInfo: ReserveUinInfo? = null var reserveUinInfo: ReserveUinInfo? = null
var t402: ByteArray? = null var t402: ByteArray? = null
lateinit var t104: ByteArray lateinit var t104: ByteArray
internal val t104Initialized get() = ::t104.isInitialized
var t543: ByteArray? = null
var t547: ByteArray? = null var t547: ByteArray? = null
/**
* t545
*/
var qimei16: String? = null
var qimei36: String? = null
} }
internal val QQAndroidClient.apkId: ByteArray get() = protocol.apkId.toByteArray() internal val QQAndroidClient.apkId: ByteArray get() = protocol.apkId.toByteArray()

View File

@ -10,7 +10,6 @@
package net.mamoe.mirai.internal.network.components package net.mamoe.mirai.internal.network.components
import io.ktor.utils.io.core.* 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.network.component.ComponentKey
import net.mamoe.mirai.internal.utils.MiraiProtocolInternal import net.mamoe.mirai.internal.utils.MiraiProtocolInternal
import net.mamoe.mirai.internal.utils.io.writeShortLVString import net.mamoe.mirai.internal.utils.io.writeShortLVString
@ -61,7 +60,7 @@ internal class CacheValidatorImpl(
val device = ssoProcessorContext.device val device = ssoProcessorContext.device
@Suppress("INVISIBLE_MEMBER") @Suppress("INVISIBLE_MEMBER")
writeFully(ProtoBufForCache.encodeToByteArray(DeviceInfo.serializer(), device)) writeFully(device.serializeToString().encodeToByteArray())
}.let { pkg -> }.let { pkg ->
try { try {
pkg.readBytes() pkg.readBytes()

View File

@ -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
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha 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.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.network.*
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
@ -140,6 +142,8 @@ internal open class SsoProcessorImpl(
ssoContext.bot.components[BotClientHolder].client = value ssoContext.bot.components[BotClientHolder].client = value
} }
private val qimeiLogger by lazy { ssoContext.bot.network.logger.subLogger("QimeiApi") }
override val ssoSession: SsoSession get() = client override val ssoSession: SsoSession get() = client
private val components get() = ssoContext.bot.components private val components get() = ssoContext.bot.components
@ -199,6 +203,12 @@ internal open class SsoProcessorImpl(
components[BdhSessionSyncer].loadServerListFromCache() components[BdhSessionSyncer].loadServerListFromCache()
try {
ssoContext.bot.requestQimei(qimeiLogger)
} catch (exception: Throwable) {
qimeiLogger.warning("Cannot get qimei from server.", exception)
}
// try fast login // try fast login
if (client.wLoginSigInfoInitialized) { if (client.wLoginSigInfoInitialized) {
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh() ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()

View File

@ -18,7 +18,10 @@ import net.mamoe.mirai.internal.utils.GuidSource
import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag
import net.mamoe.mirai.internal.utils.NetworkType import net.mamoe.mirai.internal.utils.NetworkType
import net.mamoe.mirai.internal.utils.guidFlag 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 net.mamoe.mirai.utils.*
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
import kotlin.random.Random import kotlin.random.Random
@ -49,15 +52,15 @@ internal fun TlvMap.smartToString(leadingLineBreak: Boolean = true, sorted: Bool
@JvmInline @JvmInline
internal value class Tlv(val value: ByteArray) internal value class Tlv(val value: ByteArray)
internal fun TlvMapWriter.t1(uin: Long, ip: ByteArray) { internal fun TlvMapWriter.t1(uin: Long, timeSeconds: Int, ipv4: ByteArray) {
require(ip.size == 4) { "ip.size must == 4" } require(ipv4.size == 4) { "ip.size must == 4" }
tlv(0x01) { tlv(0x01) {
writeShort(1) // _ip_ver writeShort(1) // _ip_ver
writeInt(Random.nextInt()) writeInt(Random.nextInt())
writeInt(uin.toInt()) writeInt(uin.toInt())
writeInt(currentTimeSeconds().toInt()) writeInt(timeSeconds)
writeFully(ip) writeFully(ipv4)
writeShort(0) writeShort(0)
} }
} }
@ -192,6 +195,7 @@ internal fun TlvMapWriter.t106(
client.subAppId /* maybe 1*/, client.subAppId /* maybe 1*/,
client.appClientVersion, client.appClientVersion,
client.uin, client.uin,
client.device.ipAddress,
true, true,
passwordMd5, passwordMd5,
0, 0,
@ -220,6 +224,7 @@ internal fun TlvMapWriter.t106(
subAppId: Long, subAppId: Long,
appClientVersion: Int = 0, appClientVersion: Int = 0,
uin: Long, uin: Long,
ipv4: ByteArray,
isSavePassword: Boolean = true, isSavePassword: Boolean = true,
passwordMd5: ByteArray, passwordMd5: ByteArray,
salt: Long, salt: Long,
@ -233,6 +238,7 @@ internal fun TlvMapWriter.t106(
passwordMd5.requireSize(16) passwordMd5.requireSize(16)
tgtgtKey.requireSize(16) tgtgtKey.requireSize(16)
guid?.requireSize(16) guid?.requireSize(16)
ipv4.requireSize(4)
tlv(0x106) { tlv(0x106) {
encryptAndWrite( encryptAndWrite(
@ -252,7 +258,7 @@ internal fun TlvMapWriter.t106(
} }
writeInt(currentTimeSeconds().toInt()) writeInt(currentTimeSeconds().toInt())
writeFully(ByteArray(4)) // ip // no need to write actual ip writeFully(ipv4) //
writeByte(isSavePassword.toByte()) writeByte(isSavePassword.toByte())
writeFully(passwordMd5) writeFully(passwordMd5)
writeFully(tgtgtKey) writeFully(tgtgtKey)
@ -368,16 +374,18 @@ internal fun TlvMapWriter.t174(
internal fun TlvMapWriter.t17a( internal fun TlvMapWriter.t17a(
value: Int = 0 smsAppId: Int = 0
) { ) {
tlv(0x17a) { tlv(0x17a) {
writeInt(value) writeInt(smsAppId)
} }
} }
internal fun TlvMapWriter.t197() { internal fun TlvMapWriter.t197(
devLockMobileType: Byte = 0
) {
tlv(0x197) { 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 internal fun TlvMapWriter.t544( // 1334
) { ) {
tlv(0x544) { tlv(0x544) {

View File

@ -216,6 +216,7 @@ internal class WtLogin {
tlvMap[0x403]?.let { bot.client.randSeed = it } tlvMap[0x403]?.let { bot.client.randSeed = it }
tlvMap[0x402]?.let { bot.client.t402 = it } tlvMap[0x402]?.let { bot.client.t402 = it }
tlvMap[0x546]?.let { bot.client.analysisTlv546(it) } tlvMap[0x546]?.let { bot.client.analysisTlv546(it) }
tlvMap[0x543]?.let { bot.client.t543 = it }
// tlvMap[0x402]?.let { t402 -> // tlvMap[0x402]?.let { t402 ->
// bot.client.G = buildPacket { // bot.client.G = buildPacket {
// writeFully(bot.client.device.guid) // writeFully(bot.client.device.guid)

View File

@ -71,6 +71,7 @@ internal object WtLogin10 : WtLoginExt {
) )
//t112(client.account.phoneNumber.encodeToByteArray()) //t112(client.account.phoneNumber.encodeToByteArray())
t143(client.wLoginSigInfo.d2.data) t143(client.wLoginSigInfo.d2.data)
t145(client.device.guid)
t142(client.apkId) t142(client.apkId)
t154(sequenceId) t154(sequenceId)
t18(appId, uin = client.uin) t18(appId, uin = client.uin)

View File

@ -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.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.utils._writeTlvMap import net.mamoe.mirai.utils._writeTlvMap
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.toByteArray
import kotlin.math.abs import kotlin.math.abs
import kotlin.random.Random import kotlin.random.Random
@ -26,7 +28,7 @@ internal object WtLogin15 : WtLoginExt {
client: QQAndroidClient, client: QQAndroidClient,
) = WtLogin.ExchangeEmp.buildOutgoingUniPacket( ) = WtLogin.ExchangeEmp.buildOutgoingUniPacket(
client, bodyType = 2, key = ByteArray(16), remark = "15:refresh-keys" client, bodyType = 2, key = ByteArray(16), remark = "15:refresh-keys"
) { ) { sequenceId ->
// writeSsoPacket(client, client.subAppId, WtLogin.ExchangeEmp.commandName, sequenceId = sequenceId) { // writeSsoPacket(client, client.subAppId, WtLogin.ExchangeEmp.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket( writeOicqRequestPacket(
client, client,
@ -53,10 +55,9 @@ internal object WtLogin15 : WtLoginExt {
// "").hexToBytes()) // "").hexToBytes())
// return@writeOicqRequestPacket // return@writeOicqRequestPacket
t18(appId, uin = client.uin) t18(appId, client.appClientVersion, uin = client.uin)
t1(client.uin, ByteArray(4)) t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress)
// t106(client = client)
t106(client.wLoginSigInfo.encryptA1!!) t106(client.wLoginSigInfo.encryptA1!!)
// kotlin.run { // kotlin.run {
// val key = (client.account.passwordMd5 + ByteArray(4) + client.uin.toInt().toByteArray()).md5() // 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(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, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
//t100(appId, 1, client.appClientVersion, client.ssoVersion, mainSigMap = 1048768) //t100(appId, 1, client.appClientVersion, client.ssoVersion, mainSigMap = 1048768)
@ -90,16 +94,21 @@ internal object WtLogin15 : WtLoginExt {
t107(0) t107(0)
//t108(client.ksid) // 第一次 exchange 没有 108 if (client.ksid.isNotEmpty()) {
t108(client.ksid)
}
t144(client) t144(client)
t142(client.apkId) t142(client.apkId)
if (client.uin !in 10000L..4000000000L) {
t112(client.uin.toByteArray())
}
t145(client.device.guid) t145(client.device.guid)
val noPicSig = val noPicSig =
client.wLoginSigInfo.noPicSig ?: error("Internal error: doing exchange emp 15 while noPicSig=null") client.wLoginSigInfo.noPicSig ?: error("Internal error: doing exchange emp 15 while noPicSig=null")
t16a(noPicSig) t16a(noPicSig)
t154(0) t154(sequenceId)
t141(client.device.simInfo, client.networkType, client.device.apn) t141(client.device.simInfo, client.networkType, client.device.apn)
t8(2052) t8(2052)
t511() t511()
@ -112,20 +121,22 @@ internal object WtLogin15 : WtLoginExt {
uin = client.uin, uin = client.uin,
guid = client.device.guid, guid = client.device.guid,
dpwd = client.dpwd, dpwd = client.dpwd,
appId = 1, appId = appId,
subAppId = 16, subAppId = 1,
randomSeed = client.randSeed randomSeed = client.randSeed
) )
t187(client.device.macAddress) t187(client.device.macAddress)
t188(client.device.androidId) t188(client.device.androidId)
t194(client.device.imsiMd5) t194(client.device.imsiMd5)
// ignored t201 cuz SetNeedForPayToken is never called.
t202(client.device.wifiBSSID, client.device.wifiSSID) t202(client.device.wifiBSSID, client.device.wifiSSID)
t516() t516()
t521() // new t521() // new
t525(client.loginExtraData) // new t525(client.loginExtraData) // new
//t544() // new //t544() // new 810_f
t545(client.qimei16 ?: client.device.imei)
} }
} }

View File

@ -43,7 +43,13 @@ internal object WtLogin8 : WtLoginExt {
t174(t174) t174(t174)
t17a(9) t17a(9)
t197() 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) }
} }
} }
} }

View File

@ -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.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.utils._writeTlvMap import net.mamoe.mirai.utils._writeTlvMap
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.toByteArray
internal object WtLogin9 : WtLoginExt { internal object WtLogin9 : WtLoginExt {
private const val appId = 16L private const val appId = 16L
@ -28,21 +30,16 @@ internal object WtLogin9 : WtLoginExt {
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, commandId = 0x0810) { writeOicqRequestPacket(client, commandId = 0x0810) {
writeShort(9) // subCommand writeShort(9) // subCommand
var tlvCount = if (allowSlider) 0x18 else 0x17;
val useEncryptA1AndNoPicSig = val useEncryptA1AndNoPicSig =
client.wLoginSigInfoInitialized client.wLoginSigInfoInitialized
&& client.wLoginSigInfo.noPicSig != null && client.wLoginSigInfo.noPicSig != null
&& client.wLoginSigInfo.encryptA1 != null && client.wLoginSigInfo.encryptA1 != null
if (useEncryptA1AndNoPicSig) {
tlvCount++;
}
// writeShort(tlvCount.toShort()) // count of TLVs, probably ignored by server?
//writeShort(LoginType.PASSWORD.value.toShort()) //writeShort(LoginType.PASSWORD.value.toShort())
_writeTlvMap { _writeTlvMap {
t18(appId, client.appClientVersion, client.uin) t18(appId, client.appClientVersion, client.uin)
t1(client.uin, client.device.ipAddress) t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress)
if (useEncryptA1AndNoPicSig) { if (useEncryptA1AndNoPicSig) {
t106(client.wLoginSigInfo.encryptA1!!) t106(client.wLoginSigInfo.encryptA1!!)
@ -63,25 +60,31 @@ internal object WtLogin9 : WtLoginExt {
t116(client.miscBitMap, client.subSigMap) t116(client.miscBitMap, client.subSigMap)
t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap) t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
t107(0) t107(0)
t108(client.device.imei.toByteArray()) if (client.ksid.isNotEmpty()) {
t108(client.ksid)
}
// t108(byteArrayOf()) // t108(byteArrayOf())
// ignored: t104() if (client.t104Initialized) {
t104(client.t104)
}
t142(client.apkId) t142(client.apkId)
// if login with non-number uin // if login with non-number uin
// t112() if (client.uin !in 10000L..4000000000L) {
t112(client.uin.toByteArray())
}
t144(client) t144(client)
//this.build().debugPrint("傻逼") //this.build().debugPrint("傻逼")
t145(client.device.guid) t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5) t147(appId, client.apkVersionName, client.apkSignatureMd5)
/*
if (client.miscBitMap and 0x80 != 0) { if (client.miscBitMap and 0x80 != 0) {
t166(1) t166(1) // com.tencent.luggage.wxa.me.e.CTRL_INDEX
} }
*/
if (useEncryptA1AndNoPicSig) { if (useEncryptA1AndNoPicSig) {
t16a(client.wLoginSigInfo.noPicSig!!) t16a(client.wLoginSigInfo.noPicSig!!)
} }
@ -94,7 +97,17 @@ internal object WtLogin9 : WtLoginExt {
// ignored t172 because rollbackSig is null // ignored t172 because rollbackSig is null
// ignored t185 because loginType is not SMS // 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) t187(client.device.macAddress)
t188(client.device.androidId) t188(client.device.androidId)
@ -103,8 +116,8 @@ internal object WtLogin9 : WtLoginExt {
t191() t191()
} }
/*
t201(N = byteArrayOf())*/ //t201(N = byteArrayOf())
t202(client.device.wifiBSSID, client.device.wifiSSID) t202(client.device.wifiBSSID, client.device.wifiSSID)
@ -116,6 +129,8 @@ internal object WtLogin9 : WtLoginExt {
t521() t521()
t525() t525()
t545(client.qimei16 ?: client.device.imei)
// t548()
// this.build().debugPrint("傻逼") // this.build().debugPrint("傻逼")
// ignored t318 because not logging in by QR // ignored t318 because not logging in by QR
@ -138,7 +153,7 @@ internal object WtLogin9 : WtLoginExt {
_writeTlvMap { _writeTlvMap {
t18(appId, client.appClientVersion, client.uin) 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) t106(data.tmpPwd)

View 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
)

View File

@ -18,6 +18,7 @@ internal class MiraiProtocolInternal(
var apkId: String, var apkId: String,
var id: Long, var id: Long,
var ver: String, var ver: String,
var buildVer: String,
var sdkVer: String, var sdkVer: String,
var miscBitMap: Int, var miscBitMap: Int,
var subSigMap: Int, var subSigMap: Int,
@ -25,6 +26,7 @@ internal class MiraiProtocolInternal(
var sign: String, var sign: String,
var buildTime: Long, var buildTime: Long,
var ssoVersion: Int, var ssoVersion: Int,
var appKey: String,
var supportsQRLogin: Boolean, var supportsQRLogin: Boolean,
// don't change property signatures, used externally. // don't change property signatures, used externally.
@ -38,25 +40,28 @@ internal class MiraiProtocolInternal(
protocols[protocol] ?: error("Internal Error: Missing protocol $protocol") protocols[protocol] ?: error("Internal Error: Missing protocol $protocol")
init { init {
//Updated from MiraiGo (2023/3/7) //Updated from 8.9.35 (2023/4/9)
protocols[MiraiProtocol.ANDROID_PHONE] = MiraiProtocolInternal( protocols[MiraiProtocol.ANDROID_PHONE] = MiraiProtocolInternal(
apkId = "com.tencent.mobileqq", apkId = "com.tencent.mobileqq",
id = 537151682, id = 537153295,
ver = "8.9.33.10335", ver = "8.9.35",
sdkVer = "6.0.0.2534", buildVer = "8.9.35.10440",
sdkVer = "6.0.0.2535",
miscBitMap = 150470524, miscBitMap = 150470524,
subSigMap = 0x10400, 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", sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
buildTime = 1673599898L, buildTime = 1676531414L,
ssoVersion = 19, ssoVersion = 19,
appKey = "0S200MNJT807V3GE",
supportsQRLogin = false, supportsQRLogin = false,
) )
//Updated from MiraiGo (2023/3/7) //Updated from MiraiGo (2023/3/7)
protocols[MiraiProtocol.ANDROID_PAD] = MiraiProtocolInternal( protocols[MiraiProtocol.ANDROID_PAD] = MiraiProtocolInternal(
apkId = "com.tencent.mobileqq", apkId = "com.tencent.mobileqq",
id = 537151218, id = 537151218,
ver = "8.9.33.10335", ver = "8.9.33",
buildVer = "8.9.33.10335",
sdkVer = "6.0.0.2534", sdkVer = "6.0.0.2534",
miscBitMap = 150470524, miscBitMap = 150470524,
subSigMap = 0x10400, 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", sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
buildTime = 1673599898L, buildTime = 1673599898L,
ssoVersion = 19, ssoVersion = 19,
appKey = "0S200MNJT807V3GE",
supportsQRLogin = false, supportsQRLogin = false,
) )
//Updated from MiraiGo (2023/3/24) //Updated from MiraiGo (2023/3/24)
@ -71,6 +77,7 @@ internal class MiraiProtocolInternal(
apkId = "com.tencent.qqlite", apkId = "com.tencent.qqlite",
id = 537065138, id = 537065138,
ver = "2.0.8", ver = "2.0.8",
buildVer = "2.0.8",
sdkVer = "6.0.0.2365", sdkVer = "6.0.0.2365",
miscBitMap = 16252796, miscBitMap = 16252796,
subSigMap = 0x10400, 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", sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
buildTime = 1559564731L, buildTime = 1559564731L,
ssoVersion = 5, ssoVersion = 5,
appKey = "",
supportsQRLogin = true, supportsQRLogin = true,
) )
protocols[MiraiProtocol.IPAD] = MiraiProtocolInternal( protocols[MiraiProtocol.IPAD] = MiraiProtocolInternal(
apkId = "com.tencent.minihd.qq", apkId = "com.tencent.minihd.qq",
id = 537151363, id = 537151363,
ver = "8.9.33.614", ver = "8.9.33",
buildVer = "8.9.33.614",
sdkVer = "6.0.0.2433", sdkVer = "6.0.0.2433",
miscBitMap = 150470524, miscBitMap = 150470524,
subSigMap = 66560, 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", sign = "AA 39 78 F4 1F D9 6F F9 91 4A 66 9E 18 64 74 C7",
buildTime = 1640921786L, buildTime = 1640921786L,
ssoVersion = 12, ssoVersion = 12,
appKey = "",
supportsQRLogin = false, supportsQRLogin = false,
) )
protocols[MiraiProtocol.MACOS] = MiraiProtocolInternal( protocols[MiraiProtocol.MACOS] = MiraiProtocolInternal(
apkId = "com.tencent.qq", apkId = "com.tencent.qq",
id = 0x2003ca32, id = 0x2003ca32,
ver = "6.7.9", ver = "6.7.9",
buildVer = "6.7.9",
sdkVer = "6.2.0.1023", sdkVer = "6.2.0.1023",
miscBitMap = 0x7ffc, miscBitMap = 0x7ffc,
subSigMap = 66560, subSigMap = 66560,
@ -104,6 +115,7 @@ internal class MiraiProtocolInternal(
sign = "com.tencent.qq".encodeToByteArray().toUHexString(" "), sign = "com.tencent.qq".encodeToByteArray().toUHexString(" "),
buildTime = 0L, buildTime = 0L,
ssoVersion = 7, ssoVersion = 7,
appKey = "",
supportsQRLogin = true, supportsQRLogin = true,
) )
} }

View 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

View 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

View 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())
}
}

View 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())
}
}

View 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)
}

View 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-----")
}
)
}

View File

@ -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 # -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/ \ -I/usr/local/include/ \
-IC:/openssl/include/ \ -IC:/openssl/include/ \
-IC:/vcpkg/installed/x64-windows/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);
}

View 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)
}
}
}

View 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)
}*/
}
}

View 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)
}

View 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"
}
}

View 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
}