1 /*
2 * LwsService.cpp - libwebsockets test service for Android
3 *
4 * Copyright (C) 2016 Alexander Bruines <alexander.bruines@gmail.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 * The person who associated a work with this deed has dedicated
10 * the work to the public domain by waiving all of his or her rights
11 * to the work worldwide under copyright law, including all related
12 * and neighboring rights, to the extent allowed by law. You can copy,
13 * modify, distribute and perform the work, even for commercial purposes,
14 * all without asking permission.
15 *
16 * The test apps are intended to be adapted for use in your code, which
17 * may be proprietary. So unlike the library itself, they are licensed
18 * Public Domain.
19 */
20
21 #include <libwebsockets.h>
22
23 #include <jni.h>
24 #include <android/log.h>
25 #define printf(...) __android_log_print(ANDROID_LOG_VERBOSE, "LwsService", ##__VA_ARGS__)
26
27 /////////////////////////////////////////////////////////
28 // Code executed when loading the dynamic link library //
29 /////////////////////////////////////////////////////////
30
31 // The Java class the native functions shall be part of
32 #define JNIREG_CLASS "org/libwebsockets/client/LwsService"
33
34 JavaVM* gJvm = NULL;
35 JNIEnv* gEnv = 0;
36
37 JNIEXPORT jboolean JNICALL jni_initLws(JNIEnv *env, jobject obj);
38 JNIEXPORT void JNICALL jni_exitLws(JNIEnv *env, jobject obj);
39 JNIEXPORT void JNICALL jni_serviceLws(JNIEnv *env, jobject obj);
40 JNIEXPORT void JNICALL jni_setConnectionParameters(JNIEnv *env, jobject obj, jstring serverAddress, jint serverPort);
41 JNIEXPORT jboolean JNICALL jni_connectLws(JNIEnv *env, jobject obj);
42
43 static JNINativeMethod gMethods[] = {
44 { "initLws", "()Z", (void*)jni_initLws },
45 { "exitLws", "()V", (void*)jni_exitLws },
46 { "serviceLws", "()V", (void*)jni_serviceLws },
47 { "setConnectionParameters", "(Ljava/lang/String;I)V", (void*)jni_setConnectionParameters },
48 { "connectLws", "()Z", (void*)jni_connectLws },
49 };
50
registerNativeMethods(JNIEnv * env,const char * className,JNINativeMethod * gMethods,int numMethods)51 static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods)
52 {
53 jclass cls;
54 cls = env->FindClass(className);
55 if(cls == NULL) {
56 return JNI_FALSE;
57 }
58 if (env->RegisterNatives(cls, gMethods, numMethods) < 0) {
59 return JNI_FALSE;
60 }
61
62 return JNI_TRUE;
63 }
64
registerNatives(JNIEnv * env)65 static int registerNatives(JNIEnv* env)
66 {
67 if(!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
68 return JNI_FALSE;
69 }
70 return JNI_TRUE;
71 }
72
JNI_OnLoad(JavaVM * vm,void * reserved)73 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void * reserved) {
74 jint result = -1;
75
76 gJvm = vm;
77 if(vm->GetEnv((void**)&gEnv, JNI_VERSION_1_6) != JNI_OK) goto bail;
78 if(vm->AttachCurrentThread(&gEnv, NULL) < 0) goto bail;
79 if(registerNatives(gEnv) != JNI_TRUE) goto bail;
80
81 result = JNI_VERSION_1_6;
82
83 bail:
84 return result;
85 }
86
JNI_OnUnload(JavaVM * vm,void * reserved)87 JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
88 gJvm = NULL;
89 }
90
91 ////////////////////////////////////////////////////
92 // JNI functions to export: //
93 ////////////////////////////////////////////////////
94
95 static jclass gLwsServiceCls;
96 static jobject gLwsServiceObj;
97 static jmethodID sendMessageId;
98
99 static const int MSG_DUMB_INCREMENT_PROTOCOL_COUNTER = 1;
100 static const int MSG_LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 2;
101 static const int MSG_LWS_CALLBACK_CLIENT_ESTABLISHED = 3;
102
103 #define BUFFER_SIZE 4096
104
105 static struct lws_context *context = NULL;
106 static struct lws_context_creation_info info;
107 static struct lws *wsi = NULL;
108
109 // prevents sending messages after jni_exitLws had been called
110 static int isExit = 0;
111
112 enum websocket_protocols {
113 PROTOCOL_DUMB_INCREMENT = 0,
114 PROTOCOL_COUNT
115 };
116
117 struct per_session_data {
118 ;// no data
119 };
120
121 static int callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len );
122
123 static struct lws_protocols protocols[] = {
124 {
125 "dumb-increment-protocol",
126 callback,
127 sizeof( struct per_session_data ),
128 BUFFER_SIZE,
129 },
130 { NULL, NULL, 0, 0 } // end of list
131 };
132
133 static const struct lws_extension exts[] = {
134 {
135 "deflate-frame",
136 lws_extension_callback_pm_deflate,
137 "deflate_frame"
138 },
139 { NULL, NULL, NULL }
140 };
141
142 static int port = 0;
143 static int use_ssl = 0;
144 static int use_ssl_client = 0;
145 static char address[8192];
146
147 static char ca_cert[8192];
148 static char client_cert[8192];
149 static char client_cert_key[8192];
150
151 static int deny_deflate = 0;
152 static int deny_mux = 0;
153
154 // Logging function for libwebsockets
emit_log(int level,const char * msg)155 static void emit_log(int level, const char *msg)
156 {
157 printf("%s", msg);
158 }
159
160
jni_initLws(JNIEnv * env,jobject obj)161 JNIEXPORT jboolean JNICALL jni_initLws(JNIEnv *env, jobject obj)
162 {
163 if(context) return JNI_TRUE;
164
165 // Attach the java virtual machine to this thread
166 gJvm->AttachCurrentThread(&gEnv, NULL);
167
168 // Set java global references to the class and object
169 jclass cls = env->GetObjectClass(obj);
170 gLwsServiceCls = (jclass) env->NewGlobalRef(cls);
171 gLwsServiceObj = env->NewGlobalRef(obj);
172
173 // Get the sendMessage method from the LwsService class (inherited from class ThreadService)
174 sendMessageId = gEnv->GetMethodID(gLwsServiceCls, "sendMessage", "(ILjava/lang/Object;)V");
175
176 memset(&info, 0, sizeof(info));
177 info.port = CONTEXT_PORT_NO_LISTEN;
178 info.protocols = protocols;
179 #if !defined(LWS_WITHOUT_EXTENSIONS)
180 info.extensions = exts;
181 #endif
182 info.gid = -1;
183 info.uid = -1;
184
185 lws_set_log_level( LLL_NOTICE | LLL_INFO | LLL_ERR | LLL_WARN | LLL_CLIENT, emit_log );
186
187 context = lws_create_context(&info);
188 if( context == NULL ){
189 emit_log(LLL_ERR, "Creating libwebsocket context failed");
190 return JNI_FALSE;
191 }
192
193 isExit = 0;
194
195 return JNI_TRUE;
196 }
197
198 // Send a message to the client of the service
199 // (must call jni_initLws() first)
sendMessage(int id,jobject obj)200 static inline void sendMessage(int id, jobject obj)
201 {
202 if(!isExit) gEnv->CallVoidMethod(gLwsServiceObj, sendMessageId, id, obj);
203 }
204
jni_exitLws(JNIEnv * env,jobject obj)205 JNIEXPORT void JNICALL jni_exitLws(JNIEnv *env, jobject obj)
206 {
207 if(context){
208 isExit = 1;
209 lws_context_destroy(context);
210 context = NULL;
211 env->DeleteGlobalRef(gLwsServiceObj);
212 env->DeleteGlobalRef(gLwsServiceCls);
213 }
214 }
215
callback(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)216 static int callback(
217 struct lws *wsi,
218 enum lws_callback_reasons reason,
219 void *user,
220 void *in,
221 size_t len
222 )
223 {
224 switch(reason){
225
226 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
227 sendMessage(MSG_LWS_CALLBACK_CLIENT_CONNECTION_ERROR, NULL);
228 break;
229
230 case LWS_CALLBACK_CLIENT_ESTABLISHED:
231 sendMessage(MSG_LWS_CALLBACK_CLIENT_ESTABLISHED, NULL);
232 break;
233
234 case LWS_CALLBACK_CLIENT_RECEIVE:
235 ((char *)in)[len] = '\0';
236 sendMessage(MSG_DUMB_INCREMENT_PROTOCOL_COUNTER, gEnv->NewStringUTF((const char*)in));
237 break;
238
239 case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
240 if ((strcmp((const char*)in, "deflate-stream") == 0) && deny_deflate) {
241 emit_log(LLL_ERR, "websocket: denied deflate-stream extension");
242 return 1;
243 }
244 if ((strcmp((const char*)in, "deflate-frame") == 0) && deny_deflate) {
245 emit_log(LLL_ERR, "websocket: denied deflate-frame extension");
246 return 1;
247 }
248 if ((strcmp((const char*)in, "x-google-mux") == 0) && deny_mux) {
249 emit_log(LLL_ERR, "websocket: denied x-google-mux extension");
250 return 1;
251 }
252 break;
253
254 default:
255 break;
256 }
257
258 return 0;
259 }
260
jni_serviceLws(JNIEnv * env,jobject obj)261 JNIEXPORT void JNICALL jni_serviceLws(JNIEnv *env, jobject obj)
262 {
263 if(context){
264 lws_service( context, 0 );
265 }
266 }
267
jni_setConnectionParameters(JNIEnv * env,jobject obj,jstring serverAddress,jint serverPort)268 JNIEXPORT void JNICALL jni_setConnectionParameters(
269 JNIEnv *env,
270 jobject obj,
271 jstring serverAddress,
272 jint serverPort
273 )
274 {
275 address[0] = 0;
276 port = serverPort;
277 use_ssl = 0;
278 use_ssl_client = 0;
279 snprintf(address, sizeof(address), "%s", env->GetStringUTFChars(serverAddress, 0));
280 }
281
jni_connectLws(JNIEnv * env,jobject obj)282 JNIEXPORT jboolean JNICALL jni_connectLws(JNIEnv *env, jobject obj)
283 {
284 struct lws_client_connect_info info_ws;
285 memset(&info_ws, 0, sizeof(info_ws));
286
287 info_ws.port = port;
288 info_ws.address = address;
289 info_ws.path = "/";
290 info_ws.context = context;
291 info_ws.ssl_connection = use_ssl;
292 info_ws.host = address;
293 info_ws.origin = address;
294 info_ws.ietf_version_or_minus_one = -1;
295 info_ws.client_exts = exts;
296 info_ws.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
297
298 // connect
299 wsi = lws_client_connect_via_info(&info_ws);
300 if(wsi == NULL ){
301 // Error
302 emit_log(LLL_ERR, "Protocol failed to connect.");
303 return JNI_FALSE;
304 }
305
306 return JNI_TRUE;
307 }
308