Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_textroom.c @ 5f0f0227

History | View | Annotate | Download (71.9 KB)

1
/*! \file   janus_textroom.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU General Public License v3
4
 * \brief  Janus TextRoom plugin
5
 * \details This is a plugin implementing a DataChannel only text room.
6
 * As such, it does NOT support or negotiate audio or video, but only
7
 * data channels, in order to provide text broadcasting features. The
8
 * plugin allows users to join multiple text-only rooms via a single
9
 * PeerConnection. Users can send messages either to a room in general
10
 * (broadcasting), or to individual users (whispers). This plugin can be
11
 * used within the context of any application that needs real-time text
12
 * broadcasting (e.g., chatrooms, but not only).
13
 *
14
 * The only message that is typically sent to the plugin through the Janus API is
15
 * a "setup" message, by which the user initializes the PeerConnection
16
 * itself. Apart from that, all other messages can be exchanged directly
17
 * via Data Channels. For room management purposes, though, requests like
18
 * "create", "destroy", "list" and "exists" are available through the
19
 * Janus API as well: notice that in this case you'll have to use "request"
20
 * and not "textroom" as the name of the request.
21
 *
22
 * Each room can also be configured with an HTTP backend to contact for
23
 * incoming messages. If configured, messages addressed to that room will
24
 * also be forwarded, by means of an HTTP POST, to the specified address.
25
 * Notice that this will only work if libcurl was available when
26
 * configuring and installing Janus.
27
 *
28
 * \note This plugin is only meant to showcase what you can do with
29
 * data channels involving multiple participants at the same time. While
30
 * functional, it's not inherently better or faster than doing the same
31
 * thing using the Janus API messaging itself (e.g., as part of the
32
 * plugin API messaging) or using existing instant messaging protocols
33
 * (e.g., Jabber). In fact, while data channels are being used, you're
34
 * still going through a server, so it's not really peer-to-peer. That
35
 * said, the plugin can be useful if you don't plan to use any other
36
 * infrastructure than Janus, and yet you also want to have text-based
37
 * communication (e.g., to add a chatroom to an audio or video conference).
38
 *
39
 * Notice that, in general, all users can create rooms. If you want to
40
 * limit this functionality, you can configure an admin \c admin_key in
41
 * the plugin settings. When configured, only "create" requests that
42
 * include the correct \c admin_key value in an "admin_key" property
43
 * will succeed, and will be rejected otherwise.
44
 *
45
 * \section textroomapi Text Room API
46
 * TBD.
47
 *
48
 * \ingroup plugins
49
 * \ref plugins
50
 */
51

    
52
#include "plugins/plugin.h"
53

    
54
#include <jansson.h>
55

    
56
#ifdef HAVE_LIBCURL
57
#include <curl/curl.h>
58
#endif
59

    
60
#include "debug.h"
61
#include "apierror.h"
62
#include "config.h"
63
#include "mutex.h"
64
#include "utils.h"
65

    
66

    
67
/* Plugin information */
68
#define JANUS_TEXTROOM_VERSION                        2
69
#define JANUS_TEXTROOM_VERSION_STRING        "0.0.2"
70
#define JANUS_TEXTROOM_DESCRIPTION                "This is a plugin implementing a text-only room for Janus, using DataChannels."
71
#define JANUS_TEXTROOM_NAME                                "JANUS TextRoom plugin"
72
#define JANUS_TEXTROOM_AUTHOR                        "Meetecho s.r.l."
73
#define JANUS_TEXTROOM_PACKAGE                        "janus.plugin.textroom"
74

    
75
/* Plugin methods */
76
janus_plugin *create(void);
77
int janus_textroom_init(janus_callbacks *callback, const char *config_path);
78
void janus_textroom_destroy(void);
79
int janus_textroom_get_api_compatibility(void);
80
int janus_textroom_get_version(void);
81
const char *janus_textroom_get_version_string(void);
82
const char *janus_textroom_get_description(void);
83
const char *janus_textroom_get_name(void);
84
const char *janus_textroom_get_author(void);
85
const char *janus_textroom_get_package(void);
86
void janus_textroom_create_session(janus_plugin_session *handle, int *error);
87
struct janus_plugin_result *janus_textroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
88
void janus_textroom_setup_media(janus_plugin_session *handle);
89
void janus_textroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
90
void janus_textroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
91
void janus_textroom_incoming_data(janus_plugin_session *handle, char *buf, int len);
92
void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video);
93
void janus_textroom_hangup_media(janus_plugin_session *handle);
94
void janus_textroom_destroy_session(janus_plugin_session *handle, int *error);
95
json_t *janus_textroom_query_session(janus_plugin_session *handle);
96

    
97
/* Plugin setup */
98
static janus_plugin janus_textroom_plugin =
99
        JANUS_PLUGIN_INIT (
100
                .init = janus_textroom_init,
101
                .destroy = janus_textroom_destroy,
102

    
103
                .get_api_compatibility = janus_textroom_get_api_compatibility,
104
                .get_version = janus_textroom_get_version,
105
                .get_version_string = janus_textroom_get_version_string,
106
                .get_description = janus_textroom_get_description,
107
                .get_name = janus_textroom_get_name,
108
                .get_author = janus_textroom_get_author,
109
                .get_package = janus_textroom_get_package,
110

    
111
                .create_session = janus_textroom_create_session,
112
                .handle_message = janus_textroom_handle_message,
113
                .setup_media = janus_textroom_setup_media,
114
                .incoming_rtp = janus_textroom_incoming_rtp,
115
                .incoming_rtcp = janus_textroom_incoming_rtcp,
116
                .incoming_data = janus_textroom_incoming_data,
117
                .slow_link = janus_textroom_slow_link,
118
                .hangup_media = janus_textroom_hangup_media,
119
                .destroy_session = janus_textroom_destroy_session,
120
                .query_session = janus_textroom_query_session,
121
        );
122

    
123
/* Plugin creator */
124
janus_plugin *create(void) {
125
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_TEXTROOM_NAME);
126
        return &janus_textroom_plugin;
