feat: discovery (lpac-jni) #169

Closed
septs wants to merge 6 commits from septs:smds into master
13 changed files with 222 additions and 10 deletions

View file

@ -1,9 +1,11 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import net.typeblog.lpac_jni.EuiccConfiguredAddresses
import net.typeblog.lpac_jni.EuiccInfo2 import net.typeblog.lpac_jni.EuiccInfo2
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.LocalProfileNotification import net.typeblog.lpac_jni.LocalProfileNotification
import net.typeblog.lpac_jni.ProfileDiscoveryCallback
import net.typeblog.lpac_jni.ProfileDownloadCallback import net.typeblog.lpac_jni.ProfileDownloadCallback
class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) : class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
@ -32,6 +34,9 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
override fun setEs10xMss(mss: Byte) = lpa.setEs10xMss(mss) override fun setEs10xMss(mss: Byte) = lpa.setEs10xMss(mss)
override fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses =
lpa.getEuiccConfiguredAddresses()
override fun enableProfile(iccid: String, refresh: Boolean): Boolean = override fun enableProfile(iccid: String, refresh: Boolean): Boolean =
lpa.enableProfile(iccid, refresh) lpa.enableProfile(iccid, refresh)
@ -48,6 +53,9 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
callback: ProfileDownloadCallback callback: ProfileDownloadCallback
) = lpa.downloadProfile(smdp, matchingId, imei, confirmationCode, callback) ) = 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 deleteNotification(seqNumber: Long): Boolean = lpa.deleteNotification(seqNumber)
override fun handleNotification(seqNumber: Long): Boolean = lpa.handleNotification(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?, val lastApduException: Exception?,
) : Exception("Failed to download profile") ) : 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 ProfileRenameException() : Exception("Failed to rename profile")
class ProfileNameTooLongException() : Exception("Profile name too long") class ProfileNameTooLongException() : Exception("Profile name too long")
class ProfileNameIsInvalidUTF8Exception() : Exception("Profile name is invalid UTF-8") class ProfileNameIsInvalidUTF8Exception() : Exception("Profile name is invalid UTF-8")
@ -30,6 +39,9 @@ interface LocalProfileAssistant {
*/ */
fun setEs10xMss(mss: Byte) fun setEs10xMss(mss: Byte)
// es10a
fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses
// All blocking functions in this class assume that they are executed on non-Main threads // 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. // The IO context in Kotlin's coroutine library is recommended.
fun enableProfile(iccid: String, refresh: Boolean = true): Boolean fun enableProfile(iccid: String, refresh: Boolean = true): Boolean
@ -38,6 +50,7 @@ interface LocalProfileAssistant {
fun downloadProfile(smdp: String, matchingId: String?, imei: String?, fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
confirmationCode: String?, callback: ProfileDownloadCallback) confirmationCode: String?, callback: ProfileDownloadCallback)
fun discoveryProfile(smds: String, imei: String?, callback: ProfileDiscoveryCallback)
fun deleteNotification(seqNumber: Long): Boolean fun deleteNotification(seqNumber: Long): Boolean
fun handleNotification(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 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 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 // es9p + es10b
// We do not expose all of the functions because of tediousness :) // We do not expose all of the functions because of tediousness :)
external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?, external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?,

View file

@ -0,0 +1,7 @@
package net.typeblog.lpac_jni
import java.util.ArrayList
interface ProfileDiscoveryCallback {
fun onDiscovered(servers: ArrayList<String>)
}

View file

@ -3,12 +3,14 @@ package net.typeblog.lpac_jni.impl
import android.util.Log import android.util.Log
import net.typeblog.lpac_jni.LpacJni import net.typeblog.lpac_jni.LpacJni
import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.EuiccConfiguredAddresses
import net.typeblog.lpac_jni.EuiccInfo2 import net.typeblog.lpac_jni.EuiccInfo2
import net.typeblog.lpac_jni.HttpInterface import net.typeblog.lpac_jni.HttpInterface
import net.typeblog.lpac_jni.HttpInterface.HttpResponse import net.typeblog.lpac_jni.HttpInterface.HttpResponse
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.LocalProfileNotification import net.typeblog.lpac_jni.LocalProfileNotification
import net.typeblog.lpac_jni.ProfileDiscoveryCallback
import net.typeblog.lpac_jni.ProfileDownloadCallback import net.typeblog.lpac_jni.ProfileDownloadCallback
import net.typeblog.lpac_jni.Version import net.typeblog.lpac_jni.Version
@ -93,6 +95,9 @@ class LocalProfileAssistantImpl(
LpacJni.euiccSetMss(contextHandle, mss) LpacJni.euiccSetMss(contextHandle, mss)
} }
override fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses =
LpacJni.es10aGetEuiccConfiguredAddresses(contextHandle)
override val valid: Boolean override val valid: Boolean
get() = !finalized && apduInterface.valid && try { get() = !finalized && apduInterface.valid && try {
// If we can read both eID and euiccInfo2 properly, we are likely looking at // 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 @Synchronized
override fun deleteNotification(seqNumber: Long): Boolean = override fun deleteNotification(seqNumber: Long): Boolean =
LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0 LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0

View file

@ -13,8 +13,7 @@ jmethodID method_http_transmit;
jfieldID field_resp_rcode; jfieldID field_resp_rcode;
jfieldID field_resp_data; jfieldID field_resp_data;
void interface_wrapper_init() { void interface_wrapper_init(JNIEnv *env) {
LPAC_JNI_SETUP_ENV;
jclass apdu_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ApduInterface"); jclass apdu_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ApduInterface");
method_apdu_connect = (*env)->GetMethodID(env, apdu_class, "connect", "()V"); method_apdu_connect = (*env)->GetMethodID(env, apdu_class, "connect", "()V");
method_apdu_disconnect = (*env)->GetMethodID(env, apdu_class, "disconnect", "()V"); method_apdu_disconnect = (*env)->GetMethodID(env, apdu_class, "disconnect", "()V");

View file

@ -5,7 +5,7 @@
#include <euicc/interface.h> #include <euicc/interface.h>
#include "lpac-jni.h" #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_apdu_interface lpac_jni_apdu_interface;
extern struct euicc_http_interface lpac_jni_http_interface; extern struct euicc_http_interface lpac_jni_http_interface;

View file

@ -0,0 +1,126 @@
#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>
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, "<init>",
"(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, "<init>", "()V");
jmethodID add_element = (*env)->GetMethodID(env, array_list_class, "add", "(Ljava/lang/Object;)Z");
// val addresses = new ArrayList<String>();
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;
}

View file

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

View file

@ -13,9 +13,7 @@ jobject download_state_finalizing;
jmethodID on_state_update; jmethodID on_state_update;
void lpac_download_init() { void lpac_download_init(JNIEnv *env) {
LPAC_JNI_SETUP_ENV;
jclass download_state_class = (*env)->FindClass(env, jclass download_state_class = (*env)->FindClass(env,
"net/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState"); "net/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState");
jfieldID download_state_preparing_field = (*env)->GetStaticFieldID(env, download_state_class, jfieldID download_state_preparing_field = (*env)->GetStaticFieldID(env, download_state_class,

View file

@ -3,4 +3,4 @@
#include <jni.h> #include <jni.h>
#include "lpac-jni.h" #include "lpac-jni.h"
void lpac_download_init(); void lpac_download_init(JNIEnv *env);

View file

@ -8,6 +8,7 @@
#include "lpac-jni.h" #include "lpac-jni.h"
#include "lpac-download.h" #include "lpac-download.h"
#include "lpac-notifications.h" #include "lpac-notifications.h"
#include "lpac-discovery.h"
#include "interface-wrapper.h" #include "interface-wrapper.h"
JavaVM *jvm = NULL; JavaVM *jvm = NULL;
@ -19,10 +20,12 @@ jmethodID string_constructor;
jint JNI_OnLoad(JavaVM *vm, void *reserved) { jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm; jvm = vm;
interface_wrapper_init();
lpac_download_init();
LPAC_JNI_SETUP_ENV; 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)->FindClass(env, "java/lang/String");
string_class = (*env)->NewGlobalRef(env, string_class); string_class = (*env)->NewGlobalRef(env, string_class);
string_constructor = (*env)->GetMethodID(env, string_class, "<init>", string_constructor = (*env)->GetMethodID(env, string_class, "<init>",