feat: discovery (lpac-jni) #169
13 changed files with 222 additions and 10 deletions
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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?,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package net.typeblog.lpac_jni
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
interface ProfileDiscoveryCallback {
|
||||
fun onDiscovered(servers: ArrayList<String>)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include <euicc/interface.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_http_interface lpac_jni_http_interface;
|
||||
|
|
126
libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.c
Normal file
126
libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.c
Normal 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;
|
||||
}
|
6
libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.h
Normal file
6
libs/lpac-jni/src/main/jni/lpac-jni/lpac-discovery.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
#include "lpac-jni.h"
|
||||
|
||||
void lpac_discovery_init(JNIEnv *env);
|
|
@ -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,
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
#include <jni.h>
|
||||
#include "lpac-jni.h"
|
||||
|
||||
void lpac_download_init();
|
||||
void lpac_download_init(JNIEnv *env);
|
|
@ -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, "<init>",
|
||||
|
|
Loading…
Add table
Reference in a new issue