Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_textroom.c @ 71a04f89

History | View | Annotate | Download (58 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 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 are exchanged directly
17
 * via Data Channels.
18
 *
19
 * Each room can also be configured with an HTTP backend to contact for
20
 * incoming messages. If configured, messages addressed to that room will
21
 * also be forwarded, by means of an HTTP POST, to the specified address.
22
 * Notice that this will only work if libcurl was available when
23
 * configuring and installing Janus.
24
 *
25
 * \note This plugin is only meant to showcase what you can do with
26
 * data channels involving multiple participants at the same time. While
27
 * functional, it's not inherently better or faster than doing the same
28
 * thing using the Janus API messaging itself (e.g., as part of the
29
 * plugin API messaging) or using existing instant messaging protocols
30
 * (e.g., Jabber). In fact, while data channels are being used, you're
31
 * still going through a server, so it's not really peer-to-peer. That
32
 * said, the plugin can be useful if you don't plan to use any other
33
 * infrastructure than Janus, and yet you also want to have text-based
34
 * communication (e.g., to add a chatroom to an audio or video conference).
35
 *
36
 * Notice that, in general, all users can create rooms. If you want to
37
 * limit this functionality, you can configure an admin \c admin_key in
38
 * the plugin settings. When configured, only "create" requests that
39
 * include the correct \c admin_key value in an "admin_key" property
40
 * will succeed, and will be rejected otherwise.
41
 *
42
 * \section textroomapi Text Room API
43
 * TBD.
44
 *
45
 * \ingroup plugins
46
 * \ref plugins
47
 */
48

    
49
#include "plugins/plugin.h"
50

    
51
#include <jansson.h>
52

    
53
#ifdef HAVE_LIBCURL
54
#include <curl/curl.h>
55
#endif
56

    
57
#include "debug.h"
58
#include "apierror.h"
59
#include "config.h"
60
#include "mutex.h"
61
#include "utils.h"
62

    
63

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

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

    
94
/* Plugin setup */
95
static janus_plugin janus_textroom_plugin =
96
        JANUS_PLUGIN_INIT (
97
                .init = janus_textroom_init,
98
                .destroy = janus_textroom_destroy,
99

    
100
                .get_api_compatibility = janus_textroom_get_api_compatibility,
101
                .get_version = janus_textroom_get_version,
102
                .get_version_string = janus_textroom_get_version_string,
103
                .get_description = janus_textroom_get_description,
104
                .get_name = janus_textroom_get_name,
105
                .get_author = janus_textroom_get_author,
106
                .get_package = janus_textroom_get_package,
107

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

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

    
126

    
127
/* Parameter validation */
128
static struct janus_json_parameter request_parameters[] = {
129
        {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
130
};
131
static struct janus_json_parameter transaction_parameters[] = {
132
        {"textroom", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
133
        {"transaction", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
134
};
135
static struct janus_json_parameter room_parameters[] = {
136
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
137
};
138
static struct janus_json_parameter adminkey_parameters[] = {
139
        {"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
140
};
141
static struct janus_json_parameter create_parameters[] = {
142
        {"description", JSON_STRING, 0},
143
        {"secret", JSON_STRING, 0},
144
        {"pin", JSON_STRING, 0},
145
        {"post", JSON_STRING, 0},
146
        {"is_private", JANUS_JSON_BOOL, 0}
147
};
148
static struct janus_json_parameter join_parameters[] = {
149
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
150
        {"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
151
        {"display", JSON_STRING, 0}
152
};
153
static struct janus_json_parameter message_parameters[] = {
154
        {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
155
        {"text", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
156
        {"to", JSON_STRING, 0},
157
        {"tos", JSON_ARRAY, 0},
158
        {"ack", JANUS_JSON_BOOL, 0}
159
};
160

    
161
/* Static configuration instance */
162
static janus_config *config = NULL;
163
static const char *config_folder = NULL;
164
static janus_mutex config_mutex;
165

    
166
/* Useful stuff */
167
static volatile gint initialized = 0, stopping = 0;
168
static gboolean notify_events = TRUE;
169
static janus_callbacks *gateway = NULL;
170
static GThread *handler_thread;
171
static GThread *watchdog;
172
static void *janus_textroom_handler(void *data);
173

    
174

    
175
typedef struct janus_textroom_message {
176
        janus_plugin_session *handle;
177
        char *transaction;
178
        char *message;
179
        char *sdp_type;
180
        char *sdp;
181
} janus_textroom_message;
182
static GAsyncQueue *messages = NULL;
183
static janus_textroom_message exit_message;
184

    
185
static void janus_textroom_message_free(janus_textroom_message *msg) {
186
        if(!msg || msg == &exit_message)
187
                return;
188

    
189
        msg->handle = NULL;
190

    
191
        g_free(msg->transaction);
192
        msg->transaction = NULL;
193
        g_free(msg->message);
194
        msg->message = NULL;
195
        g_free(msg->sdp_type);
196
        msg->sdp_type = NULL;
197
        g_free(msg->sdp);
198
        msg->sdp = NULL;
199

    
200
        g_free(msg);
201
}
202

    
203
typedef struct janus_textroom_room {
204
        guint64 room_id;                        /* Unique room ID */
205
        gchar *room_name;                        /* Room description */
206
        gchar *room_secret;                        /* Secret needed to manipulate (e.g., destroy) this room */
207
        gchar *room_pin;                        /* Password needed to join this room, if any */
208
        gboolean is_private;                /* Whether this room is 'private' (as in hidden) or not */
209
        gchar *http_backend;                /* Server to contact via HTTP POST for incoming messages, if any */
210
        GHashTable *participants;        /* Map of participants */
211
        gint64 destroyed;                        /* When this room has been destroyed */
212
        janus_mutex mutex;                        /* Mutex to lock this room instance */
213
} janus_textroom_room;
214
static GHashTable *rooms;
215
static janus_mutex rooms_mutex;
216
static GList *old_rooms;
217
static char *admin_key = NULL;
218

    
219
typedef struct janus_textroom_session {
220
        janus_plugin_session *handle;
221
        GHashTable *rooms;                        /* Map of rooms this user is in, and related participant instance */
222
        janus_mutex mutex;                        /* Mutex to lock this session */
223
        volatile gint setup;
224
        volatile gint hangingup;
225
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
226
} janus_textroom_session;
227
static GHashTable *sessions;
228
static GList *old_sessions;
229
static janus_mutex sessions_mutex;
230

    
231
typedef struct janus_textroom_participant {
232
        janus_textroom_session *session;
233
        janus_textroom_room *room;        /* Room this participant is in */
234
        gchar *username;                        /* Unique username in the room */
235
        gchar *display;                                /* Display name in the room, if any */
236
        janus_mutex mutex;                        /* Mutex to lock this session */
237
        gint64 destroyed;                        /* When this participant was destroyed */
238
} janus_textroom_participant;
239

    
240

    
241
/* SDP template: we only offer data channels */
242
#define sdp_template \
243
                "v=0\r\n" \
244
                "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"        /* We need current time here */ \
245
                "s=Janus TextRoom plugin\r\n" \
246
                "t=0 0\r\n" \
247
                "m=application 1 DTLS/SCTP 5000\r\n" \
248
                "c=IN IP4 1.1.1.1\r\n" \
249
                "a=sctpmap:5000 webrtc-datachannel 16\r\n"
250

    
251

    
252
/* Error codes */
253
#define JANUS_TEXTROOM_ERROR_NO_MESSAGE                        411
254
#define JANUS_TEXTROOM_ERROR_INVALID_JSON                412
255
#define JANUS_TEXTROOM_ERROR_MISSING_ELEMENT        413
256
#define JANUS_TEXTROOM_ERROR_INVALID_ELEMENT        414
257
#define JANUS_TEXTROOM_ERROR_INVALID_REQUEST        415
258
#define JANUS_TEXTROOM_ERROR_ALREADY_SETUP                416
259
#define JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM                417
260
#define JANUS_TEXTROOM_ERROR_ROOM_EXISTS                418
261
#define JANUS_TEXTROOM_ERROR_UNAUTHORIZED                419
262
#define JANUS_TEXTROOM_ERROR_USERNAME_EXISTS        420
263
#define JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM        421
264
#define JANUS_TEXTROOM_ERROR_NOT_IN_ROOM                422
265
#define JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR                499
266

    
267
/* TextRoom watchdog/garbage collector (sort of) */
268
static void *janus_textroom_watchdog(void *data) {
269
        JANUS_LOG(LOG_INFO, "TextRoom watchdog started\n");
270
        gint64 now = 0;
271
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
272
                janus_mutex_lock(&sessions_mutex);
273
                /* Iterate on all the sessions */
274
                now = janus_get_monotonic_time();
275
                if(old_sessions != NULL) {
276
                        GList *sl = old_sessions;
277
                        JANUS_LOG(LOG_HUGE, "Checking %d old TextRoom sessions...\n", g_list_length(old_sessions));
278
                        while(sl) {
279
                                janus_textroom_session *session = (janus_textroom_session *)sl->data;
280
                                if(!session) {
281
                                        sl = sl->next;
282
                                        continue;
283
                                }
284
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
285
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
286
                                        JANUS_LOG(LOG_VERB, "Freeing old TextRoom session\n");
287
                                        GList *rm = sl->next;
288
                                        old_sessions = g_list_delete_link(old_sessions, sl);
289
                                        sl = rm;
290
                                        session->handle = NULL;
291
                                        /* TODO Free session stuff */
292
                                        g_free(session);
293
                                        session = NULL;
294
                                        continue;
295
                                }
296
                                sl = sl->next;
297
                        }
298
                }
299
                janus_mutex_unlock(&sessions_mutex);
300
                janus_mutex_lock(&rooms_mutex);
301
                if(old_rooms != NULL) {
302
                        GList *rl = old_rooms;
303
                        now = janus_get_monotonic_time();
304
                        while(rl) {
305
                                janus_textroom_room *textroom = (janus_textroom_room*)rl->data;
306
                                if(!g_atomic_int_get(&initialized) || g_atomic_int_get(&stopping)){
307
                                        break;
308
                                }
309
                                if(!textroom) {
310
                                        rl = rl->next;
311
                                        continue;
312
                                }
313
                                if(now - textroom->destroyed >= 5*G_USEC_PER_SEC) {
314
                                        /* Free resources */
315
                                        JANUS_LOG(LOG_VERB, "Freeing old TextRoom room %"SCNu64"\n", textroom->room_id);
316
                                        g_free(textroom->room_name);
317
                                        g_free(textroom->room_secret);
318
                                        g_free(textroom->room_pin);
319
                                        g_hash_table_destroy(textroom->participants);
320
                                        g_free(textroom);
321
                                        /* Move on */
322
                                        GList *rm = rl->next;
323
                                        old_rooms = g_list_delete_link(old_rooms, rl);
324
                                        rl = rm;
325
                                        continue;
326
                                }
327
                                rl = rl->next;
328
                        }
329
                }
330
                janus_mutex_unlock(&rooms_mutex);
331
                g_usleep(500000);
332
        }
333
        JANUS_LOG(LOG_INFO, "TextRoom watchdog stopped\n");
334
        return NULL;
335
}
336

    
337
#ifdef HAVE_LIBCURL
338
static size_t janus_textroom_write_data(void *buffer, size_t size, size_t nmemb, void *userp) {
339
        return size*nmemb;
340
}
341
#endif
342

    
343
void janus_textroom_handle_incoming_request(janus_plugin_session *handle, char *text, gboolean internal);
344

    
345

    
346
/* Plugin implementation */
347
int janus_textroom_init(janus_callbacks *callback, const char *config_path) {
348
        if(g_atomic_int_get(&stopping)) {
349
                /* Still stopping from before */
350
                return -1;
351
        }
352
        if(callback == NULL || config_path == NULL) {
353
                /* Invalid arguments */
354
                return -1;
355
        }
356

    
357
        /* Read configuration */
358
        char filename[255];
359
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_TEXTROOM_PACKAGE);
360
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
361
        config = janus_config_parse(filename);
362
        config_folder = config_path;
363
        if(config != NULL)
364
                janus_config_print(config);
365
        janus_mutex_init(&config_mutex);
366

    
367
        rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
368
        janus_mutex_init(&rooms_mutex);
369
        sessions = g_hash_table_new(NULL, NULL);
370
        messages = g_async_queue_new_full((GDestroyNotify) janus_textroom_message_free);
371
        janus_mutex_init(&sessions_mutex);
372
        /* This is the callback we'll need to invoke to contact the gateway */
373
        gateway = callback;
374

    
375
        /* Parse configuration to populate the rooms list */
376
        if(config != NULL) {
377
                /* Any admin key to limit who can "create"? */
378
                janus_config_item *key = janus_config_get_item_drilldown(config, "general", "admin_key");
379
                if(key != NULL && key->value != NULL)
380
                        admin_key = g_strdup(key->value);
381
                janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events");
382
                if(events != NULL && events->value != NULL)
383
                        notify_events = janus_is_true(events->value);
384
                if(!notify_events && callback->events_is_enabled()) {
385
                        JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_TEXTROOM_NAME);
386
                }
387
                /* Iterate on all rooms */
388
                GList *cl = janus_config_get_categories(config);
389
                while(cl != NULL) {
390
                        janus_config_category *cat = (janus_config_category *)cl->data;
391
                        if(cat->name == NULL || !strcasecmp(cat->name, "general")) {
392
                                cl = cl->next;
393
                                continue;
394
                        }
395
                        JANUS_LOG(LOG_VERB, "Adding text room '%s'\n", cat->name);
396
                        janus_config_item *desc = janus_config_get_item(cat, "description");
397
                        janus_config_item *priv = janus_config_get_item(cat, "is_private");
398
                        janus_config_item *secret = janus_config_get_item(cat, "secret");
399
                        janus_config_item *pin = janus_config_get_item(cat, "pin");
400
                        janus_config_item *post = janus_config_get_item(cat, "post");
401
                        /* Create the text room */
402
                        janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
403
                        textroom->room_id = atol(cat->name);
404
                        char *description = NULL;
405
                        if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
406
                                description = g_strdup(desc->value);
407
                        else
408
                                description = g_strdup(cat->name);
409
                        textroom->room_name = description;
410
                        textroom->is_private = priv && priv->value && janus_is_true(priv->value);
411
                        if(secret != NULL && secret->value != NULL) {
412
                                textroom->room_secret = g_strdup(secret->value);
413
                        }
414
                        if(pin != NULL && pin->value != NULL) {
415
                                textroom->room_pin = g_strdup(pin->value);
416
                        }
417
                        if(post != NULL && post->value != NULL) {
418
#ifdef HAVE_LIBCURL
419
                                /* FIXME Should we check if this is a valid HTTP address? */
420
                                textroom->http_backend = g_strdup(post->value);
421
#else
422
                                JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
423
#endif
424
                        }
425
                        textroom->participants = g_hash_table_new(g_str_hash, g_str_equal);
426
                        textroom->destroyed = 0;
427
                        janus_mutex_init(&textroom->mutex);
428
                        JANUS_LOG(LOG_VERB, "Created textroom: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n",
429
                                textroom->room_id, textroom->room_name,
430
                                textroom->is_private ? "private" : "public",
431
                                textroom->room_secret ? textroom->room_secret : "no secret",
432
                                textroom->room_pin ? textroom->room_pin : "no pin");
433
                        g_hash_table_insert(rooms, janus_uint64_dup(textroom->room_id), textroom);
434
                        cl = cl->next;
435
                }
436
                /* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
437
        }
438

    
439
        /* Show available rooms */
440
        janus_mutex_lock(&rooms_mutex);
441
        GHashTableIter iter;
442
        gpointer value;
443
        g_hash_table_iter_init(&iter, rooms);
444
        while (g_hash_table_iter_next(&iter, NULL, &value)) {
445
                janus_textroom_room *tr = value;
446
                JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s]\n", tr->room_id, tr->room_name);
447
        }
448
        janus_mutex_unlock(&rooms_mutex);
449

    
450
#ifdef HAVE_LIBCURL
451
        curl_global_init(CURL_GLOBAL_ALL);
452
#endif
453

    
454
        GError *error = NULL;
455
        /* Start the sessions watchdog */
456
        watchdog = g_thread_try_new("textroom watchdog", &janus_textroom_watchdog, NULL, &error);
457
        if(error != NULL) {
458
                g_atomic_int_set(&initialized, 0);
459
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the TextRoom watchdog thread...\n", error->code, error->message ? error->message : "??");
460
                return -1;
461
        }
462
        /* Launch the thread that will handle incoming messages */
463
        handler_thread = g_thread_try_new("janus textroom handler", janus_textroom_handler, NULL, &error);
464
        if(error != NULL) {
465
                g_atomic_int_set(&initialized, 0);
466
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the EchoTest handler thread...\n", error->code, error->message ? error->message : "??");
467
                return -1;
468
        }
469
        g_atomic_int_set(&initialized, 1);
470
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_TEXTROOM_NAME);
471
        return 0;
472
}
473

    
474
void janus_textroom_destroy(void) {
475
        if(!g_atomic_int_get(&initialized))
476
                return;
477
        g_atomic_int_set(&stopping, 1);
478

    
479
        g_async_queue_push(messages, &exit_message);
480
        if(handler_thread != NULL) {
481
                g_thread_join(handler_thread);
482
                handler_thread = NULL;
483
        }
484
        if(watchdog != NULL) {
485
                g_thread_join(watchdog);
486
                watchdog = NULL;
487
        }
488

    
489
        /* FIXME We should destroy the sessions cleanly */
490
        janus_mutex_lock(&sessions_mutex);
491
        g_hash_table_destroy(sessions);
492
        janus_mutex_unlock(&sessions_mutex);
493
        g_async_queue_unref(messages);
494
        messages = NULL;
495
        sessions = NULL;
496

    
497
#ifdef HAVE_LIBCURL
498
        curl_global_cleanup();
499
#endif
500

    
501
        janus_config_destroy(config);
502
        g_free(admin_key);
503

    
504
        g_atomic_int_set(&initialized, 0);
505
        g_atomic_int_set(&stopping, 0);
506
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_TEXTROOM_NAME);
507
}
508

    
509
int janus_textroom_get_api_compatibility(void) {
510
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
511
        return JANUS_PLUGIN_API_VERSION;
512
}
513

    
514
int janus_textroom_get_version(void) {
515
        return JANUS_TEXTROOM_VERSION;
516
}
517

    
518
const char *janus_textroom_get_version_string(void) {
519
        return JANUS_TEXTROOM_VERSION_STRING;
520
}
521

    
522
const char *janus_textroom_get_description(void) {
523
        return JANUS_TEXTROOM_DESCRIPTION;
524
}
525

    
526
const char *janus_textroom_get_name(void) {
527
        return JANUS_TEXTROOM_NAME;
528
}
529

    
530
const char *janus_textroom_get_author(void) {
531
        return JANUS_TEXTROOM_AUTHOR;
532
}
533

    
534
const char *janus_textroom_get_package(void) {
535
        return JANUS_TEXTROOM_PACKAGE;
536
}
537

    
538
void janus_textroom_create_session(janus_plugin_session *handle, int *error) {
539
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
540
                *error = -1;
541
                return;
542
        }
543
        janus_textroom_session *session = (janus_textroom_session *)g_malloc0(sizeof(janus_textroom_session));
544
        session->handle = handle;
545
        session->rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, NULL);
546
        session->destroyed = 0;
547
        janus_mutex_init(&session->mutex);
548
        g_atomic_int_set(&session->setup, 0);
549
        g_atomic_int_set(&session->hangingup, 0);
550
        handle->plugin_handle = session;
551
        janus_mutex_lock(&sessions_mutex);
552
        g_hash_table_insert(sessions, handle, session);
553
        janus_mutex_unlock(&sessions_mutex);
554

    
555
        return;
556
}
557

    
558
void janus_textroom_destroy_session(janus_plugin_session *handle, int *error) {
559
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
560
                *error = -1;
561
                return;
562
        }
563
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
564
        if(!session) {
565
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
566
                *error = -2;
567
                return;
568
        }
569
        JANUS_LOG(LOG_VERB, "Removing Echo Test session...\n");
570
        janus_mutex_lock(&sessions_mutex);
571
        if(!session->destroyed) {
572
                g_hash_table_remove(sessions, handle);
573
                janus_textroom_hangup_media(handle);
574
                session->destroyed = janus_get_monotonic_time();
575
                /* Cleaning up and removing the session is done in a lazy way */
576
                old_sessions = g_list_append(old_sessions, session);
577
        }
578
        janus_mutex_unlock(&sessions_mutex);
579
        return;
580
}
581

    
582
char *janus_textroom_query_session(janus_plugin_session *handle) {
583
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
584
                return NULL;
585
        }