127
}
128

    
129

    
130
/* Parameter validation */
131
static struct janus_json_parameter request_parameters[] = {
132
        {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
133
};
134
static struct janus_json_parameter transaction_parameters[] = {
135
        {"textroom", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
136
        {"transaction", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
137
};
138
static struct janus_json_parameter room_parameters[] = {
139
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
140
};
141
static struct janus_json_parameter adminkey_parameters[] = {
142
        {"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
143
};
144
static struct janus_json_parameter create_parameters[] = {
145
        {"description", JSON_STRING, 0},
146
        {"secret", JSON_STRING, 0},
147
        {"pin", JSON_STRING, 0},
148
        {"post", JSON_STRING, 0},
149
        {"is_private", JANUS_JSON_BOOL, 0},
150
        {"allowed", JSON_ARRAY, 0}
151
};
152
static struct janus_json_parameter allowed_parameters[] = {
153
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
154
        {"secret", JSON_STRING, 0},
155
        {"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
156
        {"allowed", JSON_ARRAY, 0}
157
};
158
static struct janus_json_parameter kick_parameters[] = {
159
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
160
        {"secret", JSON_STRING, 0},
161
        {"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
162
};
163
static struct janus_json_parameter join_parameters[] = {
164
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
165
        {"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
166
        {"display", JSON_STRING, 0}
167
};
168
static struct janus_json_parameter message_parameters[] = {
169
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
170
        {"text", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
171
        {"to", JSON_STRING, 0},
172
        {"tos", JSON_ARRAY, 0},
173
        {"ack", JANUS_JSON_BOOL, 0}
174
};
175

    
176
/* Static configuration instance */
177
static janus_config *config = NULL;
178
static const char *config_folder = NULL;
179
static janus_mutex config_mutex;
180

    
181
/* Useful stuff */
182
static volatile gint initialized = 0, stopping = 0;
183
static gboolean notify_events = TRUE;
184
static janus_callbacks *gateway = NULL;
185
static GThread *handler_thread;
186
static GThread *watchdog;
187
static void *janus_textroom_handler(void *data);
188

    
189
/* JSON serialization options */
190
static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
191

    
192

    
193
typedef struct janus_textroom_message {
194
        janus_plugin_session *handle;
195
        char *transaction;
196
        json_t *message;
197
        json_t *jsep;
198
} janus_textroom_message;
199
static GAsyncQueue *messages = NULL;
200
static janus_textroom_message exit_message;
201

    
202
static void janus_textroom_message_free(janus_textroom_message *msg) {
203
        if(!msg || msg == &exit_message)
204
                return;
205

    
206
        msg->handle = NULL;
207

    
208
        g_free(msg->transaction);
209
        msg->transaction = NULL;
210
        if(msg->message)
211
                json_decref(msg->message);
212
        msg->message = NULL;
213
        if(msg->jsep)
214
                json_decref(msg->jsep);
215
        msg->jsep = NULL;
216

    
217
        g_free(msg);
218
}
219

    
220
typedef struct janus_textroom_room {
221
        guint64 room_id;                        /* Unique room ID */
222
        gchar *room_name;                        /* Room description */
223
        gchar *room_secret;                        /* Secret needed to manipulate (e.g., destroy) this room */
224
        gchar *room_pin;                        /* Password needed to join this room, if any */
225
        gboolean is_private;                /* Whether this room is 'private' (as in hidden) or not */
226
        gchar *http_backend;                /* Server to contact via HTTP POST for incoming messages, if any */
227
        GHashTable *participants;        /* Map of participants */
228
        gboolean check_tokens;                /* Whether to check tokens when participants join (see below) */
229
        GHashTable *allowed;                /* Map of participants (as tokens) allowed to join */
230
        gint64 destroyed;                        /* When this room has been destroyed */
231
        janus_mutex mutex;                        /* Mutex to lock this room instance */
232
} janus_textroom_room;
233
static GHashTable *rooms;
234
static janus_mutex rooms_mutex;
235
static GList *old_rooms;
236
static char *admin_key = NULL;
237

    
238
typedef struct janus_textroom_session {
239
        janus_plugin_session *handle;
240
        GHashTable *rooms;                        /* Map of rooms this user is in, and related participant instance */
241
        janus_mutex mutex;                        /* Mutex to lock this session */
242
        volatile gint setup;
243
        volatile gint hangingup;
244
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
245
} janus_textroom_session;
246
static GHashTable *sessions;
247
static GList *old_sessions;
248
static janus_mutex sessions_mutex;
249

    
250
typedef struct janus_textroom_participant {
251
        janus_textroom_session *session;
252
        janus_textroom_room *room;        /* Room this participant is in */
253
        gchar *username;                        /* Unique username in the room */
254
        gchar *display;                                /* Display name in the room, if any */
255
        janus_mutex mutex;                        /* Mutex to lock this session */
256
        gint64 destroyed;                        /* When this participant was destroyed */
257
} janus_textroom_participant;
258

    
259

    
260
/* SDP template: we only offer data channels */
261
#define sdp_template \
262
                "v=0\r\n" \
263
                "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"        /* We need current time here */ \
264
                "s=Janus TextRoom plugin\r\n" \
265
                "t=0 0\r\n" \
266
                "m=application 1 DTLS/SCTP 5000\r\n" \
267
                "c=IN IP4 1.1.1.1\r\n" \
268
                "a=sctpmap:5000 webrtc-datachannel 16\r\n"
269

    
270

    
271
/* Error codes */
272
#define JANUS_TEXTROOM_ERROR_NO_MESSAGE                        411
273
#define JANUS_TEXTROOM_ERROR_INVALID_JSON                412
274
#define JANUS_TEXTROOM_ERROR_MISSING_ELEMENT        413
275
#define JANUS_TEXTROOM_ERROR_INVALID_ELEMENT        414
276
#define JANUS_TEXTROOM_ERROR_INVALID_REQUEST        415
277
#define JANUS_TEXTROOM_ERROR_ALREADY_SETUP                416
278
#define JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM                417
279
#define JANUS_TEXTROOM_ERROR_ROOM_EXISTS                418
280
#define JANUS_TEXTROOM_ERROR_UNAUTHORIZED                419
281
#define JANUS_TEXTROOM_ERROR_USERNAME_EXISTS        420
282
#define JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM        421
283
#define JANUS_TEXTROOM_ERROR_NOT_IN_ROOM                422
284
#define JANUS_TEXTROOM_ERROR_NO_SUCH_USER                423
285
#define JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR                499
286

    
287
/* TextRoom watchdog/garbage collector (sort of) */
288
static void *janus_textroom_watchdog(void *data) {
289
        JANUS_LOG(LOG_INFO, "TextRoom watchdog started\n");
290
        gint64 now = 0;
291
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
292
                janus_mutex_lock(&sessions_mutex);
293
                /* Iterate on all the sessions */
294
                now = janus_get_monotonic_time();
295
                if(old_sessions != NULL) {
296
                        GList *sl = old_sessions;
297
                        JANUS_LOG(LOG_HUGE, "Checking %d old TextRoom sessions...\n", g_list_length(old_sessions));
298
                        while(sl) {
299
                                janus_textroom_session *session = (janus_textroom_session *)sl->data;
300
                                if(!session) {
301
                                        sl = sl->next;
302
                                        continue;
303
                                }
304
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
305
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
306
                                        JANUS_LOG(LOG_VERB, "Freeing old TextRoom session\n");
307
                                        GList *rm = sl->next;
308
                                        old_sessions = g_list_delete_link(old_sessions, sl);
309
                                        sl = rm;
310
                                        session->handle = NULL;
311
                                        /* TODO Free session stuff */
312
                                        g_free(session);
313
                                        session = NULL;
314
                                        continue;
315
                                }
316
                                sl = sl->next;
317
                        }
318
                }
319
                janus_mutex_unlock(&sessions_mutex);
320
                janus_mutex_lock(&rooms_mutex);
321
                if(old_rooms != NULL) {
322
                        GList *rl = old_rooms;
323
                        now = janus_get_monotonic_time();
324
                        while(rl) {
325
                                janus_textroom_room *textroom = (janus_textroom_room*)rl->data;
326
                                if(!g_atomic_int_get(&initialized) || g_atomic_int_get(&stopping)){
327
                                        break;
328
                                }
329
                                if(!textroom) {
330
                                        rl = rl->next;
331
                                        continue;
332
                                }
333
                                if(now - textroom->destroyed >= 5*G_USEC_PER_SEC) {
334
                                        /* Free resources */
335
                                        JANUS_LOG(LOG_VERB, "Freeing old TextRoom room %"SCNu64"\n", textroom->room_id);
336
                                        g_free(textroom->room_name);
337
                                        g_free(textroom->room_secret);
338
                                        g_free(textroom->room_pin);
339
                                        g_hash_table_destroy(textroom->participants);
340
                                        g_hash_table_destroy(textroom->allowed);
341
                                        g_free(textroom);
342
                                        /* Move on */
343
                                        GList *rm = rl->next;
344
                                        old_rooms = g_list_delete_link(old_rooms, rl);
345
                                        rl = rm;
346
                                        continue;
347
                                }
348
                                rl = rl->next;
349
                        }
350
                }
351
                janus_mutex_unlock(&rooms_mutex);
352
                g_usleep(500000);
353
        }
354
        JANUS_LOG(LOG_INFO, "TextRoom watchdog stopped\n");
355
        return NULL;
356
}
357

    
358
#ifdef HAVE_LIBCURL
359
static size_t janus_textroom_write_data(void *buffer, size_t size, size_t nmemb, void *userp) {
360
        return size*nmemb;
361
}
362
#endif
363

    
364
/* We use this method to handle incoming requests. Since most of the requests 
365
 * will arrive from data channels, but some may also arrive from the regular
366
 * plugin messaging (e.g., room management), we have the ability to pass
367
 * parsed JSON objects instead of strings, which explains why we specify a
368
 * janus_plugin_result pointer as a return value; messages handles via
369
 * datachannels would simply return NULL. Besides, some requests are actually
370
 * originated internally, and don't need any response to be sent to anyone,
371
 * which is what the additional boolean "internal" value is for */
372
janus_plugin_result *janus_textroom_handle_incoming_request(janus_plugin_session *handle,
373
        char *text, json_t *json, gboolean internal);
374

    
375

    
376
/* Plugin implementation */
377
int janus_textroom_init(janus_callbacks *callback, const char *config_path) {
378
        if(g_atomic_int_get(&stopping)) {
379
                /* Still stopping from before */
380
                return -1;
381
        }
382
        if(callback == NULL || config_path == NULL) {
383
                /* Invalid arguments */
384
                return -1;
385
        }
386

    
387
        /* Read configuration */
388
        char filename[255];
389
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_TEXTROOM_PACKAGE);
390
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
391
        config = janus_config_parse(filename);
392
        config_folder = config_path;
393
        if(config != NULL)
394
                janus_config_print(config);
395
        janus_mutex_init(&config_mutex);
396

    
397
        rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
398
        janus_mutex_init(&rooms_mutex);
399
        sessions = g_hash_table_new(NULL, NULL);
400
        messages = g_async_queue_new_full((GDestroyNotify) janus_textroom_message_free);
401
        janus_mutex_init(&sessions_mutex);
402
        /* This is the callback we'll need to invoke to contact the gateway */
403
        gateway = callback;
404

    
405
        /* Parse configuration to populate the rooms list */
406
        if(config != NULL) {
407
                janus_config_item *item = janus_config_get_item_drilldown(config, "general", "json");
408
                if(item && item->value) {
409
                        /* Check how we need to format/serialize the JSON output */
410
                        if(!strcasecmp(item->value, "indented")) {
411
                                /* Default: indented, we use three spaces for that */
412
                                json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
413
                        } else if(!strcasecmp(item->value, "plain")) {
414
                                /* Not indented and no new lines, but still readable */
415
                                json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER;
416
                        } else if(!strcasecmp(item->value, "compact")) {
417
                                /* Compact, so no spaces between separators */
418
                                json_format = JSON_COMPACT | JSON_PRESERVE_ORDER;
419
                        } else {
420
                                JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value);
421
                                json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
422
                        }
423
                }
424
                /* Any admin key to limit who can "create"? */
425
                janus_config_item *key = janus_config_get_item_drilldown(config, "general", "admin_key");
426
                if(key != NULL && key->value != NULL)
427
                        admin_key = g_strdup(key->value);
428
                janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events");
429
                if(events != NULL && events->value != NULL)
430
                        notify_events = janus_is_true(events->value);
431
                if(!notify_events && callback->events_is_enabled()) {
432
                        JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_TEXTROOM_NAME);
433
                }
434
                /* Iterate on all rooms */
435
                GList *cl = janus_config_get_categories(config);
436
                while(cl != NULL) {
437
                        janus_config_category *cat = (janus_config_category *)cl->data;
438
                        if(cat->name == NULL || !strcasecmp(cat->name, "general")) {
439
                                cl = cl->next;
440
                                continue;
441
                        }
442
                        JANUS_LOG(LOG_VERB, "Adding text room '%s'\n", cat->name);
443
                        janus_config_item *desc = janus_config_get_item(cat, "description");
444
                        janus_config_item *priv = janus_config_get_item(cat, "is_private");
445
                        janus_config_item *secret = janus_config_get_item(cat, "secret");
446
                        janus_config_item *pin = janus_config_get_item(cat, "pin");
447
                        janus_config_item *post = janus_config_get_item(cat, "post");
448
                        /* Create the text room */
449
                        janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
450
                        textroom->room_id = g_ascii_strtoull(cat->name, NULL, 0);
451
                        char *description = NULL;
452
                        if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
453
                                description = g_strdup(desc->value);
454
                        else
455
                                description = g_strdup(cat->name);
456
                        textroom->room_name = description;
457
                        textroom->is_private = priv && priv->value && janus_is_true(priv->value);
458
                        if(secret != NULL && secret->value != NULL) {
459
                                textroom->room_secret = g_strdup(secret->value);
460
                        }
461
                        if(pin != NULL && pin->value != NULL) {
462
                                textroom->room_pin = g_strdup(pin->value);
463
                        }
464
                        if(post != NULL && post->value != NULL) {
465
#ifdef HAVE_LIBCURL
466
                                /* FIXME Should we check if this is a valid HTTP address? */
467
                                textroom->http_backend = g_strdup(post->value);
468
#else
469
                                JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
470
#endif
471
                        }
472
                        textroom->participants = g_hash_table_new(g_str_hash, g_str_equal);
473
                        textroom->check_tokens = FALSE;        /* Static rooms can't have an "allowed" list yet, no hooks to the configuration file */
474
                        textroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
475
                        textroom->destroyed = 0;
476
                        janus_mutex_init(&textroom->mutex);
477
                        JANUS_LOG(LOG_VERB, "Created textroom: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n",
478
                                textroom->room_id, textroom->room_name,
479
                                textroom->is_private ? "private" : "public",
480
                                textroom->room_secret ? textroom->room_secret : "no secret",
481
                                textroom->room_pin ? textroom->room_pin : "no pin");
482
                        g_hash_table_insert(rooms, janus_uint64_dup(textroom->room_id), textroom);
483
                        cl = cl->next;
484
                }
485
                /* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
486
        }
487

    
488
        /* Show available rooms */
489
        janus_mutex_lock(&rooms_mutex);
490
        GHashTableIter iter;
491
        gpointer value;
492
        g_hash_table_iter_init(&iter, rooms);
493
        while (g_hash_table_iter_next(&iter, NULL, &value)) {
494
                janus_textroom_room *tr = value;
495
                JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s]\n", tr->room_id, tr->room_name);
496
        }
497
        janus_mutex_unlock(&rooms_mutex);
498

    
499
#ifdef HAVE_LIBCURL
500
        curl_global_init(CURL_GLOBAL_ALL);
501
#endif
502

    
503
        g_atomic_int_set(&initialized, 1);
504

    
505
        GError *error = NULL;
506
        /* Start the sessions watchdog */
507
        watchdog = g_thread_try_new("textroom watchdog", &janus_textroom_watchdog, NULL, &error);
508
        if(error != NULL) {
509
                g_atomic_int_set(&initialized, 0);
510
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the TextRoom watchdog thread...\n", error->code, error->message ? error->message : "??");
511
                return -1;
512
        }
513
        /* Launch the thread that will handle incoming messages */
514
        handler_thread = g_thread_try_new("textroom handler", janus_textroom_handler, NULL, &error);
515
        if(error != NULL) {
516
                g_atomic_int_set(&initialized, 0);
517
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the TextRoom handler thread...\n", error->code, error->message ? error->message : "??");
518
                return -1;
519
        }
520
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_TEXTROOM_NAME);
521
        return 0;
522
}
523

    
524
void janus_textroom_destroy(void) {
525
        if(!g_atomic_int_get(&initialized))
526
                return;
527
        g_atomic_int_set(&stopping, 1);
528

    
529
        g_async_queue_push(messages, &exit_message);
530
        if(handler_thread != NULL) {
531
                g_thread_join(handler_thread);
532
                handler_thread = NULL;
533
        }
534
        if(watchdog != NULL) {
535
                g_thread_join(watchdog);
536
                watchdog = NULL;
537
        }
538

    
539
        /* FIXME We should destroy the sessions cleanly */
540
        janus_mutex_lock(&sessions_mutex);
541
        g_hash_table_destroy(sessions);
542
        janus_mutex_unlock(&sessions_mutex);
543
        janus_mutex_lock(&rooms_mutex);
544
        g_hash_table_destroy(rooms);
545
        janus_mutex_unlock(&rooms_mutex);
546
        g_async_queue_unref(messages);
547
        messages = NULL;
548
        sessions = NULL;
549

    
550
#ifdef HAVE_LIBCURL
551
        curl_global_cleanup();
552
#endif
553

    
554
        janus_config_destroy(config);
555
        g_free(admin_key);
556

    
557
        g_atomic_int_set(&initialized, 0);
558
        g_atomic_int_set(&stopping, 0);
559
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_TEXTROOM_NAME);
560
}
561

    
562
int janus_textroom_get_api_compatibility(void) {
563
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
564
        return JANUS_PLUGIN_API_VERSION;
565
}
566

    
567
int janus_textroom_get_version(void) {
568
        return JANUS_TEXTROOM_VERSION;
569
}
570

    
571
const char *janus_textroom_get_version_string(void) {
572
        return JANUS_TEXTROOM_VERSION_STRING;
573
}
574

    
575
const char *janus_textroom_get_description(void) {
576
        return JANUS_TEXTROOM_DESCRIPTION;
577
}
578

    
579
const char *janus_textroom_get_name(void) {
580
        return JANUS_TEXTROOM_NAME;
581
}
582

    
583
const char *janus_textroom_get_author(void) {
584
        return JANUS_TEXTROOM_AUTHOR;
585
}
586

    
587
const char *janus_textroom_get_package(void) {
588
        return JANUS_TEXTROOM_PACKAGE;
589
}
590

    
591
void janus_textroom_create_session(janus_plugin_session *handle, int *error) {
592
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
593
                *error = -1;
594
                return;
595
        }
596
        janus_textroom_session *session = (janus_textroom_session *)g_malloc0(sizeof(janus_textroom_session));
597
        session->handle = handle;
598
        session->rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
599
        session->destroyed = 0;
600
        janus_mutex_init(&session->mutex);
601
        g_atomic_int_set(&session->setup, 0);
602
        g_atomic_int_set(&session->hangingup, 0);
603
        handle->plugin_handle = session;
604
        janus_mutex_lock(&sessions_mutex);
605
        g_hash_table_insert(sessions, handle, session);
606
        janus_mutex_unlock(&sessions_mutex);
607

    
608
        return;
609
}
610

    
611
void janus_textroom_destroy_session(janus_plugin_session *handle, int *error) {
612
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
613
                *error = -1;
614
                return;
615
        }
