refactor: [1/n] Introduce the lpac project and lpac_jni

This commit is contained in:
Peter Cai 2023-11-14 20:59:27 -05:00
parent 87cdb24579
commit 85af3bcfc0
24 changed files with 429 additions and 18 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "libs/lpac-jni/src/main/jni/lpac"]
path = libs/lpac-jni/src/main/jni/lpac
url = https://github.com/estkme/lpac

View file

@ -4,9 +4,10 @@
<bytecodeTargetLevel target="1.7">
<module name="OpenEUICC.app" target="17" />
<module name="OpenEUICC.libs.hidden-apis-shim" target="17" />
<module name="OpenEUICC.libs.lpad-sm-dp-plus-connector" target="1.8" />
<module name="OpenEUICC.libs.lpad-sm-dp-plus-connector.main" target="1.8" />
<module name="OpenEUICC.libs.lpad-sm-dp-plus-connector.test" target="1.8" />
<module name="OpenEUICC.libs.lpac-jni" target="17" />
<module name="OpenEUICC.libs.lpad-sm-dp-plus-connector" target="17" />
<module name="OpenEUICC.libs.lpad-sm-dp-plus-connector.main" target="17" />
<module name="OpenEUICC.libs.lpad-sm-dp-plus-connector.test" target="17" />
</bytecodeTargetLevel>
</component>
</project>

View file

@ -16,6 +16,7 @@
<option value="$PROJECT_DIR$/libs" />
<option value="$PROJECT_DIR$/libs/hidden-apis-shim" />
<option value="$PROJECT_DIR$/libs/hidden-apis-stub" />
<option value="$PROJECT_DIR$/libs/lpac-jni" />
<option value="$PROJECT_DIR$/libs/lpad-sm-dp-plus-connector" />
</set>
</option>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.6.21" />
<option name="version" value="1.9.20" />
</component>
</project>

View file

