mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-09 19:50:27 +08:00
[core] Update login protocol, deprecate DeviceInfo constructor and serializer (#2613)
* [core] update protocol * [core] fix t106 decryption key * [core] handle t543 * [core] revert t106 * [core] revert native test pow data * [core] split build version and apk version name * [core] pass time to t1 instead of time diff * [core] remove unused t106 writer & rename param ip to ipv4 of t1 and t106 * [core] implement qimei for t545 TODO: aes and rsa crypto for nativeMain * [core] fix base64 decode for android in rsa * [core] fix unix timestamp parsing on native and add more tests * [api] DeviceInfo move `androidId` to main constructor and adjust test. * [api] do not request qimei when protocol is neither ANDROID_PHONE nor ANDROID_PAD * [api] implement aes crypto for native target * [api] rsa crypto for multi-platform * [api] crypto test * [api] move freePointer util * [api] openssl api compatibility * [core] add explicit `androidId` param * [core] remove unused `tlvCount` * [core] optimize crypto * [core] move Qimei to network package * [core] move appId to protocol * [core] lazily initialize qimeiLogger * [core] write byte array to BIO mem * [core] move qimei to client, add direct serializer for DeviceInfo * [core] optimize DeviceInfoDelegateSerializer * [core] real user-agent when requesting qimei * [core] use `DeviceInfo.version.release` * [core] remove unused wtlogin packet * [core] do what constructor serializer should do * [core] fix endless cache validation caused by not upgrading device info file. * [core] request qimei before fast-login * [core] tlv order * [core] remove wrong tests and print more detail when deserialize failed. * [core] device info upgrade for native * [core] request qimei after validating cache * [core] DeviceInfo compatibility * [core] DeviceInfo test name * [core] compatibility serializer * [core] disable rsa crypto test on android unit test * [core] move rsa impl to jvmBase * action * import * api dump * api dump * revert wrong api dumps * [core] Deprecate DeviceInfo constructor and serializer, provide `serializeToString` and `deserializeFromString` for replacement * rerun ci * optimize * use serializer directly * optimize test * revert * [core] CacheValidator use `DeviceInfo.serializeToString()` instead of direct serializer * Remove `println` in `DeviceInfoManager` * Add legacy deserialize overload for ABI compatibility * Remove uncompleted docs for DeviceInfo * Suppress DeviceInfo deprecation warnings for internal usages --------- Co-authored-by: Him188 <Him188@mamoe.net>
This commit is contained in:
parent
dafa25d611
commit
60d360baad
@ -5604,8 +5604,9 @@ public final class net/mamoe/mirai/utils/BotConfiguration$MiraiProtocol : java/l
|
|||||||
|
|
||||||
public final class net/mamoe/mirai/utils/DeviceInfo {
|
public 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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
*/
|
|
319
mirai-core-api/src/commonMain/kotlin/utils/DeviceInfoManager.kt
Normal file
319
mirai-core-api/src/commonMain/kotlin/utils/DeviceInfoManager.kt
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import net.mamoe.mirai.utils.DeviceInfoManager.Version.Companion.trans
|
||||||
|
import kotlin.jvm.JvmInline
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
internal object DeviceInfoManager {
|
||||||
|
sealed interface Info {
|
||||||
|
fun toDeviceInfo(): DeviceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(HexStringSerializer::class)
|
||||||
|
@JvmInline
|
||||||
|
value class HexString(
|
||||||
|
val data: ByteArray
|
||||||
|
)
|
||||||
|
|
||||||
|
object HexStringSerializer : KSerializer<HexString> by String.serializer().map(
|
||||||
|
String.serializer().descriptor.copy("HexString"),
|
||||||
|
deserialize = {
|
||||||
|
HexString(it.hexToBytes())
|
||||||
|
},
|
||||||
|
serialize = { it.data.toUHexString("").lowercase() }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note: property names must be kept intact during obfuscation process if applied.
|
||||||
|
@Serializable
|
||||||
|
class Wrapper<T : Info>(
|
||||||
|
@Suppress("unused") val deviceInfoVersion: Int, // used by plain jsonObject
|
||||||
|
val data: T
|
||||||
|
)
|
||||||
|
|
||||||
|
internal object DeviceInfoVersionSerializer : KSerializer<DeviceInfo.Version> by SerialData.serializer().map(
|
||||||
|
resultantDescriptor = SerialData.serializer().descriptor,
|
||||||
|
deserialize = {
|
||||||
|
DeviceInfo.Version(incremental, release, codename, sdk)
|
||||||
|
},
|
||||||
|
serialize = {
|
||||||
|
SerialData(incremental, release, codename, sdk)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
@SerialName("Version")
|
||||||
|
@Serializable
|
||||||
|
private class SerialData(
|
||||||
|
val incremental: ByteArray = "5891938".toByteArray(),
|
||||||
|
val release: ByteArray = "10".toByteArray(),
|
||||||
|
val codename: ByteArray = "REL".toByteArray(),
|
||||||
|
val sdk: Int = 29
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class V1(
|
||||||
|
val display: ByteArray,
|
||||||
|
val product: ByteArray,
|
||||||
|
val device: ByteArray,
|
||||||
|
val board: ByteArray,
|
||||||
|
val brand: ByteArray,
|
||||||
|
val model: ByteArray,
|
||||||
|
val bootloader: ByteArray,
|
||||||
|
val fingerprint: ByteArray,
|
||||||
|
val bootId: ByteArray,
|
||||||
|
val procVersion: ByteArray,
|
||||||
|
val baseBand: ByteArray,
|
||||||
|
val version: @Serializable(DeviceInfoVersionSerializer::class) DeviceInfo.Version,
|
||||||
|
val simInfo: ByteArray,
|
||||||
|
val osType: ByteArray,
|
||||||
|
val macAddress: ByteArray,
|
||||||
|
val wifiBSSID: ByteArray,
|
||||||
|
val wifiSSID: ByteArray,
|
||||||
|
val imsiMd5: ByteArray,
|
||||||
|
val imei: String,
|
||||||
|
val apn: ByteArray
|
||||||
|
) : Info {
|
||||||
|
override fun toDeviceInfo(): DeviceInfo {
|
||||||
|
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
||||||
|
return DeviceInfo(
|
||||||
|
display = display,
|
||||||
|
product = product,
|
||||||
|
device = device,
|
||||||
|
board = board,
|
||||||
|
brand = brand,
|
||||||
|
model = model,
|
||||||
|
bootloader = bootloader,
|
||||||
|
fingerprint = fingerprint,
|
||||||
|
bootId = bootId,
|
||||||
|
procVersion = procVersion,
|
||||||
|
baseBand = baseBand,
|
||||||
|
version = version,
|
||||||
|
simInfo = simInfo,
|
||||||
|
osType = osType,
|
||||||
|
macAddress = macAddress,
|
||||||
|
wifiBSSID = wifiBSSID,
|
||||||
|
wifiSSID = wifiSSID,
|
||||||
|
imsiMd5 = imsiMd5,
|
||||||
|
imei = imei,
|
||||||
|
apn = apn,
|
||||||
|
androidId = getRandomByteArray(8).toUHexString("").lowercase().encodeToByteArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class V2(
|
||||||
|
val display: String,
|
||||||
|
val product: String,
|
||||||
|
val device: String,
|
||||||
|
val board: String,
|
||||||
|
val brand: String,
|
||||||
|
val model: String,
|
||||||
|
val bootloader: String,
|
||||||
|
val fingerprint: String,
|
||||||
|
val bootId: String,
|
||||||
|
val procVersion: String,
|
||||||
|
val baseBand: HexString,
|
||||||
|
val version: Version,
|
||||||
|
val simInfo: String,
|
||||||
|
val osType: String,
|
||||||
|
val macAddress: String,
|
||||||
|
val wifiBSSID: String,
|
||||||
|
val wifiSSID: String,
|
||||||
|
val imsiMd5: HexString,
|
||||||
|
val imei: String,
|
||||||
|
val apn: String
|
||||||
|
) : Info {
|
||||||
|
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
||||||
|
override fun toDeviceInfo(): DeviceInfo = DeviceInfo(
|
||||||
|
this.display.toByteArray(),
|
||||||
|
this.product.toByteArray(),
|
||||||
|
this.device.toByteArray(),
|
||||||
|
this.board.toByteArray(),
|
||||||
|
this.brand.toByteArray(),
|
||||||
|
this.model.toByteArray(),
|
||||||
|
this.bootloader.toByteArray(),
|
||||||
|
this.fingerprint.toByteArray(),
|
||||||
|
this.bootId.toByteArray(),
|
||||||
|
this.procVersion.toByteArray(),
|
||||||
|
this.baseBand.data,
|
||||||
|
this.version.trans(),
|
||||||
|
this.simInfo.toByteArray(),
|
||||||
|
this.osType.toByteArray(),
|
||||||
|
this.macAddress.toByteArray(),
|
||||||
|
this.wifiBSSID.toByteArray(),
|
||||||
|
this.wifiSSID.toByteArray(),
|
||||||
|
this.imsiMd5.data,
|
||||||
|
this.imei,
|
||||||
|
this.apn.toByteArray(),
|
||||||
|
androidId = getRandomByteArray(8).toUHexString("").lowercase().encodeToByteArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class V3(
|
||||||
|
val display: String,
|
||||||
|
val product: String,
|
||||||
|
val device: String,
|
||||||
|
val board: String,
|
||||||
|
val brand: String,
|
||||||
|
val model: String,
|
||||||
|
val bootloader: String,
|
||||||
|
val fingerprint: String,
|
||||||
|
val bootId: String,
|
||||||
|
val procVersion: String,
|
||||||
|
val baseBand: HexString,
|
||||||
|
val version: Version,
|
||||||
|
val simInfo: String,
|
||||||
|
val osType: String,
|
||||||
|
val macAddress: String,
|
||||||
|
val wifiBSSID: String,
|
||||||
|
val wifiSSID: String,
|
||||||
|
val imsiMd5: HexString,
|
||||||
|
val imei: String,
|
||||||
|
val apn: String,
|
||||||
|
val androidId: String,
|
||||||
|
) : Info {
|
||||||
|
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
||||||
|
override fun toDeviceInfo(): DeviceInfo = DeviceInfo(
|
||||||
|
this.display.toByteArray(),
|
||||||
|
this.product.toByteArray(),
|
||||||
|
this.device.toByteArray(),
|
||||||
|
this.board.toByteArray(),
|
||||||
|
this.brand.toByteArray(),
|
||||||
|
this.model.toByteArray(),
|
||||||
|
this.bootloader.toByteArray(),
|
||||||
|
this.fingerprint.toByteArray(),
|
||||||
|
this.bootId.toByteArray(),
|
||||||
|
this.procVersion.toByteArray(),
|
||||||
|
this.baseBand.data,
|
||||||
|
this.version.trans(),
|
||||||
|
this.simInfo.toByteArray(),
|
||||||
|
this.osType.toByteArray(),
|
||||||
|
this.macAddress.toByteArray(),
|
||||||
|
this.wifiBSSID.toByteArray(),
|
||||||
|
this.wifiSSID.toByteArray(),
|
||||||
|
this.imsiMd5.data,
|
||||||
|
this.imei,
|
||||||
|
this.apn.toByteArray(),
|
||||||
|
this.androidId.toByteArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Version(
|
||||||
|
val incremental: String,
|
||||||
|
val release: String,
|
||||||
|
val codename: String,
|
||||||
|
val sdk: Int = 29
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun DeviceInfo.Version.trans(): Version {
|
||||||
|
return Version(incremental.decodeToString(), release.decodeToString(), codename.decodeToString(), sdk)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Version.trans(): DeviceInfo.Version {
|
||||||
|
return DeviceInfo.Version(incremental.toByteArray(), release.toByteArray(), codename.toByteArray(), sdk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DeviceInfo.toCurrentInfo(): V3 = V3(
|
||||||
|
display.decodeToString(),
|
||||||
|
product.decodeToString(),
|
||||||
|
device.decodeToString(),
|
||||||
|
board.decodeToString(),
|
||||||
|
brand.decodeToString(),
|
||||||
|
model.decodeToString(),
|
||||||
|
bootloader.decodeToString(),
|
||||||
|
fingerprint.decodeToString(),
|
||||||
|
bootId.decodeToString(),
|
||||||
|
procVersion.decodeToString(),
|
||||||
|
HexString(baseBand),
|
||||||
|
version.trans(),
|
||||||
|
simInfo.decodeToString(),
|
||||||
|
osType.decodeToString(),
|
||||||
|
macAddress.decodeToString(),
|
||||||
|
wifiBSSID.decodeToString(),
|
||||||
|
wifiSSID.decodeToString(),
|
||||||
|
HexString(imsiMd5),
|
||||||
|
imei,
|
||||||
|
apn.decodeToString(),
|
||||||
|
androidId.decodeToString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val format = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
isLenient = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@Deprecated("ABI compatibility for device generator", level = DeprecationLevel.HIDDEN)
|
||||||
|
@JvmName("deserialize")
|
||||||
|
fun deserializeDeprecated(
|
||||||
|
string: String,
|
||||||
|
format: Json = this.format,
|
||||||
|
): DeviceInfo = deserialize(string, format)
|
||||||
|
|
||||||
|
@Throws(IllegalArgumentException::class, NumberFormatException::class) // in case malformed
|
||||||
|
fun deserialize(
|
||||||
|
string: String,
|
||||||
|
format: Json = this.format,
|
||||||
|
onUpgradeVersion: (DeviceInfo) -> Unit = { }
|
||||||
|
): DeviceInfo {
|
||||||
|
val element = format.parseToJsonElement(string)
|
||||||
|
val version = element.jsonObject["deviceInfoVersion"]?.jsonPrimitive?.content?.toInt() ?: 1
|
||||||
|
|
||||||
|
val deviceInfo = when (version) {
|
||||||
|
/**
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
1 -> format.decodeFromJsonElement(V1.serializer(), element)
|
||||||
|
/**
|
||||||
|
* @since 2.9
|
||||||
|
*/
|
||||||
|
2 -> format.decodeFromJsonElement(Wrapper.serializer(V2.serializer()), element).data
|
||||||
|
/**
|
||||||
|
* @since 2.15
|
||||||
|
*/
|
||||||
|
3 -> format.decodeFromJsonElement(Wrapper.serializer(V3.serializer()), element).data
|
||||||
|
else -> throw IllegalArgumentException("Unsupported deviceInfoVersion: $version")
|
||||||
|
}.toDeviceInfo()
|
||||||
|
|
||||||
|
if (version < 3) onUpgradeVersion(deviceInfo)
|
||||||
|
|
||||||
|
return deviceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serialize(info: DeviceInfo, format: Json = this.format): String {
|
||||||
|
return format.encodeToString(
|
||||||
|
Wrapper.serializer(V3.serializer()),
|
||||||
|
Wrapper(3, info.toCurrentInfo())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJsonElement(info: DeviceInfo, format: Json = this.format): JsonElement {
|
||||||
|
return format.encodeToJsonElement(
|
||||||
|
Wrapper.serializer(V3.serializer()),
|
||||||
|
Wrapper(3, info.toCurrentInfo())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal class DeviceInfoV1Legacy(
|
||||||
|
val product: ByteArray,
|
||||||
|
val display: ByteArray,
|
||||||
|
val device: ByteArray,
|
||||||
|
val board: ByteArray,
|
||||||
|
val brand: ByteArray,
|
||||||
|
val model: ByteArray,
|
||||||
|
val bootloader: ByteArray,
|
||||||
|
val fingerprint: ByteArray,
|
||||||
|
val bootId: ByteArray,
|
||||||
|
val procVersion: ByteArray,
|
||||||
|
val baseBand: ByteArray,
|
||||||
|
val version: DeviceInfoV1LegacyVersion,
|
||||||
|
val simInfo: ByteArray,
|
||||||
|
val osType: ByteArray,
|
||||||
|
val macAddress: ByteArray,
|
||||||
|
val wifiBSSID: ByteArray,
|
||||||
|
val wifiSSID: ByteArray,
|
||||||
|
val imsiMd5: ByteArray,
|
||||||
|
val imei: String,
|
||||||
|
val apn: ByteArray,
|
||||||
|
val androidId: ByteArray? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal class DeviceInfoV1LegacyVersion(
|
||||||
|
val incremental: ByteArray = "5891938".toByteArray(),
|
||||||
|
val release: ByteArray = "10".toByteArray(),
|
||||||
|
val codename: ByteArray = "REL".toByteArray(),
|
||||||
|
val sdk: Int = 29
|
||||||
|
)
|
||||||
|
|
||||||
|
internal object DeviceInfoV1LegacySerializer : KSerializer<DeviceInfo> by DeviceInfoV1Legacy.serializer().map(
|
||||||
|
DeviceInfoV1Legacy.serializer().descriptor.copy("DeviceInfo"),
|
||||||
|
deserialize = {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
DeviceInfo(
|
||||||
|
display,
|
||||||
|
product,
|
||||||
|
device,
|
||||||
|
board,
|
||||||
|
brand,
|
||||||
|
model,
|
||||||
|
bootloader,
|
||||||
|
fingerprint,
|
||||||
|
bootId,
|
||||||
|
procVersion,
|
||||||
|
baseBand,
|
||||||
|
DeviceInfo.Version(version.incremental, version.release, version.codename, version.sdk),
|
||||||
|
simInfo,
|
||||||
|
osType,
|
||||||
|
macAddress,
|
||||||
|
wifiBSSID,
|
||||||
|
wifiSSID,
|
||||||
|
imsiMd5,
|
||||||
|
imei,
|
||||||
|
apn,
|
||||||
|
androidId = display
|
||||||
|
)
|
||||||
|
},
|
||||||
|
serialize = {
|
||||||
|
DeviceInfoV1Legacy(
|
||||||
|
display,
|
||||||
|
product,
|
||||||
|
device,
|
||||||
|
board,
|
||||||
|
brand,
|
||||||
|
model,
|
||||||
|
bootloader,
|
||||||
|
fingerprint,
|
||||||
|
bootId,
|
||||||
|
procVersion,
|
||||||
|
baseBand,
|
||||||
|
DeviceInfoV1LegacyVersion(version.incremental, version.release, version.codename, version.sdk),
|
||||||
|
simInfo,
|
||||||
|
osType,
|
||||||
|
macAddress,
|
||||||
|
wifiBSSID,
|
||||||
|
wifiSSID,
|
||||||
|
imsiMd5,
|
||||||
|
imei,
|
||||||
|
apn
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
@ -42,7 +42,7 @@ class CommonDeviceInfoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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(
|
||||||
|
@ -1,354 +0,0 @@
|
|||||||
{
|
|
||||||
"display" : [
|
|
||||||
77,
|
|
||||||
73,
|
|
||||||
82,
|
|
||||||
65,
|
|
||||||
73,
|
|
||||||
46,
|
|
||||||
55,
|
|
||||||
56,
|
|
||||||
49,
|
|
||||||
56,
|
|
||||||
55,
|
|
||||||
57,
|
|
||||||
46,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
49
|
|
||||||
],
|
|
||||||
"product" : [
|
|
||||||
109,
|
|
||||||
105,
|
|
||||||
114,
|
|
||||||
97,
|
|
||||||
105
|
|
||||||
],
|
|
||||||
"device" : [
|
|
||||||
109,
|
|
||||||
105,
|
|
||||||
114,
|
|
||||||
97,
|
|
||||||
105
|
|
||||||
],
|
|
||||||
"board" : [
|
|
||||||
109,
|
|
||||||
105,
|
|
||||||
114,
|
|
||||||
97,
|
|
||||||
105
|
|
||||||
],
|
|
||||||
"brand" : [
|
|
||||||
109,
|
|
||||||
97,
|
|
||||||
109,
|
|
||||||
111,
|
|
||||||
101
|
|
||||||
],
|
|
||||||
"model" : [
|
|
||||||
109,
|
|
||||||
105,
|
|
||||||
114,
|
|
||||||
97,
|
|
||||||
105
|
|
||||||
],
|
|
||||||
"bootloader" : [
|
|
||||||
117,
|
|
||||||
110,
|
|
||||||
107,
|
|
||||||
110,
|
|
||||||
111,
|
|
||||||
119,
|
|
||||||
110
|
|
||||||
],
|
|
||||||
"fingerprint" : [
|
|
||||||
109,
|
|
||||||
97,
|
|
||||||
109,
|
|
||||||
111,
|
|
||||||
101,
|
|
||||||
47,
|
|
||||||
109,
|
|
||||||
105,
|
|
||||||
114,
|
|
||||||
97,
|
|
||||||
105,
|
|
||||||
47,
|
|
||||||
109,
|
|
||||||
105,
|
|
||||||
114,
|
|
||||||
97,
|
|
||||||
105,
|
|
||||||
58,
|
|
||||||
49,
|
|
||||||
48,
|
|
||||||
47,
|
|
||||||
77,
|
|
||||||
73,
|
|
||||||
82,
|
|
||||||
65,
|
|
||||||
73,
|
|
||||||
46,
|
|
||||||
50,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
49,
|
|
||||||
50,
|
|
||||||
50,
|
|
||||||
46,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
49,
|
|
||||||
47,
|
|
||||||
53,
|
|
||||||
56,
|
|
||||||
52,
|
|
||||||
54,
|
|
||||||
51,
|
|
||||||
56,
|
|
||||||
49,
|
|
||||||
58,
|
|
||||||
117,
|
|
||||||
115,
|
|
||||||
101,
|
|
||||||
114,
|
|
||||||
47,
|
|
||||||
114,
|
|
||||||
101,
|
|
||||||
108,
|
|
||||||
101,
|
|
||||||
97,
|
|
||||||
115,
|
|
||||||
101,
|
|
||||||
45,
|
|
||||||
107,
|
|
||||||
101,
|
|
||||||
121,
|
|
||||||
115
|
|
||||||
],
|
|
||||||
"bootId" : [
|
|
||||||
56,
|
|
||||||
53,
|
|
||||||
57,
|
|
||||||
67,
|
|
||||||
67,
|
|
||||||
54,
|
|
||||||
52,
|
|
||||||
65,
|
|
||||||
45,
|
|
||||||
57,
|
|
||||||
65,
|
|
||||||
69,
|
|
||||||
57,
|
|
||||||
45,
|
|
||||||
56,
|
|
||||||
48,
|
|
||||||
67,
|
|
||||||
51,
|
|
||||||
45,
|
|
||||||
66,
|
|
||||||
51,
|
|
||||||
68,
|
|
||||||
52,
|
|
||||||
45,
|
|
||||||
51,
|
|
||||||
49,
|
|
||||||
70,
|
|
||||||
49,
|
|
||||||
49,
|
|
||||||
67,
|
|
||||||
56,
|
|
||||||
67,
|
|
||||||
54,
|
|
||||||
66,
|
|
||||||
56,
|
|
||||||
52
|
|
||||||
],
|
|
||||||
"procVersion" : [
|
|
||||||
76,
|
|
||||||
105,
|
|
||||||
110,
|
|
||||||
117,
|
|
||||||
120,
|
|
||||||
32,
|
|
||||||
118,
|
|
||||||
101,
|
|
||||||
114,
|
|
||||||
115,
|
|
||||||
105,
|
|
||||||
111,
|
|
||||||
110,
|
|
||||||
32,
|
|
||||||
51,
|
|
||||||
46,
|
|
||||||
48,
|
|
||||||
46,
|
|
||||||
51,
|
|
||||||
49,
|
|
||||||
45,
|
|
||||||
48,
|
|
||||||
84,
|
|
||||||
102,
|
|
||||||
51,
|
|
||||||
68,
|
|
||||||
50,
|
|
||||||
53,
|
|
||||||
67,
|
|
||||||
32,
|
|
||||||
40,
|
|
||||||
97,
|
|
||||||
110,
|
|
||||||
100,
|
|
||||||
114,
|
|
||||||
111,
|
|
||||||
105,
|
|
||||||
100,
|
|
||||||
45,
|
|
||||||
98,
|
|
||||||
117,
|
|
||||||
105,
|
|
||||||
108,
|
|
||||||
100,
|
|
||||||
64,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
46,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
46,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
46,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
120,
|
|
||||||
46,
|
|
||||||
99,
|
|
||||||
111,
|
|
||||||
109,
|
|
||||||
41
|
|
||||||
],
|
|
||||||
"baseBand" : [
|
|
||||||
],
|
|
||||||
"version" : {
|
|
||||||
"incremental" : [
|
|
||||||
53,
|
|
||||||
56,
|
|
||||||
57,
|
|
||||||
49,
|
|
||||||
57,
|
|
||||||
51,
|
|
||||||
56
|
|
||||||
],
|
|
||||||
"release" : [
|
|
||||||
49,
|
|
||||||
48
|
|
||||||
],
|
|
||||||
"codename" : [
|
|
||||||
82,
|
|
||||||
69,
|
|
||||||
76
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"simInfo" : [
|
|
||||||
84,
|
|
||||||
45,
|
|
||||||
77,
|
|
||||||
111,
|
|
||||||
98,
|
|
||||||
105,
|
|
||||||
108,
|
|
||||||
101
|
|
||||||
],
|
|
||||||
"osType" : [
|
|
||||||
97,
|
|
||||||
110,
|
|
||||||
100,
|
|
||||||
114,
|
|
||||||
111,
|
|
||||||
105,
|
|
||||||
100
|
|
||||||
],
|
|
||||||
"macAddress" : [
|
|
||||||
48,
|
|
||||||
50,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48
|
|
||||||
],
|
|
||||||
"wifiBSSID" : [
|
|
||||||
48,
|
|
||||||
50,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48,
|
|
||||||
58,
|
|
||||||
48,
|
|
||||||
48
|
|
||||||
],
|
|
||||||
"wifiSSID" : [
|
|
||||||
60,
|
|
||||||
117,
|
|
||||||
110,
|
|
||||||
107,
|
|
||||||
110,
|
|
||||||
111,
|
|
||||||
119,
|
|
||||||
110,
|
|
||||||
32,
|
|
||||||
115,
|
|
||||||
115,
|
|
||||||
105,
|
|
||||||
100,
|
|
||||||
62
|
|
||||||
],
|
|
||||||
"imsiMd5" : [
|
|
||||||
69,
|
|
||||||
45,
|
|
||||||
31,
|
|
||||||
44,
|
|
||||||
85,
|
|
||||||
103,
|
|
||||||
-19,
|
|
||||||
88,
|
|
||||||
21,
|
|
||||||
-47,
|
|
||||||
94,
|
|
||||||
-128,
|
|
||||||
38,
|
|
||||||
-45,
|
|
||||||
9,
|
|
||||||
50
|
|
||||||
],
|
|
||||||
"imei" : "101633900250935",
|
|
||||||
"apn" : [
|
|
||||||
119,
|
|
||||||
105,
|
|
||||||
102,
|
|
||||||
105
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
*
|
*
|
||||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
* 此源代码的使用受 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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
|
||||||
*
|
|
||||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
|
||||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
|
||||||
*
|
|
||||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.mamoe.mirai.utils
|
|
||||||
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class JvmDeviceInfoTestJvm {
|
|
||||||
@Test
|
|
||||||
fun `can deserialize legacy versions before 2_9_0`() {
|
|
||||||
// resources not available on android
|
|
||||||
|
|
||||||
DeviceInfoManager.deserialize(
|
|
||||||
this::class.java.classLoader.getResourceAsStream("device/legacy-device-info-1.json")!!
|
|
||||||
.use { it.readBytes().decodeToString() })
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,7 +41,10 @@ public actual abstract class AbstractBotConfiguration { // open for Java
|
|||||||
if (!file.exists()) {
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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}$"""))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
297
mirai-core/src/commonMain/kotlin/network/qimei/Qimei.kt
Normal file
297
mirai-core/src/commonMain/kotlin/network/qimei/Qimei.kt
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.network.qimei
|
||||||
|
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import net.mamoe.mirai.internal.QQAndroidBot
|
||||||
|
import net.mamoe.mirai.internal.network.components.BotClientHolder
|
||||||
|
import net.mamoe.mirai.internal.network.components.HttpClientProvider
|
||||||
|
import net.mamoe.mirai.internal.network.components.SsoProcessorContext
|
||||||
|
import net.mamoe.mirai.internal.network.protocol
|
||||||
|
import net.mamoe.mirai.internal.utils.crypto.aesDecrypt
|
||||||
|
import net.mamoe.mirai.internal.utils.crypto.aesEncrypt
|
||||||
|
import net.mamoe.mirai.internal.utils.crypto.rsaEncryptWithX509PubKey
|
||||||
|
import net.mamoe.mirai.utils.*
|
||||||
|
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
private val secret = "ZdJqM15EeO2zWc08"
|
||||||
|
private val rsaPubKey = """
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIxgwoutfwoJxcGQeedgP7FG9
|
||||||
|
qaIuS0qzfR8gWkrkTZKM2iWHn2ajQpBRZjMSoSf6+KJGvar2ORhBfpDXyVtZCKpq
|
||||||
|
LQ+FLkpncClKVIrBwv6PHyUvuCb0rIarmgDnzkfQAqVufEtR64iazGDKatvJ9y6B
|
||||||
|
9NMbHddGSAUmRTCrHQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
internal suspend fun QQAndroidBot.requestQimei(logger: MiraiLogger) {
|
||||||
|
val protocol = components[BotClientHolder].client.protocol
|
||||||
|
if (protocol.appKey.isEmpty()) return
|
||||||
|
|
||||||
|
val deviceInfo = components[SsoProcessorContext].device
|
||||||
|
val httpClient = components[HttpClientProvider].getHttpClient()
|
||||||
|
|
||||||
|
val seed = deviceInfo.guid.foldRight(0x6f4L) { curr, acc -> acc + curr.toLong() }
|
||||||
|
val random = Random(seed)
|
||||||
|
|
||||||
|
val reservedData = Json.encodeToString(
|
||||||
|
ReservedData(
|
||||||
|
harmony = "0",
|
||||||
|
clone = "0",
|
||||||
|
containe = "",
|
||||||
|
oz = "UhYmelwouA+V2nPWbOvLTgN2/m8jwGB+yUB5v9tysQg=",
|
||||||
|
oo = "Xecjt+9S1+f8Pz2VLSxgpw==",
|
||||||
|
kelong = "0",
|
||||||
|
uptimes = formatTime(currentTimeMillis() - random.nextLong(14_400_000), null),
|
||||||
|
multiUser = "0",
|
||||||
|
bod = deviceInfo.board.decodeToString(),
|
||||||
|
brd = deviceInfo.brand.decodeToString(),
|
||||||
|
dv = deviceInfo.device.decodeToString(),
|
||||||
|
firstLevel = "",
|
||||||
|
manufact = deviceInfo.brand.decodeToString(),
|
||||||
|
name = deviceInfo.model.decodeToString(),
|
||||||
|
host = "se.infra",
|
||||||
|
kernel = deviceInfo.procVersion.decodeToString(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val yearMonthFormatted = formatTime(currentTimeMillis(), "yyyy-MM-01")
|
||||||
|
val rand1 = random.nextInt(899999) + 100000
|
||||||
|
val rand2 = random.nextInt(899999999) + 100000000
|
||||||
|
|
||||||
|
val beaconId = buildString {
|
||||||
|
(1..40).forEach { i ->
|
||||||
|
when (i) {
|
||||||
|
1, 2, 13, 14, 17, 18, 21, 22, 25, 26, 29, 30, 33, 34, 37, 38 -> {
|
||||||
|
append('k')
|
||||||
|
append(i)
|
||||||
|
append(':')
|
||||||
|
append(yearMonthFormatted)
|
||||||
|
append(rand1)
|
||||||
|
append('.')
|
||||||
|
append(rand2)
|
||||||
|
}
|
||||||
|
|
||||||
|
3 -> append("k3:0000000000000000")
|
||||||
|
4 -> {
|
||||||
|
append("k4:")
|
||||||
|
append(getRandomString(16))
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
append('k')
|
||||||
|
append(i)
|
||||||
|
append(':')
|
||||||
|
append(random.nextInt(10000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append(';')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val payloadParam = Json.encodeToString(
|
||||||
|
DevicePayloadData(
|
||||||
|
androidId = deviceInfo.androidId.decodeToString(),
|
||||||
|
platformId = 1,
|
||||||
|
appKey = protocol.appKey,
|
||||||
|
appVersion = protocol.buildVer,
|
||||||
|
beaconIdSrc = beaconId,
|
||||||
|
brand = deviceInfo.brand.decodeToString(),
|
||||||
|
channelId = "2017",
|
||||||
|
cid = "",
|
||||||
|
imei = deviceInfo.imei,
|
||||||
|
imsi = "",
|
||||||
|
mac = deviceInfo.macAddress.decodeToString(),
|
||||||
|
model = deviceInfo.model.decodeToString(),
|
||||||
|
networkType = "unknown",
|
||||||
|
oaid = "",
|
||||||
|
osVersion = buildString {
|
||||||
|
append("Android ")
|
||||||
|
append(deviceInfo.version.release.toString())
|
||||||
|
append(", level ")
|
||||||
|
append(deviceInfo.version.sdk.toString())
|
||||||
|
},
|
||||||
|
qimei = "",
|
||||||
|
qimei36 = "",
|
||||||
|
sdkVersion = "1.2.13.6",
|
||||||
|
audit = "",
|
||||||
|
userId = "{}",
|
||||||
|
packageId = protocol.apkId,
|
||||||
|
deviceType = if (configuration.protocol == MiraiProtocol.ANDROID_PAD) "Pad" else "Phone",
|
||||||
|
sdkName = "",
|
||||||
|
reserved = reservedData,
|
||||||
|
)
|
||||||
|
).toByteArray()
|
||||||
|
|
||||||
|
val aesKey = getRandomString(16).toByteArray()
|
||||||
|
val nonce = getRandomString(16)
|
||||||
|
val timestamp = currentTimeSeconds() * 1000
|
||||||
|
|
||||||
|
val encodedAESKey = rsaEncryptWithX509PubKey(aesKey, rsaPubKey, timestamp).encodeBase64()
|
||||||
|
val encodedPayloadParam = aesEncrypt(payloadParam, aesKey, aesKey).encodeBase64()
|
||||||
|
|
||||||
|
val payload = Json.encodeToString(
|
||||||
|
PostData(
|
||||||
|
key = encodedAESKey,
|
||||||
|
params = encodedPayloadParam,
|
||||||
|
time = timestamp,
|
||||||
|
nonce = nonce,
|
||||||
|
sign = buildString {
|
||||||
|
append(encodedAESKey)
|
||||||
|
append(encodedPayloadParam)
|
||||||
|
append(timestamp)
|
||||||
|
append(nonce)
|
||||||
|
append(secret)
|
||||||
|
}.md5().toUHexString(""),
|
||||||
|
extra = ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val resp = Json.decodeFromString(
|
||||||
|
OLAAndroidResp.serializer(),
|
||||||
|
httpClient.post("https://snowflake.qq.com/ola/android") {
|
||||||
|
userAgent(buildString {
|
||||||
|
append("Dalvik/")
|
||||||
|
append(dalvikVersions[deviceInfo.version.sdk] ?: "2.1.0")
|
||||||
|
append(" (Linux; U; Android ")
|
||||||
|
append(deviceInfo.version.release.decodeToString())
|
||||||
|
append("; ")
|
||||||
|
append(deviceInfo.device.decodeToString())
|
||||||
|
append(" Build/")
|
||||||
|
append(deviceInfo.display.decodeToString())
|
||||||
|
append(")")
|
||||||
|
})
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
header("Cookie", "")
|
||||||
|
setBody(payload.toByteArray())
|
||||||
|
timeout {
|
||||||
|
connectTimeoutMillis = 5000
|
||||||
|
requestTimeoutMillis = 5000
|
||||||
|
socketTimeoutMillis = 5000
|
||||||
|
}
|
||||||
|
}.bodyAsText()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (resp.code != 0) {
|
||||||
|
logger.warning { "Cannot get qimei from server, return code = ${resp.code}" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val decryptedData = aesDecrypt(resp.data.decodeBase64(), aesKey, aesKey)
|
||||||
|
val qimeiData = Json.decodeFromString(QimeiData.serializer(), decryptedData.decodeToString())
|
||||||
|
|
||||||
|
client.qimei36 = qimeiData.q36
|
||||||
|
client.qimei16 = qimeiData.q16
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dalvikVersions = mapOf(
|
||||||
|
14 to "1.6",
|
||||||
|
15 to "1.6",
|
||||||
|
16 to "1.6",
|
||||||
|
17 to "1.6",
|
||||||
|
18 to "1.6",
|
||||||
|
19 to "2.0",
|
||||||
|
20 to "2.0",
|
||||||
|
21 to "2.1.0",
|
||||||
|
22 to "2.1.0",
|
||||||
|
23 to "2.1.0",
|
||||||
|
24 to "2.1.0",
|
||||||
|
25 to "2.1.0",
|
||||||
|
26 to "2.1.0",
|
||||||
|
27 to "2.1.0",
|
||||||
|
28 to "2.1.0",
|
||||||
|
29 to "2.1.0",
|
||||||
|
30 to "2.1.0",
|
||||||
|
31 to "2.1.0",
|
||||||
|
32 to "2.1.0",
|
||||||
|
33 to "2.1.0",
|
||||||
|
34 to "2.1.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private class OLAAndroidResp(
|
||||||
|
val code: Int,
|
||||||
|
val data: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private class QimeiData(
|
||||||
|
val q16: String,
|
||||||
|
val q36: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@Serializable
|
||||||
|
private class ReservedData(
|
||||||
|
val harmony: String,
|
||||||
|
val clone: String,
|
||||||
|
val containe: String,
|
||||||
|
val oz: String,
|
||||||
|
val oo: String,
|
||||||
|
val kelong: String,
|
||||||
|
val uptimes: String,
|
||||||
|
val multiUser: String,
|
||||||
|
val bod: String,
|
||||||
|
val brd: String,
|
||||||
|
val dv: String,
|
||||||
|
val firstLevel: String,
|
||||||
|
val manufact: String,
|
||||||
|
val name: String,
|
||||||
|
val host: String,
|
||||||
|
val kernel: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@Serializable
|
||||||
|
private class DevicePayloadData(
|
||||||
|
val androidId: String,
|
||||||
|
val platformId: Int,
|
||||||
|
val appKey: String,
|
||||||
|
val appVersion: String,
|
||||||
|
val beaconIdSrc: String,
|
||||||
|
val brand: String,
|
||||||
|
val channelId: String,
|
||||||
|
val cid: String,
|
||||||
|
val imei: String,
|
||||||
|
val imsi: String,
|
||||||
|
val mac: String,
|
||||||
|
val model: String,
|
||||||
|
val networkType: String,
|
||||||
|
val oaid: String,
|
||||||
|
val osVersion: String,
|
||||||
|
val qimei: String,
|
||||||
|
val qimei36: String,
|
||||||
|
val sdkVersion: String,
|
||||||
|
val audit: String,
|
||||||
|
val userId: String,
|
||||||
|
val packageId: String,
|
||||||
|
val deviceType: String,
|
||||||
|
val sdkName: String,
|
||||||
|
val reserved: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@Serializable
|
||||||
|
private class PostData(
|
||||||
|
val key: String,
|
||||||
|
val params: String,
|
||||||
|
val time: Long,
|
||||||
|
val nonce: String,
|
||||||
|
val sign: String,
|
||||||
|
val extra: String
|
||||||
|
)
|
@ -18,6 +18,7 @@ internal class MiraiProtocolInternal(
|
|||||||
var apkId: String,
|
var 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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
14
mirai-core/src/commonMain/kotlin/utils/crypto/AES.kt
Normal file
14
mirai-core/src/commonMain/kotlin/utils/crypto/AES.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
internal expect fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray
|
||||||
|
|
||||||
|
internal expect fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray
|
21
mirai-core/src/commonMain/kotlin/utils/crypto/RSA.kt
Normal file
21
mirai-core/src/commonMain/kotlin/utils/crypto/RSA.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
internal class RSAKeyPair(
|
||||||
|
val plainPubPemKey: String,
|
||||||
|
val plainPrivPemKey: String
|
||||||
|
)
|
||||||
|
|
||||||
|
internal expect fun generateRSAKeyPair(keySize: Int): RSAKeyPair
|
||||||
|
|
||||||
|
internal expect fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray
|
||||||
|
|
||||||
|
internal expect fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray
|
40
mirai-core/src/commonTest/kotlin/utils/crypto/AESTest.kt
Normal file
40
mirai-core/src/commonTest/kotlin/utils/crypto/AESTest.kt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
import net.mamoe.mirai.utils.currentTimeMillis
|
||||||
|
import net.mamoe.mirai.utils.getRandomString
|
||||||
|
import net.mamoe.mirai.utils.toUHexString
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
internal class AESTest {
|
||||||
|
@Test
|
||||||
|
fun `can do crypto`() {
|
||||||
|
val random = Random(currentTimeMillis())
|
||||||
|
|
||||||
|
val key = getRandomString(16, random).encodeToByteArray()
|
||||||
|
val iv = getRandomString(16, random).encodeToByteArray()
|
||||||
|
val currentTime = currentTimeMillis()
|
||||||
|
|
||||||
|
val plainText = buildString {
|
||||||
|
append("Use of this source code is governed by the GNU AGPLv3 license ")
|
||||||
|
append("that can be found through the following link. ")
|
||||||
|
append(currentTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("AES crypto test: key = ${key.toUHexString()}, iv = ${iv.toUHexString()}, currentTimeMillis = $currentTime")
|
||||||
|
val encrypted = aesEncrypt(plainText.encodeToByteArray(), iv, key)
|
||||||
|
val decrypted = aesDecrypt(encrypted, iv, key)
|
||||||
|
|
||||||
|
assertEquals(plainText, decrypted.decodeToString())
|
||||||
|
}
|
||||||
|
}
|
112
mirai-core/src/commonTest/kotlin/utils/crypto/RSATest.kt
Normal file
112
mirai-core/src/commonTest/kotlin/utils/crypto/RSATest.kt
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
import net.mamoe.mirai.internal.testFramework.*
|
||||||
|
import net.mamoe.mirai.internal.testFramework.rules.DisabledOnJvmLikePlatform
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
@DisabledOnJvmLikePlatform(Platform.AndroidUnitTest::class)
|
||||||
|
class RSATest {
|
||||||
|
@TestFactory
|
||||||
|
fun `can generate key pair`(): DynamicTestsResult {
|
||||||
|
return runDynamicTests(buildList {
|
||||||
|
repeat(4) { exp ->
|
||||||
|
val keySize = 2.0.pow(9 + exp).toInt()
|
||||||
|
add(dynamicTest("RSAKeyGenLength$keySize") {
|
||||||
|
val rsaKeyPair = generateRSAKeyPair(keySize)
|
||||||
|
println("RSA keygen test #${exp + 1}: keySize = $keySize")
|
||||||
|
println(rsaKeyPair.plainPubPemKey)
|
||||||
|
println(rsaKeyPair.plainPrivPemKey)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can do crypto with generated key`() {
|
||||||
|
val keyPair = generateRSAKeyPair(2048)
|
||||||
|
|
||||||
|
val plainText = buildString {
|
||||||
|
append("Use of this source code is governed by the GNU AGPLv3 license ")
|
||||||
|
append("that can be found through the following link. ")
|
||||||
|
}
|
||||||
|
|
||||||
|
println(
|
||||||
|
"RSA crypto test: plainTextLength: ${plainText.length}, " +
|
||||||
|
"pubKey = ${keyPair.plainPubPemKey}, " +
|
||||||
|
"privKey = ${keyPair.plainPrivPemKey}"
|
||||||
|
)
|
||||||
|
val enc = rsaEncryptWithX509PubKey(plainText.encodeToByteArray(), keyPair.plainPubPemKey, 0)
|
||||||
|
println("rsa encrypt: data size=${enc.size}")
|
||||||
|
val decrypted = rsaDecryptWithPKCS8PrivKey(enc, keyPair.plainPrivPemKey, 0)
|
||||||
|
|
||||||
|
assertEquals(plainText, decrypted.decodeToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can do crypto with specific key`() {
|
||||||
|
val pubKey = """
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0KIpsWPW2JA7ShJI18o
|
||||||
|
wPv3Ip3Y6a0OkJOozfVlQDOjjUG6niDcrPIm+OpL7pCAzwc+h8d9sFH5c/7/bY4i
|
||||||
|
wKK6CpSaOYgQQ03P31KhzmXGJ4LVSxUIV0bhuDYQr+sU5Gu97onF8Ko8MELtWTPw
|
||||||
|
KP1dfqZ3PrK8QBH6su0GlB8onYFtzDUckr2wCrrJ1cR4L1Dg5f2egE75l1cliAIM
|
||||||
|
4FH1WFU2musfdEuCo5oPgl8ZPPLrQwp8qm9w7xBvbgbmfPTjPBC0N4gcelVzvdfC
|
||||||
|
eU8vpIlLP/9W5nkdqF6CWzjE3dIx2btOH4QDDyogDSLRAvcKN5/1EIZeu2FTbw9k
|
||||||
|
3QIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val privKey = """
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvQoimxY9bYkDt
|
||||||
|
KEkjXyjA+/cindjprQ6Qk6jN9WVAM6ONQbqeINys8ib46kvukIDPBz6Hx32wUflz
|
||||||
|
/v9tjiLAoroKlJo5iBBDTc/fUqHOZcYngtVLFQhXRuG4NhCv6xTka73uicXwqjww
|
||||||
|
Qu1ZM/Ao/V1+pnc+srxAEfqy7QaUHyidgW3MNRySvbAKusnVxHgvUODl/Z6ATvmX
|
||||||
|
VyWIAgzgUfVYVTaa6x90S4Kjmg+CXxk88utDCnyqb3DvEG9uBuZ89OM8ELQ3iBx6
|
||||||
|
VXO918J5Ty+kiUs//1bmeR2oXoJbOMTd0jHZu04fhAMPKiANItEC9wo3n/UQhl67
|
||||||
|
YVNvD2TdAgMBAAECggEAExREqRcfvJyNIeQ9Vg7xclTbuhaB+ypeSAnzGfzJeXxF
|
||||||
|
pUaPCNDeBSvVZ0qmWoG7rA4HViO3AJ9j7ydG6kfLa7orU6SKx5GS56jMZOzrdXsp
|
||||||
|
37pD+wj+n/W08+da2LPYUeeSxSmVdVYq+DwI96mKTwQKDhQULiyqBrWOW7Um/q/Z
|
||||||
|
JC8kJWKEmlNideDQHJxZViRyOdKvJtiwvoBLe1Jvbx7oMbpZnf20gV8C7UU7U38R
|
||||||
|
e0BKT6HBUHXuOOp2tFFpX6dySkJqW7Jijv04B/KnDYaSWD8TtaQfPfAhkiEVA17E
|
||||||
|
Ret04PnPMiYCkSVakO0MEeFpwb01vPca4Z64zgf7EQKBgQDyU6wsO3v8L1OlV7tx
|
||||||
|
7+T0PuOqeo7MWESSn18LeyOP2Y+fDtHKMUFULeYH1UZaGsZJvW+P+c8Mvyitbcvv
|
||||||
|
SZPTR0Dg+1HueXWkNTejs8Z2BKpPmIPaVLz5FxhV7hV2hKgII/yhyRoiWrTiawLg
|
||||||
|
ocOnYSostg+tt6kT8U2QPKhg9QKBgQC5Jho1nZ3pFPVu2rV/o9VvN7bGfn1M2o8k
|
||||||
|
9PQjLZQYiXJvPP0tNlvAOHk9cAqYecHJ3wDVacZWmLicU8xmNSFmmN6Vs9jj6km4
|
||||||
|
CWq0/wuTUO/fiH+oHZb6+JM63RXbASWyNK+WwmZtDryNBGRB5zbeCAK8tFsRCJDw
|
||||||
|
19WQUzljSQKBgCWKkuzTVlTuXA4MdmyjVpwENi8OB5tevVjdudLEg/DgKqDgod2q
|
||||||
|
Hc3VwoJKJzkEVt3LrEHo2IvH/ZxIm0R56J3dtw5jwQCp7nC/EdyZmFBmTqBAJ4Um
|
||||||
|
hZQtYMbHOKoAySthr9y8lADofodpPqvgQ7hllCwTFIC8KER/qJ2E2C0VAoGATLUM
|
||||||
|
hsoWckrMpHDYYVlvQ/TBNNuS7hRe2eDihPCNOt03G/8YpXKv8KN1F48j1KgdMZXC
|
||||||
|
sqhwE9CSK7JMLMw2WltbXIp2gXa/tA+yteo00YPm3aWfvfcEZlY2KV0PgPyosXxC
|
||||||
|
gyNnbCd+1q3LG8K/aJ3JBIV0dUonQqEpSfIxBIECgYArO3Iw+LvoePjq4yHheyEM
|
||||||
|
rz6d6RB+i1Q7ExBK7lbZxN17HmKiOewwI772zEo28IY9sIHugV7rW1vQVs3bnzgk
|
||||||
|
ExDGjYWZSKHfs+3mvrLNRIx/IsVqqwlXt5oO9TspSh68ASvmXN51dmduxRrSuScq
|
||||||
|
8a49uOr675SyFCJTIdF/Ag==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val plainText = buildString {
|
||||||
|
append("Use of this source code is governed by the GNU AGPLv3 license ")
|
||||||
|
append("that can be found through the following link. ")
|
||||||
|
}
|
||||||
|
|
||||||
|
val enc = rsaEncryptWithX509PubKey(plainText.encodeToByteArray(), pubKey, 0)
|
||||||
|
val dec = rsaDecryptWithPKCS8PrivKey(enc, privKey, 0)
|
||||||
|
|
||||||
|
assertEquals(plainText, dec.decodeToString())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
31
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/AES.kt
Normal file
31
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/AES.kt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
internal actual fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||||
|
return doAES(input, iv, key, Cipher.ENCRYPT_MODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal actual fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||||
|
return doAES(input, iv, key, Cipher.DECRYPT_MODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doAES(input: ByteArray, iv: ByteArray, key: ByteArray, opMode: Int): ByteArray {
|
||||||
|
val keySpec = SecretKeySpec(key, "AES")
|
||||||
|
|
||||||
|
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||||
|
cipher.init(opMode, keySpec, IvParameterSpec(iv))
|
||||||
|
|
||||||
|
return cipher.doFinal(input)
|
||||||
|
}
|
74
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/RSA.kt
Normal file
74
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/RSA.kt
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
import net.mamoe.mirai.utils.decodeBase64
|
||||||
|
import net.mamoe.mirai.utils.encodeBase64
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
internal actual fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray {
|
||||||
|
val encodedKey = plainPubPemKey
|
||||||
|
.replace("\n", "")
|
||||||
|
.removePrefix("-----BEGIN PUBLIC KEY-----")
|
||||||
|
.removeSuffix("-----END PUBLIC KEY-----")
|
||||||
|
.trim()
|
||||||
|
.decodeBase64()
|
||||||
|
|
||||||
|
val keyFactory = KeyFactory.getInstance("RSA")
|
||||||
|
val rsaPubKey = keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))
|
||||||
|
|
||||||
|
|
||||||
|
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, rsaPubKey, SecureRandom().apply { setSeed(seed) })
|
||||||
|
|
||||||
|
return cipher.doFinal(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal actual fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray {
|
||||||
|
val encodedKey = plainPrivPemKey
|
||||||
|
.replace("\n", "")
|
||||||
|
.removePrefix("-----BEGIN PRIVATE KEY-----")
|
||||||
|
.removeSuffix("-----END PRIVATE KEY-----")
|
||||||
|
.trim()
|
||||||
|
.decodeBase64()
|
||||||
|
|
||||||
|
val keyFactory = KeyFactory.getInstance("RSA")
|
||||||
|
val rsaPubKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
||||||
|
|
||||||
|
|
||||||
|
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, rsaPubKey, SecureRandom().apply { setSeed(seed) })
|
||||||
|
|
||||||
|
return cipher.doFinal(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal actual fun generateRSAKeyPair(keySize: Int): RSAKeyPair {
|
||||||
|
val keyGen = KeyPairGenerator.getInstance("RSA")
|
||||||
|
keyGen.initialize(keySize)
|
||||||
|
|
||||||
|
val keyPair = keyGen.generateKeyPair()
|
||||||
|
return RSAKeyPair(
|
||||||
|
plainPubPemKey = buildString {
|
||||||
|
appendLine("-----BEGIN PUBLIC KEY-----")
|
||||||
|
keyPair.public.encoded.encodeBase64().chunked(64).forEach(::appendLine)
|
||||||
|
appendLine("-----END PUBLIC KEY-----")
|
||||||
|
},
|
||||||
|
plainPrivPemKey = buildString {
|
||||||
|
appendLine("-----BEGIN PRIVATE KEY-----")
|
||||||
|
keyPair.private.encoded.encodeBase64().chunked(64).forEach(::appendLine)
|
||||||
|
appendLine("-----END PRIVATE KEY-----")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,10 @@
|
|||||||
headers = openssl/ec.h openssl/ecdh.h openssl/evp.h
|
headers = openssl/ec.h \
|
||||||
|
openssl/ecdh.h \
|
||||||
|
openssl/evp.h \
|
||||||
|
openssl/rsa.h \
|
||||||
|
openssl/pem.h \
|
||||||
|
openssl/x509.h \
|
||||||
|
openssl/err.h
|
||||||
|
|
||||||
# -L/usr/local/opt/openssl@1.1/1.1.1o/lib is for GitHub actions. See https://github.com/actions/virtual-environments/blob/main/images/macos/macos-12-Readme.md
|
# -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);
|
||||||
|
}
|
101
mirai-core/src/nativeMain/kotlin/utils/crypto/AESNative.kt
Normal file
101
mirai-core/src/nativeMain/kotlin/utils/crypto/AESNative.kt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
import net.mamoe.mirai.internal.utils.free
|
||||||
|
import net.mamoe.mirai.internal.utils.getOpenSSLError
|
||||||
|
import openssl.*
|
||||||
|
|
||||||
|
private val aes256CBC by lazy { EVP_aes_256_cbc() }
|
||||||
|
|
||||||
|
internal actual fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||||
|
return doAES(input, iv, key, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal actual fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray {
|
||||||
|
return doAES(input, iv, key, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reference:
|
||||||
|
* - https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
|
||||||
|
*/
|
||||||
|
private fun doAES(input: ByteArray, iv: ByteArray, key: ByteArray, doEncrypt: Boolean): ByteArray {
|
||||||
|
memScoped {
|
||||||
|
val evpCipherCtx = EVP_CIPHER_CTX_new() ?: error("Failed to create evp cipher context: ${getOpenSSLError()}")
|
||||||
|
|
||||||
|
val pinnedKey = key.pin()
|
||||||
|
val pinnedIv = iv.pin()
|
||||||
|
val pinnedInput = input.pin()
|
||||||
|
|
||||||
|
if (1 != EVP_CipherInit(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
cipher = aes256CBC,
|
||||||
|
key = pinnedKey.addressOf(0).reinterpret(),
|
||||||
|
iv = pinnedIv.addressOf(0).reinterpret(),
|
||||||
|
enc = if (doEncrypt) 1 else 0
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedKey.unpin()
|
||||||
|
pinnedIv.unpin()
|
||||||
|
pinnedInput.unpin()
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to init aes-256-cbc cipher: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
pinnedKey.unpin()
|
||||||
|
pinnedIv.unpin()
|
||||||
|
|
||||||
|
val blockSize = _evpCipherCtxGetBlockSize(evpCipherCtx)
|
||||||
|
val cipherBufferSize = pinnedInput.get().size + blockSize - (pinnedInput.get().size % blockSize)
|
||||||
|
val pinnedCipherBuffer = ByteArray(cipherBufferSize.convert()).pin()
|
||||||
|
|
||||||
|
|
||||||
|
val tempLen = alloc<IntVar>()
|
||||||
|
val cipherSize = alloc<IntVar>()
|
||||||
|
|
||||||
|
if (1 != EVP_CipherUpdate(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
out = pinnedCipherBuffer.addressOf(0).reinterpret(),
|
||||||
|
outl = tempLen.ptr,
|
||||||
|
`in` = pinnedInput.addressOf(0).reinterpret(),
|
||||||
|
inl = pinnedInput.get().size.convert()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
pinnedCipherBuffer.unpin()
|
||||||
|
free(tempLen.ptr, cipherSize.ptr)
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed do aes-256-cbc cipher update: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
cipherSize.value = tempLen.value
|
||||||
|
|
||||||
|
if (1 != EVP_CipherFinal(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
outm = pinnedCipherBuffer.addressOf(tempLen.value).reinterpret(),
|
||||||
|
outl = tempLen.ptr
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
pinnedCipherBuffer.unpin()
|
||||||
|
free(tempLen.ptr, cipherSize.ptr)
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed do aes-256-cbc cipher final: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
cipherSize.value += tempLen.value
|
||||||
|
|
||||||
|
return pinnedCipherBuffer.get().copyOf(cipherSize.value).also {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
pinnedCipherBuffer.unpin()
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
299
mirai-core/src/nativeMain/kotlin/utils/crypto/RSANative.kt
Normal file
299
mirai-core/src/nativeMain/kotlin/utils/crypto/RSANative.kt
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils.crypto
|
||||||
|
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
import net.mamoe.mirai.internal.utils.getOpenSSLError
|
||||||
|
import net.mamoe.mirai.internal.utils.ref
|
||||||
|
import openssl.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reference:
|
||||||
|
* - https://stackoverflow.com/questions/70535625/openssl-rsa-encryption-decryption-with-evp-methods
|
||||||
|
* - https://www.openssl.org/docs/man3.1/man3/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate RSA key pair with size of [keySize].
|
||||||
|
* The public key pair is encoded with x.509, and the private key pair is encoded with PKCS8
|
||||||
|
*/
|
||||||
|
internal actual fun generateRSAKeyPair(keySize: Int): RSAKeyPair {
|
||||||
|
memScoped {
|
||||||
|
val evpPkeyCtx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, null)
|
||||||
|
?: error("Failed to create evp pkey context: ${getOpenSSLError()}")
|
||||||
|
|
||||||
|
if (EVP_PKEY_keygen_init(evpPkeyCtx) <= 0) {
|
||||||
|
error("Failed to init evp pkey context: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// libcrypto 3 move EVP_PKEY_CTX_set_rsa_keygen_bits from macro to function
|
||||||
|
if (_evpPkeyCtxSetRSAKeygenBits(evpPkeyCtx, keySize) <= 0) {
|
||||||
|
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||||
|
error("Failed to set key bit for rsa evp pkey: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val evpPKey = EVP_PKEY_new() ?: kotlin.run {
|
||||||
|
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||||
|
error("Failed to create evp pkey: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_PKEY_keygen(evpPkeyCtx, ref(evpPKey)) <= 0) {
|
||||||
|
EVP_PKEY_free(evpPKey)
|
||||||
|
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||||
|
error("Failed to generate rsa key pair: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val publicPemKey = dumpPKey(evpPKey) { b, k -> PEM_write_bio_PUBKEY(b, k) }
|
||||||
|
?: kotlin.run {
|
||||||
|
EVP_PKEY_free(evpPKey)
|
||||||
|
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||||
|
error("Failed to dump rsa public key: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
val privatePemKey = dumpPKey(evpPKey) { b, k ->
|
||||||
|
PEM_write_bio_PKCS8PrivateKey(b, k, null, null, 0, null, null)
|
||||||
|
} ?: kotlin.run {
|
||||||
|
EVP_PKEY_free(evpPKey)
|
||||||
|
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||||
|
error("Failed to dump rsa public key: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_PKEY_free(evpPKey)
|
||||||
|
EVP_PKEY_CTX_free(evpPkeyCtx)
|
||||||
|
|
||||||
|
return RSAKeyPair(publicPemKey, privatePemKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnsafeNumber::class)
|
||||||
|
private inline fun MemScope.dumpPKey(
|
||||||
|
evpPKey: CPointer<EVP_PKEY>,
|
||||||
|
dumper: (CPointer<BIO>, CPointer<EVP_PKEY>) -> Unit
|
||||||
|
): String? {
|
||||||
|
val bio = BIO_new(BIO_s_mem()) ?: error("Failed to init mem BIO: ${getOpenSSLError()}")
|
||||||
|
|
||||||
|
dumper(bio, evpPKey)
|
||||||
|
BIO_ctrl(bio, BIO_CTRL_FLUSH, 0, null)
|
||||||
|
|
||||||
|
val pKeyBuf = allocPointerTo<ByteVar>()
|
||||||
|
BIO_ctrl(bio, BIO_CTRL_INFO, 0, pKeyBuf.ptr)
|
||||||
|
|
||||||
|
return pKeyBuf.value?.toKString().also { BIO_free(bio) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MemScope.loadPKey(
|
||||||
|
plainPemKey: String,
|
||||||
|
reader: (CPointer<BIO>) -> CPointer<RSA>?
|
||||||
|
): CPointer<RSA>? {
|
||||||
|
val bio = BIO_new(BIO_s_mem()) ?: error("Failed to init mem BIO: ${getOpenSSLError()}")
|
||||||
|
|
||||||
|
return plainPemKey.encodeToByteArray().usePinned {
|
||||||
|
BIO_write(bio, it.addressOf(0), it.get().size)
|
||||||
|
reader(bio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal actual fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray {
|
||||||
|
memScoped {
|
||||||
|
val pubPKey = loadPKey(plainPubPemKey) {
|
||||||
|
PEM_read_bio_RSA_PUBKEY(it, null, null, null)
|
||||||
|
} ?: error("Failed to read pem key from BIO: ${getOpenSSLError()}")
|
||||||
|
|
||||||
|
val pinnedInput = input.pin()
|
||||||
|
val encMsg = ByteArray(4096).pin()
|
||||||
|
|
||||||
|
val encMsgLen = RSA_public_encrypt(
|
||||||
|
flen = pinnedInput.get().size,
|
||||||
|
from = pinnedInput.addressOf(0).reinterpret(),
|
||||||
|
to = encMsg.addressOf(0).reinterpret(),
|
||||||
|
rsa = pubPKey,
|
||||||
|
padding = RSA_PKCS1_PADDING
|
||||||
|
)
|
||||||
|
if (encMsgLen <= 0) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
encMsg.unpin()
|
||||||
|
RSA_free(pubPKey)
|
||||||
|
error("Failed to do rsa decrypt: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return encMsg.get().copyOf(encMsgLen).also {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
encMsg.unpin()
|
||||||
|
RSA_free(pubPKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (1 != EVP_SealInit(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
type = aes256CBC,
|
||||||
|
ek = encKey.ptr,
|
||||||
|
ekl = encKeyLen.ptr,
|
||||||
|
pubk = ref(pubPKey),
|
||||||
|
iv = iv.ptr,
|
||||||
|
npubk = 1,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
free(encKey.ptr, encKeyLen.ptr, iv.ptr)
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to init evp seal: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
println("total size: ${pinnedInput.get().size + 1 + EVP_MAX_IV_LENGTH}")
|
||||||
|
val encMsgLen = alloc<size_tVar>().apply { value = 0u }
|
||||||
|
val blockSize = alloc<size_tVar>().apply { value = 0u }
|
||||||
|
|
||||||
|
if (1 != EVP_EncryptUpdate(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
out = encMsg.addressOf(encMsgLen.value.convert()).reinterpret(),
|
||||||
|
outl = blockSize.ptr.reinterpret(),
|
||||||
|
`in` = pinnedInput.addressOf(0).reinterpret(),
|
||||||
|
inl = pinnedInput.get().size
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
encMsg.unpin()
|
||||||
|
free(encMsgLen.ptr, blockSize.ptr, encKey.ptr, encKeyLen.ptr, iv.ptr)
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to update evp seal: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
println("${encMsgLen.value}, ${blockSize.value}")
|
||||||
|
encMsgLen.value += blockSize.value
|
||||||
|
println("${encMsg.addressOf(0)}, ${encMsg.addressOf(encMsgLen.value.convert())}")
|
||||||
|
|
||||||
|
if (1 != EVP_SealFinal(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
out = encMsg.addressOf(encMsgLen.value.convert()).reinterpret(),
|
||||||
|
outl = blockSize.ptr.reinterpret()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
encMsg.unpin()
|
||||||
|
free(encMsgLen.ptr, blockSize.ptr, encKey.ptr, encKeyLen.ptr, iv.ptr)
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to do final evp seal: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
println("${encMsgLen.value}, ${blockSize.value}")
|
||||||
|
encMsgLen.value += blockSize.value
|
||||||
|
|
||||||
|
return encMsg.get().copyOf(encMsgLen.value.convert()).also {
|
||||||
|
encMsg.unpin()
|
||||||
|
pinnedInput.unpin()
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
}.toByteArray()*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal actual fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray {
|
||||||
|
memScoped {
|
||||||
|
val evpCipherCtx = EVP_CIPHER_CTX_new()
|
||||||
|
?: error("Failed to create evp cipher context: ${getOpenSSLError()}")
|
||||||
|
|
||||||
|
val privKey = loadPKey(plainPrivPemKey) {
|
||||||
|
PEM_read_bio_RSAPrivateKey(it, null, null, null)
|
||||||
|
} ?: kotlin.run {
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to read pem key from BIO: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val pinnedInput = input.pin()
|
||||||
|
val encMsg = UByteArray(4096).pin()
|
||||||
|
|
||||||
|
val encMsgLen = RSA_private_decrypt(
|
||||||
|
flen = pinnedInput.get().size,
|
||||||
|
from = pinnedInput.addressOf(0).reinterpret(),
|
||||||
|
to = encMsg.addressOf(0).reinterpret(),
|
||||||
|
rsa = privKey,
|
||||||
|
padding = RSA_PKCS1_PADDING
|
||||||
|
)
|
||||||
|
if (encMsgLen <= 0) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
encMsg.unpin()
|
||||||
|
RSA_free(privKey)
|
||||||
|
error("Failed to do rsa decrypt: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return encMsg.get().copyOf(encMsgLen).toByteArray().also {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
encMsg.unpin()
|
||||||
|
RSA_free(privKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*println(dumpPKey(privKey) { b, k -> PEM_write_bio_PKCS8PrivateKey(b, k, null, null, 0, null, null) })
|
||||||
|
|
||||||
|
val decKeyLen = EVP_PKEY_get_size(privKey)
|
||||||
|
println("evp_pkey_size: $decKeyLen")
|
||||||
|
val decKey = ByteArray(decKeyLen).pin()
|
||||||
|
val pinnedIv = ByteArray(16).pin()
|
||||||
|
|
||||||
|
if (1 != EVP_OpenInit(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
type = aes256CBC,
|
||||||
|
ek = decKey.addressOf(0).reinterpret(),
|
||||||
|
ekl = decKeyLen,
|
||||||
|
iv = pinnedIv.addressOf(0).reinterpret(),
|
||||||
|
priv = privKey
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedIv.unpin()
|
||||||
|
decKey.unpin()
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to init evp open: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
println("init")
|
||||||
|
|
||||||
|
val pinnedInput = input.pin()
|
||||||
|
val decMsg = ByteArray(
|
||||||
|
pinnedInput.get().size + EVP_CIPHER_CTX_get_block_size(evpCipherCtx)
|
||||||
|
).pin()
|
||||||
|
val decMsgLen = alloc<size_tVar>().apply { value = 0u }
|
||||||
|
val blockSize = alloc<size_tVar>().apply { value = 0u }
|
||||||
|
|
||||||
|
if (1 != EVP_DecryptUpdate(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
out = decMsg.addressOf(0).reinterpret(),
|
||||||
|
outl = blockSize.ptr.reinterpret(),
|
||||||
|
`in` = pinnedInput.addressOf(0).reinterpret(),
|
||||||
|
inl = pinnedInput.get().size
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
decMsg.unpin()
|
||||||
|
pinnedIv.unpin()
|
||||||
|
decKey.unpin()
|
||||||
|
free(decMsgLen.ptr, blockSize.ptr)
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to update evp open: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
decMsgLen.value += blockSize.value
|
||||||
|
println("update")
|
||||||
|
|
||||||
|
if (1 != EVP_OpenFinal(
|
||||||
|
ctx = evpCipherCtx,
|
||||||
|
out = decMsg.addressOf(decMsgLen.value.convert()).reinterpret(),
|
||||||
|
outl = blockSize.ptr.reinterpret()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
pinnedInput.unpin()
|
||||||
|
decMsg.unpin()
|
||||||
|
pinnedIv.unpin()
|
||||||
|
decKey.unpin()
|
||||||
|
free(decMsgLen.ptr, blockSize.ptr)
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
error("Failed to do final evp open: ${getOpenSSLError()}")
|
||||||
|
}
|
||||||
|
decMsgLen.value += blockSize.value
|
||||||
|
println("final")
|
||||||
|
|
||||||
|
return decMsg.get().copyOf(decMsgLen.value.convert()).also {
|
||||||
|
decMsg.unpin()
|
||||||
|
pinnedInput.unpin()
|
||||||
|
pinnedIv.unpin()
|
||||||
|
decKey.unpin()
|
||||||
|
EVP_CIPHER_CTX_free(evpCipherCtx)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
19
mirai-core/src/nativeMain/kotlin/utils/freePointer.kt
Normal file
19
mirai-core/src/nativeMain/kotlin/utils/freePointer.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils
|
||||||
|
|
||||||
|
import kotlinx.cinterop.CValuesRef
|
||||||
|
import platform.posix.free
|
||||||
|
|
||||||
|
internal fun free(
|
||||||
|
vararg refs: CValuesRef<*>
|
||||||
|
) {
|
||||||
|
refs.forEach(::free)
|
||||||
|
}
|
27
mirai-core/src/nativeMain/kotlin/utils/getOpenSSLError.kt
Normal file
27
mirai-core/src/nativeMain/kotlin/utils/getOpenSSLError.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils
|
||||||
|
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
import openssl.*
|
||||||
|
|
||||||
|
@OptIn(UnsafeNumber::class)
|
||||||
|
internal fun getOpenSSLError(): String {
|
||||||
|
memScoped {
|
||||||
|
val bio = BIO_new(BIO_s_mem())
|
||||||
|
val errBuffer = allocPointerTo<ByteVar>()
|
||||||
|
|
||||||
|
ERR_print_errors(bio)
|
||||||
|
BIO_ctrl(bio, BIO_CTRL_FLUSH, 0, null)
|
||||||
|
BIO_ctrl(bio, BIO_CTRL_INFO, 0, errBuffer.ptr)
|
||||||
|
|
||||||
|
return errBuffer.value?.toKString()?.also { BIO_free(bio) } ?: "openssl error: no message"
|
||||||
|
}
|
||||||
|
}
|
19
mirai-core/src/nativeMain/kotlin/utils/ref.kt
Normal file
19
mirai-core/src/nativeMain/kotlin/utils/ref.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2023 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.utils
|
||||||
|
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns reference to a pointer(**variable), equivalent to `my_type **a = &myTypePtr`
|
||||||
|
*/
|
||||||
|
internal inline fun <T : CPointed> NativePlacement.ref(ptr: CPointer<T>): CPointer<CPointerVar<T>> {
|
||||||
|
return allocPointerTo<T>().apply { value = ptr }.ptr
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user