diff --git a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt index b715ca0..f19616d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt @@ -1,9 +1,11 @@ package im.angry.openeuicc.core +import net.typeblog.lpac_jni.EuiccConfiguredAddresses import net.typeblog.lpac_jni.EuiccInfo2 import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileNotification +import net.typeblog.lpac_jni.ProfileDiscoveryCallback import net.typeblog.lpac_jni.ProfileDownloadCallback class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) : @@ -32,6 +34,9 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) : override fun setEs10xMss(mss: Byte) = lpa.setEs10xMss(mss) + override fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses = + lpa.getEuiccConfiguredAddresses() + override fun enableProfile(iccid: String, refresh: Boolean): Boolean = lpa.enableProfile(iccid, refresh) @@ -48,6 +53,9 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) : callback: ProfileDownloadCallback ) = lpa.downloadProfile(smdp, matchingId, imei, confirmationCode, callback) + override fun discoveryProfile(smds: String, imei: String?, callback: ProfileDiscoveryCallback) = + lpa.discoveryProfile(smds, imei, callback) + override fun deleteNotification(seqNumber: Long): Boolean = lpa.deleteNotification(seqNumber) override fun handleNotification(seqNumber: Long): Boolean = lpa.handleNotification(seqNumber) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccConfiguredAddresses.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccConfiguredAddresses.kt new file mode 100644 index 0000000..e7f090a --- /dev/null +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccConfiguredAddresses.kt @@ -0,0 +1,29 @@ +package net.typeblog.lpac_jni + +import android.util.Patterns + +// example address in GSMA SGP.26, some chips use addresses like this +@Suppress("SpellCheckingInspection") +val invalidDPAddresses = setOf( + "testrootsmds.gsma.com", + "testrootsmds.example.com", +) + +class EuiccConfiguredAddresses(defaultDPAddress: String?, rootDSAddress: String?) { + val defaultDPAddress: String? = defaultDPAddress.takeUnless(::isInvalidDPAddress) + val rootDSAddress = rootDSAddress.takeUnless(::isInvalidDSAddress) + + val discoverable: Boolean + get() = !defaultDPAddress.isNullOrBlank() || !rootDSAddress.isNullOrBlank() +} + +private fun isInvalidDPAddress(address: String?): Boolean { + if (address.isNullOrBlank()) return true + return !Patterns.DOMAIN_NAME.matcher(address).matches() +} + +private fun isInvalidDSAddress(address: String?): Boolean { + if (address.isNullOrBlank()) return true + if (address in invalidDPAddresses) return true + return !Patterns.DOMAIN_NAME.matcher(address).matches() +} diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt index 48ab1c5..d98c241 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt @@ -12,6 +12,15 @@ interface LocalProfileAssistant { val lastApduException: Exception?, ) : Exception("Failed to download profile") + @Suppress("ArrayInDataClass") + data class ProfileDiscoveryException( + val lpaErrorReason: String, + val lastHttpResponse: HttpResponse?, + val lastHttpException: Exception?, + val lastApduResponse: ByteArray?, + val lastApduException: Exception?, + ) : Exception("Failed to discovery profile") + class ProfileRenameException() : Exception("Failed to rename profile") class ProfileNameTooLongException() : Exception("Profile name too long") class ProfileNameIsInvalidUTF8Exception() : Exception("Profile name is invalid UTF-8") @@ -30,6 +39,9 @@ interface LocalProfileAssistant { */ fun setEs10xMss(mss: Byte) + // es10a + fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses + // All blocking functions in this class assume that they are executed on non-Main threads // The IO context in Kotlin's coroutine library is recommended. fun enableProfile(iccid: String, refresh: Boolean = true): Boolean @@ -38,6 +50,7 @@ interface LocalProfileAssistant { fun downloadProfile(smdp: String, matchingId: String?, imei: String?, confirmationCode: String?, callback: ProfileDownloadCallback) + fun discoveryProfile(smds: String, imei: String?, callback: ProfileDiscoveryCallback) fun deleteNotification(seqNumber: Long): Boolean fun handleNotification(seqNumber: Long): Boolean diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt index fa9474f..bf57f63 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt @@ -29,6 +29,12 @@ internal object LpacJni { external fun es10bListNotification(handle: Long): Long // A native pointer to a linked list. Handle with linked list-related methods below. May be 0 (null) external fun es10bDeleteNotification(handle: Long, seqNumber: Long): Int + // es10a + external fun es10aGetEuiccConfiguredAddresses(handle: Long): EuiccConfiguredAddresses + + // es9p + es11 + external fun discoveryProfile(handle: Long, address: String, imei: String?, callback: ProfileDiscoveryCallback): Int + // es9p + es10b // We do not expose all of the functions because of tediousness :) external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?, diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDiscoveryCallback.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDiscoveryCallback.kt new file mode 100644 index 0000000..4cd7761 --- /dev/null +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDiscoveryCallback.kt @@ -0,0 +1,7 @@ +package net.typeblog.lpac_jni + +import java.util.ArrayList + +interface ProfileDiscoveryCallback { + fun onDiscovered(servers: ArrayList) +} diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index 3674f4f..e9856cd 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -3,12 +3,14 @@ package net.typeblog.lpac_jni.impl import android.util.Log import net.typeblog.lpac_jni.LpacJni import net.typeblog.lpac_jni.ApduInterface +import net.typeblog.lpac_jni.EuiccConfiguredAddresses import net.typeblog.lpac_jni.EuiccInfo2 import net.typeblog.lpac_jni.HttpInterface import net.typeblog.lpac_jni.HttpInterface.HttpResponse import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileNotification +import net.typeblog.lpac_jni.ProfileDiscoveryCallback import net.typeblog.lpac_jni.ProfileDownloadCallback import net.typeblog.lpac_jni.Version @@ -93,6 +95,9 @@ class LocalProfileAssistantImpl( LpacJni.euiccSetMss(contextHandle, mss) } + override fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses = + LpacJni.es10aGetEuiccConfiguredAddresses(contextHandle) + override val valid: Boolean get() = !finalized && apduInterface.valid && try { // If we can read both eID and euiccInfo2 properly, we are likely looking at @@ -229,6 +234,18 @@ class LocalProfileAssistantImpl( } } + override fun discoveryProfile(smds: String, imei: String?, callback: ProfileDiscoveryCallback) { + val res = LpacJni.discoveryProfile(contextHandle, smds, imei, callback) + if (res == 0) return + throw LocalProfileAssistant.ProfileDiscoveryException( + lpaErrorReason = LpacJni.downloadErrCodeToString(-res), + httpInterface.lastHttpResponse, + httpInterface.lastHttpException, + apduInterface.lastApduResponse, + apduInterface.lastApduException, + ) + } + @Synchronized override fun deleteNotification(seqNumber: Long): Boolean = LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0 diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c index a61fc96..df27e92 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c @@ -13,8 +13,7 @@ jmethodID method_http_transmit; jfieldID field_resp_rcode; jfieldID field_resp_data; -void interface_wrapper_init() { - LPAC_JNI_SETUP_ENV; +void interface_wrapper_init(JNIEnv *env) { jclass apdu_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ApduInterface"); method_apdu_connect = (*env)->GetMethodID(env, apdu_class, "connect", "()V"); method_apdu_disconnect = (*env)->GetMethodID(env, apdu_class, "disconnect", "()V"); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h index e22837b..fec3869 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h @@ -5,7 +5,7 @@ #include #include "lpac-jni.h" -void interface_wrapper_init(); +void interface_wrapper_init(JNIEnv *env); extern struct euicc_apdu_interface lpac_jni_apdu_interface; extern struct euicc_http_interface lpac_jni_http_interface; diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.c new file mode 100644 index 0000000..b0954d3 --- /dev/null +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.c @@ -0,0 +1,126 @@ +#include "lpac-notifications.h" +#include +#include +#include +#include +#include +#include + +jclass euicc_configured_addresses_class; +jmethodID euicc_configured_addresses_constructor; + +jmethodID on_discovered; + +#define EUICC_CONFIGURED_ADDRESSES_CLASS "net/typeblog/lpac_jni/EuiccConfiguredAddresses" +#define DISCOVERY_CALLBACK_CLASS "net/typeblog/lpac_jni/ProfileDiscoveryCallback" + +void lpac_discovery_init(JNIEnv *env) { + jclass download_callback_class = (*env)->FindClass(env, DISCOVERY_CALLBACK_CLASS); + on_discovered = (*env)->GetMethodID(env, download_callback_class, "onDiscovered", + "(Ljava/util/ArrayList;)V"); + + euicc_configured_addresses_class = (*env)->FindClass(env, EUICC_CONFIGURED_ADDRESSES_CLASS); + euicc_configured_addresses_class = (*env)->NewGlobalRef(env, euicc_configured_addresses_class); + euicc_configured_addresses_constructor = (*env)->GetMethodID( + env, euicc_configured_addresses_class, "", + "(Ljava/lang/String;Ljava/lang/String;)V"); +} + +JNIEXPORT jobject JNICALL +Java_net_typeblog_lpac_1jni_LpacJni_es10aGetEuiccConfiguredAddresses( + JNIEnv *env, + __attribute__((unused)) jobject thiz, + jlong handle +) { + struct euicc_ctx *ctx = (struct euicc_ctx *) handle; + struct es10a_euicc_configured_addresses addresses; + jobject ret = NULL; + if (es10a_get_euicc_configured_addresses(ctx, &addresses) < 0) { + goto out; + } + jstring default_dp_address = toJString(env, addresses.defaultDpAddress); + jstring root_ds_address = toJString(env, addresses.rootDsAddress); + ret = (*env)->NewObject(env, euicc_configured_addresses_class, + euicc_configured_addresses_constructor, + default_dp_address, root_ds_address); + out: + es10a_euicc_configured_addresses_free(&addresses); + return ret; +} + +JNIEXPORT jint JNICALL +Java_net_typeblog_lpac_1jni_LpacJni_discoveryProfile( + JNIEnv *env, + __attribute__((unused)) jobject thiz, + jlong handle, + jstring address, + jstring imei, + jobject callback +) { + struct euicc_ctx *ctx = (struct euicc_ctx *) handle; + + const char *_address = (*env)->GetStringUTFChars(env, address, NULL); + const char *_imei = NULL; + + if (imei != NULL) { + _imei = (*env)->GetStringUTFChars(env, address, NULL); + } + + ctx->http.server_address = _address; + + char **smdp_list = NULL; + jobjectArray addresses = NULL; + + int ret; + + ret = es10b_get_euicc_challenge_and_info(ctx); + syslog(LOG_INFO, "es10b_get_euicc_challenge_and_info %d", ret); + if (ret < 0) { + ret = -ES10B_ERROR_REASON_UNDEFINED; + goto out; + } + + ret = es9p_initiate_authentication(ctx); + syslog(LOG_INFO, "es9p_initiate_authentication %d", ret); + if (ret < 0) { + ret = -ES10B_ERROR_REASON_UNDEFINED; + goto out; + } + + ret = es10b_authenticate_server(ctx, NULL, _imei); + syslog(LOG_INFO, "es10b_authenticate_server %d", ret); + if (ret < 0) { + ret = -ES10B_ERROR_REASON_UNDEFINED; + goto out; + } + + ret = es11_authenticate_client(ctx, &smdp_list); + syslog(LOG_INFO, "es11_authenticate_client %d", ret); + if (ret < 0) { + ret = -ES10B_ERROR_REASON_UNDEFINED; + goto out; + } + + jclass array_list_class = (*env)->FindClass(env, "java/util/ArrayList"); + jmethodID array_list_constructor = (*env)->GetMethodID(env, array_list_class, "", "()V"); + jmethodID add_element = (*env)->GetMethodID(env, array_list_class, "add", "(Ljava/lang/Object;)Z"); + + // val addresses = new ArrayList(); + addresses = (*env)->NewObject(env, array_list_class, array_list_constructor); + + for (jsize index = 0; smdp_list[index] != NULL; index++) { + jstring element = toJString(env, smdp_list[index]); + // addresses.add(smdp_list[index]); + (*env)->CallBooleanMethod(env, addresses, add_element, element); + } + + // callback.onDiscovered(addresses); + (*env)->CallVoidMethod(env, callback, on_discovered, addresses); + + out: + + if (_imei != NULL) (*env)->ReleaseStringUTFChars(env, imei, _imei); + (*env)->ReleaseStringUTFChars(env, address, _address); + es11_smdp_list_free_all(smdp_list); + return ret; +} \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.h new file mode 100644 index 0000000..d66e1c2 --- /dev/null +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include "lpac-jni.h" + +void lpac_discovery_init(JNIEnv *env); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c index bae2ee8..1f1931f 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c @@ -13,9 +13,7 @@ jobject download_state_finalizing; jmethodID on_state_update; -void lpac_download_init() { - LPAC_JNI_SETUP_ENV; - +void lpac_download_init(JNIEnv *env) { jclass download_state_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState"); jfieldID download_state_preparing_field = (*env)->GetStaticFieldID(env, download_state_class, diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h index 7130f52..b68bc99 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h @@ -3,4 +3,4 @@ #include #include "lpac-jni.h" -void lpac_download_init(); \ No newline at end of file +void lpac_download_init(JNIEnv *env); \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index ca319db..fe7b078 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -8,6 +8,7 @@ #include "lpac-jni.h" #include "lpac-download.h" #include "lpac-notifications.h" +#include "lpac-discovery.h" #include "interface-wrapper.h" JavaVM *jvm = NULL; @@ -19,10 +20,12 @@ jmethodID string_constructor; jint JNI_OnLoad(JavaVM *vm, void *reserved) { jvm = vm; - interface_wrapper_init(); - lpac_download_init(); - LPAC_JNI_SETUP_ENV; + + interface_wrapper_init(env); + lpac_download_init(env); + lpac_discovery_init(env); + string_class = (*env)->FindClass(env, "java/lang/String"); string_class = (*env)->NewGlobalRef(env, string_class); string_constructor = (*env)->GetMethodID(env, string_class, "",