616
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
617
        if(!session) {
618
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
619
                *error = -2;
620
                return;
621
        }
622
        JANUS_LOG(LOG_VERB, "Removing Echo Test session...\n");
623
        janus_mutex_lock(&sessions_mutex);
624
        if(!session->destroyed) {
625
                g_hash_table_remove(sessions, handle);
626
                janus_textroom_hangup_media(handle);
627
                session->destroyed = janus_get_monotonic_time();
628
                /* Cleaning up and removing the session is done in a lazy way */
629
                old_sessions = g_list_append(old_sessions, session);
630
        }
631
        janus_mutex_unlock(&sessions_mutex);
632
        return;
633
}
634

    
635
json_t *janus_textroom_query_session(janus_plugin_session *handle) {
636
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
637
                return NULL;
638
        }
639
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
640
        if(!session) {
641
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
642
                return NULL;
643
        }
644
        /* TODO Return meaningful info: participant details, rooms they're in, etc. */
645
        json_t *info = json_object();
646
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
647
        return info;
648
}
649

    
650
struct janus_plugin_result *janus_textroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
651
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
652
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
653

    
654
        /* Pre-parse the message */
655
        int error_code = 0;
656
        char error_cause[512];
657
        json_t *root = message;
658
        json_t *response = NULL;
659

    
660
        if(message == NULL) {
661
                JANUS_LOG(LOG_ERR, "No message??\n");
662
                error_code = JANUS_TEXTROOM_ERROR_NO_MESSAGE;
663
                g_snprintf(error_cause, 512, "%s", "No message??");
664
                goto plugin_response;
665
        }
666

    
667
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
668
        if(!session) {
669
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
670
                error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
671
                g_snprintf(error_cause, 512, "%s", "session associated with this handle...");
672
                goto plugin_response;
673
        }
674
        if(session->destroyed) {
675
                JANUS_LOG(LOG_ERR, "Session has already been destroyed...\n");
676
                error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
677
                g_snprintf(error_cause, 512, "%s", "Session has already been destroyed...");
678
                goto plugin_response;
679
        }
680
        if(!json_is_object(root)) {
681
                JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
682
                error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
683
                g_snprintf(error_cause, 512, "JSON error: not an object");
684
                goto plugin_response;
685
        }
686
        /* Get the request first */
687
        JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
688
                error_code, error_cause, TRUE,
689
                JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
690
        if(error_code != 0)
691
                goto plugin_response;
692
        json_t *request = json_object_get(root, "request");
693
        /* Some requests (e.g., 'create' and 'destroy') can be handled synchronously */
694
        const char *request_text = json_string_value(request);
695
        if(!strcasecmp(request_text, "list")
696
                        || !strcasecmp(request_text, "exists")
697
                        || !strcasecmp(request_text, "create")
698
                        || !strcasecmp(request_text, "destroy")) {
699
                /* These requests typically only belong to the datachannel
700
                 * messaging, but for admin purposes we might use them on
701
                 * the Janus API as well: add the properties the datachannel
702
                 * processor would expect and handle everything there */
703
                json_object_set_new(root, "textroom", json_string(request_text));
704
                json_object_set_new(root, "transaction", json_string(transaction));
705
                janus_plugin_result *result = janus_textroom_handle_incoming_request(session->handle, NULL, root, FALSE);
706
                if(result == NULL) {
707
                        JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
708
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
709
                        g_snprintf(error_cause, 512, "JSON error: not an object");
710
                        goto plugin_response;
711
                }
712
                if(root != NULL)
713
                        json_decref(root);
714
                if(jsep != NULL)
715
                        json_decref(jsep);
716
                g_free(transaction);
717
                return result;
718
        } else if(!strcasecmp(request_text, "setup") || !strcasecmp(request_text, "ack")) {
719
                /* These messages are handled asynchronously */
720
                janus_textroom_message *msg = g_malloc0(sizeof(janus_textroom_message));
721
                msg->handle = handle;
722
                msg->transaction = transaction;
723
                msg->message = root;
724
                msg->jsep = jsep;
725

    
726
                g_async_queue_push(messages, msg);
727

    
728
                return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
729
        } else {
730
                JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
731
                error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
732
                g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
733
        }
