瀏覽代碼

refactor: [11/n] Implement es9p/es10b profile downloading

Peter Cai 2 年之前
父節點
當前提交
f5f146d8ee

+ 8 - 5
app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt

@@ -17,6 +17,7 @@ import im.angry.openeuicc.util.setWidthPercent
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import net.typeblog.lpac_jni.ProfileDownloadCallback
 import java.lang.Exception
 
 class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
@@ -137,11 +138,13 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
     }
 
     private suspend fun doDownloadProfile(server: String, code: String) = withContext(Dispatchers.IO) {
-        channel.lpa.downloadProfile("1\$${server}\$${code}", channel.imei/*, DownloadProgress().apply {
-            setProgressListener { _, _, percentage, _ ->
-                progress.isIndeterminate = false
-                progress.progress = (percentage * 100).toInt()
+        channel.lpa.downloadProfile(server, code, channel.imei, object : ProfileDownloadCallback {
+            override fun onStateUpdate(state: ProfileDownloadCallback.DownloadState) {
+                lifecycleScope.launch(Dispatchers.Main) {
+                    progress.isIndeterminate = false
+                    progress.progress = state.progress
+                }
             }
-        }*/)
+        })
     }
 }

+ 1 - 1
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt

@@ -8,7 +8,7 @@ interface LocalProfileAssistant {
     fun disableProfile(iccid: String): Boolean
     fun deleteProfile(iccid: String): Boolean
 
-    fun downloadProfile(matchingId: String, imei: String)
+    fun downloadProfile(smdp: String, matchingId: String, imei: String, callback: ProfileDownloadCallback): Boolean
 
     fun setNickname(
         iccid: String, nickname: String

+ 4 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt

@@ -20,4 +20,8 @@ internal object LpacJni {
     external fun es10cDisableProfile(handle: Long, iccid: String): Int
     external fun es10cDeleteProfile(handle: Long, iccid: String): Int
     external fun es10cSetNickname(handle: Long, iccid: String, nick: String): 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, callback: ProfileDownloadCallback): Int
 }

+ 13 - 0
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt

@@ -0,0 +1,13 @@
+package net.typeblog.lpac_jni
+
+interface ProfileDownloadCallback {
+    enum class DownloadState(val progress: Int) {
+        Preparing(0),
+        Connecting(20), // Before {server,client} authentication
+        Authenticating(40), // {server,client} authentication
+        Downloading(60), // prepare download, get bpp from es9p
+        Finalizing(80), // load bpp
+    }
+
+    fun onStateUpdate(state: DownloadState)
+}

+ 16 - 1
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/HttpInterfaceImpl.kt

@@ -1,9 +1,24 @@
 package net.typeblog.lpac_jni.impl
 
 import net.typeblog.lpac_jni.HttpInterface
+import java.net.HttpURLConnection
+import java.net.URL
 
 class HttpInterfaceImpl: HttpInterface {
     override fun transmit(url: String, tx: ByteArray): HttpInterface.HttpResponse {
-        TODO("Not yet implemented")
+        android.util.Log.d("aaa", url)
+        val conn = URL(url).openConnection() as HttpURLConnection
+        conn.requestMethod = "POST"
+        conn.doInput = true
+        conn.doOutput = true
+        conn.setRequestProperty("User-Agent", "gsma-rsp-lpad")
+        conn.setRequestProperty("X-Admin-Protocol", "gsma/rsp/v2.2.0")
+        conn.setRequestProperty("Content-Type", "application/json")
+
+        conn.outputStream.write(tx)
+        conn.outputStream.flush()
+        conn.outputStream.close()
+
+        return HttpInterface.HttpResponse(conn.responseCode, conn.inputStream.readBytes())
     }
 }

+ 5 - 2
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt

@@ -5,6 +5,7 @@ import net.typeblog.lpac_jni.ApduInterface
 import net.typeblog.lpac_jni.HttpInterface
 import net.typeblog.lpac_jni.LocalProfileAssistant
 import net.typeblog.lpac_jni.LocalProfileInfo
+import net.typeblog.lpac_jni.ProfileDownloadCallback
 
 class LocalProfileAssistantImpl(
     apduInterface: ApduInterface,
@@ -36,8 +37,10 @@ class LocalProfileAssistantImpl(
         return LpacJni.es10cDeleteProfile(contextHandle, iccid) == 0
     }
 
-    override fun downloadProfile(matchingId: String, imei: String) {
-        TODO("Not yet implemented")
+    override fun downloadProfile(smdp: String, matchingId: String, imei: String, callback: ProfileDownloadCallback): Boolean {
+        // See SGP.22, check digit of IMEI needs a "F" filler
+        // TODO: Do this in lpac-jni or lpac itself?
+        return LpacJni.downloadProfile(contextHandle, smdp, matchingId, imei + "F", callback) == 0
     }
 
     override fun setNickname(iccid: String, nickname: String): Boolean {

+ 1 - 0
libs/lpac-jni/src/main/jni/Android.mk

@@ -42,5 +42,6 @@ LOCAL_C_INCLUDES := \
 	$(LOCAL_PATH)/lpac
 LOCAL_SRC_FILES := \
 	lpac-jni/lpac-jni.c \
+	lpac-jni/lpac-download.c \
 	lpac-jni/interface-wrapper.c
 include $(BUILD_SHARED_LIBRARY)

+ 1 - 1
libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c

@@ -74,7 +74,7 @@ static int apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t
 
 static int http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint32_t *rcode, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len) {
     LPAC_JNI_SETUP_ENV;
-    jstring jurl = (*env)->NewString(env, url, strlen(url));
+    jstring jurl = toJString(env, url);
     jbyteArray txArr = (*env)->NewByteArray(env, tx_len);
     (*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx);
     jobject ret = (*env)->CallObjectMethod(env, LPAC_JNI_CTX(ctx)->http_interface, method_http_transmit, jurl, txArr);

+ 122 - 0
libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c

@@ -0,0 +1,122 @@
+#include <euicc/es9p.h>
+#include <euicc/es10b.h>
+#include <stdlib.h>
+#include <string.h>
+#include "lpac-download.h"
+
+jobject download_state_preparing;
+jobject download_state_connecting;
+jobject download_state_authenticating;
+jobject download_state_downloading;
+jobject download_state_finalizing;
+
+jmethodID on_state_update;
+
+void lpac_download_init() {
+    LPAC_JNI_SETUP_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, "Preparing", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
+    download_state_preparing = (*env)->GetStaticObjectField(env, download_state_class, download_state_preparing_field);
+    download_state_preparing = (*env)->NewGlobalRef(env, download_state_preparing);
+    jfieldID download_state_connecting_field = (*env)->GetStaticFieldID(env, download_state_class, "Connecting", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
+    download_state_connecting = (*env)->GetStaticObjectField(env, download_state_class, download_state_connecting_field);
+    download_state_connecting = (*env)->NewGlobalRef(env, download_state_connecting);
+    jfieldID download_state_authenticating_field = (*env)->GetStaticFieldID(env, download_state_class, "Authenticating", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
+    download_state_authenticating = (*env)->GetStaticObjectField(env, download_state_class, download_state_authenticating_field);
+    download_state_authenticating = (*env)->NewGlobalRef(env, download_state_authenticating);
+    jfieldID download_state_downloading_field = (*env)->GetStaticFieldID(env, download_state_class, "Downloading", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
+    download_state_downloading = (*env)->GetStaticObjectField(env, download_state_class, download_state_downloading_field);
+    download_state_downloading = (*env)->NewGlobalRef(env, download_state_downloading);
+    jfieldID download_state_finalizng_field = (*env)->GetStaticFieldID(env, download_state_class, "Finalizing", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
+    download_state_finalizing = (*env)->GetStaticObjectField(env, download_state_class, download_state_finalizng_field);
+    download_state_finalizing = (*env)->NewGlobalRef(env, download_state_finalizing);
+
+    jclass download_callback_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ProfileDownloadCallback");
+    on_state_update = (*env)->GetMethodID(env, download_callback_class, "onStateUpdate", "(Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;)V");
+}
+
+JNIEXPORT jint JNICALL
+Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, jlong handle,
+                                                    jstring smdp, jstring matching_id, jstring imei,
+                                                    jobject callback) {
+    struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
+    struct es9p_get_bound_profile_package_resp es9p_get_bound_profile_package_resp;
+    struct es9p_initiate_authentication_resp es9p_initiate_authentication_resp;
+    struct es10b_authenticate_server_param es10b_authenticate_server_param;
+    struct es9p_authenticate_client_resp es9p_authenticate_client_resp;
+    struct es10b_prepare_download_param es10b_prepare_download_param;
+    char *b64_authenticate_server_response = NULL;
+    char *b64_prepare_download_response = NULL;
+    char *b64_euicc_challenge = NULL;
+    char *b64_euicc_info_1 = NULL;
+    char *transaction_id = NULL;
+    const char *_matching_id = NULL;
+    const char *_smdp = NULL;
+    const char *_imei = NULL;
+    int ret;
+
+    _matching_id = (*env)->GetStringUTFChars(env, matching_id, NULL);
+    _smdp = (*env)->GetStringUTFChars(env, smdp, NULL);
+    _imei = (*env)->GetStringUTFChars(env, imei, NULL);
+
+    (*env)->CallVoidMethod(env, callback, on_state_update, download_state_preparing);
+    ret = es10b_get_euicc_challenge(ctx, &b64_euicc_challenge);
+    if (ret < 0)
+        goto out;
+
+    ret = es10b_get_euicc_info(ctx, &b64_euicc_info_1);
+    if (ret < 0)
+        goto out;
+
+    (*env)->CallVoidMethod(env, callback, on_state_update, download_state_connecting);
+    ret = es9p_initiate_authentication(ctx, _smdp, b64_euicc_challenge, b64_euicc_info_1, &es9p_initiate_authentication_resp);
+    if (ret < 0)
+        goto out;
+
+    transaction_id = strdup(es9p_initiate_authentication_resp.transaction_id);
+    es10b_authenticate_server_param.b64_server_signed_1 = es9p_initiate_authentication_resp.b64_server_signed_1;
+    es10b_authenticate_server_param.b64_server_signature_1 = es9p_initiate_authentication_resp.b64_server_signature_1;
+    es10b_authenticate_server_param.b64_euicc_ci_pkid_to_be_used = es9p_initiate_authentication_resp.b64_euicc_ci_pkid_to_be_used;
+    es10b_authenticate_server_param.b64_server_certificate = es9p_initiate_authentication_resp.b64_server_certificate;
+    es10b_authenticate_server_param.matchingId = _matching_id;
+    es10b_authenticate_server_param.imei = _imei;
+    es10b_authenticate_server_param.tac = NULL;
+
+    (*env)->CallVoidMethod(env, callback, on_state_update, download_state_authenticating);
+    ret = es10b_authenticate_server(ctx, &b64_authenticate_server_response, &es10b_authenticate_server_param);
+    if (ret < 0)
+        goto out;
+
+    ret = es9p_authenticate_client(ctx, _smdp, transaction_id, b64_authenticate_server_response, &es9p_authenticate_client_resp);
+    if (ret < 0)
+        goto out;
+
+    es10b_prepare_download_param.b64_smdp_signed_2 = es9p_authenticate_client_resp.b64_smdp_signed_2;
+    es10b_prepare_download_param.b64_smdp_signature_2 = es9p_authenticate_client_resp.b64_smdp_signature_2;
+    es10b_prepare_download_param.b64_smdp_certificate = es9p_authenticate_client_resp.b64_smdp_certificate;
+    es10b_prepare_download_param.str_checkcode = NULL; // TODO: Support confirmation code
+
+    (*env)->CallVoidMethod(env, callback, on_state_update, download_state_downloading);
+    ret = es10b_prepare_download(ctx, &b64_prepare_download_response, &es10b_prepare_download_param);
+    if (ret < 0)
+        goto out;
+
+    ret = es9p_get_bound_profile_package(ctx, _smdp, transaction_id, b64_prepare_download_response, &es9p_get_bound_profile_package_resp);
+    if (ret < 0)
+        goto out;
+
+    (*env)->CallVoidMethod(env, callback, on_state_update, download_state_finalizing);
+    ret = es10b_load_bound_profile_package(ctx, es9p_get_bound_profile_package_resp.b64_bpp);
+
+out:
+    free(b64_authenticate_server_response);
+    free(b64_prepare_download_response);
+    free(b64_euicc_info_1);
+    free(b64_euicc_challenge);
+    free(transaction_id);
+    (*env)->ReleaseStringUTFChars(env, matching_id, _matching_id);
+    (*env)->ReleaseStringUTFChars(env, smdp, _smdp);
+    (*env)->ReleaseStringUTFChars(env, imei, _imei);
+    return ret;
+}

+ 5 - 0
libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h

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

+ 2 - 0
libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c

@@ -4,6 +4,7 @@
 #include <string.h>
 #include <syslog.h>
 #include "lpac-jni.h"
+#include "lpac-download.h"
 #include "interface-wrapper.h"
 
 JavaVM  *jvm = NULL;
@@ -26,6 +27,7 @@ jmethodID string_constructor;
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     jvm = vm;
     interface_wrapper_init();
+    lpac_download_init();
 
     LPAC_JNI_SETUP_ENV;
     string_class = (*env)->FindClass(env, "java/lang/String");

+ 3 - 1
libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h

@@ -13,4 +13,6 @@ struct lpac_jni_ctx {
     JNIEnv *env; \
     (*jvm)->AttachCurrentThread(jvm, &env, NULL)
 
-extern JavaVM *jvm;
+extern JavaVM *jvm;
+
+jstring toJString(JNIEnv *env, const char *pat);