586
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
587
        if(!session) {
588
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
589
                return NULL;
590
        }
591
        /* TODO Return meaningful info: participant details, rooms they're in, etc. */
592
        json_t *info = json_object();
593
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
594
        char *info_text = json_dumps(info, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
595
        json_decref(info);
596
        return info_text;
597
}
598

    
599
struct janus_plugin_result *janus_textroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
600
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
601
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized");
602
        janus_textroom_message *msg = g_malloc0(sizeof(janus_textroom_message));
603
        msg->handle = handle;
604
        msg->transaction = transaction;
605
        msg->message = message;
606
        msg->sdp_type = sdp_type;
607
        msg->sdp = sdp;
608
        g_async_queue_push(messages, msg);
609

    
610
        /* All the requests to this plugin are handled asynchronously */
611
        return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, "I'm taking my time!");
612
}
613

    
614
void janus_textroom_setup_media(janus_plugin_session *handle) {
615
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
616
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
617
                return;
618
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
619
        if(!session) {
620
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
621
                return;
622
        }
623
        if(session->destroyed)
624
                return;
625
        g_atomic_int_set(&session->hangingup, 0);
626
}
627

    
628
void janus_textroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
629
        /* We don't do audio/video */
630
}
631

    
632
void janus_textroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
633
        /* We don't do audio/video */
