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 Check the \ref textroom for more details.
6  *
7  * \ingroup plugins
8  * \ref plugins
9  *
10  * \page textroom Janus TextRoom documentation
11  * This is a plugin implementing a DataChannel only text room.
12  * As such, it does NOT support or negotiate audio or video, but only
13  * data channels, in order to provide text broadcasting features. The
14  * plugin allows users to join multiple text-only rooms via a single
15  * PeerConnection. Users can send messages either to a room in general
16  * (broadcasting), or to individual users (whispers). This plugin can be
17  * used within the context of any application that needs real-time text
18  * broadcasting (e.g., chatrooms, but not only).
19  *
20  * The only message that is typically sent to the plugin through the Janus API is
21  * a "setup" message, by which the user initializes the PeerConnection
22  * itself. Apart from that, all other messages can be exchanged directly
23  * via Data Channels. For room management purposes, though, requests like
24  * "create", "edit", "destroy", "list", "listparticipants" and "exists"
25  * and "announcement" are available through the
26  * Janus API as well: notice that in this case you'll have to use "request"
27  * and not "textroom" as the name of the request.
28  *
29  * Each room can also be configured with an HTTP backend to contact for
30  * incoming messages. If configured, messages addressed to that room will
31  * also be forwarded, by means of an HTTP POST, to the specified address.
32  * Notice that this will only work if libcurl was available when
33  * configuring and installing Janus.
34  *
35  * \note This plugin is only meant to showcase what you can do with
36  * data channels involving multiple participants at the same time. While
37  * functional, it's not inherently better or faster than doing the same
38  * thing using the Janus API messaging itself (e.g., as part of the
39  * plugin API messaging) or using existing instant messaging protocols
40  * (e.g., Jabber). In fact, while data channels are being used, you're
41  * still going through a server, so it's not really peer-to-peer. That
42  * said, the plugin can be useful if you don't plan to use any other
43  * infrastructure than Janus, and yet you also want to have text-based
44  * communication (e.g., to add a chatroom to an audio or video conference).
45  *
46  * Notice that, in general, all users can create rooms. If you want to
47  * limit this functionality, you can configure an admin \c admin_key in
48  * the plugin settings. When configured, only "create" requests that
49  * include the correct \c admin_key value in an "admin_key" property
50  * will succeed, and will be rejected otherwise.
51  *
52  * Rooms to make available at startup are listed in the plugin configuration file.
53  * A pre-filled configuration file is provided in \c conf/janus.plugin.textroom.cfg
54  * and includes a demo room for testing.
55  *
56  * To add more static rooms or modify the existing one, you can use the following
57  * syntax:
58  *
59  * \verbatim
60 [<unique room ID>]
61 description = This is my awesome room
62 is_private = true|false (whether this room should be in the public list, default=true)
63 secret = <optional password needed for manipulating (e.g. destroying) the room>
64 pin = <optional password needed for joining the room>
65 history = <number of messages to store as a history, and send back to new participants (default=0, no history)>
66 post = <optional backend to contact via HTTP post for all incoming messages>
67 \endverbatim
68  *
69  * As explained in the next section, you can also create rooms programmatically.
70  *
71  * \section textroomapi Text Room API
72  *
73  * All TextRoom API requests are addressed by a \c textroom named property,
74  * and must contain a \c transaction string property as well, which will
75  * be returned in the response. Notice that, for the sake of brevity, the
76  * \c transaction property will not be displayed in the documentation,
77  * although, as explained, it MUST be present, and WILL be included in
78  * all responses (but not in the unsolicited events, like join/leave
79  * or incoming messages).
80  *
81  * To get a list of the available rooms (excluded those configured or
82  * created as private rooms) you can make use of the \c list request,
83  * which has to be formatted as follows:
84  *
85 \verbatim
86 {
87 	"textroom" : "list",
88 }
89 \endverbatim
90  *
91  * A successful request will produce a list of rooms in a \c success response:
92  *
93 \verbatim
94 {
95 	"textroom" : "success",
96 	"rooms" : [		// Array of room objects
97 		{	// Room #1
98 			"room" : <unique numeric ID>,
99 			"description" : "<Name of the room>",
100 			"pin_required" : <true|false, depending on whether the room is PIN-protected>,
101 			"num_participants" : <count of the participants>
102 		},
103 		// Other rooms
104 	]
105 }
106 \endverbatim
107  *
108  * To get a list of the participants in a specific room, instead, you
109  * can make use of the \c listparticipants request, which has to be
110  * formatted as follows:
111  *
112 \verbatim
113 {
114 	"request" : "listparticipants",
115 	"room" : <unique numeric ID of the room>
116 }
117 \endverbatim
118  *
119  * A successful request will produce a list of participants in a
120  * \c participants response:
121  *
122 \verbatim
123 {
124 	"room" : <unique numeric ID of the room>,
125 	"participants" : [		// Array of participant objects
126 		{	// Participant #1
127 			"username" : "<username of participant>",
128 			"display" : "<display name of participant, if any>"
129 		},
130 		// Other participants
131 	]
132 }
133 \endverbatim
134  *
135  * To create new TextRoom rooms you can use the \c create request. The API
136  * room creation supports the same fields as creation via configuration files,
137  * which means the request must be formatted as follows:
138  *
139 \verbatim
140 {
141 	"textroom" : "create",
142 	"room" : <unique numeric room ID to assign; optional, chosen by plugin if missing>,
143 	"admin_key" : "<plugin administrator key; mandatory if configured>",
144 	"description" : "<description of room; optional>",
145 	"secret" : "<secret to query/edit the room later; optional>",
146 	"pin" : "<PIN required for participants to join room; optional>",
147 	"is_private" : <true|false, whether the room should be listable; optional, true by default>,
148 	"history" : <number of messages to store as a history, and send back to new participants (default=0, no history)>,
149 	"post" : "<backend to contact via HTTP post for all incoming messages; optional>",
150 	"permanent" : <true|false, whether the mountpoint should be saved to configuration file or not; false by default>
151 }
152 \endverbatim
153  *
154  * A successful creation procedure will result in a \c success response:
155  *
156 \verbatim
157 {
158 	"textroom" : "success",
159 	"room" : <unique numeric ID>,
160 	"permanent" : <true if saved to config file, false if not>
161 }
162 \endverbatim
163  *
164  * If you requested a permanent room but a \c false value is returned
165  * instead, good chances are that there are permission problems.
166  *
167  * An error instead (and the same applies to all other requests, so this
168  * won't be repeated) would provide both an error code and a more verbose
169  * description of the cause of the issue:
170  *
171 \verbatim
172 {
173 	"textroom" : "event",
174 	"error_code" : <numeric ID, check Macros below>,
175 	"error" : "<error description as a string>"
176 }
177 \endverbatim
178  *
179  * Once a room has been created, you can still edit some (but not all)
180  * of its properties using the \c edit request. This allows you to modify
181  * the room description, secret, pin, whether it's private or not and
182  * the backend to forward incoming messages to: you won't be able to modify
183  * other more static properties, though, like the room ID for instance.
184  * If you're interested in changing the ACL, instead, check the \c allowed
185  * message. An \c edit request has to be formatted as follows:
186  *
187 \verbatim
188 {
189 	"textroom" : "edit",
190 	"room" : <unique numeric ID of the room to edit; mandatory>,
191 	"secret" : "<room secret; mandatory if configured>",
192 	"new_description" : "<new pretty name of the room; optional>",
193 	"new_secret" : "<new password required to edit/destroy the room; optional>",
194 	"new_pin" : "<new password required to join the room; optional>",
195 	"new_is_private" : <true|false, whether the room should appear in a list request; optional>,
196 	"permanent" : <true|false, whether the room should be also removed from the config file; default=false>
197 }
198 \endverbatim
199  *
200  * A successful edit procedure will result in a \c success response:
201  *
202 \verbatim
203 {
204 	"textroom" : "edited",
205 	"room" : <unique numeric ID>,
206 	"permanent" : <true if changes were saved to config file, false if not>
207 }
208 \endverbatim
209  *
210  * On the other hand, \c destroy can be used to destroy an existing text
211  * room, whether created dynamically or statically, and has to be
212  * formatted as follows:
213  *
214 \verbatim
215 {
216 	"textroom" : "destroy",
217 	"room" : <unique numeric ID of the room to destroy; mandatory>,
218 	"secret" : "<room secret; mandatory if configured>",
219 	"permanent" : <true|false, whether the room should be also removed from the config file; default=false>
220 }
221 \endverbatim
222  *
223  * A successful destruction procedure will result in a \c destroyed response:
224  *
225 \verbatim
226 {
227 	"textroom" : "destroyed",
228 	"room" : <unique numeric ID>,
229 	"permanent" : <true if the room was removed from config file too, false if not>
230 }
231 \endverbatim
232  *
233  * This will also result in a \c destroyed event being sent to all the
234  * participants in the room, which will look like this:
235  *
236 \verbatim
237 {
238 	"textroom" : "destroyed",
239 	"room" : <unique numeric ID of the destroyed room>
240 }
241 \endverbatim
242  *
243  * You can check whether a room exists using the \c exists request,
244  * which has to be formatted as follows:
245  *
246 \verbatim
247 {
248 	"textroom" : "exists",
249 	"room" : <unique numeric ID of the room to check; mandatory>
250 }
251 \endverbatim
252  *
253  * A successful request will result in a \c success response:
254  *
255 \verbatim
256 {
257 	"textroom" : "success",
258 	"room" : <unique numeric ID>,
259 	"exists" : <true|false>
260 }
261 \endverbatim
262  *
263  * You can configure whether to check tokens or add/remove people who can join
264  * a room using the \c allowed request, which has to be formatted as follows:
265  *
266 \verbatim
267 {
268 	"textroom" : "allowed",
269 	"secret" : "<room secret; mandatory if configured>",
270 	"action" : "enable|disable|add|remove",
271 	"room" : <unique numeric ID of the room to update; mandatory>,
272 	"allowed" : [
273 		// Array of strings (tokens users might pass in "join", only for add|remove)
274 	]
275 }
276 \endverbatim
277  *
278  * A successful request will result in a \c success response:
279  *
280 \verbatim
281 {
282 	"textroom" : "success",
283 	"room" : <unique numeric ID>,
284 	"allowed" : [
285 		// Updated, complete, list of allowed tokens (only for enable|add|remove)
286 	]
287 }
288 \endverbatim
289  *
290  * If you're the administrator of a room (that is, you created it and have access
291  * to the secret) you can kick participants using the \c kick request. Notice
292  * that this only kicks the user out of the room, but does not prevent them from
293  * re-joining: to ban them, you need to first remove them from the list of
294  * authorized users (see \c allowed request) and then \c kick them. The \c kick
295  * request has to be formatted as follows:
296  *
297 \verbatim
298 {
299 	"textroom" : "kick",
300 	"secret" : "<room secret; mandatory if configured>",
301 	"room" : <unique numeric ID of the room; mandatory>,
302 	"username" : "<unique username of the participant to kick; mandatory>"
303 }
304 \endverbatim
305  *
306  * A successful request will result in a \c success response:
307  *
308 \verbatim
309 {
310 	"textroom" : "success",
311 }
312 \endverbatim
313  *
314  * This will also result in a \c kicked event being sent to all the other
315  * participants in the room, which will look like this:
316  *
317 \verbatim
318 {
319 	"textroom" : "kicked",
320 	"room" : <unique numeric ID of the room>,
321 	"username" : "<unique username of the kicked participant>"
322 }
323 \endverbatim
324  *
325  * For what concerns room participation, you can join a room using the
326  * \c join request, send messages (public and private) using the
327  * \c message request, and leave a room with \c leave instead.
328  *
329  * A \c join request must be formatted as follows:
330  *
331 \verbatim
332 {
333 	"textroom" : "join",
334 	"room" : <unique numeric ID of the room to join>,
335 	"pin" : "<pin to join the room; mandatory if configured>",
336 	"username" : "<unique username to have in the room; mandatory>",
337 	"display" : "<display name to use in the room; optional>",
338 	"token" : "<invitation token, in case the room has an ACL; optional>",
339 	"history" : <true|false, whether to retrieve history messages when available (default=true)>
340 }
341 \endverbatim
342  *
343  * A successful join will result in a \c success response, which will
344  * include a list of all the other participants currently in the room:
345  *
346 \verbatim
347 {
348 	"textroom" : "success",
349 	"participants" : [
350 		{
351 			"username" : "<username of participant #1>",
352 			"display" : "<display name of participant #1, if any>"
353 		},
354 		// Other participants
355 	]
356 }
357 \endverbatim
358  *
359  * As explained previously, there's no hardcoded limit in how many rooms
360  * you can join with the same participant and on the same PeerConnection.
361  *
362  * Notice that a successful \c join request will also result in a
363  * \c join event being sent to all the other participants, so that
364  * they're notified about the new participant getting in the room:
365  *
366 \verbatim
367 {
368 	"textroom" : "join",
369 	"room" : <room ID>,
370 	"username" : "<username of new participant>",
371 	"display" : "<display name of new participant, if any>"
372 }
373 \endverbatim
374  *
375  * To leave a previously joined room, instead, the \c leave request can
376  * be used, which must be formatted like this:
377  *
378 \verbatim
379 {
380 	"textroom" : "leave",
381 	"room" : <unique numeric ID of the room to leave>
382 }
383 \endverbatim
384  *
385  * A successful leave will result in a \c success response:
386  *
387 \verbatim
388 {
389 	"textroom" : "success"
390 }
391 \endverbatim
392  *
393  * Notice that a successful \c leave request will also result in a
394  * \c leave event being sent to all the other participants, so that
395  * they're notified about the participant that just left the room:
396  *
397 \verbatim
398 {
399 	"textroom" : "leave",
400 	"room" : <room ID>,
401 	"username" : "<username of gone participant>"
402 }
403 \endverbatim
404  *
405  * Finally, the \c message request allows you to send public and private
406  * messages within the context of a room. It must be formatted like this:
407  *
408 \verbatim
409 {
410 	"textroom" : "message",
411 	"room" : <unique numeric ID of the room this message will refer to>,
412 	"to" : "<username to send the message to; optional, only needed in case of private messages>",
413 	"tos" : "<array of usernames to send the message to; optional, only needed in case of private messages>",
414 	"text" : "<content of the message to send, as a string>",
415 	"ack" : <true|false, whether the sender wants an ack for the sent message(s); optional, true by default>
416 }
417 \endverbatim
418  *
419  * A \c message with no \c to and no \c tos is considered a public message,
420  * and so will be sent to all the participants in the room. In case either
421  * \c to or \c tos is specified, instead, this is considered to be a whisper,
422  * that is a private message only meant for the specified recipients. Notice
423  * that \c to and \c tos are mutually exclusive, and you cannot specify both.
424  *
425  * \c text must be a string, but apart from that there's no limit on what
426  * you can put in there. It could be, for instance, a serialized JSON string,
427  * or a stringified XML document, or whatever makes sense to the application.
428  *
429  * A successful message delivery will result in a \c success response, but
430  * only if \c ack was \c true in the \c message request. This was done by
431  * design, to allow users to disable explicit acks for every outgoing message,
432  * especially in case of verbose communications. In case an ack is required,
433  * the response will look like this:
434  *
435 \verbatim
436 {
437 	"textroom" : "success"
438 }
439 \endverbatim
440  *
441  * Incoming messages will come either as \c message events. In particular,
442  * \c message will notify the user about an incoming public or privave
443  * message, that is either a message that was sent to the whole room,
444  * or to the user individually:
445  *
446 \verbatim
447 {
448 	"textroom" : "message",
449 	"room" : <room ID the message was sent to>,
450 	"from" : "<username of participant who sent the public message>",
451 	"date" : "<date/time of when the message was sent>",
452 	"text" : "<content of the message>",
453 	"whisper" : <true|false, depending on whether it's a public or private message>
454 }
455 \endverbatim
456  *
457  * In case the \c whisper attribute is \c true it means the user actually
458  * received a  private message from another participant in the room.
459  *
460  * Another way of injecting text into rooms is by means of announcements.
461  * Announcements are basically messages sent by the room itself, rather
462  * than individual users: as such, only users or applications managing
463  * the room can send these announcements, as the room secret will be
464  * required for the purpose. The \c announcement request implements this
465  * feature in the TextRoom plugin, and must be formatted like this:
466  *
467 \verbatim
468 {
469 	"textroom" : "announcement",
470 	"room" : <unique numeric ID of the room this announcement will be sent to>,
471 	"secret" : "<room secret; mandatory if configured>",
472 	"text" : "<content of the announcement to send, as a string>"
473 }
474 \endverbatim
475  *
476  * In case the \c announcement request is accepted, the response will look
477  * like this:
478  *
479 \verbatim
480 {
481 	"textroom" : "success"
482 }
483 \endverbatim
484  *
485  * Incoming announcements will be received by participants as \c announcement
486  * events. The syntax is pretty much identical to how \c message looks like,
487  * with the difference that no \c from attribute will be included as the
488  * announcement will be seen as coming from the room itself:
489  *
490 \verbatim
491 {
492 	"textroom" : "announcement",
493 	"room" : <room ID the announcement was sent to>,
494 	"date" : "<date/time of when the announcement was sent>",
495 	"text" : "<content of the announcement>"
496 }
497 \endverbatim
498  *
499  */
500 
501 #include "plugin.h"
502 
503 #include <jansson.h>
504 
505 #ifdef HAVE_LIBCURL
506 #include <curl/curl.h>
507 #endif
508 
509 #include "../debug.h"
510 #include "../apierror.h"
511 #include "../config.h"
512 #include "../mutex.h"
513 #include "../utils.h"
514 
515 
516 /* Plugin information */
517 #define JANUS_TEXTROOM_VERSION			2
518 #define JANUS_TEXTROOM_VERSION_STRING	"0.0.2"
519 #define JANUS_TEXTROOM_DESCRIPTION		"This is a plugin implementing a text-only room for Janus, using DataChannels."
520 #define JANUS_TEXTROOM_NAME				"JANUS TextRoom plugin"
521 #define JANUS_TEXTROOM_AUTHOR			"Meetecho s.r.l."
522 #define JANUS_TEXTROOM_PACKAGE			"janus.plugin.textroom"
523 
524 /* Plugin methods */
525 janus_plugin *create(void);
526 int janus_textroom_init(janus_callbacks *callback, const char *config_path);
527 void janus_textroom_destroy(void);
528 int janus_textroom_get_api_compatibility(void);
529 int janus_textroom_get_version(void);
530 const char *janus_textroom_get_version_string(void);
531 const char *janus_textroom_get_description(void);
532 const char *janus_textroom_get_name(void);
533 const char *janus_textroom_get_author(void);
534 const char *janus_textroom_get_package(void);
535 void janus_textroom_create_session(janus_plugin_session *handle, int *error);
536 struct janus_plugin_result *janus_textroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
537 json_t *janus_textroom_handle_admin_message(json_t *message);
538 void janus_textroom_setup_media(janus_plugin_session *handle);
539 void janus_textroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet);
540 void janus_textroom_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet);
541 void janus_textroom_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet);
542 void janus_textroom_data_ready(janus_plugin_session *handle);
543 void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video);
544 void janus_textroom_hangup_media(janus_plugin_session *handle);
545 void janus_textroom_destroy_session(janus_plugin_session *handle, int *error);
546 json_t *janus_textroom_query_session(janus_plugin_session *handle);
547 
548 /* Plugin setup */
549 static janus_plugin janus_textroom_plugin =
550 	JANUS_PLUGIN_INIT (
551 		.init = janus_textroom_init,
552 		.destroy = janus_textroom_destroy,
553 
554 		.get_api_compatibility = janus_textroom_get_api_compatibility,
555 		.get_version = janus_textroom_get_version,
556 		.get_version_string = janus_textroom_get_version_string,
557 		.get_description = janus_textroom_get_description,
558 		.get_name = janus_textroom_get_name,
559 		.get_author = janus_textroom_get_author,
560 		.get_package = janus_textroom_get_package,
561 
562 		.create_session = janus_textroom_create_session,
563 		.handle_message = janus_textroom_handle_message,
564 		.handle_admin_message = janus_textroom_handle_admin_message,
565 		.setup_media = janus_textroom_setup_media,
566 		.incoming_rtp = janus_textroom_incoming_rtp,
567 		.incoming_rtcp = janus_textroom_incoming_rtcp,
568 		.incoming_data = janus_textroom_incoming_data,
569 		.data_ready = janus_textroom_data_ready,
570 		.slow_link = janus_textroom_slow_link,
571 		.hangup_media = janus_textroom_hangup_media,
572 		.destroy_session = janus_textroom_destroy_session,
573 		.query_session = janus_textroom_query_session,
574 	);
575 
576 /* Plugin creator */
create(void)577 janus_plugin *create(void) {
578 	JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_TEXTROOM_NAME);
579 	return &janus_textroom_plugin;
580 }
581 
582 
583 /* Parameter validation */
584 static struct janus_json_parameter request_parameters[] = {
585 	{"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
586 };
587 static struct janus_json_parameter transaction_parameters[] = {
588 	{"textroom", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
589 	{"transaction", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
590 };
591 static struct janus_json_parameter room_parameters[] = {
592 	{"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
593 };
594 static struct janus_json_parameter roomopt_parameters[] = {
595 	{"room", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
596 };
597 static struct janus_json_parameter roomstr_parameters[] = {
598 	{"room", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
599 };
600 static struct janus_json_parameter roomstropt_parameters[] = {
601 	{"room", JSON_STRING, 0}
602 };
603 static struct janus_json_parameter adminkey_parameters[] = {
604 	{"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
605 };
606 static struct janus_json_parameter create_parameters[] = {
607 	{"description", JSON_STRING, 0},
608 	{"secret", JSON_STRING, 0},
609 	{"pin", JSON_STRING, 0},
610 	{"post", JSON_STRING, 0},
611 	{"is_private", JANUS_JSON_BOOL, 0},
612 	{"history", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
613 	{"allowed", JSON_ARRAY, 0},
614 	{"permanent", JANUS_JSON_BOOL, 0}
615 };
616 static struct janus_json_parameter destroy_parameters[] = {
617 	{"permanent", JANUS_JSON_BOOL, 0}
618 };
619 static struct janus_json_parameter edit_parameters[] = {
620 	{"secret", JSON_STRING, 0},
621 	{"new_description", JSON_STRING, 0},
622 	{"new_secret", JSON_STRING, 0},
623 	{"new_pin", JSON_STRING, 0},
624 	{"new_post", JSON_STRING, 0},
625 	{"new_is_private", JANUS_JSON_BOOL, 0},
626 	{"permanent", JANUS_JSON_BOOL, 0}
627 };
628 static struct janus_json_parameter allowed_parameters[] = {
629 	{"secret", JSON_STRING, 0},
630 	{"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
631 	{"allowed", JSON_ARRAY, 0}
632 };
633 static struct janus_json_parameter kick_parameters[] = {
634 	{"secret", JSON_STRING, 0},
635 	{"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
636 };
637 static struct janus_json_parameter join_parameters[] = {
638 	{"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
639 	{"pin", JSON_STRING, 0},
640 	{"token", JSON_STRING, 0},
641 	{"display", JSON_STRING, 0},
642 	{"history", JANUS_JSON_BOOL, 0}
643 };
644 static struct janus_json_parameter message_parameters[] = {
645 	{"text", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
646 	{"to", JSON_STRING, 0},
647 	{"tos", JSON_ARRAY, 0},
648 	{"ack", JANUS_JSON_BOOL, 0}
649 };
650 static struct janus_json_parameter announcement_parameters[] = {
651 	{"secret", JSON_STRING, 0},
652 	{"text", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
653 };
654 
655 /* Static configuration instance */
656 static janus_config *config = NULL;
657 static const char *config_folder = NULL;
658 static janus_mutex config_mutex = JANUS_MUTEX_INITIALIZER;
659 
660 /* Useful stuff */
661 static volatile gint initialized = 0, stopping = 0;
662 static gboolean notify_events = TRUE;
663 static gboolean string_ids = FALSE;
664 static janus_callbacks *gateway = NULL;
665 static GThread *handler_thread;
666 static void *janus_textroom_handler(void *data);
667 static void janus_textroom_hangup_media_internal(janus_plugin_session *handle);
668 
669 /* JSON serialization options */
670 static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
671 
672 
673 typedef struct janus_textroom_room {
674 	guint64 room_id;			/* Unique room ID (when using integers) */
675 	gchar *room_id_str;			/* Unique room ID (when using strings) */
676 	gchar *room_name;			/* Room description */
677 	gchar *room_secret;			/* Secret needed to manipulate (e.g., destroy) this room */
678 	gchar *room_pin;			/* Password needed to join this room, if any */
679 	gboolean is_private;		/* Whether this room is 'private' (as in hidden) or not */
680 	gchar *http_backend;		/* Server to contact via HTTP POST for incoming messages, if any */
681 	GHashTable *participants;	/* Map of participants */
682 	uint16_t history_size;		/* Number of messages we should store in the history */
683 	GQueue *history;			/* History of past messages */
684 	gboolean check_tokens;		/* Whether to check tokens when participants join (see below) */
685 	GHashTable *allowed;		/* Map of participants (as tokens) allowed to join */
686 	volatile gint destroyed;	/* Whether this room has been destroyed */
687 	janus_mutex mutex;			/* Mutex to lock this room instance */
688 	janus_refcount ref;
689 } janus_textroom_room;
690 static GHashTable *rooms = NULL;
691 static janus_mutex rooms_mutex = JANUS_MUTEX_INITIALIZER;
692 static char *admin_key = NULL;
693 
694 typedef struct janus_textroom_session {
695 	janus_plugin_session *handle;
696 	gint64 sdp_sessid;
697 	gint64 sdp_version;
698 	GHashTable *rooms;			/* Map of rooms this user is in, and related participant instance */
699 	janus_mutex mutex;			/* Mutex to lock this session */
700 	volatile gint setup;
701 	volatile gint dataready;
702 	volatile gint hangingup;
703 	volatile gint destroyed;
704 	janus_refcount ref;
705 } janus_textroom_session;
706 static GHashTable *sessions;
707 static janus_mutex sessions_mutex = JANUS_MUTEX_INITIALIZER;
708 
709 typedef struct janus_textroom_participant {
710 	janus_textroom_session *session;
711 	janus_textroom_room *room;	/* Room this participant is in */
712 	gchar *username;			/* Unique username in the room */
713 	gchar *display;				/* Display name in the room, if any */
714 	janus_mutex mutex;			/* Mutex to lock this session */
715 	volatile gint destroyed;	/* Whether this participant has been destroyed */
716 	janus_refcount ref;
717 } janus_textroom_participant;
718 
janus_textroom_room_destroy(janus_textroom_room * textroom)719 static void janus_textroom_room_destroy(janus_textroom_room *textroom) {
720 	if(textroom && g_atomic_int_compare_and_exchange(&textroom->destroyed, 0, 1))
721 		janus_refcount_decrease(&textroom->ref);
722 }
janus_textroom_room_free(const janus_refcount * textroom_ref)723 static void janus_textroom_room_free(const janus_refcount *textroom_ref) {
724 	janus_textroom_room *textroom = janus_refcount_containerof(textroom_ref, janus_textroom_room, ref);
725 	/* This room can be destroyed, free all the resources */
726 	g_free(textroom->room_id_str);
727 	g_free(textroom->room_name);
728 	g_free(textroom->room_secret);
729 	g_free(textroom->room_pin);
730 	g_free(textroom->http_backend);
731 	g_hash_table_destroy(textroom->participants);
732 	g_hash_table_destroy(textroom->allowed);
733 	if(textroom->history)
734 		g_queue_free_full(textroom->history, (GDestroyNotify)g_free);
735 	g_free(textroom);
736 }
737 
janus_textroom_session_destroy(janus_textroom_session * session)738 static void janus_textroom_session_destroy(janus_textroom_session *session) {
739 	if(session && g_atomic_int_compare_and_exchange(&session->destroyed, 0, 1))
740 		janus_refcount_decrease(&session->ref);
741 }
janus_textroom_session_free(const janus_refcount * session_ref)742 static void janus_textroom_session_free(const janus_refcount *session_ref) {
743 	janus_textroom_session *session = janus_refcount_containerof(session_ref, janus_textroom_session, ref);
744 	/* Remove the reference to the core plugin session */
745 	janus_refcount_decrease(&session->handle->ref);
746 	/* This session can be destroyed, free all the resources */
747 	g_hash_table_destroy(session->rooms);
748 	g_free(session);
749 }
750 
janus_textroom_participant_dereference(janus_textroom_participant * p)751 static void janus_textroom_participant_dereference(janus_textroom_participant *p) {
752 	if(p)
753 		janus_refcount_decrease(&p->ref);
754 }
755 
janus_textroom_participant_destroy(janus_textroom_participant * participant)756 static void janus_textroom_participant_destroy(janus_textroom_participant *participant) {
757 	if(participant && g_atomic_int_compare_and_exchange(&participant->destroyed, 0, 1))
758 		janus_refcount_decrease(&participant->ref);
759 }
janus_textroom_participant_free(const janus_refcount * participant_ref)760 static void janus_textroom_participant_free(const janus_refcount *participant_ref) {
761 	janus_textroom_participant *participant = janus_refcount_containerof(participant_ref, janus_textroom_participant, ref);
762 	/* This participant can be destroyed, free all the resources */
763 	g_free(participant->username);
764 	g_free(participant->display);
765 	g_free(participant);
766 }
767 
768 
769 typedef struct janus_textroom_message {
770 	janus_plugin_session *handle;
771 	char *transaction;
772 	json_t *message;
773 	json_t *jsep;
774 } janus_textroom_message;
775 static GAsyncQueue *messages = NULL;
776 static janus_textroom_message exit_message;
777 
janus_textroom_message_free(janus_textroom_message * msg)778 static void janus_textroom_message_free(janus_textroom_message *msg) {
779 	if(!msg || msg == &exit_message)
780 		return;
781 
782 	if(msg->handle && msg->handle->plugin_handle) {
783 		janus_textroom_session *session = (janus_textroom_session *)msg->handle->plugin_handle;
784 		janus_refcount_decrease(&session->ref);
785 	}
786 	msg->handle = NULL;
787 
788 	g_free(msg->transaction);
789 	msg->transaction = NULL;
790 	if(msg->message)
791 		json_decref(msg->message);
792 	msg->message = NULL;
793 	if(msg->jsep)
794 		json_decref(msg->jsep);
795 	msg->jsep = NULL;
796 
797 	g_free(msg);
798 }
799 
800 
801 /* SDP template: we only offer data channels */
802 #define sdp_template \
803 		"v=0\r\n" \
804 		"o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"	/* We need current time here */ \
805 		"s=Janus TextRoom plugin\r\n" \
806 		"t=0 0\r\n" \
807 		"m=application 1 UDP/DTLS/SCTP webrtc-datachannel\r\n" \
808 		"c=IN IP4 1.1.1.1\r\n" \
809 		"a=sctp-port:5000\r\n"
810 
811 
812 /* Error codes */
813 #define JANUS_TEXTROOM_ERROR_NO_MESSAGE			411
814 #define JANUS_TEXTROOM_ERROR_INVALID_JSON		412
815 #define JANUS_TEXTROOM_ERROR_MISSING_ELEMENT	413
816 #define JANUS_TEXTROOM_ERROR_INVALID_ELEMENT	414
817 #define JANUS_TEXTROOM_ERROR_INVALID_REQUEST	415
818 #define JANUS_TEXTROOM_ERROR_ALREADY_SETUP		416
819 #define JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM		417
820 #define JANUS_TEXTROOM_ERROR_ROOM_EXISTS		418
821 #define JANUS_TEXTROOM_ERROR_UNAUTHORIZED		419
822 #define JANUS_TEXTROOM_ERROR_USERNAME_EXISTS	420
823 #define JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM	421
824 #define JANUS_TEXTROOM_ERROR_NOT_IN_ROOM		422
825 #define JANUS_TEXTROOM_ERROR_NO_SUCH_USER		423
826 #define JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR		499
827 
828 #ifdef HAVE_LIBCURL
janus_textroom_write_data(void * buffer,size_t size,size_t nmemb,void * userp)829 static size_t janus_textroom_write_data(void *buffer, size_t size, size_t nmemb, void *userp) {
830 	return size*nmemb;
831 }
832 #endif
833 
834 /* We use this method to handle incoming requests. Since most of the requests
835  * will arrive from data channels, but some may also arrive from the regular
836  * plugin messaging (e.g., room management), we have the ability to pass
837  * parsed JSON objects instead of strings, which explains why we specify a
838  * janus_plugin_result pointer as a return value; messages handles via
839  * datachannels would simply return NULL. Besides, some requests are actually
840  * originated internally, and don't need any response to be sent to anyone,
841  * which is what the additional boolean "internal" value is for */
842 janus_plugin_result *janus_textroom_handle_incoming_request(janus_plugin_session *handle,
843 	char *text, json_t *json, gboolean internal);
844 
845 
846 /* Plugin implementation */
janus_textroom_init(janus_callbacks * callback,const char * config_path)847 int janus_textroom_init(janus_callbacks *callback, const char *config_path) {
848 	if(g_atomic_int_get(&stopping)) {
849 		/* Still stopping from before */
850 		return -1;
851 	}
852 	if(callback == NULL || config_path == NULL) {
853 		/* Invalid arguments */
854 		return -1;
855 	}
856 
857 #ifndef HAVE_SCTP
858 	/* Data channels not supported, no point loading this plugin */
859 	JANUS_LOG(LOG_WARN, "Data channels support not compiled, disabling TextRoom plugin\n");
860 	return -1;
861 #endif
862 
863 	/* Read configuration */
864 	char filename[255];
865 	g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_TEXTROOM_PACKAGE);
866 	JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
867 	config = janus_config_parse(filename);
868 	if(config == NULL) {
869 		JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_TEXTROOM_PACKAGE);
870 		g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_TEXTROOM_PACKAGE);
871 		JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
872 		config = janus_config_parse(filename);
873 	}
874 	config_folder = config_path;
875 	if(config != NULL)
876 		janus_config_print(config);
877 	sessions = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_textroom_session_destroy);
878 	messages = g_async_queue_new_full((GDestroyNotify) janus_textroom_message_free);
879 	/* This is the callback we'll need to invoke to contact the Janus core */
880 	gateway = callback;
881 
882 	/* Parse configuration to populate the rooms list */
883 	if(config != NULL) {
884 		janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general");
885 		janus_config_item *item = janus_config_get(config, config_general, janus_config_type_item, "json");
886 		if(item && item->value) {
887 			/* Check how we need to format/serialize the JSON output */
888 			if(!strcasecmp(item->value, "indented")) {
889 				/* Default: indented, we use three spaces for that */
890 				json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
891 			} else if(!strcasecmp(item->value, "plain")) {
892 				/* Not indented and no new lines, but still readable */
893 				json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER;
894 			} else if(!strcasecmp(item->value, "compact")) {
895 				/* Compact, so no spaces between separators */
896 				json_format = JSON_COMPACT | JSON_PRESERVE_ORDER;
897 			} else {
898 				JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value);
899 				json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER;
900 			}
901 		}
902 		/* Any admin key to limit who can "create"? */
903 		janus_config_item *key = janus_config_get(config, config_general, janus_config_type_item, "admin_key");
904 		if(key != NULL && key->value != NULL)
905 			admin_key = g_strdup(key->value);
906 		janus_config_item *events = janus_config_get(config, config_general, janus_config_type_item, "events");
907 		if(events != NULL && events->value != NULL)
908 			notify_events = janus_is_true(events->value);
909 		if(!notify_events && callback->events_is_enabled()) {
910 			JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_TEXTROOM_NAME);
911 		}
912 		janus_config_item *ids = janus_config_get(config, config_general, janus_config_type_item, "string_ids");
913 		if(ids != NULL && ids->value != NULL)
914 			string_ids = janus_is_true(ids->value);
915 		if(string_ids) {
916 			JANUS_LOG(LOG_INFO, "TextRoom will use alphanumeric IDs, not numeric\n");
917 		}
918 	}
919 	/* Iterate on all rooms */
920 	rooms = g_hash_table_new_full(string_ids ? g_str_hash : g_int64_hash, string_ids ? g_str_equal : g_int64_equal,
921 		(GDestroyNotify)g_free, (GDestroyNotify)janus_textroom_room_destroy);
922 	if(config != NULL) {
923 		GList *clist = janus_config_get_categories(config, NULL), *cl = clist;
924 		while(cl != NULL) {
925 			janus_config_category *cat = (janus_config_category *)cl->data;
926 			if(cat->name == NULL || !strcasecmp(cat->name, "general")) {
927 				cl = cl->next;
928 				continue;
929 			}
930 			JANUS_LOG(LOG_VERB, "Adding TextRoom room '%s'\n", cat->name);
931 			janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
932 			janus_config_item *priv = janus_config_get(config, cat, janus_config_type_item, "is_private");
933 			janus_config_item *secret = janus_config_get(config, cat, janus_config_type_item, "secret");
934 			janus_config_item *pin = janus_config_get(config, cat, janus_config_type_item, "pin");
935 			janus_config_item *history = janus_config_get(config, cat, janus_config_type_item, "history");
936 			janus_config_item *post = janus_config_get(config, cat, janus_config_type_item, "post");
937 			/* Create the text room */
938 			janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
939 			const char *room_num = cat->name;
940 			if(strstr(room_num, "room-") == room_num)
941 				room_num += 5;
942 			if(!string_ids) {
943 				textroom->room_id = g_ascii_strtoull(room_num, NULL, 0);
944 				if(textroom->room_id == 0) {
945 					JANUS_LOG(LOG_ERR, "Can't add the TextRoom room, invalid ID 0...\n");
946 					g_free(textroom);
947 					cl = cl->next;
948 					continue;
949 				}
950 				/* Make sure the ID is completely numeric */
951 				char room_id_str[30];
952 				g_snprintf(room_id_str, sizeof(room_id_str), "%"SCNu64, textroom->room_id);
953 				if(strcmp(room_num, room_id_str)) {
954 					JANUS_LOG(LOG_ERR, "Can't add the TextRoom room, ID '%s' is not numeric...\n", room_num);
955 					g_free(textroom);
956 					cl = cl->next;
957 					continue;
958 				}
959 			}
960 			/* Let's make sure the room doesn't exist already */
961 			janus_mutex_lock(&rooms_mutex);
962 			if(g_hash_table_lookup(rooms, string_ids ? (gpointer)room_num : (gpointer)&textroom->room_id) != NULL) {
963 				/* It does... */
964 				janus_mutex_unlock(&rooms_mutex);
965 				JANUS_LOG(LOG_ERR, "Can't add the TextRoom room, room %s already exists...\n", room_num);
966 				g_free(textroom);
967 				cl = cl->next;
968 				continue;
969 			}
970 			janus_mutex_unlock(&rooms_mutex);
971 			textroom->room_id_str = g_strdup(room_num);
972 			char *description = NULL;
973 			if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
974 				description = g_strdup(desc->value);
975 			else
976 				description = g_strdup(cat->name);
977 			textroom->room_name = description;
978 			textroom->is_private = priv && priv->value && janus_is_true(priv->value);
979 			if(secret != NULL && secret->value != NULL) {
980 				textroom->room_secret = g_strdup(secret->value);
981 			}
982 			if(pin != NULL && pin->value != NULL) {
983 				textroom->room_pin = g_strdup(pin->value);
984 			}
985 			if(history != NULL && history->value != NULL) {
986 				if(janus_string_to_uint16(history->value, &textroom->history_size) < 0) {
987 					JANUS_LOG(LOG_WARN, "Invalid history size value (%s), disabling history...\n", history->value);
988 				} else {
989 					if(textroom->history_size > 0)
990 						textroom->history = g_queue_new();
991 				}
992 			}
993 			if(post != NULL && post->value != NULL) {
994 #ifdef HAVE_LIBCURL
995 				/* FIXME Should we check if this is a valid HTTP address? */
996 				textroom->http_backend = g_strdup(post->value);
997 #else
998 				JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
999 #endif
1000 			}
1001 			textroom->participants = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)janus_textroom_participant_dereference);
1002 			textroom->check_tokens = FALSE;	/* Static rooms can't have an "allowed" list yet, no hooks to the configuration file */
1003 			textroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
1004 			textroom->destroyed = 0;
1005 			janus_mutex_init(&textroom->mutex);
1006 			janus_refcount_init(&textroom->ref, janus_textroom_room_free);
1007 			JANUS_LOG(LOG_VERB, "Created TextRoom: %s (%s, %s, secret: %s, pin: %s, history: %"SCNu16" messages)\n",
1008 				textroom->room_id_str, textroom->room_name,
1009 				textroom->is_private ? "private" : "public",
1010 				textroom->room_secret ? textroom->room_secret : "no secret",
1011 				textroom->room_pin ? textroom->room_pin : "no pin", textroom->history_size);
1012 			g_hash_table_insert(rooms,
1013 				string_ids ? (gpointer)g_strdup(textroom->room_id_str) : (gpointer)janus_uint64_dup(textroom->room_id),
1014 				textroom);
1015 			cl = cl->next;
1016 		}
1017 		g_list_free(clist);
1018 		/* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
1019 	}
1020 
1021 	/* Show available rooms */
1022 	janus_mutex_lock(&rooms_mutex);
1023 	GHashTableIter iter;
1024 	gpointer value;
1025 	g_hash_table_iter_init(&iter, rooms);
1026 	while (g_hash_table_iter_next(&iter, NULL, &value)) {
1027 		janus_textroom_room *tr = value;
1028 		JANUS_LOG(LOG_VERB, "  ::: [%s][%s]\n", tr->room_id_str, tr->room_name);
1029 	}
1030 	janus_mutex_unlock(&rooms_mutex);
1031 
1032 #ifdef HAVE_LIBCURL
1033 	curl_global_init(CURL_GLOBAL_ALL);
1034 #endif
1035 
1036 	g_atomic_int_set(&initialized, 1);
1037 
1038 	GError *error = NULL;
1039 	/* Launch the thread that will handle incoming messages */
1040 	handler_thread = g_thread_try_new("textroom handler", janus_textroom_handler, NULL, &error);
1041 	if(error != NULL) {
1042 		g_atomic_int_set(&initialized, 0);
1043 		JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the TextRoom handler thread...\n",
1044 			error->code, error->message ? error->message : "??");
1045 		g_error_free(error);
1046 		return -1;
1047 	}
1048 	JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_TEXTROOM_NAME);
1049 	return 0;
1050 }
1051 
janus_textroom_destroy(void)1052 void janus_textroom_destroy(void) {
1053 	if(!g_atomic_int_get(&initialized))
1054 		return;
1055 	g_atomic_int_set(&stopping, 1);
1056 
1057 	g_async_queue_push(messages, &exit_message);
1058 	if(handler_thread != NULL) {
1059 		g_thread_join(handler_thread);
1060 		handler_thread = NULL;
1061 	}
1062 
1063 	/* FIXME We should destroy the sessions cleanly */
1064 	janus_mutex_lock(&sessions_mutex);
1065 	g_hash_table_destroy(sessions);
1066 	sessions = NULL;
1067 	janus_mutex_unlock(&sessions_mutex);
1068 	janus_mutex_lock(&rooms_mutex);
1069 	g_hash_table_destroy(rooms);
1070 	rooms = NULL;
1071 	janus_mutex_unlock(&rooms_mutex);
1072 	g_async_queue_unref(messages);
1073 	messages = NULL;
1074 
1075 #ifdef HAVE_LIBCURL
1076 	curl_global_cleanup();
1077 #endif
1078 
1079 	janus_config_destroy(config);
1080 	g_free(admin_key);
1081 
1082 	g_atomic_int_set(&initialized, 0);
1083 	g_atomic_int_set(&stopping, 0);
1084 	JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_TEXTROOM_NAME);
1085 }
1086 
janus_textroom_get_api_compatibility(void)1087 int janus_textroom_get_api_compatibility(void) {
1088 	/* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
1089 	return JANUS_PLUGIN_API_VERSION;
1090 }
1091 
janus_textroom_get_version(void)1092 int janus_textroom_get_version(void) {
1093 	return JANUS_TEXTROOM_VERSION;
1094 }
1095 
janus_textroom_get_version_string(void)1096 const char *janus_textroom_get_version_string(void) {
1097 	return JANUS_TEXTROOM_VERSION_STRING;
1098 }
1099 
janus_textroom_get_description(void)1100 const char *janus_textroom_get_description(void) {
1101 	return JANUS_TEXTROOM_DESCRIPTION;
1102 }
1103 
janus_textroom_get_name(void)1104 const char *janus_textroom_get_name(void) {
1105 	return JANUS_TEXTROOM_NAME;
1106 }
1107 
janus_textroom_get_author(void)1108 const char *janus_textroom_get_author(void) {
1109 	return JANUS_TEXTROOM_AUTHOR;
1110 }
1111 
janus_textroom_get_package(void)1112 const char *janus_textroom_get_package(void) {
1113 	return JANUS_TEXTROOM_PACKAGE;
1114 }
1115 
janus_textroom_lookup_session(janus_plugin_session * handle)1116 static janus_textroom_session *janus_textroom_lookup_session(janus_plugin_session *handle) {
1117 	janus_textroom_session *session = NULL;
1118 	if (g_hash_table_contains(sessions, handle)) {
1119 		session = (janus_textroom_session *)handle->plugin_handle;
1120 	}
1121 	return session;
1122 }
1123 
janus_textroom_create_session(janus_plugin_session * handle,int * error)1124 void janus_textroom_create_session(janus_plugin_session *handle, int *error) {
1125 	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1126 		*error = -1;
1127 		return;
1128 	}
1129 	janus_textroom_session *session = g_malloc0(sizeof(janus_textroom_session));
1130 	session->handle = handle;
1131 	session->rooms = g_hash_table_new_full(string_ids ? g_str_hash : g_int64_hash, string_ids ? g_str_equal : g_int64_equal,
1132 		(GDestroyNotify)g_free, (GDestroyNotify)janus_textroom_participant_dereference);
1133 	session->destroyed = 0;
1134 	janus_mutex_init(&session->mutex);
1135 	janus_refcount_init(&session->ref, janus_textroom_session_free);
1136 	g_atomic_int_set(&session->setup, 0);
1137 	g_atomic_int_set(&session->dataready, 0);
1138 	g_atomic_int_set(&session->hangingup, 0);
1139 	handle->plugin_handle = session;
1140 	janus_mutex_lock(&sessions_mutex);
1141 	g_hash_table_insert(sessions, handle, session);
1142 	janus_mutex_unlock(&sessions_mutex);
1143 
1144 	return;
1145 }
1146 
janus_textroom_destroy_session(janus_plugin_session * handle,int * error)1147 void janus_textroom_destroy_session(janus_plugin_session *handle, int *error) {
1148 	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1149 		*error = -1;
1150 		return;
1151 	}
1152 	janus_mutex_lock(&sessions_mutex);
1153 	janus_textroom_session *session = janus_textroom_lookup_session(handle);
1154 	if(!session) {
1155 		janus_mutex_unlock(&sessions_mutex);
1156 		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1157 		*error = -2;
1158 		return;
1159 	}
1160 	JANUS_LOG(LOG_VERB, "Removing TextRoom session...\n");
1161 	janus_textroom_hangup_media_internal(handle);
1162 	g_hash_table_remove(sessions, handle);
1163 	janus_mutex_unlock(&sessions_mutex);
1164 
1165 	return;
1166 }
1167 
janus_textroom_query_session(janus_plugin_session * handle)1168 json_t *janus_textroom_query_session(janus_plugin_session *handle) {
1169 	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
1170 		return NULL;
1171 	}
1172 	janus_mutex_lock(&sessions_mutex);
1173 	janus_textroom_session *session = janus_textroom_lookup_session(handle);
1174 	if(!session) {
1175 		janus_mutex_unlock(&sessions_mutex);
1176 		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1177 		return NULL;
1178 	}
1179 	janus_refcount_increase(&session->ref);
1180 	janus_mutex_unlock(&sessions_mutex);
1181 	/* TODO Return meaningful info: participant details, rooms they're in, etc. */
1182 	json_t *info = json_object();
1183 	json_object_set_new(info, "destroyed", json_integer(session->destroyed));
1184 	janus_refcount_decrease(&session->ref);
1185 	return info;
1186 }
1187 
janus_textroom_handle_message(janus_plugin_session * handle,char * transaction,json_t * message,json_t * jsep)1188 struct janus_plugin_result *janus_textroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
1189 	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1190 		return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
1191 
1192 	/* Pre-parse the message */
1193 	int error_code = 0;
1194 	char error_cause[512];
1195 	json_t *root = message;
1196 	json_t *response = NULL;
1197 
1198 	janus_mutex_lock(&sessions_mutex);
1199 	janus_textroom_session *session = janus_textroom_lookup_session(handle);
1200 	if(!session) {
1201 		janus_mutex_unlock(&sessions_mutex);
1202 		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1203 		error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1204 		g_snprintf(error_cause, 512, "%s", "No session associated with this handle...");
1205 		goto plugin_response;
1206 	}
1207 	/* Increase the reference counter for this session: we'll decrease it after we handle the message */
1208 	janus_refcount_increase(&session->ref);
1209 	janus_mutex_unlock(&sessions_mutex);
1210 	if(g_atomic_int_get(&session->destroyed)) {
1211 		JANUS_LOG(LOG_ERR, "Session has already been destroyed...\n");
1212 		error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1213 		g_snprintf(error_cause, 512, "%s", "Session has already been destroyed...");
1214 		goto plugin_response;
1215 	}
1216 
1217 	if(message == NULL) {
1218 		JANUS_LOG(LOG_ERR, "No message??\n");
1219 		error_code = JANUS_TEXTROOM_ERROR_NO_MESSAGE;
1220 		g_snprintf(error_cause, 512, "%s", "No message??");
1221 		goto plugin_response;
1222 	}
1223 	if(!json_is_object(root)) {
1224 		JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
1225 		error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
1226 		g_snprintf(error_cause, 512, "JSON error: not an object");
1227 		goto plugin_response;
1228 	}
1229 	/* Get the request first */
1230 	JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
1231 		error_code, error_cause, TRUE,
1232 		JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1233 	if(error_code != 0)
1234 		goto plugin_response;
1235 	json_t *request = json_object_get(root, "request");
1236 	/* Some requests (e.g., 'create' and 'destroy') can be handled synchronously */
1237 	const char *request_text = json_string_value(request);
1238 	if(!strcasecmp(request_text, "list")
1239 			|| !strcasecmp(request_text, "listparticipants")
1240 			|| !strcasecmp(request_text, "exists")
1241 			|| !strcasecmp(request_text, "create")
1242 			|| !strcasecmp(request_text, "edit")
1243 			|| !strcasecmp(request_text, "announcement")
1244 			|| !strcasecmp(request_text, "allowed")
1245 			|| !strcasecmp(request_text, "kick")
1246 			|| !strcasecmp(request_text, "destroy")) {
1247 		/* These requests typically only belong to the datachannel
1248 		 * messaging, but for admin purposes we might use them on
1249 		 * the Janus API as well: add the properties the datachannel
1250 		 * processor would expect and handle everything there */
1251 		if(json_object_get(root, "textroom") == NULL)
1252 			json_object_set_new(root, "textroom", json_string(request_text));
1253 		json_object_set_new(root, "transaction", json_string(transaction));
1254 		janus_plugin_result *result = janus_textroom_handle_incoming_request(session->handle, NULL, root, FALSE);
1255 		if(result == NULL) {
1256 			JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
1257 			error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
1258 			g_snprintf(error_cause, 512, "JSON error: not an object");
1259 			goto plugin_response;
1260 		}
1261 		if(root != NULL)
1262 			json_decref(root);
1263 		if(jsep != NULL)
1264 			json_decref(jsep);
1265 		g_free(transaction);
1266 		janus_refcount_decrease(&session->ref);
1267 		return result;
1268 	} else if(!strcasecmp(request_text, "setup") || !strcasecmp(request_text, "ack") || !strcasecmp(request_text, "restart")) {
1269 		/* These messages are handled asynchronously */
1270 		janus_textroom_message *msg = g_malloc(sizeof(janus_textroom_message));
1271 		msg->handle = handle;
1272 		msg->transaction = transaction;
1273 		msg->message = root;
1274 		msg->jsep = jsep;
1275 
1276 		g_async_queue_push(messages, msg);
1277 
1278 		return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
1279 	} else {
1280 		JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
1281 		error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
1282 		g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1283 	}
1284 
1285 plugin_response:
1286 		{
1287 			if(!response) {
1288 				/* Prepare JSON error event */
1289 				response = json_object();
1290 				json_object_set_new(response, "textroom", json_string("event"));
1291 				json_object_set_new(response, "error_code", json_integer(error_code));
1292 				json_object_set_new(response, "error", json_string(error_cause));
1293 			}
1294 			if(root != NULL)
1295 				json_decref(root);
1296 			if(jsep != NULL)
1297 				json_decref(jsep);
1298 			g_free(transaction);
1299 
1300 			if(session != NULL)
1301 				janus_refcount_decrease(&session->ref);
1302 			return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
1303 		}
1304 
1305 }
1306 
janus_textroom_handle_admin_message(json_t * message)1307 json_t *janus_textroom_handle_admin_message(json_t *message) {
1308 	/* Some requests (e.g., 'create' and 'destroy') can be handled via Admin API */
1309 	int error_code = 0;
1310 	char error_cause[512];
1311 	json_t *response = NULL;
1312 
1313 	JANUS_VALIDATE_JSON_OBJECT(message, request_parameters,
1314 		error_code, error_cause, TRUE,
1315 		JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1316 	if(error_code != 0)
1317 		goto admin_response;
1318 	json_t *request = json_object_get(message, "request");
1319 	const char *request_text = json_string_value(request);
1320 	if(!strcasecmp(request_text, "list")
1321 			|| !strcasecmp(request_text, "listparticipants")
1322 			|| !strcasecmp(request_text, "exists")
1323 			|| !strcasecmp(request_text, "create")
1324 			|| !strcasecmp(request_text, "edit")
1325 			|| !strcasecmp(request_text, "announcement")
1326 			|| !strcasecmp(request_text, "allowed")
1327 			|| !strcasecmp(request_text, "kick")
1328 			|| !strcasecmp(request_text, "destroy")) {
1329 		if(json_object_get(message, "textroom") == NULL)
1330 			json_object_set_new(message, "textroom", json_string(request_text));
1331 		janus_plugin_result *result = janus_textroom_handle_incoming_request(NULL, NULL, message, FALSE);
1332 		if(result == NULL) {
1333 			JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
1334 			error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
1335 			g_snprintf(error_cause, 512, "JSON error: not an object");
1336 			goto admin_response;
1337 		}
1338 		response = result->content;
1339 		result->content = NULL;
1340 		janus_plugin_result_destroy(result);
1341 		goto admin_response;
1342 	} else {
1343 		JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
1344 		error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
1345 		g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
1346 	}
1347 
1348 admin_response:
1349 		{
1350 			if(!response) {
1351 				/* Prepare JSON error event */
1352 				response = json_object();
1353 				json_object_set_new(response, "textroom", json_string("event"));
1354 				json_object_set_new(response, "error_code", json_integer(error_code));
1355 				json_object_set_new(response, "error", json_string(error_cause));
1356 			}
1357 			return response;
1358 		}
1359 
1360 }
1361 
janus_textroom_setup_media(janus_plugin_session * handle)1362 void janus_textroom_setup_media(janus_plugin_session *handle) {
1363 	JANUS_LOG(LOG_INFO, "[%s-%p] WebRTC media is now available\n", JANUS_TEXTROOM_PACKAGE, handle);
1364 	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1365 		return;
1366 	janus_mutex_lock(&sessions_mutex);
1367 	janus_textroom_session *session = janus_textroom_lookup_session(handle);
1368 	if(!session) {
1369 		janus_mutex_unlock(&sessions_mutex);
1370 		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1371 		return;
1372 	}
1373 	if(session->destroyed) {
1374 		janus_mutex_unlock(&sessions_mutex);
1375 		return;
1376 	}
1377 	g_atomic_int_set(&session->hangingup, 0);
1378 	janus_mutex_unlock(&sessions_mutex);
1379 }
1380 
janus_textroom_incoming_rtp(janus_plugin_session * handle,janus_plugin_rtp * packet)1381 void janus_textroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet) {
1382 	/* We don't do audio/video */
1383 }
1384 
janus_textroom_incoming_rtcp(janus_plugin_session * handle,janus_plugin_rtcp * packet)1385 void janus_textroom_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) {
1386 	/* We don't do audio/video */
1387 }
1388 
janus_textroom_incoming_data(janus_plugin_session * handle,janus_plugin_data * packet)1389 void janus_textroom_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet) {
1390 	if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
1391 		return;
1392 	if(packet->binary) {
1393 		/* We don't support binary data in the TextRoom plugin, it has to be text */
1394 		JANUS_LOG(LOG_ERR, "Binary data received, dropping...\n");
1395 		return;
1396 	}
1397 	/* Incoming request from this user: what should we do? */
1398 	janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
1399 	if(!session) {
1400 		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
1401 		return;
1402 	}
1403 	janus_refcount_increase(&session->ref);
1404 	if(session->destroyed) {
1405 		janus_refcount_decrease(&session->ref);
1406 		return;
1407 	}
1408 	char *buf = packet->buffer;
1409 	uint16_t len = packet->length;
1410 	if(buf == NULL || len <= 0) {
1411 		janus_refcount_decrease(&session->ref);
1412 		return;
1413 	}
1414 	char *text = g_malloc(len+1);
1415 	memcpy(text, buf, len);
1416 	*(text+len) = '\0';
1417 	JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes): %s\n", strlen(text), text);
1418 	janus_textroom_handle_incoming_request(handle, text, NULL, FALSE);
1419 	janus_refcount_decrease(&session->ref);
1420 }
1421 
janus_textroom_data_ready(janus_plugin_session * handle)1422 void janus_textroom_data_ready(janus_plugin_session *handle) {
1423 	if(handle == NULL || g_atomic_int_get(&handle->stopped) ||
1424 			g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
1425 		return;
1426 	/* Data channels are writable: we shouldn't send anything before this happens */
1427 	janus_textroom_session *session = (janus_textroom_session *)handle->plugin_handle;
1428 	if(!session || g_atomic_int_get(&session->destroyed) || g_atomic_int_get(&session->hangingup))
1429 		return;
1430 	if(g_atomic_int_compare_and_exchange(&session->dataready, 0, 1)) {
1431 		JANUS_LOG(LOG_INFO, "[%s-%p] Data channel available\n", JANUS_TEXTROOM_PACKAGE, handle);
1432 	}
1433 }
1434 
1435 /* Helper method to handle incoming messages from the data channel */
janus_textroom_handle_incoming_request(janus_plugin_session * handle,char * text,json_t * json,gboolean internal)1436 janus_plugin_result *janus_textroom_handle_incoming_request(janus_plugin_session *handle, char *text, json_t *json, gboolean internal) {
1437 	janus_textroom_session *session = NULL;
1438 	if(handle)
1439 		session = (janus_textroom_session *)handle->plugin_handle;
1440 	/* Parse JSON, if needed */
1441 	json_error_t error;
1442 	json_t *root = text ? json_loads(text, 0, &error) : json;
1443 	g_free(text);
1444 	if(!root) {
1445 		JANUS_LOG(LOG_ERR, "Error parsing data channel message (JSON error: on line %d: %s)\n", error.line, error.text);
1446 		return NULL;
1447 	}
1448 	/* Handle request */
1449 	int error_code = 0;
1450 	char error_cause[512];
1451 	JANUS_VALIDATE_JSON_OBJECT(root, transaction_parameters,
1452 		error_code, error_cause, TRUE,
1453 		JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1454 	const char *transaction_text = NULL;
1455 	json_t *reply = NULL;
1456 	if(error_code != 0)
1457 		goto msg_response;
1458 	json_t *request = json_object_get(root, "textroom");
1459 	json_t *transaction = json_object_get(root, "transaction");
1460 	const char *request_text = json_string_value(request);
1461 	transaction_text = json_string_value(transaction);
1462 	if(!strcasecmp(request_text, "message")) {
1463 		JANUS_VALIDATE_JSON_OBJECT(root, message_parameters,
1464 			error_code, error_cause, TRUE,
1465 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1466 		if(error_code != 0)
1467 			goto msg_response;
1468 		if(!string_ids) {
1469 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1470 				error_code, error_cause, TRUE,
1471 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1472 		} else {
1473 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
1474 				error_code, error_cause, TRUE,
1475 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1476 		}
1477 		if(error_code != 0)
1478 			goto msg_response;
1479 		json_t *room = json_object_get(root, "room");
1480 		guint64 room_id = 0;
1481 		char room_id_num[30], *room_id_str = NULL;
1482 		if(!string_ids) {
1483 			room_id = json_integer_value(room);
1484 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
1485 			room_id_str = room_id_num;
1486 		} else {
1487 			room_id_str = (char *)json_string_value(room);
1488 		}
1489 		janus_mutex_lock(&rooms_mutex);
1490 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
1491 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
1492 		if(textroom == NULL) {
1493 			janus_mutex_unlock(&rooms_mutex);
1494 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
1495 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1496 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
1497 			goto msg_response;
1498 		}
1499 		janus_refcount_increase(&textroom->ref);
1500 		janus_mutex_unlock(&rooms_mutex);
1501 		janus_mutex_lock(&textroom->mutex);
1502 		janus_textroom_participant *participant = g_hash_table_lookup(session->rooms,
1503 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
1504 		if(participant == NULL) {
1505 			janus_mutex_unlock(&textroom->mutex);
1506 			janus_refcount_decrease(&textroom->ref);
1507 			JANUS_LOG(LOG_ERR, "Not in room %s\n", room_id_str);
1508 			error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM;
1509 			g_snprintf(error_cause, 512, "Not in room %s", room_id_str);
1510 			goto msg_response;
1511 		}
1512 		janus_refcount_increase(&participant->ref);
1513 		json_t *username = json_object_get(root, "to");
1514 		json_t *usernames = json_object_get(root, "tos");
1515 		if(username && usernames) {
1516 			janus_mutex_unlock(&textroom->mutex);
1517 			janus_refcount_decrease(&textroom->ref);
1518 			JANUS_LOG(LOG_ERR, "Both to and tos array provided\n");
1519 			error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
1520 			g_snprintf(error_cause, 512, "Both to and tos array provided");
1521 			goto msg_response;
1522 		}
1523 		json_t *text = json_object_get(root, "text");
1524 		const char *message = json_string_value(text);
1525 		/* Prepare outgoing message */
1526 		json_t *msg = json_object();
1527 		json_object_set_new(msg, "textroom", json_string("message"));
1528 		json_object_set_new(msg, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
1529 		json_object_set_new(msg, "from", json_string(participant->username));
1530 		time_t timer;
1531 		time(&timer);
1532 		struct tm *tm_info = localtime(&timer);
1533 		char msgTime[64];
1534 		strftime(msgTime, sizeof(msgTime), "%FT%T%z", tm_info);
1535 		json_object_set_new(msg, "date", json_string(msgTime));
1536 		json_object_set_new(msg, "text", json_string(message));
1537 		if(username || usernames)
1538 			json_object_set_new(msg, "whisper", json_true());
1539 		char *msg_text = json_dumps(msg, json_format);
1540 		if(msg_text == NULL) {
1541 			json_decref(msg);
1542 			janus_mutex_unlock(&textroom->mutex);
1543 			janus_refcount_decrease(&textroom->ref);
1544 			JANUS_LOG(LOG_ERR, "Failed to stringify message...\n");
1545 			error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1546 			g_snprintf(error_cause, 512, "Failed to stringify message");
1547 			goto msg_response;
1548 		}
1549 		char *history_text = NULL;
1550 		if(textroom->history) {
1551 			json_object_set_new(msg, "display", json_string(participant->display));
1552 			history_text = json_dumps(msg, json_format);
1553 		}
1554 		json_decref(msg);
1555 		/* Start preparing the response too */
1556 		reply = json_object();
1557 		json_object_set_new(reply, "textroom", json_string("success"));
1558 		/* Who should we send this message to? */
1559 		if(username) {
1560 			/* A single user */
1561 			json_t *sent = json_object();
1562 			const char *to = json_string_value(username);
1563 			JANUS_LOG(LOG_VERB, "To %s in %s: %s\n", to, room_id_str, message);
1564 			janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to);
1565 			if(top) {
1566 				janus_refcount_increase(&top->ref);
1567 				janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = msg_text, .length = strlen(msg_text) };
1568 				gateway->relay_data(top->session->handle, &data);
1569 				janus_refcount_decrease(&top->ref);
1570 				json_object_set_new(sent, to, json_true());
1571 			} else {
1572 				JANUS_LOG(LOG_WARN, "User %s is not in room %s, failed to send message\n", to, room_id_str);
1573 				json_object_set_new(sent, to, json_false());
1574 			}
1575 			json_object_set_new(reply, "sent", sent);
1576 		} else if(usernames) {
1577 			/* A limited number of users */
1578 			json_t *sent = json_object();
1579 			size_t i = 0;
1580 			for(i=0; i<json_array_size(usernames); i++) {
1581 				json_t *u = json_array_get(usernames, i);
1582 				const char *to = json_string_value(u);
1583 				JANUS_LOG(LOG_VERB, "To %s in %s: %s\n", to, room_id_str, message);
1584 				janus_textroom_participant *top = g_hash_table_lookup(textroom->participants, to);
1585 				if(top) {
1586 					janus_refcount_increase(&top->ref);
1587 					janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = msg_text, .length = strlen(msg_text) };
1588 					gateway->relay_data(top->session->handle, &data);
1589 					janus_refcount_decrease(&top->ref);
1590 					json_object_set_new(sent, to, json_true());
1591 				} else {
1592 					JANUS_LOG(LOG_WARN, "User %s is not in room %s, failed to send message\n", to, room_id_str);
1593 					json_object_set_new(sent, to, json_false());
1594 				}
1595 			}
1596 			json_object_set_new(reply, "sent", sent);
1597 		} else {
1598 			/* Everybody in the room */
1599 			JANUS_LOG(LOG_VERB, "To everybody in %s: %s\n", room_id_str, message);
1600 			if(textroom->participants) {
1601 				GHashTableIter iter;
1602 				gpointer value;
1603 				g_hash_table_iter_init(&iter, textroom->participants);
1604 				while(g_hash_table_iter_next(&iter, NULL, &value)) {
1605 					janus_textroom_participant *top = value;
1606 					JANUS_LOG(LOG_VERB, "  >> To %s in %s: %s\n", top->username, room_id_str, message);
1607 					janus_refcount_increase(&top->ref);
1608 					janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = msg_text, .length = strlen(msg_text) };
1609 					gateway->relay_data(top->session->handle, &data);
1610 					janus_refcount_decrease(&top->ref);
1611 				}
1612 			}
1613 			if(textroom->history && history_text) {
1614 				/* Store in the history */
1615 				g_queue_push_tail(textroom->history, history_text);
1616 				if(g_queue_get_length(textroom->history) > textroom->history_size) {
1617 					char *text = (char *)g_queue_pop_head(textroom->history);
1618 					g_free(text);
1619 				}
1620 			}
1621 #ifdef HAVE_LIBCURL
1622 			/* Is there a backend waiting for this message too? */
1623 			if(textroom->http_backend) {
1624 				/* Prepare the libcurl context */
1625 				CURLcode res;
1626 				CURL *curl = curl_easy_init();
1627 				if(curl == NULL) {
1628 					JANUS_LOG(LOG_ERR, "Error initializing CURL context\n");
1629 				} else {
1630 					curl_easy_setopt(curl, CURLOPT_URL, textroom->http_backend);
1631 					struct curl_slist *headers = NULL;
1632 					headers = curl_slist_append(headers, "Accept: application/json");
1633 					headers = curl_slist_append(headers, "Content-Type: application/json");
1634 					headers = curl_slist_append(headers, "charsets: utf-8");
1635 					curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
1636 					curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msg_text);
1637 					curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, janus_textroom_write_data);
1638 					/* Send the request */
1639 					res = curl_easy_perform(curl);
1640 					if(res != CURLE_OK) {
1641 						JANUS_LOG(LOG_ERR, "Couldn't relay event to the backend: %s\n", curl_easy_strerror(res));
1642 					} else {
1643 						JANUS_LOG(LOG_DBG, "Event sent!\n");
1644 					}
1645 					curl_easy_cleanup(curl);
1646 					curl_slist_free_all(headers);
1647 				}
1648 			}
1649 #endif
1650 		}
1651 		janus_refcount_decrease(&participant->ref);
1652 		free(msg_text);
1653 		janus_mutex_unlock(&textroom->mutex);
1654 		janus_refcount_decrease(&textroom->ref);
1655 		/* By default we send a confirmation back to the user that sent this message:
1656 		 * if the user passed an ack=false, though, we don't do that */
1657 		json_t *ack = json_object_get(root, "ack");
1658 		if(!internal && (ack == NULL || json_is_true(ack))) {
1659 			/* Send response back */
1660 		} else {
1661 			internal = TRUE;
1662 			json_decref(reply);
1663 			reply = NULL;
1664 		}
1665 	} else if(!strcasecmp(request_text, "join")) {
1666 		JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
1667 			error_code, error_cause, TRUE,
1668 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1669 		if(error_code != 0)
1670 			goto msg_response;
1671 		if(!string_ids) {
1672 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1673 				error_code, error_cause, TRUE,
1674 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1675 		} else {
1676 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
1677 				error_code, error_cause, TRUE,
1678 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1679 		}
1680 		if(error_code != 0)
1681 			goto msg_response;
1682 		json_t *room = json_object_get(root, "room");
1683 		guint64 room_id = 0;
1684 		char room_id_num[30], *room_id_str = NULL;
1685 		if(!string_ids) {
1686 			room_id = json_integer_value(room);
1687 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
1688 			room_id_str = room_id_num;
1689 		} else {
1690 			room_id_str = (char *)json_string_value(room);
1691 		}
1692 		janus_mutex_lock(&rooms_mutex);
1693 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
1694 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
1695 		if(textroom == NULL) {
1696 			janus_mutex_unlock(&rooms_mutex);
1697 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
1698 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1699 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
1700 			goto msg_response;
1701 		}
1702 		janus_refcount_increase(&textroom->ref);
1703 		janus_mutex_unlock(&rooms_mutex);
1704 		janus_mutex_lock(&textroom->mutex);
1705 		/* A PIN may be required for this action */
1706 		JANUS_CHECK_SECRET(textroom->room_pin, root, "pin", error_code, error_cause,
1707 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1708 		if(error_code != 0) {
1709 			janus_mutex_unlock(&textroom->mutex);
1710 			janus_refcount_decrease(&textroom->ref);
1711 			goto msg_response;
1712 		}
1713 		janus_mutex_lock(&session->mutex);
1714 		if(g_hash_table_lookup(session->rooms, string_ids ? (gpointer)room_id_str : (gpointer)&room_id) != NULL) {
1715 			janus_mutex_unlock(&session->mutex);
1716 			janus_mutex_unlock(&textroom->mutex);
1717 			janus_refcount_decrease(&textroom->ref);
1718 			JANUS_LOG(LOG_ERR, "Already in room %s\n", room_id_str);
1719 			error_code = JANUS_TEXTROOM_ERROR_ALREADY_IN_ROOM;
1720 			g_snprintf(error_cause, 512, "Already in room %s", room_id_str);
1721 			goto msg_response;
1722 		}
1723 		json_t *username = json_object_get(root, "username");
1724 		const char *username_text = json_string_value(username);
1725 		janus_textroom_participant *participant = g_hash_table_lookup(textroom->participants, username_text);
1726 		if(participant != NULL) {
1727 			janus_mutex_unlock(&session->mutex);
1728 			janus_mutex_unlock(&textroom->mutex);
1729 			janus_refcount_decrease(&textroom->ref);
1730 			JANUS_LOG(LOG_ERR, "Username already taken\n");
1731 			error_code = JANUS_TEXTROOM_ERROR_USERNAME_EXISTS;
1732 			g_snprintf(error_cause, 512, "Username already taken");
1733 			goto msg_response;
1734 		}
1735 		/* A token might be required too */
1736 		if(textroom->check_tokens) {
1737 			json_t *token = json_object_get(root, "token");
1738 			const char *token_text = token ? json_string_value(token) : NULL;
1739 			if(token_text == NULL || g_hash_table_lookup(textroom->allowed, token_text) == NULL) {
1740 				janus_mutex_unlock(&session->mutex);
1741 				janus_mutex_unlock(&textroom->mutex);
1742 				janus_refcount_decrease(&textroom->ref);
1743 				JANUS_LOG(LOG_ERR, "Unauthorized (not in the allowed list)\n");
1744 				error_code = JANUS_TEXTROOM_ERROR_UNAUTHORIZED;
1745 				g_snprintf(error_cause, 512, "Unauthorized (not in the allowed list)");
1746 				goto msg_response;
1747 			}
1748 		}
1749 		json_t *display = json_object_get(root, "display");
1750 		const char *display_text = json_string_value(display);
1751 		/* Create a participant instance */
1752 		participant = g_malloc(sizeof(janus_textroom_participant));
1753 		participant->session = session;
1754 		participant->room = textroom;
1755 		participant->username = g_strdup(username_text);
1756 		participant->display = display_text ? g_strdup(display_text) : NULL;
1757 		participant->destroyed = 0;
1758 		janus_mutex_init(&participant->mutex);
1759 		janus_refcount_init(&participant->ref, janus_textroom_participant_free);
1760 		janus_refcount_increase(&participant->ref);
1761 		g_hash_table_insert(session->rooms,
1762 			string_ids ? (gpointer)g_strdup(textroom->room_id_str) : (gpointer)janus_uint64_dup(textroom->room_id),
1763 			participant);
1764 		janus_refcount_increase(&participant->ref);
1765 		g_hash_table_insert(textroom->participants, participant->username, participant);
1766 		/* Check if we need to send some history back */
1767 		json_t *history = json_object_get(root, "history");
1768 		gboolean send_history = history ? json_is_true(history) : TRUE;
1769 		if(send_history) {
1770 			if(textroom->history != NULL && textroom->history->head != NULL) {
1771 				GList *temp = textroom->history->head;
1772 				char *text = NULL;
1773 				janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = NULL, .length = 0 };
1774 				while(temp) {
1775 					text = (char *)temp->data;
1776 					data.buffer = text;
1777 					data.length = strlen(text);
1778 					gateway->relay_data(handle, &data);
1779 					temp = temp->next;
1780 				}
1781 			}
1782 		}
1783 		/* Notify all participants */
1784 		JANUS_LOG(LOG_VERB, "Notifying all participants about the new join\n");
1785 		json_t *list = json_array();
1786 		if(textroom->participants) {
1787 			/* Prepare event */
1788 			json_t *event = json_object();
1789 			json_object_set_new(event, "textroom", json_string("join"));
1790 			json_object_set_new(event, "room", string_ids ? json_string(textroom->room_id_str) : json_integer(textroom->room_id));
1791 			json_object_set_new(event, "username", json_string(username_text));
1792 			if(display_text != NULL)
1793 				json_object_set_new(event, "display", json_string(display_text));
1794 			char *event_text = json_dumps(event, json_format);
1795 			json_decref(event);
1796 			if(event_text == NULL) {
1797 				janus_mutex_unlock(&session->mutex);
1798 				janus_mutex_unlock(&textroom->mutex);
1799 				janus_refcount_decrease(&textroom->ref);
1800 				JANUS_LOG(LOG_ERR, "Failed to stringify message...\n");
1801 				error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1802 				g_snprintf(error_cause, 512, "Failed to stringify message");
1803 				goto msg_response;
1804 			}
1805 			janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = event_text, .length = strlen(event_text) };
1806 			gateway->relay_data(handle, &data);
1807 			/* Broadcast */
1808 			GHashTableIter iter;
1809 			gpointer value;
1810 			g_hash_table_iter_init(&iter, textroom->participants);
1811 			while(g_hash_table_iter_next(&iter, NULL, &value)) {
1812 				janus_textroom_participant *top = value;
1813 				if(top == participant)
1814 					continue;	/* Skip us */
1815 				janus_refcount_increase(&top->ref);
1816 				JANUS_LOG(LOG_VERB, "  >> To %s in %s\n", top->username, room_id_str);
1817 				gateway->relay_data(top->session->handle, &data);
1818 				/* Take note of this user */
1819 				json_t *p = json_object();
1820 				json_object_set_new(p, "username", json_string(top->username));
1821 				if(top->display != NULL)
1822 					json_object_set_new(p, "display", json_string(top->display));
1823 				json_array_append_new(list, p);
1824 				janus_refcount_decrease(&top->ref);
1825 			}
1826 			free(event_text);
1827 		}
1828 		janus_mutex_unlock(&session->mutex);
1829 		janus_mutex_unlock(&textroom->mutex);
1830 		janus_refcount_decrease(&textroom->ref);
1831 		if(!internal) {
1832 			/* Send response back */
1833 			reply = json_object();
1834 			json_object_set_new(reply, "textroom", json_string("success"));
1835 			json_object_set_new(reply, "participants", list);
1836 		}
1837 		/* Also notify event handlers */
1838 		if(notify_events && gateway->events_is_enabled()) {
1839 			json_t *info = json_object();
1840 			json_object_set_new(info, "event", json_string("join"));
1841 			json_object_set_new(info, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
1842 			json_object_set_new(info, "username", json_string(username_text));
1843 			if(display_text)
1844 				json_object_set_new(info, "display", json_string(display_text));
1845 			gateway->notify_event(&janus_textroom_plugin, session->handle, info);
1846 		}
1847 	} else if(!strcasecmp(request_text, "leave")) {
1848 		if(!string_ids) {
1849 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
1850 				error_code, error_cause, TRUE,
1851 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1852 		} else {
1853 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
1854 				error_code, error_cause, TRUE,
1855 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
1856 		}
1857 		if(error_code != 0)
1858 			goto msg_response;
1859 		json_t *room = json_object_get(root, "room");
1860 		guint64 room_id = 0;
1861 		char room_id_num[30], *room_id_str = NULL;
1862 		if(!string_ids) {
1863 			room_id = json_integer_value(room);
1864 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
1865 			room_id_str = room_id_num;
1866 		} else {
1867 			room_id_str = (char *)json_string_value(room);
1868 		}
1869 		janus_mutex_lock(&rooms_mutex);
1870 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
1871 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
1872 		if(textroom == NULL) {
1873 			janus_mutex_unlock(&rooms_mutex);
1874 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
1875 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
1876 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
1877 			goto msg_response;
1878 		}
1879 		janus_refcount_increase(&textroom->ref);
1880 		janus_mutex_unlock(&rooms_mutex);
1881 		janus_mutex_lock(&textroom->mutex);
1882 		janus_mutex_lock(&session->mutex);
1883 		janus_textroom_participant *participant = g_hash_table_lookup(session->rooms,
1884 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
1885 		if(participant == NULL) {
1886 			janus_mutex_unlock(&session->mutex);
1887 			janus_mutex_unlock(&textroom->mutex);
1888 			janus_refcount_decrease(&textroom->ref);
1889 			JANUS_LOG(LOG_ERR, "Not in room %s\n", room_id_str);
1890 			error_code = JANUS_TEXTROOM_ERROR_NOT_IN_ROOM;
1891 			g_snprintf(error_cause, 512, "Not in room %s", room_id_str);
1892 			goto msg_response;
1893 		}
1894 		janus_refcount_increase(&participant->ref);
1895 		g_hash_table_remove(session->rooms, string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
1896 		g_hash_table_remove(textroom->participants, participant->username);
1897 		participant->session = NULL;
1898 		participant->room = NULL;
1899 		/* Notify all participants */
1900 		JANUS_LOG(LOG_VERB, "Notifying all participants about the new leave\n");
1901 		if(textroom->participants) {
1902 			/* Prepare event */
1903 			json_t *event = json_object();
1904 			json_object_set_new(event, "textroom", json_string("leave"));
1905 			json_object_set_new(event, "room", string_ids ? json_string(textroom->room_id_str) : json_integer(textroom->room_id));
1906 			json_object_set_new(event, "username", json_string(participant->username));
1907 			char *event_text = json_dumps(event, json_format);
1908 			json_decref(event);
1909 			if(event_text == NULL) {
1910 				janus_mutex_unlock(&session->mutex);
1911 				janus_mutex_unlock(&textroom->mutex);
1912 				janus_refcount_decrease(&textroom->ref);
1913 				janus_refcount_decrease(&participant->ref);
1914 				janus_textroom_participant_destroy(participant);
1915 				JANUS_LOG(LOG_ERR, "Failed to stringify message...\n");
1916 				error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
1917 				g_snprintf(error_cause, 512, "Failed to stringify message");
1918 				goto msg_response;
1919 			}
1920 			janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = event_text, .length = strlen(event_text) };
1921 			gateway->relay_data(handle, &data);
1922 			/* Broadcast */
1923 			GHashTableIter iter;
1924 			gpointer value;
1925 			g_hash_table_iter_init(&iter, textroom->participants);
1926 			while(g_hash_table_iter_next(&iter, NULL, &value)) {
1927 				janus_textroom_participant *top = value;
1928 				if(top == participant)
1929 					continue;	/* Skip us */
1930 				janus_refcount_increase(&top->ref);
1931 				JANUS_LOG(LOG_VERB, "  >> To %s in %s\n", top->username, room_id_str);
1932 				gateway->relay_data(top->session->handle, &data);
1933 				janus_refcount_decrease(&top->ref);
1934 			}
1935 			free(event_text);
1936 		}
1937 		/* Also notify event handlers */
1938 		if(notify_events && gateway->events_is_enabled()) {
1939 			json_t *info = json_object();
1940 			json_object_set_new(info, "event", json_string("leave"));
1941 			json_object_set_new(info, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
1942 			json_object_set_new(info, "username", json_string(participant->username));
1943 			gateway->notify_event(&janus_textroom_plugin, session->handle, info);
1944 		}
1945 		janus_mutex_unlock(&session->mutex);
1946 		janus_mutex_unlock(&textroom->mutex);
1947 		janus_refcount_decrease(&textroom->ref);
1948 		janus_refcount_decrease(&participant->ref);
1949 		janus_textroom_participant_destroy(participant);
1950 		if(!internal) {
1951 			/* Send response back */
1952 			reply = json_object();
1953 			json_object_set_new(reply, "textroom", json_string("success"));
1954 		}
1955 	} else if(!strcasecmp(request_text, "list")) {
1956 		/* List all rooms (but private ones) and their details (except for the secret, of course...) */
1957 		JANUS_LOG(LOG_VERB, "Request for the list for all text rooms\n");
1958 		gboolean lock_room_list = TRUE;
1959 		if(admin_key != NULL) {
1960 			json_t *admin_key_json = json_object_get(root, "admin_key");
1961 			/* Verify admin_key if it was provided */
1962 			if(admin_key_json != NULL && json_is_string(admin_key_json) && strlen(json_string_value(admin_key_json)) > 0) {
1963 				JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
1964 					JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
1965 				if(error_code != 0) {
1966 					goto msg_response;
1967 				} else {
1968 					lock_room_list = FALSE;
1969 				}
1970 			}
1971 		}
1972 		json_t *list = json_array();
1973 		janus_mutex_lock(&rooms_mutex);
1974 		GHashTableIter iter;
1975 		gpointer value;
1976 		g_hash_table_iter_init(&iter, rooms);
1977 		while(g_hash_table_iter_next(&iter, NULL, &value)) {
1978 			janus_textroom_room *room = value;
1979 			if(!room)
1980 				continue;
1981 			janus_refcount_increase(&room->ref);
1982 			janus_mutex_lock(&room->mutex);
1983 			if(room->is_private && lock_room_list) {
1984 				/* Skip private room if no valid admin_key was provided */
1985 				JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
1986 				janus_mutex_unlock(&room->mutex);
1987 				janus_refcount_decrease(&room->ref);
1988 				continue;
1989 			}
1990 			json_t *rl = json_object();
1991 			json_object_set_new(rl, "room", string_ids ? json_string(room->room_id_str) : json_integer(room->room_id));
1992 			json_object_set_new(rl, "description", json_string(room->room_name));
1993 			json_object_set_new(rl, "pin_required", room->room_pin ? json_true() : json_false());
1994 			json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
1995 			json_object_set_new(rl, "history", json_integer(room->history_size));
1996 			json_array_append_new(list, rl);
1997 			janus_mutex_unlock(&room->mutex);
1998 			janus_refcount_decrease(&room->ref);
1999 		}
2000 		janus_mutex_unlock(&rooms_mutex);
2001 		if(!internal) {
2002 			/* Send response back */
2003 			reply = json_object();
2004 			json_object_set_new(reply, "textroom", json_string("success"));
2005 			json_object_set_new(reply, "list", list);
2006 		}
2007 	} else if(!strcasecmp(request_text, "listparticipants")) {
2008 		/* List all participants in a room */
2009 		if(!string_ids) {
2010 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2011 				error_code, error_cause, TRUE,
2012 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2013 		} else {
2014 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
2015 				error_code, error_cause, TRUE,
2016 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2017 		}
2018 		if(error_code != 0)
2019 			goto msg_response;
2020 		json_t *room = json_object_get(root, "room");
2021 		guint64 room_id = 0;
2022 		char room_id_num[30], *room_id_str = NULL;
2023 		if(!string_ids) {
2024 			room_id = json_integer_value(room);
2025 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2026 			room_id_str = room_id_num;
2027 		} else {
2028 			room_id_str = (char *)json_string_value(room);
2029 		}
2030 		janus_mutex_lock(&rooms_mutex);
2031 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
2032 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2033 		if(textroom == NULL || g_atomic_int_get(&textroom->destroyed)) {
2034 			janus_mutex_unlock(&rooms_mutex);
2035 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
2036 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
2037 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
2038 			goto msg_response;
2039 		}
2040 		janus_refcount_increase(&textroom->ref);
2041 		/* Return a list of all participants */
2042 		json_t *list = json_array();
2043 		GHashTableIter iter;
2044 		gpointer value;
2045 		g_hash_table_iter_init(&iter, textroom->participants);
2046 		while (!g_atomic_int_get(&textroom->destroyed) && g_hash_table_iter_next(&iter, NULL, &value)) {
2047 			janus_textroom_participant *p = value;
2048 			json_t *pl = json_object();
2049 			json_object_set_new(pl, "username", json_string(p->username));
2050 			if(p->display != NULL)
2051 				json_object_set_new(pl, "display", json_string(p->display));
2052 			json_array_append_new(list, pl);
2053 		}
2054 		janus_refcount_decrease(&textroom->ref);
2055 		janus_mutex_unlock(&rooms_mutex);
2056 		if(!internal) {
2057 			/* Send response back */
2058 			reply = json_object();
2059 			json_object_set_new(reply, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2060 			json_object_set_new(reply, "participants", list);
2061 		}
2062 	} else if(!strcasecmp(request_text, "allowed")) {
2063 		JANUS_LOG(LOG_VERB, "Attempt to edit the list of allowed participants in an existing TextRoom room\n");
2064 		JANUS_VALIDATE_JSON_OBJECT(root, allowed_parameters,
2065 			error_code, error_cause, TRUE,
2066 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2067 		if(error_code != 0)
2068 			goto msg_response;
2069 		if(!string_ids) {
2070 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2071 				error_code, error_cause, TRUE,
2072 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2073 		} else {
2074 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
2075 				error_code, error_cause, TRUE,
2076 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2077 		}
2078 		if(error_code != 0)
2079 			goto msg_response;
2080 		json_t *action = json_object_get(root, "action");
2081 		json_t *room = json_object_get(root, "room");
2082 		json_t *allowed = json_object_get(root, "allowed");
2083 		const char *action_text = json_string_value(action);
2084 		if(strcasecmp(action_text, "enable") && strcasecmp(action_text, "disable") &&
2085 				strcasecmp(action_text, "add") && strcasecmp(action_text, "remove")) {
2086 			JANUS_LOG(LOG_ERR, "Unsupported action '%s' (allowed)\n", action_text);
2087 			error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
2088 			g_snprintf(error_cause, 512, "Unsupported action '%s' (allowed)", action_text);
2089 			goto msg_response;
2090 		}
2091 		guint64 room_id = 0;
2092 		char room_id_num[30], *room_id_str = NULL;
2093 		if(!string_ids) {
2094 			room_id = json_integer_value(room);
2095 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2096 			room_id_str = room_id_num;
2097 		} else {
2098 			room_id_str = (char *)json_string_value(room);
2099 		}
2100 		janus_mutex_lock(&rooms_mutex);
2101 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
2102 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2103 		if(textroom == NULL) {
2104 			janus_mutex_unlock(&rooms_mutex);
2105 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
2106 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
2107 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
2108 			goto msg_response;
2109 		}
2110 		janus_mutex_lock(&textroom->mutex);
2111 		/* A secret may be required for this action */
2112 		JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
2113 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
2114 		if(error_code != 0) {
2115 			janus_mutex_unlock(&textroom->mutex);
2116 			janus_mutex_unlock(&rooms_mutex);
2117 			goto msg_response;
2118 		}
2119 		if(!strcasecmp(action_text, "enable")) {
2120 			JANUS_LOG(LOG_VERB, "Enabling the check on allowed authorization tokens for room %s\n", room_id_str);
2121 			textroom->check_tokens = TRUE;
2122 		} else if(!strcasecmp(action_text, "disable")) {
2123 			JANUS_LOG(LOG_VERB, "Disabling the check on allowed authorization tokens for room %s (free entry)\n", room_id_str);
2124 			textroom->check_tokens = FALSE;
2125 		} else {
2126 			gboolean add = !strcasecmp(action_text, "add");
2127 			if(allowed) {
2128 				/* Make sure the "allowed" array only contains strings */
2129 				gboolean ok = TRUE;
2130 				if(json_array_size(allowed) > 0) {
2131 					size_t i = 0;
2132 					for(i=0; i<json_array_size(allowed); i++) {
2133 						json_t *a = json_array_get(allowed, i);
2134 						if(!a || !json_is_string(a)) {
2135 							ok = FALSE;
2136 							break;
2137 						}
2138 					}
2139 				}
2140 				if(!ok) {
2141 					JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
2142 					error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
2143 					g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
2144 					janus_mutex_unlock(&textroom->mutex);
2145 					janus_mutex_unlock(&rooms_mutex);
2146 					goto msg_response;
2147 				}
2148 				size_t i = 0;
2149 				for(i=0; i<json_array_size(allowed); i++) {
2150 					const char *token = json_string_value(json_array_get(allowed, i));
2151 					if(add) {
2152 						if(!g_hash_table_lookup(textroom->allowed, token))
2153 							g_hash_table_insert(textroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
2154 					} else {
2155 						g_hash_table_remove(textroom->allowed, token);
2156 					}
2157 				}
2158 			}
2159 		}
2160 		if(!internal) {
2161 			/* Send response back */
2162 			reply = json_object();
2163 			json_object_set_new(reply, "textroom", json_string("success"));
2164 			json_object_set_new(reply, "room", string_ids ? json_string(textroom->room_id_str) : json_integer(textroom->room_id));
2165 			json_t *list = json_array();
2166 			if(strcasecmp(action_text, "disable")) {
2167 				if(g_hash_table_size(textroom->allowed) > 0) {
2168 					GHashTableIter iter;
2169 					gpointer key;
2170 					g_hash_table_iter_init(&iter, textroom->allowed);
2171 					while(g_hash_table_iter_next(&iter, &key, NULL)) {
2172 						char *token = key;
2173 						json_array_append_new(list, json_string(token));
2174 					}
2175 				}
2176 				json_object_set_new(reply, "allowed", list);
2177 			}
2178 			janus_mutex_unlock(&textroom->mutex);
2179 			janus_mutex_unlock(&rooms_mutex);
2180 			JANUS_LOG(LOG_VERB, "TextRoom room allowed list updated\n");
2181 		}
2182 	} else if(!strcasecmp(request_text, "kick")) {
2183 		JANUS_LOG(LOG_VERB, "Attempt to kick a participant from an existing TextRoom room\n");
2184 		JANUS_VALIDATE_JSON_OBJECT(root, kick_parameters,
2185 			error_code, error_cause, TRUE,
2186 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2187 		if(error_code != 0)
2188 			goto msg_response;
2189 		if(!string_ids) {
2190 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2191 				error_code, error_cause, TRUE,
2192 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2193 		} else {
2194 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
2195 				error_code, error_cause, TRUE,
2196 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2197 		}
2198 		if(error_code != 0)
2199 			goto msg_response;
2200 		json_t *room = json_object_get(root, "room");
2201 		json_t *username = json_object_get(root, "username");
2202 		guint64 room_id = 0;
2203 		char room_id_num[30], *room_id_str = NULL;
2204 		if(!string_ids) {
2205 			room_id = json_integer_value(room);
2206 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2207 			room_id_str = room_id_num;
2208 		} else {
2209 			room_id_str = (char *)json_string_value(room);
2210 		}
2211 		janus_mutex_lock(&rooms_mutex);
2212 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
2213 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2214 		if(textroom == NULL) {
2215 			janus_mutex_unlock(&rooms_mutex);
2216 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
2217 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
2218 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
2219 			goto msg_response;
2220 		}
2221 		janus_mutex_lock(&textroom->mutex);
2222 		/* A secret may be required for this action */
2223 		JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
2224 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
2225 		if(error_code != 0) {
2226 			janus_mutex_unlock(&textroom->mutex);
2227 			janus_mutex_unlock(&rooms_mutex);
2228 			goto msg_response;
2229 		}
2230 		const char *user_id = json_string_value(username);
2231 		janus_textroom_participant *participant = g_hash_table_lookup(textroom->participants, user_id);
2232 		if(participant == NULL) {
2233 			janus_mutex_unlock(&textroom->mutex);
2234 			janus_mutex_unlock(&rooms_mutex);
2235 			JANUS_LOG(LOG_ERR, "No such participant %s in room %s\n", user_id, room_id_str);
2236 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_USER;
2237 			g_snprintf(error_cause, 512, "No such user %s in room %s", user_id, room_id_str);
2238 			goto msg_response;
2239 		}
2240 		/* Notify all participants */
2241 		JANUS_LOG(LOG_VERB, "Notifying all participants about the new kick\n");
2242 		if(textroom->participants) {
2243 			/* Prepare event */
2244 			json_t *event = json_object();
2245 			json_object_set_new(event, "textroom", json_string("kicked"));
2246 			json_object_set_new(event, "room", string_ids ? json_string(textroom->room_id_str) : json_integer(textroom->room_id));
2247 			json_object_set_new(event, "username", json_string(participant->username));
2248 			char *event_text = json_dumps(event, json_format);
2249 			json_decref(event);
2250 			if(event_text == NULL) {
2251 				janus_mutex_unlock(&textroom->mutex);
2252 				janus_mutex_unlock(&rooms_mutex);
2253 				JANUS_LOG(LOG_ERR, "Failed to stringify message...\n");
2254 				error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
2255 				g_snprintf(error_cause, 512, "Failed to stringify message");
2256 				goto msg_response;
2257 			}
2258 			/* Broadcast */
2259 			GHashTableIter iter;
2260 			gpointer value;
2261 			g_hash_table_iter_init(&iter, textroom->participants);
2262 			while(g_hash_table_iter_next(&iter, NULL, &value)) {
2263 				janus_textroom_participant *top = value;
2264 				JANUS_LOG(LOG_VERB, "  >> To %s in %s\n", top->username, room_id_str);
2265 				janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = event_text, .length = strlen(event_text) };
2266 				gateway->relay_data(top->session->handle, &data);
2267 			}
2268 			free(event_text);
2269 		}
2270 		/* Also notify event handlers */
2271 		if(notify_events && gateway->events_is_enabled()) {
2272 			json_t *info = json_object();
2273 			json_object_set_new(info, "textroom", json_string("kicked"));
2274 			json_object_set_new(info, "room", string_ids ? json_string(textroom->room_id_str) : json_integer(textroom->room_id));
2275 			json_object_set_new(info, "username", json_string(participant->username));
2276 			gateway->notify_event(&janus_textroom_plugin, session->handle, info);
2277 		}
2278 		/* Remove user from list */
2279 		g_hash_table_remove(participant->session->rooms, string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2280 		g_hash_table_remove(textroom->participants, participant->username);
2281 		participant->session = NULL;
2282 		participant->room = NULL;
2283 		g_free(participant->username);
2284 		g_free(participant->display);
2285 		g_free(participant);
2286 		/* Done */
2287 		janus_mutex_unlock(&textroom->mutex);
2288 		janus_mutex_unlock(&rooms_mutex);
2289 		if(!internal) {
2290 			/* Send response back */
2291 			reply = json_object();
2292 			json_object_set_new(reply, "textbridge", json_string("success"));
2293 		}
2294 	} else if(!strcasecmp(request_text, "announcement")) {
2295 		JANUS_LOG(LOG_VERB, "Attempt to send a TextRoom announcement\n");
2296 		JANUS_VALIDATE_JSON_OBJECT(root, announcement_parameters,
2297 			error_code, error_cause, TRUE,
2298 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2299 		if(error_code != 0)
2300 			goto msg_response;
2301 		if(!string_ids) {
2302 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2303 				error_code, error_cause, TRUE,
2304 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2305 		} else {
2306 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
2307 				error_code, error_cause, TRUE,
2308 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2309 		}
2310 		if(error_code != 0)
2311 			goto msg_response;
2312 		json_t *room = json_object_get(root, "room");
2313 		guint64 room_id = 0;
2314 		char room_id_num[30], *room_id_str = NULL;
2315 		if(!string_ids) {
2316 			room_id = json_integer_value(room);
2317 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2318 			room_id_str = room_id_num;
2319 		} else {
2320 			room_id_str = (char *)json_string_value(room);
2321 		}
2322 		janus_mutex_lock(&rooms_mutex);
2323 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
2324 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2325 		if(textroom == NULL) {
2326 			janus_mutex_unlock(&rooms_mutex);
2327 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
2328 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
2329 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
2330 			goto msg_response;
2331 		}
2332 		janus_refcount_increase(&textroom->ref);
2333 		janus_mutex_unlock(&rooms_mutex);
2334 		janus_mutex_lock(&textroom->mutex);
2335 		/* A secret may be required for this action */
2336 		JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
2337 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
2338 		if(error_code != 0) {
2339 			janus_mutex_unlock(&textroom->mutex);
2340 			janus_refcount_decrease(&textroom->ref);
2341 			goto msg_response;
2342 		}
2343 		json_t *text = json_object_get(root, "text");
2344 		const char *message = json_string_value(text);
2345 		/* Prepare outgoing message */
2346 		json_t *msg = json_object();
2347 		json_object_set_new(msg, "textroom", json_string("announcement"));
2348 		json_object_set_new(msg, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2349 		time_t timer;
2350 		time(&timer);
2351 		struct tm *tm_info = localtime(&timer);
2352 		char msgTime[64];
2353 		strftime(msgTime, sizeof(msgTime), "%FT%T%z", tm_info);
2354 		json_object_set_new(msg, "date", json_string(msgTime));
2355 		json_object_set_new(msg, "text", json_string(message));
2356 		char *msg_text = json_dumps(msg, json_format);
2357 		json_decref(msg);
2358 		if(msg_text == NULL) {
2359 			janus_mutex_unlock(&textroom->mutex);
2360 			janus_refcount_decrease(&textroom->ref);
2361 			JANUS_LOG(LOG_ERR, "Failed to stringify message...\n");
2362 			error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
2363 			g_snprintf(error_cause, 512, "Failed to stringify message");
2364 			goto msg_response;
2365 		}
2366 		/* Send the announcement to everybody in the room */
2367 		if(textroom->participants) {
2368 			GHashTableIter iter;
2369 			gpointer value;
2370 			g_hash_table_iter_init(&iter, textroom->participants);
2371 			while(g_hash_table_iter_next(&iter, NULL, &value)) {
2372 				janus_textroom_participant *top = value;
2373 				JANUS_LOG(LOG_VERB, "  >> To %s in %s: %s\n", top->username, room_id_str, message);
2374 				janus_refcount_increase(&top->ref);
2375 				janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = msg_text, .length = strlen(msg_text) };
2376 				gateway->relay_data(top->session->handle, &data);
2377 				janus_refcount_decrease(&top->ref);
2378 			}
2379 		}
2380 		if(textroom->history) {
2381 			/* Store in the history */
2382 			g_queue_push_tail(textroom->history, g_strdup(msg_text));
2383 			if(g_queue_get_length(textroom->history) > textroom->history_size) {
2384 				char *text = (char *)g_queue_pop_head(textroom->history);
2385 				g_free(text);
2386 			}
2387 		}
2388 #ifdef HAVE_LIBCURL
2389 		/* Is there a backend waiting for this message too? */
2390 		if(textroom->http_backend) {
2391 			/* Prepare the libcurl context */
2392 			CURLcode res;
2393 			CURL *curl = curl_easy_init();
2394 			if(curl == NULL) {
2395 				JANUS_LOG(LOG_ERR, "Error initializing CURL context\n");
2396 			} else {
2397 				curl_easy_setopt(curl, CURLOPT_URL, textroom->http_backend);
2398 				struct curl_slist *headers = NULL;
2399 				headers = curl_slist_append(headers, "Accept: application/json");
2400 				headers = curl_slist_append(headers, "Content-Type: application/json");
2401 				headers = curl_slist_append(headers, "charsets: utf-8");
2402 				curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2403 				curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msg_text);
2404 				curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, janus_textroom_write_data);
2405 				/* Send the request */
2406 				res = curl_easy_perform(curl);
2407 				if(res != CURLE_OK) {
2408 					JANUS_LOG(LOG_ERR, "Couldn't relay event to the backend: %s\n", curl_easy_strerror(res));
2409 				} else {
2410 					JANUS_LOG(LOG_DBG, "Event sent!\n");
2411 				}
2412 				curl_easy_cleanup(curl);
2413 				curl_slist_free_all(headers);
2414 			}
2415 		}
2416 #endif
2417 		free(msg_text);
2418 		janus_mutex_unlock(&textroom->mutex);
2419 		janus_refcount_decrease(&textroom->ref);
2420 		if(!internal) {
2421 			/* Send response back */
2422 			reply = json_object();
2423 			json_object_set_new(reply, "textroom", json_string("success"));
2424 		}
2425 	} else if(!strcasecmp(request_text, "create")) {
2426 		JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
2427 			error_code, error_cause, TRUE,
2428 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2429 		if(error_code != 0)
2430 			goto msg_response;
2431 		if(!string_ids) {
2432 			JANUS_VALIDATE_JSON_OBJECT(root, roomopt_parameters,
2433 				error_code, error_cause, TRUE,
2434 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2435 		} else {
2436 			JANUS_VALIDATE_JSON_OBJECT(root, roomstropt_parameters,
2437 				error_code, error_cause, TRUE,
2438 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2439 		}
2440 		if(error_code != 0)
2441 			goto msg_response;
2442 		if(admin_key != NULL) {
2443 			/* An admin key was specified: make sure it was provided, and that it's valid */
2444 			JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
2445 				error_code, error_cause, TRUE,
2446 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2447 			if(error_code != 0)
2448 				goto msg_response;
2449 			JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
2450 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
2451 			if(error_code != 0)
2452 				goto msg_response;
2453 		}
2454 		json_t *room = json_object_get(root, "room");
2455 		json_t *desc = json_object_get(root, "description");
2456 		json_t *is_private = json_object_get(root, "is_private");
2457 		json_t *allowed = json_object_get(root, "allowed");
2458 		json_t *secret = json_object_get(root, "secret");
2459 		json_t *pin = json_object_get(root, "pin");
2460 		json_t *history = json_object_get(root, "history");
2461 		json_t *post = json_object_get(root, "post");
2462 		json_t *permanent = json_object_get(root, "permanent");
2463 		if(allowed) {
2464 			/* Make sure the "allowed" array only contains strings */
2465 			gboolean ok = TRUE;
2466 			if(json_array_size(allowed) > 0) {
2467 				size_t i = 0;
2468 				for(i=0; i<json_array_size(allowed); i++) {
2469 					json_t *a = json_array_get(allowed, i);
2470 					if(!a || !json_is_string(a)) {
2471 						ok = FALSE;
2472 						break;
2473 					}
2474 				}
2475 			}
2476 			if(!ok) {
2477 				JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
2478 				error_code = JANUS_TEXTROOM_ERROR_INVALID_ELEMENT;
2479 				g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
2480 				goto msg_response;
2481 			}
2482 		}
2483 		gboolean save = permanent ? json_is_true(permanent) : FALSE;
2484 		if(save && config == NULL) {
2485 			JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
2486 			error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
2487 			g_snprintf(error_cause, 512, "No configuration file, can't create permanent room");
2488 			goto msg_response;
2489 		}
2490 		guint64 room_id = 0;
2491 		char room_id_num[30], *room_id_str = NULL;
2492 		if(!string_ids) {
2493 			room_id = json_integer_value(room);
2494 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2495 			room_id_str = room_id_num;
2496 		} else {
2497 			room_id_str = (char *)json_string_value(room);
2498 		}
2499 		if(room_id == 0 && room_id_str == NULL) {
2500 			JANUS_LOG(LOG_WARN, "Desired room ID is empty, which is not allowed... picking random ID instead\n");
2501 		}
2502 		janus_mutex_lock(&rooms_mutex);
2503 		if(room_id > 0 || room_id_str != NULL) {
2504 			/* Let's make sure the room doesn't exist already */
2505 			if(g_hash_table_lookup(rooms, string_ids ? (gpointer)room_id_str : (gpointer)&room_id) != NULL) {
2506 				/* It does... */
2507 				janus_mutex_unlock(&rooms_mutex);
2508 				error_code = JANUS_TEXTROOM_ERROR_ROOM_EXISTS;
2509 				JANUS_LOG(LOG_ERR, "Room %s already exists!\n", room_id_str);
2510 				g_snprintf(error_cause, 512, "Room %s already exists", room_id_str);
2511 				goto msg_response;
2512 			}
2513 		}
2514 		/* Create the text room */
2515 		janus_textroom_room *textroom = g_malloc0(sizeof(janus_textroom_room));
2516 		/* Generate a random ID */
2517 		gboolean room_id_allocated = FALSE;
2518 		if(!string_ids && room_id == 0) {
2519 			while(room_id == 0) {
2520 				room_id = janus_random_uint64();
2521 				if(g_hash_table_lookup(rooms, &room_id) != NULL) {
2522 					/* Room ID already taken, try another one */
2523 					room_id = 0;
2524 				}
2525 			}
2526 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2527 			room_id_str = room_id_num;
2528 		} else if(string_ids && room_id_str == NULL) {
2529 			while(room_id_str == NULL) {
2530 				room_id_str = janus_random_uuid();
2531 				if(g_hash_table_lookup(rooms, room_id_str) != NULL) {
2532 					/* Room ID already taken, try another one */
2533 					g_clear_pointer(&room_id_str, g_free);
2534 				}
2535 			}
2536 			room_id_allocated = TRUE;
2537 		}
2538 		textroom->room_id = room_id;
2539 		textroom->room_id_str = room_id_str ? g_strdup(room_id_str) : NULL;
2540 		char *description = NULL;
2541 		if(desc != NULL && strlen(json_string_value(desc)) > 0) {
2542 			description = g_strdup(json_string_value(desc));
2543 		} else {
2544 			char roomname[255];
2545 			g_snprintf(roomname, 255, "Room %s", textroom->room_id_str);
2546 			description = g_strdup(roomname);
2547 		}
2548 		textroom->room_name = description;
2549 		textroom->is_private = is_private ? json_is_true(is_private) : FALSE;
2550 		if(secret)
2551 			textroom->room_secret = g_strdup(json_string_value(secret));
2552 		if(pin)
2553 			textroom->room_pin = g_strdup(json_string_value(pin));
2554 		if(history) {
2555 			textroom->history_size = json_integer_value(history);
2556 			if(textroom->history_size > 0)
2557 				textroom->history = g_queue_new();
2558 		}
2559 		if(post) {
2560 #ifdef HAVE_LIBCURL
2561 			/* FIXME Should we check if this is a valid HTTP address? */
2562 			textroom->http_backend = g_strdup(json_string_value(post));
2563 #else
2564 			JANUS_LOG(LOG_WARN, "HTTP backend specified, but libcurl support was not built in...\n");
2565 #endif
2566 		}
2567 		textroom->participants = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)janus_textroom_participant_dereference);
2568 		textroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
2569 		if(allowed != NULL) {
2570 			/* Populate the "allowed" list as an ACL for people trying to join */
2571 			if(json_array_size(allowed) > 0) {
2572 				size_t i = 0;
2573 				for(i=0; i<json_array_size(allowed); i++) {
2574 					const char *token = json_string_value(json_array_get(allowed, i));
2575 					if(!g_hash_table_lookup(textroom->allowed, token))
2576 						g_hash_table_insert(textroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
2577 				}
2578 			}
2579 			textroom->check_tokens = TRUE;
2580 		}
2581 		textroom->destroyed = 0;
2582 		janus_mutex_init(&textroom->mutex);
2583 		janus_refcount_init(&textroom->ref, janus_textroom_room_free);
2584 		g_hash_table_insert(rooms,
2585 			string_ids ? (gpointer)g_strdup(textroom->room_id_str) : (gpointer)janus_uint64_dup(textroom->room_id),
2586 			textroom);
2587 		JANUS_LOG(LOG_VERB, "Created TextRoom: %s (%s, %s, secret: %s, pin: %s)\n",
2588 			textroom->room_id_str, textroom->room_name,
2589 			textroom->is_private ? "private" : "public",
2590 			textroom->room_secret ? textroom->room_secret : "no secret",
2591 			textroom->room_pin ? textroom->room_pin : "no pin");
2592 		if(save) {
2593 			/* This room is permanent: save to the configuration file too
2594 			 * FIXME: We should check if anything fails... */
2595 			JANUS_LOG(LOG_VERB, "Saving room %s permanently in config file\n", textroom->room_id_str);
2596 			janus_mutex_lock(&config_mutex);
2597 			char cat[BUFSIZ], value[BUFSIZ];
2598 			/* The room ID is the category (prefixed by "room-") */
2599 			g_snprintf(cat, BUFSIZ, "room-%s", textroom->room_id_str);
2600 			janus_config_category *c = janus_config_get_create(config, NULL, janus_config_type_category, cat);
2601 			/* Now for the values */
2602 			janus_config_add(config, c, janus_config_item_create("description", textroom->room_name));
2603 			if(textroom->is_private)
2604 				janus_config_add(config, c, janus_config_item_create("is_private", "yes"));
2605 			if(textroom->room_secret)
2606 				janus_config_add(config, c, janus_config_item_create("secret", textroom->room_secret));
2607 			if(textroom->room_pin)
2608 				janus_config_add(config, c, janus_config_item_create("pin", textroom->room_pin));
2609 			if(textroom->history_size) {
2610 				g_snprintf(value, BUFSIZ, "%d", textroom->history_size);
2611 				janus_config_add(config, c, janus_config_item_create("history", value));
2612 			}
2613 			if(textroom->http_backend)
2614 				janus_config_add(config, c, janus_config_item_create("post", textroom->http_backend));
2615 			/* Save modified configuration */
2616 			if(janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE) < 0)
2617 				save = FALSE;	/* This will notify the user the room is not permanent */
2618 			janus_mutex_unlock(&config_mutex);
2619 		}
2620 		/* Show updated rooms list */
2621 		GHashTableIter iter;
2622 		gpointer value;
2623 		g_hash_table_iter_init(&iter, rooms);
2624 		while (g_hash_table_iter_next(&iter, NULL, &value)) {
2625 			janus_textroom_room *tr = value;
2626 			JANUS_LOG(LOG_VERB, "  ::: [%s][%s]\n", tr->room_id_str, tr->room_name);
2627 		}
2628 		janus_mutex_unlock(&rooms_mutex);
2629 		if(!internal) {
2630 			/* Send response back */
2631 			reply = json_object();
2632 			/* Notice that we reply differently if the request came via Janus API */
2633 			json_object_set_new(reply, "textroom", json_string(json == NULL ? "success" : "created"));
2634 			json_object_set_new(reply, "room", string_ids ? json_string(textroom->room_id_str) : json_integer(textroom->room_id));
2635 			json_object_set_new(reply, "permanent", save ? json_true() : json_false());
2636 		}
2637 		/* Also notify event handlers */
2638 		if(notify_events && gateway->events_is_enabled()) {
2639 			json_t *info = json_object();
2640 			json_object_set_new(info, "event", json_string("created"));
2641 			json_object_set_new(info, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2642 			gateway->notify_event(&janus_textroom_plugin, session ? session->handle : NULL, info);
2643 		}
2644 		if(room_id_allocated)
2645 			g_free(room_id_str);
2646 	} else if(!strcasecmp(request_text, "exists")) {
2647 		if(!string_ids) {
2648 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2649 				error_code, error_cause, TRUE,
2650 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2651 		} else {
2652 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
2653 				error_code, error_cause, TRUE,
2654 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2655 		}
2656 		if(error_code != 0)
2657 			goto msg_response;
2658 		json_t *room = json_object_get(root, "room");
2659 		guint64 room_id = 0;
2660 		char room_id_num[30], *room_id_str = NULL;
2661 		if(!string_ids) {
2662 			room_id = json_integer_value(room);
2663 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2664 			room_id_str = room_id_num;
2665 		} else {
2666 			room_id_str = (char *)json_string_value(room);
2667 		}
2668 		janus_mutex_lock(&rooms_mutex);
2669 		gboolean room_exists = g_hash_table_contains(rooms, string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2670 		janus_mutex_unlock(&rooms_mutex);
2671 		if(!internal) {
2672 			/* Send response back */
2673 			reply = json_object();
2674 			json_object_set_new(reply, "textroom", json_string("success"));
2675 			json_object_set_new(reply, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2676 			json_object_set_new(reply, "exists", room_exists ? json_true() : json_false());
2677 		}
2678 	} else if(!strcasecmp(request_text, "edit")) {
2679 		JANUS_VALIDATE_JSON_OBJECT(root, edit_parameters,
2680 			error_code, error_cause, TRUE,
2681 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2682 		if(error_code != 0)
2683 			goto msg_response;
2684 		if(!string_ids) {
2685 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2686 				error_code, error_cause, TRUE,
2687 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2688 		} else {
2689 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
2690 				error_code, error_cause, TRUE,
2691 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2692 		}
2693 		if(error_code != 0)
2694 			goto msg_response;
2695 		/* We only allow for a limited set of properties to be edited */
2696 		json_t *room = json_object_get(root, "room");
2697 		json_t *desc = json_object_get(root, "new_description");
2698 		json_t *secret = json_object_get(root, "new_secret");
2699 		json_t *is_private = json_object_get(root, "new_is_private");
2700 		json_t *pin = json_object_get(root, "new_pin");
2701 		json_t *post = json_object_get(root, "new_post");
2702 		json_t *permanent = json_object_get(root, "permanent");
2703 		gboolean save = permanent ? json_is_true(permanent) : FALSE;
2704 		if(save && config == NULL) {
2705 			JANUS_LOG(LOG_ERR, "No configuration file, can't edit room permanently\n");
2706 			error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
2707 			g_snprintf(error_cause, 512, "No configuration file, can't edit room permanently");
2708 			goto msg_response;
2709 		}
2710 		guint64 room_id = 0;
2711 		char room_id_num[30], *room_id_str = NULL;
2712 		if(!string_ids) {
2713 			room_id = json_integer_value(room);
2714 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2715 			room_id_str = room_id_num;
2716 		} else {
2717 			room_id_str = (char *)json_string_value(room);
2718 		}
2719 		janus_mutex_lock(&rooms_mutex);
2720 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
2721 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2722 		if(textroom == NULL) {
2723 			janus_mutex_unlock(&rooms_mutex);
2724 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
2725 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
2726 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
2727 			goto msg_response;
2728 		}
2729 		janus_mutex_lock(&textroom->mutex);
2730 		/* A secret may be required for this action */
2731 		JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
2732 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
2733 		if(error_code != 0) {
2734 			janus_mutex_unlock(&textroom->mutex);
2735 			janus_mutex_unlock(&rooms_mutex);
2736 			goto msg_response;
2737 		}
2738 		/* Edit the room properties that were provided */
2739 		if(desc != NULL && strlen(json_string_value(desc)) > 0) {
2740 			char *old_description = textroom->room_name;
2741 			char *new_description = g_strdup(json_string_value(desc));
2742 			textroom->room_name = new_description;
2743 			g_free(old_description);
2744 		}
2745 		if(is_private)
2746 			textroom->is_private = json_is_true(is_private);
2747 		if(secret && strlen(json_string_value(secret)) > 0) {
2748 			char *old_secret = textroom->room_secret;
2749 			char *new_secret = g_strdup(json_string_value(secret));
2750 			textroom->room_secret = new_secret;
2751 			g_free(old_secret);
2752 		}
2753 		if(post && strlen(json_string_value(post)) > 0) {
2754 			char *old_post = textroom->http_backend;
2755 			char *new_post = g_strdup(json_string_value(post));
2756 			textroom->http_backend = new_post;
2757 			g_free(old_post);
2758 		}
2759 		if(pin && strlen(json_string_value(pin)) > 0) {
2760 			char *old_pin = textroom->room_pin;
2761 			char *new_pin = g_strdup(json_string_value(pin));
2762 			textroom->room_pin = new_pin;
2763 			g_free(old_pin);
2764 		}
2765 		if(save) {
2766 			/* This change is permanent: save to the configuration file too
2767 			 * FIXME: We should check if anything fails... */
2768 			JANUS_LOG(LOG_VERB, "Modifying room %s permanently in config file\n", room_id_str);
2769 			janus_mutex_lock(&config_mutex);
2770 			char cat[BUFSIZ], value[BUFSIZ];
2771 			/* The room ID is the category (prefixed by "room-") */
2772 			g_snprintf(cat, BUFSIZ, "room-%s", room_id_str);
2773 			/* Remove the old category first */
2774 			janus_config_remove(config, NULL, cat);
2775 			/* Now write the room details again */
2776 			janus_config_category *c = janus_config_get_create(config, NULL, janus_config_type_category, cat);
2777 			janus_config_add(config, c, janus_config_item_create("description", textroom->room_name));
2778 			if(textroom->is_private)
2779 				janus_config_add(config, c, janus_config_item_create("is_private", "yes"));
2780 			if(textroom->room_secret)
2781 				janus_config_add(config, c, janus_config_item_create("secret", textroom->room_secret));
2782 			if(textroom->room_pin)
2783 				janus_config_add(config, c, janus_config_item_create("pin", textroom->room_pin));
2784 			if(textroom->history_size) {
2785 				g_snprintf(value, BUFSIZ, "%d", textroom->history_size);
2786 				janus_config_add(config, c, janus_config_item_create("history", value));
2787 			}
2788 			if(textroom->http_backend)
2789 				janus_config_add(config, c, janus_config_item_create("post", textroom->http_backend));
2790 			/* Save modified configuration */
2791 			if(janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE) < 0)
2792 				save = FALSE;	/* This will notify the user the room changes are not permanent */
2793 			janus_mutex_unlock(&config_mutex);
2794 		}
2795 		janus_mutex_unlock(&textroom->mutex);
2796 		janus_mutex_unlock(&rooms_mutex);
2797 		if(!internal) {
2798 			/* Send response back */
2799 			reply = json_object();
2800 			/* Notice that we reply differently if the request came via Janus API */
2801 			json_object_set_new(reply, "textroom", json_string(json == NULL ? "success" : "edited"));
2802 			json_object_set_new(reply, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2803 			json_object_set_new(reply, "permanent", save ? json_true() : json_false());
2804 		}
2805 		/* Also notify event handlers */
2806 		if(notify_events && gateway->events_is_enabled()) {
2807 			json_t *info = json_object();
2808 			json_object_set_new(info, "event", json_string("edited"));
2809 			json_object_set_new(info, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2810 			gateway->notify_event(&janus_textroom_plugin, session ? session->handle : NULL, info);
2811 		}
2812 	} else if(!strcasecmp(request_text, "destroy")) {
2813 		JANUS_VALIDATE_JSON_OBJECT(root, destroy_parameters,
2814 			error_code, error_cause, TRUE,
2815 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2816 		if(error_code != 0)
2817 			goto msg_response;
2818 		if(!string_ids) {
2819 			JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
2820 				error_code, error_cause, TRUE,
2821 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2822 		} else {
2823 			JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters,
2824 				error_code, error_cause, TRUE,
2825 				JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
2826 		}
2827 		if(error_code != 0)
2828 			goto msg_response;
2829 		json_t *room = json_object_get(root, "room");
2830 		json_t *permanent = json_object_get(root, "permanent");
2831 		gboolean save = permanent ? json_is_true(permanent) : FALSE;
2832 		if(save && config == NULL) {
2833 			JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
2834 			error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
2835 			g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently");
2836 			goto msg_response;
2837 		}
2838 		guint64 room_id = 0;
2839 		char room_id_num[30], *room_id_str = NULL;
2840 		if(!string_ids) {
2841 			room_id = json_integer_value(room);
2842 			g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id);
2843 			room_id_str = room_id_num;
2844 		} else {
2845 			room_id_str = (char *)json_string_value(room);
2846 		}
2847 		janus_mutex_lock(&rooms_mutex);
2848 		janus_textroom_room *textroom = g_hash_table_lookup(rooms,
2849 			string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2850 		if(textroom == NULL) {
2851 			janus_mutex_unlock(&rooms_mutex);
2852 			JANUS_LOG(LOG_ERR, "No such room (%s)\n", room_id_str);
2853 			error_code = JANUS_TEXTROOM_ERROR_NO_SUCH_ROOM;
2854 			g_snprintf(error_cause, 512, "No such room (%s)", room_id_str);
2855 			goto msg_response;
2856 		}
2857 		janus_refcount_increase(&textroom->ref);
2858 		janus_mutex_lock(&textroom->mutex);
2859 		/* A secret may be required for this action */
2860 		JANUS_CHECK_SECRET(textroom->room_secret, root, "secret", error_code, error_cause,
2861 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT, JANUS_TEXTROOM_ERROR_UNAUTHORIZED);
2862 		if(error_code != 0) {
2863 			janus_mutex_unlock(&textroom->mutex);
2864 			janus_mutex_unlock(&rooms_mutex);
2865 			janus_refcount_decrease(&textroom->ref);
2866 			goto msg_response;
2867 		}
2868 		/* Remove room */
2869 		g_hash_table_remove(rooms, string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2870 		if(save) {
2871 			/* This change is permanent: save to the configuration file too
2872 			 * FIXME: We should check if anything fails... */
2873 			JANUS_LOG(LOG_VERB, "Destroying room %s permanently in config file\n", room_id_str);
2874 			janus_mutex_lock(&config_mutex);
2875 			char cat[BUFSIZ];
2876 			/* The room ID is the category (prefixed by "room-") */
2877 			g_snprintf(cat, BUFSIZ, "room-%s", room_id_str);
2878 			janus_config_remove(config, NULL, cat);
2879 			/* Save modified configuration */
2880 			if(janus_config_save(config, config_folder, JANUS_TEXTROOM_PACKAGE) < 0)
2881 				save = FALSE;	/* This will notify the user the room destruction is not permanent */
2882 			janus_mutex_unlock(&config_mutex);
2883 		}
2884 		/* Notify all participants */
2885 		JANUS_LOG(LOG_VERB, "Notifying all participants about the destroy\n");
2886 		if(textroom->participants) {
2887 			/* Prepare event */
2888 			json_t *event = json_object();
2889 			json_object_set_new(event, "textroom", json_string("destroyed"));
2890 			json_object_set_new(event, "room", string_ids ? json_string(textroom->room_id_str) : json_integer(textroom->room_id));
2891 			char *event_text = json_dumps(event, json_format);
2892 			json_decref(event);
2893 			if(event_text == NULL) {
2894 				janus_mutex_unlock(&textroom->mutex);
2895 				janus_mutex_unlock(&rooms_mutex);
2896 				janus_refcount_decrease(&textroom->ref);
2897 				JANUS_LOG(LOG_ERR, "Failed to stringify message...\n");
2898 				error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
2899 				g_snprintf(error_cause, 512, "Failed to stringify message");
2900 				goto msg_response;
2901 			}
2902 			janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = event_text, .length = strlen(event_text) };
2903 			gateway->relay_data(handle, &data);
2904 			/* Broadcast */
2905 			GHashTableIter iter;
2906 			gpointer value;
2907 			g_hash_table_iter_init(&iter, textroom->participants);
2908 			while(g_hash_table_iter_next(&iter, NULL, &value)) {
2909 				janus_textroom_participant *top = value;
2910 				janus_refcount_increase(&top->ref);
2911 				JANUS_LOG(LOG_VERB, "  >> To %s in %s\n", top->username, room_id_str);
2912 				gateway->relay_data(top->session->handle, &data);
2913 				janus_mutex_lock(&top->session->mutex);
2914 				g_hash_table_remove(top->session->rooms, string_ids ? (gpointer)room_id_str : (gpointer)&room_id);
2915 				janus_mutex_unlock(&top->session->mutex);
2916 				janus_refcount_decrease(&top->ref);
2917 				janus_textroom_participant_destroy(top);
2918 			}
2919 			free(event_text);
2920 		}
2921 		janus_mutex_unlock(&textroom->mutex);
2922 		janus_mutex_unlock(&rooms_mutex);
2923 		janus_refcount_decrease(&textroom->ref);
2924 		if(!internal) {
2925 			/* Send response back */
2926 			reply = json_object();
2927 			/* Notice that we reply differently if the request came via Janus API */
2928 			json_object_set_new(reply, "textroom", json_string(json == NULL ? "success" : "destroyed"));
2929 			json_object_set_new(reply, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2930 			json_object_set_new(reply, "permanent", save ? json_true() : json_false());
2931 		}
2932 		/* Also notify event handlers */
2933 		if(notify_events && gateway->events_is_enabled()) {
2934 			json_t *info = json_object();
2935 			json_object_set_new(info, "event", json_string("destroyed"));
2936 			json_object_set_new(info, "room", string_ids ? json_string(room_id_str) : json_integer(room_id));
2937 			gateway->notify_event(&janus_textroom_plugin, session ? session->handle : NULL, info);
2938 		}
2939 	} else {
2940 		JANUS_LOG(LOG_ERR, "Unsupported request %s\n", request_text);
2941 		error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
2942 		g_snprintf(error_cause, 512, "Unsupported request %s", request_text);
2943 		goto msg_response;
2944 	}
2945 
2946 msg_response:
2947 		{
2948 			if(!internal) {
2949 				if(error_code == 0 && !reply) {
2950 					error_code = JANUS_TEXTROOM_ERROR_UNKNOWN_ERROR;
2951 					g_snprintf(error_cause, 512, "Invalid response");
2952 				}
2953 				if(error_code != 0) {
2954 					/* Prepare JSON error event */
2955 					json_t *event = json_object();
2956 					json_object_set_new(event, "textroom", json_string("error"));
2957 					json_object_set_new(event, "error_code", json_integer(error_code));
2958 					json_object_set_new(event, "error", json_string(error_cause));
2959 					reply = event;
2960 				}
2961 				if(transaction_text && json == NULL)
2962 					json_object_set_new(reply, "transaction", json_string(transaction_text));
2963 				if(json == NULL) {
2964 					/* Reply via data channels */
2965 					char *reply_text = json_dumps(reply, json_format);
2966 					json_decref(reply);
2967 					if(reply_text == NULL) {
2968 						JANUS_LOG(LOG_ERR, "Failed to stringify message...\n");
2969 					} else {
2970 						janus_plugin_data data = { .label = NULL, .protocol = NULL, .binary = FALSE, .buffer = reply_text, .length = strlen(reply_text) };
2971 						gateway->relay_data(handle, &data);
2972 						free(reply_text);
2973 					}
2974 				} else {
2975 					/* Reply via Janus API */
2976 					return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, reply);
2977 				}
2978 			}
2979 			if(root != NULL)
2980 				json_decref(root);
2981 		}
2982 	return NULL;
2983 }
2984 
janus_textroom_slow_link(janus_plugin_session * handle,int uplink,int video)2985 void janus_textroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
2986 	/* We don't do audio/video */
2987 }
2988 
janus_textroom_hangup_media(janus_plugin_session * handle)2989 void janus_textroom_hangup_media(janus_plugin_session *handle) {
2990 	janus_mutex_lock(&sessions_mutex);
2991 	janus_textroom_hangup_media_internal(handle);
2992 	janus_mutex_unlock(&sessions_mutex);
2993 }
2994 
janus_textroom_hangup_media_internal(janus_plugin_session * handle)2995 static void janus_textroom_hangup_media_internal(janus_plugin_session *handle) {
2996 	JANUS_LOG(LOG_INFO, "[%s-%p] No WebRTC media anymore\n", JANUS_TEXTROOM_PACKAGE, handle);
2997 	if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
2998 		return;
2999 	janus_textroom_session *session = janus_textroom_lookup_session(handle);
3000 	if(!session) {
3001 		JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
3002 		return;
3003 	}
3004 	if(session->destroyed)
3005 		return;
3006 	if(!g_atomic_int_compare_and_exchange(&session->hangingup, 0, 1))
3007 		return;
3008 	g_atomic_int_set(&session->dataready, 0);
3009 	/* Get rid of all participants */
3010 	janus_mutex_lock(&session->mutex);
3011 	GList *list = NULL;
3012 	if(session->rooms) {
3013 		GHashTableIter iter;
3014 		gpointer value;
3015 		janus_mutex_lock(&rooms_mutex);
3016 		g_hash_table_iter_init(&iter, session->rooms);
3017 		while(g_hash_table_iter_next(&iter, NULL, &value)) {
3018 			janus_textroom_participant *p = value;
3019 			janus_mutex_lock(&p->mutex);
3020 			if(p->room) {
3021 				list = g_list_append(list, string_ids ?
3022 					(gpointer)g_strdup(p->room->room_id_str) : (gpointer)janus_uint64_dup(p->room->room_id));
3023 			}
3024 			janus_mutex_unlock(&p->mutex);
3025 		}
3026 		janus_mutex_unlock(&rooms_mutex);
3027 	}
3028 	janus_mutex_unlock(&session->mutex);
3029 	JANUS_LOG(LOG_VERB, "Leaving %d rooms\n", g_list_length(list));
3030 	char request[100];
3031 	GList *first = list;
3032 	while(list) {
3033 		char *room_id_str = (char *)list->data;
3034 		if(string_ids) {
3035 			g_snprintf(request, sizeof(request), "{\"textroom\":\"leave\",\"transaction\":\"internal\",\"room\":\"%s\"}", room_id_str);
3036 		} else {
3037 			guint64 room_id = *(guint64 *)room_id_str;
3038 			g_snprintf(request, sizeof(request), "{\"textroom\":\"leave\",\"transaction\":\"internal\",\"room\":%"SCNu64"}", room_id);
3039 		}
3040 		janus_textroom_handle_incoming_request(handle, g_strdup(request), NULL, TRUE);
3041 		list = list->next;
3042 	}
3043 	g_list_free_full(first, (GDestroyNotify)g_free);
3044 	g_atomic_int_set(&session->hangingup, 0);
3045 }
3046 
3047 /* Thread to handle incoming messages */
janus_textroom_handler(void * data)3048 static void *janus_textroom_handler(void *data) {
3049 	JANUS_LOG(LOG_VERB, "Joining TextRoom handler thread\n");
3050 	janus_textroom_message *msg = NULL;
3051 	int error_code = 0;
3052 	char error_cause[512];
3053 	json_t *root = NULL;
3054 	gboolean do_offer = FALSE, sdp_update = FALSE;
3055 	while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
3056 		msg = g_async_queue_pop(messages);
3057 		if(msg == &exit_message)
3058 			break;
3059 		if(msg->handle == NULL) {
3060 			janus_textroom_message_free(msg);
3061 			continue;
3062 		}
3063 		janus_mutex_lock(&sessions_mutex);
3064 		janus_textroom_session *session = janus_textroom_lookup_session(msg->handle);
3065 		if(!session) {
3066 			janus_mutex_unlock(&sessions_mutex);
3067 			JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
3068 			janus_textroom_message_free(msg);
3069 			continue;
3070 		}
3071 		if(g_atomic_int_get(&session->destroyed)) {
3072 			janus_mutex_unlock(&sessions_mutex);
3073 			janus_textroom_message_free(msg);
3074 			continue;
3075 		}
3076 		janus_mutex_unlock(&sessions_mutex);
3077 		/* Handle request */
3078 		error_code = 0;
3079 		root = msg->message;
3080 		if(msg->message == NULL) {
3081 			JANUS_LOG(LOG_ERR, "No message??\n");
3082 			error_code = JANUS_TEXTROOM_ERROR_NO_MESSAGE;
3083 			g_snprintf(error_cause, 512, "%s", "No message??");
3084 			goto error;
3085 		}
3086 		if(!json_is_object(root)) {
3087 			JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
3088 			error_code = JANUS_TEXTROOM_ERROR_INVALID_JSON;
3089 			g_snprintf(error_cause, 512, "JSON error: not an object");
3090 			goto error;
3091 		}
3092 		/* Parse request */
3093 		JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
3094 			error_code, error_cause, TRUE,
3095 			JANUS_TEXTROOM_ERROR_MISSING_ELEMENT, JANUS_TEXTROOM_ERROR_INVALID_ELEMENT);
3096 		if(error_code != 0)
3097 			goto error;
3098 		do_offer = FALSE;
3099 		sdp_update = FALSE;
3100 		json_t *request = json_object_get(root, "request");
3101 		const char *request_text = json_string_value(request);
3102 		do_offer = FALSE;
3103 		if(!strcasecmp(request_text, "setup")) {
3104 			if(!g_atomic_int_compare_and_exchange(&session->setup, 0, 1)) {
3105 				JANUS_LOG(LOG_ERR, "PeerConnection already setup\n");
3106 				error_code = JANUS_TEXTROOM_ERROR_ALREADY_SETUP;
3107 				g_snprintf(error_cause, 512, "PeerConnection already setup");
3108 				goto error;
3109 			}
3110 			do_offer = TRUE;
3111 		} else if(!strcasecmp(request_text, "restart")) {
3112 			if(!g_atomic_int_get(&session->setup)) {
3113 				JANUS_LOG(LOG_ERR, "PeerConnection not setup\n");
3114 				error_code = JANUS_TEXTROOM_ERROR_ALREADY_SETUP;
3115 				g_snprintf(error_cause, 512, "PeerConnection not setup");
3116 				goto error;
3117 			}
3118 			sdp_update = TRUE;
3119 			do_offer = TRUE;
3120 		} else if(!strcasecmp(request_text, "ack")) {
3121 			/* The peer sent their answer back: do nothing */
3122 		} else {
3123 			JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
3124 			error_code = JANUS_TEXTROOM_ERROR_INVALID_REQUEST;
3125 			g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
3126 			goto error;
3127 		}
3128 
3129 		/* Prepare JSON event */
3130 		json_t *event = json_object();
3131 		json_object_set_new(event, "textroom", json_string("event"));
3132 		json_object_set_new(event, "result", json_string("ok"));
3133 		if(!do_offer) {
3134 			int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, NULL);
3135 			JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
3136 		} else {
3137 			/* Send an offer (whether it's for an ICE restart or not) */
3138 			if(sdp_update) {
3139 				/* Renegotiation: increase version */
3140 				session->sdp_version++;
3141 			} else {
3142 				/* New session: generate new values */
3143 				session->sdp_version = 1;	/* This needs to be increased when it changes */
3144 				session->sdp_sessid = janus_get_real_time();
3145 			}
3146 			char sdp[500];
3147 			g_snprintf(sdp, sizeof(sdp), sdp_template,
3148 				session->sdp_sessid, session->sdp_version);
3149 			json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
3150 			if(sdp_update)
3151 				json_object_set_new(jsep, "restart", json_true());
3152 			/* How long will the Janus core take to push the event? */
3153 			g_atomic_int_set(&session->hangingup, 0);
3154 			gint64 start = janus_get_monotonic_time();
3155 			int res = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, jsep);
3156 			JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n",
3157 				res, janus_get_monotonic_time()-start);
3158 			json_decref(jsep);
3159 		}
3160 		json_decref(event);
3161 		janus_textroom_message_free(msg);
3162 		continue;
3163 
3164 error:
3165 		{
3166 			/* Prepare JSON error event */
3167 			json_t *event = json_object();
3168 			json_object_set_new(event, "textroom", json_string("error"));
3169 			json_object_set_new(event, "error_code", json_integer(error_code));
3170 			json_object_set_new(event, "error", json_string(error_cause));
3171 			int ret = gateway->push_event(msg->handle, &janus_textroom_plugin, msg->transaction, event, NULL);
3172 			JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
3173 			json_decref(event);
3174 			janus_textroom_message_free(msg);
3175 		}
3176 	}
3177 	JANUS_LOG(LOG_VERB, "Leaving TextRoom handler thread\n");
3178 	return NULL;
3179 }
3180