/** toxrtp.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 . * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "../toxcore/logger.h" #include "../toxcore/util.h" #include "rtp.h" #include #include #define PAYLOAD_ID_VALUE_OPUS 1 #define PAYLOAD_ID_VALUE_VP8 2 #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 Increases nonce value by 'target' * * @param nonce The nonce * @param target The target * @return void */ inline__ void increase_nonce(uint8_t *nonce, uint16_t target) { uint16_t _nonce_counter; uint8_t _reverse_bytes[2]; _reverse_bytes[0] = nonce[crypto_box_NONCEBYTES - 1]; _reverse_bytes[1] = nonce[crypto_box_NONCEBYTES - 2]; bytes_to_U16(&_nonce_counter, _reverse_bytes ); /* Check overflow */ if (_nonce_counter > UINT16_MAX - target ) { /* 2 bytes are not long enough */ uint8_t _it = 3; while ( _it <= crypto_box_NONCEBYTES ) _it += ++nonce[crypto_box_NONCEBYTES - _it] ? crypto_box_NONCEBYTES : 1; _nonce_counter = _nonce_counter - (UINT16_MAX - target ); /* Assign the rest of it */ } else { /* Increase nonce */ _nonce_counter += target; } /* Assign the last bytes */ U16_to_bytes( _reverse_bytes, _nonce_counter); nonce [crypto_box_NONCEBYTES - 1] = _reverse_bytes[0]; nonce [crypto_box_NONCEBYTES - 2] = _reverse_bytes[1]; } /** * @brief Speaks for it self. * */ static const uint32_t payload_table[] = { 8000, 8000, 8000, 8000, 8000, 8000, 16000, 8000, 8000, 8000, /* 0-9 */ 44100, 44100, 0, 0, 90000, 8000, 11025, 22050, 0, 0, /* 10-19 */ 0, 0, 0, 0, 0, 90000, 90000, 0, 90000, 0, /* 20-29 */ 0, 90000, 90000, 90000, 90000, 0, 0, 0, 0, 0, /* 30-39 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40-49 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50-59 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60-69 */ PAYLOAD_ID_VALUE_OPUS, PAYLOAD_ID_VALUE_VP8, 0, 0, 0, 0, 0, 0, 0, 0,/* 70-79 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80-89 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90-99 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 100-109 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 110-119 */ 0, 0, 0, 0, 0, 0, 0, 0 /* 120-127 */ }; /** * @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; } const uint8_t *_it = payload; RTPHeader *_retu = calloc(1, sizeof (RTPHeader)); assert(_retu); _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 ); uint32_t _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)); assert(_retu); 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; _retu->table = calloc(_ext_length, sizeof (uint32_t)); assert(_retu->table); 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) ); assert(_retu); 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 ( uint16_t sequnum, 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; } _retu->header->sequnum = sequnum; _retu->length = length - _retu->header->length; uint16_t _from_pos = _retu->header->length - 2 /* Since sequ num is excluded */ ; 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, uint8_t *data, uint32_t length ) { RTPSession *_session = object; RTPMessage *_msg; if ( !_session || length < 13 + crypto_box_MACBYTES) { /* 12 is the minimum length for rtp + desc. byte */ LOGGER_WARNING("No session or invalid length of received buffer!"); return -1; } if ( _session->queue_limit <= _session->queue_size ) { LOGGER_WARNING("Queue limit reached!"); return -1; } uint8_t _plain[MAX_UDP_PACKET_SIZE]; uint16_t _sequnum; bytes_to_U16(&_sequnum, data + 1); /* Clculate the right nonce */ uint8_t _calculated[crypto_box_NONCEBYTES]; memcpy(_calculated, _session->decrypt_nonce, crypto_box_NONCEBYTES); increase_nonce ( _calculated, _sequnum ); /* Decrypt message */ int _decrypted_length = decrypt_data_symmetric( (uint8_t *)_session->decrypt_key, _calculated, data + 3, length - 3, _plain ); /* This packet is either not encrypted properly or late */ if ( -1 == _decrypted_length ) { /* If this is the case, then the packet is most likely late. * Try with old nonce cycle. */ if ( _session->rsequnum < _sequnum ) { _decrypted_length = decrypt_data_symmetric( (uint8_t *)_session->decrypt_key, _session->nonce_cycle, data + 3, length - 3, _plain ); if ( _decrypted_length == -1 ) { LOGGER_WARNING("Packet not ecrypted properly!"); return -1; /* This packet is not encrypted properly */ } /* Otherwise, if decryption is ok with new cycle, set new cycle */ } else { increase_nonce ( _calculated, MAX_SEQU_NUM ); _decrypted_length = decrypt_data_symmetric( (uint8_t *)_session->decrypt_key, _calculated, data + 3, length - 3, _plain ); if ( _decrypted_length == -1 ) { LOGGER_WARNING("Error decrypting!"); return -1; /* This is just an error */ } /* A new cycle setting. */ memcpy(_session->nonce_cycle, _session->decrypt_nonce, crypto_box_NONCEBYTES); memcpy(_session->decrypt_nonce, _calculated, crypto_box_NONCEBYTES); } } _msg = msg_parse ( _sequnum, _plain, _decrypted_length ); 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; } 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; } _session->queue_size++; pthread_mutex_unlock(&_session->mutex); 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)); assert(_retu); /* 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; 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 ); _from_pos = add_ext_header ( _retu->ext_header, _from_pos + 1 ); } else { _from_pos = add_header ( _retu->header, _retu->data ); } /* * 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 Release all messages held by session. * * @param session The session. * @return int * @retval -1 Error occurred. * @retval 0 Success. */ int rtp_release_session_recv ( RTPSession *session ) { if ( !session ) { LOGGER_WARNING("No session!"); return -1; } RTPMessage *_tmp, * _it; pthread_mutex_lock(&session->mutex); for ( _it = session->oldest_msg; _it; _it = _tmp ) { _tmp = _it->next; rtp_free_msg( session, _it); } session->last_msg = session->oldest_msg = NULL; session->queue_size = 0; pthread_mutex_unlock(&session->mutex); return 0; } /** * @brief Call this to change queue limit * * @param session The session * @param limit new limit * @return void */ void rtp_queue_adjust_limit(RTPSession *session, uint64_t limit) { RTPMessage *_tmp, * _it; pthread_mutex_lock(&session->mutex); for ( _it = session->oldest_msg; session->queue_size > limit; _it = _tmp ) { _tmp = _it->next; rtp_free_msg( session, _it); session->queue_size --; } session->oldest_msg = _it; session->queue_limit = limit; pthread_mutex_unlock(&session->mutex); } /** * @brief Gets oldest message in the list. * * @param session Where the list is. * @return RTPMessage* The message. You _must_ call rtp_msg_free() to free it. * @retval NULL No messages in the list, or no list. */ RTPMessage *rtp_recv_msg ( RTPSession *session ) { if ( !session ) { LOGGER_WARNING("No session!"); return NULL; } pthread_mutex_lock(&session->mutex); if ( session->queue_size == 0 ) { pthread_mutex_unlock(&session->mutex); return NULL; } RTPMessage *_retu = session->oldest_msg; /*if (_retu)*/ session->oldest_msg = _retu->next; if ( !session->oldest_msg ) session->last_msg = NULL; session->queue_size --; pthread_mutex_unlock(&session->mutex); 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; } uint8_t _send_data [ MAX_UDP_PACKET_SIZE ]; _send_data[0] = session->prefix; /* Generate the right nonce */ uint8_t _calculated[crypto_box_NONCEBYTES]; memcpy(_calculated, session->encrypt_nonce, crypto_box_NONCEBYTES); increase_nonce ( _calculated, msg->header->sequnum ); /* Need to skip 2 bytes that are for sequnum */ int encrypted_length = encrypt_data_symmetric( /* TODO: msg->length - 2 (fix this properly)*/ (uint8_t *) session->encrypt_key, _calculated, msg->data + 2, msg->length, _send_data + 3 ); _send_data[1] = msg->data[0]; _send_data[2] = msg->data[1]; if ( -1 == send_custom_lossy_packet(messenger, session->dest, _send_data, encrypted_length + 3) ) { LOGGER_WARNING("Failed to send full packet! std error: %s", strerror(errno)); rtp_free_msg ( session, msg ); return -1; } /* Set sequ number */ if ( session->sequnum >= MAX_SEQU_NUM ) { session->sequnum = 0; memcpy(session->encrypt_nonce, _calculated, crypto_box_NONCEBYTES); } else { session->sequnum++; } 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. * @param encrypt_key Speaks for it self. * @param decrypt_key Speaks for it self. * @param encrypt_nonce Speaks for it self. * @param decrypt_nonce Speaks for it self. * @return RTPSession* Created control session. * @retval NULL Error occurred. */ RTPSession *rtp_init_session ( int payload_type, Messenger *messenger, int friend_num, const uint8_t *encrypt_key, const uint8_t *decrypt_key, const uint8_t *encrypt_nonce, const uint8_t *decrypt_nonce ) { RTPSession *_retu = calloc(1, sizeof(RTPSession)); assert(_retu); if ( -1 == custom_lossy_packet_registerhandler(messenger, friend_num, payload_type, rtp_handle_packet, _retu) || !encrypt_key || !decrypt_key || !encrypt_nonce || !decrypt_nonce) { 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_table[payload_type]; _retu->dest = friend_num; _retu->rsequnum = _retu->sequnum = 1; _retu->ext_header = NULL; /* When needed allocate */ _retu->encrypt_key = encrypt_key; _retu->decrypt_key = decrypt_key; /* Need to allocate new memory */ _retu->encrypt_nonce = calloc ( crypto_box_NONCEBYTES, sizeof (uint8_t) ); assert(_retu->encrypt_nonce); _retu->decrypt_nonce = calloc ( crypto_box_NONCEBYTES, sizeof (uint8_t) ); assert(_retu->decrypt_nonce); _retu->nonce_cycle = calloc ( crypto_box_NONCEBYTES, sizeof (uint8_t) ); assert(_retu->nonce_cycle); memcpy(_retu->encrypt_nonce, encrypt_nonce, crypto_box_NONCEBYTES); memcpy(_retu->decrypt_nonce, decrypt_nonce, crypto_box_NONCEBYTES); memcpy(_retu->nonce_cycle , decrypt_nonce, crypto_box_NONCEBYTES); _retu->csrc = calloc(1, sizeof (uint32_t)); assert(_retu->csrc); _retu->csrc[0] = _retu->ssrc; /* Set my ssrc to the list receive */ /* Also set payload type as prefix */ _retu->prefix = payload_type; _retu->oldest_msg = _retu->last_msg = NULL; _retu->queue_limit = 100; /* Default */ _retu->queue_size = 0; pthread_mutex_init(&_retu->mutex, NULL); /* * */ 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. */ int rtp_terminate_session ( RTPSession *session, Messenger *messenger ) { if ( !session ) { LOGGER_WARNING("No session!"); return -1; } custom_lossy_packet_registerhandler(messenger, session->dest, session->prefix, NULL, NULL); rtp_release_session_recv(session); pthread_mutex_lock(&session->mutex); free ( session->ext_header ); free ( session->csrc ); free ( session->decrypt_nonce ); free ( session->encrypt_nonce ); free ( session->nonce_cycle ); pthread_mutex_unlock(&session->mutex); pthread_mutex_destroy(&session->mutex); /* And finally free session */ free ( session ); return 0; }