634
}
635

    
636
void janus_textroom_incoming_data(janus_plugin_session *handle, char *buf, int len) {
637
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
638
                return;
639
        /* Incoming request from this user: what should we do? */
640
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
641
        if(!session) {
642
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
643
                return;
644
        }
645
        if(session->destroyed)
646
                return;
647
        if(buf == NULL || len <= 0)
648
                return;
649
        char *text = g_malloc0(len+1);
650
        memcpy(text, buf, len);
651
        *(text+len) = '\0';
652
        JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes): %s\n", strlen(text), text);
653
        janus_textroom_handle_incoming_request(handle, text, FALSE);
654
}
655

    
656
/* Helper method to handle incoming messages from the data channel */
657
void janus_textroom_handle_incoming_request(janus_plugin_session *handle, char *text, gboolean internal) {
658
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
659
        /* Parse JSON */
660
        json_error_t error;
661
        json_t *root = json_loads(text, 0, &error);
662
        g_free(text);
663
        if(!root) {
664
                JANUS_LOG(LOG_ERR, "Error parsing data channel message (JSON error: on line %d: %s)\n", error.line, error.text);
665
                return;
666
        }
667
        /* Handle request */
668
        int error_code = 0;
669
        char error_cause[512];
670
        JANUS_VALIDATE_JSON_OBJECT(root, transaction_parameters,
671
                error_code, error_cause, TRUE,
672
                JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
673
        const char *transaction_text = NULL;
674
        if(error_code != 0)
675
                goto error;
676
        json_t *request = json_object_get(root, "textroom");
677
        json_t *transaction = json_object_get(root, "transaction");
678
        const char *request_text = json_string_value(request);
679
        transaction_text = json_string_value(transaction);
680
        if(!strcasecmp(request_text, "message")) {
681
                JANUS_VALIDATE_JSON_OBJECT(root, message_parameters,
682
                        error_code, error_cause, TRUE,
683
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
684
                if(error_code != 0)
685
                        goto error;
686
                json_t *room = json_object_get(root, "room");
687
                guint64 room_id = json_integer_value(room);
688
                janus_mutex_lock(&rooms_mutex);
689
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
690
                if(textroom == NULL) {
691
                        janus_mutex_unlock(&rooms_mutex);
692
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
693
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
694
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
695
                        goto error;
696
                }
697
                janus_mutex_lock(&textroom->mutex);
698
                janus_mutex_unlock(&rooms_mutex);
699
                janus_textroom_participant *participant = g_hash_table_lookup(session->rooms, &room_id);
700
                if(participant == NULL) {
701
                        janus_mutex_unlock(&textroom->mutex);
702
                        JANUS_LOG(LOG_ERR, "Not in room %"SCNu64"\n", room_id);
703
                        error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM;
704
                        g_snprintf(error_cause, 512, "Not in room %"SCNu64, room_id);
705
                        goto error;
706
                }
707
                json_t *username = json_object_get(root, "to");
708
                json_t *usernames = json_object_get(root, "tos");
709
                if(username && usernames) {
710
                        janus_mutex_unlock(&textroom->mutex);
711
                        JANUS_LOG(LOG_ERR, "Both to and tos array provided\n");
712
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
713
                        g_snprintf(error_cause, 512, "Both to and tos array provided");
714
                        goto error;
715
                }
716
                json_t *text = json_object_get(root, "text");
717
                const char *message = json_string_value(text);
718
                /* Prepare outgoing message */
719
                json_t *msg = json_object();
720
                json_object_set_new(msg, "textroom", json_string("message"));
721
                json_object_set_new(msg, "room", json_integer(room_id));
722
                json_object_set_new(msg, "from", json_string(participant->username));
723
                time_t timer;
724
                time(&timer);
725
                struct tm *tm_info = localtime(&timer);
726
                char msgTime[64];
727
                strftime(msgTime, sizeof(msgTime), "%FT%T%z", tm_info);
728
                json_object_set_new(msg, "date", json_string(msgTime));
729
                json_object_set_new(msg, "text", json_string(message));
730
                if(username || usernames)
731
                        json_object_set_new(msg, "whisper", json_true());
732
                char *msg_text = json_dumps(msg, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
733
                json_decref(msg);
734
                /* Start preparing the response too */
735
                json_t *reply = json_object();
736
                json_object_set_new(reply, "textroom", json_string("success"));
737
                json_object_set_new(reply, "transaction", json_string(transaction_text));
738
                /* Who should we send this message to? */
739
                if(username) {
740
                        /* A single user */
741
                        json_t *sent = json_object();
742
                        const char *to = json_string_value(username);
743
                        JANUS_LOG(LOG_VERB, "To %s in %"SCNu64": %s\n", to, room_id, message);
744
                        janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to);
745
                        if(top) {
746
                                gateway->relay_data(top->session->handle, msg_text, strlen(msg_text));
747
                                json_object_set_new(sent, to, json_true());
748
                        } else {
749
                                JANUS_LOG(LOG_WARN, "User %s is not in room %"SCNu64", failed to send message\n", to, room_id);
750
                                json_object_set_new(sent, to, json_false());
751
                        }
752
                        json_object_set_new(reply, "sent", sent);
753
                } else if(usernames) {
754
                        /* A limited number of users */
755
                        json_t *sent = json_object();
756
                        size_t i = 0;
757
                        for(i=0; i<json_array_size(usernames); i++) {
758
                                json_t *u = json_array_get(usernames, i);
759
                                const char *to = json_string_value(u);
760
                                JANUS_LOG(LOG_VERB, "To %s in %"SCNu64": %s\n", to, room_id, message);
761
                                janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to);
762
                                if(top) {
763
                                        gateway->relay_data(top->session->handle, msg_text, strlen(msg_text));
764
                                        json_object_set_new(sent, to, json_true());
765
                                } else {
766
                                        JANUS_LOG(LOG_WARN, "User %s is not in room %"SCNu64", failed to send message\n", to, room_id);
767
                                        json_object_set_new(sent, to, json_false());
768
                                }
769
                        }
770
                        json_object_set_new(reply, "sent", sent);
771
                } else {
772
                        /* Everybody in the room */
773
                        JANUS_LOG(LOG_VERB, "To everybody in %"SCNu64": %s\n", room_id, message);
774
                        if(textroom->participants) {
775
                                GHashTableIter iter;
776
                                gpointer value;
777
                                g_hash_table_iter_init(&iter, textroom->participants);
778
                                while(g_hash_table_iter_next(&iter, NULL, &value)) {
779
                                        janus_textroom_participant *top = value;
780
                                        JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64": %s\n", top->username, room_id, message);
781
                                        gateway->relay_data(top->session->handle, msg_text, strlen(msg_text));
782
                                }
783
                        }
784
#ifdef HAVE_LIBCURL
785
                        /* Is there a backend waiting for this message too? */
786
                        if(textroom->http_backend) {
787
                                /* Prepare the libcurl context */
788
                                CURLcode res;
789
                                CURL *curl = curl_easy_init();
790
                                if(curl == NULL) {
791
                                        JANUS_LOG(LOG_ERR, "Error initializing CURL context\n");
792
                                } else {
793
                                        curl_easy_setopt(curl, CURLOPT_URL, textroom->http_backend);
794
                                        struct curl_slist *headers = NULL;
795
                                        headers = curl_slist_append(headers, "Accept: application/json");
796
                                        headers = curl_slist_append(headers, "Content-Type: application/json");
797
                                        headers = curl_slist_append(headers, "charsets: utf-8");
798
                                        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
799
                                        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msg_text);
800
                                        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, janus_textroom_write_data);
801
                                        /* Send the request */
802
                                        res = curl_easy_perform(curl);
803
                                        if(res != CURLE_OK) {
804
                                                JANUS_LOG(LOG_ERR, "Couldn't relay event to the backend: %s\n", curl_easy_strerror(res));
805
                                        } else {
806
                                                JANUS_LOG(LOG_DBG, "Event sent!\n");
807
                                        }
808
                                }
809
                        }
810
#endif
811
                }
