Commit 55e6c934 authored by Him188's avatar Him188

JceStruct serialization

parent 2f4a4a48
package net.mamoe.mirai.qqandroid.io
import kotlinx.io.charsets.Charset
import kotlinx.io.core.*
import kotlinx.io.pool.ObjectPool
import kotlinx.serialization.DeserializationStrategy
import net.mamoe.mirai.qqandroid.io.serialization.Jce
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.utils.io.DebugLogger
import net.mamoe.mirai.utils.io.readIoBuffer
import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toIoBuffer
@UseExperimental(ExperimentalUnsignedTypes::class)
inline class JceHead(private val value: Long) {
constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
val tag: Int get() = (value ushr 32).toInt()
val type: Byte get() = value.toUInt().toByte()
override fun toString(): String {
return "JceHead(tag=$tag, type=$type)"
}Z
}
fun <J : JceStruct> ByteArray.readJceStruct(
deserializer: DeserializationStrategy<J>,
tag: Int = 0,
charset: Charset = CharsetUTF8
): J {
this.asJceInput(charset).use {
return Jce.byCharSet(charset).load(deserializer, this.)
}
}
fun <J : JceStruct> ByteReadPacket.readJceStruct(factory: JceStruct.Factory<J>, tag: Int = 0, charset: Charset = CharsetUTF8): J {
this.asJceInput(charset).use {
return it.readJceStruct(factory, tag)
}
}
fun ByteArray.asJceInput(charset: Charset = CharsetUTF8): JceInput =
JceInput(this.toIoBuffer(), charset)
fun <J : JceStruct> ByteReadPacket.readJceRequestBufferMapVersion2ToJceStruct(factory: JceStruct.Factory<J>, charset: Charset = CharsetUTF8): J {
this.use {
val bytes =
readJceRequestBufferMapVersion2(charset).values.also { if (it.size != 1) DebugLogger.debug("读取 jce RequestPacket 时发现多个包在 map 中") }.firstOrNull()
?: error("empty request map")
return bytes.readJceStruct(factory, 0)
}
}
fun <J : JceStruct> ByteReadPacket.readJceRequestBufferMapVersion3ToJceStruct(factory: JceStruct.Factory<J>, charset: Charset = CharsetUTF8): J {
this.use {
val bytes = readJceRequestBufferMapVersion3(charset).values.firstOrNull() ?: error("empty request map")
return bytes.readJceStruct(factory, 0, charset)
}
}
fun ByteReadPacket.readJceRequestBufferMapVersion2(charset: Charset = CharsetUTF8): Map<String, ByteArray> {
this.use {
discardExact(8)
val request = this.asJceInput(charset).use {
Jce
RequestPacket.serializer()
}
val map = request.sBuffer.asJceInput(charset).withUse {
readNestedMap<String, String, ByteArray>(0)
}
return map.mapValues { it.value.values.first() }
}
}
fun ByteReadPacket.readJceRequestBufferMapVersion3(charset: Charset = CharsetUTF8): Map<String, ByteArray> {
this.use {
discardExact(8)
val request = this.asJceInput(charset).use { RequestPacket.newInstanceFrom(it) }
return request.sBuffer.asJceInput(charset).withUse { readMap(0) }
}
}
fun ByteReadPacket.asJceInput(charset: Charset = CharsetUTF8): JceInput =
JceInput(this.readIoBuffer(), charset)
inline fun <R> IoBuffer.useIoBuffer(block: IoBuffer.() -> R): R {
return try {
block(this)
} catch (first: Throwable) {
throw first
} finally {
release(IoBuffer.Pool)
}
}
inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
return try {
block(this)
} catch (first: Throwable) {
throw first
} finally {
close()
}
}
@Suppress("MemberVisibilityCanBePrivate")
@UseExperimental(ExperimentalUnsignedTypes::class)
class JceInput(
@PublishedApi
internal val input: IoBuffer,
private val charset: Charset = CharsetGBK,
private val pool: ObjectPool<IoBuffer> = IoBuffer.Pool
) : Closeable {
constructor(input: Input) : this(IoBuffer.Pool.borrow().also { input.readAvailable(it) })
override fun close() {
input.release(pool)
}
@PublishedApi
internal fun readHead(): JceHead = input.readHead()
@PublishedApi
internal fun peakHead(): JceHead = input.makeView().readHead()
private fun IoBuffer.readHead(): JceHead {
val var2 = readUByte()
val type = var2 and 15u
var tag = var2.toUInt() shr 4
if (tag == 15u)
tag = readUByte().toUInt()
return JceHead(tag = tag.toInt(), type = type.toByte())
}
fun read(default: Byte, tag: Int): Byte = readByteOrNull(tag) ?: default
fun read(default: Short, tag: Int): Short = readShortOrNull(tag) ?: default
fun read(default: Int, tag: Int): Int = readIntOrNull(tag) ?: default
fun read(default: Long, tag: Int): Long = readLongOrNull(tag) ?: default
fun read(default: Float, tag: Int): Float = readFloatOrNull(tag) ?: default
fun read(default: Double, tag: Int): Double = readDoubleOrNull(tag) ?: default
fun read(default: Boolean, tag: Int): Boolean = readBooleanOrNull(tag) ?: default
fun read(default: ByteArray, tag: Int): ByteArray = readByteArrayOrNull(tag) ?: default
fun read(default: ShortArray, tag: Int): ShortArray = readShortArrayOrNull(tag) ?: default
fun read(default: IntArray, tag: Int): IntArray = readIntArrayOrNull(tag) ?: default
fun read(default: LongArray, tag: Int): LongArray = readLongArrayOrNull(tag) ?: default
fun read(default: FloatArray, tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: default
fun read(default: DoubleArray, tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: default
fun read(default: BooleanArray, tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: default
fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag")
fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag")
fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag")
fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag")
fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag")
fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag")
fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag")
fun readString(tag: Int): String = readStringOrNull(tag) ?: error("cannot find tag $tag")
fun readByteArray(tag: Int): ByteArray = readByteArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readShortArray(tag: Int): ShortArray = readShortArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readLongArray(tag: Int): LongArray = readLongArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readFloatArray(tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readDoubleArray(tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readIntArray(tag: Int): IntArray = readIntArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readBooleanArray(tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: error("cannot find tag $tag")
fun <K, V> readMap(defaultKey: K, defaultValue: V, tag: Int): Map<K, V> = readMapOrNull(defaultKey, defaultValue, tag) ?: error("cannot find tag $tag")
inline fun <reified K, reified V> readMap(tag: Int): Map<K, V> = readMapOrNull(tag) ?: error("cannot find tag $tag")
inline fun <reified K, reified InnerK, reified InnerV> readNestedMap(tag: Int): Map<K, Map<InnerK, InnerV>> =
readNestedMapOrNull(tag) ?: error("cannot find tag $tag")
inline fun <reified J : JceStruct> readStringToJceStructMap(factory: JceStruct.Factory<J>, tag: Int): Map<String, J> =
readStringToJceStructMapOrNull(factory, tag) ?: error("cannot find tag $tag")
fun <T> readList(defaultElement: T, tag: Int): List<T> = readListOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
inline fun <reified J: JceStruct> readJceStructList(factory: JceStruct.Factory<J>, tag: Int): List<J> = readJceStructListOrNull( factory, tag) ?: error("cannot find tag $tag")
inline fun <reified T> readList(tag: Int): List<T> = readListOrNull( tag) ?: error("cannot find tag $tag")
inline fun <reified T> readSimpleArray(defaultElement: T, tag: Int): Array<T> = readArrayOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
fun <J : JceStruct> readJceStruct(factory: JceStruct.Factory<J>, tag: Int): J = readJceStructOrNull(factory, tag) ?: error("cannot find tag $tag")
fun readStringArray(tag: Int): Array<String> = readArrayOrNull("", tag) ?: error("cannot find tag $tag")
fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toLong()
1 -> input.readShort().toLong()
2 -> input.readInt().toLong()
3 -> input.readLong()
else -> error("type mismatch: ${it.type}")
}
}
fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toShort()
1 -> input.readShort()
else -> error("type mismatch: ${it.type}")
}
}
fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toInt()
1 -> input.readShort().toInt()
2 -> input.readInt()
else -> error("type mismatch: ${it.type}")
}
}
fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte()
else -> error("type mismatch")
}
}
fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0f
4 -> input.readFloat()
else -> error("type mismatch: ${it.type}")
}
}
fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0.0
4 -> input.readFloat().toDouble()
5 -> input.readDouble()
else -> error("type mismatch: ${it.type}")
}
}
fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 }
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
when (it.type.toInt()) {
9 -> ByteArray(readInt(0)) { readByte(0) }
13 -> {
val head = readHead()
check(head.type.toInt() == 0) { "type mismatch" }
input.readBytes(readInt(0))
}
else -> error("type mismatch")
}
}
fun readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
ShortArray(readInt(0)) { readShort(0) }
}
fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
DoubleArray(readInt(0)) { readDouble(0) }
}
fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
FloatArray(readInt(0)) { readFloat(0) }
}
fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
IntArray(readInt(0)) { readInt(0) }
}
fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
LongArray(readInt(0)) { readLong(0) }
}
@Suppress("UNCHECKED_CAST")
inline fun <reified T> readArrayOrNull(tag: Int): Array<T>? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
Array(readInt(0)) { readSimpleObject<T>(0) }
}
@Suppress("UNCHECKED_CAST")
inline fun <reified T> readArrayOrNull(defaultElement: T, tag: Int): Array<T>? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
Array(readInt(0)) { readObject(defaultElement, 0) }
}
@Suppress("UNCHECKED_CAST")
inline fun <reified J : JceStruct> readJceStructArrayOrNull(factory: JceStruct.Factory<J>, tag: Int): Array<J>? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
Array(readInt(0)) { readJceStruct(factory, 0) }
}
fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
BooleanArray(readInt(0)) { readBoolean(0) }
}
fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
return when (head.type.toInt()) {
6 -> input.readString(input.readUByte().toInt(), charset = charset)
7 -> input.readString(input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, charset = charset)
else -> error("type mismatch: ${head.type}")
}
}
inline fun <reified J : JceStruct> readStringToJceStructMapOrNull(factory: JceStruct.Factory<J>, tag: Int): Map<String, J>? = skipToTagOrNull(tag) {
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
val size = readInt(0)
val map = HashMap<String, J>(size)
repeat(size) {
map[readString(0)] = readJceStruct(factory, 1)
}
return map
}
fun <K, V> readMapOrNull(defaultKey: K, defaultValue: V, tag: Int): Map<K, V>? = skipToTagOrNull(tag) {
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
val size = readInt(0)
val map = HashMap<K, V>(size)
repeat(size) {
map[readObject(defaultKey, 0)] = readObject(defaultValue, 1)
}
return map
}
inline fun <reified K, reified InnerK, reified InnerV> readNestedMapOrNull(tag: Int): Map<K, Map<InnerK, InnerV>>? = skipToTagOrNull(tag) {
check(it.type.toInt() == 8) { "type mismatch" }
val size = readInt(0)
val map = HashMap<K, Map<InnerK, InnerV>>(size)
repeat(size) {
map[readSimpleObject(0)] = readMap(1)
}
return map
}
inline fun <reified K, reified V> readMapOrNull(tag: Int): Map<K, V>? = skipToTagOrNull(tag) {
check(it.type.toInt() == 8) { "type mismatch" }
val size = readInt(0)
val map = HashMap<K, V>(size)
repeat(size) {
map[readSimpleObject(0)] = readSimpleObject(1)
}
return map
}
inline fun <reified J : JceStruct> readJceStructListOrNull(factory: JceStruct.Factory<J>, tag: Int): List<J>? = skipToTagOrNull(tag) { head ->
check(head.type.toInt() == 9) { "type mismatch" }
val size = readInt(0)
val list = ArrayList<J>(size)
repeat(size) {
list.add(readJceStruct(factory, 0))
}
return list
}
inline fun <reified T> readListOrNull(tag: Int): List<T>? = skipToTagOrNull(tag) { head ->
check(head.type.toInt() == 9) { "type mismatch" }
val size = readInt(0)
val list = ArrayList<T>(size)
repeat(size) {
list.add(readSimpleObject(0))
}
return list
}
fun <T> readListOrNull(defaultElement: T, tag: Int): List<T>? = skipToTagOrNull(tag) { head ->
check(head.type.toInt() == 9) { "type mismatch" }
val size = readInt(0)
val list = ArrayList<T>(size)
repeat(size) {
list.add(readObject(defaultElement, 0))
}
return list
}
fun <J : JceStruct> readJceStructOrNull(factory: JceStruct.Factory<J>, tag: Int): J? = skipToTagOrNull(tag) { head ->
check(head.type.toInt() == 10) { "type mismatch" }
return factory.newInstanceFrom(this).also { skipToStructEnd() }
}
@Suppress("UNCHECKED_CAST")
fun <T> readArrayOrNull(default: Array<T>, tag: Int): Array<T>? = skipToTagOrNull(tag) { head ->
val defaultElement = default[0]
check(head.type.toInt() == 9) { "type mismatch" }
return Array(readInt(0)) { readObject(defaultElement, tag) as Any } as Array<T>
}
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
fun <T> readObject(default: T, tag: Int): T = when (default) {
is Byte -> readByte(tag)
is Boolean -> readBoolean(tag)
is Short -> readShort(tag)
is Int -> readInt(tag)
is Long -> readLong(tag)
is Float -> readFloat(tag)
is Double -> readDouble(tag)
is String -> readString(tag)
is BooleanArray -> readBooleanArray(tag)
is ShortArray -> readShortArray(tag)
is IntArray -> readIntArray(tag)
is LongArray -> readLongArray(tag)
is ByteArray -> readByteArray(tag)
is FloatArray -> readByteArray(tag)
is DoubleArray -> readDoubleArrayOrNull(tag)
is List<*> -> {
readList(default, tag)
}
is Map<*, *> -> {
val entry = default.entries.first()
readMap(entry.key, entry.value, tag)
}
is Array<*> -> readSimpleArray(default, tag)
else -> error("unsupported type")
} as T
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
inline fun <reified T> readSimpleObject(tag: Int): T = when (T::class) {
Byte::class -> readByte(tag)
Boolean::class -> readBoolean(tag)
Short::class -> readShort(tag)
Int::class -> readInt(tag)
Long::class -> readLong(tag)
Float::class -> readFloat(tag)
Double::class -> readDouble(tag)
String::class -> readString(tag)
BooleanArray::class -> readBooleanArray(tag)
ShortArray::class -> readShortArray(tag)
IntArray::class -> readIntArray(tag)
LongArray::class -> readLongArray(tag)
ByteArray::class -> readByteArray(tag)
FloatArray::class -> readByteArray(tag)
DoubleArray::class -> readDoubleArrayOrNull(tag)
else -> error("Type is not supported: ${T::class.simpleName}")
} as T
private fun skipField() {
skipField(readHead().type)
}
private fun skipToStructEnd() {
var head: JceHead
do {
head = readHead()
skipField(head.type)
} while (head.type.toInt() != 11)
}
@UseExperimental(ExperimentalUnsignedTypes::class)
@PublishedApi
internal fun skipField(type: Byte) = when (type.toInt()) {
0 -> this.input.discardExact(1)
1 -> this.input.discardExact(2)
2 -> this.input.discardExact(4)
3 -> this.input.discardExact(8)
4 -> this.input.discardExact(4)
5 -> this.input.discardExact(8)
6 -> this.input.discardExact(this.input.readUByte().toInt())
7 -> this.input.discardExact(this.input.readInt())
8 -> { // map
repeat(this.readInt(0) * 2) {
skipField()
}
}
9 -> { // list
repeat(this.readInt(0)) {
skipField()
}
}
10 -> this.skipToStructEnd()
11, 12 -> {
}
13 -> {
val head = readHead()
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
this.input.discardExact(this.readInt(0))
}
else -> error("invalid type: $type")
}
}
private inline fun <R> JceInput.skipToTag(tag: Int, block: (JceHead) -> R): R {
return skipToTagOrNull(tag) { block(it) } ?: error("cannot find required tag $tag")
}
@PublishedApi
internal inline fun <R> JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
while (true) {
if (this.input.endOfInput) {
println("endOfInput")
return null
}
val head = peakHead()
if (head.tag > tag) {
return null
}
readHead()
if (head.tag == tag) {
return block(head)
}
this.skipField(head.type)
}
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.io
import kotlinx.io.charsets.Charset
import kotlinx.io.core.*
import kotlin.experimental.or
import kotlin.reflect.KClass
@PublishedApi
internal val CharsetGBK = Charset.forName("GBK")
@PublishedApi
internal val CharsetUTF8 = Charset.forName("UTF8")
inline fun buildJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit): ByteReadPacket {
return JceOutput(stringCharset).apply(block).build()
}
inline fun BytePacketBuilder.writeJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit) {
return this.writePacket(buildJcePacket(stringCharset, block))
}
fun jceStruct(tag: Int, struct: JceStruct): ByteArray{
return buildJcePacket {
writeJceStruct(struct, tag)
}.readBytes()
}
fun <K, V> jceMap(tag: Int, vararg entries: Pair<K, V>): ByteArray {
return buildJcePacket {
writeMap(mapOf(*entries), tag)
}.readBytes()
}
/**
*
* From: com.qq.taf.jce.JceOutputStream
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
@UseExperimental(ExperimentalIoApi::class)
class JceOutput(
private val stringCharset: Charset = CharsetGBK
) {
private val output: BytePacketBuilder = BytePacketBuilder()
fun build(): ByteReadPacket = output.build()
fun close() = output.close()
fun flush() = output.flush()
fun writeByte(v: Byte, tag: Int) {
if (v.toInt() == 0) {
writeHead(ZERO_TYPE, tag)
} else {
writeHead(BYTE, tag)
output.writeByte(v)
}
}
fun writeDouble(v: Double, tag: Int) {
writeHead(DOUBLE, tag)
output.writeDouble(v)
}
fun writeFloat(v: Float, tag: Int) {
writeHead(FLOAT, tag)
output.writeFloat(v)
}
fun writeFully(src: ByteArray, tag: Int) {
writeHead(SIMPLE_LIST, tag)
writeHead(BYTE, 0)
writeInt(src.size, 0)
output.writeFully(src)
}
fun writeFully(src: DoubleArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeDouble(it, 0)
}
}
fun writeFully(src: FloatArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeFloat(it, 0)
}
}
fun writeFully(src: IntArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeInt(it, 0)
}
}
fun writeFully(src: LongArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeLong(it, 0)
}
}
fun writeFully(src: ShortArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeShort(it, 0)
}
}
fun writeFully(src: BooleanArray, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeBoolean(it, 0)
}
}
fun <T> writeFully(src: Array<T>, tag: Int) {
writeHead(LIST, tag)
writeInt(src.size, 0)
src.forEach {
writeObject(it, 0)
}
}
fun writeInt(v: Int, tag: Int) {
if (v in Short.MIN_VALUE..Short.MAX_VALUE) {
writeShort(v.toShort(), tag)
} else {
writeHead(INT, tag)
output.writeInt(v)
}
}
fun writeLong(v: Long, tag: Int) {
if (v in Int.MIN_VALUE..Int.MAX_VALUE) {
writeInt(v.toInt(), tag)
} else {
writeHead(LONG, tag)
output.writeLong(v)
}
}
fun writeShort(v: Short, tag: Int) {
if (v in Byte.MIN_VALUE..Byte.MAX_VALUE) {
writeByte(v.toByte(), tag)
} else {
writeHead(SHORT, tag)
output.writeShort(v)
}
}
fun writeBoolean(v: Boolean, tag: Int) {
this.writeByte(if (v) 1 else 0, tag)
}
fun writeString(v: String, tag: Int) {
val array = v.toByteArray(stringCharset)
if (array.size > 255) {
writeHead(STRING4, tag)
output.writeInt(array.size)
output.writeFully(array)
} else {
writeHead(STRING1, tag)
output.writeByte(array.size.toByte())
output.writeFully(array)
}
}
fun <K, V> writeMap(map: Map<K, V>, tag: Int) {
writeHead(MAP, tag)
if (map.isEmpty()) {
writeInt(0, 0)
} else {
writeInt(map.size, 0)
map.forEach { (key, value) ->
writeObject(key, 0)
writeObject(value, 1)
}
}
}
fun writeCollection(collection: Collection<*>?, tag: Int) {
writeHead(LIST, tag)
if (collection == null || collection.isEmpty()) {
writeInt(0, 0)
} else {
writeInt(collection.size, 0)
collection.forEach {
writeObject(it, 0)
}
}
}
fun writeJceStruct(v: JceStruct, tag: Int) {
writeHead(STRUCT_BEGIN, tag)
v.writeTo(this)
writeHead(STRUCT_END, 0)
}
fun <T> writeObject(v: T, tag: Int) {
when (v) {
is Byte -> writeByte(v, tag)
is Short -> writeShort(v, tag)
is Int -> writeInt(v, tag)
is Long -> writeLong(v, tag)
is Float -> writeFloat(v, tag)
is Double -> writeDouble(v, tag)
is JceStruct -> writeJceStruct(v, tag)
is ByteArray -> writeFully(v, tag)
is Collection<*> -> writeCollection(v, tag)
is Boolean -> writeBoolean(v, tag)
is Map<*, *> -> writeMap(v, tag)
is IntArray -> writeFully(v, tag)
is ShortArray -> writeFully(v, tag)
is BooleanArray -> writeFully(v, tag)
is LongArray -> writeFully(v, tag)
is FloatArray -> writeFully(v, tag)
is DoubleArray -> writeFully(v, tag)
is Array<*> -> writeFully(v, tag)
is String -> writeString(v, tag)
//
// is ByteReadPacket -> ByteArrayPool.useInstance {
// v.readAvailable(it)
// writeFully(it, tag)
// }
else -> error("unsupported type: ${v.getClassName()}")
}
}
fun write(v: Int, tag: Int) = writeInt(v, tag)
fun write(v: Byte, tag: Int) = writeByte(v, tag)
fun write(v: Short, tag: Int) = writeShort(v, tag)
fun write(v: Long, tag: Int) = writeLong(v, tag)
fun write(v: Float, tag: Int) = writeFloat(v, tag)
fun write(v: Double, tag: Int) = writeDouble(v, tag)
fun write(v: String, tag: Int) = writeString(v, tag)
fun write(v: Boolean, tag: Int) = writeBoolean(v, tag)
fun write(v: Collection<*>, tag: Int) = writeCollection(v, tag)
fun write(v: Map<*, *>, tag: Int) = writeMap(v, tag)
fun write(v: ByteArray, tag: Int) = writeFully(v, tag)
fun write(v: IntArray, tag: Int) = writeFully(v, tag)
fun write(v: BooleanArray, tag: Int) = writeFully(v, tag)
fun write(v: LongArray, tag: Int) = writeFully(v, tag)
fun write(v: ShortArray, tag: Int) = writeFully(v, tag)
fun write(v: Array<*>, tag: Int) = writeFully(v, tag)
fun write(v: FloatArray, tag: Int) = writeFully(v, tag)
fun write(v: DoubleArray, tag: Int) = writeFully(v, tag)
@PublishedApi
internal companion object {
const val BYTE: Int = 0
const val DOUBLE: Int = 5
const val FLOAT: Int = 4
const val INT: Int = 2
const val JCE_MAX_STRING_LENGTH = 104857600
const val LIST: Int = 9
const val LONG: Int = 3
const val MAP: Int = 8
const val SHORT: Int = 1
const val SIMPLE_LIST: Int = 13
const val STRING1: Int = 6
const val STRING4: Int = 7
const val STRUCT_BEGIN: Int = 10
const val STRUCT_END: Int = 11
const val ZERO_TYPE: Int = 12
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
}
@PublishedApi
internal fun writeHead(type: Int, tag: Int) {
if (tag < 15) {
this.output.writeByte(((tag shl 4) or type).toByte())
return
}
if (tag < 256) {
this.output.writeByte((type.toByte() or 0xF0.toByte()))
this.output.writeByte(tag.toByte())
return
}
throw JceEncodeException("tag is too large: $tag")
}
}
class JceEncodeException(message: String) : RuntimeException(message)
\ No newline at end of file
package net.mamoe.mirai.qqandroid.io package net.mamoe.mirai.qqandroid.io
interface JceStruct { interface JceStruct
\ No newline at end of file
}
\ No newline at end of file
...@@ -4,108 +4,65 @@ import kotlinx.io.ByteArrayOutputStream ...@@ -4,108 +4,65 @@ import kotlinx.io.ByteArrayOutputStream
import kotlinx.io.ByteBuffer import kotlinx.io.ByteBuffer
import kotlinx.io.ByteOrder import kotlinx.io.ByteOrder
import kotlinx.io.charsets.Charset import kotlinx.io.charsets.Charset
import kotlinx.io.core.ExperimentalIoApi import kotlinx.io.core.*
import kotlinx.io.core.toByteArray
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.internal.* import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.EmptyModule import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule import kotlinx.serialization.modules.SerialModule
import net.mamoe.mirai.qqandroid.io.CharsetUTF8
import net.mamoe.mirai.qqandroid.io.JceEncodeException
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toIoBuffer
import kotlin.reflect.KClass import kotlin.reflect.KClass
fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: Charset): T { @PublishedApi
internal val CharsetGBK = Charset.forName("GBK")
@PublishedApi
internal val CharsetUTF8 = Charset.forName("UTF8")
fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
return Jce.byCharSet(c).load(deserializer, this) return Jce.byCharSet(c).load(deserializer, this)
} }
fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.UTF8): ByteArray = Jce.byCharSet(c).dump(serializer, this)
enum class JceCharset(val kotlinCharset: Charset) { enum class JceCharset(val kotlinCharset: Charset) {
GBK(Charset.forName("GBK")), GBK(Charset.forName("GBK")),
UTF8(Charset.forName("UTF8")) UTF8(Charset.forName("UTF8"))
} }
@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class SerialCharset(val charset: JceCharset)
internal object JceType {
const val BYTE: Int = 0
const val DOUBLE: Int = 5
const val FLOAT: Int = 4
const val INT: Int = 2
const val JCE_MAX_STRING_LENGTH = 104857600
const val LIST: Int = 9
const val LONG: Int = 3
const val MAP: Int = 8
const val SHORT: Int = 1
const val SIMPLE_LIST: Int = 13
const val STRING1: Int = 6
const val STRING4: Int = 7
const val STRUCT_BEGIN: Int = 10
const val STRUCT_END: Int = 11
const val ZERO_TYPE: Int = 12
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
}
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id
internal data class JceDesc(
val id: Int,
val charset: JceCharset
) {
companion object {
val STUB_FOR_PRIMITIVE_NUMBERS_GBK = JceDesc(0, JceCharset.GBK)
}
}
class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat { class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
private inner class ListWriter( private inner class ListWriter(
defaultStringCharset: JceCharset,
private val count: Int, private val count: Int,
private val tag: JceDesc, private val tag: Int,
private val parentEncoder: JceEncoder private val parentEncoder: JceEncoder
) : JceEncoder(defaultStringCharset, ByteArrayOutputStream()) { ) : JceEncoder(ByteArrayOutputStream()) {
override fun SerialDescriptor.getTag(index: Int): JceDesc { override fun SerialDescriptor.getTag(index: Int): Int {
return JceDesc(0, getCharset(index)) return 0
} }
override fun endEncode(desc: SerialDescriptor) { override fun endEncode(desc: SerialDescriptor) {
parentEncoder.writeHead(LIST, this.tag.id) parentEncoder.writeHead(LIST, this.tag)
parentEncoder.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count) parentEncoder.encodeTaggedInt(0, count)
parentEncoder.output.write(this.output.toByteArray()) parentEncoder.output.write(this.output.toByteArray())
} }
} }
private inner class JceStructWriter(
defaultStringCharset: JceCharset,
private val tag: JceDesc,
private val parentEncoder: JceEncoder,
private val stream: ByteArrayOutputStream = ByteArrayOutputStream()
) : JceEncoder(defaultStringCharset, stream) {
override fun endEncode(desc: SerialDescriptor) {
parentEncoder.writeHead(STRUCT_BEGIN, this.tag.id)
parentEncoder.output.write(stream.toByteArray())
parentEncoder.writeHead(STRUCT_END, 0)
}
}
private inner class JceMapWriter( private inner class JceMapWriter(
defaultStringCharset: JceCharset,
output: ByteArrayOutputStream output: ByteArrayOutputStream
) : JceEncoder(defaultStringCharset, output) { ) : JceEncoder(output) {
override fun SerialDescriptor.getTag(index: Int): JceDesc { override fun SerialDescriptor.getTag(index: Int): Int {
return if (index % 2 == 0) JceDesc(0, getCharset(index)) return if (index % 2 == 0) 0 else 1
else JceDesc(1, getCharset(index))
} }
/* /*
override fun endEncode(desc: SerialDescriptor) { override fun endEncode(desc: SerialDescriptor) {
parentEncoder.writeHead(MAP, this.tag.id) parentEncoder.writeHead(MAP, this.tag)
parentEncoder.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count) parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
println(this.output.toByteArray().toUHexString()) println(this.output.toByteArray().toUHexString())
parentEncoder.output.write(this.output.toByteArray()) parentEncoder.output.write(this.output.toByteArray())
}*/ }*/
...@@ -125,20 +82,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -125,20 +82,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
@Suppress("unused", "MemberVisibilityCanBePrivate") @Suppress("unused", "MemberVisibilityCanBePrivate")
@UseExperimental(ExperimentalIoApi::class) @UseExperimental(ExperimentalIoApi::class)
private open inner class JceEncoder( private open inner class JceEncoder(
/**
* 标注在 class 上的 charset
*/
private val defaultStringCharset: JceCharset,
internal val output: ByteArrayOutputStream internal val output: ByteArrayOutputStream
) : TaggedEncoder<JceDesc>() { ) : TaggedEncoder<Int>() {
override val context get() = this@Jce.context override val context get() = this@Jce.context
protected fun SerialDescriptor.getCharset(index: Int): JceCharset { override fun SerialDescriptor.getTag(index: Int): Int {
return findAnnotation<SerialCharset>(index)?.charset ?: defaultStringCharset return getSerialId(this, index) ?: error("cannot find @SerialId")
}
override fun SerialDescriptor.getTag(index: Int): JceDesc {
return JceDesc(getSerialId(this, index) ?: error("cannot find @SerialId"), getCharset(index))
} }
/** /**
...@@ -160,38 +109,40 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -160,38 +109,40 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>) val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
this.writeHead(MAP, currentTag.id) this.writeHead(MAP, currentTag)
this.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, entries.count()) this.encodeTaggedInt(0, entries.count())
HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(charset, this.output), entries) HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries)
} }
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray) ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
is PrimitiveArrayDescriptor -> { is PrimitiveArrayDescriptor -> {
if (value is ByteArray) { if (value is ByteArray) {
this.encodeTaggedByteArray( popTag(), value) this.encodeTaggedByteArray(popTag(), value)
} else{ } else {
serializer.serialize( serializer.serialize(
ListWriter(charset, when(value){ ListWriter(
is ShortArray -> value.size when (value) {
is IntArray -> value.size is ShortArray -> value.size
is LongArray -> value.size is IntArray -> value.size
is FloatArray -> value.size is LongArray -> value.size
is DoubleArray -> value.size is FloatArray -> value.size
is CharArray -> value.size is DoubleArray -> value.size
else -> error("unknown array type: ${value.getClassName()}") is CharArray -> value.size
}, popTag(), this), else -> error("unknown array type: ${value.getClassName()}")
}, popTag(), this
),
value value
) )
} }
} }
is ArrayClassDesc-> { is ArrayClassDesc -> {
serializer.serialize( serializer.serialize(
ListWriter(charset, (value as Array<*>).size, popTag(), this), ListWriter((value as Array<*>).size, popTag(), this),
value value
) )
} }
is ListLikeDescriptor -> { is ListLikeDescriptor -> {
serializer.serialize( serializer.serialize(
ListWriter(charset, (value as Collection<*>).size, popTag(), this), ListWriter((value as Collection<*>).size, popTag(), this),
value value
) )
} }
...@@ -200,7 +151,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -200,7 +151,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
if (currentTagOrNull == null) { if (currentTagOrNull == null) {
serializer.serialize(this, value) serializer.serialize(this, value)
} else { } else {
this.writeHead(STRUCT_BEGIN, currentTag.id) this.writeHead(STRUCT_BEGIN, currentTag)
serializer.serialize(this, value) serializer.serialize(this, value)
this.writeHead(STRUCT_END, 0) this.writeHead(STRUCT_END, 0)
} }
...@@ -208,92 +159,92 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -208,92 +159,92 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} }
} }
override fun encodeTaggedByte(tag: JceDesc, value: Byte) { override fun encodeTaggedByte(tag: Int, value: Byte) {
if (value.toInt() == 0) { if (value.toInt() == 0) {
writeHead(ZERO_TYPE, tag.id) writeHead(ZERO_TYPE, tag)
} else { } else {
writeHead(BYTE, tag.id) writeHead(BYTE, tag)
output.write(value.toInt()) output.write(value.toInt())
} }
} }
override fun encodeTaggedShort(tag: JceDesc, value: Short) { override fun encodeTaggedShort(tag: Int, value: Short) {
if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) { if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) {
encodeTaggedByte(tag, value.toByte()) encodeTaggedByte(tag, value.toByte())
} else { } else {
writeHead(SHORT, tag.id) writeHead(SHORT, tag)
output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array()) output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array())
} }
} }
override fun encodeTaggedInt(tag: JceDesc, value: Int) { override fun encodeTaggedInt(tag: Int, value: Int) {
if (value in Short.MIN_VALUE..Short.MAX_VALUE) { if (value in Short.MIN_VALUE..Short.MAX_VALUE) {
encodeTaggedShort(tag, value.toShort()) encodeTaggedShort(tag, value.toShort())
} else { } else {
writeHead(INT, tag.id) writeHead(INT, tag)
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array()) output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array())
} }
} }
override fun encodeTaggedFloat(tag: JceDesc, value: Float) { override fun encodeTaggedFloat(tag: Int, value: Float) {
writeHead(FLOAT, tag.id) writeHead(FLOAT, tag)
output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array()) output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array())
} }
override fun encodeTaggedDouble(tag: JceDesc, value: Double) { override fun encodeTaggedDouble(tag: Int, value: Double) {
writeHead(DOUBLE, tag.id) writeHead(DOUBLE, tag)
output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array()) output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array())
} }
override fun encodeTaggedLong(tag: JceDesc, value: Long) { override fun encodeTaggedLong(tag: Int, value: Long) {
if (value in Int.MIN_VALUE..Int.MAX_VALUE) { if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
encodeTaggedInt(tag, value.toInt()) encodeTaggedInt(tag, value.toInt())
} else { } else {
writeHead(LONG, tag.id) writeHead(LONG, tag)
output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array()) output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array())
} }
} }
override fun encodeTaggedBoolean(tag: JceDesc, value: Boolean) { override fun encodeTaggedBoolean(tag: Int, value: Boolean) {
encodeTaggedByte(tag, if (value) 1 else 0) encodeTaggedByte(tag, if (value) 1 else 0)
} }
override fun encodeTaggedChar(tag: JceDesc, value: Char) { override fun encodeTaggedChar(tag: Int, value: Char) {
encodeTaggedByte(tag, value.toByte()) encodeTaggedByte(tag, value.toByte())
} }
override fun encodeTaggedEnum(tag: JceDesc, enumDescription: SerialDescriptor, ordinal: Int) { override fun encodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor, ordinal: Int) {
TODO() TODO()
} }
override fun encodeTaggedNull(tag: JceDesc) { override fun encodeTaggedNull(tag: Int) {
} }
override fun encodeTaggedUnit(tag: JceDesc) { override fun encodeTaggedUnit(tag: Int) {
encodeTaggedNull(tag) encodeTaggedNull(tag)
} }
fun encodeTaggedByteArray(tag: JceDesc, bytes: ByteArray) { fun encodeTaggedByteArray(tag: Int, bytes: ByteArray) {
writeHead(SIMPLE_LIST, tag.id) writeHead(SIMPLE_LIST, tag)
writeHead(BYTE, 0) writeHead(BYTE, 0)
encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, bytes.size) encodeTaggedInt(0, bytes.size)
output.write(bytes) output.write(bytes)
} }
override fun encodeTaggedString(tag: JceDesc, value: String) { override fun encodeTaggedString(tag: Int, value: String) {
val array = value.toByteArray(defaultStringCharset.kotlinCharset) val array = value.toByteArray(charset.kotlinCharset)
if (array.size > 255) { if (array.size > 255) {
writeHead(STRING4, tag.id) writeHead(STRING4, tag)
output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array()) output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array())
output.write(array) output.write(array)
} else { } else {
writeHead(STRING1, tag.id) writeHead(STRING1, tag)
output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array()) output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array())
output.write(array) output.write(array)
} }
} }
override fun encodeTaggedValue(tag: JceDesc, value: Any) { override fun encodeTaggedValue(tag: Int, value: Any) {
when (value) { when (value) {
is Byte -> encodeTaggedByte(tag, value) is Byte -> encodeTaggedByte(tag, value)
is Short -> encodeTaggedShort(tag, value) is Short -> encodeTaggedShort(tag, value)
...@@ -319,16 +270,336 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -319,16 +270,336 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
this.output.write(tag) this.output.write(tag)
return return
} }
throw JceEncodeException("tag is too large: $tag") error("tag is too large: $tag")
} }
} }
private open inner class JceDecoder(
internal val input: JceInput
) : TaggedDecoder<Int>() {
override fun SerialDescriptor.getTag(index: Int): Int {
return getSerialId(this, index) ?: error("cannot find tag")
}
override fun decodeTaggedByte(tag: Int): Byte = input.readByte(tag)
override fun decodeTaggedShort(tag: Int): Short = input.readShort(tag)
override fun decodeTaggedInt(tag: Int): Int = input.readInt(tag)
override fun decodeTaggedLong(tag: Int): Long = input.readLong(tag)
override fun decodeTaggedFloat(tag: Int): Float = input.readFloat(tag)
override fun decodeTaggedDouble(tag: Int): Double = input.readDouble(tag)
override fun decodeTaggedChar(tag: Int): Char = input.readByte(tag).toChar()
override fun decodeTaggedString(tag: Int): String = input.readString(tag)
override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag)
@Suppress("UNCHECKED_CAST")
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when (deserializer.descriptor) {
is MapLikeDescriptor -> {
deserializer as MapLikeSerializer<Any?, Any?, T, *>
val tag = popTag()
this.input.skipToTagOrNull(tag) {
check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
val size = this.input.readInt(0)
val map = HashMap<Any?, Any?>(size)
repeat(size) {
pushTag(0)
val key = deserializer.keySerializer.deserialize(this)
pushTag(1)
val value = deserializer.valueSerializer.deserialize(this)
map[key] = value
}
return map as T
} ?: error("cannot find tag $tag")
}
ByteArraySerializer.descriptor -> input.readByteArray(popTag()) as T
ShortArraySerializer.descriptor -> input.readShortArray(popTag()) as T
IntArraySerializer.descriptor -> input.readIntArray(popTag()) as T
LongArraySerializer.descriptor -> input.readLongArray(popTag()) as T
FloatArraySerializer.descriptor -> input.readFloatArray(popTag()) as T
DoubleArraySerializer.descriptor -> input.readDoubleArray(popTag()) as T
CharArraySerializer.descriptor -> input.readByteArray(popTag()).map { it.toChar() }.toCharArray() as T
BooleanArraySerializer.descriptor -> input.readBooleanArray(popTag()) as T
is ArrayClassDesc -> {
deserializer as ArrayListSerializer<Any?>
val tag = popTag()
input.skipToTagOrNull(tag) { head ->
return Array(input.readInt(0)) {
input.readHead()
deserializer.deserialize(this)
} as T
} ?: error("cannot find tag $tag")
}
is ListLikeDescriptor -> {
deserializer as ListLikeSerializer<Any?, T, *>
val tag = currentTag
input.skipToTagOrNull(tag) { head ->
val size = input.readInt(0)
val list = ArrayList<Any?>(size)
repeat(size) {
//input.readHead()
this.pushTag( 0)
list.add(deserializer.typeParams[0].also { println(it.getClassName()) }.deserialize(this))
}
return list as T
} ?: error("cannot find tag $tag")
}
else -> {
if (input.peakHead().type.toInt() == STRUCT_BEGIN) {
input.readHead()
deserializer.deserialize(this).also { input.readHead() }
} else deserializer.deserialize(this)
}
}
override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int =
TODO()
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private inner class JceInput(
@PublishedApi
internal val input: IoBuffer
) : Closeable {
override fun close() {
input.close()
}
@PublishedApi
internal fun readHead(): JceHead = input.readHead()
@PublishedApi
internal fun peakHead(): JceHead = input.makeView().readHead()
private fun IoBuffer.readHead(): JceHead {
val var2 = readUByte()
val type = var2 and 15u
var tag = var2.toUInt() shr 4
if (tag == 15u)
tag = readUByte().toUInt()
return JceHead(tag = tag.toInt(), type = type.toByte())
}
fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag")
fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag")
fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag")
fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag")
fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag")
fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag")
fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag")
fun readString(tag: Int): String = readStringOrNull(tag) ?: error("cannot find tag $tag")
fun readByteArray(tag: Int): ByteArray = readByteArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readShortArray(tag: Int): ShortArray = readShortArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readLongArray(tag: Int): LongArray = readLongArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readFloatArray(tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readDoubleArray(tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readIntArray(tag: Int): IntArray = readIntArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readBooleanArray(tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: error("cannot find tag $tag")
fun readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
ShortArray(readInt(0)) { readShort(0) }
}
fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
DoubleArray(readInt(0)) { readDouble(0) }
}
fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
FloatArray(readInt(0)) { readFloat(0) }
}
fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
IntArray(readInt(0)) { readInt(0) }
}
fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
LongArray(readInt(0)) { readLong(0) }
}
fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
require(it.type.toInt() == 9) { "type mismatch" }
BooleanArray(readInt(0)) { readBoolean(0) }
}
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
when (it.type.toInt()) {
9 -> ByteArray(readInt(0)) { readByte(0) }
13 -> {
val head = readHead()
check(head.type.toInt() == 0) { "type mismatch" }
input.readBytes(readInt(0))
}
else -> error("type mismatch")
}
}
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
fun <T> readObject(default: T, tag: Int): T = when (default) {
is Byte -> readByte(tag)
is Boolean -> readBoolean(tag)
is Short -> readShort(tag)
is Int -> readInt(tag)
is Long -> readLong(tag)
is Float -> readFloat(tag)
is Double -> readDouble(tag)
is String -> readString(tag)
else -> error("unsupported type")
} as T
fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
return when (head.type.toInt()) {
6 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
7 -> input.readString(
input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } },
charset = charset.kotlinCharset
)
else -> error("type mismatch: ${head.type}")
}
}
fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toLong()
1 -> input.readShort().toLong()
2 -> input.readInt().toLong()
3 -> input.readLong()
else -> error("type mismatch: ${it.type}")
}
}
fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toShort()
1 -> input.readShort()
else -> error("type mismatch: ${it.type}")
}
}
fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toInt()
1 -> input.readShort().toInt()
2 -> input.readInt()
else -> error("type mismatch: ${it.type}")
}
}
fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte()
else -> error("type mismatch")
}
}
fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0f
4 -> input.readFloat()
else -> error("type mismatch: ${it.type}")
}
}
fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0.0
4 -> input.readFloat().toDouble()
5 -> input.readDouble()
else -> error("type mismatch: ${it.type}")
}
}
fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 }
private fun skipField() {
skipField(readHead().type)
}
private fun skipToStructEnd() {
var head: JceHead
do {
head = readHead()
skipField(head.type)
} while (head.type.toInt() != 11)
}
@UseExperimental(ExperimentalUnsignedTypes::class)
@PublishedApi
internal fun skipField(type: Byte) = when (type.toInt()) {
0 -> this.input.discardExact(1)
1 -> this.input.discardExact(2)
2 -> this.input.discardExact(4)
3 -> this.input.discardExact(8)
4 -> this.input.discardExact(4)
5 -> this.input.discardExact(8)
6 -> this.input.discardExact(this.input.readUByte().toInt())
7 -> this.input.discardExact(this.input.readInt())
8 -> { // map
repeat(this.readInt(0) * 2) {
skipField()
}
}
9 -> { // list
repeat(this.readInt(0)) {
skipField()
}
}
10 -> this.skipToStructEnd()
11, 12 -> {
}
13 -> {
val head = readHead()
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
this.input.discardExact(this.readInt(0))
}
else -> error("invalid type: $type")
}
internal inline fun <R> skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
while (true) {
if (this.input.endOfInput) {
println("endOfInput")
return null
}
val head = peakHead()
if (head.tag > tag) {
return null
}
readHead()
if (head.tag == tag) {
return block(head)
}
this.skipField(head.type)
}
}
}
companion object { companion object {
val UTF8 = Jce(JceCharset.UTF8) val UTF8 = Jce(JceCharset.UTF8)
val GBK = Jce(JceCharset.GBK) val GBK = Jce(JceCharset.GBK)
public fun byCharSet(c: Charset): Jce { fun byCharSet(c: JceCharset): Jce {
return if (c === CharsetUTF8) { return if (c === JceCharset.UTF8) {
UTF8 UTF8
} else { } else {
GBK GBK
...@@ -352,25 +623,31 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo ...@@ -352,25 +623,31 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
internal const val ZERO_TYPE: Int = 12 internal const val ZERO_TYPE: Int = 12
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
internal const val VARINT = 0
internal const val i64 = 1
internal const val SIZE_DELIMITED = 2
internal const val i32 = 5
} }
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray { override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
val encoder = ByteArrayOutputStream() val encoder = ByteArrayOutputStream()
val dumper = JceEncoder(encoder)
val dumper = JceEncoder(charset, encoder)
dumper.encode(serializer, obj) dumper.encode(serializer, obj)
return encoder.toByteArray() return encoder.toByteArray()
} }
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T { override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
TODO() return bytes.toIoBuffer().withUse {
val decoder = JceDecoder(JceInput(this))
decoder.decode(deserializer)
}
} }
}
override fun <T> @UseExperimental(ExperimentalUnsignedTypes::class)
inline class JceHead(private val value: Long) {
constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
} val tag: Int get() = (value ushr 32).toInt()
val type: Byte get() = value.toUInt().toByte()
override fun toString(): String {
return "JceHead(tag=$tag, type=$type)"
}
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.jce package net.mamoe.mirai.qqandroid.network.protocol.jce
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
private val EMPTY_MAP = mapOf<String, String>() private val EMPTY_MAP = mapOf<String, String>()
private val EMPTY_SBUFFER_MAP = mapOf<Int, ByteArray>() private val EMPTY_SBUFFER_MAP = mapOf<Int, ByteArray>()
...@@ -15,8 +18,23 @@ class RequestPacket( ...@@ -15,8 +18,23 @@ class RequestPacket(
@SerialId(4) val iRequestId: Int = 0, @SerialId(4) val iRequestId: Int = 0,
@SerialId(5) val sServantName: String = "", @SerialId(5) val sServantName: String = "",
@SerialId(6) val sFuncName: String = "", @SerialId(6) val sFuncName: String = "",
@SerialId(7) val sBuffer: Map<Int, ByteArray> = EMPTY_SBUFFER_MAP, @SerialId(7) val sBuffer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val iTimeout: Int = 0, @SerialId(8) val iTimeout: Int = 0,
@SerialId(9) val context: Map<String, String> = EMPTY_MAP, @SerialId(9) val context: Map<String, String> = EMPTY_MAP,
@SerialId(10) val status: Map<String, String> = EMPTY_MAP @SerialId(10) val status: Map<String, String> = EMPTY_MAP
) : JceStruct
@Serializable
class RequestDataVersion3(
@SerialId(0) val map: Map<String, ByteArray>
) : JceStruct
@Serializable
class RequestDataVersion2(
@SerialId(0) val map: Map<String, Map<String, ByteArray>>
) : JceStruct
@Serializable
class RequestDataStructSvcReqRegister(
@SerialId(0) val struct: SvcReqRegister
) : JceStruct ) : JceStruct
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.jce package net.mamoe.mirai.qqandroid.network.protocol.jce
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
@Serializable @Serializable
class SvcReqRegister( class SvcReqRegister(
@SerialId(6) var bIsOnline: Byte = 0, @SerialId(0) val lUin: Long = 0L,
@SerialId(34) var bIsSetStatus: Byte = 0, @SerialId(1) val lBid: Long = 0L,
@SerialId(7) var bIsShowOnline: Byte = 0, @SerialId(2) val cConnType: Byte = 0,
@SerialId(8) var bKikPC: Byte = 0, @SerialId(3) val sOther: String = "",
@SerialId(9) var bKikWeak: Byte = 0, @SerialId(4) val iStatus: Int = 11,
@SerialId(5) var bOnlinePush: Byte = 0, @SerialId(5) val bOnlinePush: Byte = 0,
@SerialId(22) var bOpenPush: Byte = 1, @SerialId(6) val bIsOnline: Byte = 0,
@SerialId(14) var bRegType: Byte = 0, @SerialId(7) val bIsShowOnline: Byte = 0,
@SerialId(36) var bSetMute: Byte = 0, @SerialId(8) val bKikPC: Byte = 0,
@SerialId(18) var bSlientPush: Byte = 0, @SerialId(9) val bKikWeak: Byte = 0,
@SerialId(33) var bytes_0x769_reqbody: ByteArray? = null, @SerialId(10) val timeStamp: Long = 0L,
@SerialId(2) var cConnType: Byte = 0, @SerialId(11) val iOSVersion: Long = 0L,
@SerialId(12) var cNetType: Byte = 0, @SerialId(12) val cNetType: Byte = 0,
@SerialId(23) var iLargeSeq: Long = 0L, @SerialId(13) val sBuildVer: String? = "",
@SerialId(24) var iLastWatchStartTime: Long = 0L, @SerialId(14) val bRegType: Byte = 0,
@SerialId(17) var iLocaleID: Int = 2052, @SerialId(15) val vecDevParam: ByteArray? = null,
@SerialId(11) var iOSVersion: Long = 0L, @SerialId(16) val vecGuid: ByteArray? = null,
@SerialId(4) var iStatus: Int = 11, @SerialId(17) val iLocaleID: Int = 2052,
@SerialId(1) var lBid: Long = 0L, @SerialId(18) val bSlientPush: Byte = 0,
@SerialId(29) var lCpId: Long = 0L, @SerialId(19) val strDevName: String? = null,
@SerialId(0) var lUin: Long = 0L, @SerialId(20) val strDevType: String? = null,
@SerialId(13) var sBuildVer: String? = "", @SerialId(21) val strOSVer: String? = null,
@SerialId(28) var sChannelNo: String? = "", @SerialId(22) val bOpenPush: Byte = 1,
@SerialId(3) var sOther: String = "", @SerialId(23) val iLargeSeq: Long = 0L,
@SerialId(19) var strDevName: String? = null, @SerialId(24) val iLastWatchStartTime: Long = 0L,
@SerialId(20) var strDevType: String? = null, @SerialId(26) val uOldSSOIp: Long = 0L,
@SerialId(32) var strIOSIdfa: String? = "", @SerialId(27) val uNewSSOIp: Long = 0L,
@SerialId(21) var strOSVer: String? = null, @SerialId(28) val sChannelNo: String? = "",
@SerialId(30) var strVendorName: String? = null, @SerialId(29) val lCpId: Long = 0L,
@SerialId(31) var strVendorOSName: String? = null, @SerialId(30) val strVendorName: String? = null,
@SerialId(10) var timeStamp: Long = 0L, @SerialId(31) val strVendorOSName: String? = null,
@SerialId(27) var uNewSSOIp: Long = 0L, @SerialId(32) val strIOSIdfa: String? = "",
@SerialId(26) var uOldSSOIp: Long = 0L, @SerialId(33) val bytes_0x769_reqbody: ByteArray? = null,
@SerialId(15) var vecDevParam: ByteArray? = null, @SerialId(34) val bIsSetStatus: Byte = 0,
@SerialId(16) var vecGuid: ByteArray? = null, @SerialId(35) val vecServerBuf: ByteArray? = null,
@SerialId(35) var vecServerBuf: ByteArray? = null @SerialId(36) val bSetMute: Byte = 0
// @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型 // @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型
) : JceStruct ) : JceStruct
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.protocol.jce
import kotlinx.io.core.BytePacketBuilder
import net.mamoe.mirai.qqandroid.io.JceOutput
import net.mamoe.mirai.qqandroid.io.buildJcePacket
import net.mamoe.mirai.qqandroid.io.writeJcePacket
inline fun BytePacketBuilder.writeUniRequestPacket(requestPacket: RequestPacket.() -> Unit) {
writeJcePacket {
RequestPacket().apply(requestPacket).writeTo(this)
}
}
\ No newline at end of file
...@@ -4,7 +4,7 @@ import kotlinx.io.core.* ...@@ -4,7 +4,7 @@ import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.JceInput import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
...@@ -248,7 +248,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf( ...@@ -248,7 +248,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
} }
private suspend fun ByteReadPacket.parseUniResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) { private suspend fun ByteReadPacket.parseUniResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) {
val uni = RequestPacket.newInstanceFrom(JceInput(readIoBuffer(readInt() - 4))) val uni = readBytes(readInt() - 4).loadAs(RequestPacket.serializer())
PacketLogger.verbose(uni.toString()) PacketLogger.verbose(uni.toString())
consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId) consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId)
} }
......
...@@ -4,6 +4,9 @@ import kotlinx.io.core.ByteReadPacket ...@@ -4,6 +4,9 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.Jce import net.mamoe.mirai.qqandroid.io.serialization.Jce
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
...@@ -19,7 +22,7 @@ class MessageSvc { ...@@ -19,7 +22,7 @@ class MessageSvc {
) )
val messageNotification = Jce.UTF8.load( val messageNotification = Jce.UTF8.load(
RequestPushNotify.serializer(), RequestPushNotify.serializer(),
req.sBuffer[0]!! req.sBuffer.loadAs(RequestDataVersion2.serializer()).map.entries.first().value.entries.first().value
) )
println(messageNotification.contentToString()) println(messageNotification.contentToString())
TODO() TODO()
......
...@@ -219,7 +219,11 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt ...@@ -219,7 +219,11 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
sealed class LoginPacketResponse : Packet { sealed class LoginPacketResponse : Packet {
object Success : LoginPacketResponse() object Success : LoginPacketResponse(){
override fun toString(): String {
return "LoginPacketResponse.Success"
}
}
data class Error( data class Error(
val title: String, val title: String,
val message: String, val message: String,
......
package net.mamoe.mirai.qqandroid.network.protocol.packet.login package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.io.jceMap import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister
import net.mamoe.mirai.qqandroid.io.jceStruct import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.writeUniRequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769 import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.localIpAddress import net.mamoe.mirai.utils.localIpAddress
...@@ -55,72 +56,73 @@ class StatSvc { ...@@ -55,72 +56,73 @@ class StatSvc {
client, subAppId = subAppId, commandName = commandName, client, subAppId = subAppId, commandName = commandName,
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
) { ) {
writeUniRequestPacket { writeFully(
sServantName = "PushService" RequestPacket(
sFuncName = "SvcReqRegister" sServantName = "PushService",
sBuffer = jceMap( sFuncName = "SvcReqRegister",
0, sBuffer = RequestDataVersion3(
"SvcReqRegister" to jceStruct( mapOf(
0, "SvcReqRegister" to RequestDataStructSvcReqRegister(
SvcReqRegister().apply { SvcReqRegister(
cConnType = 0 cConnType = 0,
lBid = 1 or 2 or 4 lBid = 1 or 2 or 4,
lUin = client.uin lUin = client.uin,
iStatus = client.onlineStatus.id iStatus = client.onlineStatus.id,
bKikPC = 0 // 是否把 PC 踢下线 bKikPC = 0, // 是否把 PC 踢下线
bKikWeak = 0 bKikWeak = 0,
timeStamp = 0 timeStamp = 0,
// timeStamp = currentTimeSeconds // millis or seconds?? // timeStamp = currentTimeSeconds // millis or seconds??
iLargeSeq = 1551 // ? iLargeSeq = 1551, // ?
bOpenPush = 1 bOpenPush = 1,
iLocaleID = 2052 iLocaleID = 2052,
bRegType = bRegType =
(if (regPushReason == RegPushReason.appRegister || (if (regPushReason == RegPushReason.appRegister ||
regPushReason == RegPushReason.fillRegProxy || regPushReason == RegPushReason.fillRegProxy ||
regPushReason == RegPushReason.createDefaultRegInfo || regPushReason == RegPushReason.createDefaultRegInfo ||
regPushReason == RegPushReason.setOnlineStatus regPushReason == RegPushReason.setOnlineStatus
) 0 else 1).toByte() ) 0 else 1).toByte(),
bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0 bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0,
iOSVersion = client.device.version.sdk.toLong() iOSVersion = client.device.version.sdk.toLong(),
cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0 cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0,
vecGuid = client.device.guid vecGuid = client.device.guid,
strDevName = client.device.model.encodeToString() strDevName = client.device.model.encodeToString(),
strDevType = client.device.model.encodeToString() strDevType = client.device.model.encodeToString(),
strOSVer = client.device.version.release.encodeToString() strOSVer = client.device.version.release.encodeToString(),
uOldSSOIp = 0 uOldSSOIp = 0,
uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String -> uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
acc or ((s.toLong() shl (index * 16))) acc or ((s.toLong() shl (index * 16)))
} },
strVendorName = "MIUI" strVendorName = "MIUI",
strVendorOSName = "?ONEPLUS A5000_23_17" strVendorOSName = "?ONEPLUS A5000_23_17",
// register 时还需要 // register 时还需要
/* /*
var44.uNewSSOIp = field_127445; var44.uNewSSOIp = field_127445;
var44.uOldSSOIp = field_127444; var44.uOldSSOIp = field_127444;
var44.strVendorName = ROMUtil.getRomName(); var44.strVendorName = ROMUtil.getRomName();
var44.strVendorOSName = ROMUtil.getRomVersion(20); var44.strVendorOSName = ROMUtil.getRomVersion(20);
*/ */
bytes_0x769_reqbody = ProtoBuf.dump( bytes_0x769_reqbody = ProtoBuf.dump(
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody( Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
rpt_config_list = listOf( rpt_config_list = listOf(
Oidb0x769.ConfigSeq( Oidb0x769.ConfigSeq(
type = 46, type = 46,
version = 0 version = 0
), ),
Oidb0x769.ConfigSeq( Oidb0x769.ConfigSeq(
type = 283, type = 283,
version = 0 version = 0
)
)
) )
) ),
bSetMute = 0
) )
) ).toByteArray(RequestDataStructSvcReqRegister.serializer())
bSetMute = 0 )
} ).toByteArray(RequestDataVersion3.serializer())
) ).toByteArray(RequestPacket.serializer())
) )
}
this.writePacket(this.build().debugPrint("sso body"))
} }
} }
......
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.core.readBytes
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.CharsetUTF8
import net.mamoe.mirai.qqandroid.io.JceOutput
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.buildJcePacket import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
class JceEncoderTest { class JceDecoderTest {
@Serializable @Serializable
class TestSimpleJceStruct( class TestSimpleJceStruct(
...@@ -23,51 +18,16 @@ class JceEncoderTest { ...@@ -23,51 +18,16 @@ class JceEncoderTest {
@SerialId(4) val long: Long = 123, @SerialId(4) val long: Long = 123,
@SerialId(5) val float: Float = 123f, @SerialId(5) val float: Float = 123f,
@SerialId(6) val double: Double = 123.0 @SerialId(6) val double: Double = 123.0
) : JceStruct() { ) : JceStruct
override fun writeTo(builder: JceOutput) = builder.run {
writeString("123", 0)
writeByte(123, 1)
writeShort(123, 2)
writeInt(123, 3)
writeLong(123, 4)
writeFloat(123f, 5)
writeDouble(123.0, 6)
}
}
@Test @Test
fun testEncoder() { fun testEncoder() {
assertEquals( println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexJceStruct.serializer()).contentToString())
buildJcePacket {
writeString("123", 0)
writeByte(123, 1)
writeShort(123, 2)
writeInt(123, 3)
writeLong(123, 4)
writeFloat(123f, 5)
writeDouble(123.0, 6)
}.readBytes().toUHexString(),
Jce.GBK.dump(
TestSimpleJceStruct.serializer(),
TestSimpleJceStruct()
).toUHexString()
)
} }
@Test @Test
fun testEncoder2() { fun testEncoder2() {
assertEquals(
buildJcePacket(stringCharset = CharsetUTF8) {
writeFully(byteArrayOf(1, 2, 3), 7)
writeCollection(listOf(1, 2, 3), 8)
writeMap(mapOf("哈哈" to "嘿嘿"), 9)
writeJceStruct(TestSimpleJceStruct(), 10)
}.readBytes().toUHexString(),
Jce.UTF8.dump(
TestComplexJceStruct.serializer(),
TestComplexJceStruct()
).toUHexString()
)
} }
@Serializable @Serializable
...@@ -76,5 +36,5 @@ class JceEncoderTest { ...@@ -76,5 +36,5 @@ class JceEncoderTest {
@SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3), @SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3),
@SerialId(9) val map: Map<String, String> = mapOf("哈哈" to "嘿嘿"), @SerialId(9) val map: Map<String, String> = mapOf("哈哈" to "嘿嘿"),
@SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct() @SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct()
) ) : JceStruct
} }
\ No newline at end of file
...@@ -389,7 +389,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor ...@@ -389,7 +389,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor
close() close()
return return
} }
val code = configuration.loginSolver(bot, captchaCache!!) val code = configuration.loginSolver.onSolvePicCaptcha(bot, captchaCache!!)
this.captchaCache = null this.captchaCache = null
if (code == null || code.length != 4) { if (code == null || code.length != 4) {
......
package net.mamoe.mirai.utils
/**
* 直接抛出异常. 需自行处理验证码, 在 [BotConfiguration.captchaSolver] 中调整
*/
actual var DefaultCaptchaSolver: CaptchaSolver = {
error("No CaptchaSolver found. BotConfiguration.captchaSolver should be assigned manually")
}
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
/**
* 在各平台实现的默认的验证码处理器.
*/
actual var defaultLoginSolver: LoginSolver = object : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
error("should be implemented manually by you")
}
override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? {
error("should be implemented manually by you")
}
override suspend fun onGetPhoneNumber(): String {
error("should be implemented manually by you")
}
override suspend fun onGetSMSVerifyCode(): String {
error("should be implemented manually by you")
}
}
\ No newline at end of file
...@@ -28,7 +28,7 @@ import kotlin.coroutines.CoroutineContext ...@@ -28,7 +28,7 @@ import kotlin.coroutines.CoroutineContext
actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver() actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver()
class DefaultLoginSolver(): LoginSolver(){ class DefaultLoginSolver : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? { override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
loginSolverLock.withLock { loginSolverLock.withLock {
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() } val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
......
package jceTest
import io.ktor.util.InternalAPI
import jce.jce.JceInputStream
import jceTest.JceOutputTest.TestMiraiStruct
import jceTest.JceOutputTest.TestQQStruct
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.qqandroid.network.io.JceInput
import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.io.toIoBuffer
import org.junit.Test
private infix fun <T> T.shouldEqualTo(another: T) {
if (this is Array<*>) {
this.contentEquals(another as Array<*>)
} else
check(this.contentToString() == another.contentToString()) {
"""actual: ${this.contentToString()}
|required: ${another.contentToString()}
""".trimMargin()
}
}
@UseExperimental(InternalAPI::class)
private fun <R> ByteArray.qqJce(block: JceInputStream.() -> R): R {
return JceInputStream(this).run(block)
}
private fun <R> ByteArray.read(block: JceInput.() -> R): R {
return JceInput(this.toIoBuffer()).run(block)
}
private fun ByteReadPacket.check(block: ByteArray.() -> Unit) {
this.readBytes().apply(block)
}
internal class JceInputTest {
@Test
fun readByte() = buildJcePacket {
writeByte(1, 1)
}.check {
read {
readByte(1)
} shouldEqualTo qqJce {
read(0.toByte(), 1, true)
}
}
@Test
fun readDouble() = buildJcePacket {
writeDouble(1.0, 1)
}.check {
read {
readDouble(1)
} shouldEqualTo qqJce {
read(0.toDouble(), 1, true)
}
}
@Test
fun readFloat() = buildJcePacket {
writeFloat(1.0f, 1)
}.check {
read {
readFloat(1)
} shouldEqualTo qqJce {
read(0.toFloat(), 1, true)
}
}
@Test
fun readFully() = buildJcePacket {
writeFully(byteArrayOf(1, 2, 3), 1)
}.check {
read {
readByteArray(1)
} shouldEqualTo qqJce {
read(byteArrayOf(), 1, true)
}
}
@Test
fun testWriteFully() = buildJcePacket {
writeFully(shortArrayOf(1, 2, 3), 1)
}.check {
read {
readShortArray(1)
} shouldEqualTo qqJce {
read(shortArrayOf(), 1, true)
}
}
@Test
fun testWriteFully1() = buildJcePacket {
writeFully(intArrayOf(1, 2, 3), 1)
}.check {
read {
readIntArray(1)
} shouldEqualTo qqJce {
read(intArrayOf(), 1, true)
}
}
@Test
fun testWriteFully2() = buildJcePacket {
writeFully(longArrayOf(1, 2, 3), 1)
}.check {
read {
readLongArray(1)
} shouldEqualTo qqJce {
read(longArrayOf(), 1, true)
}
}
@Test
fun testWriteFully3() = buildJcePacket {
writeFully(booleanArrayOf(true, false, true), 1)
}.check {
read {
readBooleanArray(1)
} shouldEqualTo qqJce {
read(booleanArrayOf(), 1, true)
}
}
@Test
fun testWriteFully4() = buildJcePacket {
writeFully(floatArrayOf(1f, 2f, 3f), 1)
}.check {
read {
readFloatArray(1)
} shouldEqualTo qqJce {
read(floatArrayOf(), 1, true)
}
}
@Test
fun testWriteFully5() = buildJcePacket {
writeFully(doubleArrayOf(1.0, 2.0, 3.0), 1)
}.check {
read {
readDoubleArray(1)
} shouldEqualTo qqJce {
read(doubleArrayOf(), 1, true)
}
}
@Test
fun testWriteFully6() = buildJcePacket {
writeFully(arrayOf("sss", "哈哈"), 1)
}.check {
read {
readSimpleArray("", 1)
} shouldEqualTo qqJce {
read(arrayOf(""), 1, true)
}
}
@Test
fun testWriteFully7() = buildJcePacket {
writeFully(arrayOf("sss", "哈哈"), 1)
}.check {
read {
readArrayOrNull("", 1)!!
} shouldEqualTo qqJce {
read(arrayOf(""), 1, true)
}
}
@Test
fun testWriteFully8() = buildJcePacket {
writeFully(arrayOf(TestMiraiStruct("Haha")), 1)
}.check {
read {
readJceStructArrayOrNull(TestMiraiStruct, 1)!!
} shouldEqualTo qqJce {
read(arrayOf(TestQQStruct("stub")), 1, true)
}
}
@Test
fun readInt() = buildJcePacket {
writeInt(1, 2)
}.check {
read {
readInt(2)
} shouldEqualTo qqJce {
read(0, 2, true)
}
}
@Test
fun readLong() = buildJcePacket {
writeLong(1, 2)
}.check {
read {
readLong(2)
} shouldEqualTo qqJce {
read(0L, 2, true)
}
}
@Test
fun readShort() = buildJcePacket {
writeShort(1, 2)
}.check {
read {
readShort(2)
} shouldEqualTo qqJce {
read(0.toShort(), 2, true)
}
}
@Test
fun readBoolean() = buildJcePacket {
writeBoolean(true, 2)
}.check {
read {
readBoolean(2)
} shouldEqualTo qqJce {
read(false, 2, true)
}
}
@Test
fun readString() = buildJcePacket {
writeString("嗨", 2)
}.check {
read {
readString(2)
} shouldEqualTo qqJce {
read("", 2, true)
}
}
@Test
fun readMap() = buildJcePacket {
writeMap(mapOf(123.0 to "Hello"), 3)
}.check {
read {
readMap(0.0, "", 3)
} shouldEqualTo qqJce {
read(mapOf(0.0 to ""), 3, true)
}
}
@Test
fun readCollection() = buildJcePacket {
writeCollection(listOf("1", "还"), 3)
}.check {
repeat(0) {
error("fuck kotlin")
}
read {
readList("", 3)
} shouldEqualTo qqJce {
read(listOf(""), 3, true)
}
}
@Test
fun readJceStruct() = buildJcePacket {
writeJceStruct(TestMiraiStruct("123"), 3)
}.check {
read {
readJceStruct(TestMiraiStruct, 3)
} shouldEqualTo qqJce {
read(TestQQStruct("stub"), 3, true)!!
}
}
@Test
fun readObject() = buildJcePacket {
writeObject(123, 3)
}.check {
read {
readObject(123, 3)
} shouldEqualTo qqJce {
read(123 as Any, 3, true)
}
}
}
\ No newline at end of file
package jceTest
import io.ktor.util.InternalAPI
import jce.jce.JceInputStream
import jce.jce.JceOutputStream
import jce.jce.JceStruct
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.qqandroid.network.io.JceInput
import net.mamoe.mirai.qqandroid.network.io.JceOutput
import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
import net.mamoe.mirai.utils.io.toUHexString
import org.junit.Test
private infix fun ByteReadPacket.shouldEqualTo(another: ByteArray) {
this.readBytes().let {
check(it.contentEquals(another)) {
"""actual: ${it.toUHexString()}
|required: ${another.toUHexString()}
""".trimMargin()
}
}
}
@UseExperimental(InternalAPI::class)
private fun qqJce(block: JceOutputStream.() -> Unit): ByteArray {
return JceOutputStream().apply(block).toByteArray()
}
internal class JceOutputTest {
@Test
fun writeByte() {
buildJcePacket {
writeByte(1, 1)
writeByte(-128, 2)
} shouldEqualTo qqJce {
write(1.toByte(), 1)
write((-128).toByte(), 2)
}
}
@Test
fun writeDouble() {
buildJcePacket {
writeDouble(1.0, 1)
writeDouble(-128.0, 2)
} shouldEqualTo qqJce {
write(1.toDouble(), 1)
write((-128).toDouble(), 2)
}
}
@Test
fun writeFloat() {
buildJcePacket {
writeFloat(1.0f, 1)
writeFloat(-128.0f, 2)
} shouldEqualTo qqJce {
write(1.toFloat(), 1)
write((-128).toFloat(), 2)
}
}
@Test
fun writeFully() {
buildJcePacket {
writeFully(byteArrayOf(1, 2), 1)
writeFully(byteArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(byteArrayOf(1, 2), 1)
write(byteArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully() {
buildJcePacket {
writeFully(intArrayOf(1, 2), 1)
writeFully(intArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(intArrayOf(1, 2), 1)
write(intArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully1() {
buildJcePacket {
writeFully(shortArrayOf(1, 2), 1)
writeFully(shortArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(shortArrayOf(1, 2), 1)
write(shortArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully2() {
buildJcePacket {
writeFully(booleanArrayOf(true, false), 1)
writeFully(booleanArrayOf(true, false), 2)
} shouldEqualTo qqJce {
write(booleanArrayOf(true, false), 1)
write(booleanArrayOf(true, false), 2)
}
}
@Test
fun testWriteFully3() {
buildJcePacket {
writeFully(longArrayOf(1, 2), 1)
writeFully(longArrayOf(1, 2), 2)
} shouldEqualTo qqJce {
write(longArrayOf(1, 2), 1)
write(longArrayOf(1, 2), 2)
}
}
@Test
fun testWriteFully4() {
buildJcePacket {
writeFully(floatArrayOf(1f, 2f), 1)
writeFully(floatArrayOf(1f, 2f), 2)
} shouldEqualTo qqJce {
write(floatArrayOf(1f, 2f), 1)
write(floatArrayOf(1f, 2f), 2)
}
}
@Test
fun testWriteFully5() {
buildJcePacket {
writeFully(doubleArrayOf(1.0, 2.0), 1)
writeFully(doubleArrayOf(1.0, 2.0), 2)
} shouldEqualTo qqJce {
write(doubleArrayOf(1.0, 2.0), 1)
write(doubleArrayOf(1.0, 2.0), 2)
}
}
@Test
fun testWriteFully6() {
buildJcePacket {
writeFully(arrayOf("123", "哈哈"), 1)
writeFully(arrayOf("123", "哈哈"), 2)
} shouldEqualTo qqJce {
write(arrayOf("123", "哈哈"), 1)
write(arrayOf("123", "哈哈"), 2)
}
}
@Test
fun writeInt() {
buildJcePacket {
writeInt(1, 1)
writeInt(-128, 2)
} shouldEqualTo qqJce {
write(1, 1)
write(-128, 2)
}
}
@Test
fun writeLong() {
buildJcePacket {
writeLong(1, 1)
writeLong(-128, 2)
} shouldEqualTo qqJce {
write(1L, 1)
write(-128L, 2)
}
}
@Test
fun writeShort() {
buildJcePacket {
writeShort(1, 1)
writeShort(-128, 2)
} shouldEqualTo qqJce {
write(1.toShort(), 1)
write((-128).toShort(), 2)
}
}
@Test
fun writeBoolean() {
buildJcePacket {
writeBoolean(true, 1)
writeBoolean(false, 2)
} shouldEqualTo qqJce {
write(true, 1)
write(false, 2)
}
}
@Test
fun writeString() {
buildJcePacket {
writeString("1", 1)
writeString("哈啊", 2)
} shouldEqualTo qqJce {
write("1", 1)
write("哈啊", 2)
}
}
@Test
fun writeMap() {
buildJcePacket {
writeMap(mapOf("" to ""), 1)
writeMap(mapOf("" to 123), 2)
writeMap(mapOf(123.0 to "Hello"), 3)
} shouldEqualTo qqJce {
write(mapOf("" to ""), 1)
write(mapOf("" to 123), 2)
write(mapOf(123.0 to "Hello"), 3)
}
}
@Test
fun writeCollection() {
buildJcePacket {
writeCollection(listOf("啊", "333", "1"), 1)
} shouldEqualTo qqJce {
write(listOf("啊", "333", "1"), 1)
}
}
data class TestMiraiStruct(
val message: String
) : net.mamoe.mirai.qqandroid.network.io.JceStruct() {
override fun writeTo(builder: JceOutput) {
builder.writeString(message, 0)
}
companion object : Factory<TestMiraiStruct> {
override fun newInstanceFrom(input: JceInput): TestMiraiStruct {
return TestMiraiStruct(input.readString(0))
}
}
}
class TestQQStruct(
private var message: String
) : JceStruct() {
constructor() : this("")
override fun readFrom(var1: JceInputStream) {
message = var1.read("", 0, true)
}
override fun writeTo(var1: JceOutputStream) {
var1.write(message, 0)
}
override fun toString(): String {
return "TestMiraiStruct(message=$message)"
}
}
@Test
fun writeJceStruct() {
buildJcePacket {
writeJceStruct(TestMiraiStruct("Hello"), 0)
writeJceStruct(TestMiraiStruct("嗨"), 1)
} shouldEqualTo qqJce {
write(TestQQStruct("Hello"), 0)
write(TestQQStruct("嗨"), 1)
}
}
@Test
fun writeObject() {
buildJcePacket {
writeObject(0.toByte(), 1)
writeObject(0.toShort(), 2)
writeObject(0, 3)
writeObject(0L, 4)
writeObject(0f, 5)
writeObject(0.0, 6)
writeObject("hello", 7)
writeObject(TestMiraiStruct("Hello"), 8)
} shouldEqualTo qqJce {
write(0.toByte(), 1)
write(0.toShort(), 2)
write(0, 3)
write(0L, 4)
write(0f, 5)
write(0.0, 6)
write("hello", 7)
write(TestQQStruct("Hello"), 8)
}
}
}
\ No newline at end of file
...@@ -10,11 +10,13 @@ import android.os.IBinder ...@@ -10,11 +10,13 @@ import android.os.IBinder
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.timpc.TIMPC import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.utils.LoginFailedException import net.mamoe.mirai.utils.LoginFailedException
import net.mamoe.mirai.utils.LoginSolver
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
class MiraiService : Service() { class MiraiService : Service() {
...@@ -42,12 +44,27 @@ class MiraiService : Service() { ...@@ -42,12 +44,27 @@ class MiraiService : Service() {
private fun login(qq: Long, pwd: String) { private fun login(qq: Long, pwd: String) {
GlobalScope.launch { GlobalScope.launch {
mBot = TIMPC.Bot(qq, pwd) { mBot = TIMPC.Bot(qq, pwd) {
captchaSolver = { loginSolver = object : LoginSolver() {
val bytes = it.readBytes() override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) val bytes = data.readBytes()
mCaptchaDeferred = CompletableDeferred() val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
mCallback?.get()?.onCaptcha(bitmap) mCaptchaDeferred = CompletableDeferred()
mCaptchaDeferred.await() mCallback?.get()?.onCaptcha(bitmap)
return mCaptchaDeferred.await()
}
override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? {
TODO("not implemented")
}
override suspend fun onGetPhoneNumber(): String {
TODO("not implemented")
}
override suspend fun onGetSMSVerifyCode(): String {
TODO("not implemented")
}
} }
}.apply { }.apply {
try { try {
......
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