734

    
735
plugin_response:
736
                {
737
                        if(error_code == 0 && !response) {
738
                                error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
739
                                g_snprintf(error_cause, 512, "Invalid response");
740
                        }
741
                        if(error_code != 0) {
742
                                /* Prepare JSON error event */
743
                                json_t *event = json_object();
744
                                json_object_set_new(event, "textroom", json_string("event"));
745
                                json_object_set_new(event, "error_code", json_integer(error_code));
746
                                json_object_set_new(event, "error", json_string(error_cause));
747
                                response = event;
748
                        }
749
                        if(root != NULL)
750
                                json_decref(root);
751
                        if(jsep != NULL)
752
                                json_decref(jsep);
753
                        g_free(transaction);
754

    
755
                        return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
756
                }
757

    
758
}
759

    
760
void janus_textroom_setup_media(janus_plugin_session *handle) {
761
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
762
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
763
                return;
764
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
765
        if(!session) {
766
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
767
                return;
768
        }
769
        if(session->destroyed)
770
                return;
771
        g_atomic_int_set(&session->hangingup, 0);
772
}
773

    
774
void janus_textroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
775
        /* We don't do audio/video */
776
}
777

    
778
void janus_textroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
779
        /* We don't do audio/video */
780
}
781

    
782
void janus_textroom_incoming_data(janus_plugin_session *handle, char *buf, int len) {
783
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
784
                return;
785
        /* Incoming request from this user: what should we do? */
786
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
787
        if(!session) {
788
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
789
                return;
790
        }
791
        if(session->destroyed)
792
                return;
793
        if(buf == NULL || len <= 0)
794
                return;
795
        char *text = g_malloc0(len+1);
796
        memcpy(text, buf, len);
797
        *(text+len) = '\0';
798
        JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes): %s\n", strlen(text), text);
799
        janus_textroom_handle_incoming_request(handle, text, NULL, FALSE);
800
}
801

    
802
/* Helper method to handle incoming messages from the data channel */
803
janus_plugin_result *janus_textroom_handle_incoming_request(janus_plugin_session *handle, char *text, json_t *json, gboolean internal) {
804
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
805
        /* Parse JSON, if needed */
806
        json_error_t error;
807
        json_t *root = text ? json_loads(text, 0, &error) : json;
808
        g_free(text);
809
        if(!root) {
810
                JANUS_LOG(LOG_ERR, "Error parsing data channel message (JSON error: on line %d: %s)\n", error.line, error.text);
811
                return NULL;
812
        }
813
        /* Handle request */
814
        int error_code = 0;
815
        char error_cause[512];
816
        JANUS_VALIDATE_JSON_OBJECT(root, transaction_parameters,
817
                error_code, error_cause, TRUE,
818
                JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
819
        const char *transaction_text = NULL;
820
        json_t *reply = NULL;
821
        if(error_code != 0)
822
                goto msg_response;
823
        json_t *request = json_object_get(root, "textroom");
824
        json_t *transaction = json_object_get(root, "transaction");
825
        const char *request_text = json_string_value(request);
826
        transaction_text = json_string_value(transaction);
827
        if(!strcasecmp(request_text, "message")) {
828
                JANUS_VALIDATE_JSON_OBJECT(root, message_parameters,
829
                        error_code, error_cause, TRUE,
830
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
831
                if(error_code != 0)
832
                        goto msg_response;
833
                json_t *room = json_object_get(root, "room");
834
                guint64 room_id = json_integer_value(room);
835
                janus_mutex_lock(&rooms_mutex);
836
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
837
                if(textroom == NULL) {
838
                        janus_mutex_unlock(&rooms_mutex);
839
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
840
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
841
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
842
                        goto msg_response;
843
                }
844
                janus_mutex_lock(&textroom->mutex);
845
                janus_mutex_unlock(&rooms_mutex);
846
                janus_textroom_participant *participant = g_hash_table_lookup(session->rooms, &room_id);
847
                if(participant == NULL) {
848
                        janus_mutex_unlock(&textroom->mutex);
849
                        JANUS_LOG(LOG_ERR, "Not in room %"SCNu64"\n", room_id);
850
                        error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM;
851
                        g_snprintf(error_cause, 512, "Not in room %"SCNu64, room_id);
852
                        goto msg_response;
853
                }
854
                json_t *username = json_object_get(root, "to");
855
                json_t *usernames = json_object_get(root, "tos");
856
                if(username && usernames) {
857
                        janus_mutex_unlock(&textroom->mutex);
858
                        JANUS_LOG(LOG_ERR, "Both to and tos array provided\n");
859
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
860
                        g_snprintf(error_cause, 512, "Both to and tos array provided");
861
                        goto msg_response;
862
                }
863
                json_t *text = json_object_get(root, "text");
864
                const char *message = json_string_value(text);
865
                /* Prepare outgoing message */
866
                json_t *msg = json_object();
867
                json_object_set_new(msg, "textroom", json_string("message"));
868
                json_object_set_new(msg, "room", json_integer(room_id));
869
                json_object_set_new(msg, "from", json_string(participant->username));
870
                time_t timer;
871
                time(&timer);
872
                struct tm *tm_info = localtime(&timer);
873
                char msgTime[64];
874
                strftime(msgTime, sizeof(msgTime), "%FT%T%z", tm_info);
875
                json_object_set_new(msg, "date", json_string(msgTime));
876
                json_object_set_new(msg, "text", json_string(message));
877
                if(username || usernames)
878
                        json_object_set_new(msg, "whisper", json_true());
879
                char *msg_text = json_dumps(msg, json_format);
880
                json_decref(msg);
881
                /* Start preparing the response too */
882
                reply = json_object();
883
                json_object_set_new(reply, "textroom", json_string("success"));
884
                /* Who should we send this message to? */
885
                if(username) {
886
                        /* A single user */
887
                        json_t *sent = json_object();
888
                        const char *to = json_string_value(username);
889
                        JANUS_LOG(LOG_VERB, "To %s in %"SCNu64": %s\n", to, room_id, message);
890
                        janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to);
891
                        if(top) {
892
                                gateway->relay_data(top->session->handle, msg_text, strlen(msg_text));
893
                                json_object_set_new(sent, to, json_true());
894
                        } else {
895
                                JANUS_LOG(LOG_WARN, "User %s is not in room %"SCNu64", failed to send message\n", to, room_id);
896
                                json_object_set_new(sent, to, json_false());
897
                        }
898
                        json_object_set_new(reply, "sent", sent);
899
                } else if(usernames) {
900
                        /* A limited number of users */
901
                        json_t *sent = json_object();
902
                        size_t i = 0;
903
                        for(i=0; i<json_array_size(usernames); i++) {
904
                                json_t *u = json_array_get(usernames, i);
905
                                const char *to = json_string_value(u);
906
                                JANUS_LOG(LOG_VERB, "To %s in %"SCNu64": %s\n", to, room_id, message);
907
                                janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to);
908
                                if(top) {
909
                                        gateway->relay_data(top->session->handle, msg_text, strlen(msg_text));
910
                                        json_object_set_new(sent, to, json_true());
911
                                } else {
912
                                        JANUS_LOG(LOG_WARN, "User %s is not in room %"SCNu64", failed to send message\n", to, room_id);
913
                                        json_object_set_new(sent, to, json_false());
914
                                }
915
                        }
916
                        json_object_set_new(reply, "sent", sent);
917
                } else {
918
                        /* Everybody in the room */
919
                        JANUS_LOG(LOG_VERB, "To everybody in %"SCNu64": %s\n", room_id, message);
920
                        if(textroom->participants) {
921
                                GHashTableIter iter;
922
                                gpointer value;
923
                                g_hash_table_iter_init(&iter, textroom->participants);
924
                                while(g_hash_table_iter_next(&iter, NULL, &value)) {
925
                                        janus_textroom_participant *top = value;
926
                                        JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64": %s\n", top->username, room_id, message);
927
                                        gateway->relay_data(top->session->handle, msg_text, strlen(msg_text));
928
                                }
929
                        }
930
#ifdef HAVE_LIBCURL
931
                        /* Is there a backend waiting for this message too? */
932
                        if(textroom->http_backend) {
933
                                /* Prepare the libcurl context */
934
                                CURLcode res;
935
                                CURL *curl = curl_easy_init();
936
                                if(curl == NULL) {
937
                                        JANUS_LOG(LOG_ERR, "Error initializing CURL context\n");
938
                                } else {
939
                                        curl_easy_setopt(curl, CURLOPT_URL, textroom->http_backend);
940
                                        struct curl_slist *headers = NULL;
941
                                        headers = curl_slist_append(headers, "Accept: application/json");
942
                                        headers = curl_slist_append(headers, "Content-Type: application/json");
943
                                        headers = curl_slist_append(headers, "charsets: utf-8");
944
                                        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
945
                                        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msg_text);
946
                                        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, janus_textroom_write_data);
947
                                        /* Send the request */
948
                                        res = curl_easy_perform(curl);
949
                                        if(res != CURLE_OK) {
950
                                                JANUS_LOG(LOG_ERR, "Couldn't relay event to the backend: %s\n", curl_easy_strerror(res));
951
                                        } else {
952
                                                JANUS_LOG(LOG_DBG, "Event sent!\n");
953
                                        }
954
                                        curl_easy_cleanup(curl);
955
                                        curl_slist_free_all(headers);
956
                                }
957
                        }
958
#endif
959
                }
960
                free(msg_text);
961
                janus_mutex_unlock(&textroom->mutex);
962
                /* By default we send a confirmation back to the user that sent this message:
963
                 * if the user passed an ack=false, though, we don't do that */
964
                json_t *ack = json_object_get(root, "ack");
965
                if(!internal && (ack == NULL || json_is_true(ack))) {
966
                        /* Send response back */
967
                } else {
968
                        internal = TRUE;
969
                        json_decref(reply);
970
                        reply = NULL;
971
                }