812
                g_free(msg_text);
813
                janus_mutex_unlock(&textroom->mutex);
814
                /* By default we send a confirmation back to the user that sent this message:
815
                 * if the user passed an ack=false, though, we don't do that */
816
                json_t *ack = json_object_get(root, "ack");
817
                if(!internal && (ack == NULL || json_is_true(ack))) {
818
                        /* Send response back */
819
                        char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
820
                        gateway->relay_data(handle, reply_text, strlen(reply_text));
821
                        g_free(reply_text);
822
                }
823
                json_decref(reply);
824
        } else if(!strcasecmp(request_text, "join")) {
825
                JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
826
                        error_code, error_cause, TRUE,
827
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
828
                if(error_code != 0)
829
                        goto error;
830
                json_t *room = json_object_get(root, "room");
831
                guint64 room_id = json_integer_value(room);
832
                janus_mutex_lock(&rooms_mutex);
833
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
834
                if(textroom == NULL) {
835
                        janus_mutex_unlock(&rooms_mutex);
836
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
837
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
838
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
839
                        goto error;
840
                }
841
                janus_mutex_lock(&textroom->mutex);
842
                janus_mutex_unlock(&rooms_mutex);
843
                janus_mutex_lock(&session->mutex);
844
                if(g_hash_table_lookup(session->rooms, &room_id) != NULL) {
845
                        janus_mutex_unlock(&session->mutex);
846
                        janus_mutex_unlock(&textroom->mutex);
847
                        JANUS_LOG(LOG_ERR, "Already in room %"SCNu64"\n", room_id);
848
                        error_code = JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM;
849
                        g_snprintf(error_cause, 512, "Already in room %"SCNu64, room_id);
850
                        goto error;
851
                }
852
                json_t *username = json_object_get(root, "username");
853
                const char *username_text = json_string_value(username);
854
                janus_textroom_participant *participant = g_hash_table_lookup(textroom->participants, username_text);
855
                if(participant != NULL) {
856
                        janus_mutex_unlock(&session->mutex);
857
                        janus_mutex_unlock(&textroom->mutex);
858
                        JANUS_LOG(LOG_ERR, "Username already taken\n");
859
                        error_code = JANUS_TEXTROOM_ERROR_USERNAME_EXISTS;
860
                        g_snprintf(error_cause, 512, "Username already taken");
861
                        goto error;
862
                }
863
                json_t *display = json_object_get(root, "display");
864
                const char *display_text = json_string_value(display);
865
                /* Create a participant instance */
866
                participant = g_malloc0(sizeof(janus_textroom_participant));
867
                participant->session = session;
868
                participant->room = textroom;
869
                participant->username = g_strdup(username_text);
870
                participant->display = display_text ? g_strdup(display_text) : NULL;
871
                participant->destroyed = 0;
872
                janus_mutex_init(&participant->mutex);
873
                g_hash_table_insert(session->rooms, janus_uint64_dup(textroom->room_id), participant);
874
                g_hash_table_insert(textroom->participants, participant->username, participant);
875
                /* Notify all participants */
876
                JANUS_LOG(LOG_VERB, "Notifying all participants about the new join\n");
877
                json_t *list = json_array();
878
                if(textroom->participants) {
879
                        /* Prepare event */
880
                        json_t *event = json_object();
881
                        json_object_set_new(event, "textroom", json_string("join"));
882
                        json_object_set_new(event, "room", json_integer(textroom->room_id));
883
                        json_object_set_new(event, "username", json_string(username_text));
884
                        if(display_text != NULL)
885
                                json_object_set_new(event, "display", json_string(display_text));
886
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
887
                        json_decref(event);
888
                        gateway->relay_data(handle, event_text, strlen(event_text));
889
                        /* Broadcast */
890
                        GHashTableIter iter;
891
                        gpointer value;
892
                        g_hash_table_iter_init(&iter, textroom->participants);
893
                        while(g_hash_table_iter_next(&iter, NULL, &value)) {
894
                                janus_textroom_participant *top = value;
895
                                if(top == participant)
896
                                        continue;        /* Skip us */
897
                                JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64"\n", top->username, room_id);
898
                                gateway->relay_data(top->session->handle, event_text, strlen(event_text));
899
                                /* Take note of this user */
900
                                json_t *p = json_object();
901
                                json_object_set_new(p, "username", json_string(top->username));
902
                                if(top->display != NULL)
903
                                        json_object_set_new(p, "display", json_string(top->display));
904
                                json_array_append_new(list, p);
905
                        }
906
                        g_free(event_text);
907
                }