@ -37,7 +37,7 @@ def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdk 31
compileSdk 34
defaultConfig {
applicationId "im.angry.openeuicc"
@ -81,11 +81,12 @@ dependencies {
compileOnly project(':libs:hidden-apis-stub')
implementation project(':libs:hidden-apis-shim')
implementation project(":libs:lpad-sm-dp-plus-connector")
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation project(":libs:lpac-jni")
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'

View file

@ -2,8 +2,8 @@
plugins {
id 'com.android.application' version '8.1.2' apply false
id 'com.android.library' version '8.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
id 'org.jetbrains.kotlin.multiplatform' version '1.6.21' apply false
id 'org.jetbrains.kotlin.android' version '1.9.20' apply false
id 'org.jetbrains.kotlin.multiplatform' version '1.9.20' apply false
}
task clean(type: Delete) {

View file

@ -0,0 +1,46 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'net.typeblog.lpac_jni'
compileSdk 33
defaultConfig {
minSdk 27
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

View file

21
libs/lpac-jni/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,24 @@
package net.typeblog.lpac_jni
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("net.typeblog.lpac_jni.test", appContext.packageName)
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View file

@ -0,0 +1,12 @@
package net.typeblog.lpac_jni
/*
* Should reflect euicc_apdu_interface in lpac/euicc/interface.h
*/
sealed interface ApduInterface {
fun connect()
fun disconnect()
fun logicalChannelOpen(aid: ByteArray): Int
fun logicalChannelClose(handle: Int)
fun transmit(tx: ByteArray): ByteArray
}

View file

@ -0,0 +1,28 @@
package net.typeblog.lpac_jni
/*
* Should reflect euicc_http_interface in lpac/euicc/interface.h
*/
sealed interface HttpInterface {
data class HttpResponse(val rcode: Int, val data: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as HttpResponse
if (rcode != other.rcode) return false
if (!data.contentEquals(other.data)) return false
return true
}
override fun hashCode(): Int {
var result = rcode
result = 31 * result + data.contentHashCode()
return result
}
}
fun transmit(url: String, tx: ByteArray): HttpResponse
}

View file

@ -0,0 +1,11 @@
package net.typeblog.lpac_jni
private class LpacJni {
init {
System.loadLibrary("lpac-jni")
}
external fun createContext(apduInterface: ApduInterface, httpInterface: HttpInterface): Long
external fun destroyContext(handle: Long)
external fun setCurrentContext(handle: Long)
}

View file

@ -0,0 +1,46 @@
LOCAL_PATH := $(call my-dir)
# function to find all *.c files under a directory
define all-c-files-under
$(patsubst ./%,%, \
$(shell cd $(LOCAL_PATH) ; \
find $(1) -name "*.c" -and -not -name ".*" -maxdepth 1) \
)
endef
include $(CLEAR_VARS)
# libcjson
LOCAL_MODULE := lpac-cjson
LOCAL_SRC_FILES := \
$(call all-c-files-under, lpac/cjson)
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
# libasn1c, the ASN parser component from lpac
LOCAL_MODULE := lpac-asn1c
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/lpac/euicc/asn1c
LOCAL_SRC_FILES := \
$(call all-c-files-under, lpac/euicc/asn1c/asn1)
LOCAL_CFLAGS := -DHAVE_CONFIG_H
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
# libeuicc component from lpac, which contains the actual implementation
LOCAL_MODULE := lpac-euicc
LOCAL_STATIC_LIBRARIES := lpac-asn1c lpac-cjson
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/lpac
LOCAL_SRC_FILES := \
$(call all-c-files-under, lpac/euicc)
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := lpac-jni
LOCAL_STATIC_LIBRARIES := lpac-euicc
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/lpac
LOCAL_SRC_FILES := \
lpac-jni/lpac-jni.c \
lpac-jni/interface-wrapper.c
include $(BUILD_SHARED_LIBRARY)

View file

@ -0,0 +1 @@
APP_ABI := all

@ -0,0 +1 @@
Subproject commit 2eaefa6f8d79f68eff6a8c03932b861425767330

View file

@ -0,0 +1,117 @@
#include <string.h>
#include <malloc.h>
#include "interface-wrapper.h"
#include "lpac-jni.h"
jmethodID method_apdu_connect;
jmethodID method_apdu_disconnect;
jmethodID method_apdu_logical_channel_open;
jmethodID method_apdu_logical_channel_close;
jmethodID method_apdu_transmit;
jmethodID method_http_transmit;
jfieldID field_resp_rcode;
jfieldID field_resp_data;
void interface_wrapper_init() {
LPAC_JNI_SETUP_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");
method_apdu_logical_channel_open = (*env)->GetMethodID(env, apdu_class, "logicalChannelOpen", "([B)I");
method_apdu_logical_channel_close = (*env)->GetMethodID(env, apdu_class, "logicalChannelClose", "(I)V");
method_apdu_transmit = (*env)->GetMethodID(env, apdu_class, "transmit", "([B)[B");
jclass http_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/HttpInterface");
method_http_transmit = (*env)->GetMethodID(env, http_class, "transmit",
"(Ljava/lang/String;[B)Lnet/typeblog/lpac_jni/HttpInterface$HttpResponse;");
jclass resp_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/HttpInterface$HttpResponse");
field_resp_rcode = (*env)->GetFieldID(env, resp_class, "rcode", "I");
field_resp_data = (*env)->GetFieldID(env, resp_class, "data", "[B");
}
static int apdu_interface_connect(void) {
LPAC_JNI_BEGIN;
LPAC_JNI_ASSERT_CTX;
LPAC_JNI_SETUP_ENV;
(*env)->CallVoidMethod(env, jni_ctx->apdu_interface, method_apdu_connect);
LPAC_JNI_END(!((*env)->ExceptionCheck(env) == JNI_FALSE));
}
static void apdu_interface_disconnect(void) {
LPAC_JNI_BEGIN;
LPAC_JNI_ASSERT_CTX;
LPAC_JNI_SETUP_ENV;
(*env)->CallVoidMethod(env, jni_ctx->apdu_interface, method_apdu_disconnect);
LPAC_JNI_END0;
}
static int apdu_interface_logical_channel_open(const uint8_t *aid, uint8_t aid_len) {
LPAC_JNI_BEGIN;
LPAC_JNI_ASSERT_CTX;
LPAC_JNI_SETUP_ENV;
jbyteArray jbarr = (*env)->NewByteArray(env, aid_len);
(*env)->SetByteArrayRegion(env, jbarr, 0, aid_len, (const jbyte *) aid);
jint ret = (*env)->CallIntMethod(env, jni_ctx->apdu_interface, method_apdu_logical_channel_open, jbarr);
if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
LPAC_JNI_END(-1);
} else {
LPAC_JNI_END(ret);
}
}
static void apdu_interface_logical_channel_close(uint8_t channel) {
LPAC_JNI_BEGIN;
LPAC_JNI_ASSERT_CTX;
LPAC_JNI_SETUP_ENV;
(*env)->CallVoidMethod(env, jni_ctx->apdu_interface, method_apdu_logical_channel_close, channel);
LPAC_JNI_END0;
}
static int apdu_interface_transmit(uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len) {
LPAC_JNI_BEGIN;
LPAC_JNI_ASSERT_CTX;
LPAC_JNI_SETUP_ENV;
jbyteArray txArr = (*env)->NewByteArray(env, tx_len);
(*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx);
jbyteArray ret = (jbyteArray) (*env)->CallObjectMethod(env, jni_ctx->apdu_interface, method_apdu_transmit, txArr);
if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
LPAC_JNI_END(-1);
}
*rx_len = (*env)->GetArrayLength(env, ret);
*rx = malloc(*rx_len * sizeof(uint8_t));
(*env)->GetByteArrayRegion(env, ret, 0, *rx_len, *rx);
LPAC_JNI_END(0);
}
static int http_interface_transmit(const char *url, uint32_t *rcode, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len) {
LPAC_JNI_BEGIN;
LPAC_JNI_ASSERT_CTX;
LPAC_JNI_SETUP_ENV;
jstring jurl = (*env)->NewString(env, url, strlen(url));
jbyteArray txArr = (*env)->NewByteArray(env, tx_len);
(*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx);
jobject ret = (*env)->CallObjectMethod(env, jni_ctx->http_interface, method_http_transmit, jurl, txArr);
if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
LPAC_JNI_END(-1);
}
*rcode = (*env)->GetIntField(env, ret, field_resp_rcode);
jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data);
*rx_len = (*env)->GetArrayLength(env, rxArr);
*rx = malloc(*rx_len * sizeof(uint8_t));
(*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, *rx);
LPAC_JNI_END(0);
}
struct euicc_apdu_interface apdu_interface_wrapper = {
.connect = apdu_interface_connect,
.disconnect = apdu_interface_disconnect,
.logic_channel_open = apdu_interface_logical_channel_open,
.logic_channel_close = apdu_interface_logical_channel_close,
.transmit = apdu_interface_transmit
};
struct euicc_http_interface http_interface_wrapper = {
.transmit = http_interface_transmit
};

