toxcore/toxmsi/toxmsi.c
2013-10-13 16:16:47 +02:00

836 lines
23 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#define _BSD_SOURCE
#include "toxmsi.h"
#include "toxmsi_event.h"
#include "toxmsi_message.h"
#include "../toxrtp/toxrtp_helper.h"
#include "../toxcore/network.h"
#include <assert.h>
#include <unistd.h>
#include <string.h>
#define same(x, y) strcmp((const char*) x, (const char*) y) == 0
typedef enum {
error_deadcall = 1, /* has call id but it's from old call */
error_id_mismatch, /* non-existing call */
error_no_callid, /* not having call id */
error_no_call, /* no call in session */
error_busy
} msi_error_t; /* Error codes */
static inline const uint8_t *stringify_error(msi_error_t _error_code)
{
static const uint8_t* strings[] =
{
(uint8_t*)"",
(uint8_t*)"Using dead call",
(uint8_t*)"Call id not set to any call",
(uint8_t*)"Call id not available",
(uint8_t*)"No active call in session",
(uint8_t*)"Callee busy"
};
return strings[_error_code];
}
static inline const uint8_t *stringify_error_code(msi_error_t _error_code)
{
static const uint8_t* strings[] =
{
(uint8_t*)"",
(uint8_t*)"1",
(uint8_t*)"2",
(uint8_t*)"3",
(uint8_t*)"4",
(uint8_t*)"5"
};
return strings[_error_code];
}
/* ******************* */
/* --------- GLOBAL FUNCTIONS USED BY THIS FILE --------- */
/* CALLBACKS */
/*int (*msi_send_message_callback) ( int, uint8_t*, uint32_t ) = NULL;*/
int ( *msi_send_message_callback ) ( void* _core_handler, tox_IP_Port, uint8_t*, uint32_t ) = NULL;
int ( *msi_recv_message_callback ) ( tox_IP_Port*, uint8_t*, uint32_t* ) = NULL;
MCBTYPE ( *msi_recv_invite_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_start_call_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_reject_call_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_cancel_call_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_end_call_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_ringing_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_starting_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_ending_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_error_callback ) ( MCBARGS ) = NULL;
MCBTYPE ( *msi_timeout_callback ) ( MCBARGS ) = NULL;
/* End of CALLBACKS */
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/* REGISTER CALLBACKS */
/*void msi_register_callback_send(int (*callback) ( int, uint8_t*, uint32_t ) )*/
void msi_register_callback_send ( int ( *callback ) ( void* _core_handler, tox_IP_Port, uint8_t*, uint32_t ) )
{
msi_send_message_callback = callback;
}
void msi_register_callback_recv ( int ( *callback ) ( tox_IP_Port*, uint8_t*, uint32_t* ) )
{
msi_recv_message_callback = callback;
}
/* Function to be called when received invite.
* This callback is all about what you do with it.
* Everything else is done internally.
*/
void msi_register_callback_recv_invite ( MCALLBACK )
{
msi_recv_invite_callback = callback;
}
/* Function to be called when the call is started
* This callback is all about what you do with it.
* Everything else is done internally.
*/
void msi_register_callback_call_started ( MCALLBACK )
{
msi_start_call_callback = callback;
}
/* Function to be called when call is rejected
* This callback is all about what you do with it.
* Everything else is done internally.
*/
void msi_register_callback_call_rejected ( MCALLBACK )
{
msi_reject_call_callback = callback;
}
/* Function to be called when call is canceled
* This callback is all about what you do with it.
* Everything else is done internally.
*/
void msi_register_callback_call_canceled ( MCALLBACK )
{
msi_cancel_call_callback = callback;
}
void msi_register_callback_call_ended ( MCALLBACK )
{
msi_end_call_callback = callback;
}
/* Functions to be called when gotten x response */
void msi_register_callback_recv_ringing ( MCALLBACK )
{
msi_ringing_callback = callback;
}
void msi_register_callback_recv_starting ( MCALLBACK )
{
msi_starting_callback = callback;
}
void msi_register_callback_recv_ending ( MCALLBACK )
{
msi_ending_callback = callback;
}
void msi_register_callback_recv_error ( MCALLBACK )
{
msi_error_callback = callback;
}
/* Timeout */
void msi_register_callback_requ_timeout ( MCALLBACK )
{
msi_timeout_callback = callback;
}
/* END REGISTERING */
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/* Function for receiving and parsing a message that will be used internally */
msi_msg_t* receive_message ( msi_session_t* _session )
{
assert(_session);
msi_msg_t* _retu = _session->_oldest_msg;
pthread_mutex_lock ( &_session->_mutex );
if ( _retu )
_session->_oldest_msg = _retu->_next;
if ( !_session->_oldest_msg )
_session->_last_msg = NULL;
pthread_mutex_unlock ( &_session->_mutex );
return _retu;
}
void msi_store_msg ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
assert(_msg);
pthread_mutex_lock ( &_session->_mutex );
if ( _session->_last_msg ) {
_session->_last_msg->_next = _msg;
_session->_last_msg = _msg;
} else {
_session->_last_msg = _session->_oldest_msg = _msg;
}
pthread_mutex_unlock ( &_session->_mutex );
}
int msi_send_msg ( msi_session_t* _session, msi_msg_t* _msg )
{
int _status;
if ( !_session->_call ) /* Which should never happen */
return FAILURE;
msi_msg_set_call_id ( _msg, _session->_call->_id );
uint8_t _msg_string_final [MSI_MAXMSG_SIZE];
t_memset ( _msg_string_final, '\0', MSI_MAXMSG_SIZE );
_msg_string_final[0] = 69;
uint8_t* _msg_string = msi_msg_to_string ( _msg );
size_t _lenght = t_memlen ( _msg_string );
memcpy ( _msg_string_final + 1, _msg_string, _lenght );
_lenght += 1;
_status = ( *msi_send_message_callback ) ( _session->_core_handler, _session->_friend_id, _msg_string_final, _lenght );
free ( _msg_string );
return _status;
}
/* Random stuff */
void flush_peer_type ( msi_session_t* _session, msi_msg_t* _msg, int _peer_id )
{
if ( _msg->_call_type ) {
if ( strcmp ( ( const char* ) _msg->_call_type->_header_value, CT_AUDIO_HEADER_VALUE ) == 0 ) {
_session->_call->_type_peer[_peer_id] = type_audio;
} else if ( strcmp ( ( const char* ) _msg->_call_type->_header_value, CT_VIDEO_HEADER_VALUE ) == 0 ) {
_session->_call->_type_peer[_peer_id] = type_video;
} else {} /* Error */
} else {} /* Error */
}
int has_call_error ( msi_session_t* _session, msi_msg_t* _msg )
{
msi_msg_t* _msg_error = msi_msg_new ( TYPE_RESPONSE, stringify_response ( _error ) );
if ( !_msg->_call_id ) {
msi_msg_set_reason(_msg_error, stringify_error_code(error_no_callid) );
} else if ( !_session->_call ) {
msi_msg_set_reason(_msg_error, stringify_error_code(error_no_call) );
} else if ( strcmp((const char*)_session->_call->_id, (const char*)_msg->_call_id->_header_value ) != 0 ) {
msi_msg_set_reason(_msg_error, stringify_error_code(error_id_mismatch) );
}
if ( _msg_error->_reason ) {
msi_send_msg ( _session, _msg_error );
msi_free_msg ( _msg_error );
return SUCCESS;
}
msi_free_msg ( _msg_error );
return FAILURE;
}
/* --------- END OF GLOBAL FUNCTIONS USED BY THIS FILE --------- */
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
msi_session_t* msi_init_session ( void* _core_handler, const uint8_t* _user_agent )
{
assert(_core_handler);
assert(_user_agent);
msi_session_t* _session = calloc ( sizeof ( msi_session_t ), 1 );
assert(_session);
_session->_oldest_msg = _session->_last_msg = NULL;
_session->_core_handler = _core_handler;
_session->_user_agent = t_strallcpy ( _user_agent );
_session->_agent_handler = NULL;
_session->_key = 0;
_session->_call = NULL;
_session->_frequ = 10000; /* default value? */
_session->_call_timeout = 30000; /* default value? */
/* Use the same frequency */
_session->_event_handler = init_event_poll ( _session->_frequ );
pthread_mutex_init ( &_session->_mutex, NULL );
return _session;
}
int msi_terminate_session ( msi_session_t* _session )
{
assert(_session);
int _status = 0;
terminate_event_poll ( _session->_event_handler );
free ( _session );
/* TODO: terminate the rest of the session */
pthread_mutex_destroy ( &_session->_mutex );
return _status;
}
msi_call_t* msi_init_call ( msi_session_t* _session, int _peers, uint32_t _timeoutms )
{
assert(_session);
assert(_peers);
msi_call_t* _call = calloc ( sizeof ( msi_call_t ), 1 );
_call->_type_peer = calloc ( sizeof ( call_type ), _peers );
assert(_call);
assert(_call->_type_peer);
_call->_participants = _peers;
_call->_key = _session->_key;
_call->_timeoutst = _timeoutms;
_call->_outgoing_timer_id = 0;
return _call;
}
int msi_terminate_call ( msi_session_t* _session )
{
assert(_session);
if ( _session->_call->_type_peer )
free ( _session->_call->_type_peer );
cancel_timer_event(_session->_event_handler, _session->_call->_outgoing_timer_id);
free ( _session->_call );
_session->_call = NULL;
return SUCCESS;
}
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/* STATE HANDLERS */
/* REQUESTS */
int msi_handle_recv_invite ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( _session->_call ) {
msi_msg_t* _msg_error = msi_msg_new ( TYPE_RESPONSE, stringify_response ( _error ) );
msi_msg_set_reason(_msg_error, stringify_error_code(error_busy));
msi_send_msg(_session, _msg_error);
msi_free_msg(_msg_error);
return 0;
}
if ( !_msg->_call_id ) {
msi_msg_t* _msg_error = msi_msg_new ( TYPE_RESPONSE, stringify_response ( _error ) );
msi_msg_set_reason(_msg_error, stringify_error_code(error_no_callid));
msi_send_msg(_session, _msg_error);
msi_free_msg(_msg_error);
return 0;
}
_session->_call = msi_init_call ( _session, 1, _session->_call_timeout );
t_memcpy(_session->_call->_id, _msg->_call_id->_header_value, _CALL_ID_LEN);
_session->_call->_state = call_starting;
flush_peer_type ( _session, _msg, 0 );
msi_msg_t* _msg_ringing = msi_msg_new ( TYPE_RESPONSE, stringify_response ( _ringing ) );
msi_send_msg ( _session, _msg_ringing );
msi_free_msg ( _msg_ringing );
throw_event ( _session->_event_handler, msi_recv_invite_callback, _session );
return 1;
}
int msi_handle_recv_start ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( has_call_error(_session, _msg) == 0 )
return 0;
_session->_call->_state = call_active;
flush_peer_type ( _session, _msg, 0 );
throw_event ( _session->_event_handler, msi_start_call_callback, _session );
return 1;
}
int msi_handle_recv_reject ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( has_call_error(_session, _msg) == 0 )
return 0;
msi_msg_t* _msg_end = msi_msg_new ( TYPE_REQUEST, stringify_request ( _end ) );
msi_send_msg ( _session, _msg_end );
msi_free_msg ( _msg_end );
throw_event ( _session->_event_handler, msi_reject_call_callback, _session );
return 1;
}
int msi_handle_recv_cancel ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( has_call_error(_session, _msg) == 0 )
return 0;
msi_msg_t* _msg_ending = msi_msg_new ( TYPE_RESPONSE, stringify_response ( _ending ) );
msi_send_msg ( _session, _msg_ending );
msi_free_msg ( _msg_ending );
msi_terminate_call ( _session );
throw_event ( _session->_event_handler, msi_cancel_call_callback, _session );
return 1;
}
int msi_handle_recv_end ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( has_call_error(_session, _msg) == 0 )
return 0;
msi_msg_t* _msg_ending = msi_msg_new ( TYPE_RESPONSE, stringify_response ( _ending ) );
msi_send_msg ( _session, _msg_ending );
msi_free_msg ( _msg_ending );
msi_terminate_call ( _session );
throw_event ( _session->_event_handler, msi_end_call_callback, _session );
return 1;
}
/*--------*/
/* RESPONSES */
int msi_handle_recv_ringing ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( has_call_error(_session, _msg) == 0 )
return 0;
throw_event ( _session->_event_handler, msi_ringing_callback, _session );
return 1;
}
int msi_handle_recv_starting ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( has_call_error(_session, _msg) == 0 )
return 0;
_session->_call->_state = call_active;
msi_msg_t* _msg_start = msi_msg_new ( TYPE_REQUEST, stringify_request ( _start ) );
msi_send_msg ( _session, _msg_start );
msi_free_msg ( _msg_start );
flush_peer_type ( _session, _msg, 0 );
throw_event ( _session->_event_handler, msi_starting_callback, _session );
cancel_timer_event(_session->_event_handler, _session->_call->_outgoing_timer_id);
_session->_call->_outgoing_timer_id = 0;
return 1;
}
int msi_handle_recv_ending ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
if ( has_call_error(_session, _msg) == 0 )
return 0;
msi_terminate_call ( _session );
throw_event ( _session->_event_handler, msi_ending_callback, _session );
return 1;
}
int msi_handle_recv_error ( msi_session_t* _session, msi_msg_t* _msg )
{
assert(_session);
assert(_session->_call);
/* Handle error accordingly */
if ( _msg->_reason ) {
_session->_last_error_id = atoi((const char*)_msg->_reason->_header_value);
_session->_last_error_str = stringify_error(_session->_last_error_id);
}
msi_terminate_call(_session);
throw_event ( _session->_event_handler, msi_error_callback, _session );
return 1;
}
/* ------------------ */
MCBTYPE msi_handle_timeout (void* _arg)
{
msi_session_t* _session = _arg;
msi_terminate_call(_session);
(*msi_timeout_callback) (_arg);
(*msi_ending_callback) (_arg);
}
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
int msi_invite ( msi_session_t* _session, call_type _call_type, uint32_t _timeoutms )
{
assert(_session);
if ( !msi_send_message_callback )
return 0;
msi_msg_t* _msg_invite = msi_msg_new ( TYPE_REQUEST, stringify_request ( _invite ) );
_session->_call = msi_init_call ( _session, 1, _timeoutms ); /* Just one for now */
msi_genterate_call_id(_session->_call->_id, _CALL_ID_LEN);
_session->_call->_type_local = _call_type;
/* Do whatever with message */
if ( _call_type == type_audio ) {
msi_msg_set_call_type ( _msg_invite, ( const uint8_t* ) CT_AUDIO_HEADER_VALUE );
} else {
msi_msg_set_call_type ( _msg_invite, ( const uint8_t* ) CT_VIDEO_HEADER_VALUE );
}
msi_send_msg ( _session, _msg_invite );
msi_free_msg ( _msg_invite );
_session->_call->_state = call_inviting;
_session->_call->_outgoing_timer_id = throw_timer_event(_session->_event_handler, msi_handle_timeout, _session, _timeoutms );
return 1;
}
int msi_hangup ( msi_session_t* _session )
{
assert(_session);
if ( !_session->_call || ( !msi_send_message_callback && _session->_call->_state != call_active ) )
return 0;
msi_msg_t* _msg_ending = msi_msg_new ( TYPE_REQUEST, stringify_request ( _end ) );
msi_send_msg ( _session, _msg_ending );
msi_free_msg ( _msg_ending );
return 1;
}
int msi_answer ( msi_session_t* _session, call_type _call_type )
{
assert(_session);
if ( !msi_send_message_callback || !_session->_call )
return 0;
msi_msg_t* _msg_starting = msi_msg_new ( TYPE_RESPONSE, stringify_response ( _starting ) );
_session->_call->_type_local = _call_type;
if ( _call_type == type_audio ) {
msi_msg_set_call_type ( _msg_starting, ( const uint8_t* ) CT_AUDIO_HEADER_VALUE );
} else {
msi_msg_set_call_type ( _msg_starting, ( const uint8_t* ) CT_VIDEO_HEADER_VALUE );
}
msi_send_msg ( _session, _msg_starting );
msi_free_msg ( _msg_starting );
_session->_call->_state = call_active;
return 1;
}
int msi_cancel ( msi_session_t* _session )
{
assert(_session);
if ( !_session->_call || !msi_send_message_callback )
return 0;
msi_msg_t* _msg_cancel = msi_msg_new ( TYPE_REQUEST, stringify_request ( _cancel ) );
msi_send_msg ( _session, _msg_cancel );
msi_free_msg ( _msg_cancel );
return 1;
}
int msi_reject ( msi_session_t* _session )
{
assert(_session);
if ( !_session->_call || !msi_send_message_callback )
return 0;
msi_msg_t* _msg_reject = msi_msg_new ( TYPE_REQUEST, stringify_request ( _reject ) );
msi_send_msg ( _session, _msg_reject );
msi_free_msg ( _msg_reject );
return 1;
}
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/* OUR MAIN POOL FUNCTION */
/*
* Forks it self to other thread and then handles the session initiation.
*
* BASIC call flow:
*
* ALICE BOB
* | invite --> |
* | |
* | <-- ringing |
* | |
* | <-- starting |
* | |
* | start --> |
* | |
* | <-- MEDIA TRANS --> |
* | |
* | end --> |
* | |
* | <-- ending |
*
* Alice calls Bob by sending invite packet.
* Bob recvs the packet and sends an ringing packet;
* which notifies Alice that her invite is acknowledged.
* Ringing screen shown on both sides.
* Bob accepts the invite for a call by sending starting packet.
* Alice recvs the starting packet and sends the started packet to
* inform Bob that she recved the starting packet.
* Now the media transmission is established ( i.e. RTP transmission ).
* Alice hangs up and sends end packet.
* Bob recves the end packet and sends ending packet
* as the acknowledgement that the call is ending.
*
*
*/
/*
* Needs a bit more work on the protocol
*/
void* msi_poll_stack ( void* _session_p )
{
msi_session_t* _session = ( msi_session_t* ) _session_p;
msi_msg_t* _msg = NULL;
uint32_t* _frequ = &_session->_frequ;
while ( _session ) {
/* At this point it's already parsed */
_msg = receive_message ( _session );
if ( _msg ) {
if ( _msg->_request ) { /* Handle request */
const uint8_t* _request_value = _msg->_request->_header_value;
if ( same ( _request_value, stringify_request ( _invite ) ) ) {
msi_handle_recv_invite ( _session, _msg );
} else if ( same ( _request_value, stringify_request ( _start ) ) ) {
msi_handle_recv_start ( _session, _msg );
} else if ( same ( _request_value, stringify_request ( _cancel ) ) ) {
msi_handle_recv_cancel ( _session, _msg );
} else if ( same ( _request_value, stringify_request ( _reject ) ) ) {
msi_handle_recv_reject ( _session, _msg );
} else if ( same ( _request_value, stringify_request ( _end ) ) ) {
msi_handle_recv_end ( _session, _msg );
}
} else if ( _msg->_response ) { /* Handle response */
const uint8_t* _response_value = _msg->_response->_header_value;
if ( same ( _response_value, stringify_response ( _ringing ) ) ) {
msi_handle_recv_ringing ( _session, _msg );
} else if ( same ( _response_value, stringify_response ( _starting ) ) ) {
msi_handle_recv_starting ( _session, _msg );
} else if ( same ( _response_value, stringify_response ( _ending ) ) ) {
msi_handle_recv_ending ( _session, _msg );
} else if ( same ( _response_value, stringify_response ( _error ) ) ) {
msi_handle_recv_error ( _session, _msg );
}
}
msi_free_msg ( _msg );
}
usleep ( *_frequ );
}
return NULL;
}
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/*------------------------*/
/* Easy way to start the poll */
pthread_t msi_start_main_loop ( msi_session_t* _session, uint32_t _frequms )
{
assert(_session);
int _status;
pthread_t _thread_id;
_session->_frequ = _frequms * 1000;
_status = pthread_create ( &_thread_id, NULL, msi_poll_stack, _session );
if ( _status < 0 ) {
printf ( "Error while starting main loop: %d, %s\n", errno, strerror ( errno ) );
return _status;
}
_status = pthread_detach ( _thread_id );
if ( _status < 0 ) {
printf ( "Error while starting main loop: %d, %s\n", errno, strerror ( errno ) );
}
return _thread_id;
}