int janus_websockets_send_message(void *transport, void *request_id, gboolean admin, json_t *message) { if(message == NULL) return -1; if(transport == NULL) { json_decref(message); return -1; } /* Make sure this is not related to a closed /freed WebSocket session */ janus_mutex_lock(&old_wss_mutex); janus_websockets_client *client = (janus_websockets_client *)transport; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) != NULL || !client->wsi) { #else if(g_list_find(old_wss, client) != NULL || !client->context || !client->wsi) { #endif json_decref(message); message = NULL; transport = NULL; janus_mutex_unlock(&old_wss_mutex); return -1; } janus_mutex_lock(&client->mutex); /* Convert to string and enqueue */ char *payload = json_dumps(message, json_format); g_async_queue_push(client->messages, payload); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); janus_mutex_unlock(&old_wss_mutex); json_decref(message); return 0; } void janus_websockets_session_created(void *transport, guint64 session_id) { /* We don't care */ } void janus_websockets_session_over(void *transport, guint64 session_id, gboolean timeout) { if(transport == NULL || !timeout) return; /* We only care if it's a timeout: if so, close the connection */ janus_websockets_client *client = (janus_websockets_client *)transport; /* Make sure this is not related to a closed WebSocket session */ janus_mutex_lock(&old_wss_mutex); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) == NULL && client->wsi){ #else if(g_list_find(old_wss, client) == NULL && client->context && client->wsi){ #endif janus_mutex_lock(&client->mutex); client->session_timeout = 1; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); } janus_mutex_unlock(&old_wss_mutex); } /* Thread */ void *janus_websockets_thread(void *data) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws_context *service = (struct lws_context *)data; #else struct libwebsocket_context *service = (struct libwebsocket_context *)data; #endif if(service == NULL) { JANUS_LOG(LOG_ERR, "Invalid service\n"); return NULL; } const char *type = NULL; if(service == wss) type = "WebSocket (Janus API)"; else if(service == swss) type = "Secure WebSocket (Janus API)"; else if(service == admin_wss) type = "WebSocket (Admin API)"; else if(service == admin_swss) type = "Secure WebSocket (Admin API)"; JANUS_LOG(LOG_INFO, "%s thread started\n", type); while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { /* libwebsockets is single thread, we cycle through events here */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_service(service, 50); #else libwebsocket_service(service, 50); #endif } /* Get rid of the WebSockets server */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_cancel_service(service); #else libwebsocket_cancel_service(service); #endif /* Done */ JANUS_LOG(LOG_INFO, "%s thread ended\n", type); return NULL; } /* WebSockets */ static int janus_websockets_callback_http( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* This endpoint cannot be used for HTTP */ switch(reason) { case LWS_CALLBACK_HTTP: JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint\n"); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_return_http_status(wsi, 403, NULL); #else libwebsockets_return_http_status(this, wsi, 403, NULL); #endif /* Close and free connection */ return -1; case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: if (!in) { JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint: no sub-protocol specified\n"); return -1; } break; default: break; } return 0; } static int janus_websockets_callback_https( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the HTTP handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback_http(wsi, reason, user, in, len); #else return janus_websockets_callback_http(this, wsi, reason, user, in, len); #endif } /* This callback handles Janus API requests */ static int janus_websockets_common_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len, gboolean admin) { const char *log_prefix = admin ? "AdminWSS" : "WSS"; janus_websockets_client *ws_client = (janus_websockets_client *)user; switch(reason) { case LWS_CALLBACK_ESTABLISHED: { /* Is there any filtering we should apply? */ char name[256], ip[256]; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, 256, ip, 256); #else libwebsockets_get_peer_addresses(this, wsi, libwebsocket_get_socket_fd(wsi), name, 256, ip, 256); #endif JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s by %s\n", log_prefix, wsi, ip, name); if(!janus_websockets_is_allowed(ip, admin)) { JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", log_prefix, wsi, ip, admin ? "Admin" : "Janus"); /* Close the connection */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif return -1; } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Clean the old sessions list, in case this pointer was used before */ janus_mutex_lock(&old_wss_mutex); if(g_list_find(old_wss, ws_client) != NULL) old_wss = g_list_remove(old_wss, ws_client); janus_mutex_unlock(&old_wss_mutex); /* Prepare the session */ #ifndef HAVE_LIBWEBSOCKETS_NEWAPI ws_client->context = this; #endif ws_client->wsi = wsi; ws_client->messages = g_async_queue_new(); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; ws_client->session_timeout = 0; ws_client->destroy = 0; janus_mutex_init(&ws_client->mutex); /* Let us know when the WebSocket channel becomes writeable */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif JANUS_LOG(LOG_VERB, "[%s-%p] -- Ready to be used!\n", log_prefix, wsi); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", admin ? json_true() : json_false()); json_object_set_new(info, "ip", json_string(ip)); gateway->notify_event(&janus_websockets_transport, ws_client, info); } return 0; } case LWS_CALLBACK_RECEIVE: { JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Is this a new message, or part of a fragmented one? */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI const size_t remaining = lws_remaining_packet_payload(wsi); #else const size_t remaining = libwebsockets_remaining_packet_payload(wsi); #endif if(ws_client->incoming == NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] First fragment: %zu bytes, %zu remaining\n", log_prefix, wsi, len, remaining); ws_client->incoming = g_malloc0(len+1); memcpy(ws_client->incoming, in, len); ws_client->incoming[len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming); } else { size_t offset = strlen(ws_client->incoming); JANUS_LOG(LOG_HUGE, "[%s-%p] Appending fragment: offset %zu, %zu bytes, %zu remaining\n", log_prefix, wsi, offset, len, remaining); ws_client->incoming = g_realloc(ws_client->incoming, offset+len+1); memcpy(ws_client->incoming+offset, in, len); ws_client->incoming[offset+len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming+offset); } #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(remaining > 0 || !lws_is_final_fragment(wsi)) { #else if(remaining > 0 || !libwebsocket_is_final_fragment(wsi)) { #endif /* Still waiting for some more fragments */ JANUS_LOG(LOG_HUGE, "[%s-%p] Waiting for more fragments\n", log_prefix, wsi); return 0; } JANUS_LOG(LOG_HUGE, "[%s-%p] Done, parsing message: %zu bytes\n", log_prefix, wsi, strlen(ws_client->incoming)); /* If we got here, the message is complete: parse the JSON payload */ json_error_t error; json_t *root = json_loads(ws_client->incoming, 0, &error); g_free(ws_client->incoming); ws_client->incoming = NULL; /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_websockets_transport, ws_client, NULL, admin, root, &error); return 0; } case LWS_CALLBACK_SERVER_WRITEABLE: { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(!ws_client->destroy && !g_atomic_int_get(&stopping)) { janus_mutex_lock(&ws_client->mutex); /* Check if we have a pending/partial write to complete first */ if(ws_client->buffer && ws_client->bufpending > 0 && ws_client->bufoffset > 0 && !ws_client->destroy && !g_atomic_int_get(&stopping)) { JANUS_LOG(LOG_HUGE, "[%s-%p] Completing pending WebSocket write (still need to write last %d bytes)...\n", log_prefix, wsi, ws_client->bufpending); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%d bytes\n", log_prefix, wsi, sent, ws_client->bufpending); if(sent > -1 && sent < ws_client->bufpending) { /* We still couldn't send everything that was left, we'll try and complete this in the next round */ ws_client->bufpending -= sent; ws_client->bufoffset += sent; } else { /* Clear the pending/partial write queue */ ws_client->bufpending = 0; ws_client->bufoffset = 0; } /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } /* Shoot all the pending messages */ char *response = g_async_queue_try_pop(ws_client->messages); if(response && !ws_client->destroy && !g_atomic_int_get(&stopping)) { /* Gotcha! */ int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if(ws_client->buffer == NULL) { /* Let's allocate a shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Allocating %d bytes (response is %zu bytes)\n", log_prefix, wsi, buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_malloc0(buflen); } else if(buflen > ws_client->buflen) { /* We need a larger shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Re-allocating to %d bytes (was %d, response is %zu bytes)\n", log_prefix, wsi, buflen, ws_client->buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_realloc(ws_client->buffer, buflen); } memcpy(ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); JANUS_LOG(LOG_HUGE, "[%s-%p] Sending WebSocket message (%zu bytes)...\n", log_prefix, wsi, strlen(response)); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%zu bytes\n", log_prefix, wsi, sent, strlen(response)); if(sent > -1 && sent < (int)strlen(response)) { /* We couldn't send everything in a single write, we'll complete this in the next round */ ws_client->bufpending = strlen(response) - sent; ws_client->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; JANUS_LOG(LOG_HUGE, "[%s-%p] -- Couldn't write all bytes (%d missing), setting offset %d\n", log_prefix, wsi, ws_client->bufpending, ws_client->bufoffset); } /* We can get rid of the message */ free(response); /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } janus_mutex_unlock(&ws_client->mutex); } return 0; } case LWS_CALLBACK_CLOSED: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, closing\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- closed\n", log_prefix, wsi); return 0; } case LWS_CALLBACK_WSI_DESTROY: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, destroying\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- destroyed\n", log_prefix, wsi); return 0; } default: if(wsi != NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] %d (%s)\n", log_prefix, wsi, reason, janus_websockets_reason_string(reason)); } else { JANUS_LOG(LOG_HUGE, "[%s] %d (%s)\n", log_prefix, reason, janus_websockets_reason_string(reason)); } break; } return 0; } /* This callback handles Janus API requests */ static int janus_websockets_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, FALSE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, FALSE); #endif } static int janus_websockets_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Janus API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback(wsi, reason, user, in, len); #else return janus_websockets_callback(this, wsi, reason, user, in, len); #endif } /* This callback handles Admin API requests */ static int janus_websockets_admin_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, TRUE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, TRUE); #endif } static int janus_websockets_admin_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Admin API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_admin_callback(wsi, reason, user, in, len); #else return janus_websockets_admin_callback(this, wsi, reason, user, in, len); #endif }
/* This callback handles Janus API requests */ static int janus_websockets_common_callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len, gboolean admin) { const char *log_prefix = admin ? "AdminWSS" : "WSS"; janus_websockets_client *ws_client = (janus_websockets_client *)user; switch(reason) { case LWS_CALLBACK_ESTABLISHED: { /* Is there any filtering we should apply? */ char ip[256]; #ifdef HAVE_LIBWEBSOCKETS_PEER_SIMPLE lws_get_peer_simple(wsi, ip, 256); JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s\n", log_prefix, wsi, ip); #else char name[256]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, 256, ip, 256); JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s by %s\n", log_prefix, wsi, ip, name); #endif if(!janus_websockets_is_allowed(ip, admin)) { JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", log_prefix, wsi, ip, admin ? "Admin" : "Janus"); /* Close the connection */ lws_callback_on_writable(wsi); return -1; } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Prepare the session */ ws_client->wsi = wsi; ws_client->messages = g_async_queue_new(); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; g_atomic_int_set(&ws_client->destroyed, 0); ws_client->ts = janus_transport_session_create(ws_client, NULL); /* Let us know when the WebSocket channel becomes writeable */ lws_callback_on_writable(wsi); JANUS_LOG(LOG_VERB, "[%s-%p] -- Ready to be used!\n", log_prefix, wsi); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", admin ? json_true() : json_false()); json_object_set_new(info, "ip", json_string(ip)); gateway->notify_event(&janus_websockets_transport, ws_client->ts, info); } return 0; } case LWS_CALLBACK_RECEIVE: { JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len); if(ws_client == NULL || ws_client->wsi == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(g_atomic_int_get(&ws_client->destroyed)) return 0; /* Is this a new message, or part of a fragmented one? */ const size_t remaining = lws_remaining_packet_payload(wsi); if(ws_client->incoming == NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] First fragment: %zu bytes, %zu remaining\n", log_prefix, wsi, len, remaining); ws_client->incoming = g_malloc(len+1); memcpy(ws_client->incoming, in, len); ws_client->incoming[len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming); } else { size_t offset = strlen(ws_client->incoming); JANUS_LOG(LOG_HUGE, "[%s-%p] Appending fragment: offset %zu, %zu bytes, %zu remaining\n", log_prefix, wsi, offset, len, remaining); ws_client->incoming = g_realloc(ws_client->incoming, offset+len+1); memcpy(ws_client->incoming+offset, in, len); ws_client->incoming[offset+len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming+offset); } if(remaining > 0 || !lws_is_final_fragment(wsi)) { /* Still waiting for some more fragments */ JANUS_LOG(LOG_HUGE, "[%s-%p] Waiting for more fragments\n", log_prefix, wsi); return 0; } JANUS_LOG(LOG_HUGE, "[%s-%p] Done, parsing message: %zu bytes\n", log_prefix, wsi, strlen(ws_client->incoming)); /* If we got here, the message is complete: parse the JSON payload */ json_error_t error; json_t *root = json_loads(ws_client->incoming, 0, &error); g_free(ws_client->incoming); ws_client->incoming = NULL; /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_websockets_transport, ws_client->ts, NULL, admin, root, &error); return 0; } case LWS_CALLBACK_SERVER_WRITEABLE: { if(ws_client == NULL || ws_client->wsi == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(!g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { janus_mutex_lock(&ws_client->ts->mutex); /* Check if we have a pending/partial write to complete first */ if(ws_client->buffer && ws_client->bufpending > 0 && ws_client->bufoffset > 0 && !g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { JANUS_LOG(LOG_HUGE, "[%s-%p] Completing pending WebSocket write (still need to write last %d bytes)...\n", log_prefix, wsi, ws_client->bufpending); int sent = lws_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%d bytes\n", log_prefix, wsi, sent, ws_client->bufpending); if(sent > -1 && sent < ws_client->bufpending) { /* We still couldn't send everything that was left, we'll try and complete this in the next round */ ws_client->bufpending -= sent; ws_client->bufoffset += sent; } else { /* Clear the pending/partial write queue */ ws_client->bufpending = 0; ws_client->bufoffset = 0; } /* Done for this round, check the next response/notification later */ lws_callback_on_writable(wsi); janus_mutex_unlock(&ws_client->ts->mutex); return 0; } /* Shoot all the pending messages */ char *response = g_async_queue_try_pop(ws_client->messages); if(response && !g_atomic_int_get(&ws_client->destroyed) && !g_atomic_int_get(&stopping)) { /* Gotcha! */ int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if (buflen > ws_client->buflen) { /* We need a larger shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Re-allocating to %d bytes (was %d, response is %zu bytes)\n", log_prefix, wsi, buflen, ws_client->buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_realloc(ws_client->buffer, buflen); } memcpy(ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); JANUS_LOG(LOG_HUGE, "[%s-%p] Sending WebSocket message (%zu bytes)...\n", log_prefix, wsi, strlen(response)); int sent = lws_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%zu bytes\n", log_prefix, wsi, sent, strlen(response)); if(sent > -1 && sent < (int)strlen(response)) { /* We couldn't send everything in a single write, we'll complete this in the next round */ ws_client->bufpending = strlen(response) - sent; ws_client->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; JANUS_LOG(LOG_HUGE, "[%s-%p] -- Couldn't write all bytes (%d missing), setting offset %d\n", log_prefix, wsi, ws_client->bufpending, ws_client->bufoffset); } /* We can get rid of the message */ free(response); /* Done for this round, check the next response/notification later */ lws_callback_on_writable(wsi); janus_mutex_unlock(&ws_client->ts->mutex); return 0; } janus_mutex_unlock(&ws_client->ts->mutex); } return 0; } case LWS_CALLBACK_CLOSED: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, closing\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- closed\n", log_prefix, wsi); return 0; } case LWS_CALLBACK_WSI_DESTROY: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, destroying\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- destroyed\n", log_prefix, wsi); return 0; } default: if(wsi != NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] %d (%s)\n", log_prefix, wsi, reason, janus_websockets_reason_string(reason)); } else { JANUS_LOG(LOG_HUGE, "[%s] %d (%s)\n", log_prefix, reason, janus_websockets_reason_string(reason)); } break; } return 0; }
int FLwsWebSocket::CallbackWrapper(struct lws *Instance, enum lws_callback_reasons Reason, void *UserData, void *Data, size_t Length) { FLwsWebSocket* Self = reinterpret_cast<FLwsWebSocket*>(UserData); switch (Reason) { case LWS_CALLBACK_PROTOCOL_INIT: case LWS_CALLBACK_PROTOCOL_DESTROY: case LWS_CALLBACK_GET_THREAD_ID: case LWS_CALLBACK_LOCK_POLL: case LWS_CALLBACK_UNLOCK_POLL: case LWS_CALLBACK_ADD_POLL_FD: case LWS_CALLBACK_DEL_POLL_FD: case LWS_CALLBACK_CHANGE_MODE_POLL_FD: case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: case LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH: break; case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: { SSL_CTX* SslContext = reinterpret_cast<SSL_CTX*>(UserData); FSslModule::Get().GetCertificateManager().AddCertificatesToSslContext(SslContext); break; } case LWS_CALLBACK_CLIENT_ESTABLISHED: Self->bIsConnecting = false; Self->LwsConnection = Instance; if (!Self->SendQueue.IsEmpty()) { lws_callback_on_writable(Self->LwsConnection); } Self->OnConnected().Broadcast(); break; case LWS_CALLBACK_CLIENT_RECEIVE: { SIZE_T BytesLeft = lws_remaining_packet_payload(Instance); if(Self->OnMessage().IsBound()) { FUTF8ToTCHAR Convert((const ANSICHAR*)Data, Length); Self->ReceiveBuffer.Append(Convert.Get(), Convert.Length()); if (BytesLeft == 0) { Self->OnMessage().Broadcast(Self->ReceiveBuffer); Self->ReceiveBuffer.Empty(); } } Self->OnRawMessage().Broadcast(Data, Length, BytesLeft); break; } case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { Self->LwsConnection = nullptr; Self->FlushQueues(); if (Self->OnClosed().IsBound()) { uint16 CloseStatus = *((uint16*)Data); #if PLATFORM_LITTLE_ENDIAN // The status is the first two bytes of the message in network byte order CloseStatus = BYTESWAP_ORDER16(CloseStatus); #endif FUTF8ToTCHAR Convert((const ANSICHAR*)Data + sizeof(uint16), Length - sizeof(uint16)); Self->OnClosed().Broadcast(CloseStatus, Convert.Get(), true); return 1; // Close the connection without logging if the user handles Close events } break; } case LWS_CALLBACK_WSI_DESTROY: // Getting a WSI_DESTROY before a connection has been established and no errors reported usually means there was a timeout establishing a connection if (Self->bIsConnecting) { //Self->OnConnectionError().Broadcast(TEXT("Connection timed out")); //Self->bIsConnecting = false; } if (Self->LwsConnection) { Self->LwsConnection = nullptr; } break; case LWS_CALLBACK_CLOSED: { bool ClientInitiated = Self->LwsConnection == nullptr; Self->LwsConnection = nullptr; Self->OnClosed().Broadcast(LWS_CLOSE_STATUS_NORMAL, ClientInitiated?TEXT("Successfully closed connection to server"):TEXT("Connection closed by server"), ClientInitiated); Self->FlushQueues(); break; } case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: { Self->LwsConnection = nullptr; FUTF8ToTCHAR Convert((const ANSICHAR*)Data, Length); Self->OnConnectionError().Broadcast(Convert.Get()); Self->FlushQueues(); return -1; break; } case LWS_CALLBACK_RECEIVE_PONG: break; case LWS_CALLBACK_CLIENT_WRITEABLE: case LWS_CALLBACK_SERVER_WRITEABLE: { if (Self->CloseCode != 0) { Self->LwsConnection = nullptr; FTCHARToUTF8 Convert(*Self->CloseReason); // This only sets the reason for closing the connection: lws_close_reason(Instance, (enum lws_close_status)Self->CloseCode, (unsigned char *)Convert.Get(), (size_t)Convert.Length()); Self->CloseCode = 0; Self->CloseReason = FString(); Self->FlushQueues(); return -1; // Returning non-zero will close the current connection } else { Self->SendFromQueue(); } break; } default: break; } return 0; }
static int discord_ws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct im_connection *ic = NULL; discord_data *dd = NULL; if (wsi == NULL) { return 0; } struct lws_context *wsctx = lws_get_context(wsi); if (wsctx != NULL) { ic = lws_context_user(wsctx); dd = ic->proto_data; } switch(reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: dd->state = WS_CONNECTED; lws_callback_on_writable(wsi); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: imcb_error(ic, "Websocket connection error"); if (in != NULL) { imcb_error(ic, in); } b_event_remove(dd->keepalive_loop_id); dd->keepalive_loop_id = 0; dd->state = WS_CLOSING; break; case LWS_CALLBACK_CLIENT_WRITEABLE: if (dd->state == WS_CONNECTED) { GString *buf = g_string_new(""); g_string_printf(buf, "{\"d\":{\"v\":3,\"token\":\"%s\",\"properties\":{\"$referring_domain\":\"\",\"$browser\":\"bitlbee-discord\",\"$device\":\"bitlbee\",\"$referrer\":\"\",\"$os\":\"linux\"}},\"op\":2}", dd->token); discord_ws_send_payload(wsi, buf->str, buf->len); g_string_free(buf, TRUE); } else if (dd->state == WS_READY) { GString *buf = g_string_new(""); g_string_printf(buf, "{\"op\":1,\"d\":%tu}", time(NULL)); discord_ws_send_payload(dd->lws, buf->str, buf->len); g_string_free(buf, TRUE); } else { g_print("%s: Unhandled writable callback\n", __func__); } break; case LWS_CALLBACK_CLIENT_RECEIVE: { size_t rpload = lws_remaining_packet_payload(wsi); if (dd->ws_buf == NULL) { dd->ws_buf = g_string_new(""); } dd->ws_buf = g_string_append(dd->ws_buf, in); if (rpload == 0) { discord_parse_message(ic); g_string_free(dd->ws_buf, TRUE); dd->ws_buf = NULL; } break; } case LWS_CALLBACK_CLOSED: b_event_remove(dd->keepalive_loop_id); dd->keepalive_loop_id = 0; dd->state = WS_CLOSING; lws_cancel_service(dd->lwsctx); break; case LWS_CALLBACK_ADD_POLL_FD: { struct lws_pollargs *pargs = in; dd->main_loop_id = b_input_add(pargs->fd, B_EV_IO_READ, discord_ws_service_loop, ic); break; } case LWS_CALLBACK_DEL_POLL_FD: b_event_remove(dd->main_loop_id); break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: { struct lws_pollargs *pargs = in; int flags = 0; b_event_remove(dd->main_loop_id); if (pargs->events & POLLIN) { flags |= B_EV_IO_READ; } if (pargs->events & POLLOUT) { flags |= B_EV_IO_WRITE; } dd->main_loop_id = b_input_add(pargs->fd, flags, discord_ws_service_loop, ic); break; } case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED: case LWS_CALLBACK_GET_THREAD_ID: case LWS_CALLBACK_LOCK_POLL: case LWS_CALLBACK_UNLOCK_POLL: case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: case LWS_CALLBACK_PROTOCOL_INIT: case LWS_CALLBACK_PROTOCOL_DESTROY: case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: case LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH: case LWS_CALLBACK_WSI_CREATE: case LWS_CALLBACK_WSI_DESTROY: // Ignoring these, this block should be removed when defult is set to // stay silent. break; default: g_print("%s: unknown rsn=%d\n", __func__, reason); break; } return 0; }
LWS_VISIBLE LWS_EXTERN size_t libwebsockets_remaining_packet_payload(struct lws *wsi) { return lws_remaining_packet_payload(wsi); }
static int ps_websockets_callback ( struct lws * wsi, enum lws_callback_reasons reason, void * user, void * in, size_t len) { ps_websockets_client * wsclient = (ps_websockets_client *) user; switch (reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: { wsclient->wsi = wsi; wsclient->messages = g_async_queue_new (); wsclient->buffer = NULL; wsclient->buflen = 0; wsclient->bufpending = 0; wsclient->bufoffset = 0; wsclient->session_timeout = 0; wsclient->destroy = 0; ps_mutex_init(&wsclient->mutex); lws_callback_on_writable(wsi); PS_LOG (LOG_INFO,"LWS_CALLBACK_CLIENT_ESTABLISHED\n"); return 0; } case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: { PS_LOG (LOG_ERR,"LWS_CALLBACK_CLIENT_CONNECTION_ERROR\n"); return 0; } case LWS_CALLBACK_CLOSED: { PS_LOG (LOG_INFO,"LWS_CALLBACK_CLOSED: Websocket connection closed \n"); if (wsclient != NULL) { gateway->transport_gone (&ps_websockets_transport, wsclient); ps_mutex_lock (&wsclient->mutex); wsclient->destroy = 1; wsclient->wsi = NULL; /* Remove message queue */ if (wsclient->messages != NULL) { char * response = NULL; while ((response = g_async_queue_try_pop(wsclient->messages)) != NULL ) { g_free (response); } g_async_queue_unref (wsclient->messages); } g_free (wsclient->incoming); wsclient->incoming = NULL; g_free (wsclient->buffer); wsclient->buffer = NULL; wsclient->buflen = 0; wsclient->bufpending = 0; wsclient->bufoffset = 0; ps_mutex_unlock(&wsclient->mutex); } return 0; } case LWS_CALLBACK_CLIENT_RECEIVE: { if (wsclient == NULL || wsclient->wsi == NULL) { PS_LOG (LOG_ERR,"LWS_CALLBACK_CLIENT_RECEIVE: Invalid websocket client instance\n"); return -1; } const size_t remaining = lws_remaining_packet_payload (wsi); if (wsclient->incoming == NULL) { wsclient->incoming = g_malloc0 (len+1); memcpy(wsclient->incoming, in, len); wsclient->incoming[len] = '\0'; PS_LOG (LOG_VERB,"LWS_CALLBACK_CLIENT_RECEIVE: %s\n",wsclient->incoming); } else { size_t offset = strlen (wsclient->incoming); wsclient->incoming = g_realloc (wsclient->incoming, offset+len+1); wsclient->incoming[offset+len] = '\0'; PS_LOG (LOG_VERB,"LWS_CALLBACK_CLIENT_RECEIVE: %s\n",wsclient->incoming+offset); } if (remaining > 0 || !lws_is_final_fragment (wsi)) { PS_LOG (LOG_VERB,"LWS_CALLBACK_CLIENT_RECEIVE: Waiting for more fragments\n"); return 0; } json_error_t error; json_t * root = json_loads (wsclient->incoming, 0, &error); g_free (wsclient->incoming); wsclient->incoming = NULL; gateway->incoming_request(&ps_websockets_transport, wsclient, root, &error); return 0; } case LWS_CALLBACK_CLIENT_WRITEABLE : { if (wsclient == NULL || wsclient->wsi == NULL) { PS_LOG (LOG_ERR,"LWS_CALLBACK_CLIENT_WRITEABLE: Invalid websocket client instance\n"); return -1; } if (!wsclient->destroy && !g_atomic_int_get(&stopping)) { ps_mutex_lock (&wsclient->mutex); /* Check if a pending/partial write to complete first */ if (wsclient->buffer && wsclient->bufpending > 0 && wsclient->bufoffset > 0 && !wsclient->destroy && !g_atomic_int_get(&stopping)) { int sent = lws_write (wsi, wsclient->buffer + wsclient->bufoffset, wsclient->bufpending, LWS_WRITE_TEXT); if (sent > -1 && sent < wsclient->bufpending) { wsclient->bufpending -= sent; wsclient->bufoffset += sent; } else { wsclient->bufpending = 0; wsclient->bufoffset = 0; } lws_callback_on_writable (wsi); ps_mutex_unlock(&wsclient->mutex); return 0; } /* shoot all pending messages */ char * response = g_async_queue_try_pop (wsclient->messages); if (response && !wsclient->destroy && !g_atomic_int_get(&stopping)) { int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if (wsclient->buffer == NULL) { wsclient->buflen = buflen; wsclient->buffer = g_malloc0 (buflen); } else if (buflen > wsclient->buflen) { wsclient->buflen = buflen; wsclient->buffer = g_realloc (wsclient->buffer, buflen); } memcpy(wsclient->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); int sent = lws_write (wsi, wsclient->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); if (sent > -1 && sent < (int)strlen(response)) { wsclient->bufpending = strlen(response) - sent; wsclient->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; } g_free (response); lws_callback_on_writable (wsi); ps_mutex_unlock(&wsclient->mutex); return 0; } ps_mutex_unlock(&wsclient->mutex); } return 0; } default: break; } return 0; }