972
        } else if(!strcasecmp(request_text, "join")) {
973
                JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
974
                        error_code, error_cause, TRUE,
975
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
976
                if(error_code != 0)
977
                        goto msg_response;
978
                json_t *room = json_object_get(root, "room");
979
                guint64 room_id = json_integer_value(room);
980
                janus_mutex_lock(&rooms_mutex);
981
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
982
                if(textroom == NULL) {
983
                        janus_mutex_unlock(&rooms_mutex);
984
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
985
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
986
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
987
                        goto msg_response;
988
                }
989
                janus_mutex_lock(&textroom->mutex);
990
                janus_mutex_unlock(&rooms_mutex);
991
                /* A PIN may be required for this action */
992
                JANUS_CHECK_SECRET(textroom->room_pin, root, "pin", error_code, error_cause,
993
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
994
                if(error_code != 0) {
995
                        janus_mutex_unlock(&textroom->mutex);
996
                        goto msg_response;
997
                }
998
                janus_mutex_lock(&session->mutex);
999
                if(g_hash_table_lookup(session->rooms, &room_id) != NULL) {
1000
                        janus_mutex_unlock(&session->mutex);
1001
                        janus_mutex_unlock(&textroom->mutex);
1002
                        JANUS_LOG(LOG_ERR, "Already in room %"SCNu64"\n", room_id);
1003
                        error_code = JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM;
1004
                        g_snprintf(error_cause, 512, "Already in room %"SCNu64, room_id);
1005
                        goto msg_response;
1006
                }
1007
                json_t *username = json_object_get(root, "username");
1008
                const char *username_text = json_string_value(username);
1009
                janus_textroom_participant *participant = g_hash_table_lookup(textroom->participants, username_text);
1010
                if(participant != NULL) {
1011
                        janus_mutex_unlock(&session->mutex);
1012
                        janus_mutex_unlock(&textroom->mutex);
1013
                        JANUS_LOG(LOG_ERR, "Username already taken\n");
1014
                        error_code = JANUS_TEXTROOM_ERROR_USERNAME_EXISTS;
1015
                        g_snprintf(error_cause, 512, "Username already taken");
1016
                        goto msg_response;
1017
                }
1018
                json_t *display = json_object_get(root, "display");
1019
                const char *display_text = json_string_value(display);
1020
                /* Create a participant instance */
1021
                participant = g_malloc0(sizeof(janus_textroom_participant));
1022
                participant->session = session;
1023
                participant->room = textroom;
1024
                participant->username = g_strdup(username_text);
1025
                participant->display = display_text ? g_strdup(display_text) : NULL;
1026
                participant->destroyed = 0;
1027
                janus_mutex_init(&participant->mutex);
1028
                g_hash_table_insert(session->rooms, janus_uint64_dup(textroom->room_id), participant);
1029
                g_hash_table_insert(textroom->participants, participant->username, participant);
1030
                /* Notify all participants */
1031
                JANUS_LOG(LOG_VERB, "Notifying all participants about the new join\n");
1032
                json_t *list = json_array();
1033
                if(textroom->participants) {
1034
                        /* Prepare event */
1035
                        json_t *event = json_object();
1036
                        json_object_set_new(event, "textroom", json_string("join"));
1037
                        json_object_set_new(event, "room", json_integer(textroom->room_id));
1038
                        json_object_set_new(event, "username", json_string(username_text));
1039
                        if(display_text != NULL)
1040
                                json_object_set_new(event, "display", json_string(display_text));
1041
                        char *event_text = json_dumps(event, json_format);
1042
                        json_decref(event);
1043
                        gateway->relay_data(handle, event_text, strlen(event_text));
1044
                        /* Broadcast */
1045
                        GHashTableIter iter;
1046
                        gpointer value;
1047
                        g_hash_table_iter_init(&iter, textroom->participants);
1048
                        while(g_hash_table_iter_next(&iter, NULL, &value)) {
1049
                                janus_textroom_participant *top = value;
1050
                                if(top == participant)
1051
                                        continue;        /* Skip us */
1052
                                JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64"\n", top->username, room_id);
1053
                                gateway->relay_data(top->session->handle, event_text, strlen(event_text));
1054
                                /* Take note of this user */
1055
                                json_t *p = json_object();
1056
                                json_object_set_new(p, "username", json_string(top->username));
1057
                                if(top->display != NULL)
1058
                                        json_object_set_new(p, "display", json_string(top->display));
1059
                                json_array_append_new(list, p);
1060
                        }
1061
                        free(event_text);
1062
                }
1063
                janus_mutex_unlock(&session->mutex);
1064
                janus_mutex_unlock(&textroom->mutex);
1065
                if(!internal) {
1066
                        /* Send response back */
1067
                        reply = json_object();
1068
                        json_object_set_new(reply, "textroom", json_string("success"));
1069
                        json_object_set_new(reply, "participants", list);
1070
                }
1071
                /* Also notify event handlers */
1072
                if(notify_events && gateway->events_is_enabled()) {
1073
                        json_t *info = json_object();
1074
                        json_object_set_new(info, "event", json_string("join"));
1075
                        json_object_set_new(info, "room", json_integer(room_id));
1076
                        json_object_set_new(info, "username", json_string(username_text));
1077
                        if(display_text)
1078
                                json_object_set_new(info, "display", json_string(display_text));
1079
                        gateway->notify_event(&janus_textroom_plugin, session->handle, info);
1080
                }
1081
        } else if(!strcasecmp(request_text, "leave")) {
1082
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1083
                        error_code, error_cause, TRUE,
1084
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1085
                if(error_code != 0)
1086
                        goto msg_response;
1087
                json_t *room = json_object_get(root, "room");
1088
                guint64 room_id = json_integer_value(room);
1089
                janus_mutex_lock(&rooms_mutex);
1090
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
1091
                if(textroom == NULL) {
1092
                        janus_mutex_unlock(&rooms_mutex);
1093
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1094
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1095
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1096
                        goto msg_response;
1097
                }
1098
                janus_mutex_lock(&textroom->mutex);
1099
                janus_mutex_unlock(&rooms_mutex);
1100
                janus_mutex_lock(&session->mutex);
1101
                janus_textroom_participant *participant = g_hash_table_lookup(session->rooms, &room_id);
1102
                if(participant == NULL) {
1103
                        janus_mutex_unlock(&session->mutex);
1104
                        janus_mutex_unlock(&textroom->mutex);
1105
                        JANUS_LOG(LOG_ERR, "Not in room %"SCNu64"\n", room_id);
1106
                        error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM;
1107
                        g_snprintf(error_cause, 512, "Not in room %"SCNu64, room_id);
1108
                        goto msg_response;
1109
                }
1110
                g_hash_table_remove(session->rooms, &room_id);
1111
                g_hash_table_remove(textroom->participants, participant->username);
1112
                participant->session = NULL;
1113
                participant->room = NULL;
1114
                /* Notify all participants */
1115
                JANUS_LOG(LOG_VERB, "Notifying all participants about the new leave\n");
1116
                if(textroom->participants) {
1117
                        /* Prepare event */
1118
                        json_t *event = json_object();
1119
                        json_object_set_new(event, "textroom", json_string("leave"));
1120
                        json_object_set_new(event, "room", json_integer(textroom->room_id));
1121
                        json_object_set_new(event, "username", json_string(participant->username));
1122
                        char *event_text = json_dumps(event, json_format);
1123
                        json_decref(event);
1124
                        gateway->relay_data(handle, event_text, strlen(event_text));
1125
                        /* Broadcast */
1126
                        GHashTableIter iter;
1127
                        gpointer value;
1128
                        g_hash_table_iter_init(&iter, textroom->participants);
1129
                        while(g_hash_table_iter_next(&iter, NULL, &value)) {
1130
                                janus_textroom_participant *top = value;
1131
                                if(top == participant)
1132
                                        continue;        /* Skip us */
1133
                                JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64"\n", top->username, room_id);
1134
                                gateway->relay_data(top->session->handle, event_text, strlen(event_text));
1135
                        }
1136
                        free(event_text);
1137
                }
1138
                /* Also notify event handlers */
1139
                if(notify_events && gateway->events_is_enabled()) {
1140
                        json_t *info = json_object();
1141
                        json_object_set_new(info, "event", json_string("leave"));
1142
                        json_object_set_new(info, "room", json_integer(room_id));
1143
                        json_object_set_new(info, "username", json_string(participant->username));
1144
                        gateway->notify_event(&janus_textroom_plugin, session->handle, info);
1145
                }
1146
                g_free(participant->username);
1147
                g_free(participant->display);
1148
                g_free(participant);
1149
                janus_mutex_unlock(&session->mutex);
1150
                janus_mutex_unlock(&textroom->mutex);
1151
                if(!internal) {
1152
                        /* Send response back */
1153
                        reply = json_object();
1154
                        json_object_set_new(reply, "textroom", json_string("success"));
1155
                }
1156
        } else if(!strcasecmp(request_text, "list")) {
1157
                /* List all rooms (but private ones) and their details (except for the secret, of course...) */
1158
                json_t *list = json_array();
1159
                JANUS_LOG(LOG_VERB, "Request for the list for all text rooms\n");
1160
                janus_mutex_lock(&rooms_mutex);
1161
                GHashTableIter iter;
1162
                gpointer value;
1163
                g_hash_table_iter_init(&iter, rooms);
1164
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1165
                        janus_textroom_room *room = value;
1166
                        if(!room)
1167
                                continue;
1168
                        janus_mutex_lock(&room->mutex);
1169
                        if(room->is_private) {
1170
                                /* Skip private room */
1171
                                JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
1172
                                janus_mutex_unlock(&room->mutex);
1173
                                continue;
1174
                        }
1175
                        json_t *rl = json_object();
1176
                        json_object_set_new(rl, "room", json_integer(room->room_id));
1177
                        json_object_set_new(rl, "description", json_string(room->room_name));
1178
                        json_object_set_new(rl, "pin_required", room->room_pin ? json_true() : json_false());
1179
                        /* TODO: Possibly list participant details... or make it a separate API call for a specific room */
1180
                        json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
1181
                        json_array_append_new(list, rl);
1182
                        janus_mutex_unlock(&room->mutex);
1183
                }
