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
34JavaVM* gJvm = NULL;
35JNIEnv* gEnv = 0;
36
37JNIEXPORT jboolean JNICALL jni_initLws(JNIEnv *env, jobject obj);
38JNIEXPORT void JNICALL jni_exitLws(JNIEnv *env, jobject obj);
39JNIEXPORT void JNICALL jni_serviceLws(JNIEnv *env, jobject obj);
40JNIEXPORT void JNICALL jni_setConnectionParameters(JNIEnv *env, jobject obj, jstring serverAddress, jint serverPort);
41JNIEXPORT jboolean JNICALL jni_connectLws(JNIEnv *env, jobject obj);
42
43static 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
51static 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
65static 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
73JNIEXPORT 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
83bail:
84    return result;
85}
86
87JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
88    gJvm = NULL;
89}
90
91////////////////////////////////////////////////////
92// JNI functions to export:                       //
93////////////////////////////////////////////////////
94
95static jclass gLwsServiceCls;
96static jobject gLwsServiceObj;
97static jmethodID sendMessageId;
98
99static const int MSG_DUMB_INCREMENT_PROTOCOL_COUNTER = 1;
100static const int MSG_LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 2;
101static const int MSG_LWS_CALLBACK_CLIENT_ESTABLISHED = 3;
102
103#define BUFFER_SIZE 4096
104
105static struct lws_context *context = NULL;
106static struct lws_context_creation_info info;
107static struct lws *wsi = NULL;
108
109// prevents sending messages after jni_exitLws had been called
110static int isExit = 0;
111
112enum websocket_protocols {
113  PROTOCOL_DUMB_INCREMENT = 0,
114  PROTOCOL_COUNT
115};
116
117struct per_session_data {
118  ;// no data
119};
120
121static int callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len );
122
123static 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
133static 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
142static int port = 0;
143static int use_ssl = 0;
144static int use_ssl_client = 0;
145static char address[8192];
146
147static char ca_cert[8192];
148static char client_cert[8192];
149static char client_cert_key[8192];
150
151static int deny_deflate = 0;
152static int deny_mux = 0;
153
154// Logging function for libwebsockets
155static void emit_log(int level, const char *msg)
156{
157    printf("%s", msg);
158}
159
160
161JNIEXPORT 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)
200static inline void sendMessage(int id, jobject obj)
201{
202  if(!isExit) gEnv->CallVoidMethod(gLwsServiceObj, sendMessageId, id, obj);
203}
204
205JNIEXPORT 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
216static 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
261JNIEXPORT void JNICALL jni_serviceLws(JNIEnv *env, jobject obj)
262{
263  if(context){
264    lws_service( context, 0 );
265  }
266}
267
268JNIEXPORT 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
282JNIEXPORT 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