diff --git a/.gitignore b/.gitignore
index bfb6357..7e40555 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 .gradle
 build/
 local.properties
+__pycache__
diff --git a/aosp/avb/avb_test_data/testkey_atx_pik.pem b/aosp/avb/data/testkey_atx_pik.pem
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_atx_pik.pem
rename to aosp/avb/data/testkey_atx_pik.pem
diff --git a/aosp/avb/avb_test_data/testkey_atx_prk.pem b/aosp/avb/data/testkey_atx_prk.pem
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_atx_prk.pem
rename to aosp/avb/data/testkey_atx_prk.pem
diff --git a/aosp/avb/avb_test_data/testkey_atx_psk.pem b/aosp/avb/data/testkey_atx_psk.pem
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_atx_psk.pem
rename to aosp/avb/data/testkey_atx_psk.pem
diff --git a/aosp/avb/avb_test_data/testkey_rsa2048.pem b/aosp/avb/data/testkey_rsa2048.pem
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_rsa2048.pem
rename to aosp/avb/data/testkey_rsa2048.pem
diff --git a/aosp/avb/avb_test_data/testkey_rsa2048.pk8 b/aosp/avb/data/testkey_rsa2048.pk8
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_rsa2048.pk8
rename to aosp/avb/data/testkey_rsa2048.pk8
diff --git a/aosp/avb/avb_test_data/testkey_rsa4096.pem b/aosp/avb/data/testkey_rsa4096.pem
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_rsa4096.pem
rename to aosp/avb/data/testkey_rsa4096.pem
diff --git a/aosp/avb/avb_test_data/testkey_rsa4096.pk8 b/aosp/avb/data/testkey_rsa4096.pk8
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_rsa4096.pk8
rename to aosp/avb/data/testkey_rsa4096.pk8
diff --git a/aosp/avb/avb_test_data/testkey_rsa8192.pem b/aosp/avb/data/testkey_rsa8192.pem
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_rsa8192.pem
rename to aosp/avb/data/testkey_rsa8192.pem
diff --git a/aosp/avb/avb_test_data/testkey_rsa8192.pk8 b/aosp/avb/data/testkey_rsa8192.pk8
similarity index 100%
rename from aosp/avb/avb_test_data/testkey_rsa8192.pk8
rename to aosp/avb/data/testkey_rsa8192.pk8
diff --git a/bbootimg/src/main/kotlin/avb/AVBInfo.kt b/bbootimg/src/main/kotlin/avb/AVBInfo.kt
index 13b5d5c..8adf031 100644
--- a/bbootimg/src/main/kotlin/avb/AVBInfo.kt
+++ b/bbootimg/src/main/kotlin/avb/AVBInfo.kt
@@ -23,7 +23,7 @@ import avb.desc.UnknownDescriptor
 import cfig.Avb
 import cfig.helper.Helper
 import cfig.helper.Helper.Companion.paddingWith
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.apache.commons.codec.binary.Hex
 import org.slf4j.LoggerFactory
@@ -93,24 +93,24 @@ class AVBInfo(
             var vbMetaOffset: Long
         )
 
-        private fun imageGlance(dataSrc: DataSrc<*>): Glance {
+        private fun imageGlance(dp: Dumpling<*>): Glance {
             val ret = Glance(null, 0)
             try {
-                ret.footer = Footer(dataSrc.readFully(Pair(-Footer.SIZE.toLong(), Footer.SIZE)))
+                ret.footer = Footer(dp.readFully(Pair(-Footer.SIZE.toLong(), Footer.SIZE)))
                 ret.vbMetaOffset = ret.footer!!.vbMetaOffset
-                log.info("${dataSrc.getName()}: $ret.footer")
+                log.info("${dp.getName()}: $ret.footer")
             } catch (e: IllegalArgumentException) {
-                log.info("image ${dataSrc.getName()} has no AVB Footer")
+                log.info("image ${dp.getName()} has no AVB Footer")
             }
             return ret
         }
 
-        fun parseFrom(dataSrc: DataSrc<*>): AVBInfo {
-            log.info("parseFrom(${dataSrc.getName()}) ...")
+        fun parseFrom(dp: Dumpling<*>): AVBInfo {
+            log.info("parseFrom(${dp.getName()}) ...")
             // glance
-            val (footer, vbMetaOffset) = imageGlance(dataSrc)
+            val (footer, vbMetaOffset) = imageGlance(dp)
             // header
-            val vbMetaHeader = Header(dataSrc.readFully(Pair(vbMetaOffset, Header.SIZE)))
+            val vbMetaHeader = Header(dp.readFully(Pair(vbMetaOffset, Header.SIZE)))
             log.debug(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader))
 
             val atlas = mutableMapOf<String, Pair<Long, Int>>()
@@ -126,9 +126,9 @@ class AVBInfo(
             val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer)
             // Auth blob
             if (vbMetaHeader.authentication_data_block_size > 0) {
-                val ba = dataSrc.readFully(atlas["auth.hash"]!!)
+                val ba = dp.readFully(atlas["auth.hash"]!!)
                 log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba))
-                val bb = dataSrc.readFully(atlas["auth.sig"]!!)
+                val bb = dp.readFully(atlas["auth.sig"]!!)
                 log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb))
                 ai.authBlob = AuthBlob()
                 ai.authBlob!!.offset = atlas["auth"]!!.first
@@ -137,7 +137,7 @@ class AVBInfo(
                 ai.authBlob!!.signature = Hex.encodeHexString(bb)
             }
             // aux
-            val rawAuxBlob = dataSrc.readFully(atlas["aux"]!!)
+            val rawAuxBlob = dp.readFully(atlas["aux"]!!)
             // aux - desc
             if (vbMetaHeader.descriptors_size > 0) {
                 val descriptors = UnknownDescriptor.parseDescriptors(
@@ -172,7 +172,7 @@ class AVBInfo(
                 )
                 log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd))
             }
-            log.debug("vbmeta info of [${dataSrc.getName()}] has been analyzed")
+            log.debug("vbmeta info of [${dp.getName()}] has been analyzed")
             return ai
         }
     }
diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt
index d3b9b14..dad1de3 100644
--- a/bbootimg/src/main/kotlin/avb/Avb.kt
+++ b/bbootimg/src/main/kotlin/avb/Avb.kt
@@ -24,7 +24,8 @@ import avb.desc.HashDescriptor
 import cfig.helper.CryptoHelper
 import cfig.helper.Helper
 import cfig.helper.Helper.Companion.paddingWith
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
+import com.fasterxml.jackson.core.type.TypeReference
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.apache.commons.codec.binary.Hex
 import org.apache.commons.exec.CommandLine
@@ -205,7 +206,7 @@ class Avb {
                         it.auxBlob!!.hashDescriptors.get(0).partition_name
                     }
                 //read hashDescriptor from image
-                val newHashDesc = AVBInfo.parseFrom(DataSrc("$fileName.signed"))
+                val newHashDesc = AVBInfo.parseFrom(Dumpling("$fileName.signed"))
                 check(newHashDesc.auxBlob!!.hashDescriptors.size == 1)
                 var seq = -1 //means not found
                 //main vbmeta
@@ -236,32 +237,22 @@ class Avb {
             }
         }
 