1184
                janus_mutex_unlock(&rooms_mutex);
1185
                if(!internal) {
1186
                        /* Send response back */
1187
                        reply = json_object();
1188
                        json_object_set_new(reply, "textroom", json_string("success"));
1189
                        json_object_set_new(reply, "list", list);
1190
                }
1191
        } else if(!strcasecmp(request_text, "allowed")) {
1192
                JANUS_LOG(LOG_VERB, "Attempt to edit the list of allowed participants in an existing textroom room\n");
1193
                JANUS_VALIDATE_JSON_OBJECT(root, allowed_parameters,
1194
                        error_code, error_cause, TRUE,
1195
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1196
                if(error_code != 0)
1197
                        goto msg_response;
1198
                json_t *action = json_object_get(root, "action");
1199
                json_t *room = json_object_get(root, "room");
1200
                json_t *allowed = json_object_get(root, "allowed");
1201
                const char *action_text = json_string_value(action);
1202
                if(strcasecmp(action_text, "enable") && strcasecmp(action_text, "disable") &&
1203
                                strcasecmp(action_text, "add") && strcasecmp(action_text, "remove")) {
1204
                        JANUS_LOG(LOG_ERR, "Unsupported action '%s' (allowed)\n", action_text);
1205
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
1206
                        g_snprintf(error_cause, 512, "Unsupported action '%s' (allowed)", action_text);
1207
                        goto msg_response;
1208
                }
1209
                guint64 room_id = json_integer_value(room);
1210
                janus_mutex_lock(&rooms_mutex);
1211
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
1212
                if(textroom == NULL) {
1213
                        janus_mutex_unlock(&rooms_mutex);
1214
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1215
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1216
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1217
                        goto msg_response;
1218
                }
1219
                janus_mutex_lock(&textroom->mutex);
1220
                /* A secret may be required for this action */
1221
                JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
1222
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1223
                if(error_code != 0) {
1224
                        janus_mutex_unlock(&textroom->mutex);
1225
                        janus_mutex_unlock(&rooms_mutex);
1226
                        goto msg_response;
1227
                }
1228
                if(!strcasecmp(action_text, "enable")) {
1229
                        JANUS_LOG(LOG_VERB, "Enabling the check on allowed authorization tokens for room %"SCNu64"\n", room_id);
1230
                        textroom->check_tokens = TRUE;
1231
                } else if(!strcasecmp(action_text, "disable")) {
1232
                        JANUS_LOG(LOG_VERB, "Disabling the check on allowed authorization tokens for room %"SCNu64" (free entry)\n", room_id);
1233
                        textroom->check_tokens = FALSE;
1234
                } else {
1235
                        gboolean add = !strcasecmp(action_text, "add");
1236
                        if(allowed) {
1237
                                /* Make sure the "allowed" array only contains strings */
1238
                                gboolean ok = TRUE;
1239
                                if(json_array_size(allowed) > 0) {
1240
                                        size_t i = 0;
1241
                                        for(i=0; i<json_array_size(allowed); i++) {
1242
                                                json_t *a = json_array_get(allowed, i);
1243
                                                if(!a || !json_is_string(a)) {
1244
                                                        ok = FALSE;
1245
                                                        break;
1246
                                                }
1247
                                        }
1248
                                }
1249
                                if(!ok) {
1250
                                        JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
1251
                                        error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
1252
                                        g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
1253
                                        janus_mutex_unlock(&textroom->mutex);
1254
                                        janus_mutex_unlock(&rooms_mutex);
1255
                                        goto msg_response;
1256
                                }
1257
                                size_t i = 0;
1258
                                for(i=0; i<json_array_size(allowed); i++) {
1259
                                        const char *token = json_string_value(json_array_get(allowed, i));
1260
                                        if(add) {
1261
                                                if(!g_hash_table_lookup(textroom->allowed, token))
1262
                                                        g_hash_table_insert(textroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
1263
                                        } else {
1264
                                                g_hash_table_remove(textroom->allowed, token);
1265
                                        }
1266
                                }
1267
                        }
1268
                }
1269
                if(!internal) {
1270
                        /* Send response back */
1271
                        reply = json_object();
1272
                        json_object_set_new(reply, "textroom", json_string("success"));
1273
                        json_object_set_new(reply, "room", json_integer(textroom->room_id));
1274
                        json_t *list = json_array();
1275
                        if(strcasecmp(action_text, "disable")) {
1276
                                if(g_hash_table_size(textroom->allowed) > 0) {
1277
                                        GHashTableIter iter;
1278
                                        gpointer key;
1279
                                        g_hash_table_iter_init(&iter, textroom->allowed);
1280
                                        while(g_hash_table_iter_next(&iter, &key, NULL)) {
1281
                                                char *token = key;
1282
                                                json_array_append_new(list, json_string(token));
1283
                                        }
1284
                                }
1285
                                json_object_set_new(reply, "allowed", list);
1286
                        }
1287
                        janus_mutex_unlock(&textroom->mutex);
1288
                        janus_mutex_unlock(&rooms_mutex);
1289
                        JANUS_LOG(LOG_VERB, "TextRoom room allowed list updated\n");
1290
                }
1291
        } else if(!strcasecmp(request_text, "kick")) {
1292
                JANUS_LOG(LOG_VERB, "Attempt to kick a participant from an existing textroom room\n");
1293
                JANUS_VALIDATE_JSON_OBJECT(root, kick_parameters,
1294
                        error_code, error_cause, TRUE,
1295
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1296
                if(error_code != 0)
1297
                        goto msg_response;
1298
                json_t *room = json_object_get(root, "room");
1299
                json_t *username = json_object_get(root, "username");
1300
                guint64 room_id = json_integer_value(room);
1301
                janus_mutex_lock(&rooms_mutex);
1302
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
1303
                if(textroom == NULL) {
1304
                        janus_mutex_unlock(&rooms_mutex);
1305
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1306
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1307
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1308
                        goto msg_response;
1309
                }
1310
                janus_mutex_lock(&textroom->mutex);
1311
                /* A secret may be required for this action */
1312
                JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
1313
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1314
                if(error_code != 0) {
1315
                        janus_mutex_unlock(&textroom->mutex);
1316
                        janus_mutex_unlock(&rooms_mutex);
1317
                        goto msg_response;
1318
                }
1319
                const char *user_id = json_string_value(username);
1320
                janus_textroom_participant *participant = g_hash_table_lookup(textroom->participants, user_id);
1321
                if(participant == NULL) {
1322
                        janus_mutex_unlock(&textroom->mutex);
1323
                        janus_mutex_unlock(&rooms_mutex);
1324
                        JANUS_LOG(LOG_ERR, "No such participant %s in room %"SCNu64"\n", user_id, room_id);
1325
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_USER;
1326
                        g_snprintf(error_cause, 512, "No such user %s in room %"SCNu64, user_id, room_id);
1327
                        goto msg_response;
1328
                }
1329
                /* Notify all participants */
1330
                JANUS_LOG(LOG_VERB, "Notifying all participants about the new kick\n");
1331
                if(textroom->participants) {
1332
                        /* Prepare event */
1333
                        json_t *event = json_object();
1334
                        json_object_set_new(event, "textroom", json_string("kicked"));
1335
                        json_object_set_new(event, "room", json_integer(textroom->room_id));
1336
                        json_object_set_new(event, "username", json_string(participant->username));
1337
                        char *event_text = json_dumps(event, json_format);
1338
                        json_decref(event);
1339
                        /* Broadcast */
1340
                        GHashTableIter iter;
1341
                        gpointer value;
1342
                        g_hash_table_iter_init(&iter, textroom->participants);
1343
                        while(g_hash_table_iter_next(&iter, NULL, &value)) {
1344
                                janus_textroom_participant *top = value;
1345
                                JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64"\n", top->username, room_id);
1346
                                gateway->relay_data(top->session->handle, event_text, strlen(event_text));
1347
                        }
1348
                        free(event_text);
1349
                }
1350
                /* Also notify event handlers */
1351
                if(notify_events && gateway->events_is_enabled()) {
1352
                        json_t *info = json_object();
1353
                        json_object_set_new(info, "textroom", json_string("kicked"));
1354
                        json_object_set_new(info, "room", json_integer(textroom->room_id));
1355
                        json_object_set_new(info, "username", json_string(participant->username));
1356
                        gateway->notify_event(&janus_textroom_plugin, session->handle, info);
1357
                }
1358
                /* Remove user from list */
1359
                g_hash_table_remove(participant->session->rooms, &room_id);
1360
                g_hash_table_remove(textroom->participants, participant->username);
1361
                participant->session = NULL;
1362
                participant->room = NULL;
1363
                g_free(participant->username);
1364
                g_free(participant->display);
1365
                g_free(participant);
1366
                /* Done */
1367
                janus_mutex_unlock(&textroom->mutex);
1368
                janus_mutex_unlock(&rooms_mutex);
1369
                if(!internal) {
1370
                        /* Send response back */
1371
                        reply = json_object();
1372
                        json_object_set_new(reply, "textbridge", json_string("success"));
1373
                }
1374
        } else if(!strcasecmp(request_text, "create")) {
1375
                JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
1376
                        error_code, error_cause, TRUE,
1377
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1378
                if(error_code != 0)
1379
                        goto msg_response;
1380
                if(admin_key != NULL) {
1381
                        /* An admin key was specified: make sure it was provided, and that it's valid */
1382
                        JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
1383
                                error_code, error_cause, TRUE,
1384
                                JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1385
                        if(error_code != 0)
1386
                                goto msg_response;
1387
                        JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
1388
                                JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1389
                        if(error_code != 0)
1390
                                goto msg_response;
1391
                }
1392
                json_t *room = json_object_get(root, "room");
1393
                json_t *desc = json_object_get(root, "description");
1394
                json_t *is_private = json_object_get(root, "is_private");
1395
                json_t *allowed = json_object_get(root, "allowed");
1396
                json_t *secret = json_object_get(root, "secret");
1397
                json_t *pin = json_object_get(root, "pin");
1398
                json_t *post = json_object_get(root, "post");
1399
                json_t *permanent = json_object_get(root, "permanent");
1400
                if(allowed) {
1401
                        /* Make sure the "allowed" array only contains strings */
1402
                        gboolean ok = TRUE;
1403
                        if(json_array_size(allowed) > 0) {
1404
                                size_t i = 0;
1405
                                for(i=0; i<json_array_size(allowed); i++) {
1406
                                        json_t *a = json_array_get(allowed, i);
1407
                                        if(!a || !json_is_string(a)) {
1408
                                                ok = FALSE;
1409
                                                break;
1410
                                        }
1411
                                }
1412
                        }
1413
                        if(!ok) {
1414
                                JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
1415
                                error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
1416
                                g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
1417
                                goto msg_response;
1418
                        }
1419
                }
1420
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1421
                if(save && config == NULL) {
1422
                        JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
1423
                        error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1424
                        g_snprintf(error_cause, 512, "No configuration file, can't create permanent room");
1425
                        goto msg_response;
1426
                }
1427
                guint64 room_id = 0;
1428
                room_id = json_integer_value(room);
1429
                if(room_id == 0) {
1430
                        JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
1431
                }
1432
                janus_mutex_lock(&rooms_mutex);
1433
                if(room_id > 0) {
1434
                        /* Let's make sure the room doesn't exist already */
1435
                        if(g_hash_table_lookup(rooms, &room_id) != NULL) {
1436
                                /* It does... */
1437
                                janus_mutex_unlock(&rooms_mutex);
1438
                                JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id);
1439
                                error_code = JANUS_TEXTROOM_ERROR_ROOM_EXISTS;
1440
                                g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id);
1441
                                goto msg_response;
1442
                        }
1443
                }
1444
                /* Create the text room */
1445
                janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
1446
                /* Generate a random ID */
1447
                if(room_id == 0) {
1448
                        while(room_id == 0) {
1449
                                room_id = janus_random_uint64();
1450
                                if(g_hash_table_lookup(rooms, &room_id) != NULL) {
1451
                                        /* Room ID already taken, try another one */
1452
                                        room_id = 0;
1453
                                }
1454
                        }
1455
                }
1456
                textroom->room_id = room_id;
1457
                char *description = NULL;
1458
                if(desc != NULL && strlen(json_string_value(desc)) > 0) {
1459
                        description = g_strdup(json_string_value(desc));
1460
                } else {
1461
                        char roomname[255];
1462
                        g_snprintf(roomname, 255, "Room %"SCNu64"", textroom->room_id);
1463
                        description = g_strdup(roomname);
1464
                }
1465
                textroom->room_name = description;
1466
                textroom->is_private = is_private ? json_is_true(is_private) : FALSE;
1467
                if(secret)
1468
                        textroom->room_secret = g_strdup(json_string_value(secret));
1469
                if(pin)
1470
                        textroom->room_pin = g_strdup(json_string_value(pin));
1471
                if(post) {
1472
#ifdef HAVE_LIBCURL
1473
                        /* FIXME Should we check if this is a valid HTTP address? */
1474
                        textroom->http_backend = g_strdup(json_string_value(post));
1475
#else
1476
                        JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
1477
#endif
1478
                }
1479
                textroom->participants = g_hash_table_new(g_str_hash, g_str_equal);
1480
                textroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
1481
                if(allowed != NULL) {
1482
                        /* Populate the "allowed" list as an ACL for people trying to join */
1483
                        if(json_array_size(allowed) > 0) {
1484
                                size_t i = 0;
1485
                                for(i=0; i<json_array_size(allowed); i++) {
1486
                                        const char *token = json_string_value(json_array_get(allowed, i));
1487
                                        if(!g_hash_table_lookup(textroom->allowed, token))
1488
                                                g_hash_table_insert(textroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
1489
                                }
1490
                        }
1491
                        textroom->check_tokens = TRUE;
1492
                }
1493
                textroom->destroyed = 0;
1494
                janus_mutex_init(&textroom->mutex);
1495
                g_hash_table_insert(rooms, janus_uint64_dup(textroom->room_id), textroom);
1496
                JANUS_LOG(LOG_VERB, "Created textroom: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n",
1497
                        textroom->room_id, textroom->room_name,
1498
                        textroom->is_private ? "private" : "public",
1499
                        textroom->room_secret ? textroom->room_secret : "no secret",
1500
                        textroom->room_pin ? textroom->room_pin : "no pin");
1501
                if(save) {
1502
                        /* This room is permanent: save to the configuration file too
1503
                         * FIXME: We should check if anything fails... */
1504
                        JANUS_LOG(LOG_VERB, "Saving room %"SCNu64" permanently in config file\n", textroom->room_id);
1505
                        janus_mutex_lock(&config_mutex);
1506
                        char cat[BUFSIZ];
1507
                        /* The room ID is the category */
1508
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, textroom->room_id);
1509
                        janus_config_add_category(config, cat);
1510
                        /* Now for the values */
1511
                        janus_config_add_item(config, cat, "description", textroom->room_name);
1512
                        if(textroom->is_private)
1513
                                janus_config_add_item(config, cat, "is_private", "yes");
1514
                        if(textroom->room_secret)
1515
                                janus_config_add_item(config, cat, "secret", textroom->room_secret);
1516
                        if(textroom->room_pin)
1517
                                janus_config_add_item(config, cat, "pin", textroom->room_pin);
1518
                        if(textroom->http_backend)
1519
                                janus_config_add_item(config, cat, "post", textroom->http_backend);
1520
                        /* Save modified configuration */
1521
                        if(janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE) < 0)
1522
                                save = FALSE;        /* This will notify the user the room is not permanent */
1523
                        janus_mutex_unlock(&config_mutex);
1524
                }
