diff options
Diffstat (limited to 'protocols/Tox/toxcore/toxav/rtp.c')
-rw-r--r-- | protocols/Tox/toxcore/toxav/rtp.c | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/protocols/Tox/toxcore/toxav/rtp.c b/protocols/Tox/toxcore/toxav/rtp.c new file mode 100644 index 0000000000..de6c9c418c --- /dev/null +++ b/protocols/Tox/toxcore/toxav/rtp.c @@ -0,0 +1,600 @@ +/** rtp.c + * + * Copyright (C) 2013 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "../toxcore/logger.h" +#include "../toxcore/util.h" + +#include "rtp.h" +#include <stdlib.h> +void toxav_handle_packet(RTPSession *_session, RTPMessage *_msg); + +#define size_32 4 + +#define ADD_FLAG_VERSION(_h, _v) do { ( _h->flags ) &= 0x3F; ( _h->flags ) |= ( ( ( _v ) << 6 ) & 0xC0 ); } while(0) +#define ADD_FLAG_PADDING(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xDF; ( _h->flags ) |= ( ( ( _v ) << 5 ) & 0x20 ); } while(0) +#define ADD_FLAG_EXTENSION(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xEF;( _h->flags ) |= ( ( ( _v ) << 4 ) & 0x10 ); } while(0) +#define ADD_FLAG_CSRCC(_h, _v) do { ( _h->flags ) &= 0xF0; ( _h->flags ) |= ( ( _v ) & 0x0F ); } while(0) +#define ADD_SETTING_MARKER(_h, _v) do { if ( _v > 1 ) _v = 1; ( _h->marker_payloadt ) &= 0x7F; ( _h->marker_payloadt ) |= ( ( ( _v ) << 7 ) /*& 0x80 */ ); } while(0) +#define ADD_SETTING_PAYLOAD(_h, _v) do { if ( _v > 127 ) _v = 127; ( _h->marker_payloadt ) &= 0x80; ( _h->marker_payloadt ) |= ( ( _v ) /* & 0x7F */ ); } while(0) + +#define GET_FLAG_VERSION(_h) (( _h->flags & 0xd0 ) >> 6) +#define GET_FLAG_PADDING(_h) (( _h->flags & 0x20 ) >> 5) +#define GET_FLAG_EXTENSION(_h) (( _h->flags & 0x10 ) >> 4) +#define GET_FLAG_CSRCC(_h) ( _h->flags & 0x0f ) +#define GET_SETTING_MARKER(_h) (( _h->marker_payloadt ) >> 7) +#define GET_SETTING_PAYLOAD(_h) ((_h->marker_payloadt) & 0x7f) + +/** + * @brief Checks if message came in late. + * + * @param session Control session. + * @param msg The message. + * @return int + * @retval -1 The message came in order. + * @retval 0 The message came late. + */ +inline__ int check_late_message (RTPSession *session, RTPMessage *msg) +{ + /* + * Check Sequence number. If this new msg has lesser number then the session->rsequnum + * it shows that the message came in late. Also check timestamp to be 100% certain. + * + */ + return ( msg->header->sequnum < session->rsequnum && msg->header->timestamp < session->timestamp ) ? 0 : -1; +} + + +/** + * @brief Extracts header from payload. + * + * @param payload The payload. + * @param length The size of payload. + * @return RTPHeader* Extracted header. + * @retval NULL Error occurred while extracting header. + */ +RTPHeader *extract_header ( const uint8_t *payload, int length ) +{ + if ( !payload || !length ) { + LOGGER_WARNING("No payload to extract!"); + return NULL; + } + + RTPHeader *_retu = calloc(1, sizeof (RTPHeader)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + bytes_to_U16(&_retu->sequnum, payload); + + const uint8_t *_it = payload + 2; + + _retu->flags = *_it; + ++_it; + + /* This indicates if the first 2 bits are valid. + * Now it may happen that this is out of order but + * it cuts down chances of parsing some invalid value + */ + + if ( GET_FLAG_VERSION(_retu) != RTP_VERSION ) { + /* Deallocate */ + LOGGER_WARNING("Invalid version!"); + free(_retu); + return NULL; + } + + /* + * Added a check for the size of the header little sooner so + * I don't need to parse the other stuff if it's bad + */ + uint8_t _cc = GET_FLAG_CSRCC ( _retu ); + int _length = 12 /* Minimum header len */ + ( _cc * 4 ); + + if ( length < _length ) { + /* Deallocate */ + LOGGER_WARNING("Length invalid!"); + free(_retu); + return NULL; + } + + memset(_retu->csrc, 0, 16 * sizeof (uint32_t)); + + _retu->marker_payloadt = *_it; + ++_it; + _retu->length = _length; + + + bytes_to_U32(&_retu->timestamp, _it); + _it += 4; + bytes_to_U32(&_retu->ssrc, _it); + + uint8_t _x; + + for ( _x = 0; _x < _cc; _x++ ) { + _it += 4; + bytes_to_U32(&(_retu->csrc[_x]), _it); + } + + return _retu; +} + +/** + * @brief Extracts external header from payload. Must be called AFTER extract_header()! + * + * @param payload The ITERATED payload. + * @param length The size of payload. + * @return RTPExtHeader* Extracted extension header. + * @retval NULL Error occurred while extracting extension header. + */ +RTPExtHeader *extract_ext_header ( const uint8_t *payload, uint16_t length ) +{ + const uint8_t *_it = payload; + + RTPExtHeader *_retu = calloc(1, sizeof (RTPExtHeader)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + uint16_t _ext_length; + bytes_to_U16(&_ext_length, _it); + _it += 2; + + + if ( length < ( _ext_length * sizeof(uint32_t) ) ) { + LOGGER_WARNING("Length invalid!"); + free(_retu); + return NULL; + } + + _retu->length = _ext_length; + bytes_to_U16(&_retu->type, _it); + _it += 2; + + if ( !(_retu->table = calloc(_ext_length, sizeof (uint32_t))) ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + free(_retu); + return NULL; + } + + uint16_t _x; + + for ( _x = 0; _x < _ext_length; _x++ ) { + _it += 4; + bytes_to_U32(&(_retu->table[_x]), _it); + } + + return _retu; +} + +/** + * @brief Adds header to payload. Make sure _payload_ has enough space. + * + * @param header The header. + * @param payload The payload. + * @return uint8_t* Iterated position. + */ +uint8_t *add_header ( RTPHeader *header, uint8_t *payload ) +{ + uint8_t _cc = GET_FLAG_CSRCC ( header ); + + uint8_t *_it = payload; + + + /* Add sequence number first */ + U16_to_bytes(_it, header->sequnum); + _it += 2; + + *_it = header->flags; + ++_it; + *_it = header->marker_payloadt; + ++_it; + + + U32_to_bytes( _it, header->timestamp); + _it += 4; + U32_to_bytes( _it, header->ssrc); + + uint8_t _x; + + for ( _x = 0; _x < _cc; _x++ ) { + _it += 4; + U32_to_bytes( _it, header->csrc[_x]); + } + + return _it + 4; +} + +/** + * @brief Adds extension header to payload. Make sure _payload_ has enough space. + * + * @param header The header. + * @param payload The payload. + * @return uint8_t* Iterated position. + */ +uint8_t *add_ext_header ( RTPExtHeader *header, uint8_t *payload ) +{ + uint8_t *_it = payload; + + U16_to_bytes(_it, header->length); + _it += 2; + U16_to_bytes(_it, header->type); + _it -= 2; /* Return to 0 position */ + + if ( header->table ) { + uint16_t _x; + + for ( _x = 0; _x < header->length; _x++ ) { + _it += 4; + U32_to_bytes(_it, header->table[_x]); + } + } + + return _it + 4; +} + +/** + * @brief Builds header from control session values. + * + * @param session Control session. + * @return RTPHeader* Created header. + */ +RTPHeader *build_header ( RTPSession *session ) +{ + RTPHeader *_retu = calloc ( 1, sizeof (RTPHeader) ); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + ADD_FLAG_VERSION ( _retu, session->version ); + ADD_FLAG_PADDING ( _retu, session->padding ); + ADD_FLAG_EXTENSION ( _retu, session->extension ); + ADD_FLAG_CSRCC ( _retu, session->cc ); + ADD_SETTING_MARKER ( _retu, session->marker ); + ADD_SETTING_PAYLOAD ( _retu, session->payload_type ); + + _retu->sequnum = session->sequnum; + _retu->timestamp = current_time_monotonic(); /* milliseconds */ + _retu->ssrc = session->ssrc; + + int i; + + for ( i = 0; i < session->cc; i++ ) + _retu->csrc[i] = session->csrc[i]; + + _retu->length = 12 /* Minimum header len */ + ( session->cc * size_32 ); + + return _retu; +} + + +/** + * @brief Parses data into RTPMessage struct. Stores headers separately from the payload data + * and so the length variable is set accordingly. _sequnum_ argument is + * passed by the handle_packet() since it's parsed already. + * + * @param session Control session. + * @param sequnum Sequence number that's parsed from payload in handle_packet() + * @param data Payload data. + * @param length Payload size. + * @return RTPMessage* + * @retval NULL Error occurred. + */ +RTPMessage *msg_parse ( const uint8_t *data, int length ) +{ + RTPMessage *_retu = calloc(1, sizeof (RTPMessage)); + + _retu->header = extract_header ( data, length ); /* It allocates memory and all */ + + if ( !_retu->header ) { + LOGGER_WARNING("Header failed to extract!"); + free(_retu); + return NULL; + } + + uint16_t _from_pos = _retu->header->length; + _retu->length = length - _from_pos; + + + + if ( GET_FLAG_EXTENSION ( _retu->header ) ) { + _retu->ext_header = extract_ext_header ( data + _from_pos, length ); + + if ( _retu->ext_header ) { + _retu->length -= ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 ); + _from_pos += ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 ); + } else { /* Error */ + LOGGER_WARNING("Ext Header failed to extract!"); + rtp_free_msg(NULL, _retu); + return NULL; + } + } else { + _retu->ext_header = NULL; + } + + if ( length - _from_pos <= MAX_RTP_SIZE ) + memcpy ( _retu->data, data + _from_pos, length - _from_pos ); + else { + LOGGER_WARNING("Invalid length!"); + rtp_free_msg(NULL, _retu); + return NULL; + } + + _retu->next = NULL; + + return _retu; +} + +/** + * @brief Callback for networking core. + * + * @param object RTPSession object. + * @param ip_port Where the message comes from. + * @param data Message data. + * @param length Message length. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +int rtp_handle_packet ( void *object, const uint8_t *data, uint32_t length ) +{ + RTPSession *_session = object; + RTPMessage *_msg; + + if ( !_session || length < 13 ) { /* 12 is the minimum length for rtp + desc. byte */ + LOGGER_WARNING("No session or invalid length of received buffer!"); + return -1; + } + + _msg = msg_parse ( data + 1, length - 1 ); + + if ( !_msg ) { + LOGGER_WARNING("Could not parse message!"); + return -1; + } + + /* Check if message came in late */ + if ( check_late_message(_session, _msg) < 0 ) { /* Not late */ + _session->rsequnum = _msg->header->sequnum; + _session->timestamp = _msg->header->timestamp; + } + + toxav_handle_packet(_session, _msg); + + return 0; +} + + + +/** + * @brief Stores headers and payload data in one container ( data ) + * and the length is set accordingly. Returned message is used for sending _only_. + * + * @param session The control session. + * @param data Payload data to send ( This is what you pass ). + * @param length Size of the payload data. + * @return RTPMessage* Created message. + * @retval NULL Error occurred. + */ +RTPMessage *rtp_new_message ( RTPSession *session, const uint8_t *data, uint32_t length ) +{ + if ( !session ) { + LOGGER_WARNING("No session!"); + return NULL; + } + + uint8_t *_from_pos; + RTPMessage *_retu = calloc(1, sizeof (RTPMessage)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + /* Sets header values and copies the extension header in _retu */ + _retu->header = build_header ( session ); /* It allocates memory and all */ + _retu->ext_header = session->ext_header; + + + uint32_t _total_length = length + _retu->header->length + 1; + + _retu->data[0] = session->prefix; + + if ( _retu->ext_header ) { + _total_length += ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 ); + + _from_pos = add_header ( _retu->header, _retu->data + 1 ); + _from_pos = add_ext_header ( _retu->ext_header, _from_pos + 1 ); + } else { + _from_pos = add_header ( _retu->header, _retu->data + 1 ); + } + + /* + * Parses the extension header into the message + * Of course if any + */ + + /* Appends _data on to _retu->_data */ + memcpy ( _from_pos, data, length ); + + _retu->length = _total_length; + + _retu->next = NULL; + + return _retu; +} + + +/** + * @brief Sends data to _RTPSession::dest + * + * @param session The session. + * @param messenger Tox* object. + * @param data The payload. + * @param length Size of the payload. + * @return int + * @retval -1 On error. + * @retval 0 On success. + */ +int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ) +{ + RTPMessage *msg = rtp_new_message (session, data, length); + + if ( !msg ) { + LOGGER_WARNING("No session!"); + return -1; + } + + if ( -1 == send_custom_lossy_packet(messenger, session->dest, msg->data, msg->length) ) { + LOGGER_WARNING("Failed to send full packet! std error: %s", strerror(errno)); + rtp_free_msg ( session, msg ); + return -1; + } + + + /* Set sequ number */ + session->sequnum = session->sequnum >= MAX_SEQU_NUM ? 0 : session->sequnum + 1; + rtp_free_msg ( session, msg ); + + return 0; +} + + +/** + * @brief Speaks for it self. + * + * @param session The control session msg belongs to. You set it as NULL when freeing recved messages. + * Otherwise set it to session the message was created from. + * @param msg The message. + * @return void + */ +void rtp_free_msg ( RTPSession *session, RTPMessage *msg ) +{ + if ( !session ) { + if ( msg->ext_header ) { + free ( msg->ext_header->table ); + free ( msg->ext_header ); + } + } else { + if ( msg->ext_header && session->ext_header != msg->ext_header ) { + free ( msg->ext_header->table ); + free ( msg->ext_header ); + } + } + + free ( msg->header ); + free ( msg ); +} + +/** + * @brief Must be called before calling any other rtp function. It's used + * to initialize RTP control session. + * + * @param payload_type Type of payload used to send. You can use values in toxmsi.h::MSICallType + * @param messenger Tox* object. + * @param friend_num Friend id. + * @return RTPSession* Created control session. + * @retval NULL Error occurred. + */ +RTPSession *rtp_init_session ( int payload_type, Messenger *messenger, int friend_num ) +{ + RTPSession *_retu = calloc(1, sizeof(RTPSession)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + if ( -1 == custom_lossy_packet_registerhandler(messenger, friend_num, payload_type, rtp_handle_packet, _retu)) { + LOGGER_ERROR("Error setting custom register handler for rtp session"); + free(_retu); + return NULL; + } + + LOGGER_DEBUG("Registered packet handler: pt: %d; fid: %d", payload_type, friend_num); + + _retu->version = RTP_VERSION; /* It's always 2 */ + _retu->padding = 0; /* If some additional data is needed about the packet */ + _retu->extension = 0; /* If extension to header is needed */ + _retu->cc = 1; /* Amount of contributors */ + _retu->csrc = NULL; /* Container */ + _retu->ssrc = random_int(); + _retu->marker = 0; + _retu->payload_type = payload_type % 128; + + _retu->dest = friend_num; + + _retu->rsequnum = _retu->sequnum = 0; + + _retu->ext_header = NULL; /* When needed allocate */ + + + if ( !(_retu->csrc = calloc(1, sizeof (uint32_t))) ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + free(_retu); + return NULL; + } + + _retu->csrc[0] = _retu->ssrc; /* Set my ssrc to the list receive */ + + /* Also set payload type as prefix */ + _retu->prefix = payload_type; + + /* + * + */ + return _retu; +} + + +/** + * @brief Terminate the session. + * + * @param session The session. + * @param messenger The messenger who owns the session + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +void rtp_terminate_session ( RTPSession *session, Messenger *messenger ) +{ + if ( !session ) return; + + custom_lossy_packet_registerhandler(messenger, session->dest, session->prefix, NULL, NULL); + + free ( session->ext_header ); + free ( session->csrc ); + + LOGGER_DEBUG("Terminated RTP session: %p", session); + + /* And finally free session */ + free ( session ); + +} |