{"action":"created","comment":{"url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments/81942290","pull_request_review_id":2880892,"id":81942290,"diff_hunk":"@@ -0,0 +1,648 @@\n+/*! \\file   janus_mqtt.c\n+ * \\author Andrei Nesterov <ae.nesterov@gmail.com>\n+ * \\copyright GNU General Public License v3\n+ * \\brief  Janus MQTT transport plugin\n+ * \\details  This is an implementation of a MQTT transport for the Janus API,\n+ * using the Eclipse Paho C Client library (https://eclipse.org/paho/clients/c).\n+ * This means that this module adds support for MQTT based messaging as\n+ * an alternative \"transport\" for API requests, responses and notifications.\n+ * This is only useful when you're handling the communication with\n+ * clients your own way. Right now, you can only configure\n+ * the address of the MQTT broker to use, and the queues to\n+ * make use of to receive (to-janus) and send (from-janus) messages\n+ * from/to an external application. As with WebSockets, considering that\n+ * requests wouldn't include a path to address some mandatory information,\n+ * these requests addressed to Janus should include as part of their payload,\n+ * when needed, additional pieces of information like \\c session_id and\n+ * \\c handle_id. That is, where you'd send a Janus request related to a\n+ * specific session to the \\c /janus/<session> path, with RabbitMQ\n+ * you'd have to send the same request with an additional \\c session_id\n+ * field in the JSON payload.\n+ * \\note When you create a session using RabbitMQ, a subscription to the\n+ * events related to it is done automatically through the outgoing queue,\n+ * so no need for an explicit request as the GET in the plain HTTP API.\n+ *\n+ * \\ingroup transports\n+ * \\ref transports\n+ */\n+\n+#include \"transport.h\"\n+#include \"../debug.h\"\n+#include \"../config.h\"\n+#include \"../utils.h\"\n+#include <MQTTAsync.h>\n+\n+/* Transport plugin information */\n+#define JANUS_MQTT_VERSION        1\n+#define JANUS_MQTT_VERSION_STRING \"0.0.1\"\n+#define JANUS_MQTT_DESCRIPTION    \"This transport plugin adds MQTT support to the Janus API via Paho client library.\"\n+#define JANUS_MQTT_NAME           \"JANUS MQTT transport plugin\"\n+#define JANUS_MQTT_AUTHOR         \"Andrei Nesterov <ae.nesterov@gmail.com>\"\n+#define JANUS_MQTT_PACKAGE        \"janus.transport.mqtt\"\n+\n+/* Transport methods */\n+janus_transport *create(void);\n+int janus_mqtt_init(janus_transport_callbacks *callback, const char *config_path);\n+void janus_mqtt_destroy(void);\n+int janus_mqtt_get_api_compatibility(void);\n+int janus_mqtt_get_version(void);\n+const char *janus_mqtt_get_version_string(void);\n+const char *janus_mqtt_get_description(void);\n+const char *janus_mqtt_get_name(void);\n+const char *janus_mqtt_get_author(void);\n+const char *janus_mqtt_get_package(void);\n+gboolean janus_mqtt_is_janus_api_enabled(void);\n+gboolean janus_mqtt_is_admin_api_enabled(void);\n+int janus_mqtt_send_message(void *context, void *request_id, gboolean admin, json_t *message);\n+void janus_mqtt_session_created(void *context, guint64 session_id);\n+void janus_mqtt_session_over(void *context, guint64 session_id, gboolean timeout);\n+\n+/* Transport setup */\n+static janus_transport janus_mqtt_transport_ =\n+\tJANUS_TRANSPORT_INIT (\n+\t\t.init = janus_mqtt_init,\n+\t\t.destroy = janus_mqtt_destroy,\n+\n+\t\t.get_api_compatibility = janus_mqtt_get_api_compatibility,\n+\t\t.get_version = janus_mqtt_get_version,\n+\t\t.get_version_string = janus_mqtt_get_version_string,\n+\t\t.get_description = janus_mqtt_get_description,\n+\t\t.get_name = janus_mqtt_get_name,\n+\t\t.get_author = janus_mqtt_get_author,\n+\t\t.get_package = janus_mqtt_get_package,\n+\n+\t\t.is_janus_api_enabled = janus_mqtt_is_janus_api_enabled,\n+\t\t.is_admin_api_enabled = janus_mqtt_is_admin_api_enabled,\n+\n+\t\t.send_message = janus_mqtt_send_message,\n+\t\t.session_created = janus_mqtt_session_created,\n+\t\t.session_over = janus_mqtt_session_over,\n+\t);\n+\n+/* Transport creator */\n+janus_transport *create(void) {\n+\tJANUS_LOG(LOG_VERB, \"%s created!\\n\", JANUS_MQTT_NAME);\n+\treturn &janus_mqtt_transport_;\n+}\n+\n+/* API flags */\n+static gboolean janus_mqtt_api_enabled_ = FALSE;\n+static gboolean janus_mqtt_admin_api_enabled_ = FALSE;\n+\n+/* JSON serialization options */\n+static size_t json_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER;\n+\n+/* MQTT client context */\n+typedef struct janus_mqtt_context {\n+\tjanus_transport_callbacks *gateway;\n+\tMQTTAsync client;\n+\tstruct {\n+\t\tint keep_alive_interval;\n+\t\tint cleansession;\n+\t} connect;\n+\tstruct {\n+\t\tint timeout;\n+\t} disconnect;\n+\tstruct {\n+\t\tchar *topic;\n+\t\tint qos;\n+\t} subscribe;\n+\tstruct {\n+\t\tchar *topic;\n+\t\tint qos;\n+\t} publish;\n+\tstruct {\n+\t\tstruct {\n+\t\t\tchar *topic;\n+\t\t\tint qos;\n+\t\t} subscribe;\n+\t\tstruct {\n+\t\t\tchar *topic;\n+\t\t\tint qos;\n+\t\t} publish;\n+\t} admin;\n+} janus_mqtt_context;\n+\n+/* Transport client methods */\n+void janus_mqtt_client_connection_lost(void *context, char *cause);\n+int janus_mqtt_client_message_arrived(void *context, char *topicName, int topicLen, MQTTAsync_message *message);\n+void janus_mqtt_client_delivery_complete(void *context, MQTTAsync_token token);\n+int janus_mqtt_client_connect(janus_mqtt_context *ctx);\n+void janus_mqtt_client_connect_success(void *context, MQTTAsync_successData *response);\n+void janus_mqtt_client_connect_failure(void *context, MQTTAsync_failureData *response);\n+int janus_mqtt_client_reconnect(janus_mqtt_context *ctx);\n+void janus_mqtt_client_reconnect_success(void *context, MQTTAsync_successData *response);\n+void janus_mqtt_client_reconnect_failure(void *context, MQTTAsync_failureData *response);\n+int janus_mqtt_client_disconnect(janus_mqtt_context *ctx);\n+void janus_mqtt_client_disconnect_success(void *context, MQTTAsync_successData *response);\n+void janus_mqtt_client_disconnect_failure(void *context, MQTTAsync_failureData *response);\n+int janus_mqtt_client_subscribe(janus_mqtt_context *ctx);\n+void janus_mqtt_client_subscribe_success(void *context, MQTTAsync_successData *response);\n+void janus_mqtt_client_subscribe_failure(void *context, MQTTAsync_failureData *response);\n+int janus_mqtt_client_admin_subscribe(janus_mqtt_context *ctx);\n+void janus_mqtt_client_admin_subscribe_success(void *context, MQTTAsync_successData *response);\n+void janus_mqtt_client_admin_subscribe_failure(void *context, MQTTAsync_failureData *response);\n+int janus_mqtt_client_publish_janus_message(janus_mqtt_context *ctx, char *payload);\n+void janus_mqtt_client_publish_janus_success(void *context, MQTTAsync_successData *response);\n+void janus_mqtt_client_publish_janus_failure(void *context, MQTTAsync_failureData *response);\n+int janus_mqtt_client_publish_admin_message(janus_mqtt_context *ctx, char *payload);\n+void janus_mqtt_client_publish_admin_success(void *context, MQTTAsync_successData *response);\n+void janus_mqtt_client_publish_admin_failure(void *context, MQTTAsync_failureData *response);\n+void janus_mqtt_client_destroy_context(janus_mqtt_context *ctx);\n+\n+/* We only handle a single client */\n+static janus_mqtt_context *context_ = NULL;\n+\n+int janus_mqtt_init(janus_transport_callbacks *callback, const char *config_path) {\n+\tif(callback == NULL || config_path == NULL) {\n+\t\t/* Invalid arguments */\n+\t\treturn -1;\n+\t}\n+\n+\t/* Initializing context */\n+\tjanus_mqtt_context *ctx = g_malloc0(sizeof(struct janus_mqtt_context));\n+\tctx->gateway = callback;\n+\tcontext_ = ctx;\n+\n+\t/* Read configuration */\n+\tchar filename[255];\n+\tg_snprintf(filename, 255, \"%s/%s.cfg\", config_path, JANUS_MQTT_PACKAGE);\n+\tJANUS_LOG(LOG_VERB, \"Configuration file: %s\\n\", filename);\n+\tjanus_config *config = janus_config_parse(filename);\n+\tif(config != NULL)\n+\t\tjanus_config_print(config);\n+\n+\t/* Handle configuration */\n+\tjanus_config_item *url_item = janus_config_get_item_drilldown(config, \"general\", \"url\");\n+\tconst char *url = g_strdup((url_item && url_item->value) ? url_item->value : \"tcp://localhost:1883\");\n+\n+\tjanus_config_item *client_id_item = janus_config_get_item_drilldown(config, \"general\", \"client_id\");\n+\tconst char *client_id = g_strdup((client_id_item && client_id_item->value) ? client_id_item->value : \"guest\");\n+\n+\tjanus_config_item *username_item = janus_config_get_item_drilldown(config, \"general\", \"username\");\n+\tconst char *username = g_strdup((username_item && username_item->value) ? username_item->value : \"guest\");\n+\t\n+\tjanus_config_item *password_item = janus_config_get_item_drilldown(config, \"general\", \"password\");\n+\tconst char *password = g_strdup((password_item && password_item->value) ? password_item->value : \"guest\");\n+\n+\tjanus_config_item *json_item = janus_config_get_item_drilldown(config, \"general\", \"json\");\n+\tif(json_item && json_item->value) {\n+\t\t/* Check how we need to format/serialize the JSON output */\n+\t\tif(!strcasecmp(json_item->value, \"indented\")) {\n+\t\t\t/* Default: indented, we use three spaces for that */\n+\t\t\tjson_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER;\n+\t\t} else if(!strcasecmp(json_item->value, \"plain\")) {\n+\t\t\t/* Not indented and no new lines, but still readable */\n+\t\t\tjson_format_ = JSON_INDENT(0) | JSON_PRESERVE_ORDER;\n+\t\t} else if(!strcasecmp(json_item->value, \"compact\")) {\n+\t\t\t/* Compact, so no spaces between separators */\n+\t\t\tjson_format_ = JSON_COMPACT | JSON_PRESERVE_ORDER;\n+\t\t} else {\n+\t\t\tJANUS_LOG(LOG_WARN, \"Unsupported JSON format option '%s', using default (indented)\\n\", json_item->value);\n+\t\t\tjson_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER;\n+\t\t}\n+\t}\n+\n+\t{ // connect configuration\n+\t\tjanus_config_item *keep_alive_interval_item = janus_config_get_item_drilldown(config, \"connect\", \"keep_alive_interval\");\n+\t\tctx->connect.keep_alive_interval = (keep_alive_interval_item && keep_alive_interval_item->value) ? atoi(keep_alive_interval_item->value) : 20;\n+\n+\t\tjanus_config_item *cleansession_item = janus_config_get_item_drilldown(config, \"connect\", \"cleansession\");\n+\t\tctx->connect.cleansession = (cleansession_item && cleansession_item->value) ? atoi(cleansession_item->value) : 0;\n+\t}\n+\n+\t{ // disconnect configuration\n+\t\tjanus_config_item *timeout_item = janus_config_get_item_drilldown(config, \"disconnect\", \"timeout\");\n+\t\tctx->disconnect.timeout = (timeout_item && timeout_item->value) ? atoi(timeout_item->value) : 1;\n+\t}\n+\n+\tjanus_config_item *enable_item = janus_config_get_item_drilldown(config, \"general\", \"enable\");\n+\tif(enable_item && enable_item->value && janus_is_true(enable_item->value)) {\n+\t\tjanus_mqtt_api_enabled_ = TRUE;\n+\n+\t\t{ // subscribe configuration\n+\t\t\tjanus_config_item *topic_item = janus_config_get_item_drilldown(config, \"subscribe\", \"topic\");\n+\t\t\tif(!topic_item || !topic_item->value) {\n+\t\t\t\tJANUS_LOG(LOG_FATAL, \"Missing topic for incoming messages for MQTT integration...\\n\");\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\t\t\tctx->subscribe.topic = g_strdup(topic_item->value);\n+\n+\t\t\tjanus_config_item *qos_item = janus_config_get_item_drilldown(config, \"subscribe\", \"qos\");\n+\t\t\tctx->subscribe.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1;\n+\t\t}\n+\n+\t\t{ // publish configuration\n+\t\t\tjanus_config_item *topic_item = janus_config_get_item_drilldown(config, \"publish\", \"topic\");\n+\t\t\tif(!topic_item || !topic_item->value) {\n+\t\t\t\tJANUS_LOG(LOG_FATAL, \"Missing topic for outgoing messages for MQTT integration...\\n\");\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\t\t\tctx->publish.topic = g_strdup(topic_item->value);\n+\n+\t\t\tjanus_config_item *qos_item = janus_config_get_item_drilldown(config, \"publish\", \"qos\");\n+\t\t\tctx->publish.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1;\n+\t\t}\n+\t}\n+\telse {\n+\t\tjanus_mqtt_api_enabled_ = FALSE;\n+\t\tctx->subscribe.topic = NULL;\n+\t\tctx->publish.topic = NULL;\n+\t}\n+\n+\t{ // admin configuration\n+\t\tjanus_config_item *enable_item = janus_config_get_item_drilldown(config, \"admin\", \"admin_enable\");\n+\t\tif(enable_item && enable_item->value && janus_is_true(enable_item->value)) {\n+\t\t\tjanus_mqtt_admin_api_enabled_ = TRUE;\n+\n+\t\t\t{ // admin subscribe configuration\n+\t\t\t\tjanus_config_item *topic_item = janus_config_get_item_drilldown(config, \"admin.subscribe\", \"topic\");\n+\t\t\t\tif(!topic_item || !topic_item->value) {\n+\t\t\t\t\tJANUS_LOG(LOG_FATAL, \"Missing topic for incoming admin messages for MQTT integration...\\n\");\n+\t\t\t\t\tgoto error;\n+\t\t\t\t}\n+\t\t\t\tctx->admin.subscribe.topic = g_strdup(topic_item->value);\n+\n+\t\t\t\tjanus_config_item *qos_item = janus_config_get_item_drilldown(config, \"admin.subscribe\", \"qos\");\n+\t\t\t\tctx->admin.subscribe.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1;\n+\t\t\t}\n+\n+\t\t\t{ // admin publish configuration\n+\t\t\t\tjanus_config_item *topic_item = janus_config_get_item_drilldown(config, \"admin.publish\", \"topic\");\n+\t\t\t\tif(!topic_item || !topic_item->value) {\n+\t\t\t\t\tJANUS_LOG(LOG_FATAL, \"Missing topic for outgoing admin messages for MQTT integration...\\n\");\n+\t\t\t\t\tgoto error;\n+\t\t\t\t}\n+\t\t\t\tctx->admin.publish.topic = g_strdup(topic_item->value);\n+\n+\t\t\t\tjanus_config_item *qos_item = janus_config_get_item_drilldown(config, \"admin.publish\", \"qos\");\n+\t\t\t\tctx->admin.publish.qos = (qos_item && qos_item->value) ? atoi(qos_item->value) : 1;\n+\t\t\t}\n+\t\t}\n+\t\telse {\n+\t\t\tjanus_mqtt_admin_api_enabled_ = FALSE;\n+\t\t\tctx->admin.subscribe.topic = NULL;\n+\t\t\tctx->admin.publish.topic = NULL;\n+\t\t}\n+\t}\n+\n+\tif(!janus_mqtt_api_enabled_ && !janus_mqtt_admin_api_enabled_) {\n+\t\tJANUS_LOG(LOG_WARN, \"MQTT support disabled for both Janus and Admin API, giving up\\n\");\n+\t\tgoto error;\n+\t}\n+\n+\t/* Creating a client */\n+\tif(MQTTAsync_create(\n+\t\t\t&ctx->client,\n+\t\t\turl,\n+\t\t\tclient_id,\n+\t\t\tMQTTCLIENT_PERSISTENCE_NONE,\n+\t\t\tNULL) != MQTTASYNC_SUCCESS) {\n+\t\tJANUS_LOG(LOG_FATAL, \"Can't connect to MQTT broker: error creating client...\\n\");\n+\t\tgoto error;\n+\t}\n+\tif(MQTTAsync_setCallbacks(\n+\t\t\tctx->client,\n+\t\t\tctx,\n+\t\t\tjanus_mqtt_client_connection_lost,\n+\t\t\tjanus_mqtt_client_message_arrived,\n+\t\t\tjanus_mqtt_client_delivery_complete) != MQTTASYNC_SUCCESS) {\n+\t\tJANUS_LOG(LOG_FATAL, \"Can't connect to MQTT broker: error setting up callbacks...\\n\");\n+\t\tgoto error;\n+\t}\n+\n+\t/* Connecting to the broker */\n+\tint rc = janus_mqtt_client_connect(ctx);\n+\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\tJANUS_LOG(LOG_FATAL, \"Can't connect to MQTT broker, return code: %d\\n\", rc);\n+\t\tgoto error;\n+\t}\n+\n+\treturn 0;\n+\n+error:\n+\t/* If we got here, something went wrong */\n+\tjanus_mqtt_client_destroy_context(ctx);\n+\tg_free((char *)url);\n+\tg_free((char *)client_id);\n+\tg_free((char *)username);\n+\tg_free((char *)password);\n+\tg_free(config);\n+\n+\treturn -1;\n+}\n+\n+void janus_mqtt_destroy(void) {\n+\tJANUS_LOG(LOG_INFO, \"Disconnecting MQTT client...\\n\");\n+\n+\tjanus_mqtt_client_disconnect(context_);\n+}\n+\n+int janus_mqtt_get_api_compatibility(void) {\n+\treturn JANUS_TRANSPORT_API_VERSION;\n+}\n+\n+int janus_mqtt_get_version(void) {\n+\treturn JANUS_MQTT_VERSION;\n+}\n+\n+const char *janus_mqtt_get_version_string(void) {\n+\treturn JANUS_MQTT_VERSION_STRING;\n+}\n+\n+const char *janus_mqtt_get_description(void) {\n+\treturn JANUS_MQTT_DESCRIPTION;\n+}\n+\n+const char *janus_mqtt_get_name(void) {\n+\treturn JANUS_MQTT_NAME;\n+}\n+\n+const char *janus_mqtt_get_author(void) {\n+\treturn JANUS_MQTT_AUTHOR;\n+}\n+\n+const char *janus_mqtt_get_package(void) {\n+\treturn JANUS_MQTT_PACKAGE;\n+}\n+\n+gboolean janus_mqtt_is_janus_api_enabled(void) {\n+\treturn janus_mqtt_api_enabled_;\n+}\n+\n+gboolean janus_mqtt_is_admin_api_enabled(void) {\n+\treturn janus_mqtt_admin_api_enabled_;\n+}\n+\n+int janus_mqtt_send_message(void *context, void *request_id, gboolean admin, json_t *message) {\n+\tif(message == NULL) {\n+\t\treturn -1;\n+\t}\n+\tif(context == NULL) {\n+\t\tjson_decref(message);\n+\t\treturn -1;\n+\t}\n+\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tchar *payload = json_dumps(message, json_format_);\n+\tjson_decref(message);\n+\n+\tJANUS_LOG(LOG_HUGE, \"Sending %s API message via MQTT: %s\\n\", admin ? \"admin\" : \"Janus\", payload);\n+\n+\tif(admin) {\n+\t\tint rc = janus_mqtt_client_publish_admin_message(ctx, payload);\n+\t\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\t\tJANUS_LOG(LOG_ERR, \"Can't publish to MQTT topic: %s, return code: %d\\n\", ctx->admin.publish.topic, rc);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\telse {\n+\t\tint rc = janus_mqtt_client_publish_janus_message(ctx, payload);\n+\t\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\t\tJANUS_LOG(LOG_ERR, \"Can't publish to MQTT topic: %s, return code: %d\\n\", ctx->publish.topic, rc);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void janus_mqtt_session_created(void *context, guint64 session_id) {\n+\t/* We don't care */\n+}\n+\n+void janus_mqtt_session_over(void *context, guint64 session_id, gboolean timeout) {\n+\t/* We don't care, not even if it's a timeout (should we?), our client is always up */\n+}\n+\n+void janus_mqtt_client_connection_lost(void *context, char *cause) {\n+\tJANUS_LOG(LOG_INFO, \"MQTT connection lost cause of %s. Reconnecting...\\n\", cause);\n+\t// automatic reconnect\n+}\n+\n+int janus_mqtt_client_message_arrived(void *context, char *topicName, int topicLen, MQTTAsync_message *message) {\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tgchar *topic = g_strndup(topicName, topicLen);\n+\tconst gboolean janus = janus_mqtt_api_enabled_ &&  !strcasecmp(topic, ctx->subscribe.topic);\n+\tconst gboolean admin = janus_mqtt_admin_api_enabled_ && !strcasecmp(topic, ctx->admin.subscribe.topic);\n+\tg_free(topic);\n+\n+\tif((janus || admin) && message->payloadlen) {\n+\t\tJANUS_LOG(LOG_HUGE, \"Receiving %s API message over MQTT: %s\\n\", admin ? \"admin\" : \"Janus\", (char *)message->payload);\n+\n+\t\tjson_error_t error;\n+\t\tjson_t *root = json_loadb(message->payload, message->payloadlen, 0, &error);\n+\t\tctx->gateway->incoming_request(&janus_mqtt_transport_, ctx, NULL, admin, root, &error);\n+\t}\n+\n+\tMQTTAsync_freeMessage(&message);\n+\tMQTTAsync_free(topicName);\n+\treturn TRUE;\n+}\n+\n+void janus_mqtt_client_delivery_complete(void *context, MQTTAsync_token token) {\n+}\n+\n+int janus_mqtt_client_connect(janus_mqtt_context *ctx) {\n+\tMQTTAsync_connectOptions options = MQTTAsync_connectOptions_initializer;\n+\toptions.keepAliveInterval = ctx->connect.keep_alive_interval;\n+\toptions.cleansession = ctx->connect.cleansession;\n+\toptions.automaticReconnect = TRUE;\n+\toptions.onSuccess = janus_mqtt_client_connect_success;\n+\toptions.onFailure = janus_mqtt_client_connect_failure;\n+\toptions.context = ctx;\n+\treturn MQTTAsync_connect(ctx->client, &options);\n+}\n+\n+void janus_mqtt_client_connect_success(void *context, MQTTAsync_successData *response) {\n+\tJANUS_LOG(LOG_INFO, \"MQTT client has been successfully connected to the broker\\n\");\n+\n+\t/* Subscribe to one (janus or admin) topic at the time */\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tif(janus_mqtt_api_enabled_) {\n+\t\tint rc = janus_mqtt_client_subscribe(context);\n+\t\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\t\tJANUS_LOG(LOG_ERR, \"Can't subscribe to MQTT topic: %s, return code: %d\\n\", ctx->subscribe.topic, rc);\n+\t\t}\n+\t}\n+\telse if(janus_mqtt_admin_api_enabled_) {\n+\t\tint rc = janus_mqtt_client_admin_subscribe(context);\n+\t\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\t\tJANUS_LOG(LOG_ERR, \"Can't subscribe to MQTT admin topic: %s, return code: %d\\n\", ctx->admin.subscribe.topic, rc);\n+\t\t}\n+\t}\n+}\n+\n+void janus_mqtt_client_connect_failure(void *context, MQTTAsync_failureData *response) {\n+\tint rc = response ? response->code : 0;\n+\tJANUS_LOG(LOG_ERR, \"MQTT client has been failed connecting to the broker, return code: %d. Reconnecting...\\n\", rc);\n+\t// automatic reconnect\n+}\n+\n+int janus_mqtt_client_reconnect(janus_mqtt_context *ctx) {\n+\tMQTTAsync_disconnectOptions options = MQTTAsync_disconnectOptions_initializer;\n+\toptions.onSuccess = janus_mqtt_client_reconnect_success;\n+\toptions.onFailure = janus_mqtt_client_reconnect_failure;\n+\toptions.context = ctx;\n+\toptions.timeout = ctx->disconnect.timeout;\n+\treturn MQTTAsync_disconnect(ctx->client, &options);\n+}\n+\n+void janus_mqtt_client_reconnect_success(void *context, MQTTAsync_successData *response) {\n+\tJANUS_LOG(LOG_INFO, \"MQTT client has been successfully disconnected. Reconnecting...\\n\");\n+\n+\tint rc = janus_mqtt_client_connect(context);\n+\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\tJANUS_LOG(LOG_FATAL, \"Can't connect to MQTT broker, return code: %d\\n\", rc);\n+\t}\n+}\n+\n+void janus_mqtt_client_reconnect_failure(void *context, MQTTAsync_failureData *response) {\n+\tint rc = response ? response->code : 0;\n+\tJANUS_LOG(LOG_ERR, \"MQTT client has been failed reconnecting from MQTT broker, return code: %d\\n\", rc);\n+}\n+\n+int janus_mqtt_client_disconnect(janus_mqtt_context *ctx) {\n+\tMQTTAsync_disconnectOptions options = MQTTAsync_disconnectOptions_initializer;\n+\toptions.onSuccess = janus_mqtt_client_disconnect_success;\n+\toptions.onFailure = janus_mqtt_client_disconnect_failure;\n+\toptions.context = ctx;\n+\toptions.timeout = ctx->disconnect.timeout;\n+\treturn MQTTAsync_disconnect(ctx->client, &options);\n+}\n+\n+void janus_mqtt_client_disconnect_success(void *context, MQTTAsync_successData *response) {\n+\tJANUS_LOG(LOG_INFO, \"MQTT client has been successfully disconnected. Destroying the client...\\n\");\n+\n+\tjanus_mqtt_client_destroy_context(context);\n+}\n+\n+void janus_mqtt_client_disconnect_failure(void *context, MQTTAsync_failureData *response) {\n+\tint rc = response ? response->code : 0;\n+\tJANUS_LOG(LOG_ERR, \"Can't disconnect from MQTT broker, return code: %d\\n\", rc);\n+\n+\tjanus_mqtt_client_destroy_context(context);\n+}\n+\n+int janus_mqtt_client_subscribe(janus_mqtt_context *ctx) {\n+\tMQTTAsync_responseOptions options = MQTTAsync_responseOptions_initializer;\n+\toptions.onSuccess = janus_mqtt_client_subscribe_success;\n+\toptions.onFailure = janus_mqtt_client_subscribe_failure;\n+\toptions.context = ctx;\n+\treturn MQTTAsync_subscribe(ctx->client, ctx->subscribe.topic, ctx->subscribe.qos, &options);\n+}\n+\n+void janus_mqtt_client_subscribe_success(void *context, MQTTAsync_successData *response) {\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tJANUS_LOG(LOG_INFO, \"MQTT client has been successfully subscribed to MQTT topic: %s\\n\", ctx->subscribe.topic);\n+\n+\t/* Subscribe to admin topic if we haven't done it yet */\n+\tif(janus_mqtt_admin_api_enabled_ && (!janus_mqtt_api_enabled_ || strcasecmp(ctx->subscribe.topic, ctx->admin.subscribe.topic))) {\n+\t\tint rc = janus_mqtt_client_admin_subscribe(context);\n+\t\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\t\tJANUS_LOG(LOG_ERR, \"Can't subscribe to MQTT topic: %s, return code: %d\\n\", ctx->subscribe.topic, rc);\n+\t\t}\n+\t}\n+}\n+\n+void janus_mqtt_client_subscribe_failure(void *context, MQTTAsync_failureData *response) {\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tint rc = response ? response->code : 0;\n+\tJANUS_LOG(LOG_ERR, \"MQTT client has been failed subscribing to MQTT topic: %s, return code: %d. Reconnecting...\\n\", ctx->subscribe.topic, rc);\n+\n+\t{ // reconnect\n+\t\tint rc = janus_mqtt_client_reconnect(context);\n+\t\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\t\tJANUS_LOG(LOG_FATAL, \"Can't reconnect to MQTT broker, return code: %d\\n\", rc);\n+\t\t}\n+\t}\n+}\n+\n+int janus_mqtt_client_admin_subscribe(janus_mqtt_context *ctx) {\n+\tMQTTAsync_responseOptions options = MQTTAsync_responseOptions_initializer;\n+\toptions.onSuccess = janus_mqtt_client_admin_subscribe_success;\n+\toptions.onFailure = janus_mqtt_client_admin_subscribe_failure;\n+\toptions.context = ctx;\n+\treturn MQTTAsync_subscribe(ctx->client, ctx->admin.subscribe.topic, ctx->admin.subscribe.qos, &options);\n+}\n+\n+void janus_mqtt_client_admin_subscribe_success(void *context, MQTTAsync_successData *response) {\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tJANUS_LOG(LOG_INFO, \"MQTT client has been successfully subscribed to MQTT topic: %s\\n\", ctx->admin.subscribe.topic);\n+}\n+\n+void janus_mqtt_client_admin_subscribe_failure(void *context, MQTTAsync_failureData *response) {\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tint rc = response ? response->code : 0;\n+\tJANUS_LOG(LOG_ERR, \"MQTT client has been failed subscribing to MQTT topic: %s, return code: %d. Reconnecting...\\n\", ctx->admin.subscribe.topic, rc);\n+\n+\t{ // reconnect\n+\t\tint rc = janus_mqtt_client_reconnect(context);\n+\t\tif(rc != MQTTASYNC_SUCCESS) {\n+\t\t\tJANUS_LOG(LOG_FATAL, \"Can't reconnect to MQTT broker, return code: %d\\n\", rc);\n+\t\t}\n+\t}\n+}\n+\n+int janus_mqtt_client_publish_janus_message(janus_mqtt_context *ctx, char *payload) {\n+\tMQTTAsync_message msg = MQTTAsync_message_initializer;\n+\tmsg.payload = payload;\n+\tmsg.payloadlen = strlen(payload);\n+\tmsg.qos = ctx->publish.qos;\n+\tmsg.retained = 0;\n+\n+\tMQTTAsync_responseOptions options;\n+\toptions.onSuccess = janus_mqtt_client_publish_janus_success;\n+\toptions.onFailure = janus_mqtt_client_publish_janus_failure;\n+\toptions.context = ctx;\n+\treturn MQTTAsync_sendMessage(ctx->client, ctx->publish.topic, &msg, &options);\n+}\n+\n+void janus_mqtt_client_publish_janus_success(void *context, MQTTAsync_successData *response) {\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tJANUS_LOG(LOG_HUGE, \"MQTT client has been successfully published to MQTT topic: %s\\n\", ctx->publish.topic);\n+}\n+\n+void janus_mqtt_client_publish_janus_failure(void *context, MQTTAsync_failureData *response) {\n+\tjanus_mqtt_context *ctx = (janus_mqtt_context *)context;\n+\tint rc = response ? response->code : 0;\n+\tJANUS_LOG(LOG_ERR, \"MQTT client has been failed publishing to MQTT topic: %s, return code: %d\\n\", ctx->publish.topic, rc);\n+}\n+\n+int janus_mqtt_client_publish_admin_message(janus_mqtt_context *ctx, char *payload) {","path":"transports/janus_mqtt.c","position":612,"original_position":612,"commit_id":"0a5d0d2099b30ba72d3a9c5a9f197a028cdbee35","original_commit_id":"0a5d0d2099b30ba72d3a9c5a9f197a028cdbee35","user":{"login":"lminiero","id":3684796,"avatar_url":"https://avatars.githubusercontent.com/u/3684796?v=3","gravatar_id":"","url":"https://api.github.com/users/lminiero","html_url":"https://github.com/lminiero","followers_url":"https://api.github.com/users/lminiero/followers","following_url":"https://api.github.com/users/lminiero/following{/other_user}","gists_url":"https://api.github.com/users/lminiero/gists{/gist_id}","starred_url":"https://api.github.com/users/lminiero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lminiero/subscriptions","organizations_url":"https://api.github.com/users/lminiero/orgs","repos_url":"https://api.github.com/users/lminiero/repos","events_url":"https://api.github.com/users/lminiero/events{/privacy}","received_events_url":"https://api.github.com/users/lminiero/received_events","type":"User","site_admin":false},"body":"Can't some of this methods, which are basically the same code, just envisage something like an additional `gboolean admin` flag, and then use the Janus or Admin stuff according to its value? I guess it makes sense for some callbacks to remain separated, especially if you don't have anything they tell you to allow you to retrieve the original context, for active methods should allow for that.","created_at":"2016-10-05T10:20:13Z","updated_at":"2016-10-05T10:23:30Z","html_url":"https://github.com/meetecho/janus-gateway/pull/645#discussion_r81942290","pull_request_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645","_links":{"self":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments/81942290"},"html":{"href":"https://github.com/meetecho/janus-gateway/pull/645#discussion_r81942290"},"pull_request":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645"}}},"pull_request":{"url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645","id":87907520,"html_url":"https://github.com/meetecho/janus-gateway/pull/645","diff_url":"https://github.com/meetecho/janus-gateway/pull/645.diff","patch_url":"https://github.com/meetecho/janus-gateway/pull/645.patch","issue_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/645","number":645,"state":"open","locked":false,"title":"Add support of MQTT transport","user":{"login":"manifest","id":439058,"avatar_url":"https://avatars.githubusercontent.com/u/439058?v=3","gravatar_id":"","url":"https://api.github.com/users/manifest","html_url":"https://github.com/manifest","followers_url":"https://api.github.com/users/manifest/followers","following_url":"https://api.github.com/users/manifest/following{/other_user}","gists_url":"https://api.github.com/users/manifest/gists{/gist_id}","starred_url":"https://api.github.com/users/manifest/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/manifest/subscriptions","organizations_url":"https://api.github.com/users/manifest/orgs","repos_url":"https://api.github.com/users/manifest/repos","events_url":"https://api.github.com/users/manifest/events{/privacy}","received_events_url":"https://api.github.com/users/manifest/received_events","type":"User","site_admin":false},"body":"Message Queue Telemetry Transport (MQTT) is a popular and lightweight publish and subscribe messaging transport. It's also an open standard, and there is already exist a lot of open source brokers/clients. It would be nice to have build-in support for this protocol within Janus Gateway.\r\n\r\nIf it's ok, I could make another commit with Docker file describing a container with all needed development environment and installed dependencies including MQTT broker within it. I also would glad to fix any code-style and other issues. Just let me know about it","created_at":"2016-10-04T14:39:55Z","updated_at":"2016-10-05T10:23:30Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2293e0d0857a230f1072cfa439e76514f3e50361","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645/commits","review_comments_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645/comments","review_comment_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments{/number}","comments_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/645/comments","statuses_url":"https://api.github.com/repos/meetecho/janus-gateway/statuses/0a5d0d2099b30ba72d3a9c5a9f197a028cdbee35","head":{"label":"manifest:feature/janus-mqtt-transport","ref":"feature/janus-mqtt-transport","sha":"0a5d0d2099b30ba72d3a9c5a9f197a028cdbee35","user":{"login":"manifest","id":439058,"avatar_url":"https://avatars.githubusercontent.com/u/439058?v=3","gravatar_id":"","url":"https://api.github.com/users/manifest","html_url":"https://github.com/manifest","followers_url":"https://api.github.com/users/manifest/followers","following_url":"https://api.github.com/users/manifest/following{/other_user}","gists_url":"https://api.github.com/users/manifest/gists{/gist_id}","starred_url":"https://api.github.com/users/manifest/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/manifest/subscriptions","organizations_url":"https://api.github.com/users/manifest/orgs","repos_url":"https://api.github.com/users/manifest/repos","events_url":"https://api.github.com/users/manifest/events{/privacy}","received_events_url":"https://api.github.com/users/manifest/received_events","type":"User","site_admin":false},"repo":{"id":69851105,"name":"janus-gateway","full_name":"manifest/janus-gateway","owner":{"login":"manifest","id":439058,"avatar_url":"https://avatars.githubusercontent.com/u/439058?v=3","gravatar_id":"","url":"https://api.github.com/users/manifest","html_url":"https://github.com/manifest","followers_url":"https://api.github.com/users/manifest/followers","following_url":"https://api.github.com/users/manifest/following{/other_user}","gists_url":"https://api.github.com/users/manifest/gists{/gist_id}","starred_url":"https://api.github.com/users/manifest/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/manifest/subscriptions","organizations_url":"https://api.github.com/users/manifest/orgs","repos_url":"https://api.github.com/users/manifest/repos","events_url":"https://api.github.com/users/manifest/events{/privacy}","received_events_url":"https://api.github.com/users/manifest/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/manifest/janus-gateway","description":"Janus WebRTC Gateway","fork":true,"url":"https://api.github.com/repos/manifest/janus-gateway","forks_url":"https://api.github.com/repos/manifest/janus-gateway/forks","keys_url":"https://api.github.com/repos/manifest/janus-gateway/keys{/key_id}","collaborators_url":"https://api.github.com/repos/manifest/janus-gateway/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/manifest/janus-gateway/teams","hooks_url":"https://api.github.com/repos/manifest/janus-gateway/hooks","issue_events_url":"https://api.github.com/repos/manifest/janus-gateway/issues/events{/number}","events_url":"https://api.github.com/repos/manifest/janus-gateway/events","assignees_url":"https://api.github.com/repos/manifest/janus-gateway/assignees{/user}","branches_url":"https://api.github.com/repos/manifest/janus-gateway/branches{/branch}","tags_url":"https://api.github.com/repos/manifest/janus-gateway/tags","blobs_url":"https://api.github.com/repos/manifest/janus-gateway/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/manifest/janus-gateway/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/manifest/janus-gateway/git/refs{/sha}","trees_url":"https://api.github.com/repos/manifest/janus-gateway/git/trees{/sha}","statuses_url":"https://api.github.com/repos/manifest/janus-gateway/statuses/{sha}","languages_url":"https://api.github.com/repos/manifest/janus-gateway/languages","stargazers_url":"https://api.github.com/repos/manifest/janus-gateway/stargazers","contributors_url":"https://api.github.com/repos/manifest/janus-gateway/contributors","subscribers_url":"https://api.github.com/repos/manifest/janus-gateway/subscribers","subscription_url":"https://api.github.com/repos/manifest/janus-gateway/subscription","commits_url":"https://api.github.com/repos/manifest/janus-gateway/commits{/sha}","git_commits_url":"https://api.github.com/repos/manifest/janus-gateway/git/commits{/sha}","comments_url":"https://api.github.com/repos/manifest/janus-gateway/comments{/number}","issue_comment_url":"https://api.github.com/repos/manifest/janus-gateway/issues/comments{/number}","contents_url":"https://api.github.com/repos/manifest/janus-gateway/contents/{+path}","compare_url":"https://api.github.com/repos/manifest/janus-gateway/compare/{base}...{head}","merges_url":"https://api.github.com/repos/manifest/janus-gateway/merges","archive_url":"https://api.github.com/repos/manifest/janus-gateway/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/manifest/janus-gateway/downloads","issues_url":"https://api.github.com/repos/manifest/janus-gateway/issues{/number}","pulls_url":"https://api.github.com/repos/manifest/janus-gateway/pulls{/number}","milestones_url":"https://api.github.com/repos/manifest/janus-gateway/milestones{/number}","notifications_url":"https://api.github.com/repos/manifest/janus-gateway/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/manifest/janus-gateway/labels{/name}","releases_url":"https://api.github.com/repos/manifest/janus-gateway/releases{/id}","deployments_url":"https://api.github.com/repos/manifest/janus-gateway/deployments","created_at":"2016-10-03T07:56:30Z","updated_at":"2016-10-03T07:56:31Z","pushed_at":"2016-10-05T07:14:57Z","git_url":"git://github.com/manifest/janus-gateway.git","ssh_url":"git@github.com:manifest/janus-gateway.git","clone_url":"https://github.com/manifest/janus-gateway.git","svn_url":"https://github.com/manifest/janus-gateway","homepage":null,"size":14142,"stargazers_count":0,"watchers_count":0,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"meetecho:master","ref":"master","sha":"3cd959fde11a535bdcc4f41f0aea1f1831300f93","user":{"login":"meetecho","id":4520545,"avatar_url":"https://avatars.githubusercontent.com/u/4520545?v=3","gravatar_id":"","url":"https://api.github.com/users/meetecho","html_url":"https://github.com/meetecho","followers_url":"https://api.github.com/users/meetecho/followers","following_url":"https://api.github.com/users/meetecho/following{/other_user}","gists_url":"https://api.github.com/users/meetecho/gists{/gist_id}","starred_url":"https://api.github.com/users/meetecho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meetecho/subscriptions","organizations_url":"https://api.github.com/users/meetecho/orgs","repos_url":"https://api.github.com/users/meetecho/repos","events_url":"https://api.github.com/users/meetecho/events{/privacy}","received_events_url":"https://api.github.com/users/meetecho/received_events","type":"Organization","site_admin":false},"repo":{"id":16734696,"name":"janus-gateway","full_name":"meetecho/janus-gateway","owner":{"login":"meetecho","id":4520545,"avatar_url":"https://avatars.githubusercontent.com/u/4520545?v=3","gravatar_id":"","url":"https://api.github.com/users/meetecho","html_url":"https://github.com/meetecho","followers_url":"https://api.github.com/users/meetecho/followers","following_url":"https://api.github.com/users/meetecho/following{/other_user}","gists_url":"https://api.github.com/users/meetecho/gists{/gist_id}","starred_url":"https://api.github.com/users/meetecho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meetecho/subscriptions","organizations_url":"https://api.github.com/users/meetecho/orgs","repos_url":"https://api.github.com/users/meetecho/repos","events_url":"https://api.github.com/users/meetecho/events{/privacy}","received_events_url":"https://api.github.com/users/meetecho/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/meetecho/janus-gateway","description":"Janus WebRTC Gateway","fork":false,"url":"https://api.github.com/repos/meetecho/janus-gateway","forks_url":"https://api.github.com/repos/meetecho/janus-gateway/forks","keys_url":"https://api.github.com/repos/meetecho/janus-gateway/keys{/key_id}","collaborators_url":"https://api.github.com/repos/meetecho/janus-gateway/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/meetecho/janus-gateway/teams","hooks_url":"https://api.github.com/repos/meetecho/janus-gateway/hooks","issue_events_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/events{/number}","events_url":"https://api.github.com/repos/meetecho/janus-gateway/events","assignees_url":"https://api.github.com/repos/meetecho/janus-gateway/assignees{/user}","branches_url":"https://api.github.com/repos/meetecho/janus-gateway/branches{/branch}","tags_url":"https://api.github.com/repos/meetecho/janus-gateway/tags","blobs_url":"https://api.github.com/repos/meetecho/janus-gateway/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/meetecho/janus-gateway/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/meetecho/janus-gateway/git/refs{/sha}","trees_url":"https://api.github.com/repos/meetecho/janus-gateway/git/trees{/sha}","statuses_url":"https://api.github.com/repos/meetecho/janus-gateway/statuses/{sha}","languages_url":"https://api.github.com/repos/meetecho/janus-gateway/languages","stargazers_url":"https://api.github.com/repos/meetecho/janus-gateway/stargazers","contributors_url":"https://api.github.com/repos/meetecho/janus-gateway/contributors","subscribers_url":"https://api.github.com/repos/meetecho/janus-gateway/subscribers","subscription_url":"https://api.github.com/repos/meetecho/janus-gateway/subscription","commits_url":"https://api.github.com/repos/meetecho/janus-gateway/commits{/sha}","git_commits_url":"https://api.github.com/repos/meetecho/janus-gateway/git/commits{/sha}","comments_url":"https://api.github.com/repos/meetecho/janus-gateway/comments{/number}","issue_comment_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/comments{/number}","contents_url":"https://api.github.com/repos/meetecho/janus-gateway/contents/{+path}","compare_url":"https://api.github.com/repos/meetecho/janus-gateway/compare/{base}...{head}","merges_url":"https://api.github.com/repos/meetecho/janus-gateway/merges","archive_url":"https://api.github.com/repos/meetecho/janus-gateway/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/meetecho/janus-gateway/downloads","issues_url":"https://api.github.com/repos/meetecho/janus-gateway/issues{/number}","pulls_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls{/number}","milestones_url":"https://api.github.com/repos/meetecho/janus-gateway/milestones{/number}","notifications_url":"https://api.github.com/repos/meetecho/janus-gateway/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/meetecho/janus-gateway/labels{/name}","releases_url":"https://api.github.com/repos/meetecho/janus-gateway/releases{/id}","deployments_url":"https://api.github.com/repos/meetecho/janus-gateway/deployments","created_at":"2014-02-11T15:14:39Z","updated_at":"2016-10-05T03:58:12Z","pushed_at":"2016-10-05T07:14:57Z","git_url":"git://github.com/meetecho/janus-gateway.git","ssh_url":"git@github.com:meetecho/janus-gateway.git","clone_url":"https://github.com/meetecho/janus-gateway.git","svn_url":"https://github.com/meetecho/janus-gateway","homepage":null,"size":17719,"stargazers_count":795,"watchers_count":795,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":322,"mirror_url":null,"open_issues_count":28,"forks":322,"open_issues":28,"watchers":795,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645"},"html":{"href":"https://github.com/meetecho/janus-gateway/pull/645"},"issue":{"href":"https://api.github.com/repos/meetecho/janus-gateway/issues/645"},"comments":{"href":"https://api.github.com/repos/meetecho/janus-gateway/issues/645/comments"},"review_comments":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645/comments"},"review_comment":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/645/commits"},"statuses":{"href":"https://api.github.com/repos/meetecho/janus-gateway/statuses/0a5d0d2099b30ba72d3a9c5a9f197a028cdbee35"}}}}
{"actor":{"display_login":"lminiero"}}