support configuration

This commit is contained in:
5ec1cff
2024-07-11 15:38:27 +08:00
parent d55dc13e87
commit 236384b398
5 changed files with 220 additions and 142 deletions

View File

@@ -0,0 +1,19 @@
package io.github.a13e300.tricky_store;
import android.util.Log;
public class Logger {
private static final String TAG = "TrickyStore";
public static void d(String msg) {
Log.d(TAG, msg);
}
public static void e(String msg) {
Log.e(TAG, msg);
}
public static void e(String msg, Throwable t) {
Log.e(TAG, msg, t);
}
}

View File

@@ -3,7 +3,9 @@ package io.github.a13e300.tricky_store
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.IPackageManager import android.content.pm.IPackageManager
import android.os.Binder import android.os.Binder
import android.os.FileObserver
import android.os.IBinder import android.os.IBinder
import android.os.Looper
import android.os.Parcel import android.os.Parcel
import android.os.ServiceManager import android.os.ServiceManager
import android.system.keystore2.IKeystoreService import android.system.keystore2.IKeystoreService
@@ -11,6 +13,7 @@ import android.system.keystore2.KeyEntryResponse
import android.util.Log import android.util.Log
import io.github.a13e300.tricky_store.fwpatch.Android import io.github.a13e300.tricky_store.fwpatch.Android
import io.github.a13e300.tricky_store.fwpatch.Utils import io.github.a13e300.tricky_store.fwpatch.Utils
import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
open class BinderInterceptor : Binder() { open class BinderInterceptor : Binder() {
@@ -113,7 +116,8 @@ fun registerBinderInterceptor(backdoor: IBinder, target: IBinder, interceptor: B
backdoor.transact(1, data, reply, 0) backdoor.transact(1, data, reply, 0)
} }
val targetPackages = arrayOf("com.google.android.gms", "icu.nullptr.nativetest", "io.github.vvb2060.mahoshojo", "io.github.vvb2060.keyattestation") val targetPackages = mutableSetOf<String>()
val DEFAULT_TARGET_PACKAGES = listOf("com.google.android.gms", "icu.nullptr.nativetest", "io.github.vvb2060.mahoshojo", "io.github.vvb2060.keyattestation")
const val TAG = "TrickyStore" const val TAG = "TrickyStore"
@@ -138,6 +142,40 @@ fun getPm(): IPackageManager? {
return iPm return iPm
} }
fun updateTargetPackages(f: File) = runCatching {
targetPackages.clear()
f.readLines().mapNotNullTo(targetPackages) {
if (it.isNotBlank() && !it.startsWith("#")) it else null
}
Logger.d("update target packages: $targetPackages")
}.onFailure {
Logger.e("failed to update target files", it)
}
fun updateKeyBox(f: File) = runCatching {
Android.readFromXml(f.readText())
}.onFailure {
Logger.e("failed to update keybox", it)
}
const val CONFIG_PATH = "/data/adb/tricky_store"
const val TARGET_FILE = "target.txt"
const val KEYBOX_FILE = "keybox.xml"
class ConfigObserver : FileObserver(CONFIG_PATH, CLOSE_WRITE) {
val root = File(CONFIG_PATH)
override fun onEvent(event: Int, path: String?) {
path ?: return
if (event == CLOSE_WRITE) {
val f = File(root, path)
when (f.name) {
TARGET_FILE -> updateTargetPackages(f)
KEYBOX_FILE -> updateKeyBox(f)
}
}
}
}
@SuppressLint("BlockedPrivateApi") @SuppressLint("BlockedPrivateApi")
fun tryRunKeystoreInterceptor(): Boolean { fun tryRunKeystoreInterceptor(): Boolean {
val b = ServiceManager.getService("android.system.keystore2.IKeystoreService/default") ?: return false val b = ServiceManager.getService("android.system.keystore2.IKeystoreService/default") ?: return false
@@ -195,6 +233,22 @@ fun tryRunKeystoreInterceptor(): Boolean {
return Skip return Skip
} }
} }
val path = File(CONFIG_PATH)
path.mkdirs()
val target = File(path, TARGET_FILE)
if (!target.exists()) {
target.createNewFile()
target.writeText(DEFAULT_TARGET_PACKAGES.joinToString("\n"))
}
updateTargetPackages(target)
val keybox = File(path, KEYBOX_FILE)
if (!keybox.exists()) {
Logger.e("keybox file not found, please put it to $keybox !")
} else {
updateKeyBox(keybox)
}
val monitor = ConfigObserver()
monitor.startWatching()
registerBinderInterceptor(bd, b, interceptor) registerBinderInterceptor(bd, b, interceptor)
while (true) { while (true) {
Thread.sleep(1000000) Thread.sleep(1000000)

View File

@@ -37,46 +37,65 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import io.github.a13e300.tricky_store.Logger;
public final class Android { public final class Android {
private static final String TAG = "chiteroman"; private static final String TAG = "chiteroman";
private static final PEMKeyPair EC, RSA;
private static final ASN1ObjectIdentifier OID = new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17"); private static final ASN1ObjectIdentifier OID = new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17");
private static final List<Certificate> EC_CERTS = new ArrayList<>();
private static final List<Certificate> RSA_CERTS = new ArrayList<>(); record KeyBox(PEMKeyPair privateKey, List<Certificate> certificates) {}
private static final Map<String, String> map = new HashMap<>(); private static final Map<String, KeyBox> keyboxes = new HashMap<>();
private static final CertificateFactory certificateFactory; private static final CertificateFactory certificateFactory;
static { static {
map.put("MANUFACTURER", "Google");
map.put("MODEL", "Pixel");
map.put("FINGERPRINT", "google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys");
map.put("BRAND", "google");
map.put("PRODUCT", "sailfish");
map.put("DEVICE", "sailfish");
map.put("RELEASE", "8.1.0");
map.put("ID", "OPM1.171019.011");
map.put("INCREMENTAL", "4448085");
map.put("SECURITY_PATCH", "2017-12-05");
map.put("TYPE", "user");
map.put("TAGS", "release-keys");
try { try {
certificateFactory = CertificateFactory.getInstance("X.509"); certificateFactory = CertificateFactory.getInstance("X.509");
EC = parseKeyPair(Keybox.EC.PRIVATE_KEY);
EC_CERTS.add(parseCert(Keybox.EC.CERTIFICATE_1));
EC_CERTS.add(parseCert(Keybox.EC.CERTIFICATE_2));
RSA = parseKeyPair(Keybox.RSA.PRIVATE_KEY);
RSA_CERTS.add(parseCert(Keybox.RSA.CERTIFICATE_1));
RSA_CERTS.add(parseCert(Keybox.RSA.CERTIFICATE_2));
} catch (Throwable t) { } catch (Throwable t) {
Log.e(TAG, t.toString()); Log.e(TAG, t.toString());
throw new RuntimeException(t); throw new RuntimeException(t);
} }
} }
public static void readFromXml(String data) {
keyboxes.clear();
XMLParser xmlParser = new XMLParser(data);
try {
int numberOfKeyboxes = Integer.parseInt(Objects.requireNonNull(xmlParser.obtainPath(
"AndroidAttestation.NumberOfKeyboxes").get("text")));
for (int i = 0; i < numberOfKeyboxes; i++) {
String keyboxAlgorithm = xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[" + i + "]").get("algorithm");
String privateKey = xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[" + i + "].PrivateKey").get("text");
int numberOfCertificates = Integer.parseInt(Objects.requireNonNull(xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[" + i + "].CertificateChain.NumberOfCertificates").get("text")));
LinkedList<Certificate> certificateChain = new LinkedList<>();
for (int j = 0; j < numberOfCertificates; j++) {
Map<String,String> certData= xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[" + i + "].CertificateChain.Certificate[" + j + "]");
certificateChain.add(parseCert(certData.get("text")));
}
String algo;
if (keyboxAlgorithm.toLowerCase().equals("ecdsa")) {
algo = KeyProperties.KEY_ALGORITHM_EC;
} else {
algo = KeyProperties.KEY_ALGORITHM_RSA;
}
keyboxes.put(algo, new KeyBox(parseKeyPair(privateKey), certificateChain));
}
Logger.d("update " + numberOfKeyboxes + " keyboxes");
} catch (Throwable t) {
Logger.e("Error loading xml file: " + t);
}
}
private static PEMKeyPair parseKeyPair(String key) throws Throwable { private static PEMKeyPair parseKeyPair(String key) throws Throwable {
try (PEMParser parser = new PEMParser(new StringReader(key))) { try (PEMParser parser = new PEMParser(new StringReader(key))) {
return (PEMKeyPair) parser.readObject(); return (PEMKeyPair) parser.readObject();
@@ -104,7 +123,7 @@ public final class Android {
} }
public static Certificate[] engineGetCertificateChain(Certificate[] caList) { public static Certificate[] engineGetCertificateChain(Certificate[] caList) {
if (caList == null) throw new UnsupportedOperationException(); if (caList == null) throw new UnsupportedOperationException("caList is null!");
try { try {
X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded())); X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded()));
@@ -135,15 +154,12 @@ public final class Android {
X509v3CertificateBuilder builder; X509v3CertificateBuilder builder;
ContentSigner signer; ContentSigner signer;
if (KeyProperties.KEY_ALGORITHM_EC.equals(leaf.getPublicKey().getAlgorithm())) { var k = keyboxes.get(leaf.getPublicKey().getAlgorithm());
certificates = new LinkedList<>(EC_CERTS); if (k == null) throw new UnsupportedOperationException("unsupported algorithm " + leaf.getPublicKey().getAlgorithm());
builder = new X509v3CertificateBuilder(new X509CertificateHolder(EC_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), EC.getPublicKeyInfo()); certificates = new LinkedList<>(k.certificates);
signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(EC.getPrivateKeyInfo())); builder = new X509v3CertificateBuilder(new X509CertificateHolder(certificates.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), k.privateKey.getPublicKeyInfo());
} else { signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(k.privateKey.getPrivateKeyInfo()));
certificates = new LinkedList<>(RSA_CERTS);
builder = new X509v3CertificateBuilder(new X509CertificateHolder(RSA_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), RSA.getPublicKeyInfo());
signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(RSA.getPrivateKeyInfo()));
}
byte[] verifiedBootKey = new byte[32]; byte[] verifiedBootKey = new byte[32];
byte[] verifiedBootHash = new byte[32]; byte[] verifiedBootHash = new byte[32];

View File

@@ -1,107 +0,0 @@
package io.github.a13e300.tricky_store.fwpatch;
public final class Keybox {
public static final class EC {
public static final String PRIVATE_KEY = """
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
-----END EC PRIVATE KEY-----
""";
public static final String CERTIFICATE_1 = """
-----BEGIN CERTIFICATE-----
MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD
VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu
ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx
MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB
bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz
dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue
efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8
U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R
qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG
AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8
wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9
Xvsiu+f+uXc/WT/7
-----END CERTIFICATE-----
""";
public static final String CERTIFICATE_2 = """
-----BEGIN CERTIFICATE-----
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
-----END CERTIFICATE-----
""";
}
public static final class RSA {
public static final String PRIVATE_KEY = """
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb
KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI
O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB
AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3
wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C
woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs
NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU
0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy
DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY
ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3
TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y
qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x
fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l
-----END RSA PRIVATE KEY-----
""";
public static final String CERTIFICATE_1 = """
-----BEGIN CERTIFICATE-----
MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx
MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv
aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1
qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX
4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd
XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ
k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn
M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4
Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f
9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8=
-----END CERTIFICATE-----
""";
public static final String CERTIFICATE_2 = """
-----BEGIN CERTIFICATE-----
MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN
MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE
CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs
Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL
t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID
AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw
FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j
HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc
DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op
Lvtc9mL1J1IXvmM=
-----END CERTIFICATE-----
""";
}
}

View File

@@ -0,0 +1,96 @@
package io.github.a13e300.tricky_store.fwpatch;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
public class XMLParser {
private final String xml;
public XMLParser(String xml) {
this.xml = xml;
}
public Map<String, String> obtainPath(String path) throws Exception {
XmlPullParserFactory xmlFactoryObject = XmlPullParserFactory.newInstance();
XmlPullParser parser = xmlFactoryObject.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(new StringReader(xml));
String[] tags = path.split("\\.");
return readData(parser, tags, 0, new HashMap<>());
}
private Map<String, String> readData(XmlPullParser parser, String[] tags, int index,
Map<String, Integer> tagCounts) throws IOException, XmlPullParserException {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (name.equals(tags[index].split("\\[")[0])) {
String[] tagParts = tags[index].split("\\[");
if (tagParts.length > 1) {
if (tagCounts.getOrDefault(name, 0) < Integer.parseInt(tagParts[1].replace("]", ""))) {
tagCounts.put(name, tagCounts.getOrDefault(name, 0) + 1);
return readData(parser, tags, index, tagCounts);
} else {
if (index == tags.length - 1) {
return readAttributes(parser);
} else {
return readData(parser, tags, index + 1, tagCounts);
}
}
} else {
if (index == tags.length - 1) {
return readAttributes(parser);
} else {
return readData(parser, tags, index + 1, tagCounts);
}
}
} else {
skip(parser);
}
}
throw new XmlPullParserException("Path not found");
}
private Map<String, String> readAttributes(XmlPullParser parser) throws IOException, XmlPullParserException {
Map<String, String> attributes = new HashMap<>();
for (int i = 0; i < parser.getAttributeCount(); i++) {
attributes.put(parser.getAttributeName(i), parser.getAttributeValue(i));
}
if (parser.next() == XmlPullParser.TEXT) {
attributes.put("text", parser.getText());
}
return attributes;
}
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
if (parser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (parser.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
}
}
}
}