View file

@ -0,0 +1,14 @@
#pragma once
#undef NDEBUG
#include <assert.h>
#include <euicc/interface.h>
extern struct euicc_apdu_interface apdu_interface_wrapper;
extern struct euicc_http_interface http_interface_wrapper;
void interface_wrapper_init();
#define LPAC_JNI_ASSERT_CTX assert(jni_ctx != NULL)
#define LPAC_JNI_SETUP_ENV \
JNIEnv *env; \
(*jvm)->AttachCurrentThread(jvm, &env, NULL)

View file

@ -0,0 +1,49 @@
#include <euicc/interface.h>
#include <malloc.h>
#include <string.h>
#include "lpac-jni.h"
#include "interface-wrapper.h"
pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;
struct lpac_jni_ctx *jni_ctx = NULL;
JavaVM *jvm = NULL;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm;
interface_wrapper_init();
return 1;
}
JNIEXPORT jlong JNICALL
Java_net_typeblog_lpac_1jni_LpacJni_createContext(JNIEnv *env, jobject thiz,
jobject apdu_interface,
jobject http_interface) {
LPAC_JNI_BEGIN;
struct lpac_jni_ctx *_ctx = malloc(sizeof(struct lpac_jni_ctx));
memset(_ctx, 0, sizeof(struct lpac_jni_ctx));
_ctx->ctx.interface.apdu = &apdu_interface_wrapper;
_ctx->ctx.interface.http = &http_interface_wrapper;
_ctx->apdu_interface = (*env)->NewGlobalRef(env, apdu_interface);
_ctx->http_interface = (*env)->NewGlobalRef(env, http_interface);
LPAC_JNI_END((jlong) _ctx);
}
JNIEXPORT void JNICALL
Java_net_typeblog_lpac_1jni_LpacJni_destroyContext(JNIEnv *env, jobject thiz, jlong handle) {
LPAC_JNI_BEGIN;
struct lpac_jni_ctx *_ctx = (struct lpac_jni_ctx *) handle;
(*env)->DeleteGlobalRef(env, _ctx->apdu_interface);
(*env)->DeleteGlobalRef(env, _ctx->http_interface);
if (jni_ctx == _ctx) {
jni_ctx = NULL;
}
free(_ctx);
LPAC_JNI_END0;
}
JNIEXPORT void JNICALL
Java_net_typeblog_lpac_1jni_LpacJni_setCurrentContext(JNIEnv *env, jobject thiz, jlong handle) {
LPAC_JNI_BEGIN;
jni_ctx = (struct lpac_jni_ctx *) handle;
LPAC_JNI_END0;
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <euicc/euicc.h>
#include <pthread.h>
#include <jni.h>
struct lpac_jni_ctx {
struct euicc_ctx ctx;
jobject apdu_interface;
jobject http_interface;
};
extern JavaVM *jvm;
extern pthread_mutex_t global_lock;
extern struct lpac_jni_ctx *jni_ctx;
#define LPAC_JNI_BEGIN pthread_mutex_lock(&global_lock)
#define LPAC_JNI_END0 pthread_mutex_unlock(&global_lock)
#define LPAC_JNI_END(ret) LPAC_JNI_END0; return ret

View file

@ -0,0 +1,17 @@
package net.typeblog.lpac_jni
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View file

@ -26,9 +26,4 @@ task genAsn1(type: JavaExec) {
compileJava.dependsOn genAsn1
compileKotlin.dependsOn genAsn1
description = 'LPAd SM-DP+ Connector'
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
description = 'LPAd SM-DP+ Connector'

View file

@ -16,3 +16,4 @@ rootProject.name = "OpenEUICC"
include ':app', ':libs:lpad-sm-dp-plus-connector'
include ':libs:hidden-apis-stub'
include ':libs:hidden-apis-shim'
include ':libs:lpac-jni'