Struct3: retire legacy Struct3

pull/94/head
cfig 3 years ago
parent 8bf807b663
commit 41b216e840
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -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)
}
}

@ -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
}

@ -0,0 +1,29 @@
package init
class BootReason {
/*
Canonical boot reason format
<reason>,<subreason>,<detail>
*/
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<String>().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

@ -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)

@ -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)
}
}

@ -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())

@ -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<Array<Any?>>()
private val warships = ArrayList<IWarShip<*>>()
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<Any?>(typeName, multiple))
} else {
for (i in 0 until multiple) {
formats.add(arrayOf<Any?>(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<String>()
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<IWarShip<*>> {
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<T> {
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<T> : IWarShip<T> {
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<Short> {
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<UShort> {
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<Any>()
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<Int> {
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<Byte>()
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<UInt> {
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<UByte>()
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<Byte>()
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<Long> {
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<ULong> {
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<UByte>()
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<Char> {
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<T> : IWarShip<T> {
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<Byte>()
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<UByte>()
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<Byte>()
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<Byte> {
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<ByteArray> {
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<UByteArray> {
override fun get(stream: InputStream, byteOrder: ByteOrder): UByteArray {
val data = ByteArray(multiple)
check(multiple == stream.read(data))
val innerData2 = mutableListOf<UByte>()
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<String> {
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)
}
}

@ -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<Array<Any?>>()
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<Any?>(typeName, multiple))
} else {
for (i in 0 until multiple) {
formats.add(arrayOf<Any?>(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<String>()
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<Any>()
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<Byte>()
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<UByte>()
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<Byte>()
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<UByte>()
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
}

@ -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<Map<String, Int>> {
val f = inStruct.javaClass.getDeclaredField("formats")
f.isAccessible = true
val formatDumps = arrayListOf<Map<String, Int>>()
(f.get(inStruct) as ArrayList<*>).apply {
this.forEach {
@Suppress("UNCHECKED_CAST")
val format = it as Array<Any>
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 1101ie. 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("<I").pack(2L).contentEquals(Helper.fromHexString("02000000")))
Assert.assertTrue(Struct3Retire("<I").pack(2).contentEquals(Helper.fromHexString("02000000")))
//greater than Int.MAX_VALUE
Assert.assertTrue(Struct3Retire("<I").pack(2147483748L).contentEquals(Helper.fromHexString("64000080")))
Assert.assertTrue(Struct3Retire("<I").pack(2147483748).contentEquals(Helper.fromHexString("64000080")))
try {
Struct3Retire("<I").pack(-12)
throw Exception("should not reach here")
} catch (e: Throwable) {
Assert.assertTrue(e is AssertionError || e is IllegalArgumentException)
}
//negative int
Assert.assertTrue(Struct3Retire("<i").pack(-333).contentEquals(Helper.fromHexString("b3feffff")))
}
@Test
fun legacyIntegerBE() {
run {
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)
}
}
}

@ -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<Map<String, Int>> {
val f = inStruct.javaClass.getDeclaredField("formats")
f.isAccessible = true
val formatDumps = arrayListOf<Map<String, Int>>()
(f.get(inStruct) as ArrayList<*>).apply {
this.forEach {
@Suppress("UNCHECKED_CAST")
val format = it as Array<Any>
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"))

Loading…
Cancel
Save