908
                janus_mutex_unlock(&session->mutex);
909
                janus_mutex_unlock(&textroom->mutex);
910
                if(!internal) {
911
                        /* Send response back */
912
                        json_t *reply = json_object();
913
                        json_object_set_new(reply, "textroom", json_string("success"));
914
                        json_object_set_new(reply, "transaction", json_string(transaction_text));
915
                        json_object_set_new(reply, "participants", list);
916
                        char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
917
                        json_decref(reply);
918
                        gateway->relay_data(handle, reply_text, strlen(reply_text));
919
                        g_free(reply_text);
920
                }
921
                /* Also notify event handlers */
922
                if(notify_events && gateway->events_is_enabled()) {
923
                        json_t *info = json_object();
924
                        json_object_set_new(info, "event", json_string("join"));
925
                        json_object_set_new(info, "room", json_integer(room_id));
926
                        json_object_set_new(info, "username", json_string(username_text));
927
                        if(display_text)
928
                                json_object_set_new(info, "display", json_string(display_text));
929
                        gateway->notify_event(handle, info);
930
                }
931
        } else if(!strcasecmp(request_text, "leave")) {
932
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
933
                        error_code, error_cause, TRUE,
934
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
935
                if(error_code != 0)
936
                        goto error;
937
                json_t *room = json_object_get(root, "room");
938
                guint64 room_id = json_integer_value(room);
939
                janus_mutex_lock(&rooms_mutex);
940
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
941
                if(textroom == NULL) {
942
                        janus_mutex_unlock(&rooms_mutex);
943
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
944
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
945
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
946
                        goto error;
947
                }
948
                janus_mutex_lock(&textroom->mutex);
949
                janus_mutex_unlock(&rooms_mutex);
950
                janus_mutex_lock(&session->mutex);
951
                janus_textroom_participant *participant = g_hash_table_lookup(session->rooms, &room_id);
952
                if(participant == NULL) {
953
                        janus_mutex_unlock(&session->mutex);
954
                        janus_mutex_unlock(&textroom->mutex);
955
                        JANUS_LOG(LOG_ERR, "Not in room %"SCNu64"\n", room_id);
956
                        error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM;
957
                        g_snprintf(error_cause, 512, "Not in room %"SCNu64, room_id);
958
                        goto error;
959
                }
960
                g_hash_table_remove(session->rooms, &room_id);
961
                g_hash_table_remove(textroom->participants, participant->username);
962
                participant->session = NULL;
963
                participant->room = NULL;
964
                /* Notify all participants */
965
                JANUS_LOG(LOG_VERB, "Notifying all participants about the new leave\n");
966
                if(textroom->participants) {
967
                        /* Prepare event */
968
                        json_t *event = json_object();
969
                        json_object_set_new(event, "textroom", json_string("leave"));
970
                        json_object_set_new(event, "room", json_integer(textroom->room_id));
971
                        json_object_set_new(event, "username", json_string(participant->username));
972
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
973
                        json_decref(event);
974
                        gateway->relay_data(handle, event_text, strlen(event_text));
975
                        /* Broadcast */
976
                        GHashTableIter iter;
977
                        gpointer value;
978
                        g_hash_table_iter_init(&iter, textroom->participants);
979
                        while(g_hash_table_iter_next(&iter, NULL, &value)) {
980
                                janus_textroom_participant *top = value;
981
                                if(top == participant)
982
                                        continue;        /* Skip us */
983
                                JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64"\n", top->username, room_id);
984
                                gateway->relay_data(top->session->handle, event_text, strlen(event_text));
985
                        }
986
                        g_free(event_text);
987
                }
988
                /* Also notify event handlers */
