1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include <td/telegram/Client.h>
8 #include <td/telegram/Log.h>
9 #include <td/telegram/td_api.h>
10 
11 #include <td/tl/tl_jni_object.h>
12 
13 #include <cstdint>
14 #include <cstdlib>
15 #include <string>
16 #include <utility>
17 
18 namespace td_jni {
19 
fetch_function(JNIEnv * env,jobject function)20 static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env, jobject function) {
21   td::jni::reset_parse_error();
22   auto result = td::td_api::Function::fetch(env, function);
23   if (td::jni::have_parse_error()) {
24     std::abort();
25   }
26   return result;
27 }
28 
get_manager()29 static td::ClientManager *get_manager() {
30   return td::ClientManager::get_manager_singleton();
31 }
32 
Client_createNativeClient(JNIEnv * env,jclass clazz)33 static jint Client_createNativeClient(JNIEnv *env, jclass clazz) {
34   return static_cast<jint>(get_manager()->create_client_id());
35 }
36 
Client_nativeClientSend(JNIEnv * env,jclass clazz,jint client_id,jlong id,jobject function)37 static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jint client_id, jlong id, jobject function) {
38   get_manager()->send(static_cast<std::int32_t>(client_id), static_cast<std::uint64_t>(id),
39                       fetch_function(env, function));
40 }
41 
Client_nativeClientReceive(JNIEnv * env,jclass clazz,jintArray client_ids,jlongArray ids,jobjectArray events,jdouble timeout)42 static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jintArray client_ids, jlongArray ids,
43                                        jobjectArray events, jdouble timeout) {
44   jsize events_size = env->GetArrayLength(ids);  // client_ids, ids and events must be of equal size
45   if (events_size == 0) {
46     return 0;
47   }
48   jsize result_size = 0;
49 
50   auto *manager = get_manager();
51   auto response = manager->receive(timeout);
52   while (response.object) {
53     auto client_id = static_cast<jint>(response.client_id);
54     env->SetIntArrayRegion(client_ids, result_size, 1, &client_id);
55 
56     auto request_id = static_cast<jlong>(response.request_id);
57     env->SetLongArrayRegion(ids, result_size, 1, &request_id);
58 
59     jobject object;
60     response.object->store(env, object);
61     env->SetObjectArrayElement(events, result_size, object);
62     env->DeleteLocalRef(object);
63 
64     result_size++;
65     if (result_size == events_size) {
66       break;
67     }
68 
69     response = manager->receive(0);
70   }
71   return result_size;
72 }
73 
Client_nativeClientExecute(JNIEnv * env,jclass clazz,jobject function)74 static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) {
75   jobject result;
76   td::ClientManager::execute(fetch_function(env, function))->store(env, result);
77   return result;
78 }
79 
Log_setVerbosityLevel(JNIEnv * env,jclass clazz,jint new_log_verbosity_level)80 static void Log_setVerbosityLevel(JNIEnv *env, jclass clazz, jint new_log_verbosity_level) {
81   td::Log::set_verbosity_level(static_cast<int>(new_log_verbosity_level));
82 }
83 
Log_setFilePath(JNIEnv * env,jclass clazz,jstring file_path)84 static jboolean Log_setFilePath(JNIEnv *env, jclass clazz, jstring file_path) {
85   return td::Log::set_file_path(td::jni::from_jstring(env, file_path)) ? JNI_TRUE : JNI_FALSE;
86 }
87 
Log_setMaxFileSize(JNIEnv * env,jclass clazz,jlong max_file_size)88 static void Log_setMaxFileSize(JNIEnv *env, jclass clazz, jlong max_file_size) {
89   td::Log::set_max_file_size(max_file_size);
90 }
91 
Object_toString(JNIEnv * env,jobject object)92 static jstring Object_toString(JNIEnv *env, jobject object) {
93   return td::jni::to_jstring(env, to_string(td::td_api::Object::fetch(env, object)));
94 }
95 
Function_toString(JNIEnv * env,jobject object)96 static jstring Function_toString(JNIEnv *env, jobject object) {
97   return td::jni::to_jstring(env, to_string(td::td_api::Function::fetch(env, object)));
98 }
99 
100 static constexpr jint JAVA_VERSION = JNI_VERSION_1_6;
101 static JavaVM *java_vm;
102 static jclass log_class;
103 
on_log_message(int verbosity_level,const char * error_message)104 static void on_log_message(int verbosity_level, const char *error_message) {
105   if (verbosity_level != 0) {
106     return;
107   }
108   auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION);
109   if (env == nullptr) {
110     return;
111   }
112   jmethodID on_fatal_error_method = env->GetStaticMethodID(log_class, "onFatalError", "(Ljava/lang/String;)V");
113   if (on_fatal_error_method) {
114     jstring error_str = td::jni::to_jstring(env.get(), error_message);
115     env->CallStaticVoidMethod(log_class, on_fatal_error_method, error_str);
116     if (error_str) {
117       env->DeleteLocalRef(error_str);
118     }
119   }
120 }
121 
register_native(JavaVM * vm)122 static jint register_native(JavaVM *vm) {
123   JNIEnv *env;
124   if (vm->GetEnv(reinterpret_cast<void **>(&env), JAVA_VERSION) != JNI_OK) {
125     return -1;
126   }
127 
128   java_vm = vm;
129 
130   auto register_method = [env](jclass clazz, std::string name, std::string signature, auto function_ptr) {
131     td::jni::register_native_method(env, clazz, std::move(name), std::move(signature),
132                                     reinterpret_cast<void *>(function_ptr));
133   };
134 
135   auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/Client");
136   log_class = td::jni::get_jclass(env, PACKAGE_NAME "/Log");
137   auto object_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Object");
138   auto function_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Function");
139 
140 #define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;"
141 #define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;"
142   register_method(client_class, "createNativeClient", "()I", Client_createNativeClient);
143   register_method(client_class, "nativeClientSend", "(IJ" TD_FUNCTION ")V", Client_nativeClientSend);
144   register_method(client_class, "nativeClientReceive", "([I[J[" TD_OBJECT "D)I", Client_nativeClientReceive);
145   register_method(client_class, "nativeClientExecute", "(" TD_FUNCTION ")" TD_OBJECT, Client_nativeClientExecute);
146 
147   register_method(log_class, "setVerbosityLevel", "(I)V", Log_setVerbosityLevel);
148   register_method(log_class, "setFilePath", "(Ljava/lang/String;)Z", Log_setFilePath);
149   register_method(log_class, "setMaxFileSize", "(J)V", Log_setMaxFileSize);
150 
151   register_method(object_class, "toString", "()Ljava/lang/String;", Object_toString);
152 
153   register_method(function_class, "toString", "()Ljava/lang/String;", Function_toString);
154 #undef TD_FUNCTION
155 #undef TD_OBJECT
156 
157   td::jni::init_vars(env, PACKAGE_NAME);
158   td::td_api::Object::init_jni_vars(env, PACKAGE_NAME);
159   td::td_api::Function::init_jni_vars(env, PACKAGE_NAME);
160   td::ClientManager::set_log_message_callback(0, on_log_message);
161 
162   return JAVA_VERSION;
163 }
164 
165 }  // namespace td_jni
166 
JNI_OnLoad(JavaVM * vm,void * reserved)167 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
168   static jint jni_version = td_jni::register_native(vm);  // call_once
169   return jni_version;
170 }
171