Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
72
TMessagesProj/jni/tde2e/CMakeLists.txt
Normal file
72
TMessagesProj/jni/tde2e/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
|
||||
|
||||
project(TdAndroid VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
set(TD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
||||
|
||||
option(TD_ANDROID_JSON "Use \"ON\" to build JSON interface.")
|
||||
option(TD_ANDROID_JSON_JAVA "Use \"ON\" to build Java wrapper for JSON API.")
|
||||
|
||||
if (TD_ANDROID_JSON)
|
||||
if (CMAKE_CROSSCOMPILING)
|
||||
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -Oz -fdata-sections -ffunction-sections")
|
||||
list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
|
||||
endif()
|
||||
add_subdirectory(${TD_DIR} td)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT TD_ANDROID_JSON_JAVA)
|
||||
option(TD_ENABLE_JNI "Enable JNI-compatible TDLib API" ON)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CROSSCOMPILING)
|
||||
set(CMAKE_MODULE_PATH "${TD_DIR}/CMake")
|
||||
|
||||
include(TdSetUpCompiler)
|
||||
td_set_up_compiler()
|
||||
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -Oz -fdata-sections -ffunction-sections")
|
||||
|
||||
list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
|
||||
add_subdirectory(${TD_DIR} td)
|
||||
|
||||
add_library(tdjni SHARED "${TD_DIR}/example/java/td_jni.cpp")
|
||||
|
||||
if (TD_ANDROID_JSON_JAVA)
|
||||
target_link_libraries(tdjni PRIVATE Td::TdJsonStatic)
|
||||
target_compile_definitions(tdjni PRIVATE TD_JSON_JAVA=1)
|
||||
set_target_properties(tdjni PROPERTIES OUTPUT_NAME "tdjsonjava")
|
||||
else()
|
||||
target_link_libraries(tdjni PRIVATE Td::TdStatic)
|
||||
endif()
|
||||
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="org/drinkless/tdlib")
|
||||
|
||||
add_custom_command(TARGET tdjni POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E rename $<TARGET_FILE:tdjni> $<TARGET_FILE:tdjni>.debug
|
||||
COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded $<TARGET_FILE:tdjni>.debug -o $<TARGET_FILE:tdjni>)
|
||||
else()
|
||||
add_subdirectory(${TD_DIR} td)
|
||||
|
||||
if (TD_ANDROID_JSON_JAVA)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
|
||||
set(TD_API_JAVA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${TD_API_JAVA_PACKAGE}/TdApi.java")
|
||||
set(TD_API_TLO_PATH "${TD_DIR}/td/generate/auto/tlo/td_api.tlo")
|
||||
set(TD_API_TL_PATH "${TD_DIR}/td/generate/scheme/td_api.tl")
|
||||
set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH "${TD_DIR}/td/generate/JavadocTlDocumentationGenerator.php")
|
||||
set(GENERATE_JAVA_CMD td_generate_java_api TdApi ${TD_API_TLO_PATH} ${CMAKE_CURRENT_SOURCE_DIR} ${TD_API_JAVA_PACKAGE})
|
||||
if (PHP_EXECUTABLE)
|
||||
set(GENERATE_JAVA_CMD ${GENERATE_JAVA_CMD} &&
|
||||
${PHP_EXECUTABLE} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH} ${TD_API_TL_PATH} ${TD_API_JAVA_PATH} androidx.annotation.Nullable @Nullable &&
|
||||
${PHP_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/AddIntDef.php ${TD_API_JAVA_PATH})
|
||||
endif()
|
||||
|
||||
file(MAKE_DIRECTORY ${TD_API_JAVA_PACKAGE})
|
||||
add_custom_target(tl_generate_java
|
||||
COMMAND ${GENERATE_JAVA_CMD}
|
||||
COMMENT "Generate Java TL source files"
|
||||
DEPENDS td_generate_java_api tl_generate_tlo ${TD_API_TLO_PATH} ${TD_API_TL_PATH}
|
||||
)
|
||||
endif()
|
||||
12
TMessagesProj/jni/tde2e/README
Normal file
12
TMessagesProj/jni/tde2e/README
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
git clone https://github.com/tdlib/td
|
||||
cd td/example/android
|
||||
# copy build-tdlib.sh and CMakeLists.txt into this folder
|
||||
./fetch-sdk.sh
|
||||
# copy boringssl from TMessagesProj/jni/boringssl into td/example/android/third-party/openssl with such structure:
|
||||
# third-party/
|
||||
# openssl/
|
||||
# $ARCH/
|
||||
# include/ <- TMessagesProj/jni/boringssl/include
|
||||
# lib/ <- TMessagesProj/jni/boringssl/lib/$ARCH
|
||||
./build-tdlib.sh
|
||||
# gather libtde2e.a and libtdutils.a into TMessagesProj/jni/tde2e/$ARCH
|
||||
411
TMessagesProj/jni/tde2e/bridge.cpp
Normal file
411
TMessagesProj/jni/tde2e/bridge.cpp
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
#include <jni.h>
|
||||
#include "e2e_api.h"
|
||||
|
||||
void java_throw(JNIEnv *env, tde2e_api::Error error) {
|
||||
jclass exceptionCls = env->FindClass("java/lang/RuntimeException");
|
||||
if (exceptionCls != nullptr) {
|
||||
env->ThrowNew(exceptionCls, ("tde2e error: " + error.message).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void java_throw(JNIEnv *env, std::string error) {
|
||||
jclass exceptionCls = env->FindClass("java/lang/RuntimeException");
|
||||
if (exceptionCls != nullptr) {
|
||||
env->ThrowNew(exceptionCls, ("tde2e bridge error: " + error).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
jbyteArray java_bytes(JNIEnv *env, const std::string& str) {
|
||||
jbyteArray byteArray = env->NewByteArray(str.size());
|
||||
if (byteArray == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
env->SetByteArrayRegion(byteArray, 0, str.size(), reinterpret_cast<const jbyte*>(str.data()));
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
jobjectArray java_bytes2d(JNIEnv *env, const std::vector<std::string>& arr) {
|
||||
jobjectArray array = env->NewObjectArray(arr.size(), env->FindClass("[B"), nullptr);
|
||||
for (size_t i = 0; i < arr.size(); ++i) {
|
||||
auto bytes = java_bytes(env, arr[i]);
|
||||
env->SetObjectArrayElement(array, i, bytes);
|
||||
env->DeleteLocalRef(bytes);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_key_1generate_1temporary_1private_1key(JNIEnv *env, jclass clazz) {
|
||||
auto result = tde2e_api::key_generate_temporary_private_key();
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0L;
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jbyteArray Java_org_telegram_messenger_voip_ConferenceCall_key_1to_1public_1key(JNIEnv *env, jclass clazz, jlong private_key) {
|
||||
auto result = tde2e_api::key_to_public_key(private_key);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0L;
|
||||
}
|
||||
return java_bytes(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_key_1from_1public_1key(JNIEnv *env, jclass clazz,
|
||||
jbyteArray public_key) {
|
||||
jsize length = env->GetArrayLength(public_key);
|
||||
jbyte* bytes = env->GetByteArrayElements(public_key, nullptr);
|
||||
std::string_view view(reinterpret_cast<const char*>(bytes), length);
|
||||
auto result = tde2e_api::key_from_public_key(view);
|
||||
env->ReleaseByteArrayElements(public_key, bytes, JNI_ABORT);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0L;
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
|
||||
jobject java_CallParticipant(JNIEnv* env, tde2e_api::CallParticipant o) {
|
||||
const jclass cls = env->FindClass("org/telegram/messenger/voip/ConferenceCall$CallParticipant");
|
||||
jmethodID constructor = env->GetMethodID(cls, "<init>", "()V");
|
||||
|
||||
jobject obj = env->NewObject(cls, constructor);
|
||||
env->SetLongField(obj, env->GetFieldID(cls, "user_id", "J"), o.user_id);
|
||||
env->SetLongField(obj, env->GetFieldID(cls, "public_key_id", "J"), o.public_key_id);
|
||||
env->SetIntField(obj, env->GetFieldID(cls, "permissions", "I"), o.permissions);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
tde2e_api::CallParticipant jni_CallParticipant(JNIEnv* env, jobject o) {
|
||||
const jclass cls = env->FindClass("org/telegram/messenger/voip/ConferenceCall$CallParticipant");
|
||||
|
||||
return {
|
||||
env->GetLongField(o, env->GetFieldID(cls, "user_id", "J")),
|
||||
env->GetLongField(o, env->GetFieldID(cls, "public_key_id", "J")),
|
||||
env->GetIntField(o, env->GetFieldID(cls, "permissions", "I"))
|
||||
};
|
||||
}
|
||||
|
||||
jobject java_CallState(JNIEnv* env, tde2e_api::CallState o) {
|
||||
const jclass cls = env->FindClass("org/telegram/messenger/voip/ConferenceCall$CallState");
|
||||
const jmethodID constructor = env->GetMethodID(cls, "<init>", "()V");
|
||||
|
||||
const jobject obj = env->NewObject(cls, constructor);
|
||||
env->SetIntField(obj, env->GetFieldID(cls, "height", "I"), o.height);
|
||||
|
||||
const jobjectArray array = env->NewObjectArray(
|
||||
o.participants.size(),
|
||||
env->FindClass("org/telegram/messenger/voip/ConferenceCall$CallParticipant"),
|
||||
nullptr
|
||||
);
|
||||
for (jsize i = 0; i < o.participants.size(); ++i) {
|
||||
const auto participant = java_CallParticipant(env, o.participants[i]);
|
||||
env->SetObjectArrayElement(array, i, participant);
|
||||
env->DeleteLocalRef(participant);
|
||||
}
|
||||
env->SetObjectField(obj, env->GetFieldID(cls, "participants", "[Lorg/telegram/messenger/voip/ConferenceCall$CallParticipant;"), array);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
tde2e_api::CallState jni_CallState(JNIEnv* env, jobject o) {
|
||||
const jclass cls = env->FindClass("org/telegram/messenger/voip/ConferenceCall$CallState");
|
||||
|
||||
const jobjectArray array = (jobjectArray) env->GetObjectField(o, env->GetFieldID(cls, "participants", "[Lorg/telegram/messenger/voip/ConferenceCall$CallParticipant;"));
|
||||
const int arraySize = env->GetArrayLength(array);
|
||||
std::vector<tde2e_api::CallParticipant> participants;
|
||||
for (int i = 0; i < arraySize; ++i) {
|
||||
participants.push_back(jni_CallParticipant(env, env->GetObjectArrayElement(array, i)));
|
||||
}
|
||||
|
||||
return {
|
||||
env->GetIntField(o, env->GetFieldID(cls, "height", "I")),
|
||||
participants
|
||||
};
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1create_1zero_1block(JNIEnv *env, jclass clazz,
|
||||
jlong private_key,
|
||||
jobject initial_state) {
|
||||
auto result = tde2e_api::call_create_zero_block(private_key, jni_CallState(env, initial_state));
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return nullptr;
|
||||
}
|
||||
return java_bytes(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1create_1self_1add_1block(JNIEnv *env, jclass clazz,
|
||||
jlong private_key_id,
|
||||
jbyteArray previous_block,
|
||||
jobject self) {
|
||||
jsize length = env->GetArrayLength(previous_block);
|
||||
jbyte* bytes = env->GetByteArrayElements(previous_block, nullptr);
|
||||
std::string_view view(reinterpret_cast<const char*>(bytes), length);
|
||||
auto result = tde2e_api::call_create_self_add_block(private_key_id, view, jni_CallParticipant(env, self));
|
||||
env->ReleaseByteArrayElements(previous_block, bytes, JNI_ABORT);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return nullptr;
|
||||
}
|
||||
return java_bytes(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1create(JNIEnv *env, jclass clazz,
|
||||
jlong user_id,
|
||||
jlong private_key_id,
|
||||
jbyteArray last_block) {
|
||||
jsize length = env->GetArrayLength(last_block);
|
||||
jbyte* bytes = env->GetByteArrayElements(last_block, nullptr);
|
||||
std::string_view view(reinterpret_cast<const char*>(bytes), length);
|
||||
auto result = tde2e_api::call_create(user_id, private_key_id, view);
|
||||
env->ReleaseByteArrayElements(last_block, bytes, JNI_ABORT);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1create_1change_1state_1block(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong call_id,
|
||||
jobject new_state) {
|
||||
auto result = tde2e_api::call_create_change_state_block(call_id, jni_CallState(env, new_state));
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return java_bytes(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1get_1height(JNIEnv *env, jclass clazz,
|
||||
jlong call_id) {
|
||||
auto result = tde2e_api::call_get_height(call_id);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1apply_1block(JNIEnv *env, jclass clazz,
|
||||
jlong call_id,
|
||||
jbyteArray block) {
|
||||
jsize length = env->GetArrayLength(block);
|
||||
jbyte* bytes = env->GetByteArrayElements(block, nullptr);
|
||||
std::string_view view(reinterpret_cast<const char*>(bytes), length);
|
||||
auto result = tde2e_api::call_apply_block(call_id, view);
|
||||
env->ReleaseByteArrayElements(block, bytes, JNI_ABORT);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return java_CallState(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1get_1state(JNIEnv *env, jclass clazz,
|
||||
jlong call_id) {
|
||||
auto result = tde2e_api::call_get_state(call_id);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return java_CallState(env, result.value());
|
||||
}
|
||||
|
||||
jobject java_CallVerificationState(JNIEnv* env, tde2e_api::CallVerificationState o) {
|
||||
const jclass cls = env->FindClass("org/telegram/messenger/voip/ConferenceCall$CallVerificationState");
|
||||
const jmethodID constructor = env->GetMethodID(cls, "<init>", "()V");
|
||||
|
||||
const jobject obj = env->NewObject(cls, constructor);
|
||||
env->SetIntField(obj, env->GetFieldID(cls, "height", "I"), o.height);
|
||||
|
||||
if (o.emoji_hash) {
|
||||
jbyteArray arr = java_bytes(env, o.emoji_hash.value());
|
||||
env->SetObjectField(obj, env->GetFieldID(cls, "emoji_hash", "[B"), arr);
|
||||
env->DeleteLocalRef(arr);
|
||||
} else {
|
||||
env->SetObjectField(obj, env->GetFieldID(cls, "emoji_hash", "[B"), (jbyteArray) nullptr); // set as null
|
||||
}
|
||||
env->DeleteLocalRef(cls);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1get_1verification_1state(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong call_id) {
|
||||
auto result = tde2e_api::call_get_verification_state(call_id);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return java_CallVerificationState(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1receive_1inbound_1message(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong call_id,
|
||||
jbyteArray message) {
|
||||
jsize length = env->GetArrayLength(message);
|
||||
jbyte* bytes = env->GetByteArrayElements(message, nullptr);
|
||||
std::string_view view(reinterpret_cast<const char*>(bytes), length);
|
||||
auto result = tde2e_api::call_receive_inbound_message(call_id, view);
|
||||
env->ReleaseByteArrayElements(message, bytes, JNI_ABORT);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return java_CallVerificationState(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1pull_1outbound_1messages(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong call_id) {
|
||||
auto result = tde2e_api::call_pull_outbound_messages(call_id);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return 0;
|
||||
}
|
||||
return java_bytes2d(env, result.value());
|
||||
}
|
||||
|
||||
jobject java_CallVerificationWords(JNIEnv* env, tde2e_api::CallVerificationWords o) {
|
||||
const jclass cls = env->FindClass("org/telegram/messenger/voip/ConferenceCall$CallVerificationWords");
|
||||
const jmethodID constructor = env->GetMethodID(cls, "<init>", "()V");
|
||||
|
||||
const jobject obj = env->NewObject(cls, constructor);
|
||||
env->SetIntField(obj, env->GetFieldID(cls, "height", "I"), o.height);
|
||||
|
||||
const jclass stringClass = env->FindClass("java/lang/String");
|
||||
const jobjectArray wordArray = env->NewObjectArray(o.words.size(), stringClass, nullptr);
|
||||
for (size_t i = 0; i < o.words.size(); ++i) {
|
||||
const jstring jstr = env->NewStringUTF(o.words[i].c_str());
|
||||
env->SetObjectArrayElement(wordArray, i, jstr);
|
||||
env->DeleteLocalRef(jstr);
|
||||
}
|
||||
env->SetObjectField(obj, env->GetFieldID(cls, "words", "[Ljava/lang/String;"), wordArray);
|
||||
env->DeleteLocalRef(wordArray);
|
||||
env->DeleteLocalRef(cls);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1get_1verification_1words(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong call_id) {
|
||||
auto result = tde2e_api::call_get_verification_words(call_id);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return nullptr;
|
||||
}
|
||||
return java_CallVerificationWords(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1destroy(JNIEnv *env, jclass clazz,
|
||||
jlong call_id) {
|
||||
auto result = tde2e_api::call_destroy(call_id);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1destroy_1all(JNIEnv *env, jclass clazz) {
|
||||
auto result = tde2e_api::call_destroy_all();
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
}
|
||||
}
|
||||
|
||||
jstring java_string(JNIEnv* env, const std::string& s) {
|
||||
jobject bb = env->NewDirectByteBuffer((void*) s.data(), s.size());
|
||||
|
||||
jclass cls_Charset = env->FindClass("java/nio/charset/Charset");
|
||||
jmethodID mid_Charset_forName = env->GetStaticMethodID(cls_Charset, "forName", "(Ljava/lang/String;)Ljava/nio/charset/Charset;");
|
||||
jobject charset = env->CallStaticObjectMethod(cls_Charset, mid_Charset_forName, env->NewStringUTF("UTF-8"));
|
||||
|
||||
jmethodID mid_Charset_decode = env->GetMethodID(cls_Charset, "decode", "(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;");
|
||||
jobject cb = env->CallObjectMethod(charset, mid_Charset_decode, bb);
|
||||
env->DeleteLocalRef(bb);
|
||||
|
||||
jclass cls_CharBuffer = env->FindClass("java/nio/CharBuffer");
|
||||
jmethodID mid_CharBuffer_toString = env->GetMethodID(cls_CharBuffer, "toString", "()Ljava/lang/String;");
|
||||
return (jstring) env->CallObjectMethod(cb, mid_CharBuffer_toString);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1describe(JNIEnv *env, jclass clazz,
|
||||
jlong call_id) {
|
||||
auto result = tde2e_api::call_describe(call_id);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return nullptr;
|
||||
}
|
||||
return java_string(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1describe_1block(JNIEnv *env, jclass clazz,
|
||||
jbyteArray block) {
|
||||
jsize length = env->GetArrayLength(block);
|
||||
jbyte* bytes = env->GetByteArrayElements(block, nullptr);
|
||||
std::string_view view(reinterpret_cast<const char*>(bytes), length);
|
||||
auto result = tde2e_api::call_describe_block(view);
|
||||
env->ReleaseByteArrayElements(block, bytes, JNI_ABORT);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return nullptr;
|
||||
}
|
||||
return java_string(env, result.value());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_telegram_messenger_voip_ConferenceCall_call_1describe_1message(JNIEnv *env, jclass clazz,
|
||||
jbyteArray message) {
|
||||
jsize length = env->GetArrayLength(message);
|
||||
jbyte* bytes = env->GetByteArrayElements(message, nullptr);
|
||||
std::string_view view(reinterpret_cast<const char*>(bytes), length);
|
||||
auto result = tde2e_api::call_describe_message(view);
|
||||
env->ReleaseByteArrayElements(message, bytes, JNI_ABORT);
|
||||
if (!result.is_ok()) {
|
||||
java_throw(env, result.error());
|
||||
return nullptr;
|
||||
}
|
||||
return java_string(env, result.value());
|
||||
}
|
||||
82
TMessagesProj/jni/tde2e/build-tdlib.sh
Executable file
82
TMessagesProj/jni/tde2e/build-tdlib.sh
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
ANDROID_SDK_ROOT=${1:-SDK}
|
||||
ANDROID_NDK_VERSION=${2:-23.2.8568313}
|
||||
OPENSSL_INSTALL_DIR=${3:-third-party/openssl}
|
||||
ANDROID_STL=${4:-c++_static}
|
||||
TDLIB_INTERFACE=${5:-Java}
|
||||
|
||||
if [ "$ANDROID_STL" != "c++_static" ] && [ "$ANDROID_STL" != "c++_shared" ] ; then
|
||||
echo 'Error: ANDROID_STL must be either "c++_static" or "c++_shared".'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$TDLIB_INTERFACE" != "Java" ] && [ "$TDLIB_INTERFACE" != "JSON" ] && [ "$TDLIB_INTERFACE" != "JSONJava" ] ; then
|
||||
echo 'Error: TDLIB_INTERFACE must be either "Java", "JSON", or "JSONJava".'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source ./check-environment.sh || exit 1
|
||||
|
||||
if [ ! -d "$ANDROID_SDK_ROOT" ] ; then
|
||||
echo "Error: directory \"$ANDROID_SDK_ROOT\" doesn't exist. Run ./fetch-sdk.sh first, or provide a valid path to Android SDK."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$OPENSSL_INSTALL_DIR" ] ; then
|
||||
echo "Error: directory \"$OPENSSL_INSTALL_DIR\" doesn't exists. Run ./build-openssl.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ANDROID_SDK_ROOT="$(cd "$(dirname -- "$ANDROID_SDK_ROOT")" >/dev/null; pwd -P)/$(basename -- "$ANDROID_SDK_ROOT")"
|
||||
ANDROID_NDK_ROOT="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION"
|
||||
OPENSSL_INSTALL_DIR="$(cd "$(dirname -- "$OPENSSL_INSTALL_DIR")" >/dev/null; pwd -P)/$(basename -- "$OPENSSL_INSTALL_DIR")"
|
||||
PATH=$ANDROID_SDK_ROOT/cmake/3.22.1/bin:$PATH
|
||||
TDLIB_INTERFACE_OPTION=$([ "$TDLIB_INTERFACE" == "JSON" ] && echo "-DTD_ANDROID_JSON=ON" || [ "$TDLIB_INTERFACE" == "JSONJava" ] && echo "-DTD_ANDROID_JSON_JAVA=ON" || echo "")
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
echo "Generating tde2e source files..."
|
||||
mkdir -p build-native-$TDLIB_INTERFACE || exit 1
|
||||
cd build-native-$TDLIB_INTERFACE
|
||||
cmake -DCMAKE_BUILD_TYPE=Release $TDLIB_INTERFACE_OPTION -DTD_GENERATE_SOURCE_FILES=ON .. || exit 1
|
||||
cmake --build . || exit 1
|
||||
cd ..
|
||||
|
||||
rm -rf tdlib || exit 1
|
||||
|
||||
echo "Building tde2e..."
|
||||
for ABI in arm64-v8a armeabi-v7a x86_64 x86 ; do
|
||||
mkdir -p tdlib/libs/$ABI/ || exit 1
|
||||
|
||||
mkdir -p build-$ABI-$TDLIB_INTERFACE || exit 1
|
||||
cd build-$ABI-$TDLIB_INTERFACE
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake" -DOPENSSL_ROOT_DIR="$OPENSSL_INSTALL_DIR/$ABI" -DCMAKE_BUILD_TYPE=RelWithDebInfo -GNinja -DANDROID_ABI=$ABI -DANDROID_STL=$ANDROID_STL -DANDROID_PLATFORM=android-16 $TDLIB_INTERFACE_OPTION .. || exit 1
|
||||
if [ "$TDLIB_INTERFACE" == "Java" ] || [ "$TDLIB_INTERFACE" == "JSONJava" ] ; then
|
||||
cmake --build . --target tde2e || exit 1
|
||||
fi
|
||||
cd ..
|
||||
|
||||
if [[ "$ANDROID_STL" == "c++_shared" ]] ; then
|
||||
if [[ "$ABI" == "arm64-v8a" ]] ; then
|
||||
FULL_ABI="aarch64-linux-android"
|
||||
elif [[ "$ABI" == "armeabi-v7a" ]] ; then
|
||||
FULL_ABI="arm-linux-androideabi"
|
||||
elif [[ "$ABI" == "x86_64" ]] ; then
|
||||
FULL_ABI="x86_64-linux-android"
|
||||
elif [[ "$ABI" == "x86" ]] ; then
|
||||
FULL_ABI="i686-linux-android"
|
||||
fi
|
||||
cp "$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/sysroot/usr/lib/$FULL_ABI/libc++_shared.so" tdlib/libs/$ABI/ || exit 1
|
||||
"$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin/llvm-strip" tdlib/libs/$ABI/libc++_shared.so || exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Compressing..."
|
||||
rm -f tdlib.zip tdlib-debug.zip || exit 1
|
||||
jar -cMf tdlib-debug.zip tdlib || exit 1
|
||||
rm tdlib/libs/*/*.debug || exit 1
|
||||
jar -cMf tdlib.zip tdlib || exit 1
|
||||
mv tdlib.zip tdlib-debug.zip tdlib || exit 1
|
||||
|
||||
echo "Done."
|
||||
436
TMessagesProj/jni/tde2e/include/e2e_api.h
Normal file
436
TMessagesProj/jni/tde2e/include/e2e_api.h
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace tde2e_api {
|
||||
|
||||
enum class ErrorCode : int {
|
||||
UnknownError = 100,
|
||||
Any = 101,
|
||||
InvalidInput = 102,
|
||||
InvalidKeyId = 103,
|
||||
InvalidId = 104,
|
||||
InvalidBlock = 200,
|
||||
InvalidBlock_NoChanges = 201,
|
||||
InvalidBlock_InvalidSignature = 202,
|
||||
InvalidBlock_HashMismatch = 203,
|
||||
InvalidBlock_HeightMismatch = 204,
|
||||
InvalidBlock_InvalidStateProof_Group = 205,
|
||||
InvalidBlock_InvalidStateProof_Secret = 206,
|
||||
InvalidBlock_NoPermissions = 207,
|
||||
InvalidBlock_InvalidGroupState = 208,
|
||||
InvalidBlock_InvalidSharedSecret = 209,
|
||||
InvalidCallGroupState_NotParticipant = 300,
|
||||
InvalidCallGroupState_WrongUserId = 301,
|
||||
Decrypt_UnknownEpoch = 400,
|
||||
Encrypt_UnknownEpoch = 401,
|
||||
InvalidBroadcast_InFuture = 500,
|
||||
InvalidBroadcast_NotInCommit = 501,
|
||||
InvalidBroadcast_NotInReveal = 502,
|
||||
InvalidBroadcast_UnknownUserId = 503,
|
||||
InvalidBroadcast_AlreadyApplied = 504,
|
||||
InvalidBroadcast_InvalidReveal = 505,
|
||||
InvalidBroadcast_InvalidBlockHash = 506,
|
||||
InvalidCallChannelId = 600,
|
||||
CallFailed = 601,
|
||||
CallKeyAlreadyUsed = 602
|
||||
};
|
||||
inline std::string_view error_string(ErrorCode error_code) {
|
||||
switch (error_code) {
|
||||
case ErrorCode::Any:
|
||||
return "";
|
||||
case ErrorCode::UnknownError:
|
||||
return "UNKNOWN_ERROR";
|
||||
case ErrorCode::InvalidInput:
|
||||
return "INVALID_INPUT";
|
||||
case ErrorCode::InvalidKeyId:
|
||||
return "INVALID_KEY_ID";
|
||||
case ErrorCode::InvalidId:
|
||||
return "INVALID_ID";
|
||||
case ErrorCode::InvalidBlock:
|
||||
return "INVALID_BLOCK";
|
||||
case ErrorCode::InvalidBlock_NoChanges:
|
||||
return "INVALID_BLOCK__NO_CHANGES";
|
||||
case ErrorCode::InvalidBlock_InvalidSignature:
|
||||
return "INVALID_BLOCK__INVALID_SIGNATURE";
|
||||
case ErrorCode::InvalidBlock_HashMismatch:
|
||||
return "INVALID_BLOCK__HASH_MISMATCH";
|
||||
case ErrorCode::InvalidBlock_HeightMismatch:
|
||||
return "INVALID_BLOCK__HEIGHT_MISMATCH";
|
||||
case ErrorCode::InvalidBlock_InvalidStateProof_Group:
|
||||
return "INVALID_BLOCK__INVALID_STATE_PROOF__GROUP";
|
||||
case ErrorCode::InvalidBlock_InvalidStateProof_Secret:
|
||||
return "INVALID_BLOCK__INVALID_STATE_PROOF__SECRET";
|
||||
case ErrorCode::InvalidBlock_InvalidGroupState:
|
||||
return "INVALID_BLOCK__INVALID_GROUP_STATE";
|
||||
case ErrorCode::InvalidBlock_InvalidSharedSecret:
|
||||
return "INVALID_BLOCK__INVALID_SHARED_SECRET";
|
||||
case ErrorCode::InvalidBlock_NoPermissions:
|
||||
return "INVALID_BLOCK__NO_PERMISSIONS";
|
||||
case ErrorCode::InvalidCallGroupState_NotParticipant:
|
||||
return "INVALID_CALL_GROUP_STATE__NOT_PARTICIPANT";
|
||||
case ErrorCode::InvalidCallGroupState_WrongUserId:
|
||||
return "INVALID_CALL_GROUP_STATE__WRONG_USER_ID";
|
||||
case ErrorCode::Decrypt_UnknownEpoch:
|
||||
return "DECRYPT__UNKNOWN_EPOCH";
|
||||
case ErrorCode::Encrypt_UnknownEpoch:
|
||||
return "ENCRYPT__UNKNOWN_EPOCH";
|
||||
case ErrorCode::InvalidBroadcast_InFuture:
|
||||
return "INVALID_BROADCAST__IN_FUTURE";
|
||||
case ErrorCode::InvalidBroadcast_NotInCommit:
|
||||
return "INVALID_BROADCAST__NOT_IN_COMMIT";
|
||||
case ErrorCode::InvalidBroadcast_NotInReveal:
|
||||
return "INVALID_BROADCAST__NOT_IN_REVEAL";
|
||||
case ErrorCode::InvalidBroadcast_UnknownUserId:
|
||||
return "INVALID_BROADCAST__UNKNOWN_USER_ID";
|
||||
case ErrorCode::InvalidBroadcast_AlreadyApplied:
|
||||
return "INVALID_BROADCAST__ALREADY_APPLIED";
|
||||
case ErrorCode::InvalidBroadcast_InvalidReveal:
|
||||
return "INVALID_BROADCAST__INVALID_REVEAL";
|
||||
case ErrorCode::InvalidBroadcast_InvalidBlockHash:
|
||||
return "INVALID_BROADCAST__INVALID_BLOCK_HASH";
|
||||
case ErrorCode::CallFailed:
|
||||
return "CALL_FAILED";
|
||||
case ErrorCode::CallKeyAlreadyUsed:
|
||||
return "CALL_KEY_ALREADY_USED";
|
||||
case ErrorCode::InvalidCallChannelId:
|
||||
return "INVALID_CALL_CHANNEL_ID";
|
||||
}
|
||||
return "UNKNOWN_ERROR";
|
||||
}
|
||||
|
||||
} // namespace tde2e_api
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace td {
|
||||
template <class T>
|
||||
class Result;
|
||||
class Status;
|
||||
} // namespace td
|
||||
|
||||
namespace tde2e_api {
|
||||
//
|
||||
// Result and Error helper classes
|
||||
//
|
||||
|
||||
struct Error {
|
||||
ErrorCode code;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Result {
|
||||
public:
|
||||
Result(const T &value) : data_(value) {
|
||||
}
|
||||
Result(T &&value) : data_(std::move(value)) {
|
||||
}
|
||||
|
||||
Result(const Error &error) : data_(error) {
|
||||
}
|
||||
Result(Error &&error) : data_(std::move(error)) {
|
||||
}
|
||||
|
||||
Result(td::Result<T> &&value);
|
||||
Result(td::Status &&status);
|
||||
|
||||
// Check if the result is a success
|
||||
bool is_ok() const {
|
||||
return std::holds_alternative<T>(data_);
|
||||
}
|
||||
|
||||
T &value() {
|
||||
return std::get<T>(data_);
|
||||
}
|
||||
const T &value() const {
|
||||
return std::get<T>(data_);
|
||||
}
|
||||
|
||||
Error &error() {
|
||||
return std::get<Error>(data_);
|
||||
}
|
||||
const Error &error() const {
|
||||
return std::get<Error>(data_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<T, Error> data_;
|
||||
};
|
||||
|
||||
//
|
||||
// Encryption
|
||||
//
|
||||
|
||||
using Int256 = std::array<unsigned char, 32>;
|
||||
using Int512 = std::array<unsigned char, 64>;
|
||||
//TODO: strong typed ids
|
||||
using PublicKey = std::string;
|
||||
using HandshakeId = std::int64_t;
|
||||
using LoginId = std::int64_t;
|
||||
using UserId = std::int64_t;
|
||||
using AnyKeyId = std::int64_t;
|
||||
using PrivateKeyId = std::int64_t;
|
||||
using PublicKeyId = std::int64_t;
|
||||
using SymmetricKeyId = std::int64_t;
|
||||
using Bytes = std::string;
|
||||
using SecureBytes = std::string;
|
||||
using Slice = std::string_view;
|
||||
using SecureSlice = std::string_view;
|
||||
struct Ok {};
|
||||
|
||||
struct EncryptedMessageForMany {
|
||||
std::vector<std::string> encrypted_headers;
|
||||
std::string encrypted_message;
|
||||
};
|
||||
|
||||
Result<Ok> set_log_verbosity_level(int level);
|
||||
|
||||
// Keys management
|
||||
// private keys will stay inside the library when it is possible
|
||||
// all keys are stored only in memory and should be created or imported before usage
|
||||
Result<PrivateKeyId> key_generate_private_key();
|
||||
Result<PrivateKeyId> key_generate_temporary_private_key();
|
||||
Result<SymmetricKeyId> key_derive_secret(PrivateKeyId key_id, Slice tag);
|
||||
Result<SymmetricKeyId> key_from_bytes(SecureSlice secret);
|
||||
Result<Bytes> key_to_encrypted_private_key(PrivateKeyId key_id, SymmetricKeyId secret_id);
|
||||
Result<PrivateKeyId> key_from_encrypted_private_key(Slice encrypted_key, SymmetricKeyId secret_id);
|
||||
Result<PublicKeyId> key_from_public_key(Slice public_key);
|
||||
Result<SymmetricKeyId> key_from_ecdh(PrivateKeyId key_id, PublicKeyId other_public_key_id);
|
||||
Result<PublicKey> key_to_public_key(PrivateKeyId key_id);
|
||||
Result<SecureBytes> key_to_words(PrivateKeyId key_id);
|
||||
Result<PrivateKeyId> key_from_words(SecureSlice words);
|
||||
Result<Int512> key_sign(PrivateKeyId key, Slice data);
|
||||
Result<Ok> key_destroy(AnyKeyId key_id);
|
||||
Result<Ok> key_destroy_all();
|
||||
|
||||
// Used to encrypt key between processes, secret_id must be generated with key_from_ecdh
|
||||
Result<Bytes> key_to_encrypted_private_key_internal(PrivateKeyId key_id, SymmetricKeyId secret_id);
|
||||
Result<PrivateKeyId> key_from_encrypted_private_key_internal(Slice encrypted_key, SymmetricKeyId secret_id);
|
||||
|
||||
Result<EncryptedMessageForMany> encrypt_message_for_many(const std::vector<SymmetricKeyId> &key_ids,
|
||||
SecureSlice message);
|
||||
// keeps encrypted_message empty in result
|
||||
Result<EncryptedMessageForMany> re_encrypt_message_for_many(SymmetricKeyId decrypt_key_id,
|
||||
const std::vector<SymmetricKeyId> &encrypt_key_ids,
|
||||
Slice encrypted_header, Slice encrypted_message);
|
||||
Result<SecureBytes> decrypt_message_for_many(SymmetricKeyId key_id, Slice encrypted_header, Slice encrypted_message);
|
||||
Result<Bytes> encrypt_message_for_one(SymmetricKeyId key_id, SecureSlice message);
|
||||
Result<SecureBytes> decrypt_message_for_one(SymmetricKeyId key_id, Slice encrypted_message);
|
||||
|
||||
// Utilities for secret key verification/transfer via qr (or any other alternative channel)
|
||||
// Requires:
|
||||
// - alternative channel to reliably transfer 'start' message from Bob to Alice (scanning QR is such channel)
|
||||
//
|
||||
// Alice:
|
||||
// - knows shared secret right after the handshake creation
|
||||
// - Bob's secret key is verified after finish is received
|
||||
//
|
||||
// Bob:
|
||||
// - knows shared secret right after accept is received
|
||||
// - Alice's secret key is verified after accept is received
|
||||
//
|
||||
// Use cases:
|
||||
// - Transfer of the key from old device to the new device
|
||||
// - Verification of other person's public key
|
||||
// - Contact sharing
|
||||
//
|
||||
Result<HandshakeId> handshake_create_for_bob(UserId bob_user_id, PrivateKeyId bob_private_key_id);
|
||||
Result<HandshakeId> handshake_create_for_alice(UserId alice_user_id, PrivateKeyId alice_private_key_id,
|
||||
UserId bob_user_id, const PublicKey &bob_public_key, Slice start);
|
||||
|
||||
Result<Bytes> handshake_bob_send_start(HandshakeId bob_handshake_id);
|
||||
Result<Bytes> handshake_alice_send_accept(HandshakeId alice_handshake_id);
|
||||
Result<Bytes> handshake_bob_receive_accept_send_finish(HandshakeId bob_handshake_id, UserId alice_id,
|
||||
const PublicKey &alice_public_key, Slice accept);
|
||||
Result<Ok> handshake_alice_receive_finish(HandshakeId alice_handshake_id, Slice finish);
|
||||
Result<SymmetricKeyId> handshake_get_shared_key_id(HandshakeId handshake_id);
|
||||
Result<Ok> handshake_destroy(HandshakeId handshake_id);
|
||||
Result<Ok> handshake_destroy_all();
|
||||
|
||||
// Helper to get QR-code identifier
|
||||
Result<Bytes> handshake_start_id(Slice start);
|
||||
|
||||
// There is wrapper for login
|
||||
Result<LoginId> login_create_for_bob();
|
||||
Result<Bytes> login_bob_send_start(LoginId bob_login_id);
|
||||
Result<Bytes> login_create_for_alice(UserId alice_user_id, PrivateKeyId alice_private_key_id, Slice start);
|
||||
Result<PrivateKeyId> login_finish_for_bob(LoginId bob_login_id, UserId alice_user_id, const PublicKey &alice_public_key,
|
||||
Slice data);
|
||||
Result<Ok> login_destroy(LoginId login_id);
|
||||
Result<Ok> login_destroy_all();
|
||||
|
||||
// Personal info
|
||||
|
||||
// 1. Each entry stored and signed separately
|
||||
// 2. Signature is never stored, but always is verified
|
||||
// 3. It should be possible to save data without signature (but it can't override data with signature)
|
||||
// 4. We should keep source of entry. Our, Server, Contact+Ts
|
||||
// 5. We must keep is_contact flag.
|
||||
|
||||
template <class T>
|
||||
struct Entry {
|
||||
enum Source { Self, Server, Contact };
|
||||
Source source;
|
||||
std::uint32_t timestamp;
|
||||
T value;
|
||||
|
||||
Entry() : source(Self), timestamp(0), value() {
|
||||
}
|
||||
Entry(Source source, std::uint32_t timestamp, T &&value)
|
||||
: source(source), timestamp(timestamp), value(std::move(value)) {
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct SignedEntry {
|
||||
Int512 signature;
|
||||
std::uint32_t timestamp{0};
|
||||
T value;
|
||||
};
|
||||
|
||||
struct Name {
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
};
|
||||
|
||||
struct PhoneNumber {
|
||||
std::string phone_number;
|
||||
};
|
||||
|
||||
struct EmojiNonces {
|
||||
std::optional<Int256> self_nonce;
|
||||
std::optional<Int256> contact_nonce_hash;
|
||||
std::optional<Int256> contact_nonce;
|
||||
};
|
||||
|
||||
struct ContactState {
|
||||
enum State { Unknown, Contact, NotContact };
|
||||
State state{Unknown};
|
||||
|
||||
ContactState() = default;
|
||||
|
||||
explicit ContactState(State state) : state(state) {
|
||||
}
|
||||
};
|
||||
|
||||
struct Contact {
|
||||
// Each contact has both public_key and user_id
|
||||
// how it is stored internally is not clients library user's concern
|
||||
std::uint32_t generation{0};
|
||||
|
||||
// we always have public key. Essentially contact is defined by its public key
|
||||
PublicKeyId public_key{};
|
||||
|
||||
// Personal data, signed by contact. If it is not signed, it has no relations to this public key
|
||||
std::optional<Entry<UserId>> o_user_id;
|
||||
std::optional<Entry<Name>> o_name;
|
||||
std::optional<Entry<PhoneNumber>> o_phone_number;
|
||||
|
||||
// Personal data save by user_itself
|
||||
// It always exists and it always given but we keep timestamp just in case
|
||||
Entry<EmojiNonces> emoji_nonces;
|
||||
Entry<ContactState> contact_state;
|
||||
};
|
||||
|
||||
// library can't enforce persistency of changes, because it has no persistent state.
|
||||
// so it is client's responsibility to ensure that each change will be eventually saved to the server
|
||||
// NB: it is unclear how protect ourself from server ignoring our queries.
|
||||
|
||||
using StorageId = std::int64_t;
|
||||
using UpdateId = std::int64_t;
|
||||
|
||||
struct StorageBlockchainState {
|
||||
std::string next_suggested_block;
|
||||
std::vector<std::string> required_proofs;
|
||||
};
|
||||
struct StorageUpdates {
|
||||
std::vector<std::pair<PublicKeyId, std::optional<Contact>>> updates;
|
||||
};
|
||||
|
||||
Result<StorageId> storage_create(PrivateKeyId key_id, Slice last_block);
|
||||
Result<Ok> storage_destroy(StorageId storage_id);
|
||||
Result<Ok> storage_destroy_all();
|
||||
|
||||
template <class T>
|
||||
Result<UpdateId> storage_update_contact(StorageId storage_id, PublicKeyId key, SignedEntry<T> signed_entry);
|
||||
template <class T>
|
||||
Result<SignedEntry<T>> storage_sign_entry(PrivateKeyId key, Entry<T> entry);
|
||||
|
||||
Result<std::optional<Contact>> storage_get_contact(StorageId storage_id, PublicKeyId key);
|
||||
Result<std::optional<Contact>> storage_get_contact_optimistic(StorageId storage_id, PublicKeyId key);
|
||||
|
||||
Result<std::int64_t> storage_blockchain_height(StorageId storage_id);
|
||||
Result<StorageUpdates> storage_blockchain_apply_block(StorageId storage_id, Slice block);
|
||||
Result<Ok> storage_blockchain_add_proof(StorageId storage_id, Slice proof, const std::vector<std::string> &keys);
|
||||
|
||||
Result<StorageBlockchainState> storage_get_blockchain_state(StorageId);
|
||||
|
||||
using CallId = std::int64_t;
|
||||
using CallChannelId = std::int32_t;
|
||||
struct CallParticipant {
|
||||
UserId user_id;
|
||||
PublicKeyId public_key_id;
|
||||
int permissions{};
|
||||
};
|
||||
|
||||
struct CallState {
|
||||
int height{};
|
||||
std::vector<CallParticipant> participants;
|
||||
};
|
||||
|
||||
Result<Bytes> call_create_zero_block(PrivateKeyId private_key_id, const CallState &initial_state);
|
||||
Result<Bytes> call_create_self_add_block(PrivateKeyId private_key_id, Slice previous_block,
|
||||
const CallParticipant &self);
|
||||
Result<CallId> call_create(UserId user_id, PrivateKeyId private_key_id, Slice last_block);
|
||||
|
||||
Result<std::string> call_describe(CallId call);
|
||||
Result<std::string> call_describe_block(Slice block);
|
||||
Result<std::string> call_describe_message(Slice message);
|
||||
|
||||
Result<Bytes> call_create_change_state_block(CallId call_id, const CallState &new_state);
|
||||
Result<Bytes> call_encrypt(CallId call_id, CallChannelId channel_id, SecureSlice message,
|
||||
size_t unencrypted_prefix_size);
|
||||
Result<SecureBytes> call_decrypt(CallId call_id, UserId user_id, CallChannelId channel_id, Slice message);
|
||||
|
||||
Result<int> call_get_height(CallId call_id);
|
||||
Result<CallState> call_apply_block(CallId call_id, Slice block);
|
||||
|
||||
Result<CallState> call_get_state(CallId call_id);
|
||||
|
||||
struct CallVerificationState {
|
||||
int height{};
|
||||
std::optional<Bytes> emoji_hash;
|
||||
};
|
||||
Result<CallVerificationState> call_get_verification_state(CallId call_id);
|
||||
Result<CallVerificationState> call_receive_inbound_message(CallId call_id, Slice message);
|
||||
|
||||
// should be called after:
|
||||
// - creation
|
||||
// - call_apply_block
|
||||
// - call_receive_inbound_messages
|
||||
Result<std::vector<Bytes>> call_pull_outbound_messages(CallId call_id);
|
||||
|
||||
struct CallVerificationWords {
|
||||
int height{};
|
||||
std::vector<std::string> words;
|
||||
};
|
||||
|
||||
Result<CallVerificationWords> call_get_verification_words(CallId call_id);
|
||||
Result<Ok> call_destroy(CallId call_id);
|
||||
Result<Ok> call_destroy_all();
|
||||
|
||||
} // namespace tde2e_api
|
||||
Loading…
Add table
Add a link
Reference in a new issue