diff --git a/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/BackendRequestsTest.kt b/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/BackendRequestsTest.kt deleted file mode 100644 index 03233ca..0000000 --- a/library/src/androidTest/kotlin/network/loki/messenger/libsession_util/protocol/BackendRequestsTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package network.loki.messenger.libsession_util.protocol - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import network.loki.messenger.libsession_util.pro.BackendRequests -import org.junit.Assert.assertNotNull -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class BackendRequestsTest { - - @Test - fun getProviderMetadataWorks() { - val metadata = BackendRequests.getPaymentProviderMetadata(BackendRequests.PAYMENT_PROVIDER_GOOGLE_PLAY) - assertNotNull(metadata) - } -} \ No newline at end of file diff --git a/library/src/main/cpp/conversation.cpp b/library/src/main/cpp/conversation.cpp index 29f464b..48059a7 100644 --- a/library/src/main/cpp/conversation.cpp +++ b/library/src/main/cpp/conversation.cpp @@ -13,9 +13,9 @@ static auto ptrToConvoInfo(JNIEnv *env, jobject obj) { } JavaLocalRef serialize_pro_proof_info(JNIEnv *env, - std::optional> gen_index_hash, - const std::chrono::sys_time & expiry) { - if (!gen_index_hash) { + std::optional> revocation_tag, + const std::chrono::sys_seconds & expiry) { + if (!revocation_tag) { return {env, nullptr}; } @@ -28,7 +28,7 @@ JavaLocalRef serialize_pro_proof_info(JNIEnv *env, return {env, env->NewObject( class_info.java_class, class_info.constructor, - util::bytes_from_span(env, *gen_index_hash).get(), + util::bytes_from_span(env, *revocation_tag).get(), static_cast(expiry.time_since_epoch().count()) )}; } @@ -45,8 +45,8 @@ JavaLocalRef serialize_one_to_one(JNIEnv *env, const session::config::c JavaLocalRef(env, env->NewStringUTF(one_to_one.session_id.data())).get(), (jlong) one_to_one.last_read, (jboolean) one_to_one.unread, - serialize_pro_proof_info(env, one_to_one.pro_gen_index_hash, - one_to_one.pro_expiry_unix_ts).get())}; + serialize_pro_proof_info(env, one_to_one.pro_revocation_tag, + one_to_one.pro_expiry_at).get())}; } @@ -65,12 +65,12 @@ struct WithProProofInfoClassInfo : public JavaClassInfo { }; struct ProProofInfoClassInfo : public JavaClassInfo { - jmethodID genIndexHash_getter; + jmethodID revocationTag_getter; jmethodID expiryUnixTs_getter; ProProofInfoClassInfo(JNIEnv *env, jobject obj) : JavaClassInfo(env, obj) - , genIndexHash_getter(env->GetMethodID(java_class, "getGenIndexHashBytes", "()[B")) + , revocationTag_getter(env->GetMethodID(java_class, "getRevocationTagBytes", "()[B")) , expiryUnixTs_getter(env->GetMethodID(java_class, "getExpiryUnixTs", "()J")) {} @@ -79,7 +79,7 @@ struct ProProofInfoClassInfo : public JavaClassInfo { return instance; } - static void read_gen_index_hash(std::optional &out, JNIEnv *env, jobject obj) { + static void read_revocation_tag(std::optional &out, JNIEnv *env, jobject obj) { if (!obj) { out = std::nullopt; return; @@ -89,7 +89,7 @@ struct ProProofInfoClassInfo : public JavaClassInfo { env, (jbyteArray) env->CallObjectMethod( obj, - get(env, obj).genIndexHash_getter)); + get(env, obj).revocationTag_getter)); JavaByteArrayRef bytes_ref(env, hash_bytes.get()); @@ -98,11 +98,11 @@ struct ProProofInfoClassInfo : public JavaClassInfo { std::min(env->GetArrayLength(hash_bytes.get()), 32), out->begin()); } - static std::chrono::sys_time read_pro_expiry(JNIEnv *env, jobject obj) { + static std::chrono::sys_seconds read_pro_expiry(JNIEnv *env, jobject obj) { if (!obj) return {}; jlong expiry_ts = env->CallLongMethod(obj, get(env, obj).expiryUnixTs_getter); - return std::chrono::sys_time{std::chrono::milliseconds{expiry_ts}}; + return std::chrono::sys_seconds{std::chrono::seconds{expiry_ts}}; } }; @@ -135,8 +135,8 @@ session::config::convo::one_to_one deserialize_one_to_one(JNIEnv *env, jobject i JavaLocalRef pro_proof(env, env->CallObjectMethod( info, WithProProofInfoClassInfo::get(env).proProofInfo_getter)); - ProProofInfoClassInfo::read_gen_index_hash(r.pro_gen_index_hash, env, pro_proof.get()); - r.pro_expiry_unix_ts = ProProofInfoClassInfo::read_pro_expiry(env, pro_proof.get()); + ProProofInfoClassInfo::read_revocation_tag(r.pro_revocation_tag, env, pro_proof.get()); + r.pro_expiry_at = ProProofInfoClassInfo::read_pro_expiry(env, pro_proof.get()); return r; } @@ -274,7 +274,7 @@ JavaLocalRef serialize_blinded_one_to_one(JNIEnv *env, const session::c JavaLocalRef(env, env->NewStringUTF(blinded_one_to_one.blinded_session_id.data())).get(), (jlong) blinded_one_to_one.last_read, (jboolean) blinded_one_to_one.unread, - serialize_pro_proof_info(env, blinded_one_to_one.pro_gen_index_hash, blinded_one_to_one.pro_expiry_unix_ts).get() + serialize_pro_proof_info(env, blinded_one_to_one.pro_revocation_tag, blinded_one_to_one.pro_expiry_at).get() )}; } @@ -302,8 +302,8 @@ session::config::convo::blinded_one_to_one deserialize_blinded_one_to_one(JNIEnv JavaLocalRef pro_proof(env, env->CallObjectMethod( info, WithProProofInfoClassInfo::get(env).proProofInfo_getter)); - ProProofInfoClassInfo::read_gen_index_hash(r.pro_gen_index_hash, env, pro_proof.get()); - r.pro_expiry_unix_ts = ProProofInfoClassInfo::read_pro_expiry(env, pro_proof.get()); + ProProofInfoClassInfo::read_revocation_tag(r.pro_revocation_tag, env, pro_proof.get()); + r.pro_expiry_at = ProProofInfoClassInfo::read_pro_expiry(env, pro_proof.get()); return r; diff --git a/library/src/main/cpp/pro_backend.cpp b/library/src/main/cpp/pro_backend.cpp index d40a82f..9b6e10a 100644 --- a/library/src/main/cpp/pro_backend.cpp +++ b/library/src/main/cpp/pro_backend.cpp @@ -1,97 +1,346 @@ #include -#include "jni_utils.h" -#include "util.h" +#include +#include +#include #include +#include "jni_utils.h" +#include "util.h" +#include "pro_proof_util.h" + using namespace jni_utils; +namespace { + +namespace pb = session::pro_backend; + +constexpr const char* PKG = "network/loki/messenger/libsession_util/pro/"; + +std::span string_to_span(std::string_view s) { + return {reinterpret_cast(s.data()), s.size()}; +} + +// --- struct -> Kotlin marshalling helpers --- + +JavaLocalRef serialize_response_header(JNIEnv* env, const pb::ResponseBase& r) { + // §5: status is an enum (Ok/Fail/Error), plus an optional machine slug (error_code, §5.1) + // and an optional English diagnostic (error); no more errors[] array. We marshal status as its + // ordinal (int) — the Kotlin @Keep ctor maps it to the ProResponseStatus enum. + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/ProResponseHeader", + "(ILjava/lang/String;Ljava/lang/String;)V"); + auto sv = [](const std::optional& o) -> std::optional { + if (o) + return std::string_view{*o}; + return std::nullopt; + }; + return {env, env->NewObject(cls.java_class, cls.constructor, + static_cast(r.status), + jstring_from_optional(env, sv(r.error_code)).get(), + jstring_from_optional(env, sv(r.error)).get())}; +} + +jobject serialize_pro_request(JNIEnv* env, const pb::ProRequest& r) { + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/ProRequest", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + return env->NewObject(cls.java_class, cls.constructor, + jstring_from_optional(env, r.endpoint).get(), + jstring_from_optional(env, r.content_type).get(), + jstring_from_optional(env, std::string_view(r.data)).get()); +} + +JavaLocalRef serialize_provider_urls(JNIEnv* env, const pb::ProviderURLs& u) { + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/ProviderUrls", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + return {env, env->NewObject(cls.java_class, cls.constructor, + jstring_from_optional(env, u.refund_platform_url).get(), + jstring_from_optional(env, u.refund_support_url).get(), + jstring_from_optional(env, u.refund_status_url).get(), + jstring_from_optional(env, u.update_subscription_url).get(), + jstring_from_optional(env, u.cancel_subscription_url).get())}; +} + +// §1: `plan` is a parsed ProPlanPeriod (count + unit). We hand the app the structured pair — +// count as an int, unit as a lowercase name — so nothing downstream has to re-parse a slug (matching +// the nodejs glue's {planCount, planUnit}). The unit is preserved exactly, never canonicalized. +std::string_view plan_unit_to_string(pb::ProPlanUnit unit) { + switch (unit) { + case pb::ProPlanUnit::second: return "second"; + case pb::ProPlanUnit::day: return "day"; + case pb::ProPlanUnit::week: return "week"; + case pb::ProPlanUnit::month: return "month"; + case pb::ProPlanUnit::year: return "year"; + case pb::ProPlanUnit::lifetime: return "lifetime"; + } + return ""; +} + +JavaLocalRef serialize_payment_item(JNIEnv* env, const pb::ProPaymentItem& it) { + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/ProPaymentItem", + "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;ZJJJJJJJLjava/lang/String;)V"); + return {env, env->NewObject(cls.java_class, cls.constructor, + jstring_from_optional(env, std::string_view(it.status)).get(), // opaque status code string + static_cast(it.plan.count), + jstring_from_optional(env, plan_unit_to_string(it.plan.unit)).get(), + jstring_from_optional(env, std::string_view(it.payment_provider)).get(), + static_cast(it.auto_renewing), + static_cast(it.purchased_at.time_since_epoch().count()), // ms + static_cast(it.redeemed_at.time_since_epoch().count()), // s + static_cast(it.expiry_at.time_since_epoch().count()), // s + static_cast(it.grace_period_duration.count()), // s + static_cast(it.platform_refund_expiry_at.time_since_epoch().count()), // s + static_cast(it.revoked_at.time_since_epoch().count()), // ms + static_cast(it.refund_requested_at.time_since_epoch().count()), // s + jstring_from_optional(env, std::string_view(it.payment_id)).get())}; +} + +JavaLocalRef serialize_revocation_item(JNIEnv* env, const pb::ProRevocationItem& it) { + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/ProRevocationItem", + "(Ljava/lang/String;J)V"); + auto hex = oxenc::to_hex(it.revocation_tag.begin(), it.revocation_tag.end()); + return {env, env->NewObject(cls.java_class, cls.constructor, + jstring_from_optional(env, std::string_view(hex)).get(), + static_cast(it.effective_at.time_since_epoch().count()))}; +} + +jobject serialize_pro_proof_response(JNIEnv* env, const pb::ProProofResponse& resp) { + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/ProProofResponse", + "(Lnetwork/loki/messenger/libsession_util/pro/ProResponseHeader;" + "Lnetwork/loki/messenger/libsession_util/pro/ProProof;)V"); + auto header = serialize_response_header(env, resp); + JavaLocalRef proof(env, nullptr); + if (resp) // ResponseBase::operator bool: true iff status == Ok (proof populated on success) + proof = cpp_to_java_proof(env, resp.proof); + return env->NewObject(cls.java_class, cls.constructor, header.get(), proof.get()); +} + +jobject serialize_pro_status_response(JNIEnv* env, const pb::ProStatusResponse& resp) { + // §3.4: get_pro_status carries a single optional + // latest_payment (has-flag + nullable item) instead of an items[] list + payments_total. + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/GetProStatusResponse", + "(Lnetwork/loki/messenger/libsession_util/pro/ProResponseHeader;Ljava/lang/String;Z" + "Lnetwork/loki/messenger/libsession_util/pro/ProPaymentItem;IZJJJ)V"); + auto header = serialize_response_header(env, resp); + JavaLocalRef latest_payment(env, nullptr); + if (resp.latest_payment) + latest_payment = serialize_payment_item(env, *resp.latest_payment); + return env->NewObject(cls.java_class, cls.constructor, + header.get(), + jstring_from_optional(env, std::string_view(resp.user_status)).get(), // opaque status code string + static_cast(resp.latest_payment.has_value()), + latest_payment.get(), + static_cast(resp.error_report), + static_cast(resp.auto_renewing), + static_cast(resp.expiry_at.time_since_epoch().count()), + static_cast(resp.grace_period_duration.count()), + static_cast(resp.refund_requested_at.time_since_epoch().count())); +} + +jobject serialize_revocations_response(JNIEnv* env, const pb::GetProRevocationsResponse& resp) { + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/GetProRevocationsResponse", + "(Lnetwork/loki/messenger/libsession_util/pro/ProResponseHeader;JJJLjava/util/List;)V"); + auto header = serialize_response_header(env, resp); + JavaLocalRef items(env, jlist_from_collection(env, resp.items, + [](JNIEnv* env, const pb::ProRevocationItem& it) -> std::optional> { + return serialize_revocation_item(env, it); + })); + return env->NewObject(cls.java_class, cls.constructor, + header.get(), + static_cast(resp.ticket), + static_cast(resp.retry_in.count()), + static_cast(resp.retain_for.count()), + items.get()); +} + +jobject serialize_refund_response(JNIEnv* env, const pb::SetPaymentRefundRequestedResponse& resp) { + static BasicJavaClassInfo cls(env, "network/loki/messenger/libsession_util/pro/SetPaymentRefundRequestedResponse", + "(Lnetwork/loki/messenger/libsession_util/pro/ProResponseHeader;Z)V"); + auto header = serialize_response_header(env, resp); + return env->NewObject(cls.java_class, cls.constructor, header.get(), + static_cast(resp.updated)); +} + +} // namespace + +// --- Request builders (return ProRequest{endpoint, body}) --- + extern "C" -JNIEXPORT jstring JNICALL -Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildAddProPaymentRequestJson( - JNIEnv *env, jobject thiz, jint version, jbyteArray master_private_key, - jbyteArray rotating_private_key, jint payment_provider, jstring payment_id, - jstring order_id) { - return run_catching_cxx_exception_or_throws(env, [=]() { - auto json = session::pro_backend::AddProPaymentRequest::build_to_json( - version, +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildAddProPaymentRequest( + JNIEnv* env, jobject, jbyteArray master_private_key, jbyteArray rotating_private_key, + jstring provider_code, jstring payment_id) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaStringRef provider_ref(env, provider_code); + JavaStringRef payment_ref(env, payment_id); + auto req = pb::add_payment_request( JavaByteArrayRef(env, master_private_key).get(), JavaByteArrayRef(env, rotating_private_key).get(), - static_cast(payment_provider), - JavaStringRef(env, payment_id).get_raw(), - JavaStringRef(env, order_id).get_raw()); - - return jni_utils::jstring_from_optional(env, json).release(); + provider_ref.view(), + string_to_span(payment_ref.view())); + return serialize_pro_request(env, req); }); } extern "C" -JNIEXPORT jstring JNICALL -Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildGenerateProProofRequestJson( - JNIEnv *env, jobject thiz, jint version, jbyteArray master_private_key, - jbyteArray rotating_private_key, jlong now_ms) { - return run_catching_cxx_exception_or_throws(env, [=]() { - auto json = session::pro_backend::GenerateProProofRequest::build_to_json( - version, +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildGenerateProProofRequest( + JNIEnv* env, jobject, jbyteArray master_private_key, jbyteArray rotating_private_key, + jlong now_seconds) { + return run_catching_cxx_exception_or_throws(env, [=]() { + auto req = pb::pro_proof_request( JavaByteArrayRef(env, master_private_key).get(), JavaByteArrayRef(env, rotating_private_key).get(), - std::chrono::sys_time { - std::chrono::milliseconds(now_ms) - } - ); + std::chrono::sys_seconds{std::chrono::seconds(now_seconds)}); + return serialize_pro_request(env, req); + }); +} - return jni_utils::jstring_from_optional(env, json).release(); +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildGetProStatusRequest( + JNIEnv* env, jobject, jbyteArray master_private_key, jlong now_seconds) { + return run_catching_cxx_exception_or_throws(env, [=]() { + auto req = pb::pro_status_request( + JavaByteArrayRef(env, master_private_key).get(), + std::chrono::sys_seconds{std::chrono::seconds(now_seconds)}); + return serialize_pro_request(env, req); }); } extern "C" -JNIEXPORT jstring JNICALL -Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildGetProDetailsRequestJson( - JNIEnv *env, jobject thiz, jint version, jbyteArray pro_master_private_key, jlong now_ms, - jint count) { - return run_catching_cxx_exception_or_throws(env, [=]() { - auto json = session::pro_backend::GetProDetailsRequest::build_to_json( - version, - JavaByteArrayRef(env, pro_master_private_key).get(), - std::chrono::sys_time { - std::chrono::milliseconds(now_ms) - }, - static_cast(count) - ); - - return jni_utils::jstring_from_optional(env, json).release(); +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildRefundRequest( + JNIEnv* env, jobject, jbyteArray master_private_key, jlong now_seconds, + jlong refund_requested_seconds, jstring provider_code, jstring payment_id) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaStringRef provider_ref(env, provider_code); + JavaStringRef payment_ref(env, payment_id); + auto req = pb::refund_request( + JavaByteArrayRef(env, master_private_key).get(), + std::chrono::sys_seconds{std::chrono::seconds(now_seconds)}, + std::chrono::sys_seconds{std::chrono::seconds(refund_requested_seconds)}, + provider_ref.view(), + string_to_span(payment_ref.view())); + return serialize_pro_request(env, req); }); } extern "C" JNIEXPORT jobject JNICALL -Java_network_loki_messenger_libsession_1util_pro_BackendRequests_getPaymentProviderMetadata( - JNIEnv *env, jobject thiz, jint payment_provider) { +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_buildRevocationsRequest( + JNIEnv* env, jobject, jlong ticket) { + return run_catching_cxx_exception_or_throws(env, [=]() { + auto req = pb::revocations_request(static_cast(ticket)); + return serialize_pro_request(env, req); + }); +} + +// --- Response parsers (return typed structs) --- + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_parseAddPaymentResponse( + JNIEnv* env, jobject, jstring json) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaStringRef json_ref(env, json); + return serialize_pro_proof_response(env, pb::parse_add_payment(json_ref.view())); + }); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_parseProProofResponse( + JNIEnv* env, jobject, jstring json) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaStringRef json_ref(env, json); + return serialize_pro_proof_response(env, pb::parse_pro_proof(json_ref.view())); + }); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_parseProStatusResponse( + JNIEnv* env, jobject, jstring json) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaStringRef json_ref(env, json); + return serialize_pro_status_response(env, pb::parse_pro_status(json_ref.view())); + }); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_parseRefundResponse( + JNIEnv* env, jobject, jstring json) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaStringRef json_ref(env, json); + return serialize_refund_response(env, pb::parse_refund(json_ref.view())); + }); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_parseRevocationsResponse( + JNIEnv* env, jobject, jstring json) { + return run_catching_cxx_exception_or_throws(env, [=]() { + JavaStringRef json_ref(env, json); + return serialize_revocations_response(env, pb::parse_revocations(json_ref.view())); + }); +} + +// --- Provider URLs (source of truth in libsession; null if none) --- + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_providerUrls( + JNIEnv* env, jobject, jstring provider_code) { return run_catching_cxx_exception_or_throws(env, [=]() -> jobject { - if (payment_provider >= SESSION_PRO_BACKEND_PAYMENT_PROVIDER_COUNT || payment_provider < 0) { + JavaStringRef code_ref(env, provider_code); + auto urls = pb::provider_urls(code_ref.view()); + if (!urls) return nullptr; - } + return serialize_provider_urls(env, *urls).release(); + }); +} + +// --- Backend identity constants (single source of truth in libsession) --- - const auto & metadata = SESSION_PRO_BACKEND_PAYMENT_PROVIDER_METADATA[payment_provider]; - - static BasicJavaClassInfo class_info( - env, - "network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - - - return env->NewObject( - class_info.java_class, - class_info.constructor, - jni_utils::jstring_from_optional(env, std::string_view(metadata.device.data, metadata.device.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.store.data, metadata.store.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.platform.data, metadata.platform.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.platform_account.data, metadata.platform_account.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.refund_platform_url.data, metadata.refund_platform_url.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.refund_support_url.data, metadata.refund_support_url.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.refund_status_url.data, metadata.refund_status_url.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.update_subscription_url.data, metadata.update_subscription_url.size)).get(), - jni_utils::jstring_from_optional(env, std::string_view(metadata.cancel_subscription_url.data, metadata.cancel_subscription_url.size)).get() - ); +extern "C" +JNIEXPORT jstring JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_proBackendUrl( + JNIEnv* env, jobject) { + return run_catching_cxx_exception_or_throws(env, [=]() { + return env->NewStringUTF(std::string(pb::URL).c_str()); }); -} \ No newline at end of file +} + +extern "C" +JNIEXPORT jstring JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_proBackendPubKeyHex( + JNIEnv* env, jobject) { + return run_catching_cxx_exception_or_throws(env, [=]() { + auto hex = oxenc::to_hex(pb::PUBKEY.begin(), pb::PUBKEY.end()); + return env->NewStringUTF(hex.c_str()); + }); +} + +// --- Visible (purchasable) payment-provider slugs (single source of truth in libsession) --- + +extern "C" +JNIEXPORT jobjectArray JNICALL +Java_network_loki_messenger_libsession_1util_pro_BackendRequests_visiblePlatforms( + JNIEnv* env, jobject) { + return run_catching_cxx_exception_or_throws(env, [=]() -> jobjectArray { + auto platforms = pb::visible_platforms(); // std::span + jclass string_cls = env->FindClass("java/lang/String"); + jobjectArray arr = env->NewObjectArray( + static_cast(platforms.size()), string_cls, nullptr); + jsize i = 0; + for (auto slug : platforms) { + std::string s(slug); + jstring js = env->NewStringUTF(s.c_str()); + env->SetObjectArrayElement(arr, i++, js); + env->DeleteLocalRef(js); + } + return arr; + }); +} diff --git a/library/src/main/cpp/pro_proof_util.cpp b/library/src/main/cpp/pro_proof_util.cpp index 48e2fcb..e640168 100644 --- a/library/src/main/cpp/pro_proof_util.cpp +++ b/library/src/main/cpp/pro_proof_util.cpp @@ -20,33 +20,33 @@ static std::array from_hex(std::span input) { session::ProProof java_to_cpp_proof(JNIEnv *env, jobject proof) { struct ProProofMethods : public JavaClassInfo { jmethodID get_version; - jmethodID get_gen_index_hash; + jmethodID get_revocation_tag; jmethodID get_rotating_pub_key; - jmethodID get_expiry_ms; + jmethodID get_expiry_seconds; jmethodID get_signature; ProProofMethods(JNIEnv *env, jobject obj) : JavaClassInfo(env, obj) , get_version(env->GetMethodID(java_class, "getVersion", "()I")) - , get_gen_index_hash(env->GetMethodID(java_class, "getGenIndexHashHex", "()Ljava/lang/String;")) + , get_revocation_tag(env->GetMethodID(java_class, "getRevocationTagHex", "()Ljava/lang/String;")) , get_rotating_pub_key(env->GetMethodID(java_class, "getRotatingPubKeyHex", "()Ljava/lang/String;")) - , get_expiry_ms(env->GetMethodID(java_class, "getExpiryMs", "()J")) + , get_expiry_seconds(env->GetMethodID(java_class, "getExpirySeconds", "()J")) , get_signature(env->GetMethodID(java_class, "getSignatureHex", "()Ljava/lang/String;")) {} }; // Cache method IDs static ProProofMethods methods(env, proof); - jni_utils::JavaLocalRef gen_index_hash(env, (jstring) env->CallObjectMethod(proof, methods.get_gen_index_hash)); + jni_utils::JavaLocalRef revocation_tag(env, (jstring) env->CallObjectMethod(proof, methods.get_revocation_tag)); jni_utils::JavaLocalRef rotating_pub_key(env, (jstring) env->CallObjectMethod(proof, methods.get_rotating_pub_key)); jni_utils::JavaLocalRef signature(env, (jstring) env->CallObjectMethod(proof, methods.get_signature)); return { .version = static_cast(env->CallIntMethod(proof, methods.get_version)), - .gen_index_hash = from_hex<32>(jni_utils::JavaStringRef(env, gen_index_hash.get()).get()), + .revocation_tag = from_hex<32>(jni_utils::JavaStringRef(env, revocation_tag.get()).get()), .rotating_pubkey = from_hex<32>(jni_utils::JavaStringRef(env, rotating_pub_key.get()).get()), - .expiry_unix_ts = std::chrono::sys_time( - std::chrono::milliseconds(env->CallLongMethod(proof, methods.get_expiry_ms))), + .expiry_at = std::chrono::sys_seconds( + std::chrono::seconds(env->CallLongMethod(proof, methods.get_expiry_seconds))), .sig = from_hex<64>(jni_utils::JavaStringRef(env, signature.get()).get()), }; } @@ -60,9 +60,9 @@ JavaLocalRef cpp_to_java_proof(JNIEnv *env, const session::ProProof &pr class_info.java_class, class_info.constructor, static_cast(proof.version), - util::bytes_from_span(env, proof.gen_index_hash).get(), + util::bytes_from_span(env, proof.revocation_tag).get(), util::bytes_from_span(env, proof.rotating_pubkey).get(), - static_cast(proof.expiry_unix_ts.time_since_epoch().count()), + static_cast(proof.expiry_at.time_since_epoch().count()), util::bytes_from_span(env, proof.sig).get() )}; } @@ -88,7 +88,7 @@ Java_network_loki_messenger_libsession_1util_pro_ProProof_nativeStatus(JNIEnv *e return static_cast(java_to_cpp_proof(env, thiz).status( JavaByteArrayRef(env, verify_pub_key).get(), - std::chrono::sys_time{std::chrono::milliseconds(now_unix_ts)}, + std::chrono::sys_seconds{std::chrono::seconds(now_unix_ts)}, signed_msg )); }); diff --git a/library/src/main/cpp/protocol.cpp b/library/src/main/cpp/protocol.cpp index 4b431aa..b762f32 100644 --- a/library/src/main/cpp/protocol.cpp +++ b/library/src/main/cpp/protocol.cpp @@ -149,8 +149,9 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_decodeForC auto decoded = session::decode_for_community( payload_ref.get(), - std::chrono::sys_time{ - std::chrono::milliseconds{timestamp_ms}}, + std::chrono::sys_seconds{ + std::chrono::duration_cast( + std::chrono::milliseconds{timestamp_ms})}, *java_to_cpp_array<32>(env, pro_backend_pub_key) ); diff --git a/library/src/main/cpp/user_profile.cpp b/library/src/main/cpp/user_profile.cpp index ceeaba4..3ff0dab 100644 --- a/library/src/main/cpp/user_profile.cpp +++ b/library/src/main/cpp/user_profile.cpp @@ -165,11 +165,11 @@ Java_network_loki_messenger_libsession_1util_UserProfile_setAnimatedAvatar(JNIEn extern "C" JNIEXPORT void JNICALL -Java_network_loki_messenger_libsession_1util_UserProfile_setProAccessExpiryMs(JNIEnv *env, - jobject thiz, - jlong epoch_mills) { - ptrToProfile(env, thiz)->set_pro_access_expiry(std::chrono::sys_time{ - std::chrono::milliseconds{epoch_mills} +Java_network_loki_messenger_libsession_1util_UserProfile_setProAccessExpiry(JNIEnv *env, + jobject thiz, + jlong epoch_seconds) { + ptrToProfile(env, thiz)->set_pro_access_expiry(std::chrono::sys_seconds{ + std::chrono::seconds{epoch_seconds} }); } @@ -210,8 +210,8 @@ Java_network_loki_messenger_libsession_1util_UserProfile_getProConfig(JNIEnv *en extern "C" JNIEXPORT jlong JNICALL -Java_network_loki_messenger_libsession_1util_UserProfile_getProAccessExpiryMsOrZero(JNIEnv *env, - jobject thiz) { +Java_network_loki_messenger_libsession_1util_UserProfile_getProAccessExpiryOrZero(JNIEnv *env, + jobject thiz) { auto expiry = ptrToProfile(env, thiz)->get_pro_access_expiry(); return expiry ? expiry->time_since_epoch().count() : 0; } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/Config.kt b/library/src/main/java/network/loki/messenger/libsession_util/Config.kt index 8f8eef5..3b7d553 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -85,7 +85,7 @@ interface ReadableUserProfile: ReadableConfig { fun getProFeatures(): ProProfileFeatures fun getProConfig(): ProConfig? - fun getProAccessExpiryMs(): Long? + fun getProAccessExpiry(): Long? } interface MutableUserProfile : ReadableUserProfile, MutableConfig { @@ -108,7 +108,7 @@ interface MutableUserProfile : ReadableUserProfile, MutableConfig { fun setProConfig(proConfig: ProConfig) fun setProBadge(proBadge: Boolean) fun setAnimatedAvatar(animatedAvatar: Boolean) - fun setProAccessExpiryMs(epochMills: Long) + fun setProAccessExpiry(epochSeconds: Long) fun removeProAccessExpiry() } diff --git a/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt b/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt index 7762f60..c864b9c 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/UserProfile.kt @@ -45,12 +45,12 @@ class UserProfile private constructor(pointer: Long) : ConfigBase(pointer), Muta external override fun setProBadge(proBadge: Boolean) external override fun setAnimatedAvatar(animatedAvatar: Boolean) - external override fun setProAccessExpiryMs(epochMills: Long) + external override fun setProAccessExpiry(epochSeconds: Long) external override fun removeProAccessExpiry() private external fun getProFeaturesRaw(): Long override fun getProFeatures(): ProProfileFeatures = ProProfileFeatures(getProFeaturesRaw()) external override fun getProConfig(): ProConfig? - private external fun getProAccessExpiryMsOrZero(): Long - override fun getProAccessExpiryMs(): Long? = getProAccessExpiryMsOrZero().takeIf { it != 0L } + private external fun getProAccessExpiryOrZero(): Long + override fun getProAccessExpiry(): Long? = getProAccessExpiryOrZero().takeIf { it != 0L } } \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/pro/BackendRequests.kt b/library/src/main/java/network/loki/messenger/libsession_util/pro/BackendRequests.kt index a76e9d0..38703e0 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/pro/BackendRequests.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/pro/BackendRequests.kt @@ -1,36 +1,74 @@ package network.loki.messenger.libsession_util.pro import network.loki.messenger.libsession_util.LibSessionUtilCApi -import network.loki.messenger.libsession_util.protocol.PaymentProviderMetadata - -typealias PaymentProvider = Int +/** + * Session Pro backend request builders + response parsers. libsession-util owns the wire format on + * both sides — the single source of truth — so the app never builds or parses the JSON itself: each + * `build*Request` signs internally and returns a [ProRequest] (endpoint + body) to POST, and each + * `parse*Response` turns the raw reply JSON into a typed struct (check `header.errors` first). + */ object BackendRequests : LibSessionUtilCApi() { - const val PAYMENT_PROVIDER_GOOGLE_PLAY: PaymentProvider = 1 - const val PAYMENT_PROVIDER_APP_STORE: PaymentProvider = 2 + // Canonical provider code slugs; must match libsession's + // SESSION_PRO_BACKEND_PAYMENT_PROVIDER_CODE_* constants (opaque wire codes). + const val PAYMENT_PROVIDER_GOOGLE_PLAY: String = "google_play" + const val PAYMENT_PROVIDER_APP_STORE: String = "app_store" + const val PAYMENT_PROVIDER_RANGEPROOF: String = "rangeproof" + + // --- Request builders (sign internally; return endpoint + JSON body) --- - external fun buildAddProPaymentRequestJson( - version: Int, + external fun buildAddProPaymentRequest( masterPrivateKey: ByteArray, rotatingPrivateKey: ByteArray, - paymentProvider: PaymentProvider, + providerCode: String, paymentId: String, - orderId: String, - ): String + ): ProRequest - external fun buildGenerateProProofRequestJson( - version: Int, + external fun buildGenerateProProofRequest( masterPrivateKey: ByteArray, rotatingPrivateKey: ByteArray, - nowMs: Long, - ): String + nowSeconds: Long, + ): ProRequest + + external fun buildGetProStatusRequest( + masterPrivateKey: ByteArray, + nowSeconds: Long, + ): ProRequest + + external fun buildRefundRequest( + masterPrivateKey: ByteArray, + nowSeconds: Long, + refundRequestedSeconds: Long, + providerCode: String, + paymentId: String, + ): ProRequest + + external fun buildRevocationsRequest(ticket: Long): ProRequest + + // --- Response parsers (typed structs; check header.errors first) --- + + external fun parseAddPaymentResponse(json: String): ProProofResponse + external fun parseProProofResponse(json: String): ProProofResponse + external fun parseProStatusResponse(json: String): GetProStatusResponse + external fun parseRefundResponse(json: String): SetPaymentRefundRequestedResponse + external fun parseRevocationsResponse(json: String): GetProRevocationsResponse + + // --- Provider URLs (libsession is the source of truth; null if none, e.g. rangeproof/unknown) --- + + external fun providerUrls(providerCode: String): ProviderUrls? + + /** + * The purchasable payment-provider slugs to surface to users (single source of truth in libsession; + * excludes non-purchasable providers like `rangeproof`). Order is not significant — the client + * applies its own ordering and skips slugs it has no display translation for. + */ + external fun visiblePlatforms(): Array + + // --- Backend identity (single source of truth in libsession; read these, don't hard-code) --- - external fun buildGetProDetailsRequestJson( - version: Int, - proMasterPrivateKey: ByteArray, - nowMs: Long, - count: Int, - ): String + /** The Session Pro backend base URL (overridable prod/default), e.g. "https://pro.session.codes". */ + external fun proBackendUrl(): String - external fun getPaymentProviderMetadata(paymentProvider: PaymentProvider): PaymentProviderMetadata? + /** The Session Pro backend Ed25519 signing pubkey, hex (64 chars). */ + external fun proBackendPubKeyHex(): String } diff --git a/library/src/main/java/network/loki/messenger/libsession_util/pro/ProBackendResponses.kt b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProBackendResponses.kt new file mode 100644 index 0000000..0ee9508 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProBackendResponses.kt @@ -0,0 +1,239 @@ +package network.loki.messenger.libsession_util.pro + +import androidx.annotation.Keep +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.Duration +import java.time.Instant + +/** + * Kotlin mirrors of the `session::pro_backend` response structs. libsession-util owns request + * construction and response parsing (the single source of truth for the wire shape); these types are + * what the glue hands the app so it never re-parses the wire itself. + * + * These are the app-facing shapes: dates are [Instant], intervals are [Duration], and opaque codes + * (plan/provider/status) stay [String] slugs — no magic-int constants. Because the JNI boundary can + * only marshal primitives, each type keeps a `@Keep` secondary constructor taking the raw epoch + * integers (the shape the C++ builds via `NewObject`); it converts once — here at the glue boundary — + * into the typed primary constructor, mapping the `0` "unset" sentinel to null. The types are + * `@Serializable` so the app can persist them directly (e.g. its SQLite pro-status cache). + */ + +/** Epoch **seconds** as an [Instant], or null when unset (`0`). */ +private fun Long.secondsToInstantOrNull(): Instant? = + if (this == 0L) null else Instant.ofEpochSecond(this) + +/** Epoch **milliseconds** as an [Instant], or null when unset (`0`). */ +private fun Long.msToInstantOrNull(): Instant? = + if (this == 0L) null else Instant.ofEpochMilli(this) + +/** Serializes an [Instant] as epoch-millis, for the app's local persistence of these types. */ +private object InstantAsEpochMillisSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeLong(value.toEpochMilli()) + override fun deserialize(decoder: Decoder): Instant = Instant.ofEpochMilli(decoder.decodeLong()) +} + +/** Serializes a [Duration] as whole seconds. */ +private object DurationAsSecondsSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("Duration", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Duration) = encoder.encodeLong(value.seconds) + override fun deserialize(decoder: Decoder): Duration = Duration.ofSeconds(decoder.decodeLong()) +} + +/** Route + content-type + body for a request to POST to the Pro backend (from the `*Request` builders). */ +@Keep +data class ProRequest( + val endpoint: String, + val contentType: String, // relay verbatim as the Content-Type header; don't assume a format + val body: String, +) + +/** Per-provider support/management URLs — libsession is the source of truth for these. */ +@Keep +data class ProviderUrls( + val refundPlatformUrl: String, + val refundSupportUrl: String, + val refundStatusUrl: String, + val updateSubscriptionUrl: String, + val cancelSubscriptionUrl: String, +) + +/** + * Outcome category for a Pro backend response (§5). CLOSED set: [Ok] = success (payload fields + * set); [Fail] = rejected on client input / a precondition; [Error] = backend fault (the same request + * may succeed later, i.e. retryable). Ordinals match the C `SESSION_PRO_BACKEND_RESPONSE_STATUS` enum. + */ +@Serializable +enum class ProResponseStatus { Ok, Fail, Error } + +/** + * Common response header (§5). Check [isSuccess] first. + * + * On non-[ProResponseStatus.Ok]: [errorCode] is a stable machine-readable slug (spec §5.1) — map known + * ones to a localized (Crowdin) string; an unknown slug is forward-compatible (falls through). [error] is + * an English diagnostic — NOT user-facing text; show it only when the slug has no i18n entry at all, and + * it's always safe to log. Both are null on success. + */ +@Serializable +@Keep +data class ProResponseHeader( + val status: ProResponseStatus, + val errorCode: String?, + val error: String?, +) { + val isSuccess: Boolean get() = status == ProResponseStatus.Ok + + /** JNI-facing constructor: status as the C enum ordinal, nullable slug + diagnostic. */ + @Keep + constructor(statusOrdinal: Int, errorCode: String?, error: String?) : this( + status = ProResponseStatus.values().getOrElse(statusOrdinal) { ProResponseStatus.Error }, + errorCode = errorCode, + error = error, + ) +} + +/** Common interface for the parsed Pro backend responses; check [header] before reading the data. */ +interface ProResponse { + val header: ProResponseHeader +} + +/** Response to add-payment / generate-proof: carries the freshly-issued proof (null on error). */ +@Keep +data class ProProofResponse( + override val header: ProResponseHeader, + val proof: ProProof?, +) : ProResponse + +/** One payment/subscription record from get-pro-status. */ +@Serializable +@Keep +data class ProPaymentItem( + val status: String, // opaque per-payment status slug: unredeemed/redeemed/expired/revoked + val planCount: Int, // parsed billing-period count; >= 1 for periodic units, 0 for "lifetime" + val planUnit: String, // parsed unit name: second/day/week/month/year/lifetime (never canonicalized) + val paymentProvider: String, // provider slug, e.g. "google_play" + val autoRenewing: Boolean, + @Serializable(with = InstantAsEpochMillisSerializer::class) + val purchased: Instant, // provider purchase instant; always set + @Serializable(with = InstantAsEpochMillisSerializer::class) + val redeemed: Instant?, // when activated; null if not activated + @Serializable(with = InstantAsEpochMillisSerializer::class) + val expiry: Instant?, // access expiry for this payment; null if not activated + @Serializable(with = DurationAsSecondsSerializer::class) + val gracePeriod: Duration, // grace beyond [expiry] before access is really lost + @Serializable(with = InstantAsEpochMillisSerializer::class) + val platformRefundExpiry: Instant?, // deadline for a platform ("quick") refund; null if n/a + @Serializable(with = InstantAsEpochMillisSerializer::class) + val revoked: Instant?, // when revoked; null if not revoked + @Serializable(with = InstantAsEpochMillisSerializer::class) + val refundRequested: Instant?, // when a refund was requested; null if none + val paymentId: String, // opaque; confidential +) { + /** Raw-epoch constructor used by the JNI layer (see the file header); converts to the typed fields. */ + @Keep + constructor( + status: String, + planCount: Int, + planUnit: String, + paymentProvider: String, + autoRenewing: Boolean, + purchasedUnixTsMs: Long, + redeemedUnixTs: Long, + expiryUnixTs: Long, + gracePeriodDurationSeconds: Long, + platformRefundExpiryUnixTs: Long, + revokedUnixTsMs: Long, + refundRequestedUnixTs: Long, + paymentId: String, + ) : this( + status = status, + planCount = planCount, + planUnit = planUnit, + paymentProvider = paymentProvider, + autoRenewing = autoRenewing, + purchased = Instant.ofEpochMilli(purchasedUnixTsMs), + redeemed = redeemedUnixTs.secondsToInstantOrNull(), + expiry = expiryUnixTs.secondsToInstantOrNull(), + gracePeriod = Duration.ofSeconds(gracePeriodDurationSeconds), + platformRefundExpiry = platformRefundExpiryUnixTs.secondsToInstantOrNull(), + revoked = revokedUnixTsMs.msToInstantOrNull(), + refundRequested = refundRequestedUnixTs.secondsToInstantOrNull(), + paymentId = paymentId, + ) +} + +/** + * Response to get-pro-status (endpoint `get_pro_status`, §3.4 — the "am I Pro?" read). + * Carries the account status plus its single most-recent payment; the full payment history is a + * separate (library-only) query and is not wired here. + */ +@Serializable +@Keep +data class GetProStatusResponse( + override val header: ProResponseHeader, + val userStatus: String, // opaque account-status slug: never/active/expired + val latestPayment: ProPaymentItem?, // the single most-recent payment, or null when none + val errorReport: Int, // SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT + val autoRenewing: Boolean, + @Serializable(with = InstantAsEpochMillisSerializer::class) + val expiry: Instant?, // account access expiry (incl. grace); null if never subscribed + @Serializable(with = DurationAsSecondsSerializer::class) + val gracePeriod: Duration, // grace included in [expiry] + @Serializable(with = InstantAsEpochMillisSerializer::class) + val refundRequested: Instant?, // when a refund was requested; null if none +) : ProResponse { + /** Raw-epoch constructor used by the JNI layer (see the file header); converts to the typed fields. */ + @Keep + constructor( + header: ProResponseHeader, + userStatus: String, + hasLatestPayment: Boolean, + latestPayment: ProPaymentItem?, + errorReport: Int, + autoRenewing: Boolean, + expiryUnixTs: Long, + gracePeriodDurationSeconds: Long, + refundRequestedUnixTs: Long, + ) : this( + header = header, + userStatus = userStatus, + latestPayment = if (hasLatestPayment) latestPayment else null, + errorReport = errorReport, + autoRenewing = autoRenewing, + expiry = expiryUnixTs.secondsToInstantOrNull(), + gracePeriod = Duration.ofSeconds(gracePeriodDurationSeconds), + refundRequested = refundRequestedUnixTs.secondsToInstantOrNull(), + ) +} + +/** One revocation-list entry. */ +@Keep +data class ProRevocationItem( + val revocationTagHex: String, // 32-byte opaque tag, hex + val effectiveUnixTs: Long, // seconds; revoked only once client clock >= this +) { + /** The revocation only takes effect once the client clock reaches this instant. */ + val effective: Instant get() = Instant.ofEpochSecond(effectiveUnixTs) +} + +/** Response to get-revocations. */ +@Keep +data class GetProRevocationsResponse( + override val header: ProResponseHeader, + val ticket: Long, + val retryInSeconds: Long, + val retainForSeconds: Long, + val items: List, +) : ProResponse + +/** Response to set-refund-requested. */ +@Keep +data class SetPaymentRefundRequestedResponse( + override val header: ProResponseHeader, + val updated: Boolean, +) : ProResponse diff --git a/library/src/main/java/network/loki/messenger/libsession_util/pro/ProProof.kt b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProProof.kt index 996bee0..eeba248 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/pro/ProProof.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/pro/ProProof.kt @@ -15,14 +15,14 @@ typealias ProProofStatus = Int data class ProProof( val version: Int, - @SerialName("gen_index_hash") - val genIndexHashHex: String, + @SerialName("revocation_tag") + val revocationTagHex: String, @SerialName("rotating_pkey") val rotatingPubKeyHex: String, - @SerialName("expiry_unix_ts_ms") - val expiryMs: Long, + @SerialName("expiry_ts") + val expirySeconds: Long, @SerialName("sig") val signatureHex: String @@ -30,15 +30,15 @@ data class ProProof( @Keep constructor( version: Int, - genIndexHash: ByteArray, + revocationTag: ByteArray, rotatingPubKey: ByteArray, - expiryMs: Long, + expirySeconds: Long, signature: ByteArray ): this( version = version, - genIndexHashHex = genIndexHash.toHexString(), + revocationTagHex = revocationTag.toHexString(), rotatingPubKeyHex = rotatingPubKey.toHexString(), - expiryMs = expiryMs, + expirySeconds = expirySeconds, signatureHex = signature.toHexString() ) @@ -73,7 +73,7 @@ data class ProProof( val signedMessageData = signedMessage?.data val signedMessageSignature = signedMessage?.signature return nativeStatus( - nowUnixTs = now.toEpochMilli(), + nowUnixTs = now.epochSecond, verifyPubKey = senderED25519PubKey, signedMessageData = signedMessageData, signedMessageSignature = signedMessageSignature diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata.kt deleted file mode 100644 index c7b7149..0000000 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/PaymentProviderMetadata.kt +++ /dev/null @@ -1,15 +0,0 @@ -package network.loki.messenger.libsession_util.protocol - -import androidx.annotation.Keep - -data class PaymentProviderMetadata @Keep constructor( - val device: String, - val store: String, - val platform: String, - val platformAccount: String, - val refundPlatformUrl: String, - val refundSupportUrl: String, - val refundStatusUrl: String, - val updateSubscriptionUrl: String, - val cancelSubscriptionUrl: String, -) \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt b/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt index 19b0c12..44b80d4 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt @@ -13,25 +13,25 @@ sealed interface Conversation { * This is not a full ProProof, it contains just enough for the clients to check it it's revoked. */ data class ProProofInfo( - val genIndexHash: Bytes, + val revocationTag: Bytes, val expiry: Instant, ) { @Keep constructor( - genIndexHash: ByteArray, - expiryMs: Long, + revocationTag: ByteArray, + expirySeconds: Long, ) : this( - genIndexHash = Bytes(genIndexHash), - expiry = Instant.ofEpochMilli(expiryMs) + revocationTag = Bytes(revocationTag), + expiry = Instant.ofEpochSecond(expirySeconds) ) @get:Keep - val genIndexHashBytes: ByteArray - get() = genIndexHash.data + val revocationTagBytes: ByteArray + get() = revocationTag.data @get:Keep val expiryUnixTs: Long - get() = expiry.toEpochMilli() + get() = expiry.epochSecond } /** diff --git a/libsession-util b/libsession-util index bb4edf8..7e4d764 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit bb4edf8d694bfcec75c348cffde013a5350fb1b3 +Subproject commit 7e4d7641a8243baa9ca6ec3a749f251fdd18488b