diff --git a/mirai-core-utils/src/commonMain/kotlin/Annotations.kt b/mirai-core-utils/src/commonMain/kotlin/Annotations.kt index 13d2a5371..832ba3683 100644 --- a/mirai-core-utils/src/commonMain/kotlin/Annotations.kt +++ b/mirai-core-utils/src/commonMain/kotlin/Annotations.kt @@ -14,7 +14,7 @@ import kotlin.annotation.AnnotationTarget.* @RequiresOptIn("This can only be used in tests.", level = ERROR) -@Target(CLASS, FUNCTION, PROPERTY, CLASS, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER) +@Target(CLASS, FUNCTION, PROPERTY, CLASS, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER) public annotation class TestOnly /** diff --git a/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt b/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt index e4c9c616b..ddcf9ee43 100644 --- a/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt +++ b/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt @@ -11,6 +11,8 @@ package net.mamoe.mirai.utils +import io.ktor.utils.io.bits.* +import io.ktor.utils.io.core.* import kotlinx.cinterop.* import platform.zlib.* @@ -29,93 +31,440 @@ public actual fun ByteArray.sha1(offset: Int, length: Int): ByteArray = SHA1.cre return digest().bytes } -public actual fun ByteArray.gzip(offset: Int, length: Int): ByteArray { - val output = ByteArray(length * 5) - output.usePinned { out -> - usePinned { pin -> - memScoped { - val z = alloc() - z.avail_in = size.toUInt() - z.next_in = pin.addressOf(0).reinterpret() - z.avail_out = output.size.toUInt() - val initialOutAddress = out.addressOf(0) - z.next_out = initialOutAddress.reinterpret() - deflateInit2(z.ptr, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 or 16, 8, Z_DEFAULT_STRATEGY) - deflate(z.ptr, Z_FINISH) // TODO: 2022/5/28 buf - deflateEnd(z.ptr) +/** + * WARNING: DO NOT SET THIS BUFFER TOO SMALL, OR YOU WILL SEE COMPRESSION ERROR. + */ +@set:TestOnly +public var ZLIB_BUFFER_SIZE: Long = 8192 - val resultSize = z.next_out.toLong() - initialOutAddress.toLong() - return output.copyOf(resultSize.toInt()) - } - } - } +public actual fun ByteArray.gzip(offset: Int, length: Int): ByteArray { + return ZlibInput( + source = this.toReadPacket(offset, length), + zlibInit = { deflateInit2(it, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 or 16, 8, Z_DEFAULT_STRATEGY) }, + zlibProcess = { z, flush -> deflate(z, flush) }, + zlibHasPending = null, + zlibFlushMode = { if (it) Z_FINISH else Z_NO_FLUSH }, + zlibEnd = { deflateEnd(it) }, + ).readBytes() } -// TODO: 2022/5/28 optimize length - public actual fun ByteArray.ungzip(offset: Int, length: Int): ByteArray { - val output = ByteArray(length) - output.usePinned { out -> - usePinned { pin -> - memScoped { - val z = alloc() - z.avail_in = size.toUInt() - z.next_in = pin.addressOf(0).reinterpret() - z.avail_out = output.size.toUInt() - val initialOutAddress = out.addressOf(0) - z.next_out = initialOutAddress.reinterpret() - inflateInit2(z.ptr, 15 or 16) - inflate(z.ptr, Z_FINISH) - inflateEnd(z.ptr) - - val resultSize = z.next_out.toLong() - initialOutAddress.toLong() - return output.copyOf(resultSize.toInt()) - } - } - } + return ZlibInput( + source = this.toReadPacket(offset, length), + zlibInit = { inflateInit2(it, 15 or 16) }, + zlibProcess = { z, flush -> inflate(z, flush) }, + zlibHasPending = null, + zlibFlushMode = { if (it) Z_SYNC_FLUSH else Z_NO_FLUSH }, + zlibEnd = { inflateEnd(it) }, + ).readBytes() } public actual fun ByteArray.deflate(offset: Int, length: Int): ByteArray { - val output = ByteArray(length * 2) - output.usePinned { out -> - usePinned { pin -> + return ZlibInput( + source = this.toReadPacket(offset, length), + zlibInit = { deflateInit(it, Z_DEFAULT_COMPRESSION) }, + zlibProcess = { z, flush -> deflate(z, flush) }, + zlibHasPending = { z -> memScoped { - val z = alloc() - z.avail_in = size.toUInt() - z.next_in = pin.addressOf(0).reinterpret() - z.avail_out = output.size.toUInt() - val initialOutAddress = out.addressOf(0) - z.next_out = initialOutAddress.reinterpret() - deflateInit(z.ptr, Z_DEFAULT_COMPRESSION) - deflate(z.ptr, Z_FINISH) - deflateEnd(z.ptr) + val pendingBytes = cValue().ptr + val pendingBits = cValue().ptr - val resultSize = z.next_out.toLong() - initialOutAddress.toLong() - return output.copyOf(resultSize.toInt()) + debug { "deflatePending checking" } + if (deflatePending(z, pendingBytes, pendingBits) != Z_OK) { + debug { "deflatePending: failed" } + false + } else { + pendingBytes.pointed.value > 0u || pendingBits.pointed.value > 0 + } } - } - } + }, + zlibFlushMode = { if (it) Z_FINISH else Z_NO_FLUSH }, + zlibEnd = { deflateEnd(it) }, + ).readBytes() } public actual fun ByteArray.inflate(offset: Int, length: Int): ByteArray { - val output = ByteArray(length) - output.usePinned { out -> - usePinned { pin -> - memScoped { - val z = alloc() - z.avail_in = size.toUInt() - z.next_in = pin.addressOf(0).reinterpret() - z.avail_out = output.size.toUInt() - val initialOutAddress = out.addressOf(0) - z.next_out = initialOutAddress.reinterpret() - inflateInit(z.ptr) - inflate(z.ptr, Z_FINISH) - inflateEnd(z.ptr) +// val input = this +// val output = ByteArray((10 * length) + 12) +// this.usePinned { pin -> +// output.usePinned { outPin -> +// memScoped { +// val values = ulongArrayOf(output.size.toULong()) +// values.usePinned { sizes -> +// uncompress( +// outPin.addressOf(0).reinterpret(), +// sizes.addressOf(0).reinterpret(), +// pin.addressOf(offset).reinterpret(), +// length.convert(), +// ) +// } +// +// return output.copyOf(values[0].toInt()) +// +// } +// } +// return output +// } - val resultSize = z.next_out.toLong() - initialOutAddress.toLong() - return output.copyOf(resultSize.toInt()) + return ZlibInput( + source = this.toReadPacket(offset, length), + zlibInit = { inflateInit2(it, 15) }, + zlibProcess = { z, flush -> inflate(z, flush) }, + zlibHasPending = null, + zlibFlushMode = { if (it) Z_SYNC_FLUSH else Z_NO_FLUSH }, + zlibEnd = { inflateEnd(it) }, + ).readBytes() +} + +private const val debugging = false +private inline fun debug(string: () -> String) { + if (debugging) println(string()) +} + +private inline fun debug() { + if (debugging) println() +} + +private fun ZlibInput( + source: Input, + zlibInit: (z_streamp) -> Int, + zlibProcess: (z_streamp, flush: Int) -> Int, + zlibHasPending: ((z_streamp) -> Boolean)?, // null lambda means operation not defined + zlibFlushMode: (shouldFlushAll: Boolean) -> Int, + zlibEnd: (z_streamp) -> Int, +): Input { + val z = nativeHeap.alloc() + + val r = zlibInit(z.ptr) + if (r != 0) { + nativeHeap.free(z) + error("Failed to init zlib: $r (${getZlibError(r)})") + } + return object : Input { + @Deprecated( + "Not supported anymore. All operations are big endian by default. Use readXXXLittleEndian or readXXX then X.reverseByteOrder() instead.", + level = DeprecationLevel.ERROR + ) + override var byteOrder: ByteOrder + get() = throw UnsupportedOperationException() + set(_) { + throw UnsupportedOperationException() } + + private inline val BUFFER_TOTAL_SIZE get() = ZLIB_BUFFER_SIZE + private var bufferReadableSize = 0 + private val inputBuffer = nativeHeap.allocArray(BUFFER_TOTAL_SIZE) + private val buffer = nativeHeap.allocArray(BUFFER_TOTAL_SIZE) + private var bufferIndex = 0L + + private var closed: Boolean = false + + override val endOfInput: Boolean + get() = closed || !prepare() + + override fun close() { + debug { "closing" } + if (closed) return + this.closed = true + + zlibEnd(z.ptr) + nativeHeap.free(z) + nativeHeap.free(buffer) + nativeHeap.free(inputBuffer) } + + override fun discard(n: Long): Long { + if (closed) { + return 0 + } + val old = bufferIndex + if (old >= bufferReadableSize) { + if (prepare()) return discard(n) + return 0 + } + bufferIndex = (bufferIndex + n).coerceAtMost(ZLIB_BUFFER_SIZE) + return bufferIndex - old + } + + override fun peekTo(destination: Memory, destinationOffset: Long, offset: Long, min: Long, max: Long): Long { + debug() + debug { "peekTo" } + require(min <= max) { "min > max" } + if (!prepare()) return 0 + val readableLength = (bufferReadableSize - bufferIndex - offset).coerceAtLeast(0) + if (offset > readableLength) { + if (min == 0L) { + throw EOFException("offset($offset) > readableLength($readableLength)") + } + } + if (min > readableLength) return 0 + val len = readableLength.coerceAtMost(max).coerceAtMost(destination.size) + debug { "peekTo: read $len" } + buffer.copyTo(destination, bufferIndex + offset, len, destinationOffset) + + return len + } + + override fun readByte(): Byte { + if (!prepare()) { + throw EOFException("One more byte required") + } + return buffer[bufferIndex++] + } + + override fun tryPeek(): Int { + if (!prepare()) { + return -1 + } + return buffer[bufferIndex].toIntUnsigned() + } + + private fun prepare(): Boolean { + if (closed) { + return false + } + debug { "prepare: bufferIndex = $bufferIndex, bufferReadableSize = $bufferReadableSize" } + if (bufferIndex < bufferReadableSize) { + debug { "prepare returned, because " } + return true // has buf unused + } + + bufferIndex = 0 + debug { "prepare: previous value: z.avail_in=${z.avail_in}, z.avail_out=${z.avail_out}" } + + if (z.avail_in == 0u) { + if (z.avail_out == 0u) { + // Last time we used all the output, there is either something cached in Zlib, or no further source. + + debug { "both used" } + // bot input and output are used + val flush = updateAvailIn() ?: return false + copyOutputsFromZlib(flush) + } else { + // We did not use all the inputs, meaning least time we used all avail_in. + + debug { "both used2" } + + val flush = updateAvailIn() ?: return false + copyOutputsFromZlib(flush) + } + } else { + // Inputs not used up. + copyOutputsFromZlib(Z_NO_FLUSH) + } + + return true + } + + private fun copyOutputsFromZlib(flush: Int): Boolean { + z.avail_out = BUFFER_TOTAL_SIZE.toUInt() + z.next_out = buffer.reinterpret() + + // We still have input, no need to update. + debug { "Set z.avail_out=${z.avail_out}, z.next_out=buffer.reinterpret()" } + debug { "Calling zlib, flush = $flush" } + + val p = zlibProcess(z.ptr, flush) + when (p) { + Z_BUF_ERROR -> error("Zlib failed to process data. (Z_BUF_ERROR)") + Z_MEM_ERROR -> throw OutOfMemoryError("Insufficient native heap memory for Zlib. (Z_MEM_ERROR)") + Z_STREAM_ERROR -> error("Zlib failed to process data. (Z_STREAM_ERROR)") + Z_DATA_ERROR -> error("Zlib failed to process data. (Z_DATA_ERROR)") + Z_NEED_DICT -> error("Zlib failed to process data. (Z_NEED_DICT)") + else -> debug { "zlib: $p" } + } + bufferReadableSize = (BUFFER_TOTAL_SIZE.toUInt() - z.avail_out).toInt() + + debug { "Zlib produced bufferReadableSize=$bufferReadableSize bytes" } + debug { "Partial output: ${buffer.readBytes(bufferReadableSize).toUHexString()}" } + debug { "Now z.avail_in=${z.avail_in}, z.avail_out=${z.avail_out}" } + + if (p == Z_FINISH) { + debug { "Zlib returned Z_FINISH. Ignoring result check." } + return true + } + + if (p == Z_STREAM_END) { + debug { "Zlib returned Z_STREAM_END. Ignoring result check." } + return true + } + + if (bufferReadableSize == 0 && (z.avail_in == 0u && source.endOfInput)) { + if (zlibHasPending?.invoke(z.ptr) == true) { + // has pending. So the data must be incomplete. + error("Failed to process data, possibly bad data inputted.") +// if (z.avail_in == 0u && source.endOfInput) { +// // no any input. +// } else { +// // there's some input, so we can still read. +// } + } else { + // no pending, but we should expect Z_FINISH in this case. + error("Zlib read 0 byte, but it should not happen.") + } + // can't read + } + return true + } + + private fun updateAvailIn(): Int? { + val read = source.readAvailable(inputBuffer, 0, BUFFER_TOTAL_SIZE) + if (read == 0L) { + debug { "updateAvailIn: endOfInput, closing" } + close() // automatically close + return null // no more source available + } + z.avail_in = read.toUInt() + val flush = zlibFlushMode(read < BUFFER_TOTAL_SIZE || source.endOfInput) + println(inputBuffer.readBytes(read.toInt()).toUHexString()) + z.next_in = inputBuffer.reinterpret() + debug { "Updated availIn: z.avail_in=${z.avail_in}, z.next_in = inputBuffer.reinterpret()" } + return flush + } + } + + // https://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/zlib-inflate-1.html + +// val input = source +// input.usePinned { pin -> +// buildPacket { +// memScoped { +// val outBuffer = ByteArray(ZLIB_BUFFER_SIZE) +// check(outBuffer.isNotEmpty()) +// outBuffer.usePinned { out -> +// +// val z = alloc() +// z.avail_out = outBuffer.size.toUInt() +// val outBufferAddr = out.addressOf(0) +// z.next_out = outBufferAddr.reinterpret() +// +// checkZlibResp(zlibInit(z.ptr), "zlibInit") +// +// // On success, inflate() shall return +// // Z_OK +// // if decompression progress has been made, or +// // +// // Z_STREAM_END +// // if all of the input data has been decompressed and +// // there was sufficient space in the output buffer to store the uncompressed result. +// // +// // On error, inflate() shall return a value to indicate the error. +// // Z_BUF_ERROR +// // No progress is possible; either avail_in or avail_out was zero. +// // +// // Z_MEM_ERROR +// // Insufficient memory. +// // +// // Z_STREAM_ERROR +// // The state (as represented in stream) is inconsistent, or stream was NULL. +// // +// // Z_NEED_DICT +// // A preset dictionary is required. The adler field shall be set to the Adler-32 checksum of the dictionary chosen by the compressor. +// +// var flush: Boolean = false; +// do { +// input.readAvailable() +// z.avail_in = length.toUInt() +// z.next_in = pin.addressOf(offset).reinterpret() +// +// } while () +// +// while (true) { +// val r = zlibProcess(z.ptr) +// when (r) { +// Z_OK, Z_STREAM_END -> { +// val wroteSize = outBuffer.size - z.avail_out.toInt() +// debug(wroteSize) +// +// this.writeFully(outBuffer, 0, wroteSize) +// +// if (z.avail_out == 0u) { +// if (z.avail_in == 0u) { +// +// } else { +// z.avail_out = outBuffer.size.toUInt() +// z.next_out = outBufferAddr.reinterpret() +// continue +// } +// } else { +//// checkZlibResp(zlibEnd(z.ptr), "zlibEnd") +//// return build() +// +// } +// if (r == Z_STREAM_END) { +//// check(r == Z_STREAM_END) { "Zlib result must be Z_STREAM_END" } +// checkZlibResp(zlibEnd(z.ptr), "zlibEnd") +// return this.build() +// } +// +// } +// Z_BUF_ERROR -> error("Zlib failed to process data. (Z_BUF_ERROR)") +// Z_MEM_ERROR -> throw OutOfMemoryError("Insufficient native heap memory for Zlib. (Z_MEM_ERROR)") +// Z_STREAM_ERROR -> error("Zlib failed to process data. (Z_STREAM_ERROR)") +// Z_NEED_DICT -> error("Zlib failed to process data. (Z_NEED_DICT)") +// else -> error("Internal error: unexpected result from Zlib: $r") +// } +// +//// if (r == Z_STREAM_END && z.) { +//// checkZlibResp(zlibEnd(z.ptr), "zlibEnd") +//// return build() +//// } +// } +// +//// while (true) { +//// val r = zlibProcess(z.ptr) +//// when (r) { +//// Z_OK, Z_STREAM_END -> { +//// val wroteSize = outBuffer.size - z.avail_out.toInt() +//// debug(wroteSize) +//// +//// writeFully(outBuffer, 0, wroteSize) +//// +//// if (z.avail_out == 0u) { +//// if (z.avail_in == 0u) { +//// +//// } else { +//// z.avail_out = outBuffer.size.toUInt() +//// z.next_out = outBufferAddr.reinterpret() +//// continue +//// } +//// } else { +////// checkZlibResp(zlibEnd(z.ptr), "zlibEnd") +////// return build() +//// +//// } +//// if (r == Z_STREAM_END) { +////// check(r == Z_STREAM_END) { "Zlib result must be Z_STREAM_END" } +//// checkZlibResp(zlibEnd(z.ptr), "zlibEnd") +//// return build() +//// } +//// +//// } +//// Z_BUF_ERROR -> error("Zlib failed to process data. (Z_BUF_ERROR)") +//// Z_MEM_ERROR -> throw OutOfMemoryError("Insufficient native heap memory for Zlib. (Z_MEM_ERROR)") +//// Z_STREAM_ERROR -> error("Zlib failed to process data. (Z_STREAM_ERROR)") +//// Z_NEED_DICT -> error("Zlib failed to process data. (Z_NEED_DICT)") +//// else -> error("Internal error: unexpected result from Zlib: $r") +//// } +//// +////// if (r == Z_STREAM_END && z.) { +////// checkZlibResp(zlibEnd(z.ptr), "zlibEnd") +////// return build() +////// } +//// } +// +// } +// } +// } +// } +// error("Unreachable") // no contract was declared for memScoped and usePinned +} + +private fun getZlibError(it: Int): String { + return when (it) { + Z_DATA_ERROR -> "Z_DATA_ERROR" + Z_STREAM_ERROR -> "Z_STREAM_ERROR" + else -> "Unknown error $it" } } diff --git a/mirai-core-utils/src/nativeTest/kotlin/ByteArrayOpTest.kt b/mirai-core-utils/src/nativeTest/kotlin/ByteArrayOpTest.kt index 2b9a85d42..5c067133b 100644 --- a/mirai-core-utils/src/nativeTest/kotlin/ByteArrayOpTest.kt +++ b/mirai-core-utils/src/nativeTest/kotlin/ByteArrayOpTest.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ +@file:OptIn(TestOnly::class) + package net.mamoe.mirai.utils import io.ktor.utils.io.core.* @@ -65,11 +67,64 @@ class ByteArrayOpTest { val str = "qGnJ1RrFC9" println(str) val hash = str.toByteArray().deflate() - assertContentEquals( + assertContentEquals( // if we change "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes(), hash, message = hash.toUHexString() ) + + assertEquals(str, str.toByteArray().deflate().inflate().decodeToString()) + } + + private fun withZlibBufferSize(size: Long, block: () -> R): R { + val o = ZLIB_BUFFER_SIZE + ZLIB_BUFFER_SIZE = size + try { + return block() + } finally { + ZLIB_BUFFER_SIZE = o + } + } + + @Test + fun testDeflateBig() { + withZlibBufferSize(8192) { // use smaller buffer to check. + val str = sampleLongText.repeat(8) // 4000+ chars + println("Input size: ${str.length}") + println("Deflating") + println() + val bytes = str.toByteArray().deflate() + println() + println("Deflated, size = ${bytes.size}, content = ${bytes.toUHexString()}") + println() + println("Inflating") + println() + val inflated = bytes.inflate() + println() + println("Inflated, size = ${inflated.size}, content = ${inflated.toUHexString()}") + println() + assertEquals(str, inflated.decodeToString()) + } + } + + + @Test + fun testInflateBigDataFromJvm() { + withZlibBufferSize(8192) { // use smaller buffer to check. + val input = + "78 9C ED 92 31 8E 1B 31 0C 45 AF C2 2E CD C2 B0 8B 5D 20 65 4A 03 0E B0 57 E0 68 68 0F 61 89 12 44 CA B3 73 FB E5 C8 09 E2 23 A4 50 27 41 E4 E7 E7 D7 BB E4 4A 09 CE 45 5B 02 56 50 4E 25 6E 30 B7 94 36 30 FA 32 C8 57 B0 85 A0 54 16 63 B9 01 CA 0C B6 15 52 B2 7E 67 99 9B 5A DD 0E 70 79 91 5A 50 61 22 92 DE FB B7 E4 87 EB 9B F7 63 9D 5F 27 D0 83 AA 0F 96 40 BD FA F4 7E 3C EA 1B AC 8B 77 A3 40 93 BB E4 55 9E 06 BC D0 72 BE 03 C2 0D 63 A4 AD BB 73 33 DD 95 86 8A 69 8A 34 03 9B 97 41 C2 BB 3F 3C DF B5 50 E0 E4 8A 93 B7 1F E0 6C DD A1 B6 FA E0 87 37 48 F6 45 C5 17 BF FA 15 02 89 B5 CA E4 2E A6 66 80 51 73 77 16 09 8B 2F E3 D2 14 29 58 CD C2 E1 35 8B 37 F0 00 90 65 8F 85 54 5D 85 DD E5 E6 2B 84 05 E5 46 73 1F BC FA E0 92 4B 8B 58 59 77 B3 CF 90 4E 3F 3F 8E 0A 2B DB D2 AF D5 47 A0 D2 BE E0 85 AC FA D1 40 17 22 53 08 59 EC CF 94 D7 C8 0B AA E2 6D 37 BD 87 91 FC C5 45 F6 55 DC 41 97 9D 49 EF 96 0B 94 36 45 D6 65 EF D7 7C B5 15 BD 32 B2 67 F5 2B FA 3F C1 A7 8B FC F6 E8 AA 3B 0B B1 CD 7B 9D FF 90 72 16 ED 76 FE CD 3C 5C 06 3D 83 9E 41 CF A0 67 D0 33 E8 19 F4 0C 7A 06 3D 83 9E 41 CF A0 67 D0 33 E8 19 F4 FC 87 F4 7C 03 ED CB 93 EB" + .hexToBytes() + println() + val bytes = input.inflate() + println("Inflated, size = ${bytes.size}, content = ${bytes.toUHexString()}") + println() + val expected = sampleLongText.repeat(8) + val actual = bytes.decodeToString() + println(expected) + println(actual) + assertEquals(expected.length, actual.length) + assertEquals(expected, actual) + } } @Test @@ -84,16 +139,29 @@ class ByteArrayOpTest { ) } + @Test + fun testInflateRealLongData() { + val result = + "78 DA ED D9 6D 50 DB E6 1D 00 70 49 E6 45 D6 B5 19 73 9B 8E B0 2F 9A 3E AC 59 EF 8A 1F C9 06 D9 5E DC DD B1 DB 5D BB 6B EF F6 65 B7 DE F6 21 A3 AE 21 4E 8C 63 C0 64 D9 3E 51 F2 E6 40 78 33 64 25 6F 84 92 00 CE 08 85 00 0D 0A 25 90 B6 59 E9 BA 8C D2 A4 D7 5E 9B A4 C9 06 B6 43 5E 69 93 90 34 69 93 3D B2 1C 59 C6 12 71 81 72 C7 45 3E CE 77 92 2D F1 7F FE CF EF F9 DF 5F 7E 70 44 87 64 50 69 DE E3 57 EA 53 D2 EB 1B 0E B7 13 64 5F EB 58 39 B1 BC A2 F9 DE 69 F4 E5 F2 3B 1F 06 B4 7F 5E EA 46 48 F4 99 F2 35 F8 7D F8 FA 51 5A 5F EB BE 1B 58 3A 4A ED 46 89 E5 78 F8 20 ED EC 57 BE BE D4 F4 33 C9 24 B2 BC A5 0C 05 9F 1E 1A A9 4F 31 9D D9 E4 DD 50 0A 5F 18 87 22 3A 1C 47 D2 90 74 84 44 32 F2 08 44 F7 4A A6 F7 14 FB 63 E4 4F 7F FC 4D 46 06 7E AB 96 4C FB 84 BF 22 BD BB 31 38 A1 81 F7 40 C0 77 0D 9B 9B 52 F0 F0 FB 33 18 8E 00 C4 84 FC 76 19 8E EB 96 06 76 35 8D 73 FD C1 DD 5B 27 EA 77 04 BC ED 81 8E D7 C7 4A CB 38 CC 7F B7 E5 14 86 A7 92 E1 70 FC D8 ED 5A 92 DA 85 12 4F C7 87 37 F0 2C D8 D1 9D 78 74 CB F0 EF 60 74 F5 FC 15 E9 65 9F 85 56 85 83 1B BB F3 CD E7 04 1E 7E 17 83 CB C0 93 75 4F 05 BA 0E 05 1B 3A 2F 0C EE 1B 3F E2 0D B5 34 86 DA 1A 26 F7 36 54 71 D8 9D F3 5D 5D DA 68 74 F7 60 74 F2 C9 AB BF AD 01 57 0F 7F 9F E4 95 F9 C8 B4 CB FC 15 E9 97 A7 DA CE A4 CE 2A 79 BB BA 6A 7A 93 A2 E1 C1 5B 52 77 92 E5 C2 3B 7C 40 03 6E F4 09 E1 9D A8 90 09 EF BD 64 18 DF 60 32 1F E0 93 58 1F FE A4 70 CB 55 4F 9C 44 1B 93 F1 C7 D2 5A F0 F4 54 70 05 B7 3C 4E 68 5D 0E DB 9A 95 C5 9E 22 5A 87 48 0F 19 78 F8 14 81 97 38 5C C2 87 04 63 00 26 33 60 0C 46 56 72 9E 81 E7 19 36 0B B0 0C 0B 80 65 09 A1 F5 FC D5 6D 17 3E 40 81 E5 17 04 51 5C 92 97 E7 58 CF 9F D1 FD 34 B4 73 E3 44 47 7D B0 67 FB 44 C3 5B 81 EA 86 A0 B7 66 BC 7F 73 68 5B 7B C0 DF 60 79 9E 58 92 6B F3 38 D6 BA 56 3A 0A F2 57 96 14 39 75 D9 AB 3C 1E B7 45 AF F7 38 72 5D 85 25 B9 AE CC 7C 0F FC 28 D3 E6 D2 BB 4A 5E CD B7 DB F3 F2 EC 36 8F DE E1 B1 17 E8 59 BD CD E9 B0 BB 3C 99 F9 8E 3C CB 52 02 CF 75 7A 84 A0 B5 41 6F 0F FC 1F F0 DD F2 07 22 75 B5 5B 38 FB 22 7F E7 62 78 EB BF BD 92 B9 CE E1 CE 2C 2C CC B4 AD 2D D0 AF 63 F4 EE DC 7C 7B B1 70 FF 97 72 9D CE 5F AD FC CB 3A 2B F3 F3 DC 02 F7 2F 85 E0 5E 78 D5 0A C2 87 C2 7F 87 87 6C CE 07 D8 0A 18 99 9B CC 75 3A F2 5D 56 CA 06 E3 B0 17 51 CF 91 F0 B5 A2 B0 90 84 89 B2 52 D1 DC 51 A4 6D AD D3 4A D1 14 E9 2A B0 52 14 A9 17 BE 08 47 46 16 17 D9 AC D4 EC 06 4D 91 AB DD C2 B5 F3 35 2C 0A 8E C7 63 A5 C4 FC 51 7C A0 D1 F1 88 73 3E 7D 3C 1E 18 07 A0 22 A3 72 AD 2D 22 3D EB E1 6D 66 98 79 FE BB 2B F4 7C 06 9F FB 5D 55 E3 F1 6F 34 1C 76 7F 70 E3 AF FD 58 25 C4 7F 4D 16 FF D4 B7 18 B8 15 C1 7F 4C 0E 3F C7 E3 EF 89 E0 3F 28 C5 EF 13 F1 5F 9E 2D FE A5 12 FC 5A 13 9B 6D 30 18 0D 66 43 9C 7D 7D 8C FD 9F C1 0C 5C D8 C0 4D 6C F0 07 AA 6B 03 5B FA E1 F0 27 7C CD C1 3D 43 F0 7C 60 5F D7 C4 DE 1E CB 4F 08 22 B2 02 F8 0B B4 C1 8A 4A 3E F3 15 95 96 17 E3 96 86 65 66 25 C2 B7 05 25 B4 DE BE DE 5D 64 2F 2E 86 67 32 57 BB F3 E1 42 13 D7 81 75 2E 60 E8 9C 63 0A F0 13 53 9F 28 F9 99 06 33 77 F6 B4 10 8B 08 55 CC 7A 98 E5 83 81 88 B3 AC A8 3D 86 FA CC 13 2D 05 FF D5 40 F3 B5 54 0E 3B 3D D4 5E A7 F1 63 55 50 FC B0 AC F8 52 9F 06 DC 8D 88 9F 2C 97 11 5F C3 8B AF 88 88 DF 28 15 7F 31 69 76 E2 E9 6C 96 35 31 06 1A D0 D3 CA 7D 74 25 4C 27 9F 11 43 FE 31 7E C4 5D D5 A1 AD BB E0 5F 9C EE CA 6A 3E CF 95 D5 73 D3 6D FE A1 74 9B 73 0E 3E 4C 77 34 41 F3 A4 DB 3C EF BA CD D3 75 3F C8 7A 8C 6E E5 65 2A CF 5B 9C 54 A9 E4 A6 1B 3B 26 61 E9 AE EA DD 73 1D F5 63 D5 50 F2 78 B2 5C D7 77 9E 00 F7 22 90 0F CA 95 EE 37 79 C8 07 22 90 9B A5 90 BD 73 2F DD 31 90 25 C0 1F DE B7 04 FD 6F 04 1A 2B AE 0D 6C 0B 36 77 06 FA 5B 43 DE CD A1 32 0E E6 72 F1 55 ED B7 1F 99 AA AD BC 40 E5 5D 2B CD B1 94 F9 7F 83 F7 CB 92 38 AC EE 64 B8 60 F3 CC AF CA 32 1F 7D 02 6C E2 D4 0E 45 ED 50 16 7B 87 12 FA DF 7F CA 60 87 52 F7 E5 B1 2E AD 00 FE AE 6C 87 72 EA 04 0A BC 11 F1 A3 72 E2 8F F3 E2 87 22 E2 39 F5 81 54 7D 20 5D 04 0F A4 53 DF DE 3D 90 02 FB 73 DF D4 17 B0 AB A9 51 D4 1F 68 45 41 68 DF E8 82 EA 07 66 63 16 63 62 61 61 57 F5 7F 5F FD 62 EE 54 FD 33 E8 6F DB 7F E2 24 AC FD 17 CF 36 37 63 7E EC 9F 8A BF C7 34 0D A3 E0 62 44 FF 42 75 3B 51 FD 6A B7 93 70 B7 33 13 7B B5 DB 81 E2 CF FA 03 95 50 7C 7F C7 BF 8F E2 7E EC 7D C5 F6 FE 74 12 F0 1D 5E 58 F0 70 79 67 65 D1 2C C3 A8 E0 13 07 2F 26 4D 05 2F 0F BE AF FB DC 30 CA 61 7D 53 6D 47 53 FC D8 07 8A 25 7E E8 35 0D F8 E8 CA C2 8A 37 D1 34 63 64 0D 46 A0 8A 4F 5C BC 98 34 55 BC BC F8 DE ED 6F F7 26 71 D8 9E E1 E1 F3 B0 A5 FF 48 51 FC CD 33 18 38 AD 8A 57 C5 2F 7A F1 83 DB C6 7C 1A 0E 1B FD BA 76 E4 71 3F 36 0A C5 77 C8 36 35 FF F8 3D 38 D2 F6 69 18 FC 80 DC 1E D3 AD 24 08 FE EB 24 01 FC 1A 29 F8 8F 67 B9 C7 C4 18 69 B3 81 31 01 26 3B F6 19 D6 90 6D C8 CE 02 0C 30 31 71 E2 97 C4 88 47 E2 40 8B 0F 98 73 02 0D 7E 28 D0 20 A7 E9 A1 A0 C5 A4 CC 13 68 30 EF A0 C1 74 D0 D2 C7 52 71 20 D1 59 4C 40 B4 14 EC CD CB 2D 4E 0E 2B BF 79 96 EF 49 B6 D4 91 D4 29 D9 0A 7D EE B6 46 04 EB 95 AB D0 BB F9 0A FD 7A A4 42 D7 48 C1 4E CD 12 AC 04 66 EC 8F 2E 51 C8 D3 C1 92 31 60 75 FC 0A DD DF CB 6F 32 54 6D 85 39 BB 36 F0 DA E2 AB C9 DD 0F 23 AC 3C F3 8B AC 26 2B AF 45 85 AD D1 D8 A9 95 A2 FE 7B E9 C8 78 32 87 DD 38 32 D2 AE 15 54 77 CA AA EE E8 44 C1 97 11 D5 C7 E4 CA F0 6D BE 0C 5F 8F 94 61 A7 54 F5 C9 D9 6E F5 9B 58 93 89 36 19 4C B4 5A 86 A3 BB 87 62 52 1E D5 32 1C 1A 69 DD 92 CA 61 23 E5 93 6F 44 EA F0 9B B2 7D C3 78 1A 08 2D 2C 58 06 C0 C7 7A 90 C5 64 99 55 B0 92 5F F7 1E 24 E5 51 05 7B E9 52 DD 18 DF E8 1E EF D9 AF F1 63 5E 01 AC 4C 89 DD 54 8E 81 EB 0B DC E9 AA 62 55 B1 F1 62 3B BA 4B 21 D8 F7 EB FE F5 0E EC 09 B6 2A F6 04 9F F8 35 60 CF BB 0B 5B 62 59 9A 36 02 C6 90 C5 AA 60 25 BF 27 3F 48 CA A3 0A 76 43 E8 EA 25 D8 C5 4E DE 7F A7 19 F6 04 E5 75 4A 1B E2 C3 53 51 B1 0B B4 21 AE F8 6C 16 95 3C 5D EC D3 31 62 97 F1 BF AA 6C E4 2E 74 C2 1E 7E 67 D0 5B 1B F2 1E 1A 3F DA 34 B9 D7 B7 DD F2 42 9C 58 36 F1 9D 61 DA A8 B8 1F 3E DE DF 0D 67 07 BE 5B 5E 8E 2A 7E 69 2E F3 6F 88 DD 38 A6 8D 09 6D 88 CF F8 C0 36 CB 0D F1 98 61 CF 1D 76 DC C0 22 5B E2 62 0A A7 6D 89 2B 2E 55 C5 2D 71 F9 B9 97 EA 1F 6A DF 79 11 E5 B0 73 B5 1F 0E 0A FA 01 6A 42 73 90 E7 91 FF 03 32 BC E8 A3" + .hexToBytes().inflate().toUHexString() + + println(result) + } + + private val sampleLongText = // 574 chars + "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + @Test fun testGzip() { - val str = "qGnJ1RrFC9" - println(str) - val hash = str.toByteArray().gzip() - assertContentEquals( - "1F 8B 08 00 00 00 00 00 00 13 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 A8 35 6D D9 0A 00 00 00".hexToBytes(), - hash, - message = hash.toUHexString() - ) + val str = sampleLongText +// val hash = str.toByteArray().gzip() +// assertContentEquals( +// "1F 8B 08 00 00 00 00 00 00 13 2B 74 CF F3 32 0C 1F 8B 08 00 00 00 00 00 00 13 2B 74 CF F3".hexToBytes(), +// hash, +// message = hash.toUHexString() +// ) + + assertEquals(str, str.toByteArray().gzip().ungzip().decodeToString()) } @Test