2 * Carla REST API Server
3 * Copyright (C) 2018 Filipe Coelho <falktx@falktx.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
19 * Even though Carla is GPL, restbed if AGPL.
20 * As such, the resulting binary will be AGPL.
21 * Take this into consideration before deploying it to any servers.
26 #include "carla-host.cpp"
27 #include "carla-utils.cpp"
29 #include "CarlaMutex.hpp"
30 #include "CarlaStringList.hpp"
32 // -------------------------------------------------------------------------------------------------------------------
36 #include <system_error>
37 #include <openssl/sha.h>
38 #include <openssl/hmac.h>
39 #include <openssl/evp.h>
40 #include <openssl/bio.h>
41 #include <openssl/buffer.h>
44 using namespace restbed
;
45 using namespace std::chrono
;
47 // std::vector<std::shared_ptr<Session>> gSessions;
49 CarlaStringList gSessionMessages
;
50 CarlaMutex gSessionMessagesMutex
;
52 std::map
< string
, shared_ptr
< WebSocket
> > sockets
= { };
54 // -------------------------------------------------------------------------------------------------------------------
56 void send_server_side_message(const char* const message
)
58 const CarlaMutexLocker
cml(gSessionMessagesMutex
);
60 gSessionMessages
.append(message
);
63 // -------------------------------------------------------------------------------------------------------------------
65 static void event_stream_handler(void)
67 static bool firstInit
= true;
72 carla_stdout("Carla REST-API Server started");
75 const bool running
= carla_is_engine_running();
80 CarlaStringList messages
;
83 const CarlaMutexLocker
cml(gSessionMessagesMutex
);
85 if (gSessionMessages
.count() > 0)
86 gSessionMessages
.moveTo(messages
);
89 for (auto message
: messages
)
91 for (auto entry
: sockets
)
93 auto socket
= entry
.second
;
95 if (socket
->is_open())
96 socket
->send(message
);
102 if (const uint count
= carla_get_current_plugin_count())
107 for (uint i
=0; i
<count
; ++i
)
109 peaks
= carla_get_peak_values(i
);
110 CARLA_SAFE_ASSERT_BREAK(peaks
!= nullptr);
112 std::snprintf(msgBuf
, 1023, "Peaks: %u %f %f %f %f", i
, peaks
[0], peaks
[1], peaks
[2], peaks
[3]);
115 for (auto entry
: sockets
)
117 auto socket
= entry
.second
;
119 if (socket
->is_open())
120 socket
->send(msgBuf
);
126 for (auto entry
: sockets
)
128 auto socket
= entry
.second
;
130 if (socket
->is_open())
131 socket
->send("Keep-Alive");
136 // -------------------------------------------------------------------------------------------------------------------
138 string
base64_encode( const unsigned char* input
, int length
)
143 b64
= BIO_new( BIO_f_base64( ) );
144 bmem
= BIO_new( BIO_s_mem( ) );
145 b64
= BIO_push( b64
, bmem
);
146 BIO_write( b64
, input
, length
);
147 ( void ) BIO_flush( b64
);
148 BIO_get_mem_ptr( b64
, &bptr
);
150 char* buff
= ( char* )malloc( bptr
->length
);
151 memcpy( buff
, bptr
->data
, bptr
->length
- 1 );
152 buff
[ bptr
->length
- 1 ] = 0;
159 multimap
< string
, string
> build_websocket_handshake_response_headers( const shared_ptr
< const Request
>& request
)
161 auto key
= request
->get_header( "Sec-WebSocket-Key" );
162 key
.append( "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" );
164 Byte hash
[ SHA_DIGEST_LENGTH
];
165 SHA1( reinterpret_cast< const unsigned char* >( key
.data( ) ), key
.length( ), hash
);
167 multimap
< string
, string
> headers
;
168 headers
.insert( make_pair( "Upgrade", "websocket" ) );
169 headers
.insert( make_pair( "Connection", "Upgrade" ) );
170 headers
.insert( make_pair( "Sec-WebSocket-Accept", base64_encode( hash
, SHA_DIGEST_LENGTH
) ) );
175 void close_handler( const shared_ptr
< WebSocket
> socket
)
177 carla_stdout("CLOSE %i", __LINE__
);
179 if ( socket
->is_open( ) )
181 auto response
= make_shared
< WebSocketMessage
>( WebSocketMessage::CONNECTION_CLOSE_FRAME
, Bytes( { 10, 00 } ) );
182 socket
->send( response
);
184 carla_stdout("CLOSE %i", __LINE__
);
186 const auto key
= socket
->get_key( );
187 sockets
.erase( key
);
189 fprintf( stderr
, "Closed connection to %s.\n", key
.data( ) );
192 void error_handler( const shared_ptr
< WebSocket
> socket
, const error_code error
)
194 const auto key
= socket
->get_key( );
195 fprintf( stderr
, "WebSocket Errored '%s' for %s.\n", error
.message( ).data( ), key
.data( ) );
198 void message_handler( const shared_ptr
< WebSocket
> source
, const shared_ptr
< WebSocketMessage
> message
)
200 const auto opcode
= message
->get_opcode( );
202 if ( opcode
== WebSocketMessage::PING_FRAME
)
204 auto response
= make_shared
< WebSocketMessage
>( WebSocketMessage::PONG_FRAME
, message
->get_data( ) );
205 source
->send( response
);
207 else if ( opcode
== WebSocketMessage::PONG_FRAME
)
211 //Every time the ping_handler is scheduled to run, it fires off a PING_FRAME to each
212 //WebSocket. The client, if behaving correctly, will respond with a PONG_FRAME.
214 //On each occasion the underlying TCP socket sees any packet data transfer, whether
215 //a PING, PONG, TEXT, or BINARY... frame. It will automatically reset the timeout counter
216 //leaving the connection active; see also Settings::set_connection_timeout.
219 else if ( opcode
== WebSocketMessage::CONNECTION_CLOSE_FRAME
)
223 else if ( opcode
== WebSocketMessage::BINARY_FRAME
)
225 //We don't support binary data.
226 auto response
= make_shared
< WebSocketMessage
>( WebSocketMessage::CONNECTION_CLOSE_FRAME
, Bytes( { 10, 03 } ) );
227 source
->send( response
);
229 else if ( opcode
== WebSocketMessage::TEXT_FRAME
)
231 auto response
= make_shared
< WebSocketMessage
>( *message
);
232 response
->set_mask( 0 );
234 for ( auto socket
: sockets
)
236 auto destination
= socket
.second
;
237 destination
->send( response
);
240 const auto key
= source
->get_key( );
241 const auto data
= String::format( "Received message '%.*s' from %s\n", message
->get_data( ).size( ), message
->get_data( ).data( ), key
.data( ) );
242 fprintf( stderr
, "%s", data
.data( ) );
246 void get_method_handler(const shared_ptr
<Session
> session
)
248 carla_stdout("HERE %i", __LINE__
);
249 const auto request
= session
->get_request();
250 const auto connection_header
= request
->get_header("connection", String::lowercase
);
251 carla_stdout("HERE %i", __LINE__
);
253 if ( connection_header
.find( "upgrade" ) not_eq string::npos
)
255 if ( request
->get_header( "upgrade", String::lowercase
) == "websocket" )
257 const auto headers
= build_websocket_handshake_response_headers( request
);
259 session
->upgrade( SWITCHING_PROTOCOLS
, headers
, [ ]( const shared_ptr
< WebSocket
> socket
)
261 if ( socket
->is_open( ) )
263 socket
->set_close_handler( close_handler
);
264 socket
->set_error_handler( error_handler
);
265 socket
->set_message_handler( message_handler
);
267 socket
->send("Welcome to Corvusoft Chat!");
269 auto key
= socket
->get_key( );
270 sockets
[key
] = socket
;
274 fprintf( stderr
, "WebSocket Negotiation Failed: Client closed connection.\n" );
282 session
->close( BAD_REQUEST
);
285 void ping_handler( void )
287 for ( auto entry
: sockets
)
289 auto key
= entry
.first
;
290 auto socket
= entry
.second
;
292 if ( socket
->is_open( ) )
294 socket
->send( WebSocketMessage::PING_FRAME
);
303 // -------------------------------------------------------------------------------------------------------------------
305 static void make_resource(Service
& service
,
306 const char* const path
,
307 const std::function
<void (const std::shared_ptr
<Session
>)>& callback
)
309 std::shared_ptr
<Resource
> resource
= std::make_shared
<Resource
>();
310 resource
->set_path(path
);
311 resource
->set_method_handler("GET", callback
);
312 service
.publish(resource
);
315 // -------------------------------------------------------------------------------------------------------------------
317 int main(int, const char**)
323 std::shared_ptr
<Resource
> resource
= std::make_shared
<Resource
>();
324 resource
->set_path("/ws");
325 resource
->set_method_handler("GET", get_method_handler
);
326 service
.publish(resource
);
330 make_resource(service
, "/get_engine_driver_count", handle_carla_get_engine_driver_count
);
331 make_resource(service
, "/get_engine_driver_name", handle_carla_get_engine_driver_name
);
332 make_resource(service
, "/get_engine_driver_device_names", handle_carla_get_engine_driver_device_names
);
333 make_resource(service
, "/get_engine_driver_device_info", handle_carla_get_engine_driver_device_info
);
335 make_resource(service
, "/engine_init", handle_carla_engine_init
);
336 make_resource(service
, "/engine_close", handle_carla_engine_close
);
337 make_resource(service
, "/is_engine_running", handle_carla_is_engine_running
);
338 make_resource(service
, "/set_engine_about_to_close", handle_carla_set_engine_about_to_close
);
340 make_resource(service
, "/set_engine_option", handle_carla_set_engine_option
);
341 make_resource(service
, "/load_file", handle_carla_load_file
);
342 make_resource(service
, "/load_project", handle_carla_load_project
);
343 make_resource(service
, "/save_project", handle_carla_save_project
);
345 make_resource(service
, "/patchbay_connect", handle_carla_patchbay_connect
);
346 make_resource(service
, "/patchbay_disconnect", handle_carla_patchbay_disconnect
);
347 make_resource(service
, "/patchbay_refresh", handle_carla_patchbay_refresh
);
349 make_resource(service
, "/transport_play", handle_carla_transport_play
);
350 make_resource(service
, "/transport_pause", handle_carla_transport_pause
);
351 make_resource(service
, "/transport_bpm", handle_carla_transport_bpm
);
352 make_resource(service
, "/transport_relocate", handle_carla_transport_relocate
);
353 make_resource(service
, "/get_current_transport_frame", handle_carla_get_current_transport_frame
);
354 make_resource(service
, "/get_transport_info", handle_carla_get_transport_info
);
356 make_resource(service
, "/get_current_plugin_count", handle_carla_get_current_plugin_count
);
357 make_resource(service
, "/get_max_plugin_number", handle_carla_get_max_plugin_number
);
358 make_resource(service
, "/add_plugin", handle_carla_add_plugin
);
359 make_resource(service
, "/remove_plugin", handle_carla_remove_plugin
);
360 make_resource(service
, "/remove_all_plugins", handle_carla_remove_all_plugins
);
362 make_resource(service
, "/rename_plugin", handle_carla_rename_plugin
);
363 make_resource(service
, "/clone_plugin", handle_carla_clone_plugin
);
364 make_resource(service
, "/replace_plugin", handle_carla_replace_plugin
);
365 make_resource(service
, "/switch_plugins", handle_carla_switch_plugins
);
367 make_resource(service
, "/load_plugin_state", handle_carla_load_plugin_state
);
368 make_resource(service
, "/save_plugin_state", handle_carla_save_plugin_state
);
369 make_resource(service
, "/export_plugin_lv2", handle_carla_export_plugin_lv2
);
371 make_resource(service
, "/get_plugin_info", handle_carla_get_plugin_info
);
372 make_resource(service
, "/get_audio_port_count_info", handle_carla_get_audio_port_count_info
);
373 make_resource(service
, "/get_midi_port_count_info", handle_carla_get_midi_port_count_info
);
374 make_resource(service
, "/get_parameter_count_info", handle_carla_get_parameter_count_info
);
375 make_resource(service
, "/get_parameter_info", handle_carla_get_parameter_info
);
376 make_resource(service
, "/get_parameter_scalepoint_info", handle_carla_get_parameter_scalepoint_info
);
378 make_resource(service
, "/get_parameter_data", handle_carla_get_parameter_data
);
379 make_resource(service
, "/get_parameter_ranges", handle_carla_get_parameter_ranges
);
380 make_resource(service
, "/get_midi_program_data", handle_carla_get_midi_program_data
);
381 make_resource(service
, "/get_custom_data", handle_carla_get_custom_data
);
382 make_resource(service
, "/get_custom_data_value", handle_carla_get_custom_data_value
);
383 make_resource(service
, "/get_chunk_data", handle_carla_get_chunk_data
);
385 make_resource(service
, "/get_parameter_count", handle_carla_get_parameter_count
);
386 make_resource(service
, "/get_program_count", handle_carla_get_program_count
);
387 make_resource(service
, "/get_midi_program_count", handle_carla_get_midi_program_count
);
388 make_resource(service
, "/get_custom_data_count", handle_carla_get_custom_data_count
);
390 make_resource(service
, "/get_parameter_text", handle_carla_get_parameter_text
);
391 make_resource(service
, "/get_program_name", handle_carla_get_program_name
);
392 make_resource(service
, "/get_midi_program_name", handle_carla_get_midi_program_name
);
393 make_resource(service
, "/get_real_plugin_name", handle_carla_get_real_plugin_name
);
395 make_resource(service
, "/get_current_program_index", handle_carla_get_current_program_index
);
396 make_resource(service
, "/get_current_midi_program_index", handle_carla_get_current_midi_program_index
);
398 make_resource(service
, "/get_default_parameter_value", handle_carla_get_default_parameter_value
);
399 make_resource(service
, "/get_current_parameter_value", handle_carla_get_current_parameter_value
);
400 make_resource(service
, "/get_internal_parameter_value", handle_carla_get_internal_parameter_value
);
401 make_resource(service
, "/get_input_peak_value", handle_carla_get_input_peak_value
);
402 make_resource(service
, "/get_output_peak_value", handle_carla_get_output_peak_value
);
404 make_resource(service
, "/set_active", handle_carla_set_active
);
405 make_resource(service
, "/set_drywet", handle_carla_set_drywet
);
406 make_resource(service
, "/set_volume", handle_carla_set_volume
);
407 make_resource(service
, "/set_balance_left", handle_carla_set_balance_left
);
408 make_resource(service
, "/set_balance_right", handle_carla_set_balance_right
);
409 make_resource(service
, "/set_panning", handle_carla_set_panning
);
410 make_resource(service
, "/set_ctrl_channel", handle_carla_set_ctrl_channel
);
411 make_resource(service
, "/set_option", handle_carla_set_option
);
413 make_resource(service
, "/set_parameter_value", handle_carla_set_parameter_value
);
414 make_resource(service
, "/set_parameter_midi_channel", handle_carla_set_parameter_midi_channel
);
415 make_resource(service
, "/set_parameter_midi_cc", handle_carla_set_parameter_midi_cc
);
416 make_resource(service
, "/set_program", handle_carla_set_program
);
417 make_resource(service
, "/set_midi_program", handle_carla_set_midi_program
);
418 make_resource(service
, "/set_custom_data", handle_carla_set_custom_data
);
419 make_resource(service
, "/set_chunk_data", handle_carla_set_chunk_data
);
421 make_resource(service
, "/prepare_for_save", handle_carla_prepare_for_save
);
422 make_resource(service
, "/reset_parameters", handle_carla_reset_parameters
);
423 make_resource(service
, "/randomize_parameters", handle_carla_randomize_parameters
);
424 make_resource(service
, "/send_midi_note", handle_carla_send_midi_note
);
426 make_resource(service
, "/get_buffer_size", handle_carla_get_buffer_size
);
427 make_resource(service
, "/get_sample_rate", handle_carla_get_sample_rate
);
428 make_resource(service
, "/get_last_error", handle_carla_get_last_error
);
429 make_resource(service
, "/get_host_osc_url_tcp", handle_carla_get_host_osc_url_tcp
);
430 make_resource(service
, "/get_host_osc_url_udp", handle_carla_get_host_osc_url_udp
);
433 make_resource(service
, "/get_complete_license_text", handle_carla_get_complete_license_text
);
434 make_resource(service
, "/get_supported_file_extensions", handle_carla_get_supported_file_extensions
);
435 make_resource(service
, "/get_supported_features", handle_carla_get_supported_features
);
436 make_resource(service
, "/get_cached_plugin_count", handle_carla_get_cached_plugin_count
);
437 make_resource(service
, "/get_cached_plugin_info", handle_carla_get_cached_plugin_info
);
440 service
.schedule(event_stream_handler
, std::chrono::milliseconds(33));
441 service
.schedule(ping_handler
, milliseconds(5000));
443 std::shared_ptr
<Settings
> settings
= std::make_shared
<Settings
>();
444 settings
->set_port(2228);
445 settings
->set_default_header("Connection", "close");
447 service
.start(settings
);
451 // -------------------------------------------------------------------------------------------------------------------