Compare commits

...

1 commit

Author SHA1 Message Date
c343074839
feat: discovery 2025-03-10 10:00:01 +08:00
9 changed files with 219 additions and 13 deletions

View file

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

View file

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

View file

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

View file

@ -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?,

View file

@ -0,0 +1,5 @@
package net.typeblog.lpac_jni
interface ProfileDiscoveryCallback {
fun onDiscovered(hosts: Array<String>)
}

View file

@ -9,6 +9,7 @@ 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 +94,9 @@ class LocalProfileAssistantImpl(
LpacJni.euiccSetMss(contextHandle, mss)
}
override fun getEuiccConfiguredAddresses() =
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
@ -212,7 +216,7 @@ class LocalProfileAssistantImpl(
callback
)
if (res != 0) {
if (res == 0) return
// Construct the error now to store any error information we _can_ access
val err = LocalProfileAssistant.ProfileDownloadException(
lpaErrorReason = LpacJni.downloadErrCodeToString(-res),
@ -227,6 +231,17 @@ class LocalProfileAssistantImpl(
throw err
}
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

View file

@ -0,0 +1,122 @@
#include "lpac-notifications.h"
#include <euicc/es10a.h>
#include <euicc/es10b.h>
#include <euicc/es9p.h>
#include <string.h>
#include <malloc.h>
#include <syslog.h>
#define EUICC_CONFIGURED_ADDRESSES_CLASS "net/typeblog/lpac_jni/EuiccConfiguredAddresses"
jclass euicc_configured_addresses_class;
jmethodID euicc_configured_addresses_constructor;
jmethodID on_discovered;
#define DISCOVERY_CALLBACK_CLASS "net/typeblog/lpac_jni/ProfileDiscoveryCallback"
#define STRING_CLASS "java/lang/String"
void lpac_discovery_init() {
LPAC_JNI_SETUP_ENV;
jclass download_callback_class = (*env)->FindClass(env, DISCOVERY_CALLBACK_CLASS);
on_discovered = (*env)->GetMethodID(env, download_callback_class, "onDiscovered",
"([L" STRING_CLASS ";)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, "<init>",
"(L" STRING_CLASS ";L" STRING_CLASS ";)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) {
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);
}
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 = -1;
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;
}
jsize n = 0;
for (n = 0; smdp_list[n] != NULL; n++) continue;
addresses = (*env)->NewObjectArray(env, n, string_class, NULL);
for (jsize index = 0; index < n; index++) {
jstring element = toJString(env, smdp_list[index]);
(*env)->SetObjectArrayElement(env, addresses, index, element);
}
(*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;
}

View file

@ -0,0 +1,6 @@
#pragma once
#include <jni.h>
#include "lpac-jni.h"
void lpac_discovery_init();

View file

@ -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;
@ -21,6 +22,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm;
interface_wrapper_init();
lpac_download_init();
lpac_discovery_init();
LPAC_JNI_SETUP_ENV;
string_class = (*env)->FindClass(env, "java/lang/String");