989
                if(notify_events && gateway->events_is_enabled()) {
990
                        json_t *info = json_object();
991
                        json_object_set_new(info, "event", json_string("leave"));
992
                        json_object_set_new(info, "room", json_integer(room_id));
993
                        json_object_set_new(info, "username", json_string(participant->username));
994
                        gateway->notify_event(handle, info);
995
                }
996
                g_free(participant->username);
997
                g_free(participant->display);
998
                g_free(participant);
999
                janus_mutex_unlock(&session->mutex);
1000
                janus_mutex_unlock(&textroom->mutex);
1001
                if(!internal) {
1002
                        /* Send response back */
1003
                        json_t *reply = json_object();
1004
                        json_object_set_new(reply, "textroom", json_string("success"));
1005
                        json_object_set_new(reply, "transaction", json_string(transaction_text));
1006
                        char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1007
                        json_decref(reply);
1008
                        gateway->relay_data(handle, reply_text, strlen(reply_text));
1009
                        g_free(reply_text);
1010
                }
1011
        } else if(!strcasecmp(request_text, "list")) {
1012
                /* List all rooms (but private ones) and their details (except for the secret, of course...) */
1013
                json_t *list = json_array();
1014
                JANUS_LOG(LOG_VERB, "Request for the list for all video rooms\n");
1015
                janus_mutex_lock(&rooms_mutex);
1016
                GHashTableIter iter;
1017
                gpointer value;
1018
                g_hash_table_iter_init(&iter, rooms);
1019
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1020
                        janus_textroom_room *room = value;
1021
                        if(!room)
1022
                                continue;
1023
                        janus_mutex_lock(&room->mutex);
1024
                        if(room->is_private) {
1025
                                /* Skip private room */
1026
                                JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
1027
                                janus_mutex_unlock(&room->mutex);
1028
                                continue;
1029
                        }
1030
                        json_t *rl = json_object();
1031
                        json_object_set_new(rl, "room", json_integer(room->room_id));
1032
                        json_object_set_new(rl, "description", json_string(room->room_name));
1033
                        /* TODO: Possibly list participant details... or make it a separate API call for a specific room */
1034
                        json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
1035
                        json_array_append_new(list, rl);
1036
                        janus_mutex_unlock(&room->mutex);
1037
                }
1038
                janus_mutex_unlock(&rooms_mutex);
1039
                if(!internal) {
1040
                        /* Send response back */
1041
                        json_t *reply = json_object();
1042
                        json_object_set_new(reply, "textroom", json_string("success"));
1043
                        json_object_set_new(reply, "transaction", json_string(transaction_text));
1044
                        json_object_set_new(reply, "list", list);
1045
                        char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1046
                        json_decref(reply);
1047
                        gateway->relay_data(handle, reply_text, strlen(reply_text));
1048
                        g_free(reply_text);
1049
                }
1050
        } else if(!strcasecmp(request_text, "create")) {
1051
                JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
1052
                        error_code, error_cause, TRUE,
1053
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1054
                if(error_code != 0)
1055
                        goto error;
1056
                if(admin_key != NULL) {
1057
                        /* An admin key was specified: make sure it was provided, and that it's valid */
1058
                        JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
1059
                                error_code, error_cause, TRUE,
1060
                                JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1061
                        if(error_code != 0)
1062
                                goto error;
1063
                        JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
1064
                                JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1065
                        if(error_code != 0)
1066
                                goto error;
1067
                }
1068
                json_t *room = json_object_get(root, "room");
1069
                json_t *desc = json_object_get(root, "description");
1070
                json_t *is_private = json_object_get(root, "is_private");
1071
                json_t *secret = json_object_get(root, "secret");
1072
                json_t *pin = json_object_get(root, "pin");
1073
                json_t *post = json_object_get(root, "post");
1074
                json_t *permanent = json_object_get(root, "permanent");
1075
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1076
                if(save && config == NULL) {
1077
                        JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
1078
                        error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1079
                        g_snprintf(error_cause, 512, "No configuration file, can't create permanent room");
1080
                        goto error;
1081
                }
1082
                guint64 room_id = 0;
1083
                room_id = json_integer_value(room);
1084
                if(room_id == 0) {
1085
                        JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
1086
                }
1087
                janus_mutex_lock(&rooms_mutex);
1088
                if(room_id > 0) {
1089
                        /* Let's make sure the room doesn't exist already */
1090
                        if(g_hash_table_lookup(rooms, &room_id) != NULL) {
1091
                                /* It does... */
1092
                                janus_mutex_unlock(&rooms_mutex);
1093
                                JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id);
1094
                                error_code = JANUS_TEXTROOM_ERROR_ROOM_EXISTS;
1095
                                g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id);
1096
                                goto error;
1097
                        }
1098
                }
1099
                /* Create the text room */
1100
                janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
1101
                /* Generate a random ID */
1102
                if(room_id == 0) {
1103
                        while(room_id == 0) {
1104
                                room_id = janus_random_uint64();
1105
                                if(g_hash_table_lookup(rooms, &room_id) != NULL) {
1106
                                        /* Room ID already taken, try another one */
1107
                                        room_id = 0;
1108
                                }
1109
                        }
1110
                }
1111
                textroom->room_id = room_id;
1112
                char *description = NULL;
1113
                if(desc != NULL && strlen(json_string_value(desc)) > 0) {
1114
                        description = g_strdup(json_string_value(desc));
1115
                } else {
1116
                        char roomname[255];
1117
                        g_snprintf(roomname, 255, "Room %"SCNu64"", textroom->room_id);
1118
                        description = g_strdup(roomname);
1119
                }
1120
                textroom->room_name = description;
1121
                textroom->is_private = is_private ? json_is_true(is_private) : FALSE;
1122
                if(secret)
1123
                        textroom->room_secret = g_strdup(json_string_value(secret));
1124
                if(pin)
1125
                        textroom->room_pin = g_strdup(json_string_value(pin));
1126
                if(post) {
1127
#ifdef HAVE_LIBCURL
1128
                        /* FIXME Should we check if this is a valid HTTP address? */
1129
                        textroom->http_backend = g_strdup(json_string_value(post));
1130
#else
1131
                        JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
1132
#endif
1133
                }
1134
                textroom->participants = g_hash_table_new(g_str_hash, g_str_equal);
1135
                textroom->destroyed = 0;
1136
                janus_mutex_init(&textroom->mutex);
1137
                g_hash_table_insert(rooms, janus_uint64_dup(textroom->room_id), textroom);
1138
                JANUS_LOG(LOG_VERB, "Created textroom: %"SCNu64" (%s, %s, secret: %s, pin: %s)\n",
1139
                        textroom->room_id, textroom->room_name,
1140
                        textroom->is_private ? "private" : "public",
1141
                        textroom->room_secret ? textroom->room_secret : "no secret",
1142
                        textroom->room_pin ? textroom->room_pin : "no pin");
1143
                if(save) {
1144
                        /* This room is permanent: save to the configuration file too
1145
                         * FIXME: We should check if anything fails... */
1146
                        JANUS_LOG(LOG_VERB, "Saving room %"SCNu64" permanently in config file\n", textroom->room_id);
1147
                        janus_mutex_lock(&config_mutex);
1148
                        char cat[BUFSIZ];
1149
                        /* The room ID is the category */
1150
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, textroom->room_id);
1151
                        janus_config_add_category(config, cat);
1152
                        /* Now for the values */
1153
                        janus_config_add_item(config, cat, "description", textroom->room_name);
1154
                        if(textroom->is_private)
1155
                                janus_config_add_item(config, cat, "is_private", "yes");
1156
                        if(textroom->room_secret)
1157
                                janus_config_add_item(config, cat, "secret", textroom->room_secret);
1158
                        if(textroom->room_pin)
1159
                                janus_config_add_item(config, cat, "pin", textroom->room_pin);