-        fun verify(ai: AVBInfo, image_file: String, parent: String = ""): Array<Any> {
+        fun verify(ai: AVBInfo, dp: Dumpling<*>, parent: String = ""): Array<Any> {
             val ret: Array<Any> = arrayOf(true, "")
-            val localParent = parent.ifEmpty { image_file }
+            val localParent = parent.ifEmpty { dp.getLabel() }
             //header
-            val rawHeaderBlob = DataSrc(image_file).readFully(Pair(ai.footer?.vbMetaOffset ?: 0, Header.SIZE))
+            val rawHeaderBlob = dp.readFully(Pair(ai.footer?.vbMetaOffset ?: 0, Header.SIZE))
             // aux
             val vbOffset = ai.footer?.vbMetaOffset ?: 0
             //@formatter:off
-            val rawAuxBlob = DataSrc(image_file).readFully(
-                    Pair(vbOffset + Header.SIZE + ai.header!!.authentication_data_block_size,
-                        ai.header!!.auxiliary_data_block_size.toInt()))
+            val rawAuxBlob = dp.readFully(
+                Pair(vbOffset + Header.SIZE + ai.header!!.authentication_data_block_size,
+                    ai.header!!.auxiliary_data_block_size.toInt()))
             //@formatter:on
             //integrity check
             val declaredAlg = Algorithms.get(ai.header!!.algorithm_type)
             if (declaredAlg!!.public_key_num_bytes > 0) {
-                val gkiPubKey = if (declaredAlg.algorithm_type == 1) AuxBlob.encodePubKey(
-                    declaredAlg,
-                    File("aosp/make/target/product/gsi/testkey_rsa2048.pem").readBytes()
-                ) else null
-                if (AuxBlob.encodePubKey(declaredAlg).contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
-                    log.info("VERIFY($localParent): signed with dev key: " + declaredAlg.defaultKey)
-                } else if (gkiPubKey.contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
-                    log.info("VERIFY($localParent): signed with dev GKI key: " + declaredAlg.defaultKey)
-                } else {
-                    log.info("VERIFY($localParent): signed with release key")
-                }
+                inspectKey(ai)
                 val calcHash =
                     Helper.join(declaredAlg.padding, AuthBlob.calcHash(rawHeaderBlob, rawAuxBlob, declaredAlg.name))
                 val readHash = Helper.join(declaredAlg.padding, Helper.fromHexString(ai.authBlob!!.hash!!))
@@ -293,7 +284,7 @@ class Avb {
             ai.auxBlob!!.chainPartitionDescriptors.forEach {
                 val vRet = it.verify(
                     prefixes.map { prefix -> "$prefix${it.partition_name}.img" },
-                    image_file + "->Chain[${it.partition_name}]"
+                    dp.getLabel() + "->Chain[${it.partition_name}]"
                 )
                 if (vRet[0] as Boolean) {
                     log.info("VERIFY($localParent->Chain[${it.partition_name}]): " + "PASS")
@@ -307,7 +298,7 @@ class Avb {
             ai.auxBlob!!.hashDescriptors.forEach {
                 val vRet = it.verify(
                     prefixes.map { prefix -> "$prefix${it.partition_name}.img" },
-                    image_file + "->HashDescriptor[${it.partition_name}]"
+                    dp.getLabel() + "->HashDescriptor[${it.partition_name}]"
                 )
                 if (vRet[0] as Boolean) {
                     log.info("VERIFY($localParent->HashDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + "PASS")
@@ -321,7 +312,7 @@ class Avb {
             ai.auxBlob!!.hashTreeDescriptors.forEach {
                 val vRet = it.verify(
                     prefixes.map { prefix -> "$prefix${it.partition_name}.img" },
-                    image_file + "->HashTreeDescriptor[${it.partition_name}]"
+                    dp.getLabel() + "->HashTreeDescriptor[${it.partition_name}]"
                 )
                 if (vRet[0] as Boolean) {
                     log.info("VERIFY($localParent->HashTreeDescriptor[${it.partition_name}]): ${it.hash_algorithm} " + "PASS")
@@ -334,5 +325,51 @@ class Avb {
 
             return ret
         }
+
+        fun inspectKey(ai: AVBInfo): String {
+            var ret = "NONE"
+            if (ai.auxBlob!!.pubkey == null) {
+                log.info("vbmeta blob is unsigned")
+                return ret
+            }
+            val declaredAlg = Algorithms.get(ai.header!!.algorithm_type)
+            val gkiPubKey = if (declaredAlg!!.algorithm_type == 1) AuxBlob.encodePubKey(
+                declaredAlg,
+                File("aosp/make/target/product/gsi/testkey_rsa2048.pem").readBytes()
+            ) else null
+            if (AuxBlob.encodePubKey(declaredAlg).contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
+                log.info("signed with dev key: " + declaredAlg.defaultKey)
+                ret = declaredAlg.defaultKey
+            } else if (gkiPubKey.contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
+                log.info("signed with dev GKI key: " + declaredAlg.defaultKey)
+                ret = declaredAlg.defaultKey
+            } else {
+                val keys = ObjectMapper().readValue(
+                    this::class.java.classLoader.getResource("known_keys.json"),
+                    object : TypeReference<List<KnownPublicKey>>() {})
+                val found = keys.filter { it.pubk.contentEquals(ai.auxBlob!!.pubkey!!.pubkey) }
+                if (found.isNotEmpty()) {
+                    log.info("signed with release key: '${found[0].device}' by ${found[0].manufacturer}")
+                    log.warn("Found key: ${found[0].toShortString()}")
+                    ret = "${found[0].device} by ${found[0].manufacturer}"
+                } else {
+                    log.info("signed with release key")
+                    ret = "private release key"
+                }
+            }
+            return ret
+        }
+
+        data class KnownPublicKey(
+            var device: String = "",
+            var manufacturer: String = "",
+            var algorithm: String = "",
+            var pubk: ByteArray = byteArrayOf(),
+            var sha1: String = ""
+        ) {
+            fun toShortString(): String {
+                return "PublicKey(device='$device' by '$manufacturer', algorithm='$algorithm', sha1='$sha1')"
+            }
+        }
     }
 }
diff --git a/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt b/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt
index f4159cb..9663f1d 100644
--- a/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt
+++ b/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt
@@ -49,7 +49,7 @@ class Algorithms {
                             intArrayOf(0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
                                     0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
                                     0x00, 0x04, 0x20)),
-                    defaultKey = "aosp/avb/avb_test_data/testkey_rsa2048.pem")
+                    defaultKey = "aosp/avb/data/testkey_rsa2048.pem")
 
             val SHA256_RSA4096 = Algorithm(
                     name = "SHA256_RSA4096",
@@ -66,7 +66,7 @@ class Algorithms {
                                     0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
                                     0x00, 0x04, 0x20)
                     ),
-                    defaultKey = "aosp/avb/avb_test_data/testkey_rsa4096.pem"
+                    defaultKey = "aosp/avb/data/testkey_rsa4096.pem"
             )
 
             val SHA256_RSA8192 = Algorithm(
@@ -83,7 +83,7 @@ class Algorithms {
                             intArrayOf(0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
                                     0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
                                     0x00, 0x04, 0x20)),
-                    defaultKey = "aosp/avb/avb_test_data/testkey_rsa8192.pem")
+                    defaultKey = "aosp/avb/data/testkey_rsa8192.pem")
 
             val SHA512_RSA2048 = Algorithm(
                     name = "SHA512_RSA2048",
@@ -99,7 +99,7 @@ class Algorithms {
                             intArrayOf(0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
                                     0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
                                     0x00, 0x04, 0x40)),
-                    defaultKey = "aosp/avb/avb_test_data/testkey_rsa2048.pem")
+                    defaultKey = "aosp/avb/data/testkey_rsa2048.pem")
 
             val SHA512_RSA4096 = Algorithm(
                     name = "SHA512_RSA4096",
@@ -115,7 +115,7 @@ class Algorithms {
                             intArrayOf(0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
                                     0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
                                     0x00, 0x04, 0x40)),
-                    defaultKey = "aosp/avb/avb_test_data/testkey_rsa4096.pem")
+                    defaultKey = "aosp/avb/data/testkey_rsa4096.pem")
 
             val SHA512_RSA8192 = Algorithm(
                     name = "SHA512_RSA8192",
@@ -132,7 +132,7 @@ class Algorithms {
                             intArrayOf(0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
                                     0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
                                     0x00, 0x04, 0x40)),
-                    defaultKey = "aosp/avb/avb_test_data/testkey_rsa8192.pem")
+                    defaultKey = "aosp/avb/data/testkey_rsa8192.pem")
 
             algMap[NONE.name] = NONE
 
diff --git a/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt
index 27d44c2..6733280 100644
--- a/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt
+++ b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt
@@ -17,7 +17,7 @@ package avb.desc
 import avb.AVBInfo
 import cfig.Avb
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cc.cfig.io.Struct
 import java.io.File
 import java.io.InputStream
@@ -86,10 +86,10 @@ class ChainPartitionDescriptor(
         val ret: Array<Any> = arrayOf(false, "file not found")
         for (item in image_files) {
             if (File(item).exists()) {
-                val subAi = AVBInfo.parseFrom(DataSrc(item))
+                val subAi = AVBInfo.parseFrom(Dumpling(item))
                 if (pubkey.contentEquals(subAi.auxBlob!!.pubkey!!.pubkey)) {
                     log.info("VERIFY($parent): public key matches, PASS")
-                    return Avb.verify(subAi, item, parent)
+                    return Avb.verify(subAi, Dumpling(item), parent)
                 } else {
                     log.info("VERIFY($parent): public key mismatch, FAIL")
                     ret[1] = "public key mismatch"
diff --git a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt
index e5b07fd..ebdb353 100644
--- a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt
+++ b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt
@@ -17,7 +17,7 @@ package avb.desc
 import avb.blob.Header
 import cfig.helper.CryptoHelper
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cc.cfig.io.Struct
 import org.slf4j.LoggerFactory
 import java.io.*
@@ -113,7 +113,7 @@ class HashTreeDescriptor(
         for (item in fileNames) {
             if (File(item).exists()) {
                 val trimmedHash = this.genMerkleTree(item, "hash.tree")
-                val readTree = DataSrc(item).readFully(Pair(this.tree_offset, this.tree_size.toInt()))
+                val readTree = Dumpling(item).readFully(Pair(this.tree_offset, this.tree_size.toInt()))
                 val ourHtHash = CryptoHelper.Hasher.sha256(File("hash.tree").readBytes())
                 val diskHtHash = CryptoHelper.Hasher.sha256(readTree)
                 if (!ourHtHash.contentEquals(diskHtHash)) {
diff --git a/bbootimg/src/main/kotlin/bootimg/Common.kt b/bbootimg/src/main/kotlin/bootimg/Common.kt
index 7e72b0f..879f11a 100644
--- a/bbootimg/src/main/kotlin/bootimg/Common.kt
+++ b/bbootimg/src/main/kotlin/bootimg/Common.kt
@@ -160,6 +160,14 @@ class Common {
                     ZipHelper.lz4cat(s.dumpFile + ".lz4", s.dumpFile)
                     ret = "lz4"
                 }
+                ZipHelper.isAndroidCpio(s.dumpFile) -> {
+                    log.info("ramdisk is uncompressed cpio")
+                    Files.copy(
+                        Paths.get(s.dumpFile), Paths.get(s.dumpFile + ".cpio"),
+                        java.nio.file.StandardCopyOption.REPLACE_EXISTING
+                    )
+                    ret = "cpio"
+                }
                 else -> {
                     throw IllegalArgumentException("ramdisk is in unknown format")
                 }
@@ -273,6 +281,11 @@ class Common {
                     AndroidCpio().pack(root, f, "${f}_filelist.txt")
                     FileInputStream(f).use { ZipHelper.xz(ramdiskGz, it) }
                 }
+                ramdiskGz.endsWith(".cpio") -> {
+                    val f = ramdiskGz.removeSuffix(".cpio")
+                    AndroidCpio().pack(root, f, "${f}_filelist.txt")
+                    File(f).copyTo(File(ramdiskGz), true)
+                }
                 else -> {
                     throw IllegalArgumentException("$ramdiskGz is not supported")
                 }
diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt
index 34cd980..bd230c4 100644
--- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt
+++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt
@@ -19,9 +19,10 @@ import cfig.Avb
 import cfig.bootimg.Common
 import cfig.bootimg.Common.Companion.deleleIfExists
 import cfig.bootimg.Signer
+import cfig.bootimg.v3.BootV3
 import cfig.bootimg.v3.VendorBoot
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cfig.packable.VBMetaParser
 import cfig.utils.EnvironmentVerifier
 import com.fasterxml.jackson.databind.ObjectMapper
@@ -77,6 +78,7 @@ data class BootV2(
     companion object {
         private val log = LoggerFactory.getLogger(BootV2::class.java)
         private val workDir = Helper.prop("workDir")
+        private val mapper = ObjectMapper()
 
         fun parse(fileName: String): BootV2 {
             val ret = BootV2()
@@ -184,9 +186,8 @@ data class BootV2(
     }
 
     fun extractImages(): BootV2 {
-        val workDir = Helper.prop("workDir")
         //info
-        ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this)
+        mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this)
         //kernel
         Common.dumpKernel(Helper.Slice(info.output, kernel.position.toInt(), kernel.size, kernel.file!!))
         //ramdisk
@@ -196,7 +197,7 @@ data class BootV2(
             )
             this.ramdisk.file = this.ramdisk.file!! + ".$fmt"
             //dump info again
-            ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this)
+            mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this)
         }
         //second bootloader
         secondBootloader?.let {
@@ -226,7 +227,7 @@ data class BootV2(
 
     fun extractVBMeta(): BootV2 {
         if (this.info.verify.startsWith("VB2.0")) {
-            AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
+            AVBInfo.parseFrom(Dumpling(info.output)).dumpDefault(info.output)
             if (File("vbmeta.img").exists()) {
                 log.warn("Found vbmeta.img, parsing ...")
                 VBMetaParser().unpack("vbmeta.img")
@@ -238,7 +239,6 @@ data class BootV2(
     }
 
     fun printSummary(): BootV2 {
-        val workDir = Helper.prop("workDir")
         val tableHeader = AsciiTable().apply {
             addRule()
             addRow("What", "Where")
@@ -254,7 +254,17 @@ data class BootV2(
                 } else {
                     "verify fail"
                 }
-                it.addRow("AVB info [$verifyStatus]", Avb.getJsonFileName(info.output))
+                Avb.getJsonFileName(info.output).let { jsonFile ->
+                    it.addRow("AVB info [$verifyStatus]", jsonFile)
+                    if (File(jsonFile).exists()) {
+                        mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai ->
+                            val inspectRet = Avb.inspectKey(ai)
+                            if (inspectRet != "NONE") {
+                                it.addRow("\\-- signing key", inspectRet)
+                            }
+                        }
+                    }
+                }
             }
             //kernel
             it.addRule()
diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt
index 328d5a1..1c72cba 100644
--- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt
+++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt
@@ -21,7 +21,7 @@ import cfig.bootimg.Common.Companion.deleleIfExists
 import cfig.bootimg.Signer
 import cfig.bootimg.v3.VendorBoot
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cfig.packable.VBMetaParser
 import cfig.utils.EnvironmentVerifier
 import com.fasterxml.jackson.databind.ObjectMapper
@@ -245,7 +245,7 @@ data class BootV2Dialects(
 
     fun extractVBMeta(): BootV2Dialects {
         if (this.info.verify.startsWith("VB2.0")) {
-            AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
+            AVBInfo.parseFrom(Dumpling(info.output)).dumpDefault(info.output)
             if (File("vbmeta.img").exists()) {
                 log.warn("Found vbmeta.img, parsing ...")
                 VBMetaParser().unpack("vbmeta.img")
diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt
index 416762f..5cd7575 100644
--- a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt
+++ b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt
@@ -23,7 +23,7 @@ import cfig.bootimg.Common.Companion.deleleIfExists
 import cfig.bootimg.Common.Companion.getPaddingSize
 import cfig.bootimg.Signer
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cfig.packable.VBMetaParser
 import com.fasterxml.jackson.databind.ObjectMapper
 import de.vandermeer.asciitable.AsciiTable
@@ -241,7 +241,7 @@ data class BootV3(
     fun extractVBMeta(): BootV3 {
         // vbmeta in image
         try {
-            AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
+            val ai = AVBInfo.parseFrom(Dumpling(info.output)).dumpDefault(info.output)
             if (File("vbmeta.img").exists()) {
                 log.warn("Found vbmeta.img, parsing ...")
                 VBMetaParser().unpack("vbmeta.img")
@@ -253,16 +253,21 @@ data class BootV3(
 
         //GKI 1.0 bootsig
         if (info.signatureSize > 0) {
-            Helper.extractFile(
-                info.output, this.bootSignature.file,
-                this.bootSignature.position.toLong(), this.bootSignature.size
-            )
-            try {
-                val bootsig = AVBInfo.parseFrom(DataSrc(this.bootSignature.file)).dumpDefault(this.bootSignature.file)
-                Avb.verify(bootsig, "${workDir}bootsig")
-            } catch (e: IllegalArgumentException) {
-                log.warn("boot signature is invalid")
-            }
+            log.info("GKI 1.0 signature")
+            Dumpling(info.output).readFully(Pair(this.bootSignature.position.toLong(), this.bootSignature.size))
+                .let { bootsigData ->
+                    File(this.bootSignature.file).writeBytes(bootsigData)
+                    if (bootsigData.any { it.toInt() != 0 }) {
+                        try {
+                            val bootsig = AVBInfo.parseFrom(Dumpling(bootsigData)).dumpDefault(this.bootSignature.file)
+                            Avb.verify(bootsig, Dumpling(bootsigData, "bootsig"))
+                        } catch (e: IllegalArgumentException) {
+                            log.warn("GKI 1.0 boot signature is invalid")
+                        }
+                    } else {
+                        log.warn("GKI 1.0 boot signature has only NULL data")
+                    }
+                }
             return this
         }
 
@@ -277,18 +282,22 @@ data class BootV3(
             AVBInfo::class.java
         )
         val bootSig16kData =
-            DataSrc(DataSrc(info.output).readFully(Pair(mainBlob.footer!!.originalImageSize - 16 * 1024, 16 * 1024)))
+            Dumpling(Dumpling(info.output).readFully(Pair(mainBlob.footer!!.originalImageSize - 16 * 1024, 16 * 1024)))
         try {
             val blob1 = AVBInfo.parseFrom(bootSig16kData)
-                .also { it.dumpDefault("bootsig." + it.auxBlob!!.hashDescriptors[0].partition_name) }
+                .also { check(it.auxBlob!!.hashDescriptors[0].partition_name == "boot") }
+                .also { it.dumpDefault("sig.boot") }
             val blob2 =
-                AVBInfo.parseFrom(DataSrc(bootSig16kData.readFully(blob1.encode().size until bootSig16kData.getLength())))
-                    .also { it.dumpDefault("bootsig." + it.auxBlob!!.hashDescriptors[0].partition_name) }
-            File("build/unzip_boot/generic_kernel_avb").writeBytes(bootSig16kData.readFully(blob1.encode().size until bootSig16kData.getLength()))
-            File("build/unzip_boot/kernel").copyTo(File("build/unzip_boot/generic_kernel.img"), true)
-            System.setProperty("more", "build/unzip_boot")
-            Avb.verify(blob2, "generic_kernel_avb")
-
+                AVBInfo.parseFrom(Dumpling(bootSig16kData.readFully(blob1.encode().size until bootSig16kData.getLength())))
+                    .also { check(it.auxBlob!!.hashDescriptors[0].partition_name == "generic_kernel") }
+                    .also { it.dumpDefault("sig.kernel") }
+            val gkiAvbData = bootSig16kData.readFully(blob1.encode().size until bootSig16kData.getLength())
+            File("${workDir}kernel.img").let { gki ->
+                File("${workDir}kernel").copyTo(gki)
+                System.setProperty("more", workDir)
+                Avb.verify(blob2, Dumpling(gkiAvbData))
+                gki.delete()
+            }
             log.info(blob1.auxBlob!!.hashDescriptors[0].partition_name)
             log.info(blob2.auxBlob!!.hashDescriptors[0].partition_name)
         } catch (e: IllegalArgumentException) {
@@ -329,14 +338,41 @@ data class BootV3(
                 it.addRule()
             }
             if (this.info.signatureSize > 0) {
-                it.addRow("boot signature", this.bootSignature.file)
-                Avb.getJsonFileName(this.bootSignature.file).let { jsFile ->
-                    it.addRow("\\-- decoded boot signature", if (File(jsFile).exists()) jsFile else "N/A")
+                it.addRow("GKI signature 1.0", this.bootSignature.file)
+                File(Avb.getJsonFileName(this.bootSignature.file)).let { jsFile ->
+                    it.addRow("\\-- decoded boot signature", if (jsFile.exists()) jsFile.path else "N/A")
+                    if (jsFile.exists()) {
+                        it.addRow("\\------ signing key", Avb.inspectKey(mapper.readValue(jsFile, AVBInfo::class.java)))
+                    }
                 }
                 it.addRule()
             }
+
+            //GKI signature 2.0
+            File(Avb.getJsonFileName("sig.boot")).let { jsonFile ->
+                if (jsonFile.exists()) {
+                    it.addRow("GKI signature 2.0", this.bootSignature.file)
+                    it.addRow("\\-- boot", jsonFile.path)
+                    it.addRow("\\------ signing key", Avb.inspectKey(mapper.readValue(jsonFile, AVBInfo::class.java)))
+                }
+            }
+            File(Avb.getJsonFileName("sig.kernel")).let { jsonFile ->
+                if (jsonFile.exists()) {
+                    val readBackAvb = mapper.readValue(jsonFile, AVBInfo::class.java)
+                    it.addRow("\\-- kernel", jsonFile.path)
+                    it.addRow("\\------ signing key", Avb.inspectKey(readBackAvb))
+                    it.addRule()
+                }
+            }
+
+            //AVB info
             Avb.getJsonFileName(info.output).let { jsonFile ->
                 it.addRow("AVB info", if (File(jsonFile).exists()) jsonFile else "NONE")
+                if (File(jsonFile).exists()) {
+                    mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai ->
+                        it.addRow("\\------ signing key", Avb.inspectKey(ai))
+                    }
+                }
             }
             it.addRule()
             it
diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt
index 0847824..ec11400 100644
--- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt
+++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt
@@ -21,7 +21,7 @@ import cfig.utils.EnvironmentVerifier
 import cfig.bootimg.Common.Companion.deleleIfExists
 import cfig.bootimg.Signer
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cfig.packable.VBMetaParser
 import com.fasterxml.jackson.databind.ObjectMapper
 import de.vandermeer.asciitable.AsciiTable
@@ -156,6 +156,7 @@ data class VendorBoot(
     companion object {
         private val log = LoggerFactory.getLogger(VendorBoot::class.java)
         private val workDir = Helper.prop("workDir")
+        private val mapper = ObjectMapper()
         fun parse(fileName: String): VendorBoot {
             val ret = VendorBoot()
             FileInputStream(fileName).use { fis ->
@@ -373,7 +374,7 @@ data class VendorBoot(
 
     fun extractVBMeta(): VendorBoot {
         try {
-            AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
+            AVBInfo.parseFrom(Dumpling(info.output)).dumpDefault(info.output)
         } catch (e: Exception) {
             log.error("extraceVBMeta(): $e")
         }
@@ -413,8 +414,15 @@ data class VendorBoot(
                 it.addRow("bootconfig", this.bootconfig.file)
             }
             it.addRule()
-            it.addRow("AVB info", Avb.getJsonFileName(info.output))
-            it.addRule()
+            Avb.getJsonFileName(info.output).let { jsonFile ->
+                it.addRow("AVB info", Avb.getJsonFileName(info.output))
+                if (File(jsonFile).exists()) {
+                    mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai ->
+                        it.addRow("\\-- signing key", Avb.inspectKey(ai))
+                    }
+                }
+                it.addRule()
+            }
             it
         }
         val tabVBMeta = AsciiTable().let {
diff --git a/bbootimg/src/main/kotlin/ota/Payload.kt b/bbootimg/src/main/kotlin/ota/Payload.kt
index f3c07e7..404ae3a 100644
--- a/bbootimg/src/main/kotlin/ota/Payload.kt
+++ b/bbootimg/src/main/kotlin/ota/Payload.kt
@@ -17,6 +17,7 @@ package cc.cfig.droid.ota
 import cc.cfig.io.Struct
 import cfig.helper.CryptoHelper.Hasher
 import cfig.helper.Helper
+import cfig.helper.Dumpling
 import chromeos_update_engine.UpdateMetadata
 import com.fasterxml.jackson.annotation.JsonInclude
 import com.fasterxml.jackson.databind.ObjectMapper
@@ -88,10 +89,10 @@ class Payload {
             }
 
             val calcMetadataHash =
-                Hasher.hash(inFileName, listOf(Pair(0L, ret.metaSize.toLong())), "sha-256")
+                Hasher.hash(Dumpling(inFileName), listOf(Pair(0L, ret.metaSize.toLong())), "sha-256")
             log.info("calc meta hash: " + Helper.toHexString(calcMetadataHash))
             val calcPayloadHash = Hasher.hash(
-                inFileName, listOf(
+                Dumpling(inFileName), listOf(
                     Pair(0L, ret.metaSize.toLong()),
                     Pair(ret.metaSize.toLong() + ret.header.metaSigLen, ret.manifest.signaturesOffset)
                 ), "sha-256"
diff --git a/bbootimg/src/main/kotlin/packable/IPackable.kt b/bbootimg/src/main/kotlin/packable/IPackable.kt
index fa76b21..82dd240 100644
--- a/bbootimg/src/main/kotlin/packable/IPackable.kt
+++ b/bbootimg/src/main/kotlin/packable/IPackable.kt
@@ -17,7 +17,7 @@ package cfig.packable
 import avb.AVBInfo
 import cfig.Avb
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cfig.helper.Helper.Companion.check_call
 import cfig.helper.Helper.Companion.check_output
 import cfig.helper.Helper.Companion.deleteIfExists
@@ -68,8 +68,8 @@ interface IPackable {
 
     // invoked solely by reflection
     fun `@verify`(fileName: String) {
-        val ai = AVBInfo.parseFrom(DataSrc(fileName)).dumpDefault(fileName)
-        Avb.verify(ai, fileName)
+        val ai = AVBInfo.parseFrom(Dumpling(fileName)).dumpDefault(fileName)
+        Avb.verify(ai, Dumpling(fileName, fileName))
     }
 
     fun clear() {
diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
index aa4b183..51c69e4 100644
--- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
+++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
@@ -15,10 +15,13 @@
 package cfig.packable
 
 import avb.AVBInfo
+import avb.alg.Algorithms
 import cfig.Avb
+import cfig.helper.CryptoHelper
+import cfig.helper.Dumpling
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
 import cfig.helper.Helper.Companion.deleteIfExists
+import com.fasterxml.jackson.core.type.TypeReference
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.slf4j.LoggerFactory
 import java.io.File
@@ -40,7 +43,8 @@ class VBMetaParser : IPackable {
                 it.mkdirs()
             }
         }
-        AVBInfo.parseFrom(DataSrc(fileName)).dumpDefault(fileName)
+        val ai = AVBInfo.parseFrom(Dumpling(fileName)).dumpDefault(fileName)
+        log.info("Signing Key: " + Avb.inspectKey(ai))
     }
 
     override fun pack(fileName: String) {
diff --git a/bbootimg/src/main/kotlin/utils/Dtbo.kt b/bbootimg/src/main/kotlin/utils/Dtbo.kt
index e6cc371..024cdcf 100644
--- a/bbootimg/src/main/kotlin/utils/Dtbo.kt
+++ b/bbootimg/src/main/kotlin/utils/Dtbo.kt
@@ -7,7 +7,7 @@ import cfig.bootimg.Common
 import cfig.bootimg.Signer
 import cfig.bootimg.v3.VendorBoot
 import cfig.helper.Helper
-import cfig.helper.Helper.DataSrc
+import cfig.helper.Dumpling
 import cfig.packable.VBMetaParser
 import cfig.utils.DTC
 import com.fasterxml.jackson.databind.ObjectMapper
@@ -156,7 +156,7 @@ class Dtbo(
 
     fun extractVBMeta(): Dtbo {
         try {
-            AVBInfo.parseFrom(DataSrc(info.output)).dumpDefault(info.output)
+            AVBInfo.parseFrom(Dumpling(info.output)).dumpDefault(info.output)
         } catch (e: Exception) {
             log.error("extraceVBMeta(): $e")
         }
diff --git a/bbootimg/src/main/resources/known_keys.json b/bbootimg/src/main/resources/known_keys.json
new file mode 100644
index 0000000..69041d4
--- /dev/null
+++ b/bbootimg/src/main/resources/known_keys.json
@@ -0,0 +1,64 @@
+[{
+  "device" : "ADT-3",
+  "manufacturer" : "amlogic",
+  "algorithm" : "SHA256_RSA2048",
+  "pubk" : "AAAIAJHD1q2K7QEFh5MBfReSuk2oQ9vQLC1/kdSBfizd0iFTyNiVd57KUIjCzfN2uMJWiLtNUYa7gK+1TxMt8wkFZq61Mob563hmPl8FbNn2qb4+/g/DsaeZstuptD9L9pBVuIyUskl8hZ3GFO3Xn9BXW19NAhXYdi5qUxGsWsInRSqqASLxmV/zEQGFhhEVh9ZlCEyYukqaVaUunEDdkcAABRpcaT61QOww5QbZe8T8K/FgVxz1Mz4eF1plphSaah94TCG5Wc+RFeZ5QYATuBqsKPU7t9BAmnxXBbhAGiSkKUpI9Ln9N42MLvR9R+NBAiCZCoUOKPYBirqcyp48TZJiLuY4MgLbYr0mJ3OKgxUF6Q0MLPPrdKca/KagLkojDnshDTFmejPEWJS1b5snCI0lH3mr7HRVNfst4e8l4IfS5AxX2YLuAhWvjHql7Yvhdysy7toGZapQw6REjOgvleIUwI6QGBSHlr2PQ8zCgJFFk3dAo2EYuk3hjNEWRGkW8DB4l4ij2yMYI8w4vFVg5iacdfVL4lfgItQb6OYSJJZADOcCrHhqR+OWOLftyjFP/oSZC3T6I30F/PIBAb+i0FSm+np2yycv7T0GgxZQD3ESx1Vsm4QtXIy5E1ToLumXWUN2++yi30XM+wfqpargU0vNdG5pgWg2DR7SGcKjIkWqJ+LJ2B283A==",
+  "sha1" : "294dbb09016c013c57750edce0df0187409d56ce"
+},
+{
+  "device" : "GKI",
+  "manufacturer" : "google",
+  "algorithm" : "SHA256_RSA4096",
+  "pubk" : "AAAQAN3haYGQPHFa9yJAviIpp5Feoyn9fr9IEOXPfDzY+SzDr0drrCjO5gY59btYOt/RbVhiXRZK3uSwN3OSiQ98CtECYG9AxUu+3m7o0W+y/UiQkvbbLN+gnbZjiAqWWBIdT0RUTdIf98eMBdgzbdpbDn7uzmwDjLvCkrc/rsWcYYeD6VuS0qKiw/Bzm/8hp7lqqtW0fgn4Y7OBPXRfckyNExjkSvktZZKDFFKp/Xcio5coGLBqXc+1g0Lmo2osbx4bSP4cwJSxKtEeSfH51DLPSc8w+tba/8Jmp+j0KAf5jowNzRKyxyjxavzs0c0Us087MkHN4MaBeLIRH4twUtBRUVKNwacmC3vkFjgnVhpATzXHQa6OYQdV6I8Hu1GyKJ1mr85vEOroNB710Kr7r4Y6YhkW4TpYJQzQ0YZGVQXgp0q00qAQGa9BANrsBaPHFLfLMt/oxGWmIIEeJPKMJ20VTojJqC0XJp39Ujbn0UXC/5V9lU94BQYwog84FOVh2THwcMYsDFb/WlJkF6ERKDYjQwEcIPwR18E1oM3nBM3x5TJpeCV929gxBKYVdf3uoPwiq/tJmM6m+HcHrqjnwN17i1SyG54Hg5rXRQG98Zx+jh4mam1j6dD9dnm0UYNbU24Bca5QhfSR9gwTXxj+9owvyNhKFF41/LcEeaIl+4W0azzj0Mcpf45+UlP0vG5mxKpzlasQthS8m6nPdkYrKrSTClWueuZRhiW0lnPF7PCj8n9jccJ2xYoqcdKRja3hQuBp5u6cRjZi0ACimdTwAMjw7IVkyNlJsTEHPCcQCMknWp5sncJtqady+BbO4W4AfGqSdi6zEZQc3bP5QdQsrnT3rocNHoX3gNwh+hDaED5Rj3MzPXFclb0xfLf1yMSIVuGCePKAcOqwlVbQIeoo2yfHPWX02EvS1JbBrkBCAJ6HRw3e5t/JQXm+faSBaTWGrSBwqaG/ykXgEDzjdeTsGnVr5g9vazd3Z3Lr6F7HqTIYvMZ+ovkIwiBC6dapTX0Ow35m+NjYSTUd15igmaqxjxq4zq2wxE5T5DQhF5ria8cp+0AJhWkfl35oxPWPnLworlDjvu2BdEb0RtfkHM8/po1BBlHzYqqTyV1NqkQq9nO6gVCyGW/X8XufeXKg1CDUiGfjn3IhIIXVcmjW4NxAgdCZdAjGNqIyaYR/LOksw0uaB1IFh0VQNjj300LOXUbJMXdaxzx1sQpw+nFSNYtTX6Uck/XfeBJDSiae5kbVQcey0hhbj1CDWT/u05hVvtxbmmcqVcS+pXk3wjJ/nnWJNga+DYtzK678/u9Fcj0dC+fsWXlEgRen9i4hDynaB7nkOFsnrweA014TLwuSSOmdEDnO0NMnxeQH",
+  "sha1" : "144820003f9d46c96e9090dcf0e4feb84ca84810"
+},
+{
+  "device" : "Pixel 3",
+  "manufacturer" : "google",
+  "algorithm" : "SHA256_RSA2048",
+  "pubk" : "AAAIAH0QbcWadnydcrOvnaY8DvH/VQVZ4o2h/uUahVy2SY+E8krdSWU9Wcg0CZl/AvHPPL2rD1i6KoLDzqgS9cyybSJYH2LA+J7MIlBTV7+z0tMT+rYT7NkxcC+5AZjkQsmL+fbS1q0VIEtCM/ugPnOY1Imympq3rOxrGee8I/bNm5GxCT//2xUIjQn5D/1oQ6eCtBGBMgY4B2/r6ooUM8Dhp+xiuwxSs6iEoZuh5Ba/xWh6neRe7AHT54axGdXz50B8aQx3DXlvVjuz8LA9aUrlD9o6qYftNgg4hfaKPx5Vi0zw4teIL86F6Plte1F7HDgyjPDxYyAKsEk+PJPGexGJHeEPg3bzgRR3+ss18VcmMgL9Dk2afbuf2VANcxfNA5J7e4J3YGapPwILLMLlojIACk0b1gBclH1hffkQZ4O/SYC0AGsMJDI45kauUWJmYglONmqxgh+rDI5xQP1KiDkRDtBWjocGcf7mHlxvI0E7X+oK7V+cEFEbAK5sEvSs4QDGlb+bjFS+WRYpodO/HzF5M0Cinbn7izvek3uP+LMogj6ELEnZqe9u7ubmgKv/BIhQY/RiRx0goLkwXzVwaAp7y6vh/Vi6YB2pzR6IiItRCkoPK6U1xnVJY/oElv0icqDA2ynXbxZcc6D6OXqTcueKEmeuSGe7jan0y4oDnMLd3k5MaYn5iQ==",
+  "sha1" : "ad8569837cc06521720a35357475f87283b59234"
+},
+{
+  "device" : "Pixel 4",
+  "manufacturer" : "google",
+  "algorithm" : "SHA256_RSA4096",
+  "pubk" : "AAAQANeIn3+J2UI8Yc4LIB5gWoLlw4d/OHXZv0M5zfchR9JB/RNaGhW1ffN4AgsdQJ4vLBSdf4i5QNlzMTC7sQ5eFjKCGrAp20cht69eDM94UTmTEizWDTH3tGRNL0Z7qyQUA4p21QY0GCcsMYt/r3XY6OSpbSz9iqu6bByX6u1mZzv/Tbd4C5SyA8hHHuTth6/PqAUhKvkfzg8MhVfGYx2hVTvZlun5jnXJyZXX4tqbzE2hZbpSSdTG2LvvJ9fBCuqeTs9UQTVaRZsp07l4OKReIlHDZrcyt01Gq+HrvWtpnX8JxgBLSMtO88BAfS8sgTQjtE/OC5jcRbrr7b7WYVpwUjeUa5hxgEnMJbcWn3fE2RxWWTQUFJM99UTUuSQm6xygc2u7cBcphdnQdqHyrT+DL6WQHFxGo9wbhXuq4MChvOsilqyWmml3tjUYlfv8CqP88ltb56bFD3Igk1cyaLrIE01sPs7tdfrKQf8t4ljW6cAPSwf16VKKCR5dBx250S5ru76+dqdVk3h2XY3eAp2EjiSSSfFVW+ZyOy22+4b10pZCe1ruqdc6sUE0Y4QzgRGBy6Ek9MjXEijVjc+5X9ALS8ED6N0cgmWaclk/cw8AHQKCcBP3HlnccjK4dQXBOw97YolYSYNJFWljJUpm69TgFfyf2RyWeN2kCHqzRBDS105UOsjfgUf9yf8ubHKFITlyqhvMzl6dCBfUDgQgcMFccnMxXPEZy3g24m/kjyB9WiBI2KBn6SbC4BkiAOqjLB9f/pb1nn/6aILeY+iqirs77ucWkMz/H3Xlk5lCNos57g70r5gCBtjnTbWMFHs1m6X43v9iOsr3I4zPq4/QdEwpXnz9gBLz63n+kR/IT54ugiliYMdFRSyrTNyGd1L2mCczdxk5AapxVVZTQHdl3bMU1/Mu1YjGLh1F/ipug35VvlIfL+Y1Wded3vdFB0DAciG/8m0na6mQyfHYxEurt4Fq4ifkxMOP2NVh5CXOHCSbnHyqTDj5XvMT6fcnEEHE+Z25ALC4GI4kkHsMfoejdINmiJePTYDjCSgSBAoFCCul0HVeTH/VUBMotlf3LPhRuWGWuT3/Tk/yIR0JASSeCc03YUR0lsf1rCZEyWhPc1n1e6Rwlp8fCEa3J/Q7i/pHNXSWE5EJKlU8Jm4VH8J/oetSa+7NKgCeSUFtBcg00zg2YBfuam/dd53LM1FyJYRWV4ONWpL3Mhxi0TuSVb9j3BJL7R56wiMuuq/tVgsNzujhWdezYVo1oZTJrFj6LLdK4DHD51BK3erc62FlSGw7vcHFRVhgE8y6EJFcqKTVBhqJ0Qmr6XgTBA12BKArAjs4QG5/AwrDrImDqrB7udkAutzx2vHQ2ytR",
+  "sha1" : "8c44014b96f0f41f3daa3825d4af410233372b65"
+},
+{
+  "device" : "Pixel 5",
+  "manufacturer" : "google",
+  "algorithm" : "SHA256_RSA4096",
+  "pubk" : "AAAQAJQ75c2T8qdhsNTAlO37M/QNDoIIS7jUkIqOINM+UygqTj8/BVbIih31wOT7oglvXR+3baLR90iAqrTCBhhMrWPw6cdFE5IrpIho+4y7heWCc8VCkR4MBsygEHuseYtB0WJegHnI3YxNOx9HyDo3HuWFkYx8VNlOSNCep6Ce2Xbn1BYjOTIUeWOzee0UY0lLCyriFAf6YkLDmplI1jYBWs4R0TANTJQlMzOThratZXW39IDLuCmlX6r0Wg5ljT25tb767ZG2NHUTQJoLHV95iOyUwuKZa/hvUOmOM0JGVDcVXpzwUX0HN2Q5i4LBulxtQVFrol5yHO1jbK+el76FMhlgZHjEto6peUmZodwLzXybAgbakK3vF8V91DOy0dnkbyHiejzgMhPCCat6wNjhtYs0EMsOtS+WOzxdxFUZMp+gPQrT3f+5HTBkfpTNHdvhiyxZsM+Xqyi4O5OX8LSszWMEviq67OkQawgc9tbsNX6tCc0gH9EiYro/ecOcI/JA5yfJce5gJlIUvavgFzsi9DFQqHcnVevd/C0D6CderCwv5QuGqGfSkt3FJv6ITmFAG7IhYU4Y9A/OkLInZChiQ4nxL+c63K1dVFmNidwQpFwo0+49hvZ9VJ5HsL6px7Z167VAVOkReFjBbqOPYAeq0Xy3wZoCxQapuqxO+VUtR4H5S6xw+ygZzGpXfJJA0BNIdxsOEO3DRlv1lx4Sa6efHYBhodtelrayF19izHpHHRifFMwQgDz1mOU9AcM4cvpqYKIhFaH9d6wRMsBif0L+rT1LJh691bfLR5P5EyPhrb28+1/agYI6EIt83Aaz3d9SnamqAqyGR2E86o8HXGo8x5NAPdp7UM1yt7rOyhNwcG+dVj535XsS2hzHsaUn+PWWanLF/bSuoOYwCbLf22eCmE08uCZW2JHJdzRCwMXy3ZSGbYxOCfaMcaHOpl//4KcHZNBd4j07ODKXiE8rueB/hfMx2NMmxpkeCIf1dxQsFgnC3iHtixgMVRkxuQTAh7ePOu9yEgj17ImTFOVLm1BaBBnd7dDdg7bgiOMloH8+1HvPWerDnz8ghLE044nz73NSsixKvEAm8wAeWbbG8/uOjCsi5opYh1oBgdjUTQEjsu8CApbvjrh8//f8/46n+/SJgBB0AJKY+F9pRk0p2duX2a1R/zbpM4Fa06qjvwRycyGw8A4gdmwFt7189dYjYAukPoQ4qONJn7BXQMFM2EhNLOUyfdz8SmekEL5d9NVG2UB0m9mYdYCbwgPvCZQAsqH3i2pNslx/9GbRS59Q6dSqsDDk2TSFst/2biBiAyl7glFoN/bggoGESxPc3t43Mi/O/ym3l1qd7oQGav7wZL0J8iZ/I575",
+  "sha1" : "6908461c2ccd5568d1f39016a9da5aec5b10a785"
+},
+{
+  "device" : "Pixel 5a",
+  "manufacturer" : "google",
+  "algorithm" : "SHA256_RSA4096",
+  "pubk" : "AAAQAP2EZ6+lhRYjp4cE4WW11okXlC20K5OJWj7iX0bmAGcmPMtWNJt3Jq/w+5ZvfRMa5xy3CMZeJICV1XeD7R/Pwcs9Wadd+DR+Lsai209qJiuMv6R4Y+eF+EacD7NunRpDdaDGnTxdaWJUbYDSFDrwLO1PHBavewd3ujVCmuh1zaQ2CW3Y1lo0UvmO0tISRr4yvR5Ia+zaufQ0TtmlWwX6se7jJ0/ap9Z4wQYJS9R8WDRqV+XX9QUDuOU/w6TI4hB9oYDjiysCtzJe9zibK6jSNq1/3CfOm4GuaCLQEJseoqgsNh25MkTqg4rmmPsk+5Kcylr2gDFRmu4NOJ/sCRIVDXkigrz2WDaB3UaBTN/JZMjuFvxk6LAQSURE4oFuljzfTvnp2bIYbqZ8NUTHuzhEDwelj0eChAkW9hQ85OBTs23lJmAYQxJ+WqYixrB1lpmgz7MUzc/2PJBXLh96FdLJcU2jCzCfrAAepELWsutPfVcE8rOFAdrAF6PPeHUN87pfrbGZx/oj1Nkf0kOic77dqHERqWw//7WngbzHPahjyX33OheZK+xK5tzx2EUX09yXMK84d25UipJuHQ0SsBMM3EmI1b9U2IDEHfhIg5QpJFO5xF1DQyLTu5lG9mAqJcGAjnIt47Jkv3s5i6zBlmXWJJicgnnbDjN7sn/w4LiZY2Uu8ZSwsSobI5EDG5vIdD7NlueFB5zDoqhm19eG2z/QfywOdFlgsfYPDmg1JikeWnpDb3Yy5xPb1ua5TFYItduid0aM+VkoL/KbPt2oVgh4O2uZTFebUHMMCnyGw2zwfjeh4xrD9mD5xWdcjo3KYB1nuv/GtkNEDXpsUxIII4E3ipjfnNHJBz0UPlYnuxDB34KpvHoa/GG8wEWJCDcL0dZX5/KaaiTHwlypWUXnSi3rlm+qcMivO14f8LFmmabMEetiA2YIVqtIXB4MCIYKYr5aF6879LpHA2bFI/LXyGf6pL0tM1CAiQXZwNc+WHC24ujCngT/QD2nRdqWDKuwycG/DtRJVnn+Z5WVD28PZotv/6ODT/4f1OLgM9ufq38Vm2SETZNYCZr4Zcn5bgR7ObqyTnD+vCcpfTDifVp8xPlHT1iY7gdb0gSV8ZIIXpcxUB0kBpM6jO2HJST9hVOBQfMo3V5xjBJYkFwcrCI0bC58zf344C145YuUKchByC5NCkJXHe7YcD9Hut2fIxcZqO08w5XDgJucfS6hMOaspt7XhibxwsE/xF33pgqRz9kFOJ6DnjhGbvbWo3VfNpGdeBfu8vmSpNKI3plaxImWEaK360KHCBVgwqLrhoIgf91FdT6MSO7gGVOHbB+o41UZ8mjE71yrIWA4Opp4/qpVUJhVR0aR6z8d",
+  "sha1" : "14e3fd68feb4fe7c9837787ed218d083050574da"
+},
+{
+  "device" : "Pixel 6",
+  "manufacturer" : "google",
+  "algorithm" : "SHA256_RSA4096",
+  "pubk" : "AAAQAPzSq6nF6uIU7cpTp90ATW+SFDH+L1Rv8NBew/USm5nNdFNhm+HXbWwz6cADn/dtUEd+eksUc6uxisT4EwziErAMbgYyN81+W1ndYFhp4zU1C0YhlS+LnlPxY+rNPmShpFvMIvMCH8GK24tO50FjjTBZrz5kwGfJt0kF7jQwZKQr6e3PMaKYZH11jeAdasEoxwsMjX31U74N8T4r92H8caaLzGJm2qaXn2Nec1HIWwUn7GSigG9yDf6RSzlc6PhKYZvcNG8AmiqghBxuN9HrBc4zo5z2IIm0+W2zxzoPkoRQrKR5LLRoBAifw9zO/xTiiuz/LcDjOesFrdk/vWKH1BJN3bx2etaH7qpqqQo3gjfc//VBZrZu1GelxL1KBNYQ+somh1NGUUKm3WDHtDBzMOrCph4zUyKLU5sqQIzVaqk3mjD4QpLM3nhsuZfheSDEFv38fmX34w0Y34bdogOqM7qsWOAEkxX4SugVrEakxuNoTDaXi765vxnrXcsgyWaalMZpsNra9AJujXATlfvBSRLqd/++B83voe41rnCLJUR3TRIy5PQiE7Zvpv2HZn0ix7EVg0ZRVRKBjubcAVFrtD8pXStqA5v6Jt4sy4g8QKHecgmKj3oad37ubWFDEQIRiEYceAA1xYRwCbMrJoFaBysNUuYp4a68FeZiRqbIg5pxC8LXZ5WfbGOcq4xzXkqk327J/1UxdTLvQT/HKkcefRPbrCh7yEFYSn+XCNCx+w+/GdYyKJ6X3sYPP6Ko4R2O+N7FC7SPfn7qfl9yIOeRcvX6EHaXXMfA4PG8EW7dNZvPXa5Sp1g79w2aFEIRWolj6czvgPfC0fHvflFrd60t1K9WiqxvcQFG5spOUrhqltobGjHo6FS7ohWiX5sE9mJxUZap7AR7avAkAWibFBSyv59uxt4KzXZGqsZJw2vuEWeImXAIc14xSkLLJgeoI0ZIfxqrs+sctno4HMwr7tdzMKpTS8Ni/sJSVplmSzdTJ01V74uDLC7VreL+rjXvYLcBV9363/knhX0Os+O01aTSVwGsYBbKwpEuiqonS4yNs5G2NydQvHoDaXUNAzztMd/UmC4WHZOV+vM/daqXAniafJx3lXi+Zgxw3nl/vU2mgNNek2U1Y5xmefhKjBwVhqmIzZY4Lab6Cobs7sEmXrwOe+578PjnFzzs0MDEaJd6vJExVOe5pLvWhUk4vBFb0eK8JcbY65rXWfOJhm0DKlMd7yIidIQaYK0drCNhCM4Fr33YJmQcFBo2QgLsmESnayMJgmPU/KhK3BwZU0OdUJbsGupzvoJIGdnFrp+0oTxO/X4Mr5ArUmoMSFx+rq6x0sMBJNt/kxvvFKcETHN9oRn15V8pRpDl",
+  "sha1" : "85322346680a860c091fa14a64cef1fe4a3ffe31"
+},
+{
+  "device" : "Pixel 6pro",
+  "manufacturer" : "google",
+  "algorithm" : "SHA256_RSA4096",
+  "pubk" : "AAAQAAw4tB+0tXQVo8gxwKDukkqG5UfUBSsSy/Y46e/wBitpPv6EwpFsTzg8Jw+Xlgw1wXDJPk0nWnAWk2CrxI//64nVRiOMYquuL4PlUyB0JHkeDH9+2EH6YsOZABRUHkj8KvKovqFWxjXyS7pGbFTYxrZn2f04A1GfYJK9sG0TvW2CvuJs2/MgflXLie4N8V1hpjiuP5Qsu7xEzh/uJrzWDBKLEdgC8FjkxufHK366KaYqfrUuuX/bgQpO5MfzynFaf+PysG3YR2+JN2rnd4hpgqFt73qILm8EQ6Ls4gWijloA6M+MV4QW+jMtKjKhBCNITPLSjZx/0GgZNN5MS2/7kq4dNgmjBCP5qFL8V1xa+r1sdDG2WA3n5uEnW4WxddHgrTVg0F6owpHyaQ28uyNFRwKbUK5td7rS5RDFOS4f3Qpy9Gp0VAFXVylxUtpzJqU8e4UkgfdXDgOVcZrH80NtGasEKgZjvuvTVxv4WBVsyOqIxwv/rpMKm/zNUXxRLeX57UFRfhHTXi/Oyjz+ImGFeMSuv3vS3tIE1bH89sKDL9u6KshjqClK5Gv0p47XSSMaHzh2WDG7SM4Lf3zuqwOv1v1iN2QECaYeBDRuroMItmpqftP4D09p3SylEnK8DeJ8MHPFJHbjfMCo0S1GIs6maKNycYKr2SBKPGnjFaPpFqPLIXY4IU5bEKur+soemPDVylpRL2wV4UnUdhMgQAaESRTNqln4rIyNUCZRJPROshq5F4R8dX7Iy+XIb1B1ctI/njVNP+87qirrJmol71qnRPRSJ02SXUb48/cAjXVKqb678/9MoTwjW/t0XXDWPWJFnXC/zc0gATFz8oZdLtnizCkjeXZkkM4v916lvcQ2Zha2GXT2ZC11NU/K20rYjgtyRg2RT3MRfgwtwymjGMl3lI4PG4m0DL63foVpBvG6TDnIp2FJXbBoBZLfaSquP1A8Yqr0fcMfRPeWiTCfCiKIaXVN09SAsJA/ZB+VjeC3hX5hO/PMDfYBzgaJFOCfpLCqaEVZi0MKPgFoXcCc+wyIt2aW5LyplUEB7DS4BKnckZM1SP6MJZfBBalEcpbsB0g2koZHBDtC6I0C0F7pEJedATUCG8jHNha8wOTKZf2+I/nvJP7UMER+kjADHuyqr6TFvSbch3H7zOqT+oVPaxTqLA8tQ45cTNjeGPLb4CghfHrvKqDqQDkpF+w0zBLFbrzs0Ab3OKfaF7nhVPn7Qp/g41pF3Xo7pv/HVXwIL6WnbQhyJmQsdxFnVjndzFzkNwoL+it2v/cBXhAFbacm385E44DEZNTSehxgofGn5Y4ShVeVd2t3hC167vpBMsyxf9ilvn0jYeA8CTK7+ec2H0jrCBNyBxY5",
+  "sha1" : "5f9cdcb2a3e84c93eb031529eb7b3a05b1887861"
+},
+{
+  "device" : "Xiaomi Phone",
+  "manufacturer" : "Mi",
+  "algorithm" : "SHA256_RSA2048",
+  "pubk" : "AAAIAN3C1onuaVOoEOIIpxRB4slj0xuKT7t2yrvDzvHQhOAlM8vz+rnR//6imJ+ET07/dMvAE893FM5LnFQLNcUJ+FYIGjbYdzFCVSYErfVTq96e37/sxVT86Zuz392q8OBkY4UFzpNdwwzWq6J+0tAsEyu778ECITiPceTJgol7IxzjIljHccK3ffP9dQf1FziB6BBTOQBKj26J2KCqtSmXM3Kk4GeV0wdCMZUUcuWXGrt0UjmrD+xHa8jSj9+YRb7BWe+RfYCXtqrAaree/Q24GAm4nSznDjfSH4T50JiNBkaeOf9YZwQlh67JtFZbueUP5prlQ3eXmubddodnA0LdDkOJooBHvMZAM/73+AWtsf145vMRUdtuvj5Dp9shR+kHaBoB78kQ2vUcrT7eetQynrH1TtwpXzXqAfzHRfv1tw2icvPnjFYV7m3njNcrb05Bx/oMvqKzrx99kCZEWuVm8un3gC/ucW9cXkAB+onTQnfJbeS5XcbEPcZkuQ0b7BepCScYnMrlOMSClYYl8OPNK576YRa2KQxdWEbdukihNIhaZhBN/tqdszA2w2960rO70ektljBVPP6m2jcT5iV0spMiers5KtqZ8cy++Urhvg0mWiuPcnue4L7W59lf9bJVlkaFrBfOHvxMdgmmrHCgZd9278zJrYDgFK17kojwSqqqwCkoFQ==",
+  "sha1" : "b2a02f1e56e366d727a1a8e089762fe0b91bbc84"
+}
+]
diff --git a/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt
index 707f565..3233634 100644
--- a/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt
+++ b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt
@@ -234,30 +234,14 @@ class CryptoHelper {
                 return MessageDigest.getInstance("SHA-256").digest(inData)
             }
 
-            //fun hash(file: String, algorithm: String): ByteArray {
-            //    val md = MessageDigest.getInstance(algorithm)
-            //    FileInputStream(file).use { fis ->
-            //        val buffer = ByteArray(1024 * 1024)
-            //        while (true) {
-            //            val bytesRead = fis.read(buffer)
-            //            if (bytesRead <= 0) break
-            //            md.update(buffer, 0, bytesRead)
-            //        }
-            //    }
-            //    return md.digest()
-            //}
-            fun hash(file: String, algorithm: String): ByteArray {
-                return hash(file, listOf(Pair(0, File(file).length())), algorithm)
-            }
-
-            fun hash(file: String, coordinates: List<Pair<Long, Long>>, algorithm: String): ByteArray {
+            fun hash(ds: Dumpling<*>, coordinates: List<Pair<Long, Long>>, algorithm: String): ByteArray {
                 require(coordinates.isNotEmpty())
                 coordinates.forEach {
                     require(it.first >= 0 && it.second > 0)
                 }
                 return MessageDigest.getInstance(algorithm).let { md ->
                     coordinates.forEach { coordinate ->
-                        FileInputStream(file).use { fis ->
+                        ds.getInputStream().use { fis ->
                             fis.skip(coordinate.first)
                             val ibs = 1024 * 1024
                             val buffer = ByteArray(ibs)
@@ -280,6 +264,14 @@ class CryptoHelper {
                     md
                 }.digest()
             }
+
+            fun hash(file: String, algorithm: String): ByteArray {
+                return hash(Dumpling(file), listOf(Pair(0, File(file).length())), algorithm)
+            }
+
+            fun hash(ds: Dumpling<*>, algorithm: String): ByteArray {
+                return hash(ds, listOf(Pair(0, ds.getLength())), algorithm)
+            }
         }
     }
 
diff --git a/helper/src/main/kotlin/cfig/helper/Dumpling.kt b/helper/src/main/kotlin/cfig/helper/Dumpling.kt
new file mode 100644
index 0000000..e7cefbe
--- /dev/null
+++ b/helper/src/main/kotlin/cfig/helper/Dumpling.kt
@@ -0,0 +1,93 @@
+package cfig.helper
+
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.InputStream
+
+class Dumpling<in T>(private val filling: T, private val label: String? = null) {
+    fun getLabel(): String {
+        return label ?: getName()
+    }
+
+    fun getName(): String {
+        return if (filling is String) "FILE:$filling" else "ByteArray"
+    }
+
+    fun getInputStream(): InputStream {
+        return when (filling) {
+            is String -> {
+                FileInputStream(filling)
+            }
+            is ByteArray -> {
+                ByteArrayInputStream(filling)
+            }
+            else -> {
+                throw IllegalArgumentException("type ${filling!!::class} is not supported")
+            }
+        }
+    }
+
+    fun getLength(): Long {
+        return when (filling) {
+            is String -> {
+                File(filling).length()
+            }
+            is ByteArray -> {
+                filling.size.toLong()
+            }
+            else -> {
+                throw IllegalArgumentException("type ${filling!!::class} is not supported")
+            }
+        }
+    }
+
+    @Throws(IllegalArgumentException::class)
+    fun readFully(range: LongRange): ByteArray {
+        when (filling) {
+            is String -> {
+                return ByteArray(range.count()).apply {
+                    FileInputStream(filling).use { fis ->
+                        fis.skip(range.first)
+                        fis.read(this)
+                    }
+                }
+            }
+            is ByteArray -> {
+                return filling.sliceArray(range.first.toInt()..range.last.toInt())
+            }
+            else -> {
+                throw IllegalArgumentException("type ${filling!!::class} is not supported")
+            }
+        }
+    }
+
+    @Throws(IllegalArgumentException::class)
+    fun readFully(loc: Pair<Long, Int>): ByteArray {
+        when (filling) {
+            is String -> {
+                return ByteArray(loc.second).apply {
+                    FileInputStream(filling).use { fis ->
+                        if (loc.first < 0) {
+                            fis.skip(getLength() + loc.first)
+                        } else {
+                            fis.skip(loc.first)
+                        }
+                        fis.read(this)
+                    }
+                }
+            }
+            is ByteArray -> {
+                val subRangeStart = if (loc.first < 0) {
+                    (getLength() + loc.first).toInt()
+                } else {
+                    loc.first.toInt()
+                }
+                return filling.sliceArray(subRangeStart until (subRangeStart + loc.second).toInt())
+            }
+            else -> {
+                throw IllegalArgumentException("type ${filling!!::class} is not supported")
+            }
+        }
+    }
+}
diff --git a/helper/src/main/kotlin/cfig/helper/Helper.kt b/helper/src/main/kotlin/cfig/helper/Helper.kt
index f066cbc..272fac7 100644
--- a/helper/src/main/kotlin/cfig/helper/Helper.kt
+++ b/helper/src/main/kotlin/cfig/helper/Helper.kt
@@ -38,75 +38,6 @@ class Helper {
         var dumpFile: String
     )
 
-    class DataSrc<in T>(private val incoming: T) {
-        fun getName(): String {
-            return if (incoming is String) "FILE:$incoming" else "data"
-        }
-
-        fun getLength(): Long {
-            return when (incoming) {
-                is String -> {
-                    File(incoming).length()
-                }
-                is ByteArray -> {
-                    incoming.size.toLong()
-                }
-                else -> {
-                    throw IllegalArgumentException("type ${incoming!!::class} is not supported")
-                }
-            }
-        }
-
-        @Throws(IllegalArgumentException::class)
-        fun readFully(range: LongRange): ByteArray {
-            when (incoming) {
-                is String -> {
-                    return ByteArray(range.count()).apply {
-                        FileInputStream(incoming).use { fis ->
-                            fis.skip(range.first)
-                            fis.read(this)
-                        }
-                    }
-                }
-                is ByteArray -> {
-                    return incoming.sliceArray(range.first.toInt()..range.last.toInt())
-                }
-                else -> {
-                    throw IllegalArgumentException("type ${incoming!!::class} is not supported")
-                }
-            }
-        }
-
-        @Throws(IllegalArgumentException::class)
-        fun readFully(loc: Pair<Long, Int>): ByteArray {
-            when (incoming) {
-                is String -> {
-                    return ByteArray(loc.second).apply {
-                        FileInputStream(incoming).use { fis ->
-                            if (loc.first < 0) {
-                                fis.skip(getLength() + loc.first)
-                            } else {
-                                fis.skip(loc.first)
-                            }
-                            fis.read(this)
-                        }
-                    }
-                }
-                is ByteArray -> {
-                    val subRangeStart = if (loc.first < 0) {
-                        (getLength() + loc.first).toInt()
-                    } else {
-                        loc.first.toInt()
-                    }
-                    return incoming.sliceArray(subRangeStart until (subRangeStart + loc.second).toInt())
-                }
-                else -> {
-                    throw IllegalArgumentException("type ${incoming!!::class} is not supported")
-                }
-            }
-        }
-    }
-
     companion object {
         private val gcfg: Properties = Properties().apply {
             load(Helper::class.java.classLoader.getResourceAsStream("general.cfg"))
diff --git a/helper/src/main/kotlin/cfig/helper/ZipHelper.kt b/helper/src/main/kotlin/cfig/helper/ZipHelper.kt
index f82fdf5..c923b1c 100644
--- a/helper/src/main/kotlin/cfig/helper/ZipHelper.kt
+++ b/helper/src/main/kotlin/cfig/helper/ZipHelper.kt
@@ -352,6 +352,11 @@ class ZipHelper {
             }
         }
 
+        fun isAndroidCpio(compressedFile: String): Boolean {
+            return Dumpling(compressedFile).readFully(0L..5)
+                .contentEquals(byteArrayOf(0x30, 0x37, 0x30, 0x37, 0x30, 0x31))
+        }
+
         fun isGZ(compressedFile: String): Boolean {
             return try {
                 FileInputStream(compressedFile).use { fis ->
diff --git a/helper/src/test/kotlin/cfig/helper/CryptoHelperTest.kt b/helper/src/test/kotlin/cfig/helper/CryptoHelperTest.kt
index 7e6760f..458b115 100644
--- a/helper/src/test/kotlin/cfig/helper/CryptoHelperTest.kt
+++ b/helper/src/test/kotlin/cfig/helper/CryptoHelperTest.kt
@@ -1,7 +1,6 @@
 package cfig.helper
 
 import org.junit.Assert.assertEquals
-import org.junit.Test
 import java.io.File
 
 class CryptoHelperTest {
@@ -35,7 +34,7 @@ class CryptoHelperTest {
 
     fun geographicalHash() {
         val f = "/home/work/boot/payload.bin"
-        val dg = CryptoHelper.Hasher.hash(f, listOf(Pair(0, 1862657060)), "sha-256")
+        val dg = CryptoHelper.Hasher.hash(Dumpling(f), listOf(Pair(0, 1862657060)), "sha-256")
         println(Helper.toHexString(dg))
     }
 }
diff --git a/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt b/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt
index 3bbfcb7..20f18ef 100644
--- a/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt
+++ b/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt
@@ -54,9 +54,9 @@ class ZipHelperTest {
     @Test
     fun testDataSrc() {
         if (File("/proc/cpuinfo").exists()) {
-            val ds1 = Helper.DataSrc("/proc/cpuinfo")
+            val ds1 = Dumpling("/proc/cpuinfo")
             Assert.assertTrue(ds1.readFully(0L..31).contentEquals(ds1.readFully(Pair(0, 32))))
-            val d2 = Helper.DataSrc(ds1.readFully(0L..31))
+            val d2 = Dumpling(ds1.readFully(0L..31))
             Assert.assertTrue(d2.readFully(0..15L).contentEquals(d2.readFully(Pair(0, 16))))
         }
     }