1525
                /* Show updated rooms list */
1526
                GHashTableIter iter;
1527
                gpointer value;
1528
                g_hash_table_iter_init(&iter, rooms);
1529
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1530
                        janus_textroom_room *tr = value;
1531
                        JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s]\n", tr->room_id, tr->room_name);
1532
                }
1533
                janus_mutex_unlock(&rooms_mutex);
1534
                if(!internal) {
1535
                        /* Send response back */
1536
                        reply = json_object();
1537
                        /* Notice that we reply differently if the request came via Janus API */
1538
                        json_object_set_new(reply, "textroom", json_string(json == NULL ? "success" : "created"));
1539
                        json_object_set_new(reply, "room", json_integer(textroom->room_id));
1540
                        json_object_set_new(reply, "permanent", save ? json_true() : json_false());
1541
                }
1542
                /* Also notify event handlers */
1543
                if(notify_events && gateway->events_is_enabled()) {
1544
                        json_t *info = json_object();
1545
                        json_object_set_new(info, "event", json_string("created"));
1546
                        json_object_set_new(info, "room", json_integer(room_id));
1547
                        gateway->notify_event(&janus_textroom_plugin, session->handle, info);
1548
                }
1549
        } else if(!strcasecmp(request_text, "exists")) {
1550
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1551
                        error_code, error_cause, TRUE,
1552
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1553
                if(error_code != 0)
1554
                        goto msg_response;
1555
                json_t *room = json_object_get(root, "room");
1556
                guint64 room_id = json_integer_value(room);
1557
                janus_mutex_lock(&rooms_mutex);
1558
                gboolean room_exists = g_hash_table_contains(rooms, &room_id);
1559
                janus_mutex_unlock(&rooms_mutex);
1560
                if(!internal) {
1561
                        /* Send response back */
1562
                        reply = json_object();
1563
                        json_object_set_new(reply, "textroom", json_string("success"));
1564
                        json_object_set_new(reply, "room", json_integer(room_id));
1565
                        json_object_set_new(reply, "exists", room_exists ? json_true() : json_false());
1566
                }
1567
        } else if(!strcasecmp(request_text, "destroy")) {
1568
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1569
                        error_code, error_cause, TRUE,
1570
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1571
                if(error_code != 0)
1572
                        goto msg_response;
1573
                json_t *room = json_object_get(root, "room");
1574
                json_t *permanent = json_object_get(root, "permanent");
1575
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1576
                if(save && config == NULL) {
1577
                        JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
1578
                        error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1579
                        g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently");
1580
                        goto msg_response;
1581
                }
1582
                guint64 room_id = json_integer_value(room);
1583
                janus_mutex_lock(&rooms_mutex);
1584
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
1585
                if(textroom == NULL) {
1586
                        janus_mutex_unlock(&rooms_mutex);
1587
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1588
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1589
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1590
                        goto msg_response;
1591
                }
1592
                janus_mutex_lock(&textroom->mutex);
1593
                /* A secret may be required for this action */
1594
                JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
1595
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1596
                if(error_code != 0) {
1597
                        janus_mutex_unlock(&textroom->mutex);
1598
                        janus_mutex_unlock(&rooms_mutex);
1599
                        goto msg_response;
1600
                }
1601
                /* Remove room */
1602
                g_hash_table_remove(rooms, &room_id);
1603
                if(save) {
1604
                        /* This change is permanent: save to the configuration file too
1605
                         * FIXME: We should check if anything fails... */
1606
                        JANUS_LOG(LOG_VERB, "Destroying room %"SCNu64" permanently in config file\n", room_id);
1607
                        janus_mutex_lock(&config_mutex);
1608
                        char cat[BUFSIZ];
1609
                        /* The room ID is the category */
1610
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, room_id);
1611
                        janus_config_remove_category(config, cat);
1612
                        /* Save modified configuration */
1613
                        janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE);
1614
                        janus_mutex_unlock(&config_mutex);
1615
                }
1616
                /* Notify all participants */
1617
                JANUS_LOG(LOG_VERB, "Notifying all participants about the destroy\n");
