From 41b216e840e60db615807302d02f4d35098ef804 Mon Sep 17 00:00:00 2001 From: cfig Date: Wed, 5 Jan 2022 23:41:12 +0800 Subject: [PATCH] Struct3: retire legacy Struct3 --- bbootimg/src/main/kotlin/bootimg/Common.kt | 4 +- .../src/main/kotlin/bootimg/v3/VendorBoot.kt | 3 +- bbootimg/src/main/kotlin/init/BootReason.kt | 29 + .../main/kotlin/cfig/helper/CryptoHelper.kt | 4 +- helper/src/main/kotlin/cfig/helper/Helper.kt | 2 +- .../src/main/kotlin/cfig/helper/Launcher.kt | 12 +- helper/src/main/kotlin/cfig/io/Struct3.kt | 867 +++++++++--------- .../src/main/kotlin/cfig/io/Struct3Retire.kt | 530 +++++++++++ .../test/kotlin/cfig/io/Struct3RetireTest.kt | 511 +++++++++++ helper/src/test/kotlin/cfig/io/Struct3Test.kt | 197 ++-- 10 files changed, 1639 insertions(+), 520 deletions(-) create mode 100644 bbootimg/src/main/kotlin/init/BootReason.kt create mode 100644 helper/src/main/kotlin/cfig/io/Struct3Retire.kt create mode 100644 helper/src/test/kotlin/cfig/io/Struct3RetireTest.kt diff --git a/bbootimg/src/main/kotlin/bootimg/Common.kt b/bbootimg/src/main/kotlin/bootimg/Common.kt index c43bc3e..827db39 100644 --- a/bbootimg/src/main/kotlin/bootimg/Common.kt +++ b/bbootimg/src/main/kotlin/bootimg/Common.kt @@ -19,7 +19,7 @@ import cfig.bootimg.cpio.AndroidCpio import cfig.utils.DTC import cfig.helper.Helper import cfig.helper.ZipHelper -import cfig.io.Struct3.InputStreamExt.Companion.getInt +import cfig.io.Struct3 import cfig.utils.KernelExtractor import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor @@ -363,7 +363,7 @@ class Common { fun probeHeaderVersion(fileName: String): Int { return FileInputStream(fileName).let { fis -> fis.skip(40) - fis.getInt(ByteOrder.LITTLE_ENDIAN) + Struct3.IntShip().get(fis, ByteOrder.LITTLE_ENDIAN) } } diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt index 18fbb1e..c16cf7e 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt @@ -21,7 +21,6 @@ import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Signer import cfig.helper.Helper import cfig.io.Struct3 -import cfig.io.Struct3.ByteArrayExt.Companion.toCString import cfig.packable.VBMetaParser import com.fasterxml.jackson.databind.ObjectMapper import de.vandermeer.asciitable.AsciiTable @@ -140,7 +139,7 @@ data class VendorBoot( this.type = VrtType.fromInt((info[2] as UInt).toInt()) this.name = info[3] as String this.boardId = info[4] as ByteArray - this.boardIdStr = boardId.toCString() + this.boardIdStr = Struct3.StringFleet().get(boardId, ByteOrder.LITTLE_ENDIAN) this.file = dumpFile } diff --git a/bbootimg/src/main/kotlin/init/BootReason.kt b/bbootimg/src/main/kotlin/init/BootReason.kt new file mode 100644 index 0000000..61afe17 --- /dev/null +++ b/bbootimg/src/main/kotlin/init/BootReason.kt @@ -0,0 +1,29 @@ +package init + +class BootReason { + /* + Canonical boot reason format + ,,… + */ + class Reason private constructor(private val reason: String, subReason: String?, detail: String?) { + companion object { + val kernelSet = listOf("watchdog", "kernel_panic") + val strongSet = listOf("recovery", "bootloader") + val bluntSet = listOf("cold", "hard", "warm", "shutdown", "reboot") + fun create( + firstSpanReason: String, + secondSpanReason: String? = null, + detailReason: String? = null + ): Reason { + if (firstSpanReason !in mutableListOf().apply { + addAll(kernelSet) + addAll(strongSet) + addAll(bluntSet) + }) { + throw IllegalArgumentException("$firstSpanReason is not allowd first span boot reason in Android") + } + return Reason(firstSpanReason, secondSpanReason, detailReason) + } + }//end-of-companion + } //end-of-Reason +} //EOF diff --git a/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt index b158338..c31b562 100644 --- a/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt +++ b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt @@ -178,9 +178,9 @@ class CryptoHelper { from avbtool::encode_rsa_key() */ fun encodeRSAkey(rsa: org.bouncycastle.asn1.pkcs.RSAPrivateKey): ByteArray { - assert(65537.toBigInteger() == rsa.publicExponent) + require(65537.toBigInteger() == rsa.publicExponent) val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING) - assert(rsa.modulus.bitLength() == numBits) + require(rsa.modulus.bitLength() == numBits) val b = BigInteger.valueOf(2).pow(32) val n0inv = b.minus(rsa.modulus.modInverse(b)).toLong() val rrModn = BigInteger.valueOf(4).pow(numBits).rem(rsa.modulus) diff --git a/helper/src/main/kotlin/cfig/helper/Helper.kt b/helper/src/main/kotlin/cfig/helper/Helper.kt index 69e791c..ebc0c03 100644 --- a/helper/src/main/kotlin/cfig/helper/Helper.kt +++ b/helper/src/main/kotlin/cfig/helper/Helper.kt @@ -117,7 +117,7 @@ class Helper { RandomAccessFile(outImgName, "rw").use { outRaf -> inRaf.seek(offset) val data = ByteArray(length) - assert(length == inRaf.read(data)) + check(length == inRaf.read(data)) outRaf.write(data) } } diff --git a/helper/src/main/kotlin/cfig/helper/Launcher.kt b/helper/src/main/kotlin/cfig/helper/Launcher.kt index 6871c5d..2e6c91b 100644 --- a/helper/src/main/kotlin/cfig/helper/Launcher.kt +++ b/helper/src/main/kotlin/cfig/helper/Launcher.kt @@ -138,7 +138,7 @@ class Launcher { val outFile = File(kFile).name + ".csr" val inBytes = File(kFile).readBytes() val k = (CryptoHelper.KeyBox.parse2(inBytes) as Array<*>)[2] - assert(k is org.bouncycastle.asn1.pkcs.RSAPrivateKey) { + require(k is org.bouncycastle.asn1.pkcs.RSAPrivateKey) { "${k!!::class} is not org.bouncycastle.asn1.pkcs.RSAPrivateKey" } OpenSslHelper.PK1Key(KeyFormat.PEM, inBytes).toCsr(info2.getProperty("csr.info")).writeTo(outFile) @@ -163,7 +163,7 @@ class Launcher { val k = CryptoHelper.KeyBox.parse2(inBytes) as Array<*> val kType = if ((k[1] as String) == "PEM") KeyFormat.PEM else KeyFormat.DER val outFile = File(info2.getProperty("file")).name + ".pk1" - assert(k[2] is java.security.interfaces.RSAPrivateKey) { + require(k[2] is java.security.interfaces.RSAPrivateKey) { "${k[2]!!::class} is NOT java.security.interfaces.RSAPrivateKey" } val hint = "RSA private: PK8($kType) => PK1(PEM)" @@ -180,7 +180,7 @@ class Launcher { val k = CryptoHelper.KeyBox.parse2(inBytes) as Array<*> val kType = if ((k[1] as String) == "PEM") KeyFormat.PEM else KeyFormat.DER val outFileStem = File(info2.getProperty("file")).name - assert(k[2] is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + require(k[2] is org.bouncycastle.asn1.pkcs.RSAPrivateKey) val hint = "RSA private: PK1 => PK8(PEM,DER)" log.info("Running: $hint") OpenSslHelper.PK1Key(data = File(kFile).readBytes()).let { rsa -> @@ -251,7 +251,7 @@ class Launcher { val hint = "RSA private(PK8): => Public Key(PK8, PEM)" val kFile = args[1] val outFile = args[2] - assert((CryptoHelper.KeyBox.parse2(File(kFile).readBytes()) as Array<*>)[2] is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + require((CryptoHelper.KeyBox.parse2(File(kFile).readBytes()) as Array<*>)[2] is org.bouncycastle.asn1.pkcs.RSAPrivateKey) val pk8rsa = OpenSslHelper.PK8RsaKey(KeyFormat.PEM, File(kFile).readBytes()) pk8rsa.getPublicKey().writeTo(outFile) log.info("$hint: $kFile => $outFile") @@ -262,7 +262,7 @@ class Launcher { val kFile = args[1] val outFile = args[2] val inBytes = File(kFile).readBytes() - assert((CryptoHelper.KeyBox.parse2(inBytes) as Array<*>)[2] is java.security.interfaces.RSAPrivateKey) + require((CryptoHelper.KeyBox.parse2(inBytes) as Array<*>)[2] is java.security.interfaces.RSAPrivateKey) val p = PemReader(InputStreamReader(ByteArrayInputStream(File(kFile).readBytes()))).readPemObject() if (p != null) {//pem hint = "PK8 RSA: PEM => DER" @@ -290,7 +290,7 @@ class Launcher { val kFile = args[1] val crtFile = args[2] val outFile = args[3] - assert((CryptoHelper.KeyBox.parse2(File(crtFile).readBytes()) as Array<*>)[2] is Certificate) + require((CryptoHelper.KeyBox.parse2(File(crtFile).readBytes()) as Array<*>)[2] is Certificate) val envPassword = System.getProperty("password") ?: "secretpassword" val envAlias = System.getProperty("alias") ?: "someUnknownAlias" val crt = OpenSslHelper.Crt(File(crtFile).readBytes()) diff --git a/helper/src/main/kotlin/cfig/io/Struct3.kt b/helper/src/main/kotlin/cfig/io/Struct3.kt index a7155f5..7a782de 100644 --- a/helper/src/main/kotlin/cfig/io/Struct3.kt +++ b/helper/src/main/kotlin/cfig/io/Struct3.kt @@ -1,40 +1,6 @@ -// Copyright 2021 yuyezhong@gmail.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package cfig.io -import cfig.io.Struct3.ByteArrayExt.Companion.toCString -import cfig.io.Struct3.ByteArrayExt.Companion.toInt -import cfig.io.Struct3.ByteArrayExt.Companion.toLong -import cfig.io.Struct3.ByteArrayExt.Companion.toShort -import cfig.io.Struct3.ByteArrayExt.Companion.toUInt -import cfig.io.Struct3.ByteArrayExt.Companion.toULong -import cfig.io.Struct3.ByteArrayExt.Companion.toUShort -import cfig.io.Struct3.ByteBufferExt.Companion.appendByteArray -import cfig.io.Struct3.ByteBufferExt.Companion.appendPadding -import cfig.io.Struct3.ByteBufferExt.Companion.appendUByteArray -import cfig.io.Struct3.InputStreamExt.Companion.getByteArray -import cfig.io.Struct3.InputStreamExt.Companion.getCString -import cfig.io.Struct3.InputStreamExt.Companion.getChar -import cfig.io.Struct3.InputStreamExt.Companion.getInt -import cfig.io.Struct3.InputStreamExt.Companion.getLong -import cfig.io.Struct3.InputStreamExt.Companion.getPadding -import cfig.io.Struct3.InputStreamExt.Companion.getShort -import cfig.io.Struct3.InputStreamExt.Companion.getUByteArray -import cfig.io.Struct3.InputStreamExt.Companion.getUInt -import cfig.io.Struct3.InputStreamExt.Companion.getULong -import cfig.io.Struct3.InputStreamExt.Companion.getUShort +import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.IOException import java.io.InputStream @@ -43,488 +9,533 @@ import java.nio.ByteOrder import java.nio.charset.StandardCharsets import java.util.* import java.util.regex.Pattern -import kotlin.random.Random -class Struct3 { - private val formatString: String +class Struct3(inFormatString: String) { + private val formatString: String = inFormatString private var byteOrder = ByteOrder.LITTLE_ENDIAN - private val formats = ArrayList>() + private val warships = ArrayList>() - constructor(inFormatString: String) { - assert(inFormatString.isNotEmpty()) { "FORMAT_STRING must not be empty" } - formatString = inFormatString + init { + require(inFormatString.isNotEmpty()) { "FORMAT_STRING must not be empty" } val m = Pattern.compile("(\\d*)([a-zA-Z])").matcher(formatString) - when (formatString[0]) { - '>', '!' -> this.byteOrder = ByteOrder.BIG_ENDIAN - '@', '=' -> this.byteOrder = ByteOrder.nativeOrder() - else -> this.byteOrder = ByteOrder.LITTLE_ENDIAN + this.byteOrder = when (formatString[0]) { + '>', '!' -> ByteOrder.BIG_ENDIAN + '@', '=' -> ByteOrder.nativeOrder() + else -> ByteOrder.LITTLE_ENDIAN } while (m.find()) { //item[0]: Type, item[1]: multiple // if need to expand format items, explode it // eg: "4L" will be exploded to "1L 1L 1L 1L", so it's treated as primitive // eg: "10x" won't be exploded, it's still "10x", so it's treated as non-primitive - val typeName: Any = when (m.group(2)) { - //primitive types - "x" -> Random //byte 1 (exploded) - "b" -> Byte //byte 1 (exploded) - "B" -> UByte //UByte 1 (exploded) - "s" -> String //string (exploded) - //zippable types, which need to be exploded with multiple=1 - "c" -> Char - "h" -> Short //2 - "H" -> UShort //2 - "i", "l" -> Int //4 - "I", "L" -> UInt //4 - "q" -> Long //8 - "Q" -> ULong //8 - else -> throw IllegalArgumentException("type [" + m.group(2) + "] not supported") - } - val bPrimitive = m.group(2) in listOf("x", "b", "B", "s") - val multiple = if (m.group(1).isEmpty()) 1 else Integer.decode(m.group(1)) - if (bPrimitive) { - formats.add(arrayOf(typeName, multiple)) - } else { - for (i in 0 until multiple) { - formats.add(arrayOf(typeName, 1)) - } - } + require(m.group(2).length == 1) + val marker = m.group(2)[0] + val count = if (m.group(1).isEmpty()) 1 else Integer.decode(m.group(1)) + warships.addAll(makeWarship(marker, count)) } } - private fun getFormatInfo(inCursor: Int): String { - return ("type=" + formats.get(inCursor)[0] + ", value=" + formats.get(inCursor)[1]) - } - - override fun toString(): String { - val formatStr = mutableListOf() - formats.forEach { - val fs = StringBuilder() - when (it[0]) { - Random -> fs.append("x") - Byte -> fs.append("b") - UByte -> fs.append("B") - String -> fs.append("s") - Char -> fs.append("c") - Short -> fs.append("h") - UShort -> fs.append("H") - Int -> fs.append("i") - UInt -> fs.append("I") - Long -> fs.append("q") - ULong -> fs.append("Q") - else -> throw IllegalArgumentException("type [" + it[0] + "] not supported") - } - fs.append(":" + it[1]) - formatStr.add(fs.toString()) + private fun makeWarship(marker: Char, count: Int): List> { + return when (marker) { + //primitive types + 's' -> listOf(StringFleet(count)) //string (exploded) + 'x' -> listOf(PaddingFleet(count)) //byte 1 (exploded) + 'b' -> listOf(ByteFleet(count)) //byte 1 (exploded) + 'B' -> listOf(UByteFleet(count)) //UByte 1 (exploded) + //zippable types, which need to be exploded with multiple=1 + 'c' -> List(count) { CharShip() } //1 + 'h' -> List(count) { ShortShip() } //2 + 'H' -> List(count) { UShortShip() } //2 + 'i', 'l' -> List(count) { IntShip() } //4 + 'I', 'L' -> List(count) { UIntShip() } //4 + 'q' -> List(count) { LongShip() } //8 + 'Q' -> List(count) { ULongShip() } //8 + else -> throw IllegalArgumentException("type [$marker] not supported") } - return "Struct3(formatString='$formatString', byteOrder=$byteOrder, formats=$formatStr)" } fun calcSize(): Int { - var ret = 0 - for (format in formats) { - ret += when (val formatType = format[0]) { - Random, Byte, UByte, Char, String -> format[1] as Int - Short, UShort -> 2 * format[1] as Int - Int, UInt -> 4 * format[1] as Int - Long, ULong -> 8 * format[1] as Int - else -> throw IllegalArgumentException("Class [$formatType] not supported") - } - } - return ret + return warships.sumOf { it.multiple * it.sz } + } + + override fun toString(): String { + val sb = StringBuilder("Struct[$byteOrder]") + warships.map { it.toString() }.reduce { acc, s -> "$acc$s" } + return sb.toString() } @Throws(IllegalArgumentException::class) fun pack(vararg args: Any?): ByteArray { - if (args.size != this.formats.size) { - throw IllegalArgumentException("argument size " + args.size + " doesn't match format size " + this.formats.size) - } - val bf = ByteBuffer.allocate(this.calcSize()) - bf.order(this.byteOrder) - for (i in args.indices) { - val arg = args[i] - val typeName = formats[i][0] - val multiple = formats[i][1] as Int - - if (typeName !in arrayOf(Random, Byte, String, UByte)) { - assert(1 == multiple) + if (args.size != this.warships.size) { + throw IllegalArgumentException("argument size " + args.size + " doesn't match format size " + this.warships.size) + } + return ByteBuffer.allocate(this.calcSize()).let { bf -> + bf.order(this.byteOrder) + args.forEachIndexed { index, arg -> + warships.get(index).put(bf, arg) } + bf.array() + } + } - //x: padding: - if (Random == typeName) { - when (arg) { - null -> bf.appendPadding(0, multiple) - is Byte -> bf.appendPadding(arg, multiple) - is Int -> bf.appendPadding(arg.toByte(), multiple) - else -> throw IllegalArgumentException("Index[" + i + "] Unsupported arg [" + arg + "] with type [" + formats[i][0] + "]") - } - continue - } + @Throws(IOException::class, IllegalArgumentException::class) + fun unpack(iS: InputStream): List<*> { + return warships.map { it.get(iS, byteOrder) } + } - //c: character - if (Char == typeName) { - assert(arg is Char) { "[$arg](${arg!!::class.java}) is NOT Char" } - if ((arg as Char) !in '\u0000'..'\u00ff') { - throw IllegalArgumentException("arg[${arg.code}] exceeds 8-bit bound") - } - bf.put(arg.code.toByte()) - continue - } + private interface IWarShip { + val sz: Int + var multiple: Int + val log: Logger + fun get(stream: InputStream, byteOrder: ByteOrder): T + fun get(ba: ByteArray, byteOrder: ByteOrder): T + fun put(bf: ByteBuffer, arg: Any?) + } - //b: byte array - if (Byte == typeName) { - when (arg) { - is IntArray -> bf.appendByteArray(arg, multiple) - is ByteArray -> bf.appendByteArray(arg, multiple) - else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT ByteArray/IntArray") - } - continue - } + private interface IBaseShip : IWarShip { + override var multiple: Int + get() = 1 + set(@Suppress("UNUSED_PARAMETER") value) {} + } - //B: UByte array - if (UByte == typeName) { - when (arg) { - is ByteArray -> bf.appendByteArray(arg, multiple) - is UByteArray -> bf.appendUByteArray(arg, multiple) - is IntArray -> bf.appendUByteArray(arg, multiple) - else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT ByteArray/IntArray") - } - continue - } + private class ShortShip : IBaseShip { + override fun get(stream: InputStream, byteOrder: ByteOrder): Short { + val data = ByteArray(Short.SIZE_BYTES) + check(Short.SIZE_BYTES == stream.read(data)) + return get(data, byteOrder) + } - //s: String - if (String == typeName) { - assert(arg != null) { "arg can not be NULL for String, formatString=$formatString, ${getFormatInfo(i)}" } - assert(arg is String) { "[$arg](${arg!!::class.java}) is NOT String, ${getFormatInfo(i)}" } - bf.appendByteArray((arg as String).toByteArray(), multiple) - continue + override fun get(ba: ByteArray, byteOrder: ByteOrder): Short { + val typeSize = Short.SIZE_BYTES / Byte.SIZE_BYTES + check(typeSize == ba.size) { "Short must have $typeSize bytes" } + return ByteBuffer.allocate(ba.size).let { + it.order(byteOrder) + it.put(ba) + it.flip() + it.getShort() } + } + override fun put(bf: ByteBuffer, arg: Any?) { //h: Short - if (Short == typeName) { - when (arg) { - is Int -> { - assert(arg in Short.MIN_VALUE..Short.MAX_VALUE) { "[$arg] is truncated as type Short.class" } - bf.putShort(arg.toShort()) - } - is Short -> bf.putShort(arg) //instance Short - else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT Short/Int") + when (arg) { + is Int -> { + require(arg in Short.MIN_VALUE..Short.MAX_VALUE) { "[$arg] is truncated as type Short.class" } + bf.putShort(arg.toShort()) } - continue + is Short -> bf.putShort(arg) //instance Short + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT Short/Int") } + } - //H: UShort - if (UShort == typeName) { - assert(arg is UShort || arg is UInt || arg is Int) { "[$arg](${arg!!::class.java}) is NOT UShort/UInt/Int" } - when (arg) { - is Int -> { - assert(arg >= UShort.MIN_VALUE.toInt() && arg <= UShort.MAX_VALUE.toInt()) { "[$arg] is truncated as type UShort" } - bf.putShort(arg.toShort()) - } - is UInt -> { - assert(arg >= UShort.MIN_VALUE && arg <= UShort.MAX_VALUE) { "[$arg] is truncated as type UShort" } - bf.putShort(arg.toShort()) - } - is UShort -> bf.putShort(arg.toShort()) - } - continue - } + override fun toString(): String { + return "h" + } - //i, l: Int - if (Int == typeName) { - assert(arg is Int) { "[$arg](${arg!!::class.java}) is NOT Int" } - bf.putInt(arg as Int) - continue - } + override val sz: Int = Short.SIZE_BYTES + override val log: Logger = LoggerFactory.getLogger(ShortShip::class.java) + } - //I, L: UInt - if (UInt == typeName) { - when (arg) { - is Int -> { - assert(arg >= 0) { "[$arg] is invalid as type UInt" } - bf.putInt(arg) - } - is UInt -> bf.putInt(arg.toInt()) - is Long -> { - assert(arg >= 0) { "[$arg] is invalid as type UInt" } - bf.putInt(arg.toInt()) - } - else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT UInt/Int/Long") - } - continue - } + private class UShortShip : IBaseShip { + override fun get(stream: InputStream, byteOrder: ByteOrder): UShort { + val data = ByteArray(UShort.SIZE_BYTES) + check(UShort.SIZE_BYTES == stream.read(data)) + return get(data, byteOrder) + } - //q: Long - if (Long == typeName) { - when (arg) { - is Long -> bf.putLong(arg) - is Int -> bf.putLong(arg.toLong()) - else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT Long/Int") - } - continue + override fun get(ba: ByteArray, byteOrder: ByteOrder): UShort { + val typeSize = UShort.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == ba.size) { "UShort must have $typeSize bytes" } + return ByteBuffer.allocate(ba.size).let { + it.order(byteOrder) + it.put(ba) + it.flip() + it.getShort().toUShort() } + } - //Q: ULong - if (ULong == typeName) { - when (arg) { - is Int -> { - assert(arg >= 0) { "[$arg] is invalid as type ULong" } - bf.putLong(arg.toLong()) - } - is Long -> { - assert(arg >= 0) { "[$arg] is invalid as type ULong" } - bf.putLong(arg) - } - is ULong -> bf.putLong(arg.toLong()) - else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT Int/Long/ULong") + override fun put(bf: ByteBuffer, arg: Any?) { + require(arg is UShort || arg is UInt || arg is Int) { "[$arg](${arg!!::class.java}) is NOT UShort/UInt/Int" } + when (arg) { + is Int -> { + require(arg >= UShort.MIN_VALUE.toInt() && arg <= UShort.MAX_VALUE.toInt()) { "[$arg] is truncated as type UShort" } + bf.putShort(arg.toShort()) } - continue + is UInt -> { + require(arg >= UShort.MIN_VALUE && arg <= UShort.MAX_VALUE) { "[$arg] is truncated as type UShort" } + bf.putShort(arg.toShort()) + } + is UShort -> bf.putShort(arg.toShort()) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT valid for UShort") } + } - throw IllegalArgumentException("unrecognized format $typeName") + override fun toString(): String { + return "H" } - return bf.array() - } - @Throws(IOException::class, IllegalArgumentException::class) - fun unpack(iS: InputStream): List<*> { - val ret = ArrayList() - for (format in this.formats) { - when (format[0]) { - Random -> ret.add(iS.getPadding(format[1] as Int)) //return padding byte - Byte -> ret.add(iS.getByteArray(format[1] as Int)) //b: byte array - UByte -> ret.add(iS.getUByteArray(format[1] as Int)) //B: ubyte array - Char -> ret.add(iS.getChar()) //char: 1 - String -> ret.add(iS.getCString(format[1] as Int)) //c string - Short -> ret.add(iS.getShort(this.byteOrder)) //h: short - UShort -> ret.add(iS.getUShort(this.byteOrder)) //H: UShort - Int -> ret.add(iS.getInt(this.byteOrder)) //i, l: Int - UInt -> ret.add(iS.getUInt(this.byteOrder)) //I, L: UInt - Long -> ret.add(iS.getLong(this.byteOrder)) //q: Long - ULong -> ret.add(iS.getULong(this.byteOrder)) //Q: ULong - else -> throw IllegalArgumentException("Class [" + format[0] + "] not supported") - }//end-of-when - }//end-of-for - return ret + override val sz: Int = UShort.SIZE_BYTES + override val log: Logger = LoggerFactory.getLogger(UShortShip::class.java) } - class ByteBufferExt { - companion object { - private val log = LoggerFactory.getLogger(ByteBufferExt::class.java) - - @Throws(IllegalArgumentException::class) - fun ByteBuffer.appendPadding(b: Byte, bufSize: Int) { - when { - bufSize == 0 -> { - log.debug("paddingSize is zero, perfect match") - return - } - bufSize < 0 -> { - throw IllegalArgumentException("illegal padding size: $bufSize") - } - else -> { - log.debug("paddingSize $bufSize") - } - } - val padding = ByteArray(bufSize) - Arrays.fill(padding, b) - this.put(padding) - } + //i, l: Int + class IntShip : IBaseShip { + override fun get(stream: InputStream, byteOrder: ByteOrder): Int { + val data = ByteArray(Int.SIZE_BYTES) + check(Int.SIZE_BYTES == stream.read(data)) + return get(data, byteOrder) + } - fun ByteBuffer.appendByteArray(inIntArray: IntArray, bufSize: Int) { - val arg2 = mutableListOf() - inIntArray.toMutableList().mapTo(arg2) { - if (it in Byte.MIN_VALUE..Byte.MAX_VALUE) { - it.toByte() - } else { - throw IllegalArgumentException("$it is not valid Byte") - } - } - appendByteArray(arg2.toByteArray(), bufSize) + override fun get(ba: ByteArray, byteOrder: ByteOrder): Int { + val typeSize = Int.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == ba.size) { "Int must have $typeSize bytes" } + return ByteBuffer.allocate(ba.size).let { + it.order(byteOrder) + it.put(ba) + it.flip() + it.getInt() } + } - @Throws(IllegalArgumentException::class) - fun ByteBuffer.appendByteArray(inByteArray: ByteArray, bufSize: Int) { - val paddingSize = bufSize - inByteArray.size - if (paddingSize < 0) throw IllegalArgumentException("arg length [${inByteArray.size}] exceeds limit: $bufSize") - //data - this.put(inByteArray) - //padding - this.appendPadding(0.toByte(), paddingSize) - log.debug("paddingSize $paddingSize") + override fun put(bf: ByteBuffer, arg: Any?) { + require(arg is Int) { "[$arg](${arg!!::class.java}) is NOT Int" } + bf.putInt(arg) + } + + override fun toString(): String { + return "i" + } + + override val sz: Int = Int.SIZE_BYTES + override val log: Logger = LoggerFactory.getLogger(IntShip::class.java) + } + + //I, L: UInt + class UIntShip : IBaseShip { + override fun get(stream: InputStream, byteOrder: ByteOrder): UInt { + val data = ByteArray(UInt.SIZE_BYTES) + check(UInt.SIZE_BYTES == stream.read(data)) + return get(data, byteOrder) + } + + override fun get(ba: ByteArray, byteOrder: ByteOrder): UInt { + val typeSize = UInt.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == ba.size) { "UInt must have $typeSize bytes" } + return ByteBuffer.allocate(ba.size).let { + it.order(byteOrder) + it.put(ba) + it.flip() + it.getInt().toUInt() } + } - fun ByteBuffer.appendUByteArray(inIntArray: IntArray, bufSize: Int) { - val arg2 = mutableListOf() - inIntArray.toMutableList().mapTo(arg2) { - if (it in UByte.MIN_VALUE.toInt()..UByte.MAX_VALUE.toInt()) - it.toUByte() - else { - throw IllegalArgumentException("$it is not valid Byte") - } + override fun put(bf: ByteBuffer, arg: Any?) { + when (arg) { + is Int -> { + require(arg >= 0) { "[$arg] is invalid as type UInt" } + bf.putInt(arg) + } + is UInt -> bf.putInt(arg.toInt()) + is Long -> { + require(arg >= 0) { "[$arg] is invalid as type UInt" } + bf.putInt(arg.toInt()) } - appendUByteArray(arg2.toUByteArray(), bufSize) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT valid UInt") } + } - fun ByteBuffer.appendUByteArray(inUByteArray: UByteArray, bufSize: Int) { - val bl = mutableListOf() - inUByteArray.toMutableList().mapTo(bl) { it.toByte() } - this.appendByteArray(bl.toByteArray(), bufSize) - } + override fun toString(): String { + return "I" } + + override val sz: Int = UInt.SIZE_BYTES + override val log: Logger = LoggerFactory.getLogger(UIntShip::class.java) } - class InputStreamExt { - companion object { - fun InputStream.getChar(): Char { - val data = ByteArray(Byte.SIZE_BYTES) - assert(Byte.SIZE_BYTES == this.read(data)) - return data[0].toInt().toChar() - } + //q: Long + private class LongShip : IBaseShip { + override fun get(stream: InputStream, byteOrder: ByteOrder): Long { + val data = ByteArray(Long.SIZE_BYTES) + check(Long.SIZE_BYTES == stream.read(data)) + return get(data, byteOrder) + } - fun InputStream.getShort(inByteOrder: ByteOrder): Short { - val data = ByteArray(Short.SIZE_BYTES) - assert(Short.SIZE_BYTES == this.read(data)) - return data.toShort(inByteOrder) + override fun get(ba: ByteArray, byteOrder: ByteOrder): Long { + val typeSize = Long.SIZE_BYTES / Byte.SIZE_BYTES + check(typeSize == ba.size) { "Long must have $typeSize bytes" } + return ByteBuffer.allocate(ba.size).let { + it.order(byteOrder) + it.put(ba) + it.flip() + it.getLong() } + } - fun InputStream.getInt(inByteOrder: ByteOrder): Int { - val data = ByteArray(Int.SIZE_BYTES) - assert(Int.SIZE_BYTES == this.read(data)) - return data.toInt(inByteOrder) + override fun put(bf: ByteBuffer, arg: Any?) { + when (arg) { + is Long -> bf.putLong(arg) + is Int -> bf.putLong(arg.toLong()) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT valid Long") } + } - fun InputStream.getLong(inByteOrder: ByteOrder): Long { - val data = ByteArray(Long.SIZE_BYTES) - assert(Long.SIZE_BYTES == this.read(data)) - return data.toLong(inByteOrder) - } + override fun toString(): String { + return "q" + } - fun InputStream.getUShort(inByteOrder: ByteOrder): UShort { - val data = ByteArray(UShort.SIZE_BYTES) - assert(UShort.SIZE_BYTES == this.read(data)) - return data.toUShort(inByteOrder) - } + override val sz: Int = Long.SIZE_BYTES + override val log: Logger = LoggerFactory.getLogger(LongShip::class.java) + } - fun InputStream.getUInt(inByteOrder: ByteOrder): UInt { - val data = ByteArray(UInt.SIZE_BYTES) - assert(UInt.SIZE_BYTES == this.read(data)) - return data.toUInt(inByteOrder) - } + //Q: ULong + private class ULongShip : IBaseShip { + override fun get(stream: InputStream, byteOrder: ByteOrder): ULong { + val data = ByteArray(ULong.SIZE_BYTES) + check(ULong.SIZE_BYTES == stream.read(data)) + return get(data, byteOrder) + } - fun InputStream.getULong(inByteOrder: ByteOrder): ULong { - val data = ByteArray(ULong.SIZE_BYTES) - assert(ULong.SIZE_BYTES == this.read(data)) - return data.toULong(inByteOrder) + override fun get(ba: ByteArray, byteOrder: ByteOrder): ULong { + val typeSize = ULong.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == ba.size) { "ULong must have $typeSize bytes" } + return ByteBuffer.allocate(ba.size).let { + it.order(byteOrder) + it.put(ba) + it.flip() + it.getLong().toULong() } + } - fun InputStream.getByteArray(inSize: Int): ByteArray { - val data = ByteArray(inSize) - assert(inSize == this.read(data)) - return data + override fun put(bf: ByteBuffer, arg: Any?) { + when (arg) { + is Int -> { + require(arg >= 0) { "[$arg] is invalid as type ULong" } + bf.putLong(arg.toLong()) + } + is Long -> { + require(arg >= 0) { "[$arg] is invalid as type ULong" } + bf.putLong(arg) + } + is ULong -> bf.putLong(arg.toLong()) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT valid ULong") } + } - fun InputStream.getUByteArray(inSize: Int): UByteArray { - val data = ByteArray(inSize) - assert(inSize == this.read(data)) - val innerData2 = mutableListOf() - data.toMutableList().mapTo(innerData2) { it.toUByte() } - return innerData2.toUByteArray() - } + override fun toString(): String { + return "Q" + } - fun InputStream.getCString(inSize: Int): String { - val data = ByteArray(inSize) - assert(inSize == this.read(data)) - return data.toCString() - } + override val sz: Int = ULong.SIZE_BYTES + override val log: Logger = LoggerFactory.getLogger(ULongShip::class.java) + } - fun InputStream.getPadding(inSize: Int): Byte { - val data = ByteArray(Byte.SIZE_BYTES) - assert(Byte.SIZE_BYTES == this.read(data)) //sample the 1st byte - val skipped = this.skip(inSize.toLong() - Byte.SIZE_BYTES)//skip remaining to save memory - assert(inSize.toLong() - Byte.SIZE_BYTES == skipped) - return data[0] + //c: character + private class CharShip : IBaseShip { + override fun get(stream: InputStream, byteOrder: ByteOrder): Char { + val data = ByteArray(Byte.SIZE_BYTES) + check(Byte.SIZE_BYTES == stream.read(data)) + return data[0].toInt().toChar() + } + + override fun get(ba: ByteArray, byteOrder: ByteOrder): Char { + return ba.get(0).toInt().toChar() + } + + override fun put(bf: ByteBuffer, arg: Any?) { + require(arg is Char) { "[$arg](${arg!!::class.java}) is NOT Char" } + if (arg !in '\u0000'..'\u00ff') { + throw IllegalArgumentException("arg[${arg.code}] exceeds 8-bit bound") } + bf.put(arg.code.toByte()) } + + override fun toString(): String { + return "c" + } + + override val sz: Int = 1 + override val log: Logger = LoggerFactory.getLogger(CharShip::class.java) } - class ByteArrayExt { - companion object { - fun ByteArray.toShort(inByteOrder: ByteOrder): Short { - val typeSize = Short.SIZE_BYTES / Byte.SIZE_BYTES - assert(typeSize == this.size) { "Short must have $typeSize bytes" } - return ByteBuffer.allocate(this.size).let { - it.order(inByteOrder) - it.put(this) - it.flip() - it.getShort() + private interface IBaseFleet : IWarShip { + fun appendPadding(bf: ByteBuffer, b: Byte, bufSize: Int) { + when { + bufSize == 0 -> { + log.debug("paddingSize is zero, perfect match") + return + } + bufSize < 0 -> { + throw IllegalArgumentException("illegal padding size: $bufSize") + } + else -> { + log.debug("paddingSize $bufSize") } } + val padding = ByteArray(bufSize) + Arrays.fill(padding, b) + bf.put(padding) + } - fun ByteArray.toInt(inByteOrder: ByteOrder): Int { - val typeSize = Int.SIZE_BYTES / Byte.SIZE_BYTES - assert(typeSize == this.size) { "Int must have $typeSize bytes" } - return ByteBuffer.allocate(this.size).let { - it.order(inByteOrder) - it.put(this) - it.flip() - it.getInt() + @Throws(IllegalArgumentException::class) + fun appendByteArray(bf: ByteBuffer, inByteArray: ByteArray, bufSize: Int) { + val paddingSize = bufSize - inByteArray.size + if (paddingSize < 0) throw IllegalArgumentException("arg length [${inByteArray.size}] exceeds limit: $bufSize") + //data + bf.put(inByteArray) + //padding + appendPadding(bf, 0.toByte(), paddingSize) + log.debug("paddingSize $paddingSize") + } + + fun appendByteArray(bf: ByteBuffer, inIntArray: IntArray, bufSize: Int) { + val arg2 = mutableListOf() + inIntArray.toMutableList().mapTo(arg2) { + if (it in Byte.MIN_VALUE..Byte.MAX_VALUE) { + it.toByte() + } else { + throw IllegalArgumentException("$it is not valid Byte") } } + appendByteArray(bf, arg2.toByteArray(), bufSize) + } - fun ByteArray.toLong(inByteOrder: ByteOrder): Long { - val typeSize = Long.SIZE_BYTES / Byte.SIZE_BYTES - assert(typeSize == this.size) { "Long must have $typeSize bytes" } - return ByteBuffer.allocate(this.size).let { - it.order(inByteOrder) - it.put(this) - it.flip() - it.getLong() + fun appendUByteArray(bf: ByteBuffer, inIntArray: IntArray, bufSize: Int) { + val arg2 = mutableListOf() + inIntArray.toMutableList().mapTo(arg2) { + if (it in UByte.MIN_VALUE.toInt()..UByte.MAX_VALUE.toInt()) + it.toUByte() + else { + throw IllegalArgumentException("$it is not valid Byte") } } + appendUByteArray(bf, arg2.toUByteArray(), bufSize) + } - fun ByteArray.toUShort(inByteOrder: ByteOrder): UShort { - val typeSize = UShort.SIZE_BYTES / Byte.SIZE_BYTES - assert(typeSize == this.size) { "UShort must have $typeSize bytes" } - return ByteBuffer.allocate(this.size).let { - it.order(inByteOrder) - it.put(this) - it.flip() - it.getShort().toUShort() - } + fun appendUByteArray(bf: ByteBuffer, inUByteArray: UByteArray, bufSize: Int) { + val bl = mutableListOf() + inUByteArray.toMutableList().mapTo(bl) { it.toByte() } + appendByteArray(bf, bl.toByteArray(), bufSize) + } + } + + //x: padding: + private class PaddingFleet(override var multiple: Int, override val sz: Int = 1) : IBaseFleet { + override fun get(stream: InputStream, byteOrder: ByteOrder): Byte { + val data = ByteArray(Byte.SIZE_BYTES) + check(Byte.SIZE_BYTES == stream.read(data)) //sample the 1st byte + val skipped = stream.skip(multiple.toLong() - Byte.SIZE_BYTES)//skip remaining to save memory + check(multiple.toLong() - Byte.SIZE_BYTES == skipped) + return data[0] + } + + override fun get(ba: ByteArray, byteOrder: ByteOrder): Byte { + TODO("Not yet implemented") + } + + override fun put(bf: ByteBuffer, arg: Any?) { + when (arg) { + null -> appendPadding(bf, 0, multiple) + is Byte -> appendPadding(bf, arg, multiple) + is Int -> appendPadding(bf, arg.toByte(), multiple) + else -> throw IllegalArgumentException("Unsupported arg [$arg]") } + } - fun ByteArray.toUInt(inByteOrder: ByteOrder): UInt { - val typeSize = UInt.SIZE_BYTES / Byte.SIZE_BYTES - assert(typeSize == this.size) { "UInt must have $typeSize bytes" } - return ByteBuffer.allocate(this.size).let { - it.order(inByteOrder) - it.put(this) - it.flip() - it.getInt().toUInt() - } + override fun toString(): String { + return "${multiple}x" + } + + override val log: Logger = LoggerFactory.getLogger(PaddingFleet::class.java) + } + + //b: byte array + private class ByteFleet(override var multiple: Int = 0) : IBaseFleet { + override fun get(stream: InputStream, byteOrder: ByteOrder): ByteArray { + val data = ByteArray(multiple) + check(multiple == stream.read(data)) + return data + } + + override fun get(ba: ByteArray, byteOrder: ByteOrder): ByteArray { + TODO("Not yet implemented") + } + + override fun put(bf: ByteBuffer, arg: Any?) { + when (arg) { + is IntArray -> appendByteArray(bf, arg, multiple) + is ByteArray -> appendByteArray(bf, arg, multiple) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT ByteArray/IntArray") } + } - fun ByteArray.toULong(inByteOrder: ByteOrder): ULong { - val typeSize = ULong.SIZE_BYTES / Byte.SIZE_BYTES - assert(typeSize == this.size) { "ULong must have $typeSize bytes" } - return ByteBuffer.allocate(this.size).let { - it.order(inByteOrder) - it.put(this) - it.flip() - it.getLong().toULong() - } + override fun toString(): String { + return "${multiple}b" + } + + override val sz: Int = Byte.SIZE_BYTES + override val log: Logger = LoggerFactory.getLogger(ByteFleet::class.java) + } + + //B: UByte array + private class UByteFleet(override var multiple: Int = 0) : IBaseFleet { + override fun get(stream: InputStream, byteOrder: ByteOrder): UByteArray { + val data = ByteArray(multiple) + check(multiple == stream.read(data)) + val innerData2 = mutableListOf() + data.toMutableList().mapTo(innerData2) { it.toUByte() } + return innerData2.toUByteArray() + } + + override fun put(bf: ByteBuffer, arg: Any?) { + when (arg) { + is ByteArray -> appendByteArray(bf, arg, multiple) + is UByteArray -> appendUByteArray(bf, arg, multiple) + is IntArray -> appendUByteArray(bf, arg, multiple) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT ByteArray/IntArray") } + } + + override fun toString(): String { + return "${multiple}B" + } - //similar to this.toString(StandardCharsets.UTF_8).replace("${Character.MIN_VALUE}", "") - // not Deprecated for now, "1.3.41 experimental api: ByteArray.decodeToString()") is a little different - fun ByteArray.toCString(): String { - return this.toString(StandardCharsets.UTF_8).let { str -> - str.indexOf(Character.MIN_VALUE).let { nullPos -> - if (nullPos >= 0) str.substring(0, nullPos) else str - } + override val sz: Int + get() = UByte.SIZE_BYTES + + override fun get(ba: ByteArray, byteOrder: ByteOrder): UByteArray { + TODO("Not yet implemented") + } + + override val log: Logger = LoggerFactory.getLogger(UByteFleet::class.java) + } + + //s: String + class StringFleet(override var multiple: Int = 0) : IBaseFleet { + override fun get(stream: InputStream, byteOrder: ByteOrder): String { + val data = ByteArray(multiple) + check(multiple == stream.read(data)) + return get(data, byteOrder) + } + + override fun get(ba: ByteArray, byteOrder: ByteOrder): String { + return ba.toString(StandardCharsets.UTF_8).let { str -> + str.indexOf(Character.MIN_VALUE).let { nullPos -> + if (nullPos >= 0) str.substring(0, nullPos) else str } } - }//end-of-Companion - }//end-of-ByteArrayExt + } + + override fun put(bf: ByteBuffer, arg: Any?) { + requireNotNull(arg) { "arg can not be NULL for String" } + require(arg is String) { "[$arg](${arg::class.java}) is NOT String" } + appendByteArray(bf, arg.toByteArray(), multiple) + } + + override fun toString(): String { + return "${multiple}s" + } + + override val sz: Int = 1 + override val log: Logger = LoggerFactory.getLogger(StringFleet::class.java) + } } diff --git a/helper/src/main/kotlin/cfig/io/Struct3Retire.kt b/helper/src/main/kotlin/cfig/io/Struct3Retire.kt new file mode 100644 index 0000000..5fd2cc9 --- /dev/null +++ b/helper/src/main/kotlin/cfig/io/Struct3Retire.kt @@ -0,0 +1,530 @@ +// Copyright 2021 yuyezhong@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cfig.io + +import cfig.io.Struct3Retire.ByteArrayExt.Companion.toCString +import cfig.io.Struct3Retire.ByteArrayExt.Companion.toInt +import cfig.io.Struct3Retire.ByteArrayExt.Companion.toLong +import cfig.io.Struct3Retire.ByteArrayExt.Companion.toShort +import cfig.io.Struct3Retire.ByteArrayExt.Companion.toUInt +import cfig.io.Struct3Retire.ByteArrayExt.Companion.toULong +import cfig.io.Struct3Retire.ByteArrayExt.Companion.toUShort +import cfig.io.Struct3Retire.ByteBufferExt.Companion.appendByteArray +import cfig.io.Struct3Retire.ByteBufferExt.Companion.appendPadding +import cfig.io.Struct3Retire.ByteBufferExt.Companion.appendUByteArray +import cfig.io.Struct3Retire.InputStreamExt.Companion.getByteArray +import cfig.io.Struct3Retire.InputStreamExt.Companion.getCString +import cfig.io.Struct3Retire.InputStreamExt.Companion.getChar +import cfig.io.Struct3Retire.InputStreamExt.Companion.getInt +import cfig.io.Struct3Retire.InputStreamExt.Companion.getLong +import cfig.io.Struct3Retire.InputStreamExt.Companion.getPadding +import cfig.io.Struct3Retire.InputStreamExt.Companion.getShort +import cfig.io.Struct3Retire.InputStreamExt.Companion.getUByteArray +import cfig.io.Struct3Retire.InputStreamExt.Companion.getUInt +import cfig.io.Struct3Retire.InputStreamExt.Companion.getULong +import cfig.io.Struct3Retire.InputStreamExt.Companion.getUShort +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.charset.StandardCharsets +import java.util.* +import java.util.regex.Pattern +import kotlin.random.Random + +class Struct3Retire { + private val formatString: String + private var byteOrder = ByteOrder.LITTLE_ENDIAN + private val formats = ArrayList>() + + constructor(inFormatString: String) { + assert(inFormatString.isNotEmpty()) { "FORMAT_STRING must not be empty" } + formatString = inFormatString + val m = Pattern.compile("(\\d*)([a-zA-Z])").matcher(formatString) + when (formatString[0]) { + '>', '!' -> this.byteOrder = ByteOrder.BIG_ENDIAN + '@', '=' -> this.byteOrder = ByteOrder.nativeOrder() + else -> this.byteOrder = ByteOrder.LITTLE_ENDIAN + } + while (m.find()) { + //item[0]: Type, item[1]: multiple + // if need to expand format items, explode it + // eg: "4L" will be exploded to "1L 1L 1L 1L", so it's treated as primitive + // eg: "10x" won't be exploded, it's still "10x", so it's treated as non-primitive + val typeName: Any = when (m.group(2)) { + //primitive types + "x" -> Random //byte 1 (exploded) + "b" -> Byte //byte 1 (exploded) + "B" -> UByte //UByte 1 (exploded) + "s" -> String //string (exploded) + //zippable types, which need to be exploded with multiple=1 + "c" -> Char + "h" -> Short //2 + "H" -> UShort //2 + "i", "l" -> Int //4 + "I", "L" -> UInt //4 + "q" -> Long //8 + "Q" -> ULong //8 + else -> throw IllegalArgumentException("type [" + m.group(2) + "] not supported") + } + val bPrimitive = m.group(2) in listOf("x", "b", "B", "s") + val multiple = if (m.group(1).isEmpty()) 1 else Integer.decode(m.group(1)) + if (bPrimitive) { + formats.add(arrayOf(typeName, multiple)) + } else { + for (i in 0 until multiple) { + formats.add(arrayOf(typeName, 1)) + } + } + } + } + + private fun getFormatInfo(inCursor: Int): String { + return ("type=" + formats.get(inCursor)[0] + ", value=" + formats.get(inCursor)[1]) + } + + override fun toString(): String { + val formatStr = mutableListOf() + formats.forEach { + val fs = StringBuilder() + when (it[0]) { + Random -> fs.append("x") + Byte -> fs.append("b") + UByte -> fs.append("B") + String -> fs.append("s") + Char -> fs.append("c") + Short -> fs.append("h") + UShort -> fs.append("H") + Int -> fs.append("i") + UInt -> fs.append("I") + Long -> fs.append("q") + ULong -> fs.append("Q") + else -> throw IllegalArgumentException("type [" + it[0] + "] not supported") + } + fs.append(":" + it[1]) + formatStr.add(fs.toString()) + } + return "Struct3Retire(formatString='$formatString', byteOrder=$byteOrder, formats=$formatStr)" + } + + fun calcSize(): Int { + var ret = 0 + for (format in formats) { + ret += when (val formatType = format[0]) { + Random, Byte, UByte, Char, String -> format[1] as Int + Short, UShort -> 2 * format[1] as Int + Int, UInt -> 4 * format[1] as Int + Long, ULong -> 8 * format[1] as Int + else -> throw IllegalArgumentException("Class [$formatType] not supported") + } + } + return ret + } + + @Throws(IllegalArgumentException::class) + fun pack(vararg args: Any?): ByteArray { + if (args.size != this.formats.size) { + throw IllegalArgumentException("argument size " + args.size + " doesn't match format size " + this.formats.size) + } + val bf = ByteBuffer.allocate(this.calcSize()) + bf.order(this.byteOrder) + for (i in args.indices) { + val arg = args[i] + val typeName = formats[i][0] + val multiple = formats[i][1] as Int + + if (typeName !in arrayOf(Random, Byte, String, UByte)) { + assert(1 == multiple) + } + + //x: padding: + if (Random == typeName) { + when (arg) { + null -> bf.appendPadding(0, multiple) + is Byte -> bf.appendPadding(arg, multiple) + is Int -> bf.appendPadding(arg.toByte(), multiple) + else -> throw IllegalArgumentException("Index[" + i + "] Unsupported arg [" + arg + "] with type [" + formats[i][0] + "]") + } + continue + } + + //c: character + if (Char == typeName) { + assert(arg is Char) { "[$arg](${arg!!::class.java}) is NOT Char" } + if ((arg as Char) !in '\u0000'..'\u00ff') { + throw IllegalArgumentException("arg[${arg.code}] exceeds 8-bit bound") + } + bf.put(arg.code.toByte()) + continue + } + + //b: byte array + if (Byte == typeName) { + when (arg) { + is IntArray -> bf.appendByteArray(arg, multiple) + is ByteArray -> bf.appendByteArray(arg, multiple) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT ByteArray/IntArray") + } + continue + } + + //B: UByte array + if (UByte == typeName) { + when (arg) { + is ByteArray -> bf.appendByteArray(arg, multiple) + is UByteArray -> bf.appendUByteArray(arg, multiple) + is IntArray -> bf.appendUByteArray(arg, multiple) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT ByteArray/IntArray") + } + continue + } + + //s: String + if (String == typeName) { + assert(arg != null) { "arg can not be NULL for String, formatString=$formatString, ${getFormatInfo(i)}" } + assert(arg is String) { "[$arg](${arg!!::class.java}) is NOT String, ${getFormatInfo(i)}" } + bf.appendByteArray((arg as String).toByteArray(), multiple) + continue + } + + //h: Short + if (Short == typeName) { + when (arg) { + is Int -> { + assert(arg in Short.MIN_VALUE..Short.MAX_VALUE) { "[$arg] is truncated as type Short.class" } + bf.putShort(arg.toShort()) + } + is Short -> bf.putShort(arg) //instance Short + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT Short/Int") + } + continue + } + + //H: UShort + if (UShort == typeName) { + assert(arg is UShort || arg is UInt || arg is Int) { "[$arg](${arg!!::class.java}) is NOT UShort/UInt/Int" } + when (arg) { + is Int -> { + assert(arg >= UShort.MIN_VALUE.toInt() && arg <= UShort.MAX_VALUE.toInt()) { "[$arg] is truncated as type UShort" } + bf.putShort(arg.toShort()) + } + is UInt -> { + assert(arg >= UShort.MIN_VALUE && arg <= UShort.MAX_VALUE) { "[$arg] is truncated as type UShort" } + bf.putShort(arg.toShort()) + } + is UShort -> bf.putShort(arg.toShort()) + } + continue + } + + //i, l: Int + if (Int == typeName) { + assert(arg is Int) { "[$arg](${arg!!::class.java}) is NOT Int" } + bf.putInt(arg as Int) + continue + } + + //I, L: UInt + if (UInt == typeName) { + when (arg) { + is Int -> { + assert(arg >= 0) { "[$arg] is invalid as type UInt" } + bf.putInt(arg) + } + is UInt -> bf.putInt(arg.toInt()) + is Long -> { + assert(arg >= 0) { "[$arg] is invalid as type UInt" } + bf.putInt(arg.toInt()) + } + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT UInt/Int/Long") + } + continue + } + + //q: Long + if (Long == typeName) { + when (arg) { + is Long -> bf.putLong(arg) + is Int -> bf.putLong(arg.toLong()) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT Long/Int") + } + continue + } + + //Q: ULong + if (ULong == typeName) { + when (arg) { + is Int -> { + assert(arg >= 0) { "[$arg] is invalid as type ULong" } + bf.putLong(arg.toLong()) + } + is Long -> { + assert(arg >= 0) { "[$arg] is invalid as type ULong" } + bf.putLong(arg) + } + is ULong -> bf.putLong(arg.toLong()) + else -> throw IllegalArgumentException("[$arg](${arg!!::class.java}) is NOT Int/Long/ULong") + } + continue + } + + throw IllegalArgumentException("unrecognized format $typeName") + } + return bf.array() + } + + @Throws(IOException::class, IllegalArgumentException::class) + fun unpack(iS: InputStream): List<*> { + val ret = ArrayList() + for (format in this.formats) { + when (format[0]) { + Random -> ret.add(iS.getPadding(format[1] as Int)) //return padding byte + Byte -> ret.add(iS.getByteArray(format[1] as Int)) //b: byte array + UByte -> ret.add(iS.getUByteArray(format[1] as Int)) //B: ubyte array + Char -> ret.add(iS.getChar()) //char: 1 + String -> ret.add(iS.getCString(format[1] as Int)) //c string + Short -> ret.add(iS.getShort(this.byteOrder)) //h: short + UShort -> ret.add(iS.getUShort(this.byteOrder)) //H: UShort + Int -> ret.add(iS.getInt(this.byteOrder)) //i, l: Int + UInt -> ret.add(iS.getUInt(this.byteOrder)) //I, L: UInt + Long -> ret.add(iS.getLong(this.byteOrder)) //q: Long + ULong -> ret.add(iS.getULong(this.byteOrder)) //Q: ULong + else -> throw IllegalArgumentException("Class [" + format[0] + "] not supported") + }//end-of-when + }//end-of-for + return ret + } + + class ByteBufferExt { + companion object { + private val log = LoggerFactory.getLogger(ByteBufferExt::class.java) + + @Throws(IllegalArgumentException::class) + fun ByteBuffer.appendPadding(b: Byte, bufSize: Int) { + when { + bufSize == 0 -> { + log.debug("paddingSize is zero, perfect match") + return + } + bufSize < 0 -> { + throw IllegalArgumentException("illegal padding size: $bufSize") + } + else -> { + log.debug("paddingSize $bufSize") + } + } + val padding = ByteArray(bufSize) + Arrays.fill(padding, b) + this.put(padding) + } + + fun ByteBuffer.appendByteArray(inIntArray: IntArray, bufSize: Int) { + val arg2 = mutableListOf() + inIntArray.toMutableList().mapTo(arg2) { + if (it in Byte.MIN_VALUE..Byte.MAX_VALUE) { + it.toByte() + } else { + throw IllegalArgumentException("$it is not valid Byte") + } + } + appendByteArray(arg2.toByteArray(), bufSize) + } + + @Throws(IllegalArgumentException::class) + fun ByteBuffer.appendByteArray(inByteArray: ByteArray, bufSize: Int) { + val paddingSize = bufSize - inByteArray.size + if (paddingSize < 0) throw IllegalArgumentException("arg length [${inByteArray.size}] exceeds limit: $bufSize") + //data + this.put(inByteArray) + //padding + this.appendPadding(0.toByte(), paddingSize) + log.debug("paddingSize $paddingSize") + } + + fun ByteBuffer.appendUByteArray(inIntArray: IntArray, bufSize: Int) { + val arg2 = mutableListOf() + inIntArray.toMutableList().mapTo(arg2) { + if (it in UByte.MIN_VALUE.toInt()..UByte.MAX_VALUE.toInt()) + it.toUByte() + else { + throw IllegalArgumentException("$it is not valid Byte") + } + } + appendUByteArray(arg2.toUByteArray(), bufSize) + } + + fun ByteBuffer.appendUByteArray(inUByteArray: UByteArray, bufSize: Int) { + val bl = mutableListOf() + inUByteArray.toMutableList().mapTo(bl) { it.toByte() } + this.appendByteArray(bl.toByteArray(), bufSize) + } + } + } + + private class InputStreamExt { + companion object { + fun InputStream.getChar(): Char { + val data = ByteArray(Byte.SIZE_BYTES) + assert(Byte.SIZE_BYTES == this.read(data)) + return data[0].toInt().toChar() + } + + fun InputStream.getShort(inByteOrder: ByteOrder): Short { + val data = ByteArray(Short.SIZE_BYTES) + assert(Short.SIZE_BYTES == this.read(data)) + return data.toShort(inByteOrder) + } + + fun InputStream.getInt(inByteOrder: ByteOrder): Int { + val data = ByteArray(Int.SIZE_BYTES) + assert(Int.SIZE_BYTES == this.read(data)) + return data.toInt(inByteOrder) + } + + fun InputStream.getLong(inByteOrder: ByteOrder): Long { + val data = ByteArray(Long.SIZE_BYTES) + assert(Long.SIZE_BYTES == this.read(data)) + return data.toLong(inByteOrder) + } + + fun InputStream.getUShort(inByteOrder: ByteOrder): UShort { + val data = ByteArray(UShort.SIZE_BYTES) + assert(UShort.SIZE_BYTES == this.read(data)) + return data.toUShort(inByteOrder) + } + + fun InputStream.getUInt(inByteOrder: ByteOrder): UInt { + val data = ByteArray(UInt.SIZE_BYTES) + assert(UInt.SIZE_BYTES == this.read(data)) + return data.toUInt(inByteOrder) + } + + fun InputStream.getULong(inByteOrder: ByteOrder): ULong { + val data = ByteArray(ULong.SIZE_BYTES) + assert(ULong.SIZE_BYTES == this.read(data)) + return data.toULong(inByteOrder) + } + + fun InputStream.getByteArray(inSize: Int): ByteArray { + val data = ByteArray(inSize) + assert(inSize == this.read(data)) + return data + } + + fun InputStream.getUByteArray(inSize: Int): UByteArray { + val data = ByteArray(inSize) + assert(inSize == this.read(data)) + val innerData2 = mutableListOf() + data.toMutableList().mapTo(innerData2) { it.toUByte() } + return innerData2.toUByteArray() + } + + fun InputStream.getCString(inSize: Int): String { + val data = ByteArray(inSize) + assert(inSize == this.read(data)) + return data.toCString() + } + + fun InputStream.getPadding(inSize: Int): Byte { + val data = ByteArray(Byte.SIZE_BYTES) + assert(Byte.SIZE_BYTES == this.read(data)) //sample the 1st byte + val skipped = this.skip(inSize.toLong() - Byte.SIZE_BYTES)//skip remaining to save memory + assert(inSize.toLong() - Byte.SIZE_BYTES == skipped) + return data[0] + } + } + } + + class ByteArrayExt { + companion object { + fun ByteArray.toShort(inByteOrder: ByteOrder): Short { + val typeSize = Short.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == this.size) { "Short must have $typeSize bytes" } + return ByteBuffer.allocate(this.size).let { + it.order(inByteOrder) + it.put(this) + it.flip() + it.getShort() + } + } + + fun ByteArray.toInt(inByteOrder: ByteOrder): Int { + val typeSize = Int.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == this.size) { "Int must have $typeSize bytes" } + return ByteBuffer.allocate(this.size).let { + it.order(inByteOrder) + it.put(this) + it.flip() + it.getInt() + } + } + + fun ByteArray.toLong(inByteOrder: ByteOrder): Long { + val typeSize = Long.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == this.size) { "Long must have $typeSize bytes" } + return ByteBuffer.allocate(this.size).let { + it.order(inByteOrder) + it.put(this) + it.flip() + it.getLong() + } + } + + fun ByteArray.toUShort(inByteOrder: ByteOrder): UShort { + val typeSize = UShort.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == this.size) { "UShort must have $typeSize bytes" } + return ByteBuffer.allocate(this.size).let { + it.order(inByteOrder) + it.put(this) + it.flip() + it.getShort().toUShort() + } + } + + fun ByteArray.toUInt(inByteOrder: ByteOrder): UInt { + val typeSize = UInt.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == this.size) { "UInt must have $typeSize bytes" } + return ByteBuffer.allocate(this.size).let { + it.order(inByteOrder) + it.put(this) + it.flip() + it.getInt().toUInt() + } + } + + fun ByteArray.toULong(inByteOrder: ByteOrder): ULong { + val typeSize = ULong.SIZE_BYTES / Byte.SIZE_BYTES + assert(typeSize == this.size) { "ULong must have $typeSize bytes" } + return ByteBuffer.allocate(this.size).let { + it.order(inByteOrder) + it.put(this) + it.flip() + it.getLong().toULong() + } + } + + //similar to this.toString(StandardCharsets.UTF_8).replace("${Character.MIN_VALUE}", "") + // not Deprecated for now, "1.3.41 experimental api: ByteArray.decodeToString()") is a little different + fun ByteArray.toCString(): String { + return this.toString(StandardCharsets.UTF_8).let { str -> + str.indexOf(Character.MIN_VALUE).let { nullPos -> + if (nullPos >= 0) str.substring(0, nullPos) else str + } + } + } + }//end-of-Companion + }//end-of-ByteArrayExt +} diff --git a/helper/src/test/kotlin/cfig/io/Struct3RetireTest.kt b/helper/src/test/kotlin/cfig/io/Struct3RetireTest.kt new file mode 100644 index 0000000..30f9514 --- /dev/null +++ b/helper/src/test/kotlin/cfig/io/Struct3RetireTest.kt @@ -0,0 +1,511 @@ +// Copyright 2021 yuyezhong@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cfig.io + +import cfig.helper.Helper +import cfig.io.Struct3Retire +import com.fasterxml.jackson.databind.ObjectMapper +import org.junit.Assert +import org.junit.Test +import java.io.ByteArrayInputStream + +@OptIn(kotlin.ExperimentalUnsignedTypes::class) +class Struct3RetireTest { + private fun getConvertedFormats(inStruct: Struct3Retire): ArrayList> { + val f = inStruct.javaClass.getDeclaredField("formats") + f.isAccessible = true + val formatDumps = arrayListOf>() + (f.get(inStruct) as ArrayList<*>).apply { + this.forEach { + @Suppress("UNCHECKED_CAST") + val format = it as Array + val k = if (format[0].toString().indexOf(" ") > 0) { + format[0].toString().split(" ")[1] + } else { + format[0].toString() + } + formatDumps.add(mapOf(k to (format[1] as Int))) + } + } + return formatDumps + } + + private fun constructorTestFun1(inFormatString: String) { + println(ObjectMapper().writeValueAsString(getConvertedFormats(Struct3Retire(inFormatString)))) + } + + @Test + fun constructorTest() { + constructorTestFun1("3s") + constructorTestFun1("5b") + constructorTestFun1("5x") + constructorTestFun1("2c") + } + + @Test + fun calcSizeTest() { + Assert.assertEquals(3, Struct3Retire("3s").calcSize()) + Assert.assertEquals(5, Struct3Retire("5b").calcSize()) + Assert.assertEquals(5, Struct3Retire("5x").calcSize()) + Assert.assertEquals(9, Struct3Retire("9c").calcSize()) + } + + @Test + fun toStringTest() { + println(Struct3Retire("!4s2L2QL11QL4x47sx80x")) + } + + //x + @Test + fun paddingTest() { + Assert.assertEquals("0000000000", Helper.toHexString(Struct3Retire("5x").pack(null))) + Assert.assertEquals("0000000000", Helper.toHexString(Struct3Retire("5x").pack(0))) + Assert.assertEquals("0101010101", Helper.toHexString(Struct3Retire("5x").pack(1))) + Assert.assertEquals("1212121212", Helper.toHexString(Struct3Retire("5x").pack(0x12))) + //Integer高位被截掉 + Assert.assertEquals("2323232323", Helper.toHexString(Struct3Retire("5x").pack(0x123))) + // minus 0001_0011 -> 补码 1110 1101,ie. 0xed + Assert.assertEquals("ededededed", Helper.toHexString(Struct3Retire("5x").pack(-0x13))) + //0xff + Assert.assertEquals("ffffffffff", Helper.toHexString(Struct3Retire("5x").pack(-1))) + + try { + Struct3Retire("5x").pack("bad") + Assert.assertTrue("should throw exception here", false) + } catch (e: IllegalArgumentException) { + } + + //unpack + Struct3Retire("3x").unpack(ByteArrayInputStream(Helper.fromHexString("000000"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(0.toByte(), it[0]) + } + Struct3Retire("x2xx").unpack(ByteArrayInputStream(Helper.fromHexString("01121210"))).let { + Assert.assertEquals(3, it.size) + Assert.assertEquals(0x1.toByte(), it[0]) + Assert.assertEquals(0x12.toByte(), it[1]) + Assert.assertEquals(0x10.toByte(), it[2]) + } + } + + //c + @Test + fun characterTest() { + //constructor + Struct3Retire("c") + + //calcSize + Assert.assertEquals(3, Struct3Retire("3c").calcSize()) + + //pack illegal + try { + Struct3Retire("c").pack("a") + Assert.fail("should throw exception here") + } catch (e: Throwable) { + Assert.assertTrue(e is AssertionError || e is IllegalArgumentException) + } + + //pack legal + Assert.assertEquals( + "61", + Helper.toHexString(Struct3Retire("!c").pack('a')) + ) + Assert.assertEquals( + "61", + Helper.toHexString(Struct3Retire("c").pack('a')) + ) + Assert.assertEquals( + "616263", + Helper.toHexString(Struct3Retire("3c").pack('a', 'b', 'c')) + ) + + //unpack + Struct3Retire("3c").unpack(ByteArrayInputStream(Helper.fromHexString("616263"))).let { + Assert.assertEquals(3, it.size) + Assert.assertEquals('a', it[0]) + Assert.assertEquals('b', it[1]) + Assert.assertEquals('c', it[2]) + } + } + + //b + @Test + fun bytesTest() { + //constructor + Struct3Retire("b") + + //calcSize + Assert.assertEquals(3, Struct3Retire("3b").calcSize()) + + //pack + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3Retire("3b").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3Retire("!3b").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123400", Helper.toHexString( + Struct3Retire("3b").pack(byteArrayOf(0x12, 0x34)) + ) + ) + + //unpack + Struct3Retire("3b").unpack(ByteArrayInputStream(Helper.fromHexString("123400"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals("123400", Helper.toHexString(it[0] as ByteArray)) + } + Struct3Retire("bbb").unpack(ByteArrayInputStream(Helper.fromHexString("123400"))).let { + Assert.assertEquals(3, it.size) + Assert.assertEquals("12", Helper.toHexString(it[0] as ByteArray)) + Assert.assertEquals("34", Helper.toHexString(it[1] as ByteArray)) + Assert.assertEquals("00", Helper.toHexString(it[2] as ByteArray)) + } + } + + //B: UByte array + @Test + fun uBytesTest() { + //constructor + Struct3Retire("B") + + //calcSize + Assert.assertEquals(3, Struct3Retire("3B").calcSize()) + + //pack + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3Retire("3B").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3Retire("!3B").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123400", Helper.toHexString( + Struct3Retire("3B").pack(byteArrayOf(0x12, 0x34)) + ) + ) + + //unpack + Struct3Retire("3B").unpack(ByteArrayInputStream(Helper.fromHexString("123400"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals("123400", Helper.toHexString(it[0] as UByteArray)) + } + Struct3Retire("BBB").unpack(ByteArrayInputStream(Helper.fromHexString("123400"))).let { + Assert.assertEquals(3, it.size) + Assert.assertEquals("12", Helper.toHexString(it[0] as UByteArray)) + Assert.assertEquals("34", Helper.toHexString(it[1] as UByteArray)) + Assert.assertEquals("00", Helper.toHexString(it[2] as UByteArray)) + } + } + + //s + @Test + fun stringTest() { + //constructor + Struct3Retire("s") + + //calcSize + Assert.assertEquals(3, Struct3Retire("3s").calcSize()) + + //pack + Struct3Retire("3s").pack("a") + Struct3Retire("3s").pack("abc") + try { + Struct3Retire("3s").pack("abcd") + Assert.fail("should throw exception here") + } catch (e: Throwable) { + Assert.assertTrue(e.toString(), e is AssertionError || e is IllegalArgumentException) + } + + //unpack + Struct3Retire("3s").unpack(ByteArrayInputStream(Helper.fromHexString("616263"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals("abc", it[0]) + } + Struct3Retire("3s").unpack(ByteArrayInputStream(Helper.fromHexString("610000"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals("a", it[0]) + } + } + + //h + @Test + fun shortTest() { + //constructor + Struct3Retire("h") + + //calcSize + Assert.assertEquals(6, Struct3Retire("3h").calcSize()) + + //pack + Assert.assertEquals("ff7f", Helper.toHexString(Struct3Retire("h").pack(0x7fff))) + Assert.assertEquals("0080", Helper.toHexString(Struct3Retire("h").pack(-0x8000))) + Assert.assertEquals("7fff0000", Helper.toHexString(Struct3Retire(">2h").pack(0x7fff, 0))) + + //unpack + Struct3Retire(">2h").unpack(ByteArrayInputStream(Helper.fromHexString("7fff0000"))).let { + Assert.assertEquals(2, it.size) + Assert.assertEquals(0x7fff.toShort(), it[0]) + Assert.assertEquals(0.toShort(), it[1]) + } + } + + //H + @Test + fun uShortTest() { + //constructor + Struct3Retire("H") + + //calcSize + Assert.assertEquals(6, Struct3Retire("3H").calcSize()) + + //pack + Assert.assertEquals("0100", Helper.toHexString(Struct3Retire("H").pack((1U).toUShort()))) + Assert.assertEquals("0100", Helper.toHexString(Struct3Retire("H").pack(1U))) + Assert.assertEquals("ffff", Helper.toHexString(Struct3Retire("H").pack(65535U))) + Assert.assertEquals("ffff", Helper.toHexString(Struct3Retire("H").pack(65535))) + try { + Struct3Retire("H").pack(-1) + Assert.fail("should throw exception here") + } catch (e: Throwable) { + Assert.assertTrue(e is AssertionError || e is IllegalArgumentException) + } + //unpack + Struct3Retire("H").unpack(ByteArrayInputStream(Helper.fromHexString("ffff"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(65535U.toUShort(), it[0]) + } + } + + //i, l + @Test + fun intTest() { + //constructor + Struct3Retire("i") + Struct3Retire("l") + + //calcSize + Assert.assertEquals(12, Struct3Retire("3i").calcSize()) + Assert.assertEquals(12, Struct3Retire("3l").calcSize()) + + //pack + Struct3Retire("i").pack(65535 + 1) + Struct3Retire("i").pack(-1) + //unpack + Struct3Retire("i").unpack(ByteArrayInputStream(Helper.fromHexString("00000100"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(65536, it[0]) + } + Struct3Retire("i").unpack(ByteArrayInputStream(Helper.fromHexString("ffffffff"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(-1, it[0]) + } + } + + //I, L + @Test + fun uIntTest() { + //constructor + Struct3Retire("I") + Struct3Retire("L") + + //calcSize + Assert.assertEquals(12, Struct3Retire("3I").calcSize()) + Assert.assertEquals(12, Struct3Retire("3L").calcSize()) + + //pack + Assert.assertEquals( + "01000000", Helper.toHexString( + Struct3Retire("I").pack(1U) + ) + ) + Assert.assertEquals( + "80000000", Helper.toHexString( + Struct3Retire(">I").pack(Int.MAX_VALUE.toUInt() + 1U) + ) + ) + //unpack + Struct3Retire("I").unpack(ByteArrayInputStream(Helper.fromHexString("01000000"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(1U, it[0]) + } + Struct3Retire(">I").unpack(ByteArrayInputStream(Helper.fromHexString("80000000"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(Int.MAX_VALUE.toUInt() + 1U, it[0]) + } + } + + //q: Long + @Test + fun longTest() { + //constructor + Struct3Retire("q") + + //calcSize + Assert.assertEquals(24, Struct3Retire("3q").calcSize()) + + //pack + Assert.assertEquals( + "8000000000000000", Helper.toHexString( + Struct3Retire(">q").pack(Long.MIN_VALUE) + ) + ) + Assert.assertEquals( + "7fffffffffffffff", Helper.toHexString( + Struct3Retire(">q").pack(Long.MAX_VALUE) + ) + ) + Assert.assertEquals( + "ffffffffffffffff", Helper.toHexString( + Struct3Retire(">q").pack(-1L) + ) + ) + //unpack + Struct3Retire(">q").unpack(ByteArrayInputStream(Helper.fromHexString("8000000000000000"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(Long.MIN_VALUE, it[0]) + } + Struct3Retire(">q").unpack(ByteArrayInputStream(Helper.fromHexString("7fffffffffffffff"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(Long.MAX_VALUE, it[0]) + } + Struct3Retire(">q").unpack(ByteArrayInputStream(Helper.fromHexString("ffffffffffffffff"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(-1L, it[0]) + } + } + + //Q: ULong + @Test + fun uLongTest() { + //constructor + Struct3Retire("Q") + + //calcSize + Assert.assertEquals(24, Struct3Retire("3Q").calcSize()) + + //pack + Assert.assertEquals( + "7fffffffffffffff", Helper.toHexString( + Struct3Retire(">Q").pack(Long.MAX_VALUE) + ) + ) + Assert.assertEquals( + "0000000000000000", Helper.toHexString( + Struct3Retire(">Q").pack(ULong.MIN_VALUE) + ) + ) + Assert.assertEquals( + "ffffffffffffffff", Helper.toHexString( + Struct3Retire(">Q").pack(ULong.MAX_VALUE) + ) + ) + try { + Struct3Retire(">Q").pack(-1L) + } catch (e: Throwable) { + Assert.assertTrue(e is AssertionError || e is IllegalArgumentException) + } + //unpack + Struct3Retire(">Q").unpack(ByteArrayInputStream(Helper.fromHexString("7fffffffffffffff"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(Long.MAX_VALUE.toULong(), it[0]) + } + Struct3Retire(">Q").unpack(ByteArrayInputStream(Helper.fromHexString("0000000000000000"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(ULong.MIN_VALUE, it[0]) + } + Struct3Retire(">Q").unpack(ByteArrayInputStream(Helper.fromHexString("ffffffffffffffff"))).let { + Assert.assertEquals(1, it.size) + Assert.assertEquals(ULong.MAX_VALUE, it[0]) + } + } + + @Test + fun legacyTest() { + Assert.assertTrue( + Struct3Retire("<2i4b4b").pack( + 1, 7321, byteArrayOf(1, 2, 3, 4), byteArrayOf(200.toByte(), 201.toByte(), 202.toByte(), 203.toByte()) + ) + .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb")) + ) + Assert.assertTrue( + Struct3Retire("<2i4b4B").pack( + 1, 7321, byteArrayOf(1, 2, 3, 4), intArrayOf(200, 201, 202, 203) + ) + .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb")) + ) + + Assert.assertTrue(Struct3Retire("b2x").pack(byteArrayOf(0x13), null).contentEquals(Helper.fromHexString("130000"))) + Assert.assertTrue( + Struct3Retire("b2xi").pack(byteArrayOf(0x13), null, 55).contentEquals(Helper.fromHexString("13000037000000")) + ) + + Struct3Retire("5s").pack("Good").contentEquals(Helper.fromHexString("476f6f6400")) + Struct3Retire("5s1b").pack("Good", byteArrayOf(13)).contentEquals(Helper.fromHexString("476f6f64000d")) + } + + + @Test + fun legacyIntegerLE() { + //int (4B) + Assert.assertTrue(Struct3Retire("<2i").pack(1, 7321).contentEquals(Helper.fromHexString("01000000991c0000"))) + val ret = Struct3Retire("<2i").unpack(ByteArrayInputStream(Helper.fromHexString("01000000991c0000"))) + Assert.assertEquals(2, ret.size) + Assert.assertTrue(ret[0] is Int) + Assert.assertTrue(ret[1] is Int) + Assert.assertEquals(1, ret[0] as Int) + Assert.assertEquals(7321, ret[1] as Int) + + //unsigned int (4B) + Assert.assertTrue(Struct3Retire("2i").pack(1, 7321).contentEquals(Helper.fromHexString("0000000100001c99"))) + val ret = Struct3Retire(">2i").unpack(ByteArrayInputStream(Helper.fromHexString("0000000100001c99"))) + Assert.assertEquals(1, ret[0] as Int) + Assert.assertEquals(7321, ret[1] as Int) + } + + run { + Assert.assertTrue(Struct3Retire("!i").pack(-333).contentEquals(Helper.fromHexString("fffffeb3"))) + val ret2 = Struct3Retire("!i").unpack(ByteArrayInputStream(Helper.fromHexString("fffffeb3"))) + Assert.assertEquals(-333, ret2[0] as Int) + } + } +} diff --git a/helper/src/test/kotlin/cfig/io/Struct3Test.kt b/helper/src/test/kotlin/cfig/io/Struct3Test.kt index dcbceba..9b180b8 100644 --- a/helper/src/test/kotlin/cfig/io/Struct3Test.kt +++ b/helper/src/test/kotlin/cfig/io/Struct3Test.kt @@ -1,68 +1,51 @@ -// Copyright 2021 yuyezhong@gmail.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +package cfig.io import cfig.helper.Helper -import cfig.io.Struct3 -import com.fasterxml.jackson.databind.ObjectMapper import org.junit.Assert import org.junit.Test import java.io.ByteArrayInputStream -@OptIn(kotlin.ExperimentalUnsignedTypes::class) class Struct3Test { - private fun getConvertedFormats(inStruct: Struct3): ArrayList> { - val f = inStruct.javaClass.getDeclaredField("formats") - f.isAccessible = true - val formatDumps = arrayListOf>() - (f.get(inStruct) as ArrayList<*>).apply { - this.forEach { - @Suppress("UNCHECKED_CAST") - val format = it as Array - val k = if (format[0].toString().indexOf(" ") > 0) { - format[0].toString().split(" ")[1] - } else { - format[0].toString() - } - formatDumps.add(mapOf(k to (format[1] as Int))) - } - } - return formatDumps - } - private fun constructorTestFun1(inFormatString: String) { - println(ObjectMapper().writeValueAsString(getConvertedFormats(Struct3(inFormatString)))) + println(Struct3(inFormatString)) } @Test fun constructorTest() { constructorTestFun1("3s") - constructorTestFun1("5b") constructorTestFun1("5x") + constructorTestFun1("5b") + constructorTestFun1("5B") + constructorTestFun1("2c") + constructorTestFun1("1h") + constructorTestFun1("1H") + constructorTestFun1("1i") + constructorTestFun1("3I") + constructorTestFun1("3q") + constructorTestFun1("3Q") + constructorTestFun1(">2b202x1b19B") + constructorTestFun1("<2b2x1b3i2q") } @Test fun calcSizeTest() { Assert.assertEquals(3, Struct3("3s").calcSize()) - Assert.assertEquals(5, Struct3("5b").calcSize()) Assert.assertEquals(5, Struct3("5x").calcSize()) + Assert.assertEquals(5, Struct3("5b").calcSize()) + Assert.assertEquals(5, Struct3("5B").calcSize()) + Assert.assertEquals(9, Struct3("9c").calcSize()) + Assert.assertEquals(8, Struct3("2i").calcSize()) + Assert.assertEquals(8, Struct3("2I").calcSize()) + Assert.assertEquals(24, Struct3("3q").calcSize()) + Assert.assertEquals(24, Struct3("3Q").calcSize()) } @Test fun toStringTest() { println(Struct3("!4s2L2QL11QL4x47sx80x")) + println(Struct3("@4s2L2QL11QL4x47sx80x")) } //x @@ -116,12 +99,18 @@ class Struct3Test { } //pack legal - Assert.assertEquals("61", - Helper.toHexString(Struct3("!c").pack('a'))) - Assert.assertEquals("61", - Helper.toHexString(Struct3("c").pack('a'))) - Assert.assertEquals("616263", - Helper.toHexString(Struct3("3c").pack('a', 'b', 'c'))) + Assert.assertEquals( + "61", + Helper.toHexString(Struct3("!c").pack('a')) + ) + Assert.assertEquals( + "61", + Helper.toHexString(Struct3("c").pack('a')) + ) + Assert.assertEquals( + "616263", + Helper.toHexString(Struct3("3c").pack('a', 'b', 'c')) + ) //unpack Struct3("3c").unpack(ByteArrayInputStream(Helper.fromHexString("616263"))).let { @@ -142,12 +131,21 @@ class Struct3Test { Assert.assertEquals(3, Struct3("3b").calcSize()) //pack - Assert.assertEquals("123456", Helper.toHexString( - Struct3("3b").pack(byteArrayOf(0x12, 0x34, 0x56)))) - Assert.assertEquals("123456", Helper.toHexString( - Struct3("!3b").pack(byteArrayOf(0x12, 0x34, 0x56)))) - Assert.assertEquals("123400", Helper.toHexString( - Struct3("3b").pack(byteArrayOf(0x12, 0x34)))) + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3("3b").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3("!3b").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123400", Helper.toHexString( + Struct3("3b").pack(byteArrayOf(0x12, 0x34)) + ) + ) //unpack Struct3("3b").unpack(ByteArrayInputStream(Helper.fromHexString("123400"))).let { @@ -172,12 +170,21 @@ class Struct3Test { Assert.assertEquals(3, Struct3("3B").calcSize()) //pack - Assert.assertEquals("123456", Helper.toHexString( - Struct3("3B").pack(byteArrayOf(0x12, 0x34, 0x56)))) - Assert.assertEquals("123456", Helper.toHexString( - Struct3("!3B").pack(byteArrayOf(0x12, 0x34, 0x56)))) - Assert.assertEquals("123400", Helper.toHexString( - Struct3("3B").pack(byteArrayOf(0x12, 0x34)))) + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3("3B").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123456", Helper.toHexString( + Struct3("!3B").pack(byteArrayOf(0x12, 0x34, 0x56)) + ) + ) + Assert.assertEquals( + "123400", Helper.toHexString( + Struct3("3B").pack(byteArrayOf(0x12, 0x34)) + ) + ) //unpack Struct3("3B").unpack(ByteArrayInputStream(Helper.fromHexString("123400"))).let { @@ -308,10 +315,16 @@ class Struct3Test { Assert.assertEquals(12, Struct3("3L").calcSize()) //pack - Assert.assertEquals("01000000", Helper.toHexString( - Struct3("I").pack(1U))) - Assert.assertEquals("80000000", Helper.toHexString( - Struct3(">I").pack(Int.MAX_VALUE.toUInt() + 1U))) + Assert.assertEquals( + "01000000", Helper.toHexString( + Struct3("I").pack(1U) + ) + ) + Assert.assertEquals( + "80000000", Helper.toHexString( + Struct3(">I").pack(Int.MAX_VALUE.toUInt() + 1U) + ) + ) //unpack Struct3("I").unpack(ByteArrayInputStream(Helper.fromHexString("01000000"))).let { Assert.assertEquals(1, it.size) @@ -333,12 +346,21 @@ class Struct3Test { Assert.assertEquals(24, Struct3("3q").calcSize()) //pack - Assert.assertEquals("8000000000000000", Helper.toHexString( - Struct3(">q").pack(Long.MIN_VALUE))) - Assert.assertEquals("7fffffffffffffff", Helper.toHexString( - Struct3(">q").pack(Long.MAX_VALUE))) - Assert.assertEquals("ffffffffffffffff", Helper.toHexString( - Struct3(">q").pack(-1L))) + Assert.assertEquals( + "8000000000000000", Helper.toHexString( + Struct3(">q").pack(Long.MIN_VALUE) + ) + ) + Assert.assertEquals( + "7fffffffffffffff", Helper.toHexString( + Struct3(">q").pack(Long.MAX_VALUE) + ) + ) + Assert.assertEquals( + "ffffffffffffffff", Helper.toHexString( + Struct3(">q").pack(-1L) + ) + ) //unpack Struct3(">q").unpack(ByteArrayInputStream(Helper.fromHexString("8000000000000000"))).let { Assert.assertEquals(1, it.size) @@ -364,12 +386,21 @@ class Struct3Test { Assert.assertEquals(24, Struct3("3Q").calcSize()) //pack - Assert.assertEquals("7fffffffffffffff", Helper.toHexString( - Struct3(">Q").pack(Long.MAX_VALUE))) - Assert.assertEquals("0000000000000000", Helper.toHexString( - Struct3(">Q").pack(ULong.MIN_VALUE))) - Assert.assertEquals("ffffffffffffffff", Helper.toHexString( - Struct3(">Q").pack(ULong.MAX_VALUE))) + Assert.assertEquals( + "7fffffffffffffff", Helper.toHexString( + Struct3(">Q").pack(Long.MAX_VALUE) + ) + ) + Assert.assertEquals( + "0000000000000000", Helper.toHexString( + Struct3(">Q").pack(ULong.MIN_VALUE) + ) + ) + Assert.assertEquals( + "ffffffffffffffff", Helper.toHexString( + Struct3(">Q").pack(ULong.MAX_VALUE) + ) + ) try { Struct3(">Q").pack(-1L) } catch (e: Throwable) { @@ -392,15 +423,23 @@ class Struct3Test { @Test fun legacyTest() { - Assert.assertTrue(Struct3("<2i4b4b").pack( - 1, 7321, byteArrayOf(1, 2, 3, 4), byteArrayOf(200.toByte(), 201.toByte(), 202.toByte(), 203.toByte())) - .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb"))) - Assert.assertTrue(Struct3("<2i4b4B").pack( - 1, 7321, byteArrayOf(1, 2, 3, 4), intArrayOf(200, 201, 202, 203)) - .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb"))) + Assert.assertTrue( + Struct3("<2i4b4b").pack( + 1, 7321, byteArrayOf(1, 2, 3, 4), byteArrayOf(200.toByte(), 201.toByte(), 202.toByte(), 203.toByte()) + ) + .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb")) + ) + Assert.assertTrue( + Struct3("<2i4b4B").pack( + 1, 7321, byteArrayOf(1, 2, 3, 4), intArrayOf(200, 201, 202, 203) + ) + .contentEquals(Helper.fromHexString("01000000991c000001020304c8c9cacb")) + ) Assert.assertTrue(Struct3("b2x").pack(byteArrayOf(0x13), null).contentEquals(Helper.fromHexString("130000"))) - Assert.assertTrue(Struct3("b2xi").pack(byteArrayOf(0x13), null, 55).contentEquals(Helper.fromHexString("13000037000000"))) + Assert.assertTrue( + Struct3("b2xi").pack(byteArrayOf(0x13), null, 55).contentEquals(Helper.fromHexString("13000037000000")) + ) Struct3("5s").pack("Good").contentEquals(Helper.fromHexString("476f6f6400")) Struct3("5s1b").pack("Good", byteArrayOf(13)).contentEquals(Helper.fromHexString("476f6f64000d"))