Commit b062c580 authored by Him188's avatar Him188

Android stuff

parent 67903e67
......@@ -66,6 +66,8 @@ actual class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(
get() = md5(kotlin.runCatching {
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).subscriberId.toByteArray()
}.getOrElse { byteArrayOf() })
override val imei: String get() = "858414369211993" // TODO: 2020/1/5 get actual imei and ksid
override val ksid: String get() = "|454001228437590|A8.2.0.27f6ea96"
override val ipAddress: String get() = localIpAddress()
override val androidId: ByteArray get() = Build.ID.toByteArray()
override val apn: ByteArray get() = "wifi".toByteArray()
......
package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.qqandroid.utils.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.hexToBytes
/*
......@@ -33,7 +36,11 @@ internal open class QQAndroidClient(
var mainSigMap: Int = 16724722
var subSigMap: Int = 0x10400 //=66,560
var ssoSequenceId: Int = 85602
private val _ssoSequenceId: AtomicInt = atomic(85600)
@MiraiInternalAPI("Do not use directly. Get from the lambda param of buildSsoPacket")
internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
var openAppId: Long = 715019303L
val apkVersionName: ByteArray = "8.2.0".toByteArray()
......
package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.io.core.*
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
import net.mamoe.mirai.qqandroid.utils.ECDH
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeIntLVPacket
import net.mamoe.mirai.utils.io.writeQQ
import net.mamoe.mirai.utils.io.writeShortLVByteArray
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
import net.mamoe.mirai.utils.cryptor.encryptBy
import net.mamoe.mirai.utils.io.*
/**
* 待发送给服务器的数据包. 它代表着一个 [ByteReadPacket],
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
class OutgoingPacket constructor(
internal class OutgoingPacket constructor(
name: String?,
val packetId: PacketId,
val sequenceId: UShort,
......@@ -27,28 +30,218 @@ class OutgoingPacket constructor(
}
}
private val KEY_16_ZEROS = ByteArray(16)
private val EMPTY_BYTE_ARRAY = ByteArray(0)
/**
* Encryption method to be used for packet body.
* Outermost packet for login
*
* **Packet structure**
* int remaining.length + 4
* int 0x0A
* byte 0x02
* int extra data size + 4
* byte[] extra data
* byte 0
* int [uinAccount].length + 4
* byte[] uinAccount
*
* byte[] body encrypted by 16 zero
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
inline class EncryptMethod(val value: UByte) {
companion object {
val BySessionToken = EncryptMethod(69u)
val ByECDH7 = EncryptMethod(7u)
// 登录都使用 135
val ByECDH135 = EncryptMethod(135u)
internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
uinAccount: String,
extraData: ByteArray = EMPTY_BYTE_ARRAY,
name: String? = null,
id: PacketId = this.id,
sequenceId: UShort = PacketFactory.atomicNextSequenceId(),
body: BytePacketBuilder.() -> Unit
): OutgoingPacket = OutgoingPacket(name, id, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x00_00_00_0A)
writeByte(0x02)
writeInt(extraData.size + 4)
writeFully(extraData)
writeByte(0x00)
writeInt(uinAccount.length + 4)
writeStringUtf8(uinAccount)
encryptAndWrite(KEY_16_ZEROS, body)
}
})
internal class CommandId(val stringValue: String, val id: Int) {
override fun toString(): String = stringValue
}
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
/**
* The second outermost packet for login
*
*
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeLoginSsoPacket(
client: QQAndroidClient,
subAppId: Long,
commandId: CommandId,
extraData: ByteReadPacket = BRP_STUB,
body: BytePacketBuilder.(ssoSequenceId: Int) -> Unit
) {
val ssoSequenceId = client.nextSsoSequenceId()
// head
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(ssoSequenceId)
writeInt(subAppId.toInt())
writeInt(subAppId.toInt())
writeHex("01 00 00 00 00 00 00 00 00 00 01 00")
if (extraData === BRP_STUB) {
writeInt(0x04)
} else {
writeInt((extraData.remaining + 4).toInt())
writePacket(extraData)
}
writeInt(commandId.stringValue.length + 4)
writeStringUtf8(commandId.stringValue)
writeInt(4 + 4)
writeInt(45112203) // 02 B0 5B 8B
val imei = client.device.imei
writeInt(imei.length + 4)
writeStringUtf8(imei)
writeInt(4)
val ksid = client.device.ksid
writeShort((ksid.length + 2).toShort())
writeStringUtf8(ksid)
writeInt(4)
}
// body
writeIntLVPacket(lengthOffset = { it + 4 }) {
body(ssoSequenceId)
}
}
/**
* Builds a outgoing packet.
* [OutgoingPacket] is the **outermost** packet structure.
* This packet will be sent to the server.
* Outermost packet, not for login
*
* **Packet structure**
* int remaining.length + 4
* int 0x0B
* byte 0x01
* byte 0
* int [uinAccount].length + 4
* byte[] uinAccount
*
* byte[] body encrypted by [sessionKey]
*/
internal inline fun PacketFactory<*, *>.buildSessionOutgoingPacket(
uinAccount: String,
sessionKey: DecrypterByteArray,
body: BytePacketBuilder.() -> Unit
): ByteReadPacket = buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x00_00_00_0B)
writeByte(0x01)
writeByte(0)
writeInt(uinAccount.length + 4)
writeStringUtf8(uinAccount)
encryptAndWrite(sessionKey, body)
}
}
/**
* Encryption method to be used for packet body.
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
interface EncryptMethod {
val id: Int
fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket)
}
internal interface EncryptMethodSessionKey : EncryptMethod {
override val id: Int get() = 69
val currentLoginState: Int
val sessionKey: ByteArray
/**
* buildPacket{
* byte 1
* byte if (currentLoginState == 2) 3 else 2
* fully key
* short 258
* short 0
* fully encrypted
* }
*/
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) {
require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" }
writeByte(1) // const
writeByte(if (currentLoginState == 2) 3 else 2)
writeFully(sessionKey)
writeShort(258) // const
writeShort(0) // const, length of publicKey
body.encryptBy(sessionKey) { encrypted -> writeFully(encrypted) }
}
}
inline class EncryptMethodSessionKeyLoginState2(override val sessionKey: ByteArray) : EncryptMethodSessionKey {
override val currentLoginState: Int get() = 2
}
inline class EncryptMethodSessionKeyLoginState3(override val sessionKey: ByteArray) : EncryptMethodSessionKey {
override val currentLoginState: Int get() = 3
}
inline class EncryptMethodECDH135(override val ecdh: ECDH) : EncryptMethodECDH {
override val id: Int get() = 135
}
inline class EncryptMethodECDH7(override val ecdh: ECDH) : EncryptMethodECDH {
override val id: Int get() = 7
}
internal interface EncryptMethodECDH : EncryptMethod {
val ecdh: ECDH
/**
* **Packet Structure**
* byte 1
* byte 1
* byte[] [ECDH.privateKey]
* short 258
* short [ECDH.publicKey].size
* byte[] [ECDH.publicKey]
* byte[] encrypted `body()` by [ECDH.shareKey]
*/
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) = ecdh.run {
writeByte(1) // const
writeByte(1) // const
writeFully(privateKey)
writeShort(258) // const
writeShortLVByteArray(publicKey)
body.encryptBy(shareKey) { encrypted -> writeFully(encrypted) }
}
}
/**
* Writes a request packet
* This is the innermost packet structure
*
* **Packet Structure**
* byte 2 // head flag
* short 27 + 2 + body.size
* short 27 + 2 + remaining.length
* ushort client.protocolVersion // const 8001
* ushort sequenceId
* uint client.account.id
......@@ -60,44 +253,39 @@ inline class EncryptMethod(val value: UByte) {
* int 0 // const
* bodyBlock()
* byte 3 // tail
*
* @param name optional name to be displayed in logs
*
*/
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal inline fun PacketFactory<*, *>.buildOutgoingPacket(
internal inline fun BytePacketBuilder.writeRequestPacket(
client: QQAndroidClient,
encryptMethod: EncryptMethod,
name: String? = null,
id: PacketId = this.id,
commandId: CommandId,
sequenceId: UShort = PacketFactory.atomicNextSequenceId(),
bodyBlock: BytePacketBuilder.() -> Unit
): OutgoingPacket {
val body = buildPacket { bodyBlock() }
return OutgoingPacket(name, id, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
// Head
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion)
writeShort(sequenceId.toShort())
writeShort(id.commandId.toShort())
writeQQ(client.account.id)
writeByte(3) // originally const
writeUByte(encryptMethod.value)
writeByte(0) // const8_always_0
writeInt(2) // originally const
writeInt(client.appClientVersion)
writeInt(0) // constp_always_0
// Body
writePacket(body)
// Tail
writeByte(0x03) // tail
}
})
) {
val body = encryptMethod.run {
buildPacket { encryptAndWrite(buildPacket { bodyBlock() }) }
}
writeIntLVPacket(lengthOffset = { it + 4 }) {
// Head
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion)
writeShort(sequenceId.toShort())
writeShort(commandId.id.toShort())
writeQQ(client.account.id)
writeByte(3) // originally const
writeByte(encryptMethod.id.toByte())
writeByte(0) // const8_always_0
writeInt(2) // originally const
writeInt(client.appClientVersion)
writeInt(0) // constp_always_0
// Body
writePacket(body)
// Tail
writeByte(0x03) // tail
}
}
/*
00 00 01 64
......@@ -158,57 +346,4 @@ F8 22 FC 39 2E 93 D7 73 A9 75 A2 D4 67 D2 C4 0D F1 02 1F A5 74 8F D8 0E 8E 86 AF
00 00 00 0E 31 39 39 34 37 30 31 30 32 31 D2 D5 37 8A 3C 47 B1 84 E2 94 B2 AF BF 14 70 4D 73 17 BB 38 BE 82 73 DF A2 87 E0 0A 7A BA 8A 81 71 77 1D E1 71 7F B7 C1 66 1D 8C 3D 41 4F 51 09 6A B7 B7 7B 88 28 A6 5A AB 7E 40 25 9B C8 35 9C C6 E2 3A 5F 94 1D 70 0F D7 89 4D 41 6B 7A 29 A2 70 77 3D F8 1D 32 65 D7 D8 D1 6D 13 42 9C 0C 72 DB 48 95 4B 66 EF B9 E6 E4 C1 3B 2C 36 B0 D7 3F E2 85 C8 2A 8C 65 0F 0B 1C F1 A7 C7 E1 1F 0C 32 F5 08 14 AA 5A 43 CD 8E A8 82 14 24 97 63 F0 53 79 4E 33 8D 5F 1C F8 1C 89 3B 39 44 CC A7 63 5F FC BF 87 42 89 2D A5 F4 BC B2 69 49 54 DD AE E6 3F A2 A2 98 DC 3B D4 A2 27 10 F2 06 42 93 C5 30 4A D4 FA F5 BA A5 B2 4B 56 45 59 94 CA 4C 4B 17 55 C7 23 AF F0 8B E5 DC 3A 1B B6 A7 2E 10 BB 9A E7 70 54 BA F5 4B 70 91
*/
/**
* Encrypt the [body] by [ECDH.shareKey], then write encryption arguments stuff.
* This is **the second outermost** packet structure
*
* **Packet Structure**
* byte 1
* byte 1
* byte[] [ECDH.privateKey]
* short 258
* short [ECDH.publicKey].size
* byte[] [ECDH.publicKey]
* byte[] encrypted `body()` by [ECDH.shareKey]
*/
inline fun BytePacketBuilder.writeECDHEncryptedPacket(
ecdh: ECDH,
body: BytePacketBuilder.() -> Unit
) = ecdh.run {
writeByte(1) // const
writeByte(1) // const
writeFully(privateKey)
writeShort(258) // const
writeShortLVByteArray(publicKey)
encryptAndWrite(shareKey, body)
}
/**
* buildPacket{
* byte 1
* if loginState == 2:
* byte 3
* else:
* byte 2
* fully key
* short 258
* short 0
* fully encrypted
* }
*/
inline fun BytePacketBuilder.writeTEAEncryptedPacket(
loginState: Int,
key: ByteArray,
body: BytePacketBuilder.() -> Unit
) {
require(loginState == 2 || loginState == 3)
writeByte(1) // const
writeByte(if (loginState == 2) 3 else 2) // const
writeFully(key)
writeShort(258) // const
writeShort(0) // const, length of publicKey
encryptAndWrite(key, body)
}
\ No newline at end of file
*/
\ No newline at end of file
......@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.cryptor.DecrypterType
* @param TDecrypter 服务器回复包解密器
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val decrypterType: DecrypterType<TDecrypter>) {
internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val decrypterType: DecrypterType<TDecrypter>) {
@Suppress("PropertyName")
internal var _id: PacketId = NullPacketId
......
......@@ -22,140 +22,142 @@ class LoginPacketDecrypter(override val value: ByteArray) : DecrypterByteArray {
@UseExperimental(ExperimentalUnsignedTypes::class)
internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, LoginPacketDecrypter>(LoginPacketDecrypter) {
init {
this._id = PacketId(0x0810, 9)
this._id = PacketId(CommandId("wtlogin.login", 0x0810), 9)
}
operator fun invoke(
client: QQAndroidClient
): OutgoingPacket = buildOutgoingPacket(client, EncryptMethod.ByECDH135) {
writeECDHEncryptedPacket(client.ecdh) {
writeShort(9) // subCommand
writeShort(LoginType.PASSWORD.value.toShort())
val appId = 16L
val subAppId = 537062845L
t18(appId, client.appClientVersion, client.account.id)
t1(client.account.id, client.device.ipAddress)
t106(
appId,
subAppId /* maybe 1*/,
client.appClientVersion,
client.account.id,
1,
client.account.passwordMd5,
0,
client.account.id.toByteArray(),
client.tgtgtKey,
true,
client.device.guid,
LoginType.PASSWORD
)
/* // from GetStWithPasswd
int mMiscBitmap = this.mMiscBitmap;
if (t.uinDeviceToken) {
mMiscBitmap = (this.mMiscBitmap | 0x2000000);
}
// defaults true
if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
*/
t116(client.miscBitMap, client.subSigMap)
t100(appId, subAppId, client.appClientVersion, client.mainSigMap or 0xC0)
t107(0)
// t108(byteArrayOf())
// ignored: t104()
t142(client.apkId)
// if login with non-number uin
// t112()
t144(
androidId = client.device.androidId,
androidDevInfo = client.device.generateDeviceInfoData(),
osType = client.device.osType,
osVersion = client.device.version.release,
networkType = client.networkType,
simInfo = client.device.simInfo,
unknown = byteArrayOf(),
apn = client.device.apn,
isGuidFromFileNull = false,
isGuidAvailable = true,
isGuidChanged = false,
guidFlag = guidFlag(GuidSource.FROM_STORAGE, MacOrAndroidIdChangeFlag.NoChange),
buildModel = client.device.model,
guid = client.device.guid,
buildBrand = client.device.brand,
tgtgtKey = client.tgtgtKey
)
t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5)
if (client.miscBitMap and 0x80 != 0) {
t166(1)
}
// ignored t16a because array5 is null
t154(client.ssoSequenceId)
t141(client.device.simInfo, client.networkType, client.device.apn)
t8(2052)
t511(
listOf(
"tenpay.com",
"openmobile.qq.com",
"docs.qq.com",
"connect.qq.com",
"qzone.qq.com",
"vip.qq.com",
"qun.qq.com",
"game.qq.com",
"qqweb.qq.com",
"office.qq.com",
"ti.qq.com",
"mail.qq.com",
"qzone.com",
"mma.qq.com"
): OutgoingPacket = buildLoginOutgoingPacket(client.account.id.toString()) {
val appId = 16L
val subAppId = 537062845L
writeLoginSsoPacket(client, subAppId, _id.commandId) { ssoSequenceId ->
writeRequestPacket(client, EncryptMethodECDH135(client.ecdh), _id.commandId) {
writeShort(9) // subCommand
writeShort(LoginType.PASSWORD.value.toShort())
t18(appId, client.appClientVersion, client.account.id)
t1(client.account.id, client.device.ipAddress)
t106(
appId,
subAppId /* maybe 1*/,
client.appClientVersion,
client.account.id,
1,
client.account.passwordMd5,
0,
client.account.id.toByteArray(),
client.tgtgtKey,
true,
client.device.guid,
LoginType.PASSWORD
)
)
// ignored t172 because rollbackSig is null
// ignored t185 because loginType is not SMS
// ignored t400 because of first login
t187(client.device.macAddress)
t188(client.device.androidId)
if (client.device.imsiMd5.isNotEmpty()) {
t194(client.device.imsiMd5)
}
t191()
/* // from GetStWithPasswd
int mMiscBitmap = this.mMiscBitmap;
if (t.uinDeviceToken) {
mMiscBitmap = (this.mMiscBitmap | 0x2000000);
}
// defaults true
if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
*/
t116(client.miscBitMap, client.subSigMap)
t100(appId, subAppId, client.appClientVersion, client.mainSigMap or 0xC0)
t107(0)
// t108(byteArrayOf())
// ignored: t104()
t142(client.apkId)
// if login with non-number uin
// t112()
t144(
androidId = client.device.androidId,
androidDevInfo = client.device.generateDeviceInfoData(),
osType = client.device.osType,
osVersion = client.device.version.release,
networkType = client.networkType,
simInfo = client.device.simInfo,
unknown = byteArrayOf(),
apn = client.device.apn,
isGuidFromFileNull = false,
isGuidAvailable = true,
isGuidChanged = false,
guidFlag = guidFlag(GuidSource.FROM_STORAGE, MacOrAndroidIdChangeFlag.NoChange),
buildModel = client.device.model,
guid = client.device.guid,
buildBrand = client.device.brand,
tgtgtKey = client.tgtgtKey
)
/*
t201(N = byteArrayOf())*/
t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5)
if (client.miscBitMap and 0x80 != 0) {
t166(1)
}
// ignored t16a because array5 is null
t154(ssoSequenceId)
t141(client.device.simInfo, client.networkType, client.device.apn)
t8(2052)
t511(
listOf(
"tenpay.com",
"openmobile.qq.com",
"docs.qq.com",
"connect.qq.com",
"qzone.qq.com",
"vip.qq.com",
"qun.qq.com",
"game.qq.com",
"qqweb.qq.com",
"office.qq.com",
"ti.qq.com",
"mail.qq.com",
"qzone.com",
"mma.qq.com"
)
)
val bssid = client.device.wifiBSSID
val ssid = client.device.wifiSSID
if (bssid != null && ssid != null) {
t202(bssid, ssid)
// ignored t172 because rollbackSig is null
// ignored t185 because loginType is not SMS
// ignored t400 because of first login
t187(client.device.macAddress)
t188(client.device.androidId)
if (client.device.imsiMd5.isNotEmpty()) {
t194(client.device.imsiMd5)
}
t191()
/*
t201(N = byteArrayOf())*/
val bssid = client.device.wifiBSSID
val ssid = client.device.wifiSSID
if (bssid != null && ssid != null) {
t202(bssid, ssid)
}
t177()
t516()
t521()
t525(buildPacket {
t536(buildPacket {
//com.tencent.loginsecsdk.ProtocolDet#packExtraData
writeByte(1) // const
writeByte(0) // data count
}.readBytes())
})
// ignored t318 because not logging in by QR
}
t177()
t516()
t521()
t525(buildPacket {
t536(buildPacket {
//com.tencent.loginsecsdk.ProtocolDet#packExtraData
writeByte(1) // const
writeByte(0) // data count
}.readBytes())
})
// ignored t318 because not logging in by QR
}
}
......@@ -170,20 +172,20 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
@Suppress("FunctionName")
fun PacketId(commandId: Int, subCommandId: Int) = object : PacketId {
override val commandId: Int
internal fun PacketId(commandId: CommandId, subCommandId: Int) = object : PacketId {
override val commandId: CommandId
get() = commandId
override val subCommandId: Int
get() = subCommandId
}
interface PacketId {
val commandId: Int // ushort actually
internal interface PacketId {
val commandId: CommandId // ushort actually
val subCommandId: Int // ushort actually
}
object NullPacketId : PacketId {
override val commandId: Int
internal object NullPacketId : PacketId {
override val commandId: CommandId
get() = error("uninitialized")
override val subCommandId: Int
get() = error("uninitialized")
......
......@@ -33,6 +33,8 @@ abstract class DeviceInfo(
abstract val wifiSSID: ByteArray?
abstract val imsiMd5: ByteArray
abstract val imei: String
abstract val ksid: String
abstract val ipAddress: String
......
......@@ -26,6 +26,8 @@ actual class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(
@UseExperimental(ExperimentalUnsignedTypes::class)
override val imsiMd5: ByteArray
get() = ubyteArrayOf(0xD4u, 0x1Du, 0x8Cu, 0xD9u, 0x8Fu, 0x00u, 0xB2u, 0x04u, 0xE9u, 0x80u, 0x09u, 0x98u, 0xECu, 0xF8u, 0x42u, 0x7Eu).toByteArray()
override val imei: String get() = "858414369211993"
override val ksid: String get() = "|454001228437590|A8.2.0.27f6ea96"
override val ipAddress: String get() = localIpAddress()
override val androidId: ByteArray get() = "QSR1.190920.001".toByteArray()
override val apn: ByteArray get() = "wifi".toByteArray()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment