Issue #55: fully support boot V4

v3 -> v4 changelog:
 - boot signature for GKI
pull/66/head
cfig 5 years ago
parent b274b358e5
commit b8dd19be1e
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -14,15 +14,198 @@
package avb package avb
import avb.alg.Algorithms
import avb.blob.AuthBlob import avb.blob.AuthBlob
import avb.blob.AuxBlob import avb.blob.AuxBlob
import avb.blob.Footer import avb.blob.Footer
import avb.blob.Header import avb.blob.Header
import avb.desc.*
import cfig.Avb
import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
/* /*
a wonderfaul base64 encoder/decoder: https://cryptii.com/base64-to-hex a wonderfaul base64 encoder/decoder: https://cryptii.com/base64-to-hex
*/ */
class AVBInfo(var header: Header? = null, class AVBInfo(
var authBlob: AuthBlob? = null, var header: Header? = null,
var auxBlob: AuxBlob? = null, var authBlob: AuthBlob? = null,
var footer: Footer? = null) var auxBlob: AuxBlob? = null,
var footer: Footer? = null,
) {
fun encode(): ByteArray {
val alg = Algorithms.get(header!!.algorithm_type)!!
//3 - whole aux blob
val newAuxBlob = auxBlob?.encode(alg) ?: byteArrayOf()
//1 - whole header blob
val headerBlob = this.header!!.apply {
auxiliary_data_block_size = newAuxBlob.size.toLong()
authentication_data_block_size = Helper.round_to_multiple(
(alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64
)
descriptors_offset = 0
descriptors_size = auxBlob?.descriptorSize?.toLong() ?: 0
hash_offset = 0
hash_size = alg.hash_num_bytes.toLong()
signature_offset = alg.hash_num_bytes.toLong()
signature_size = alg.signature_num_bytes.toLong()
public_key_offset = descriptors_size
public_key_size = AuxBlob.encodePubKey(alg).size.toLong()
public_key_metadata_size = auxBlob!!.pubkeyMeta?.pkmd?.size?.toLong() ?: 0L
public_key_metadata_offset = public_key_offset + public_key_size
log.info("pkmd size: $public_key_metadata_size, pkmd offset : $public_key_metadata_offset")
}.encode()
//2 - auth blob
val authBlob = AuthBlob.createBlob(headerBlob, newAuxBlob, alg.name)
val ret = Helper.join(headerBlob, authBlob, newAuxBlob)
//Helper.dumpToFile("_debug_vbmeta_", ret)
return ret
}
fun encodePadded(): ByteArray {
return encode().paddingWith(Avb.BLOCK_SIZE.toUInt())
}
fun dumpDefault(imageFile: String): AVBInfo {
val jsonFile = Avb.getJsonFileName(imageFile)
mapper.writerWithDefaultPrettyPrinter().writeValue(File(jsonFile), this)
log.info("VBMeta: $imageFile -> $jsonFile")
return this
}
companion object {
private val log = LoggerFactory.getLogger(AVBInfo::class.java)
private val mapper = ObjectMapper()
fun parseFrom(imageFile: String): AVBInfo {
log.info("parseFrom($imageFile) ...")
var footer: Footer? = null
var vbMetaOffset: Long = 0
// footer
FileInputStream(imageFile).use { fis ->
fis.skip(File(imageFile).length() - Footer.SIZE)
try {
footer = Footer(fis)
vbMetaOffset = footer!!.vbMetaOffset
log.info("$imageFile: $footer")
} catch (e: IllegalArgumentException) {
log.info("image $imageFile has no AVB Footer")
}
}
// header
val rawHeaderBlob = ByteArray(Header.SIZE).apply {
FileInputStream(imageFile).use { fis ->
fis.skip(vbMetaOffset)
fis.read(this)
}
}
val vbMetaHeader = Header(ByteArrayInputStream(rawHeaderBlob))
log.debug(vbMetaHeader.toString())
log.debug(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader))
val authBlockOffset = vbMetaOffset + Header.SIZE
val auxBlockOffset = authBlockOffset + vbMetaHeader.authentication_data_block_size
val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer)
// Auth blob
if (vbMetaHeader.authentication_data_block_size > 0) {
FileInputStream(imageFile).use { fis ->
fis.skip(vbMetaOffset)
fis.skip(Header.SIZE.toLong())
fis.skip(vbMetaHeader.hash_offset)
val ba = ByteArray(vbMetaHeader.hash_size.toInt())
fis.read(ba)
log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba))
val bb = ByteArray(vbMetaHeader.signature_size.toInt())
fis.read(bb)
log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb))
ai.authBlob = AuthBlob()
ai.authBlob!!.offset = authBlockOffset
ai.authBlob!!.size = vbMetaHeader.authentication_data_block_size
ai.authBlob!!.hash = Hex.encodeHexString(ba)
ai.authBlob!!.signature = Hex.encodeHexString(bb)
}
}
// aux
val rawAuxBlob = ByteArray(vbMetaHeader.auxiliary_data_block_size.toInt()).apply {
FileInputStream(imageFile).use { fis ->
fis.skip(auxBlockOffset)
fis.read(this)
}
}
// aux - desc
var descriptors: List<Any>
if (vbMetaHeader.descriptors_size > 0) {
ByteArrayInputStream(rawAuxBlob).use { bis ->
bis.skip(vbMetaHeader.descriptors_offset)
descriptors = UnknownDescriptor.parseDescriptors2(bis, vbMetaHeader.descriptors_size)
}
descriptors.forEach {
log.debug(it.toString())
when (it) {
is PropertyDescriptor -> {
ai.auxBlob!!.propertyDescriptors.add(it)
}
is HashDescriptor -> {
ai.auxBlob!!.hashDescriptors.add(it)
}
is KernelCmdlineDescriptor -> {
ai.auxBlob!!.kernelCmdlineDescriptors.add(it)
}
is HashTreeDescriptor -> {
ai.auxBlob!!.hashTreeDescriptors.add(it)
}
is ChainPartitionDescriptor -> {
ai.auxBlob!!.chainPartitionDescriptors.add(it)
}
is UnknownDescriptor -> {
ai.auxBlob!!.unknownDescriptors.add(it)
}
else -> {
throw IllegalArgumentException("invalid descriptor: $it")
}
}
}
}
// aux - pubkey
if (vbMetaHeader.public_key_size > 0) {
ai.auxBlob!!.pubkey = AuxBlob.PubKeyInfo()
ai.auxBlob!!.pubkey!!.offset = vbMetaHeader.public_key_offset
ai.auxBlob!!.pubkey!!.size = vbMetaHeader.public_key_size
ByteArrayInputStream(rawAuxBlob).use { bis ->
bis.skip(vbMetaHeader.public_key_offset)
ai.auxBlob!!.pubkey!!.pubkey = ByteArray(vbMetaHeader.public_key_size.toInt())
bis.read(ai.auxBlob!!.pubkey!!.pubkey)
log.debug("Parsed Pub Key: " + Hex.encodeHexString(ai.auxBlob!!.pubkey!!.pubkey))
}
}
// aux - pkmd
if (vbMetaHeader.public_key_metadata_size > 0) {
ai.auxBlob!!.pubkeyMeta = AuxBlob.PubKeyMetadataInfo()
ai.auxBlob!!.pubkeyMeta!!.offset = vbMetaHeader.public_key_metadata_offset
ai.auxBlob!!.pubkeyMeta!!.size = vbMetaHeader.public_key_metadata_size
ByteArrayInputStream(rawAuxBlob).use { bis ->
bis.skip(vbMetaHeader.public_key_metadata_offset)
ai.auxBlob!!.pubkeyMeta!!.pkmd = ByteArray(vbMetaHeader.public_key_metadata_size.toInt())
bis.read(ai.auxBlob!!.pubkeyMeta!!.pkmd)
log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd))
}
}
log.debug("vbmeta info of [$imageFile] has been analyzed")
return ai
}
}
}

@ -25,13 +25,11 @@ import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith import cfig.helper.Helper.Companion.paddingWith
import cfig.helper.KeyHelper import cfig.helper.KeyHelper
import cfig.helper.KeyHelper2 import cfig.helper.KeyHelper2
import cfig.io.Struct3
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.apache.commons.exec.CommandLine import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
@ -42,12 +40,10 @@ import java.nio.file.StandardOpenOption
class Avb { class Avb {
private val MAX_VBMETA_SIZE = 64 * 1024 private val MAX_VBMETA_SIZE = 64 * 1024
private val MAX_FOOTER_SIZE = 4096 private val MAX_FOOTER_SIZE = 4096
private val BLOCK_SIZE = 4096
private val DEBUG = false
//migrated from: avbtool::Avb::addHashFooter //migrated from: avbtool::Avb::addHashFooter
fun addHashFooter( fun addHashFooter(
image_file: String, image_file: String, //file to be hashed and signed
partition_size: Long, //aligned by Avb::BLOCK_SIZE partition_size: Long, //aligned by Avb::BLOCK_SIZE
partition_name: String, partition_name: String,
newAvbInfo: AVBInfo newAvbInfo: AVBInfo
@ -77,18 +73,13 @@ class Avb {
this.auxBlob!!.hashDescriptors.add(hd) this.auxBlob!!.hashDescriptors.add(hd)
} }
val vbmetaBlob = packVbMeta(newAvbInfo)
log.debug("vbmeta_blob: " + Helper.toHexString(vbmetaBlob))
if (DEBUG) {
Helper.dumpToFile("hashDescriptor.vbmeta.blob", vbmetaBlob)
}
// image + padding // image + padding
val imgPaddingNeeded = Helper.round_to_multiple(newImageSize, BLOCK_SIZE) - newImageSize val imgPaddingNeeded = Helper.round_to_multiple(newImageSize, BLOCK_SIZE) - newImageSize
// + vbmeta + padding // + vbmeta + padding
val vbmetaBlob = newAvbInfo.encode()
val vbmetaOffset = newImageSize + imgPaddingNeeded val vbmetaOffset = newImageSize + imgPaddingNeeded
val vbmetaBlobWithPadding = vbmetaBlob.paddingWith(BLOCK_SIZE.toUInt()) val vbmetaBlobWithPadding = newAvbInfo.encodePadded()
// + DONT_CARE chunk // + DONT_CARE chunk
val vbmetaEndOffset = vbmetaOffset + vbmetaBlobWithPadding.size val vbmetaEndOffset = vbmetaOffset + vbmetaBlobWithPadding.size
@ -172,138 +163,6 @@ class Avb {
} }
} }
fun parseVbMeta(image_file: String, dumpFile: Boolean = true): AVBInfo {
log.info("parseVbMeta($image_file) ...")
val jsonFile = getJsonFileName(image_file)
var footer: Footer? = null
var vbMetaOffset: Long = 0
// footer
FileInputStream(image_file).use { fis ->
fis.skip(File(image_file).length() - Footer.SIZE)
try {
footer = Footer(fis)
vbMetaOffset = footer!!.vbMetaOffset
log.info("$image_file: $footer")
} catch (e: IllegalArgumentException) {
log.info("image $image_file has no AVB Footer")
}
}
// header
val rawHeaderBlob = ByteArray(Header.SIZE).apply {
FileInputStream(image_file).use { fis ->
fis.skip(vbMetaOffset)
fis.read(this)
}
}
val vbMetaHeader = Header(ByteArrayInputStream(rawHeaderBlob))
log.debug(vbMetaHeader.toString())
log.debug(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader))
val authBlockOffset = vbMetaOffset + Header.SIZE
val auxBlockOffset = authBlockOffset + vbMetaHeader.authentication_data_block_size
val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer)
// Auth blob
if (vbMetaHeader.authentication_data_block_size > 0) {
FileInputStream(image_file).use { fis ->
fis.skip(vbMetaOffset)
fis.skip(Header.SIZE.toLong())
fis.skip(vbMetaHeader.hash_offset)
val ba = ByteArray(vbMetaHeader.hash_size.toInt())
fis.read(ba)
log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba))
val bb = ByteArray(vbMetaHeader.signature_size.toInt())
fis.read(bb)
log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb))
ai.authBlob = AuthBlob()
ai.authBlob!!.offset = authBlockOffset
ai.authBlob!!.size = vbMetaHeader.authentication_data_block_size
ai.authBlob!!.hash = Hex.encodeHexString(ba)
ai.authBlob!!.signature = Hex.encodeHexString(bb)
}
}
// aux
val rawAuxBlob = ByteArray(vbMetaHeader.auxiliary_data_block_size.toInt()).apply {
FileInputStream(image_file).use { fis ->
fis.skip(auxBlockOffset)
fis.read(this)
}
}
// aux - desc
var descriptors: List<Any>
if (vbMetaHeader.descriptors_size > 0) {
ByteArrayInputStream(rawAuxBlob).use { bis ->
bis.skip(vbMetaHeader.descriptors_offset)
descriptors = UnknownDescriptor.parseDescriptors2(bis, vbMetaHeader.descriptors_size)
}
descriptors.forEach {
log.debug(it.toString())
when (it) {
is PropertyDescriptor -> {
ai.auxBlob!!.propertyDescriptors.add(it)
}
is HashDescriptor -> {
ai.auxBlob!!.hashDescriptors.add(it)
}
is KernelCmdlineDescriptor -> {
ai.auxBlob!!.kernelCmdlineDescriptors.add(it)
}
is HashTreeDescriptor -> {
ai.auxBlob!!.hashTreeDescriptors.add(it)
}
is ChainPartitionDescriptor -> {
ai.auxBlob!!.chainPartitionDescriptors.add(it)
}
is UnknownDescriptor -> {
ai.auxBlob!!.unknownDescriptors.add(it)
}
else -> {
throw IllegalArgumentException("invalid descriptor: $it")
}
}
}
}
// aux - pubkey
if (vbMetaHeader.public_key_size > 0) {
ai.auxBlob!!.pubkey = AuxBlob.PubKeyInfo()
ai.auxBlob!!.pubkey!!.offset = vbMetaHeader.public_key_offset
ai.auxBlob!!.pubkey!!.size = vbMetaHeader.public_key_size
ByteArrayInputStream(rawAuxBlob).use { bis ->
bis.skip(vbMetaHeader.public_key_offset)
ai.auxBlob!!.pubkey!!.pubkey = ByteArray(vbMetaHeader.public_key_size.toInt())
bis.read(ai.auxBlob!!.pubkey!!.pubkey)
log.debug("Parsed Pub Key: " + Hex.encodeHexString(ai.auxBlob!!.pubkey!!.pubkey))
}
}
// aux - pkmd
if (vbMetaHeader.public_key_metadata_size > 0) {
ai.auxBlob!!.pubkeyMeta = AuxBlob.PubKeyMetadataInfo()
ai.auxBlob!!.pubkeyMeta!!.offset = vbMetaHeader.public_key_metadata_offset
ai.auxBlob!!.pubkeyMeta!!.size = vbMetaHeader.public_key_metadata_size
ByteArrayInputStream(rawAuxBlob).use { bis ->
bis.skip(vbMetaHeader.public_key_metadata_offset)
ai.auxBlob!!.pubkeyMeta!!.pkmd = ByteArray(vbMetaHeader.public_key_metadata_size.toInt())
bis.read(ai.auxBlob!!.pubkeyMeta!!.pkmd)
log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd))
}
}
if (dumpFile) {
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(jsonFile), ai)
log.info("parseVbMeta($image_file) done. Result: $jsonFile")
} else {
log.debug("vbmeta info of [$image_file] has been analyzed, no dummping")
}
return ai
}
fun verify(ai: AVBInfo, image_file: String, parent: String = ""): Array<Any> { fun verify(ai: AVBInfo, image_file: String, parent: String = ""): Array<Any> {
val ret: Array<Any> = arrayOf(true, "") val ret: Array<Any> = arrayOf(true, "")
val localParent = if (parent.isEmpty()) image_file else parent val localParent = if (parent.isEmpty()) image_file else parent
@ -397,54 +256,9 @@ class Avb {
return ret return ret
} }
private fun packVbMeta(info: AVBInfo? = null, image_file: String? = null): ByteArray {
val ai = info ?: ObjectMapper().readValue(File(getJsonFileName(image_file!!)), AVBInfo::class.java)
val alg = Algorithms.get(ai.header!!.algorithm_type)!!
//3 - whole aux blob
val auxBlob = ai.auxBlob?.encode(alg) ?: byteArrayOf()
//1 - whole header blob
val headerBlob = ai.header!!.apply {
auxiliary_data_block_size = auxBlob.size.toLong()
authentication_data_block_size = Helper.round_to_multiple(
(alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64
)
descriptors_offset = 0
descriptors_size = ai.auxBlob?.descriptorSize?.toLong() ?: 0
hash_offset = 0
hash_size = alg.hash_num_bytes.toLong()
signature_offset = alg.hash_num_bytes.toLong()
signature_size = alg.signature_num_bytes.toLong()
public_key_offset = descriptors_size
public_key_size = AuxBlob.encodePubKey(alg).size.toLong()
public_key_metadata_size = ai.auxBlob!!.pubkeyMeta?.pkmd?.size?.toLong() ?: 0L
public_key_metadata_offset = public_key_offset + public_key_size
log.info("pkmd size: $public_key_metadata_size, pkmd offset : $public_key_metadata_offset")
}.encode()
//2 - auth blob
val authBlob = AuthBlob.createBlob(headerBlob, auxBlob, alg.name)
return Helper.join(headerBlob, authBlob, auxBlob)
}
fun packVbMetaWithPadding(image_file: String? = null, info: AVBInfo? = null) {
val rawBlob = packVbMeta(info, image_file)
val paddingSize = Helper.round_to_multiple(rawBlob.size.toLong(), BLOCK_SIZE) - rawBlob.size
val paddedBlob = Helper.join(rawBlob, Struct3("${paddingSize}x").pack(null))
log.info("raw vbmeta size ${rawBlob.size}, padding size $paddingSize, total blob size ${paddedBlob.size}")
log.info("Writing padded vbmeta to file: $image_file.signed")
Files.write(Paths.get("$image_file.signed"), paddedBlob, StandardOpenOption.CREATE)
}
companion object { companion object {
private val log = LoggerFactory.getLogger(Avb::class.java) private val log = LoggerFactory.getLogger(Avb::class.java)
const val BLOCK_SIZE = 4096
const val AVB_VERSION_MAJOR = 1 const val AVB_VERSION_MAJOR = 1
const val AVB_VERSION_MINOR = 1 const val AVB_VERSION_MINOR = 1
const val AVB_VERSION_SUB = 0 const val AVB_VERSION_SUB = 0
@ -483,7 +297,8 @@ class Avb {
ObjectMapper().readValue(File(getJsonFileName(fileName)), AVBInfo::class.java).let { ObjectMapper().readValue(File(getJsonFileName(fileName)), AVBInfo::class.java).let {
it.auxBlob!!.hashDescriptors.get(0).partition_name it.auxBlob!!.hashDescriptors.get(0).partition_name
} }
val newHashDesc = Avb().parseVbMeta("$fileName.signed", dumpFile = false) //read hashDescriptor from image
val newHashDesc = AVBInfo.parseFrom("$fileName.signed")
assert(newHashDesc.auxBlob!!.hashDescriptors.size == 1) assert(newHashDesc.auxBlob!!.hashDescriptors.size == 1)
var seq = -1 //means not found var seq = -1 //means not found
//main vbmeta //main vbmeta
@ -501,9 +316,11 @@ class Avb {
if (-1 == seq) { if (-1 == seq) {
log.warn("main vbmeta doesn't have $partitionName hashDescriptor, won't update vbmeta.img") log.warn("main vbmeta doesn't have $partitionName hashDescriptor, won't update vbmeta.img")
} else { } else {
//add hashDescriptor back to main vbmeta
val hd = newHashDesc.auxBlob!!.hashDescriptors.get(0).apply { this.sequence = seq } val hd = newHashDesc.auxBlob!!.hashDescriptors.get(0).apply { this.sequence = seq }
this.auxBlob!!.hashDescriptors.add(hd) this.auxBlob!!.hashDescriptors.add(hd)
Avb().packVbMetaWithPadding("vbmeta.img", this) log.info("Writing padded vbmeta to file: vbmeta.img.signed")
Files.write(Paths.get("vbmeta.img.signed"), encodePadded(), StandardOpenOption.CREATE)
log.info("Updating vbmeta.img side by side (partition=$partitionName, seq=$seq) done") log.info("Updating vbmeta.img side by side (partition=$partitionName, seq=$seq) done")
} }
} }

@ -14,6 +14,7 @@
package avb.desc package avb.desc
import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper import cfig.helper.Helper
import cfig.io.Struct3 import cfig.io.Struct3
@ -84,7 +85,7 @@ class ChainPartitionDescriptor(
val ret: Array<Any> = arrayOf(false, "file not found") val ret: Array<Any> = arrayOf(false, "file not found")
for (item in image_files) { for (item in image_files) {
if (File(item).exists()) { if (File(item).exists()) {
val subAi = Avb().parseVbMeta(item, false) val subAi = AVBInfo.parseFrom(item)
if (pubkey.contentEquals(subAi.auxBlob!!.pubkey!!.pubkey)) { if (pubkey.contentEquals(subAi.auxBlob!!.pubkey!!.pubkey)) {
log.info("VERIFY($parent): public key matches, PASS") log.info("VERIFY($parent): public key matches, PASS")
return Avb().verify(subAi, item, parent) return Avb().verify(subAi, item, parent)

@ -33,7 +33,7 @@ class Signer {
fun signAVB(output: String, imageSize: Long, avbtool: String) { fun signAVB(output: String, imageSize: Long, avbtool: String) {
log.info("Adding hash_footer with verified-boot 2.0 style") log.info("Adding hash_footer with verified-boot 2.0 style")
val ai = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java) val ai = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java)
val alg = Algorithms.get(ai.header!!.algorithm_type.toInt()) val alg = Algorithms.get(ai.header!!.algorithm_type)
val bootDesc = ai.auxBlob!!.hashDescriptors[0] val bootDesc = ai.auxBlob!!.hashDescriptors[0]
val newAvbInfo = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java) val newAvbInfo = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java)

@ -14,6 +14,7 @@
package cfig.bootimg.v2 package cfig.bootimg.v2
import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.EnvironmentVerifier import cfig.EnvironmentVerifier
import cfig.bootimg.Common import cfig.bootimg.Common
@ -38,7 +39,7 @@ data class BootV2(
var ramdisk: CommArgs = CommArgs(), var ramdisk: CommArgs = CommArgs(),
var secondBootloader: CommArgs? = null, var secondBootloader: CommArgs? = null,
var recoveryDtbo: CommArgsLong? = null, var recoveryDtbo: CommArgsLong? = null,
var dtb: CommArgsLong? = null var dtb: CommArgsLong? = null,
) { ) {
data class MiscInfo( data class MiscInfo(
var output: String = "", var output: String = "",
@ -54,21 +55,21 @@ data class BootV2(
var osPatchLevel: String? = null, var osPatchLevel: String? = null,
var hash: ByteArray? = byteArrayOf(), var hash: ByteArray? = byteArrayOf(),
var verify: String = "", var verify: String = "",
var imageSize: Long = 0 var imageSize: Long = 0,
) )
data class CommArgs( data class CommArgs(
var file: String? = null, var file: String? = null,
var position: Long = 0, var position: Long = 0,
var size: Int = 0, var size: Int = 0,
var loadOffset: Long = 0 var loadOffset: Long = 0,
) )
data class CommArgsLong( data class CommArgsLong(
var file: String? = null, var file: String? = null,
var position: Long = 0, var position: Long = 0,
var size: Int = 0, var size: Int = 0,
var loadOffset: Long = 0 var loadOffset: Long = 0,
) )
companion object { companion object {
@ -223,7 +224,7 @@ data class BootV2(
fun extractVBMeta(): BootV2 { fun extractVBMeta(): BootV2 {
if (this.info.verify.startsWith("VB2.0")) { if (this.info.verify.startsWith("VB2.0")) {
Avb().parseVbMeta(info.output) AVBInfo.parseFrom(info.output).dumpDefault(info.output)
if (File("vbmeta.img").exists()) { if (File("vbmeta.img").exists()) {
log.warn("Found vbmeta.img, parsing ...") log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img") VBMetaParser().unpack("vbmeta.img")

@ -14,6 +14,9 @@
package cfig.bootimg.v3 package cfig.bootimg.v3
import avb.AVBInfo
import avb.alg.Algorithms
import avb.blob.AuxBlob
import cfig.Avb import cfig.Avb
import cfig.EnvironmentVerifier import cfig.EnvironmentVerifier
import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.deleleIfExists
@ -37,10 +40,11 @@ data class BootV3(
var info: MiscInfo = MiscInfo(), var info: MiscInfo = MiscInfo(),
var kernel: CommArgs = CommArgs(), var kernel: CommArgs = CommArgs(),
val ramdisk: CommArgs = CommArgs(), val ramdisk: CommArgs = CommArgs(),
var bootSignature: CommArgs = CommArgs() var bootSignature: CommArgs = CommArgs(),
) { ) {
companion object { companion object {
private val log = LoggerFactory.getLogger(BootV3::class.java) private val log = LoggerFactory.getLogger(BootV3::class.java)
private val mapper = ObjectMapper()
private val workDir = Helper.prop("workDir") private val workDir = Helper.prop("workDir")
fun parse(fileName: String): BootV3 { fun parse(fileName: String): BootV3 {
@ -89,13 +93,13 @@ data class BootV3(
var osVersion: String = "", var osVersion: String = "",
var osPatchLevel: String = "", var osPatchLevel: String = "",
var imageSize: Long = 0, var imageSize: Long = 0,
var signatureSize: Int = 0 var signatureSize: Int = 0,
) )
data class CommArgs( data class CommArgs(
var file: String = "", var file: String = "",
var position: Int = 0, var position: Int = 0,
var size: Int = 0 var size: Int = 0,
) )
fun pack(): BootV3 { fun pack(): BootV3 {
@ -129,11 +133,31 @@ data class BootV3(
bf.order(ByteOrder.LITTLE_ENDIAN) bf.order(ByteOrder.LITTLE_ENDIAN)
C.writePaddedFile(bf, this.kernel.file, this.info.pageSize) C.writePaddedFile(bf, this.kernel.file, this.info.pageSize)
C.writePaddedFile(bf, this.ramdisk.file, this.info.pageSize) C.writePaddedFile(bf, this.ramdisk.file, this.info.pageSize)
//write //write V3 data
FileOutputStream("${this.info.output}.clear", true).use { fos -> FileOutputStream("${this.info.output}.clear", true).use { fos ->
fos.write(bf.array(), 0, bf.position()) fos.write(bf.array(), 0, bf.position())
} }
//write V4 boot sig
if (this.info.headerVersion > 3) {
val bootSigJson = File(Avb.getJsonFileName(this.bootSignature.file))
var bootSigBytes = ByteArray(this.bootSignature.size)
if (bootSigJson.exists()) {
log.warn("V4 BootImage has GKI boot signature")
val readBackBootSig = mapper.readValue(bootSigJson, AVBInfo::class.java)
val alg = Algorithms.get(readBackBootSig.header!!.algorithm_type)!!
//replace new pub key
readBackBootSig.auxBlob!!.pubkey!!.pubkey = AuxBlob.encodePubKey(alg)
//update hash and sig
readBackBootSig.auxBlob!!.hashDescriptors.get(0).update(this.info.output + ".clear")
bootSigBytes = readBackBootSig.encodePadded()
}
//write V4 data
FileOutputStream("${this.info.output}.clear", true).use { fos ->
fos.write(bootSigBytes)
}
}
//google way //google way
this.toCommandLine().addArgument(this.info.output + ".google").let { this.toCommandLine().addArgument(this.info.output + ".google").let {
log.info(it.toString()) log.info(it.toString())
@ -161,14 +185,15 @@ data class BootV3(
osVersion = info.osVersion, osVersion = info.osVersion,
osPatchLevel = info.osPatchLevel, osPatchLevel = info.osPatchLevel,
headerSize = info.headerSize, headerSize = info.headerSize,
cmdline = info.cmdline cmdline = info.cmdline,
signatureSize = info.signatureSize
) )
} }
fun extractImages(): BootV3 { fun extractImages(): BootV3 {
val workDir = Helper.prop("workDir") val workDir = Helper.prop("workDir")
//info //info
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this)
//kernel //kernel
C.dumpKernel(Helper.Slice(info.output, kernel.position, kernel.size, kernel.file)) C.dumpKernel(Helper.Slice(info.output, kernel.position, kernel.size, kernel.file))
//ramdisk //ramdisk
@ -183,20 +208,20 @@ data class BootV3(
this.bootSignature.position.toLong(), this.bootSignature.size this.bootSignature.position.toLong(), this.bootSignature.size
) )
try { try {
Avb().parseVbMeta(this.bootSignature.file) AVBInfo.parseFrom(this.bootSignature.file).dumpDefault(this.bootSignature.file)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
log.warn("boot signature is invalid") log.warn("boot signature is invalid")
} }
} }
//dump info again //dump info again
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this)
return this return this
} }
fun extractVBMeta(): BootV3 { fun extractVBMeta(): BootV3 {
try { try {
Avb().parseVbMeta(info.output) AVBInfo.parseFrom(info.output).dumpDefault(info.output)
if (File("vbmeta.img").exists()) { if (File("vbmeta.img").exists()) {
log.warn("Found vbmeta.img, parsing ...") log.warn("Found vbmeta.img, parsing ...")
VBMetaParser().unpack("vbmeta.img") VBMetaParser().unpack("vbmeta.img")
@ -290,6 +315,12 @@ data class BootV3(
ret.addArgument(" --os_patch_level") ret.addArgument(" --os_patch_level")
ret.addArgument(info.osPatchLevel) ret.addArgument(info.osPatchLevel)
} }
if (this.bootSignature.size > 0 && File(Avb.getJsonFileName(this.bootSignature.file)).exists()) {
val origSig = mapper.readValue(File(Avb.getJsonFileName(this.bootSignature.file)), AVBInfo::class.java)
val alg = Algorithms.get(origSig.header!!.algorithm_type)!!
ret.addArgument("--gki_signing_algorithm").addArgument(alg.name)
ret.addArgument("--gki_signing_key").addArgument(alg.defaultKey)
}
ret.addArgument(" --id ") ret.addArgument(" --id ")
ret.addArgument(" --output ") ret.addArgument(" --output ")
//ret.addArgument("boot.img" + ".google") //ret.addArgument("boot.img" + ".google")

@ -14,6 +14,7 @@
package cfig.bootimg.v3 package cfig.bootimg.v3
import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.EnvironmentVerifier import cfig.EnvironmentVerifier
import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.deleleIfExists
@ -37,13 +38,13 @@ data class VendorBoot(
var ramdisk: CommArgs = CommArgs(), var ramdisk: CommArgs = CommArgs(),
var dtb: CommArgs = CommArgs(), var dtb: CommArgs = CommArgs(),
var ramdisk_table: Vrt = Vrt(), var ramdisk_table: Vrt = Vrt(),
var bootconfig: CommArgs = CommArgs() var bootconfig: CommArgs = CommArgs(),
) { ) {
data class CommArgs( data class CommArgs(
var file: String = "", var file: String = "",
var position: Long = 0, var position: Long = 0,
var size: Int = 0, var size: Int = 0,
var loadAddr: Long = 0 var loadAddr: Long = 0,
) )
data class MiscInfo( data class MiscInfo(
@ -56,7 +57,7 @@ data class VendorBoot(
var cmdline: String = "", var cmdline: String = "",
var tagsLoadAddr: Long = 0, var tagsLoadAddr: Long = 0,
var kernelLoadAddr: Long = 0, var kernelLoadAddr: Long = 0,
var imageSize: Long = 0 var imageSize: Long = 0,
) )
enum class VrtType { enum class VrtType {
@ -82,7 +83,7 @@ data class VendorBoot(
var size: Int = 0, var size: Int = 0,
var eachEntrySize: Int = 0, var eachEntrySize: Int = 0,
var position: Long = 0, var position: Long = 0,
var ramdidks: MutableList<VrtEntry> = mutableListOf() var ramdidks: MutableList<VrtEntry> = mutableListOf(),
) { ) {
fun update(): Vrt { fun update(): Vrt {
var totalSz = 0 var totalSz = 0
@ -113,11 +114,10 @@ data class VendorBoot(
var name: String = "", //32s var name: String = "", //32s
var boardId: ByteArray = byteArrayOf(), //16I (aka. 64 bytes) var boardId: ByteArray = byteArrayOf(), //16I (aka. 64 bytes)
var boardIdStr: String = "", var boardIdStr: String = "",
var file: String = "" var file: String = "",
) { ) {
companion object { companion object {
private val log = LoggerFactory.getLogger(VrtEntry::class.java) private const val VENDOR_RAMDISK_NAME_SIZE = 32
const val VENDOR_RAMDISK_NAME_SIZE = 32
const val VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16 const val VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16
//const val FORMAT_STRING = "3I${VENDOR_RAMDISK_NAME_SIZE}s${VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}I" //const val FORMAT_STRING = "3I${VENDOR_RAMDISK_NAME_SIZE}s${VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}I"
@ -156,7 +156,6 @@ data class VendorBoot(
companion object { companion object {
private val log = LoggerFactory.getLogger(VendorBoot::class.java) private val log = LoggerFactory.getLogger(VendorBoot::class.java)
private val workDir = Helper.prop("workDir") private val workDir = Helper.prop("workDir")
private val VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE = 108
fun parse(fileName: String): VendorBoot { fun parse(fileName: String): VendorBoot {
val ret = VendorBoot() val ret = VendorBoot()
FileInputStream(fileName).use { fis -> FileInputStream(fileName).use { fis ->
@ -342,7 +341,7 @@ data class VendorBoot(
fun extractVBMeta(): VendorBoot { fun extractVBMeta(): VendorBoot {
try { try {
Avb().parseVbMeta(info.output) AVBInfo.parseFrom(info.output).dumpDefault(info.output)
} catch (e: Exception) { } catch (e: Exception) {
log.error("extraceVBMeta(): $e") log.error("extraceVBMeta(): $e")
} }

@ -14,6 +14,7 @@
package cfig.packable package cfig.packable
import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.Companion.check_call import cfig.helper.Helper.Companion.check_call
@ -62,7 +63,7 @@ interface IPackable {
// invoked solely by reflection // invoked solely by reflection
fun `@verify`(fileName: String) { fun `@verify`(fileName: String) {
val ai = Avb().parseVbMeta(fileName, true) val ai = AVBInfo.parseFrom(fileName).dumpDefault(fileName)
Avb().verify(ai, fileName) Avb().verify(ai, fileName)
} }

@ -14,9 +14,15 @@
package cfig.packable package cfig.packable
import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper import cfig.helper.Helper
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import java.io.File import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
class VBMetaParser: IPackable { class VBMetaParser: IPackable {
override val loopNo: Int override val loopNo: Int
@ -32,11 +38,13 @@ class VBMetaParser: IPackable {
override fun unpack(fileName: String) { override fun unpack(fileName: String) {
cleanUp() cleanUp()
Avb().parseVbMeta(fileName) AVBInfo.parseFrom(fileName).dumpDefault(fileName)
} }
override fun pack(fileName: String) { override fun pack(fileName: String) {
Avb().packVbMetaWithPadding(fileName) val blob = ObjectMapper().readValue(File(Avb.getJsonFileName(fileName)), AVBInfo::class.java).encodePadded()
log.info("Writing padded vbmeta to file: $fileName.signed")
Files.write(Paths.get("$fileName.signed"), blob, StandardOpenOption.CREATE)
} }
override fun flash(fileName: String, deviceName: String) { override fun flash(fileName: String, deviceName: String) {
@ -51,4 +59,6 @@ class VBMetaParser: IPackable {
override fun pull(fileName: String, deviceName: String) { override fun pull(fileName: String, deviceName: String) {
super.pull(fileName, deviceName) super.pull(fileName, deviceName)
} }
private val log = LoggerFactory.getLogger(VBMetaParser::class.java)
} }

@ -46,7 +46,6 @@ import java.util.regex.Pattern
import kotlin.random.Random import kotlin.random.Random
class Struct3 { class Struct3 {
private val log = LoggerFactory.getLogger(Struct3::class.java)
private val formatString: String private val formatString: String
private var byteOrder = ByteOrder.LITTLE_ENDIAN private var byteOrder = ByteOrder.LITTLE_ENDIAN
private val formats = ArrayList<Array<Any?>>() private val formats = ArrayList<Array<Any?>>()
@ -135,10 +134,10 @@ class Struct3 {
return ret return ret
} }
@Throws(IllegalArgumentException::class)
fun pack(vararg args: Any?): ByteArray { fun pack(vararg args: Any?): ByteArray {
if (args.size != this.formats.size) { if (args.size != this.formats.size) {
throw IllegalArgumentException("argument size " + args.size + throw IllegalArgumentException("argument size " + args.size + " doesn't match format size " + this.formats.size)
" doesn't match format size " + this.formats.size)
} }
val bf = ByteBuffer.allocate(this.calcSize()) val bf = ByteBuffer.allocate(this.calcSize())
bf.order(this.byteOrder) bf.order(this.byteOrder)
@ -157,8 +156,7 @@ class Struct3 {
null -> bf.appendPadding(0, multiple) null -> bf.appendPadding(0, multiple)
is Byte -> bf.appendPadding(arg, multiple) is Byte -> bf.appendPadding(arg, multiple)
is Int -> bf.appendPadding(arg.toByte(), multiple) is Int -> bf.appendPadding(arg.toByte(), multiple)
else -> throw IllegalArgumentException("Index[" + i + "] Unsupported arg [" else -> throw IllegalArgumentException("Index[" + i + "] Unsupported arg [" + arg + "] with type [" + formats[i][0] + "]")
+ arg + "] with type [" + formats[i][0] + "]")
} }
continue continue
} }
@ -288,7 +286,7 @@ class Struct3 {
return bf.array() return bf.array()
} }
@Throws(IOException::class) @Throws(IOException::class, IllegalArgumentException::class)
fun unpack(iS: InputStream): List<*> { fun unpack(iS: InputStream): List<*> {
val ret = ArrayList<Any>() val ret = ArrayList<Any>()
for (format in this.formats) { for (format in this.formats) {
@ -314,6 +312,7 @@ class Struct3 {
companion object { companion object {
private val log = LoggerFactory.getLogger(ByteBufferExt::class.java) private val log = LoggerFactory.getLogger(ByteBufferExt::class.java)
@Throws(IllegalArgumentException::class)
fun ByteBuffer.appendPadding(b: Byte, bufSize: Int) { fun ByteBuffer.appendPadding(b: Byte, bufSize: Int) {
when { when {
bufSize == 0 -> { bufSize == 0 -> {
@ -334,15 +333,17 @@ class Struct3 {
fun ByteBuffer.appendByteArray(inIntArray: IntArray, bufSize: Int) { fun ByteBuffer.appendByteArray(inIntArray: IntArray, bufSize: Int) {
val arg2 = mutableListOf<Byte>() val arg2 = mutableListOf<Byte>()
inIntArray.toMutableList().mapTo(arg2, { inIntArray.toMutableList().mapTo(arg2) {
if (it in Byte.MIN_VALUE..Byte.MAX_VALUE) if (it in Byte.MIN_VALUE..Byte.MAX_VALUE) {
it.toByte() it.toByte()
else } else {
throw IllegalArgumentException("$it is not valid Byte") throw IllegalArgumentException("$it is not valid Byte")
}) }
}
appendByteArray(arg2.toByteArray(), bufSize) appendByteArray(arg2.toByteArray(), bufSize)
} }
@Throws(IllegalArgumentException::class)
fun ByteBuffer.appendByteArray(inByteArray: ByteArray, bufSize: Int) { fun ByteBuffer.appendByteArray(inByteArray: ByteArray, bufSize: Int) {
val paddingSize = bufSize - inByteArray.size val paddingSize = bufSize - inByteArray.size
if (paddingSize < 0) throw IllegalArgumentException("arg length [${inByteArray.size}] exceeds limit: $bufSize") if (paddingSize < 0) throw IllegalArgumentException("arg length [${inByteArray.size}] exceeds limit: $bufSize")
@ -355,18 +356,19 @@ class Struct3 {
fun ByteBuffer.appendUByteArray(inIntArray: IntArray, bufSize: Int) { fun ByteBuffer.appendUByteArray(inIntArray: IntArray, bufSize: Int) {
val arg2 = mutableListOf<UByte>() val arg2 = mutableListOf<UByte>()
inIntArray.toMutableList().mapTo(arg2, { inIntArray.toMutableList().mapTo(arg2) {
if (it in UByte.MIN_VALUE.toInt()..UByte.MAX_VALUE.toInt()) if (it in UByte.MIN_VALUE.toInt()..UByte.MAX_VALUE.toInt())
it.toUByte() it.toUByte()
else else {
throw IllegalArgumentException("$it is not valid Byte") throw IllegalArgumentException("$it is not valid Byte")
}) }
}
appendUByteArray(arg2.toUByteArray(), bufSize) appendUByteArray(arg2.toUByteArray(), bufSize)
} }
fun ByteBuffer.appendUByteArray(inUByteArray: UByteArray, bufSize: Int) { fun ByteBuffer.appendUByteArray(inUByteArray: UByteArray, bufSize: Int) {
val bl = mutableListOf<Byte>() val bl = mutableListOf<Byte>()
inUByteArray.toMutableList().mapTo(bl, { it.toByte() }) inUByteArray.toMutableList().mapTo(bl) { it.toByte() }
this.appendByteArray(bl.toByteArray(), bufSize) this.appendByteArray(bl.toByteArray(), bufSize)
} }
} }
@ -426,7 +428,7 @@ class Struct3 {
val data = ByteArray(inSize) val data = ByteArray(inSize)
assert(inSize == this.read(data)) assert(inSize == this.read(data))
val innerData2 = mutableListOf<UByte>() val innerData2 = mutableListOf<UByte>()
data.toMutableList().mapTo(innerData2, { it.toUByte() }) data.toMutableList().mapTo(innerData2) { it.toUByte() }
return innerData2.toUByteArray() return innerData2.toUByteArray()
} }
@ -451,92 +453,78 @@ class Struct3 {
fun ByteArray.toShort(inByteOrder: ByteOrder): Short { fun ByteArray.toShort(inByteOrder: ByteOrder): Short {
val typeSize = Short.SIZE_BYTES / Byte.SIZE_BYTES val typeSize = Short.SIZE_BYTES / Byte.SIZE_BYTES
assert(typeSize == this.size) { "Short must have $typeSize bytes" } assert(typeSize == this.size) { "Short must have $typeSize bytes" }
var ret: Short return ByteBuffer.allocate(this.size).let {
ByteBuffer.allocate(typeSize).let {
it.order(inByteOrder) it.order(inByteOrder)
it.put(this) it.put(this)
it.flip() it.flip()
ret = it.getShort() it.getShort()
} }
return ret
} }
fun ByteArray.toInt(inByteOrder: ByteOrder): Int { fun ByteArray.toInt(inByteOrder: ByteOrder): Int {
val typeSize = Int.SIZE_BYTES / Byte.SIZE_BYTES val typeSize = Int.SIZE_BYTES / Byte.SIZE_BYTES
assert(typeSize == this.size) { "Int must have $typeSize bytes" } assert(typeSize == this.size) { "Int must have $typeSize bytes" }
var ret: Int return ByteBuffer.allocate(this.size).let {
ByteBuffer.allocate(typeSize).let {
it.order(inByteOrder) it.order(inByteOrder)
it.put(this) it.put(this)
it.flip() it.flip()
ret = it.getInt() it.getInt()
} }
return ret
} }
fun ByteArray.toLong(inByteOrder: ByteOrder): Long { fun ByteArray.toLong(inByteOrder: ByteOrder): Long {
val typeSize = Long.SIZE_BYTES / Byte.SIZE_BYTES val typeSize = Long.SIZE_BYTES / Byte.SIZE_BYTES
assert(typeSize == this.size) { "Long must have $typeSize bytes" } assert(typeSize == this.size) { "Long must have $typeSize bytes" }
var ret: Long return ByteBuffer.allocate(this.size).let {
ByteBuffer.allocate(typeSize).let {
it.order(inByteOrder) it.order(inByteOrder)
it.put(this) it.put(this)
it.flip() it.flip()
ret = it.getLong() it.getLong()
} }
return ret
} }
fun ByteArray.toUShort(inByteOrder: ByteOrder): UShort { fun ByteArray.toUShort(inByteOrder: ByteOrder): UShort {
val typeSize = UShort.SIZE_BYTES / Byte.SIZE_BYTES val typeSize = UShort.SIZE_BYTES / Byte.SIZE_BYTES
assert(typeSize == this.size) { "UShort must have $typeSize bytes" } assert(typeSize == this.size) { "UShort must have $typeSize bytes" }
var ret: UShort return ByteBuffer.allocate(this.size).let {
ByteBuffer.allocate(typeSize).let {
it.order(inByteOrder) it.order(inByteOrder)
it.put(this) it.put(this)
it.flip() it.flip()
ret = it.getShort().toUShort() it.getShort().toUShort()
} }
return ret
} }
fun ByteArray.toUInt(inByteOrder: ByteOrder): UInt { fun ByteArray.toUInt(inByteOrder: ByteOrder): UInt {
val typeSize = UInt.SIZE_BYTES / Byte.SIZE_BYTES val typeSize = UInt.SIZE_BYTES / Byte.SIZE_BYTES
assert(typeSize == this.size) { "UInt must have $typeSize bytes" } assert(typeSize == this.size) { "UInt must have $typeSize bytes" }
var ret: UInt return ByteBuffer.allocate(this.size).let {
ByteBuffer.allocate(typeSize).let {
it.order(inByteOrder) it.order(inByteOrder)
it.put(this) it.put(this)
it.flip() it.flip()
ret = it.getInt().toUInt() it.getInt().toUInt()
} }
return ret
} }
fun ByteArray.toULong(inByteOrder: ByteOrder): ULong { fun ByteArray.toULong(inByteOrder: ByteOrder): ULong {
val typeSize = ULong.SIZE_BYTES / Byte.SIZE_BYTES val typeSize = ULong.SIZE_BYTES / Byte.SIZE_BYTES
assert(typeSize == this.size) { "ULong must have $typeSize bytes" } assert(typeSize == this.size) { "ULong must have $typeSize bytes" }
var ret: ULong return ByteBuffer.allocate(this.size).let {
ByteBuffer.allocate(typeSize).let {
it.order(inByteOrder) it.order(inByteOrder)
it.put(this) it.put(this)
it.flip() it.flip()
ret = it.getLong().toULong() it.getLong().toULong()
} }
return ret
} }
//similar to this.toString(StandardCharsets.UTF_8).replace("${Character.MIN_VALUE}", "") //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 // not Deprecated for now, "1.3.41 experimental api: ByteArray.decodeToString()") is a little different
fun ByteArray.toCString(): String { fun ByteArray.toCString(): String {
val str = this.toString(StandardCharsets.UTF_8) return this.toString(StandardCharsets.UTF_8).let { str ->
val nullPos = str.indexOf(Character.MIN_VALUE) str.indexOf(Character.MIN_VALUE).let { nullPos ->
return if (nullPos >= 0) { if (nullPos >= 0) str.substring(0, nullPos) else str
str.substring(0, nullPos) }
} else {
str
} }
} }
} }//end-of-Companion
} }//end-of-ByteArrayExt
} }

Loading…
Cancel
Save