1160
                        if(textroom->http_backend)
1161
                                janus_config_add_item(config, cat, "post", textroom->http_backend);
1162
                        /* Save modified configuration */
1163
                        janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE);
1164
                        janus_mutex_unlock(&config_mutex);
1165
                }
1166
                /* Show updated rooms list */
1167
                GHashTableIter iter;
1168
                gpointer value;
1169
                g_hash_table_iter_init(&iter, rooms);
1170
                while (g_hash_table_iter_next(&iter, NULL, &value)) {
1171
                        janus_textroom_room *tr = value;
1172
                        JANUS_LOG(LOG_VERB, "  ::: [%"SCNu64"][%s]\n", tr->room_id, tr->room_name);
1173
                }
1174
                janus_mutex_unlock(&rooms_mutex);
1175
                if(!internal) {
1176
                        /* Send response back */
1177
                        json_t *reply = json_object();
1178
                        json_object_set_new(reply, "textroom", json_string("success"));
1179
                        json_object_set_new(reply, "transaction", json_string(transaction_text));
1180
                        json_object_set_new(reply, "room", json_integer(textroom->room_id));
1181
                        char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1182
                        json_decref(reply);
1183
                        gateway->relay_data(handle, reply_text, strlen(reply_text));
1184
                        g_free(reply_text);
1185
                }
1186
                /* Also notify event handlers */
1187
                if(notify_events && gateway->events_is_enabled()) {
1188
                        json_t *info = json_object();
1189
                        json_object_set_new(info, "event", json_string("created"));
1190
                        json_object_set_new(info, "room", json_integer(room_id));
1191
                        gateway->notify_event(handle, info);
1192
                }
1193
        } else if(!strcasecmp(request_text, "exists")) {
1194
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1195
                        error_code, error_cause, TRUE,
1196
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1197
                if(error_code != 0)
1198
                        goto error;
1199
                json_t *room = json_object_get(root, "room");
1200
                guint64 room_id = json_integer_value(room);
1201
                janus_mutex_lock(&rooms_mutex);
1202
                gboolean room_exists = g_hash_table_contains(rooms, &room_id);
1203
                janus_mutex_unlock(&rooms_mutex);
1204
                if(!internal) {
1205
                        /* Send response back */
1206
                        json_t *reply = json_object();
1207
                        json_object_set_new(reply, "textroom", json_string("success"));
1208
                        json_object_set_new(reply, "room", json_integer(room_id));
1209
                        json_object_set_new(reply, "exists", room_exists ? json_true() : json_false());
1210
                        json_object_set_new(reply, "transaction", json_string(transaction_text));
1211
                        char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1212
                        json_decref(reply);
1213
                        gateway->relay_data(handle, reply_text, strlen(reply_text));
1214
                        g_free(reply_text);
1215
                }
1216
        } else if(!strcasecmp(request_text, "destroy")) {
1217
                JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1218
                        error_code, error_cause, TRUE,
1219
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1220
                if(error_code != 0)
1221
                        goto error;
1222
                json_t *room = json_object_get(root, "room");
1223
                json_t *permanent = json_object_get(root, "permanent");
1224
                gboolean save = permanent ? json_is_true(permanent) : FALSE;
1225
                if(save && config == NULL) {
1226
                        JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
1227
                        error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1228
                        g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently");
1229
                        goto error;
1230
                }
1231
                guint64 room_id = json_integer_value(room);
1232
                janus_mutex_lock(&rooms_mutex);
1233
                janus_textroom_room *textroom = g_hash_table_lookup(rooms, &room_id);
1234
                if(textroom == NULL) {
1235
                        janus_mutex_unlock(&rooms_mutex);
1236
                        JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
1237
                        error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1238
                        g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
1239
                        goto error;
1240
                }
1241
                janus_mutex_lock(&textroom->mutex);
1242
                /* A secret may be required for this action */
1243
                JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
1244
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1245
                if(error_code != 0) {
1246
                        janus_mutex_unlock(&textroom->mutex);
1247
                        janus_mutex_unlock(&rooms_mutex);
1248
                        goto error;
1249
                }
1250
                /* Remove room */
1251
                g_hash_table_remove(rooms, &room_id);
1252
                if(save) {
1253
                        /* This change is permanent: save to the configuration file too
1254
                         * FIXME: We should check if anything fails... */
1255
                        JANUS_LOG(LOG_VERB, "Destroying room %"SCNu64" permanently in config file\n", room_id);
1256
                        janus_mutex_lock(&config_mutex);
1257
                        char cat[BUFSIZ];
1258
                        /* The room ID is the category */
1259
                        g_snprintf(cat, BUFSIZ, "%"SCNu64, room_id);
1260
                        janus_config_remove_category(config, cat);
1261
                        /* Save modified configuration */
1262
                        janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE);
1263
                        janus_mutex_unlock(&config_mutex);
1264
                }
1265
                /* Notify all participants */
1266
                JANUS_LOG(LOG_VERB, "Notifying all participants about the destroy\n");
1267
                if(textroom->participants) {
1268
                        /* Prepare event */
1269
                        json_t *event = json_object();
1270
                        json_object_set_new(event, "textroom", json_string("destroyed"));
1271
                        json_object_set_new(event, "room", json_integer(textroom->room_id));
1272
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1273
                        json_decref(event);
1274
                        gateway->relay_data(handle, event_text, strlen(event_text));
1275
                        /* Broadcast */
1276
                        GHashTableIter iter;
1277
                        gpointer value;
1278
                        g_hash_table_iter_init(&iter, textroom->participants);
1279
                        while(g_hash_table_iter_next(&iter, NULL, &value)) {
1280
                                janus_textroom_participant *top = value;
1281
                                JANUS_LOG(LOG_VERB, "  >> To %s in %"SCNu64"\n", top->username, room_id);
1282
                                gateway->relay_data(top->session->handle, event_text, strlen(event_text));
1283
                                janus_mutex_unlock(&top->session->mutex);
1284
                                g_hash_table_remove(top->session->rooms, &room_id);
1285
                                janus_mutex_unlock(&top->session->mutex);
1286
                                g_free(top->username);
1287
                                g_free(top->display);
1288
                                g_free(top);
1289
                        }
1290
                        g_free(event_text);
1291
                }
1292
                janus_mutex_unlock(&textroom->mutex);
1293
                janus_mutex_unlock(&rooms_mutex);
1294
                if(!internal) {
1295
                        /* Send response back */
1296
                        json_t *reply = json_object();
1297
                        json_object_set_new(reply, "textroom", json_string("success"));
1298
                        json_object_set_new(reply, "transaction", json_string(transaction_text));
1299
                        char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1300
                        json_decref(reply);
1301
                        gateway->relay_data(handle, reply_text, strlen(reply_text));
1302
                        g_free(reply_text);
1303
                }
1304
                /* We'll let the watchdog worry about freeing resources */
1305
                old_rooms = g_list_append(old_rooms, textroom);
1306
                /* Also notify event handlers */
1307
                if(notify_events && gateway->events_is_enabled()) {
1308
                        json_t *info = json_object();
1309
                        json_object_set_new(info, "event", json_string("destroyed"));
1310
                        json_object_set_new(info, "room", json_integer(room_id));
1311
                        gateway->notify_event(handle, info);
1312
                }
1313
        } else {
1314
                JANUS_LOG(LOG_ERR, "Unsupported request %s\n", request_text);
1315
                error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
1316
                g_snprintf(error_cause, 512, "Unsupported request %s", request_text);
1317
                goto error;
1318
        }
1319

    
1320
        json_decref(root);
1321
        return;
