quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit b6aaa50d419125384134d8a4435bac045480eb40
parent 74a63c75bda18044c747ebe3b9efa3f895cab9d3
Author: Iván Ávalos <avalos@disroot.org>
Date:   Wed, 12 Jun 2024 11:06:28 -0600

android: implement native networking API

Diffstat:
AQuickJS-android/qtart/src/main/java/net/taler/qtart/Networking.kt | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MQuickJS-android/qtart/src/main/java/net/taler/qtart/TalerWalletCore.kt | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 225 insertions(+), 1 deletion(-)

diff --git a/QuickJS-android/qtart/src/main/java/net/taler/qtart/Networking.kt b/QuickJS-android/qtart/src/main/java/net/taler/qtart/Networking.kt @@ -0,0 +1,116 @@ +package net.taler.qtart + +import android.util.Log +import com.sun.jna.StringArray +import java.util.concurrent.atomic.AtomicInteger +import net.taler.qtart.TalerWalletCore.TalerNative.* + +object Networking { + private val lastRequestId = AtomicInteger(0) + + interface RequestHandler { + fun handleRequest(req: RequestInfo, id: Int, sendResponse: (resp: ResponseInfo) -> Unit) + fun cancelRequest(id: Int): Boolean + } + + enum class RedirectMode(val value: Int) { + Unknown(-1), + + /** + * Handle redirects transparently. + */ + Transparent(0), + + /** + * Redirect status codes are returned to the client. + * The client can choose to follow them manually (or not). + */ + Manual(1), + + /** + * All redirect status codes result in an error. + */ + Error(2); + + companion object { + fun fromValue(value: Int): RedirectMode = + RedirectMode.values().find { it.value == value } ?: Unknown + } + } + + class RequestInfo( + val url: String, + val method: String, + val headers: Array<String>, + val redirectMode: RedirectMode, + val timeoutMs: Long, + val debug: Boolean, + val body: ByteArray?, + ) + + class ResponseInfo( + val requestId: Int, + val status: Int, + val errorMsg: String?, + val headers: Array<String>, + val body: ByteArray?, + ) + + /** + * Function to create a new HTTP fetch request. + * The request can still be configured until it is started. + * An identifier for the request will be written to @a handle. + * + * @return negative number on error, positive request_id on success + */ + internal fun httpCreateRequest( + reqInfo: JSHttpRequestInfo, + reqHandler: RequestHandler, + ) { + val requestId = lastRequestId.addAndGet(1) + + reqHandler.handleRequest( + req = RequestInfo( + url = reqInfo.url!!, + method = reqInfo.method!!, + headers = reqInfo.request_headers!!.getStringArray(0), + redirectMode = RedirectMode.fromValue(reqInfo.redirect!!), + timeoutMs = reqInfo.timeout_ms!!.toLong(), + debug = reqInfo.debug != 0, + body = if (reqInfo.req_body_len!! > 0) { + reqInfo.req_body!!.getByteArray(0, reqInfo.req_body_len!!) + } else null, + ), + id = requestId, + ) { resp -> + Log.d("Networking", "Receiving response from Android side") + val rawResp = JSHttpResponseInfo() + rawResp.request_id = resp.requestId + rawResp.status = resp.status + rawResp.errmsg = resp.errorMsg + rawResp.response_headers = StringArray(resp.headers, false) + rawResp.num_response_headers = resp.headers.size + if (resp.body != null) { + rawResp.body = resp.body + rawResp.body_len = resp.body.size + } else { + rawResp.body_len = 0 + } + + Log.d("Networking", "Response ready to send to Qtart: $rawResp") + reqInfo.response_cb?.invoke(reqInfo.response_cb_cls!!, rawResp) + } + } + + /** + * Cancel a request. The request_id will become invalid + * and the callback won't be called with request_id. + */ + internal fun httpCancelRequest( + requestId: Int, + handler: RequestHandler, + ): Int { + val success = handler.cancelRequest(requestId) + return if (success) requestId else -requestId + } +} +\ No newline at end of file diff --git a/QuickJS-android/qtart/src/main/java/net/taler/qtart/TalerWalletCore.kt b/QuickJS-android/qtart/src/main/java/net/taler/qtart/TalerWalletCore.kt @@ -36,9 +36,10 @@ import com.sun.jna.Callback import com.sun.jna.Library import com.sun.jna.Native import com.sun.jna.Pointer +import com.sun.jna.Structure class TalerWalletCore { - private interface TalerNative : Library { + internal interface TalerNative : Library { companion object { val INSTANCE: TalerNative by lazy { Native.load("talerwalletcore", TalerNative::class.java) @@ -65,6 +66,80 @@ class TalerWalletCore { fun TALER_WALLET_join(twi: Pointer) fun TALER_start_redirect_std(logfn: TALER_LogFn, cls: Pointer) fun TALER_set_curl_http_client(twi: Pointer) + + /** + * Native networking + */ + + class JSHttpRequestInfo: Structure() { + @JvmField var response_cb: JSHttpResponseCb? = null + @JvmField var response_cb_cls: Pointer? = null + @JvmField var url: String? = null + @JvmField var method: String? = null + @JvmField var request_headers: Pointer? = null // Array<String> + @JvmField var redirect: Int? = null + @JvmField var timeout_ms: Int? = null + @JvmField var debug: Int? = null + @JvmField var req_body: Pointer? = null // ByteArray + @JvmField var req_body_len: Int? = null + + override fun getFieldOrder() = mutableListOf( + "response_cb", + "response_cb_cls", + "url", + "method", + "request_headers", + "redirect", + "timeout_ms", + "debug", + "req_body", + "req_body_len", + ) + } + + class JSHttpResponseInfo: Structure() { + @JvmField var request_id: Int? = null + @JvmField var status: Int? = null + @JvmField var errmsg: String? = null + @JvmField var response_headers: Pointer? = null + @JvmField var num_response_headers: Int? = null + @JvmField var body: ByteArray? = null + @JvmField var body_len: Int? = null + + override fun getFieldOrder() = mutableListOf( + "request_id", + "status", + "errmsg", + "response_headers", + "num_response_headers", + "body", + "body_len", + ) + } + + interface JSHttpResponseCb: Callback { + fun invoke(cls: Pointer, resp: JSHttpResponseInfo) + } + + interface JSHttpRequestCb: Callback { + fun invoke(cls: Pointer, req_info: JSHttpRequestInfo) + } + + interface JSHttpReqCreateFn: Callback { + fun invoke(cls: Pointer, req_info: JSHttpRequestInfo): Int + } + + interface JSHttpReqCancelFn: Callback { + fun invoke(cls: Pointer, request_id: Int): Int + } + + fun TALER_set_http_client_implementation(twi: Pointer, impl: Pointer) + + fun TALER_pack_http_client_implementation( + req_create: JSHttpReqCreateFn, + req_cancel: JSHttpReqCancelFn, + handler_p: Pointer, + ): Pointer } private val walletInstance: Pointer = TalerNative.INSTANCE.TALER_WALLET_create() @@ -73,6 +148,9 @@ class TalerWalletCore { // Please don't refactor them, or else you'll suffer! private var currentMessageHandler: TalerNative.TALER_WALLET_MessageHandlerFn? = null private var currentLogHandler: TalerNative.TALER_LogFn? = null + private var currentReqCreateHandler: TalerNative.JSHttpReqCreateFn? = null + private var currentReqCancelHandler: TalerNative.JSHttpReqCancelFn? = null + private var currentResHandler: TalerNative.JSHttpResponseCb? = null fun setMessageHandler(handler: (msg: String) -> Unit) { this.currentMessageHandler = object : TalerNative.TALER_WALLET_MessageHandlerFn { @@ -111,4 +189,33 @@ class TalerWalletCore { fun join() { TalerNative.INSTANCE.TALER_WALLET_join(walletInstance) } + + /** + * Native networking + */ + + fun setHttpClient(handler: Networking.RequestHandler) { + this.currentReqCreateHandler = object: TalerNative.JSHttpReqCreateFn { + override fun invoke(cls: Pointer, req_info: TalerNative.JSHttpRequestInfo): Int { + Networking.httpCreateRequest(req_info, handler) + // TODO: return positive number on success, negative on failure + // right now, this can't be done synchronously; doing it + // async might require a modification in the C API. + return 1 + } + } + this.currentReqCancelHandler = object: TalerNative.JSHttpReqCancelFn { + override fun invoke(cls: Pointer, request_id: Int): Int { + return Networking.httpCancelRequest(request_id, handler) + } + } + + val client = TalerNative.INSTANCE.TALER_pack_http_client_implementation( + this.currentReqCreateHandler!!, + this.currentReqCancelHandler!!, + this.walletInstance, + ) + + TalerNative.INSTANCE.TALER_set_http_client_implementation(walletInstance, client) + } }