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