1322

    
1323
error:
1324
                {
1325
                        if(!internal) {
1326
                                /* Prepare JSON error response */
1327
                                json_t *reply = json_object();
1328
                                json_object_set_new(reply, "textroom", json_string("error"));
1329
                                if(transaction_text)
1330
                                        json_object_set_new(reply, "transaction", json_string(transaction_text));
1331
                                json_object_set_new(reply, "error_code", json_integer(error_code));
1332
                                json_object_set_new(reply, "error", json_string(error_cause));
1333
                                char *reply_text = json_dumps(reply, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1334
                                json_decref(reply);
1335
                                gateway->relay_data(handle, reply_text, strlen(reply_text));
1336
                                g_free(reply_text);
1337
                        }
1338
                        if(root != NULL)
1339
                                json_decref(root);
1340
                }
1341
}
1342

    
1343
void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
1344
        /* We don't do audio/video */
1345
}
1346

    
1347
void janus_textroom_hangup_media(janus_plugin_session *handle) {
1348
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
1349
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1350
                return;
1351
        janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
1352
        if(!session) {
1353
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1354
                return;
1355
        }
1356
        if(session->destroyed)
1357
                return;
1358
        if(g_atomic_int_add(&session->hangingup, 1))
1359
                return;
1360
        /* Get rid of all participants */
1361
        janus_mutex_lock(&session->mutex);
1362
        GList *list = NULL;
1363
        if(session->rooms) {
1364
                GHashTableIter iter;
1365
                gpointer value;
1366
                g_hash_table_iter_init(&iter, session->rooms);
1367
                while(g_hash_table_iter_next(&iter, NULL, &value)) {
1368
                        janus_textroom_participant *p = value;
1369
                        janus_mutex_lock(&p->mutex);
1370
                        if(p->room)
1371
                                list = g_list_append(list, janus_uint64_dup(p->room->room_id));
1372
                        janus_mutex_unlock(&p->mutex);
1373
                }
1374
                janus_mutex_unlock(&rooms_mutex);
1375
        }
1376
        janus_mutex_unlock(&session->mutex);
1377
        JANUS_LOG(LOG_VERB, "Leaving %d rooms\n", g_list_length(list));
1378
        char request[100];
1379
        GList *first = list;
1380
        while(list) {
1381
                guint64 room_id = *((guint64 *)list->data);
1382
                g_snprintf(request, sizeof(request), "{\"textroom\":\"leave\",\"transaction\":\"internal\",\"room\":%"SCNu64"}", room_id);
1383
                janus_textroom_handle_incoming_request(handle, g_strdup(request), TRUE);
1384
                list = list->next;
1385
        }
1386
        g_list_free_full(first, (GDestroyNotify)g_free);
1387
}
1388

    
1389
/* Thread to handle incoming messages */
1390
static void *janus_textroom_handler(void *data) {
1391
        JANUS_LOG(LOG_VERB, "Joining TextRoom handler thread\n");
1392
        janus_textroom_message *msg = NULL;
1393
        int error_code = 0;
1394
        char *error_cause = g_malloc0(512);
1395
        json_t *root = NULL;
1396
        gboolean do_offer = FALSE;
1397
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
1398
                msg = g_async_queue_pop(messages);
1399
                if(msg == NULL)
1400
                        continue;
1401
                if(msg == &exit_message)
1402
                        break;
1403
                if(msg->handle == NULL) {
1404
                        janus_textroom_message_free(msg);
1405
                        continue;
1406
                }
1407
                janus_textroom_session *session = NULL;
1408
                janus_mutex_lock(&sessions_mutex);
1409
                if(g_hash_table_lookup(sessions, msg->handle) != NULL ) {
1410
                        session = (janus_textroom_session *)msg->handle->plugin_handle;
1411
                }
1412
                janus_mutex_unlock(&sessions_mutex);
1413
                if(!session) {
1414
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1415
                        janus_textroom_message_free(msg);
1416
                        continue;
1417
                }
1418
                if(session->destroyed) {
1419
                        janus_textroom_message_free(msg);
1420
                        continue;
1421
                }
1422
                /* Handle request */
1423
                error_code = 0;
1424
                root = NULL;
1425
                JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message);
1426
                if(msg->message == NULL) {
1427
                        JANUS_LOG(LOG_ERR, "No message??\n");
1428
                        error_code = JANUS_TEXTROOM_ERROR_NO_MESSAGE;
1429
                        g_snprintf(error_cause, 512, "%s", "No message??");
1430
                        goto error;
1431
                }
1432
                json_error_t error;
1433
                root = json_loads(msg->message, 0, &error);
1434
                if(!root) {
1435
                        JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
1436
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
1437
                        g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text);
1438
                        goto error;
1439
                }
1440
                if(!json_is_object(root)) {
1441
                        JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
1442
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
1443
                        g_snprintf(error_cause, 512, "JSON error: not an object");
1444
                        goto error;
1445
                }
1446
                /* Parse request */
1447
                JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
1448
                        error_code, error_cause, TRUE,
1449
                        JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1450
                if(error_code != 0)
1451
                        goto error;
1452
                json_t *request = json_object_get(root, "request");
1453
                const char *request_text = json_string_value(request);
1454
                if(!strcasecmp(request_text, "setup")) {
1455
                        if(!g_atomic_int_compare_and_exchange(&session->setup, 0, 1)) {
1456
                                JANUS_LOG(LOG_ERR, "PeerConnection already setup\n");
1457
                                error_code = JANUS_TEXTROOM_ERROR_ALREADY_SETUP;
1458
                                g_snprintf(error_cause, 512, "PeerConnection already setup");
1459
                                goto error;
1460
                        }
1461
                        do_offer = TRUE;
1462
                } else if(!strcasecmp(request_text, "ack")) {
1463
                        /* The peer send their answer back: do nothing */
1464
                        do_offer = FALSE;
1465
                } else {
1466
                        JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
1467
                        error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
1468
                        g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1469
                        goto error;
1470
                }
1471

    
1472
                json_decref(root);
1473
                /* Prepare JSON event */
1474
                json_t *event = json_object();
1475
                json_object_set_new(event, "textroom", json_string("event"));
1476
                json_object_set_new(event, "result", json_string("ok"));
1477
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1478
                json_decref(event);
1479
                JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
1480
                if(!do_offer) {
1481
                        int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event_text, NULL, NULL);
1482
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1483
                } else {
1484
                        /* Send an offer */
1485
                        char sdp[500];
1486
                        g_snprintf(sdp, sizeof(sdp), sdp_template,
1487
                                janus_get_real_time(),                        /* We need current time here */
1488
                                janus_get_real_time());                        /* We need current time here */
1489
                        /* How long will the gateway take to push the event? */
1490
                        g_atomic_int_set(&session->hangingup, 0);
1491
                        gint64 start = janus_get_monotonic_time();
1492
                        int res = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event_text, "offer", sdp);
1493
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n",
1494
                                res, janus_get_monotonic_time()-start);
1495
                }
1496
                g_free(event_text);
1497
                janus_textroom_message_free(msg);
1498
                continue;
1499

    
1500
error:
1501
                {
1502
                        if(root != NULL)
1503
                                json_decref(root);
1504
                        /* Prepare JSON error event */
1505
                        json_t *event = json_object();
1506
                        json_object_set_new(event, "textroom", json_string("error"));
1507
                        json_object_set_new(event, "error_code", json_integer(error_code));
1508
                        json_object_set_new(event, "error", json_string(error_cause));
1509
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
1510
                        json_decref(event);
1511
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
1512
                        int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event_text, NULL, NULL);
1513
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
1514
                        g_free(event_text);
1515
                        janus_textroom_message_free(msg);
1516
                }
1517
        }
1518
        g_free(error_cause);
1519
        JANUS_LOG(LOG_VERB, "Leaving TextRoom handler thread\n");
1520
        return NULL;
1521
}