doubango/tinyDAV/src/t140/tdav_session_t140.c
c732d49e
 #if HAVE_CRT
 #define _CRTDBG_MAP_ALLOC 
 #include <stdlib.h> 
 #include <crtdbg.h>
 #endif //HAVE_CRT
 /*
74ca6d11
 * Copyright (C) 2020, University of the Basque Country (UPV/EHU)
c732d49e
 * Contact for licensing options: <licensing-mcpttclient(at)mcopenplatform(dot)com>
 *
 * The original file was part of Open Source Doubango Framework
 * Copyright (C) 2010-2011 Mamadou Diop.
 * Copyright (C) 2012 Doubango Telecom <http://doubango.org>
 *
 * This file is part of Open Source Doubango Framework.
 *
 * DOUBANGO 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.
 *
 * DOUBANGO 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 DOUBANGO.
 *
 */
 
 
 /**@file tdav_session_t140.c
  * @brief Session for T140 protocol (RFC 4103/5194/2198)
  */
 #include "tinydav/t140/tdav_session_t140.h"
 #include "tinydav/t140/tdav_consumer_t140.h"
 #include "tinydav/t140/tdav_producer_t140.h"
 #include "tinydav/codecs/fec/tdav_codec_red.h"
 
 #include "tinymedia/tmedia_consumer.h"
 #include "tinymedia/tmedia_producer.h"
 #include "tinymedia/tmedia_defaults.h"
 
 #include "tinyrtp/trtp_manager.h"
 #include "tinyrtp/rtp/trtp_rtp_packet.h"
 
 #include "tsk_debug.h"
 
 #include <limits.h>
 
 #define T140_BLOCKS_MAX_COUNT			20 /* number of blocks to buffer for packet loss detection and reordering */
 #define T140_BLOCK_MAX_TRANSMIT_COUNT	4 /* maximum number of times to retransmit a bloc when RED is ON */	
 
 #define T140_ZERO_WIDTH_NO_BREAK_SPACE 0xEFBBBF /* send at the begining of the T.140 session */
 #define T140_BACKSPACE		0x08
 #define T140_ESC            0x1B
 #define T140_CR				0x0D
 #define T140_LF				0x0A
 #define T140_CR_LF          0x0D0A
 #define T140_BELL           0x07
 #define T140_SOS            0x98
 #define T140_STRING_TERM    0x9C
 #define T140_GRAPHIC_START  0x9B
 #define T140_GRAPHIC_END    0x6D
 #define T140_LOSS_CHAR_CHAR 0xFFFD
 #define T140_LOSS_UTF8		0xEFBFBD
 
 #define T140_WAIT_FOR_MISSING_PKT_RED_OFF	500  /*Time to wait for missing pkts when RED is OFF (in ms) */
 #define T140_WAIT_FOR_MISSING_PKT_RED_ON	3000  /*Time to wait for missing pkts when RED is ON (in ms) */
 #define T140_WAIT_FOR_BUFFERING				300		/* Time to wait for buffering (T.140 blocks forming) */
 #define T140_WAIT_FOR_IDLE					10000 /* Time to wait before entering in IDLE state */
 
 static const char zero_width_no_break_space[3] = 
 { 
 	(T140_ZERO_WIDTH_NO_BREAK_SPACE >> 16) & 0xFF, 
 	(T140_ZERO_WIDTH_NO_BREAK_SPACE >> 8) & 0xFF, 
 	T140_ZERO_WIDTH_NO_BREAK_SPACE & 0xFF
 };
 
 typedef struct t140_block_s
 {
 	TSK_DECLARE_OBJECT;
 
 	uint8_t pt;
 	uint16_t seq_num;
 	uint32_t timestamp;
 	uint32_t transmit_count; // number of times this block have been transmitted
 	// whether the block have been forwarded to the end user for UI display or sent through the RTP channel
 	tsk_bool_t delivered;
 	// whether the block contains data skipped because too late
 	tsk_bool_t too_late;
 	struct{
 		uint8_t* ptr;
 		tsk_size_t size;
 	}data;
 }
 t140_block_t;
 #define T140_BLOCK(self) ((t140_block_t*)(self))
 extern const tsk_object_def_t *t140_block_def_t;
 
 static int _tdav_session_t140_producer_enc_cb(const void* callback_data, const void* buffer, tsk_size_t size);
 static int _tdav_session_t140_save_outgoing_data(tdav_session_t140_t* self, const void* data_ptr, tsk_size_t data_size);
 static tsk_size_t _tdav_session_t140_encap_red(tdav_session_t140_t* self, void** out_data_ptr, tsk_size_t* out_data_size, tsk_bool_t *last_data);
 static int _tdav_session_t140_rtp_cb(const void* callback_data, const struct trtp_rtp_packet_s* packet);
 static int _tdav_session_t140_recv_red(tdav_session_t140_t* self, const struct trtp_rtp_packet_s* packet);
 static int _tdav_session_t140_recv_raw(tdav_session_t140_t* self, const void* data_ptr, tsk_size_t data_size);
 static int _tdav_session_t140_decode_and_deliver_blocks(tdav_session_t140_t* self, tsk_bool_t called_from_timer);
 static int _tdav_session_t140_consume_data(tdav_session_t140_t* self, enum tmedia_t140_data_type_e data_type, const void *data_ptr, tsk_size_t data_size);
 static int _tdav_session_t140_timer_cb(const void* arg, tsk_timer_id_t timer_id);
 static int _tdav_session_t140_pred_block_with_transmit_count_more_than_or_equal(const tsk_list_item_t *item, const void *transmit_count);
 
 static tsk_bool_t _tdav_session_t140_blocks_has_gap(tdav_session_t140_blocks_L_t* blocks);
 static tsk_bool_t _tdav_session_t140_blocks_has_seqnum(tdav_session_t140_blocks_L_t* blocks, uint16_t seq_num);
 static int _tdav_session_t140_blocks_add(tdav_session_t140_blocks_L_t* blocks, int64_t* blocks_count, t140_block_t** block, int64_t blocks_count_max);
 static t140_block_t* _tdav_session_t140_block_create(uint8_t pt, uint16_t seq_num, uint32_t timestamp, tsk_bool_t delivered, const void* data_ptr, tsk_size_t data_size);
 
 #define T140_RESIZE_BUFFER(_in_buff_ptr, _in_buff_size, _new_size) \
 if((_in_buff_size) < (_new_size)){ \
 	if(!((_in_buff_ptr) = tsk_realloc((_in_buff_ptr), (_new_size)))){ \
 		TSK_DEBUG_ERROR("Failed to allocate new buffer"); \
 		(_in_buff_size) = 0; \
 	} \
 	(_in_buff_size) = _new_size; \
 }
 
 static int tdav_session_t140_send_data(tmedia_session_t* self, enum tmedia_t140_data_type_e data_type, const void* data_ptr, unsigned data_size);
 #define tdav_session_t140_send_data_2(self, data_type) tdav_session_t140_send_data((self), (data_type), tsk_null, 0)
 
 static int tdav_session_t140_set(tmedia_session_t* self, const tmedia_param_t* param)
 {
 	int ret = 0;
 	tdav_session_t140_t* t140;
 
 	if(!self){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	if(tdav_session_av_set(TDAV_SESSION_AV(self), param) == tsk_true){
 		return 0;
 	}
 
 	t140 = (tdav_session_t140_t*)self;
 
 	if(param->plugin_type == tmedia_ppt_consumer){
 		TSK_DEBUG_ERROR("Not expected consumer(%s)", param->key);
 	}
 	else if(param->plugin_type == tmedia_ppt_producer){
 		TSK_DEBUG_ERROR("Not expected producer_set(%s)", param->key);
 	}
 	else{
 		TSK_DEBUG_ERROR("Not expected producer_set(%s)", param->key);
 	}
 
 	return ret;
 }
 
 static int tdav_session_t140_get(tmedia_session_t* self, tmedia_param_t* param)
 {
 	if(!self || !param){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 	
 	if(tdav_session_av_get(TDAV_SESSION_AV(self), param) == tsk_true){
 		return 0;
 	}
 
 	TSK_DEBUG_ERROR("(%s) not expected param key for plugin_type=%d", param->key, param->plugin_type);
 	return -2;
 }
 
 static int tdav_session_t140_prepare(tmedia_session_t* self)
 {
 	tdav_session_av_t* base = (tdav_session_av_t*)(self);
 	int ret;
 
 	if((ret = tdav_session_av_prepare(base))){
 		TSK_DEBUG_ERROR("tdav_session_av_prepare(t140) failed");
 		return ret;
 	}
 
 	if(base->rtp_manager){
 		ret = trtp_manager_set_rtp_callback(base->rtp_manager, _tdav_session_t140_rtp_cb, base);
 	}
 
 	return ret;
 }
 
 static int tdav_session_t140_start(tmedia_session_t* self)
 {
 	int ret;
 	tdav_session_t140_t* t140;
 	const tmedia_codec_t* best_codec;
 	tdav_session_av_t* base;
 
 	if(!self){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	t140 = (tdav_session_t140_t*)self;
 	base = (tdav_session_av_t*)self;
 	
 	if(!(best_codec = tdav_session_av_get_best_neg_codec(base))){
 		TSK_DEBUG_ERROR("No codec matched");
 		return -2;
 	}
 	
 	TSK_OBJECT_SAFE_FREE(t140->encoder.codec);
 	t140->encoder.codec = tsk_object_ref((tsk_object_t*)best_codec);
 
 	if((ret = tdav_session_av_start(base, best_codec))){
 		TSK_DEBUG_ERROR("tdav_session_av_start(t140) failed");
 		return ret;
 	}
 
 	if((ret = tsk_timer_manager_start(t140->h_timer)) != 0){
 		TSK_DEBUG_ERROR("Failed to start the timer");
 		return ret;
 	}
 
 	if(base->rtp_manager){
 		int pt;
 		t140->encoder.payload_type = t140->encoder.codec->neg_format ? atoi(t140->encoder.codec->neg_format) : atoi(t140->encoder.codec->format);
 		pt = base->red.codec ? 
 			( base->red.codec->neg_format ? atoi(base->red.codec->neg_format) : atoi(base->red.codec->format) ) :
 			( t140->encoder.payload_type );
 		trtp_manager_set_payload_type(base->rtp_manager, pt);
 	}
 
 	t140->decoder.last_seq_num = -1;
 
 	t140->started = (ret == 0);
 
 	if(t140->started){
 		t140->encoder.timer_idle.timeout = T140_WAIT_FOR_IDLE;
 		t140->encoder.timer_buffering.timeout = T140_WAIT_FOR_BUFFERING;
 		t140->encoder.timer_idle.id = tsk_timer_manager_schedule(t140->h_timer, t140->encoder.timer_idle.timeout, _tdav_session_t140_timer_cb, t140);
 		TSK_DEBUG_INFO("T.140 - Start IDLE timer");
 	}
 
 	return ret;
 }
 
 static int tdav_session_t140_pause(tmedia_session_t* self)
 {
 	return 0;
 }
 
 static int tdav_session_t140_stop(tmedia_session_t* self)
 {
 	int ret;
 	ret = tsk_timer_manager_stop(TDAV_SESSION_T140(self)->h_timer);
 	ret = tdav_session_av_stop(TDAV_SESSION_AV(self));
 	TSK_OBJECT_SAFE_FREE(TDAV_SESSION_T140(self)->encoder.codec);
 	TSK_OBJECT_SAFE_FREE(TDAV_SESSION_T140(self)->decoder.codec);
 	TDAV_SESSION_T140(self)->started = tsk_false;
 	return ret;
 }
 
 static const tsdp_header_M_t* tdav_session_t140_get_lo(tmedia_session_t* self)
 {
 	tsk_bool_t updated = tsk_false;
 	const tsdp_header_M_t* ret;
 	tdav_session_av_t* base = TDAV_SESSION_AV(self);
 
 
 	if(!(ret = tdav_session_av_get_lo(base, &updated))){
 		TSK_DEBUG_ERROR("tdav_session_av_get_lo(t140) failed");
 		return tsk_null;
 	}
 
 	if(updated){
 		tsk_safeobj_lock(base);
 		TSK_OBJECT_SAFE_FREE(TDAV_SESSION_T140(self)->encoder.codec);
 		tsk_safeobj_unlock(base);
 	}
 	
 	return ret;
 }
 
 static int tdav_session_t140_set_ro(tmedia_session_t* self, const tsdp_header_M_t* ro)
 {
 	int ret;
 	tsk_bool_t updated = tsk_false;
 	tdav_session_av_t* base = TDAV_SESSION_AV(self);
 
 	if((ret = tdav_session_av_set_ro(base, ro, &updated))){
 		TSK_DEBUG_ERROR("tdav_session_av_set_ro(t140) failed");
 		return ret;
 	}
 
 	if(updated){
 		tsk_safeobj_lock(base);
 		TSK_OBJECT_SAFE_FREE(TDAV_SESSION_T140(self)->encoder.codec);
 		tsk_safeobj_unlock(base);
 	}	
 
 	return ret;
 }
 
 static int tdav_session_t140_set_ondata_cbfn(tmedia_session_t* self, const void* context, tmedia_session_t140_ondata_cb_f func)
 {
 	tdav_session_t140_t* t140 = (tdav_session_t140_t*)self;
 	tdav_session_av_t* base = (tdav_session_av_t*)self;
 	if(!self){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	t140->cb_ondata.context = context;
 	t140->cb_ondata.func = func;
 
 	if(base->consumer){
 		return tdav_consumer_t140_set_ondata_cbfn(TDAV_CONSUMER_T140(base->consumer), context, func);
 	}
 	return -2;
 }
 
 static int tdav_session_t140_send_data(tmedia_session_t* self, enum tmedia_t140_data_type_e data_type, const void* data_ptr, unsigned data_size)
 {
 	tdav_session_av_t* base = (tdav_session_av_t*)self;
 	if(!self){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	if(base->producer){
 		return tdav_producer_send_data(TDAV_PRODUCER_T140(base->producer), data_type, data_ptr, data_size);
 	}
 	return -2;
 }
 
 // RTP/RTCP callback (From the network to the consumer)
 static int _tdav_session_t140_rtp_cb(const void* callback_data, const struct trtp_rtp_packet_s* packet)
 {
 	tdav_session_t140_t* t140 = (tdav_session_t140_t*)callback_data;
 	tdav_session_av_t* base = (tdav_session_av_t*)callback_data;
 
 	if(!t140 || !packet || !packet->header){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	if(!t140->started || !base->consumer){
 		TSK_DEBUG_ERROR("Not ready yet");
 		return -1;
 	}
 
 	if(packet->header->payload_type == base->red.payload_type){
 		//static void* __red_buffer_ptr = tsk_null; // Never used
 		//static tsk_size_t __red_buffer_size = 0; // Never used
 		if(!base->red.codec){
 			TSK_DEBUG_ERROR("No RED codec could be found");
 			return -2;
 		}
 		// Decode RED data
 		/* base->red.codec->plugin->decode(
 				base->red.codec, 
 				(packet->payload.data ? packet->payload.data : packet->payload.data_const), packet->payload.size, 
 				&__red_buffer_ptr, &__red_buffer_size, 
 				packet->header
 			);
 		return 0;
 		*/
 		_tdav_session_t140_recv_red(t140, packet);
 	}
 	else{
 		tsk_size_t out_size;
 		// Find the codec to use to decode the RTP payload
 		if(!t140->decoder.codec || t140->decoder.payload_type != packet->header->payload_type){
 			tsk_istr_t format;
 			TSK_OBJECT_SAFE_FREE(t140->decoder.codec);
 			tsk_itoa(packet->header->payload_type, &format);
 			if(!(t140->decoder.codec = tmedia_codec_find_by_format(TMEDIA_SESSION(t140)->neg_codecs, format)) || !t140->decoder.codec->plugin || !t140->decoder.codec->plugin->decode){
 				TSK_DEBUG_ERROR("%s is not a valid payload for this session", format);
 				return -2;
 			}
 			t140->decoder.payload_type = packet->header->payload_type;
 		}
 		
 		// Open codec if not already done
 		if(!TMEDIA_CODEC(t140->decoder.codec)->opened){
 			int ret;
 			tsk_safeobj_lock(base);
 			if((ret = tmedia_codec_open(t140->decoder.codec))){
 				tsk_safeobj_unlock(base);
 				TSK_OBJECT_SAFE_FREE(t140->decoder.codec);
 				TSK_DEBUG_ERROR("Failed to open [%s] codec", t140->decoder.codec->plugin->desc);
 				return ret;
 			}
 			tsk_safeobj_unlock(base);
 		}
 		// Decode data
 		out_size = t140->decoder.codec->plugin->decode(t140->decoder.codec, packet->payload.data, packet->payload.size, &t140->decoder.buffer, &t140->decoder.buffer_size, packet->header);
 		if(out_size){
 			_tdav_session_t140_recv_raw(t140, t140->decoder.buffer, out_size);
 		}
 	}
 
 	return 0;
 }
 
 // Producer callback (From the producer to the network). Will encode() data before sending
 static int _tdav_session_t140_producer_enc_cb(const void* callback_data, const void* buffer, tsk_size_t size)
 {
 	int ret = 0;
 
 	tdav_session_t140_t* t140 = (tdav_session_t140_t*)callback_data;
 	tdav_session_av_t* base = (tdav_session_av_t*)callback_data;
 
 	if(!t140){
 		TSK_DEBUG_ERROR("Null session");
 		return 0;
 	}
 	
 	if(t140->started && base->rtp_manager && base->rtp_manager->is_started && t140->encoder.codec){
 		/* encode */
 		//tsk_size_t out_size = 0;
 
 		if(!base->rtp_manager->is_started){
 			TSK_DEBUG_ERROR("Not started");
 			return 0;
 		}
 
 		// Open codec if not already done
 		if(!t140->encoder.codec->opened){
 			tsk_safeobj_lock(base);
 			if((ret = tmedia_codec_open(t140->encoder.codec))){
 				tsk_safeobj_unlock(base);
 				TSK_DEBUG_ERROR("Failed to open [%s] codec", t140->encoder.codec->plugin->desc);
 				return -4;
 			}
 			tsk_safeobj_unlock(base);
 		}
 		
 		ret = _tdav_session_t140_save_outgoing_data(t140, buffer, size);
 		
 		// start buffering timer if not already done
 		if(!TSK_TIMER_ID_IS_VALID(t140->encoder.timer_buffering.id)){
 			TSK_DEBUG_INFO("T.140 - Schedule buffering timer");
 			t140->encoder.timer_buffering.id = tsk_timer_manager_schedule(t140->h_timer, t140->encoder.timer_buffering.timeout, _tdav_session_t140_timer_cb, t140);
 		}
 		
 		//if((t140->encoder.codec = tsk_object_ref(t140->encoder.codec))){ /* Thread safeness (SIP reINVITE or UPDATE could update the encoder) */
 			//out_size = t140->encoder.codec->plugin->encode(t140->encoder.codec, buffer, size, &t140->encoder.buffer, &t140->encoder.buffer_size);
 			//if(out_size){
 				// trtp_manager_send_rtp(base->rtp_manager, t140->encoder.buffer, out_size, TMEDIA_CODEC_PCM_FRAME_SIZE(t140->encoder.codec), tsk_false/*Marker*/, tsk_true/*lastPacket*/);
 			//}
 			//tsk_object_unref(t140->encoder.codec);
 		//}
 	}
 	return ret;
 }
 
 // save data before sending (when timer fires)
 static int _tdav_session_t140_save_outgoing_data(tdav_session_t140_t* self, const void* data_ptr, tsk_size_t data_size)
 {
 	tdav_session_av_t* base = (tdav_session_av_t*)self;
 
 	if(!self || !data_ptr || !data_size){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	tsk_safeobj_lock(base);
 
 	T140_RESIZE_BUFFER(self->encoder.buffer, self->encoder.buffer_size, (self->encoder.buffer_idx + data_size));
 	if(self->encoder.buffer && self->encoder.buffer_size){
 		memcpy(&((uint8_t*)self->encoder.buffer)[self->encoder.buffer_idx], data_ptr, data_size);
 		self->encoder.buffer_idx += data_size;
 	}
 
 	tsk_safeobj_unlock(base);
 
 	return 0;
 }
 
 // encapsulated data for sending
 static tsk_size_t _tdav_session_t140_encap_red(tdav_session_t140_t* self, void** out_data_ptr, tsk_size_t* out_data_size, tsk_bool_t *last_data)
 {
 	tdav_session_av_t* base = (tdav_session_av_t*)self;
 	t140_block_t* block, *block_curr = tsk_null;
 	t140_block_t* red_blocks[T140_BLOCK_MAX_TRANSMIT_COUNT] = { tsk_null };
 	tsk_size_t red_blocks_idx, idx, red_blocks_size, red_blocks_hdrs_size;
 	tsk_list_item_t* item;
 	uint16_t rtp_seq_num;
 	uint32_t* pout_data_ptr32;
 	uint8_t* pout_data_ptr8;
 	static const uint32_t max_transmit_count = T140_BLOCK_MAX_TRANSMIT_COUNT;
 
 	if(!self || !base->rtp_manager || !out_data_ptr || !out_data_size || !last_data){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return 0;
 	}
 
 	*last_data = tsk_false;
 
 	tsk_safeobj_lock(base);
 	tsk_list_lock(self->encoder.blocks);
 
 	// cancel IDLE timer
 	if(TSK_TIMER_ID_IS_VALID(self->encoder.timer_idle.id)){
 		tsk_timer_manager_cancel(self->h_timer, self->encoder.timer_idle.id);
 		self->encoder.timer_idle.id = TSK_INVALID_TIMER_ID;
 		TSK_DEBUG_INFO("T.140 - Cancel IDLE timer");
 	}
 
 	rtp_seq_num = base->rtp_manager->rtp.seq_num;
 
 	if(self->encoder.buffer && self->encoder.buffer_idx){
 		block_curr = _tdav_session_t140_block_create(self->encoder.payload_type, rtp_seq_num, 0, tsk_false, self->encoder.buffer, self->encoder.buffer_idx);
 		self->encoder.buffer_idx = 0;
 	}
 
 	// build RED blocks
 	red_blocks_idx = 0;
 	tsk_list_foreach(item, self->encoder.blocks){
 		if(!(block = item->data) || (block->transmit_count + 1) >= T140_BLOCK_MAX_TRANSMIT_COUNT){
 			continue;
 		}
 
 		if(block->seq_num <= rtp_seq_num && (idx = (rtp_seq_num - block->seq_num)) < T140_BLOCK_MAX_TRANSMIT_COUNT){
 			++block->transmit_count; // increment transmission count
 			red_blocks[(T140_BLOCK_MAX_TRANSMIT_COUNT - idx - 1)] = block; // backwards counting
 			++red_blocks_idx;
 		}
 		
 		if((red_blocks_idx + 1) >= T140_BLOCK_MAX_TRANSMIT_COUNT){
 			break;
 		}
 	}
 
 	// compute RED blocks size
 	red_blocks_hdrs_size = ((T140_BLOCK_MAX_TRANSMIT_COUNT - 1) << 2) + 1;
 	red_blocks_size = red_blocks_hdrs_size;
 	for(idx = 0; idx < T140_BLOCK_MAX_TRANSMIT_COUNT; ++idx){
 		if(red_blocks[idx] && red_blocks[idx]->data.ptr && red_blocks[idx]->data.size){
 			red_blocks_size += red_blocks[idx]->data.size;
 		}
 	}
 	if(block_curr && block_curr->data.ptr && block_curr->data.size){
 		red_blocks_size += block_curr->data.size;
 	}
 
 	// resize output buffer
 	T140_RESIZE_BUFFER(*out_data_ptr, *out_data_size, red_blocks_size);
 	if(!*out_data_ptr) {
 		red_blocks_size = 0;
 		goto bail;
 	}
 
 	// compute RED headers
 	pout_data_ptr32 = (uint32_t*)*out_data_ptr;
 	for(idx = 0; idx < T140_BLOCK_MAX_TRANSMIT_COUNT; ++idx){
 		if((idx + 1) == T140_BLOCK_MAX_TRANSMIT_COUNT){ // last header ?
 			*((uint8_t*)&pout_data_ptr32[idx]) = (self->encoder.payload_type & 0x7F); // F=0 | PT
 		}
 		else{
 			pout_data_ptr32[idx] = tnet_htonl(
 					( (0x80 | (self->encoder.payload_type & 0x7F)) << 24 ) | // F=1 | PT
 					( 0x0000 << 8 ) | // imestamp offset
 					( red_blocks[idx] ? (red_blocks[idx]->data.size & 0xFF) : 0x00 ) // block length
 				);
 		}
 	}
 
 	// compute RED data
 	pout_data_ptr8 = &((uint8_t*)*out_data_ptr)[red_blocks_hdrs_size];
 	for(idx = 0; idx < T140_BLOCK_MAX_TRANSMIT_COUNT; ++idx){
 		if(red_blocks[idx] && red_blocks[idx]->data.ptr && red_blocks[idx]->data.size){
 			memcpy(pout_data_ptr8, red_blocks[idx]->data.ptr, red_blocks[idx]->data.size);
 			pout_data_ptr8 += red_blocks[idx]->data.size;
 		}
 	}
 	if(block_curr && block_curr->data.ptr && block_curr->data.size){
 		memcpy(pout_data_ptr8, block_curr->data.ptr, block_curr->data.size);
 		pout_data_ptr8 += block_curr->data.size;
 	}
 
 	if((*last_data = (red_blocks_hdrs_size == red_blocks_size))){
 		// no data available -> clear items, send empty block, schedule idle timer, cancel buffering timer
 		if(TSK_TIMER_ID_IS_VALID(self->encoder.timer_buffering.id)){
 			tsk_timer_manager_cancel(self->h_timer, self->encoder.timer_buffering.id);
 			self->encoder.timer_buffering.id = TSK_INVALID_TIMER_ID;
 			TSK_DEBUG_INFO("T.140 - Cancel buffering timer");
 		}
 		self->encoder.timer_idle.id = tsk_timer_manager_schedule(self->h_timer, self->encoder.timer_idle.timeout, _tdav_session_t140_timer_cb, self);
 		TSK_DEBUG_INFO("T.140 - Schedule IDLE timer");
 		tsk_list_clear_items(self->encoder.blocks);
 	}
 	else{
 		// clean up all blocks retransmitted more than max times
 		while(tsk_list_remove_item_by_pred(self->encoder.blocks, _tdav_session_t140_pred_block_with_transmit_count_more_than_or_equal, &max_transmit_count)) ;
 	}
 
 bail:
 	if(block_curr){
 		_tdav_session_t140_blocks_add(self->encoder.blocks, &self->encoder.blocks_count, &block_curr, INT_MAX);
 		TSK_OBJECT_SAFE_FREE(block_curr);
 	}
 	tsk_list_unlock(self->encoder.blocks);
 	tsk_safeobj_unlock(base);
 
 	return red_blocks_size;
 }
 
 // decode data from RTP channel
 static int _tdav_session_t140_recv_red(tdav_session_t140_t* self, const struct trtp_rtp_packet_s* packet)
 {
 	const uint8_t* pdata;
 	const uint8_t* red_hdr;
 	tsk_size_t red_hdrs_count, i, in_size;
 	tsk_bool_t last, block_add;
 	uint8_t F, pt;
 	int32_t pkt_loss_start = -1, pkt_loss_count, seq_num;
 	uint16_t timestamp_offset, block_length;
 
 	if(!self || !packet || !packet->header || !(packet->payload.data || packet->payload.data_const) || (packet->payload.size < TDAV_CODEC_RED_MIN_PKT_SIZE)){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	pdata = red_hdr = packet->payload.data ? packet->payload.data : packet->payload.data_const;
 	in_size = packet->payload.size;
 	
 	if((F = (pdata[0] & 0x80)) == 0){
 		i = 1;
 		red_hdrs_count = 1;
 	}
 	else{
 		for(i = 0, red_hdrs_count = 0; i < in_size; i+= 4, ++red_hdrs_count){
 			if((F = (pdata[i] & 0x80)) == 0){ ++i; ++red_hdrs_count;  break; }
 		}
 	}
 	
 	if(i == in_size){
 		TSK_DEBUG_INFO("T.140 - Empty block");
 		return 0;
 	}
 	
 	pdata += i;
 	in_size -= i;
 	if(self->decoder.last_seq_num != -1 && (self->decoder.last_seq_num + 1) != packet->header->seq_num){
 		pkt_loss_start = (self->decoder.last_seq_num + 1);
 		pkt_loss_count = (packet->header->seq_num - pkt_loss_start);
 		TSK_DEBUG_INFO("T.140 - Packet loss[curr=%d, loss_start=%d, loss_count=%d]", packet->header->seq_num, pkt_loss_start, pkt_loss_count);
 	}
 
 	for(i = 0, block_add = tsk_false; i < red_hdrs_count; ++i, block_add = tsk_false){
 		last = (i == (red_hdrs_count - 1));
 		F = (red_hdr[0] & 0x80);
 		pt = (red_hdr[0] & 0x7F);
 		if(last || !F){
 			/*
 			 0 1 2 3 4 5 6 7
              +-+-+-+-+-+-+-+-+
              |0|   Block PT  |
              +-+-+-+-+-+-+-+-+
 			 */
 			block_length = (uint16_t)in_size;
 			seq_num = packet->header->seq_num;
 			timestamp_offset = 0;
 			block_add = tsk_true;
 		}
 		else{
 			/*
 				+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 				|1| block PT=7  |  timestamp offset         |   block length    |
 				+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 			*/
 			seq_num = (int32_t)(packet->header->seq_num - (red_hdrs_count - 1 - i)); // inferred by counting backwards
 			block_add = (pkt_loss_start != -1 && (seq_num <= pkt_loss_start && pkt_loss_start >= seq_num));	
 			timestamp_offset = ((red_hdr[1] << 8) | red_hdr[2]) >> 2;
 			block_length = ((red_hdr[2] & 0x03) << 8) | red_hdr[3];
 			/* block_add &= (block_length > 0); */ // take empty block to avoid scheduling pkt loss timer
 			if(pkt_loss_start != -1){
 				TSK_DEBUG_INFO("retransmit seq_num=%d, takken=%d", seq_num, block_add);
 			}
 			if(block_length > in_size){
 				TSK_DEBUG_ERROR("Invalid 'block length'");
 				break;
 			}
 			red_hdr += 4;
 		}
 		
 		if(block_add && !_tdav_session_t140_blocks_has_seqnum(self->decoder.blocks, seq_num)){
 			t140_block_t* block = _tdav_session_t140_block_create(
 				pt, 
 				seq_num,
 				(packet->header->timestamp + timestamp_offset), 
 				tsk_false,
 				pdata,
 				block_length);
 			_tdav_session_t140_blocks_add(self->decoder.blocks, &self->decoder.blocks_count, &block, T140_BLOCKS_MAX_COUNT);
 			TSK_OBJECT_SAFE_FREE(block);
 		}
 			
 
 		pdata += block_length;
 		in_size -= block_length;
 	}
 
 	self->decoder.last_seq_num = packet->header->seq_num;
 	
 	return _tdav_session_t140_decode_and_deliver_blocks(self, tsk_false);
 }
 
 static int _tdav_session_t140_recv_raw(tdav_session_t140_t* self, const void* data_ptr, tsk_size_t data_size)
 {
 	tsk_size_t i, j, cmd_extra_len, utf8_start, utf8_end;
 	tsk_bool_t is_cmd;
 	enum tmedia_t140_data_type_e cmd_type;
 	const uint8_t* _data_ptr = (const uint8_t*)data_ptr;
 
 	if(!self || !data_ptr || !data_size){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	for(i = 0, j = 0, is_cmd = tsk_false, cmd_extra_len = 0; i < data_size; ++i, is_cmd = tsk_false, cmd_extra_len = 0){
 		switch(_data_ptr[i]){
 			case T140_BACKSPACE:
 				{
 					TSK_DEBUG_INFO("T.140 - BACKSPACE");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_backspace;
 					break;
 				}
 			case T140_ESC:
 				{
 					TSK_DEBUG_INFO("T.140 - ESC");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_esc;
 					break;
 				}
 			case T140_LF:
 				{
 					TSK_DEBUG_INFO("T.140 - LF");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_lf;
 					break;
 				}
 			case T140_CR:
 				{
 					if((i + 1) < data_size && _data_ptr[i + 1] == T140_LF){
 						// CR_LF
 						TSK_DEBUG_INFO("T.140 - CRLF");
 						cmd_extra_len = 1;
 						is_cmd = tsk_true;
 						cmd_type = tmedia_t140_data_type_cr_lf;
 					}
 					else{
 						// CR
 						TSK_DEBUG_INFO("T.140 - CR");
 						is_cmd = tsk_true;
 						cmd_type = tmedia_t140_data_type_cr;
 					}
 					break;
 				}
 			case T140_BELL:
 				{
 					TSK_DEBUG_INFO("T.140 - BELL");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_bell;
 					break;
 				}
 			case T140_SOS:
 				{
 					TSK_DEBUG_INFO("T.140 - SOS");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_sos;
 					break;
 				}
 			case T140_STRING_TERM:
 				{
 					TSK_DEBUG_INFO("T.140 - ST");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_string_term;
 					break;
 				}
 			case (T140_LOSS_CHAR_CHAR >> 8):
 				{
 					if((i + 1) < data_size && _data_ptr[i + 1] == (T140_LOSS_CHAR_CHAR & 0xFF)){
 						TSK_DEBUG_INFO("T.140 - LOSS_CHAR_CHAR");
 						cmd_extra_len = 1;
 						is_cmd = tsk_true;
 						cmd_type = tmedia_t140_data_type_loss_char_char;
 					}
 					break;
 				}
 			case T140_GRAPHIC_START:
 				{
 					TSK_DEBUG_INFO("T.140 - GRAPHIC_START");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_graphic_start;
 					break;
 				}
 			case T140_GRAPHIC_END:
 				{
 					TSK_DEBUG_INFO("T.140 - GRAPHIC_END");
 					is_cmd = tsk_true;
 					cmd_type = tmedia_t140_data_type_graphic_end;
 					break;
 				}
 			// case (T140_ZERO_WIDTH_NO_BREAK_SPACE >> 16):
 			// case (T140_LOSS_UTF8 >> 16):
 			case 0xEF:
 				{
 					if((i + 2) < data_size && _data_ptr[i + 1] == ((T140_ZERO_WIDTH_NO_BREAK_SPACE >> 8) & 0xFF) && _data_ptr[i + 2] == (T140_ZERO_WIDTH_NO_BREAK_SPACE & 0xFF)){
 						TSK_DEBUG_INFO("T.140 - ZERO_WIDTH_NO_BREAK_SPACE");
 						cmd_extra_len = 2;
 						is_cmd = tsk_true;
 						cmd_type = tmedia_t140_data_type_zero_width_no_break_space;
 					}
 					else if((i + 2) < data_size && _data_ptr[i + 1] == ((T140_LOSS_UTF8 >> 8) & 0xFF) && _data_ptr[i + 2] == (T140_LOSS_UTF8 & 0xFF)){
 						TSK_DEBUG_INFO("T.140 - LOSS_UTF8");
 						cmd_extra_len = 2;
 						is_cmd = tsk_true;
 						cmd_type = tmedia_t140_data_type_loss_utf8;
 					}
 					break;
 				}
 		}//switch
 		
 		if(is_cmd || ((i + 1) >= data_size)){
 			utf8_start = j;
 			utf8_end = utf8_start + ( (j < i || (j == i && !is_cmd)) ? (i - j + 1) : 0 );
 			if(utf8_start < utf8_end){ // there is utf8 data to deliver
 				_tdav_session_t140_consume_data(self, tmedia_t140_data_type_utf8, &_data_ptr[utf8_start], (utf8_end - utf8_start));
 			}
 			if(is_cmd){ // there is cmd data to deliver
 				i += cmd_extra_len;
 				_tdav_session_t140_consume_data(self, cmd_type, tsk_null, 0);
 			}
 			j = i;
 		}
 	}// for
 
 	return 0;
 }
 
 static int _tdav_session_t140_decode_and_deliver_blocks(tdav_session_t140_t* self, tsk_bool_t called_from_timer)
 {
 	tdav_session_av_t* base = (tdav_session_av_t*)self;
 	if(!self){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	tsk_safeobj_lock(base);
 	tsk_list_lock(self->decoder.blocks);
 
 	if(!called_from_timer && _tdav_session_t140_blocks_has_gap(self->decoder.blocks)){
 		self->decoder.timer_pkt_loss.timeout = (base->red.codec ? T140_WAIT_FOR_MISSING_PKT_RED_ON : T140_WAIT_FOR_MISSING_PKT_RED_OFF);
 		self->decoder.timer_pkt_loss.id = tsk_timer_manager_schedule(self->h_timer, self->decoder.timer_pkt_loss.timeout, _tdav_session_t140_timer_cb, self);
 	}
 	else{
 		const tsk_list_item_t* item;
 		const t140_block_t* block;
 
 		tsk_list_foreach(item, self->decoder.blocks){
 			if(!(block = (const t140_block_t*)item->data) || block->delivered || block->too_late){
 				continue;
 			}
 			if(block->data.ptr && block->data.size){
 				_tdav_session_t140_recv_raw(self, block->data.ptr, block->data.size);
 			}
 			((t140_block_t*)block)->delivered = tsk_true;
 		}
 	}
 
 	tsk_list_unlock(self->decoder.blocks);
 	tsk_safeobj_unlock(base);
 
 	return 0;
 }
 
 static int _tdav_session_t140_consume_data(tdav_session_t140_t* self, enum tmedia_t140_data_type_e data_type, const void *data_ptr, tsk_size_t data_size)
 {
 	tdav_session_av_t* base = (tdav_session_av_t*)self;
 	if(!self || !base->consumer){
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	T140_RESIZE_BUFFER(self->decoder.consumer_buffer, self->decoder.consumer_buffer_size, (data_size + 4));
 	if(self->decoder.consumer_buffer){
 		*((int32_t*)self->decoder.consumer_buffer) = data_type;
 		if(data_ptr && data_size){
 			memcpy(&((uint8_t*)self->decoder.consumer_buffer)[4], data_ptr, data_size);
 		}
 		tmedia_consumer_consume(base->consumer, self->decoder.consumer_buffer, (data_size + 4), tsk_null);
 	}
 	return 0;
 }
 
 static int _tdav_session_t140_pred_block_with_transmit_count_more_than_or_equal(const tsk_list_item_t *item, const void *transmit_count)
 {
 	if(item && item->data){
 		t140_block_t *block = item->data;
 		return (block->transmit_count >= *((uint32_t*)transmit_count)) ? 0 : -1;
 	}
 	return -1;
 }
 
 static int _tdav_session_t140_timer_cb(const void* arg, tsk_timer_id_t timer_id)
 {
 	tdav_session_t140_t* t140 = (tdav_session_t140_t*)arg;
 	tdav_session_av_t* base = (tdav_session_av_t*)arg;
 	static const tsk_bool_t called_from_timer = tsk_true;
 	int ret = 0;
 
 	tsk_safeobj_lock(base);
 
 	if(timer_id == t140->decoder.timer_pkt_loss.id){
 		ret = _tdav_session_t140_decode_and_deliver_blocks(t140, called_from_timer);
 	}
 	else if(timer_id == t140->encoder.timer_idle.id){
 		ret = tdav_session_t140_send_data_2(TMEDIA_SESSION(t140), tmedia_t140_data_type_zero_width_no_break_space);
 		t140->encoder.timer_idle.id = tsk_timer_manager_schedule(t140->h_timer, t140->encoder.timer_idle.timeout, _tdav_session_t140_timer_cb, t140);
 	}
 	else if(timer_id == t140->encoder.timer_buffering.id){
 		void* buffer_ptr;
 		tsk_size_t buffer_size;
 		tsk_bool_t was_idle = TSK_TIMER_ID_IS_VALID(t140->encoder.timer_idle.id);
 		tsk_bool_t last_data = tsk_false;
 		if(base->red.codec){
 			// encapsulated red data and schedule timers if required
 			buffer_size = _tdav_session_t140_encap_red(t140, &t140->encoder.red_buffer, &t140->encoder.red_buffer_size, &last_data);
 			buffer_ptr = t140->encoder.red_buffer;
 		}
 		else{
 			// send buffered data
 			buffer_ptr = t140->encoder.buffer;
 			buffer_size = t140->encoder.buffer_idx;
 			last_data = tsk_true;
 			t140->encoder.buffer_idx = 0;
 		}
 		if(buffer_ptr && buffer_size){
 			trtp_manager_send_rtp(base->rtp_manager, buffer_ptr, buffer_size, 300, was_idle, last_data);
 		}
 		t140->encoder.timer_buffering.id = last_data
 			? TSK_INVALID_TIMER_ID 
 			: tsk_timer_manager_schedule(t140->h_timer, t140->encoder.timer_buffering.timeout, _tdav_session_t140_timer_cb, t140);
 	}
 
 	tsk_safeobj_unlock(base);
 
 	return ret;
 }
 
 
 //=================================================================================================
 //	Session Audio Plugin object definition
 //
 /* constructor */
 static tsk_object_t* tdav_session_t140_ctor(tsk_object_t * self, va_list * app)
 {
 	tdav_session_t140_t *t140 = self;
 	if(t140){
 		int ret;
 		tdav_session_av_t *base = TDAV_SESSION_AV(self);
 
 		/* init() base */
 		if((ret = tdav_session_av_init(base, tmedia_t140)) != 0){
 			TSK_DEBUG_ERROR("tdav_session_av_init(t140) failed");
 			return tsk_null;
 		}
 
 		/* init() self */
 		t140->encoder.timer_buffering.id = t140->decoder.timer_pkt_loss.id = TSK_INVALID_TIMER_ID;
 		if(base->producer){
 			tmedia_producer_set_enc_callback(base->producer, _tdav_session_t140_producer_enc_cb, t140);
 		}
 		if(base->consumer){
 		}
 		if(!(t140->encoder.blocks = tsk_list_create())){
 			TSK_DEBUG_ERROR("Failed to create encoder blocks list");
 			return tsk_null;
 		}
 		if(!(t140->decoder.blocks = tsk_list_create())){
 			TSK_DEBUG_ERROR("Failed to create decoder blocks list");
 			return tsk_null;
 		}
 		if(!(t140->h_timer = tsk_timer_manager_create())){
 			TSK_DEBUG_ERROR("Failed to create timer manager");
 			return tsk_null;
 		}
 	}
 	return self;
 }
 /* destructor */
 static tsk_object_t* tdav_session_t140_dtor(tsk_object_t * self)
 { 
 	tdav_session_t140_t *t140 = self;
 	if(t140){
 		tdav_session_t140_stop((tmedia_session_t*)t140);
 		TSK_OBJECT_SAFE_FREE(t140->h_timer);
 		// Do it in this order (deinit self first)
 		TSK_OBJECT_SAFE_FREE(t140->encoder.codec);
 		TSK_FREE(t140->encoder.buffer);
 		TSK_FREE(t140->encoder.red_buffer);
 		TSK_OBJECT_SAFE_FREE(t140->encoder.blocks);
 		TSK_OBJECT_SAFE_FREE(t140->decoder.codec);
 		TSK_FREE(t140->decoder.buffer);
 		TSK_FREE(t140->decoder.consumer_buffer);
 		TSK_OBJECT_SAFE_FREE(t140->decoder.blocks);
 
 		/* deinit base */
 		tdav_session_av_deinit(TDAV_SESSION_AV(self));
 	}
 
 	return self;
 }
 /* object definition */
 static const tsk_object_def_t tdav_session_t140_def_s = 
 {
 	sizeof(tdav_session_t140_t),
 	tdav_session_t140_ctor, 
 	tdav_session_t140_dtor,
 	tmedia_session_cmp, 
 };
 /* plugin definition*/
 static const tmedia_session_plugin_def_t tdav_session_t140_plugin_def_s = 
 {
 	&tdav_session_t140_def_s,
 
 	tmedia_t140,
 	"text",
 
 	tdav_session_t140_set,
 	tdav_session_t140_get,
 	tdav_session_t140_prepare,
 	tdav_session_t140_start,
 	tdav_session_t140_pause,
 	tdav_session_t140_stop,
 
 	/* Audio part */
 	{0},
 
 	tdav_session_t140_get_lo,
 	tdav_session_t140_set_ro,
 
 	/* T.140 part */
 	{
 		tdav_session_t140_set_ondata_cbfn,
 		tdav_session_t140_send_data
 	}
 };
 const tmedia_session_plugin_def_t *tdav_session_t140_plugin_def_t = &tdav_session_t140_plugin_def_s;
 
 //=================================================================================================
 //	T140Block object definition
 //
 static tsk_object_t* t140_block_ctor(tsk_object_t * self, va_list * app)
 {
 	t140_block_t *block = self;
 	if(block){		
 	}
 	return self;
 }
 static tsk_object_t* t140_block_dtor(tsk_object_t * self)
 { 
 	t140_block_t *block = self;
 	if(block){
 		TSK_FREE(block->data.ptr);
 	}
 
 	return self;
 }
 static int t140_block_cmp(const tsk_object_t *_b1, const tsk_object_t *_b2)
 {
 	const t140_block_t *b1 = _b1;
 	const t140_block_t *b2 = _b2;
 
 	if(b1 && b2){
 		return (int)(b1->seq_num - b2->seq_num);
 	}
 	else if(!b1 && !b2) return 0;
 	else return -1;
 }
 static const tsk_object_def_t t140_block_def_s = 
 {
 	sizeof(t140_block_t),
 	t140_block_ctor, 
 	t140_block_dtor,
 	t140_block_cmp, 
 };
 const tsk_object_def_t *t140_block_def_t = &t140_block_def_s;
 
 
 static tsk_bool_t _tdav_session_t140_blocks_has_gap(tdav_session_t140_blocks_L_t* blocks)
 {
 	if(blocks && !TSK_LIST_IS_EMPTY(blocks)){
 		const tsk_list_item_t* item;
 		int32_t last_seq_num = -1;
 		tsk_bool_t has_gap = tsk_false;
 		tsk_list_lock(blocks);
 		tsk_list_foreach(item, blocks){
 			if(last_seq_num > 0 && ((last_seq_num + 1) != T140_BLOCK(item->data)->seq_num)){
 				has_gap = tsk_true;
 			}
 			last_seq_num = T140_BLOCK(item->data)->seq_num;
 			if(has_gap){
 				break;
 			}
 		}
 		tsk_list_unlock(blocks);
 		return has_gap;
 	}
 	return tsk_false;
 }
 
 static tsk_bool_t _tdav_session_t140_blocks_has_seqnum(tdav_session_t140_blocks_L_t* blocks, uint16_t seq_num)
 {
 	if(blocks){
 		const tsk_list_item_t* item;
 		tsk_bool_t has_seqnum = tsk_false;
 		tsk_list_lock(blocks);
 		tsk_list_foreach(item, blocks){
 			if(seq_num == T140_BLOCK(item->data)->seq_num){
 				has_seqnum = tsk_true;
 				break;
 			}
 		}
 		tsk_list_unlock(blocks);
 		return has_seqnum;
 	}
 	return tsk_false;
 }
 
 static int _tdav_session_t140_blocks_add(tdav_session_t140_blocks_L_t* blocks, int64_t* blocks_count, t140_block_t** block, int64_t blocks_count_max)
 {
 	if(blocks && blocks_count && block){
 		tsk_list_lock(blocks);
 		if(tsk_list_push_ascending_data(blocks, (void**)block) == 0){
 			if((*blocks_count = (*blocks_count) + 1) > blocks_count_max){
 				tsk_list_item_t* first_item = tsk_list_pop_first_item(blocks);
 				TSK_OBJECT_SAFE_FREE(first_item);
 			}
 		}
 		tsk_list_unlock(blocks);
 	}
 	return 0;
 }
 
 static t140_block_t* _tdav_session_t140_block_create(uint8_t pt, uint16_t seq_num, uint32_t timestamp, tsk_bool_t delivered, const void* data_ptr, tsk_size_t data_size)
 {
 	void* _data_ptr = tsk_null;
 	t140_block_t* block = tsk_null;
 
 	if(data_ptr && data_size){
 		#if HAVE_CRT //Debug memory
 		if(!(_data_ptr = malloc(data_size))){
 	#else
 		if(!(_data_ptr = tsk_malloc(data_size))){
 	#endif //HAVE_CRT
 		
 			TSK_DEBUG_ERROR("Failed to alloc data with size = %u", data_size);
 			goto bail;
 		}
 		memcpy(_data_ptr, data_ptr, data_size);
 	}
 	if(!(block = tsk_object_new(t140_block_def_t))){
 		TSK_DEBUG_ERROR("Failed to create new block object");
 		goto bail;
 	}
 	if(_data_ptr){
 		block->data.ptr = _data_ptr, _data_ptr = tsk_null;
 		block->data.size = data_size;
 	}
 	block->pt = pt;
 	block->seq_num = seq_num;
 	block->timestamp = timestamp;
 
 bail:
 	TSK_FREE(_data_ptr);
 	return block;
 }