1618
                if(textroom->participants) {
1619
                        /* Prepare event */
1620
                        json_t *event = json_object();
1621
                        json_object_set_new(event, "textroom", json_string("destroyed"));
1622
                        json_object_set_new(event, "room", json_integer(textroom->room_id));
1623
                        char *event_text = json_dumps(event, json_format);
1624
                        json_decref(event);
1625
                        gateway->relay_data(handle, event_text, strlen(event_text));
1626
                        /* Broadcast */
1627
                        GHashTableIter iter;
1628
                        gpointer value;
1629
                        g_hash_table_iter_init(&iter, textroom->participants);
1630
                        while(g_hash_table_iter_next(&iter, NULL, &value)) {
1631
                                janus_textroom_participant *top = value;
1632
                                JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64"\n", top->username, room_id);
1633
                                gateway->relay_data(top->session->handle, event_text, strlen(event_text));
1634
                                janus_mutex_unlock(&top->session->mutex);
1635
                                g_hash_table_remove(top->session->rooms, &room_id);
1636
                                janus_mutex_unlock(&top->session->mutex);
1637
                                g_free(top->username);
1638
                                g_free(top->display);
1639
                                g_free(top);
1640
                        }
1641
                        free(event_text);
1642
                }
1643
                janus_mutex_unlock(&textroom->mutex);
1644
                janus_mutex_unlock(&rooms_mutex);
1645
                if(!internal) {
1646
                        /* Send response back */
1647
                        reply = json_object();
1648
                        /* Notice that we reply differently if the request came via Janus API */
1649
                        json_object_set_new(reply, "textroom", json_string(json == NULL ? "success" : "destroyed"));
1650
                }
1651
                /* We'll let the watchdog worry about freeing resources */
1652
                old_rooms = g_list_append(old_rooms, textroom);
1653
                /* Also notify event handlers */
1654
                if(notify_events && gateway->events_is_enabled()) {
1655
                        json_t *info = json_object();
1656
                        json_object_set_new(info, "event", json_string("destroyed"));
1657
                        json_object_set_new(info, "room", json_integer(room_id));
1658
                        gateway->notify_event(&janus_textroom_plugin, session->handle, info);
1659
                }
1660
        } else {
1661
                JANUS_LOG(LOG_ERR, "Unsupported request %s\n", request_text);
1662
                error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
1663
                g_snprintf(error_cause, 512, "Unsupported request %s", request_text);
1664
                goto msg_response;
1665
        }
1666

    
1667
msg_response:
1668
                {
1669
                        if(!internal) {
1670
                                if(error_code == 0 && !reply) {
1671
                                        error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1672
                                        g_snprintf(error_cause, 512, "Invalid response");
1673
                                }
1674
                                if(error_code != 0) {
1675
                                        /* Prepare JSON error event */
1676
                                        json_t *event = json_object();
1677
                                        json_object_set_new(event, "textroom", json_string("error"));
1678
                                        json_object_set_new(event, "error_code", json_integer(error_code));
1679
                                        json_object_set_new(event, "error", json_string(error_cause));
1680
                                        reply = event;
1681
                                }
1682
                                if(transaction_text && json == NULL)
1683
                                        json_object_set_new(reply, "transaction", json_string(transaction_text));
1684
                                if(json == NULL) {
1685
                                        /* Reply via data channels */
1686
                                        char *reply_text = json_dumps(reply, json_format);
1687
                                        json_decref(reply);
1688
                                        gateway->relay_data(handle, reply_text, strlen(reply_text));
1689
                                        free(reply_text);
1690
                                } else {
1691
                                        /* Reply via Janus API */
1692
                                        return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, reply);
1693
                                }
1694
                        }
1695
                        if(root != NULL)
1696
                                json_decref(root);
1697
                }
1698
        return NULL;
1699
}
1700

    
1701
void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
1702
        /* We don't do audio/video */
1703
}
1704

    
1705
void janus_textroom_hangup_media(janus_plugin_session *handle) {
1706
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
1707
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1708
                return;
1709
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
1710
        if(!session) {
1711
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1712
                return;
1713
        }
1714
        if(session->destroyed)
1715
                return;
1716
        if(g_atomic_int_add(&session->hangingup, 1))
1717
                return;
1718
        /* Get rid of all participants */
1719
        janus_mutex_lock(&session->mutex);
1720
        GList *list = NULL;
1721
        if(session->rooms) {
1722
                GHashTableIter iter;
1723
                gpointer value;
1724
                g_hash_table_iter_init(&iter, session->rooms);
1725
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1726
                        janus_textroom_participant *p = value;
1727
                        janus_mutex_lock(&p->mutex);
1728
                        if(p->room)
1729
                                list = g_list_append(list, janus_uint64_dup(p->room->room_id));
1730
                        janus_mutex_unlock(&p->mutex);
1731
                }
1732
                janus_mutex_unlock(&rooms_mutex);
1733
        }
1734
        janus_mutex_unlock(&session->mutex);
1735
        JANUS_LOG(LOG_VERB, "Leaving %d rooms\n", g_list_length(list));
1736
        char request[100];
1737
        GList *first = list;
1738
        while(list) {
1739
                guint64 room_id = *((guint64 *)list->data);
1740
                g_snprintf(request, sizeof(request), "{\"textroom\":\"leave\",\"transaction\":\"internal\",\"room\":%"SCNu64"}", room_id);
1741
                janus_textroom_handle_incoming_request(handle, g_strdup(request), NULL, TRUE);
1742
                list = list->next;
1743
        }
1744
        g_list_free_full(first, (GDestroyNotify)g_free);
1745
}
1746

    
1747
/* Thread to handle incoming messages */
1748
static void *janus_textroom_handler(void *data) {
1749
        JANUS_LOG(LOG_VERB, "Joining TextRoom handler thread\n");
1750
        janus_textroom_message *msg = NULL;
1751
        int error_code = 0;
1752
        char error_cause[512];
1753
        json_t *root = NULL;
1754
        gboolean do_offer = FALSE;
1755
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
1756
                msg = g_async_queue_pop(messages);
1757
                if(msg == NULL)
1758
                        continue;
1759
                if(msg == &exit_message)
1760
                        break;
1761
                if(msg->handle == NULL) {
1762
                        janus_textroom_message_free(msg);
1763
                        continue;
1764
                }
1765
                janus_textroom_session *session = NULL;
1766
                janus_mutex_lock(&sessions_mutex);
1767
                if(g_hash_table_lookup(sessions, msg->handle) != NULL ) {
1768
                        session = (janus_textroom_session *)msg->handle->plugin_handle;
1769
                }
1770
                janus_mutex_unlock(&sessions_mutex);
1771
                if(!session) {
1772
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1773
                        janus_textroom_message_free(msg);
1774
                        continue;
1775
                }
1776
                if(session->destroyed) {
1777
                        janus_textroom_message_free(msg);
1778
                        continue;
1779
                }
1780
                /* Handle request */
1781
                error_code = 0;
1782
                root = msg->message;
1783
                if(msg->message == NULL) {
1784
                        JANUS_LOG(LOG_ERR, "No message??\n");
1785
                        error_code = JANUS_TEXTROOM_ERROR_NO_MESSAGE;
1786
                        g_snprintf(error_cause, 512, "%s", "No message??");
1787
                        goto error;
1788
                }
1789
                if(!json_is_object(root)) {
1790
                        JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
1791
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
1792
                        g_snprintf(error_cause, 512, "JSON error: not an object");
1793
                        goto error;
1794
                }
1795
                /* Parse request */
1796
                JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
1797
                        error_code, error_cause, TRUE,
1798
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1799
                if(error_code != 0)
1800
                        goto error;
1801
                json_t *request = json_object_get(root, "request");
1802
                const char *request_text = json_string_value(request);
1803
                do_offer = FALSE;
1804
                if(!strcasecmp(request_text, "setup")) {
1805
                        if(!g_atomic_int_compare_and_exchange(&session->setup, 0, 1)) {
1806
                                JANUS_LOG(LOG_ERR, "PeerConnection already setup\n");
1807
                                error_code = JANUS_TEXTROOM_ERROR_ALREADY_SETUP;
1808
                                g_snprintf(error_cause, 512, "PeerConnection already setup");
1809
                                goto error;
1810
                        }
1811
                        do_offer = TRUE;
1812
                } else if(!strcasecmp(request_text, "ack")) {
1813
                        /* The peer sent their answer back: do nothing */
1814
                } else {
1815
                        JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
1816
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
1817
                        g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1818
                        goto error;
1819
                }
1820

    
1821
                /* Prepare JSON event */
1822
                json_t *event = json_object();
1823
                json_object_set_new(event, "textroom", json_string("event"));
1824
                json_object_set_new(event, "result", json_string("ok"));
1825
                if(!do_offer) {
1826
                        int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, NULL);
1827
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
1828
                } else {
1829
                        /* Send an offer */
1830
                        char sdp[500];
1831
                        g_snprintf(sdp, sizeof(sdp), sdp_template,
1832
                                janus_get_real_time(),                        /* We need current time here */
1833
                                janus_get_real_time());                        /* We need current time here */
1834
                        json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
1835
                        /* How long will the gateway take to push the event? */
1836
                        g_atomic_int_set(&session->hangingup, 0);
1837
                        gint64 start = janus_get_monotonic_time();
1838
                        int res = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, jsep);
1839
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n",
1840
                                res, janus_get_monotonic_time()-start);
1841
                        json_decref(jsep);
1842
                }
1843
                json_decref(event);
1844
                janus_textroom_message_free(msg);
1845
                continue;
1846

    
1847
error:
1848
                {
1849
                        /* Prepare JSON error event */
1850
                        json_t *event = json_object();
1851
                        json_object_set_new(event, "textroom", json_string("error"));
1852
                        json_object_set_new(event, "error_code", json_integer(error_code));
1853
                        json_object_set_new(event, "error", json_string(error_cause));
1854
                        int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, NULL);
1855
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
1856
                        json_decref(event);
1857
                        janus_textroom_message_free(msg);
1858
                }
1859
        }
1860
        JANUS_LOG(LOG_VERB, "Leaving TextRoom handler thread\n");
1861
        return NULL;
1862
}