993 lines
32 KiB
C
993 lines
32 KiB
C
/*
|
|
* Copyright 2008-2014 Arsen Chaloyan
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* $Id: rtsp_client.c 2249 2014-11-19 05:26:24Z achaloyan@gmail.com $
|
|
*/
|
|
|
|
#ifdef WIN32
|
|
#pragma warning(disable: 4127)
|
|
#endif
|
|
#include <apr_ring.h>
|
|
#include <apr_hash.h>
|
|
#include "rtsp_client.h"
|
|
#include "rtsp_stream.h"
|
|
#include "apt_poller_task.h"
|
|
#include "apt_text_stream.h"
|
|
#include "apt_pool.h"
|
|
#include "apt_obj_list.h"
|
|
#include "apt_log.h"
|
|
|
|
#define RTSP_STREAM_BUFFER_SIZE 1024
|
|
|
|
typedef struct rtsp_client_connection_t rtsp_client_connection_t;
|
|
|
|
typedef enum {
|
|
TERMINATION_STATE_NONE,
|
|
TERMINATION_STATE_REQUESTED,
|
|
TERMINATION_STATE_INPROGRESS
|
|
} termination_state_e;
|
|
|
|
/** RTSP client */
|
|
struct rtsp_client_t {
|
|
apr_pool_t *pool;
|
|
apt_poller_task_t *task;
|
|
|
|
/** List (ring) of RTSP connections */
|
|
APR_RING_HEAD(rtsp_client_connection_head_t, rtsp_client_connection_t) connection_list;
|
|
|
|
apr_uint32_t request_timeout;
|
|
|
|
void *obj;
|
|
const rtsp_client_vtable_t *vtable;
|
|
};
|
|
|
|
/** RTSP connection */
|
|
struct rtsp_client_connection_t {
|
|
/** Ring entry */
|
|
APR_RING_ENTRY(rtsp_client_connection_t) link;
|
|
|
|
/** Memory pool */
|
|
apr_pool_t *pool;
|
|
/** Connected socket */
|
|
apr_socket_t *sock;
|
|
/** Socket poll descriptor */
|
|
apr_pollfd_t sock_pfd;
|
|
/** String identifier used for traces */
|
|
const char *id;
|
|
/** RTSP client, connection belongs to */
|
|
rtsp_client_t *client;
|
|
|
|
/** Handle table (rtsp_client_session_t*) */
|
|
apr_hash_t *handle_table;
|
|
/** Session table (rtsp_client_session_t*) */
|
|
apr_hash_t *session_table;
|
|
|
|
/** Inprogress request/session queue (rtsp_client_session_t*) */
|
|
apt_obj_list_t *inprogress_request_queue;
|
|
|
|
/** Last CSeq sent */
|
|
apr_size_t last_cseq;
|
|
|
|
char rx_buffer[RTSP_STREAM_BUFFER_SIZE];
|
|
apt_text_stream_t rx_stream;
|
|
rtsp_parser_t *parser;
|
|
|
|
char tx_buffer[RTSP_STREAM_BUFFER_SIZE];
|
|
apt_text_stream_t tx_stream;
|
|
rtsp_generator_t *generator;
|
|
};
|
|
|
|
/** RTSP session */
|
|
struct rtsp_client_session_t {
|
|
apr_pool_t *pool;
|
|
void *obj;
|
|
|
|
/** Connection */
|
|
rtsp_client_connection_t *connection;
|
|
/** Session identifier */
|
|
apt_str_t id;
|
|
|
|
apt_str_t server_ip;
|
|
apr_port_t server_port;
|
|
apt_str_t resource_location;
|
|
|
|
/** In-progress request */
|
|
rtsp_message_t *active_request;
|
|
/** Pending request queue (rtsp_message_t*) */
|
|
apt_obj_list_t *pending_request_queue;
|
|
|
|
/** Timer used for request timeouts */
|
|
apt_timer_t *request_timer;
|
|
|
|
/** Resource table */
|
|
apr_hash_t *resource_table;
|
|
|
|
/** termination state (none -> requested -> terminating) */
|
|
termination_state_e term_state;
|
|
};
|
|
|
|
typedef enum {
|
|
TASK_MSG_SEND_MESSAGE,
|
|
TASK_MSG_TERMINATE_SESSION
|
|
} task_msg_data_type_e;
|
|
|
|
typedef struct task_msg_data_t task_msg_data_t;
|
|
|
|
struct task_msg_data_t {
|
|
task_msg_data_type_e type;
|
|
rtsp_client_t *client;
|
|
rtsp_client_session_t *session;
|
|
rtsp_message_t *message;
|
|
};
|
|
|
|
static apt_bool_t rtsp_client_task_msg_process(apt_task_t *task, apt_task_msg_t *msg);
|
|
|
|
static apt_bool_t rtsp_client_poller_signal_process(void *obj, const apr_pollfd_t *descriptor);
|
|
|
|
static apt_bool_t rtsp_client_message_handler(rtsp_client_connection_t *rtsp_connection, rtsp_message_t *message, apt_message_status_e status);
|
|
static apt_bool_t rtsp_client_message_send(rtsp_client_t *client, rtsp_client_connection_t *connection, rtsp_message_t *message);
|
|
static apt_bool_t rtsp_client_session_message_process(rtsp_client_t *client, rtsp_client_session_t *session, rtsp_message_t *message);
|
|
static apt_bool_t rtsp_client_session_response_process(rtsp_client_t *client, rtsp_client_session_t *session, rtsp_message_t *request, rtsp_message_t *response);
|
|
|
|
static void rtsp_client_timer_proc(apt_timer_t *timer, void *obj);
|
|
|
|
/** Get string identifier */
|
|
static const char* rtsp_client_id_get(const rtsp_client_t *client)
|
|
{
|
|
apt_task_t *task = apt_poller_task_base_get(client->task);
|
|
return apt_task_name_get(task);
|
|
}
|
|
|
|
/** Create RTSP client */
|
|
RTSP_DECLARE(rtsp_client_t*) rtsp_client_create(
|
|
const char *id,
|
|
apr_size_t max_connection_count,
|
|
apr_size_t request_timeout,
|
|
void *obj,
|
|
const rtsp_client_vtable_t *handler,
|
|
apr_pool_t *pool)
|
|
{
|
|
apt_task_t *task;
|
|
apt_task_vtable_t *vtable;
|
|
apt_task_msg_pool_t *msg_pool;
|
|
rtsp_client_t *client;
|
|
|
|
apt_log(APT_LOG_MARK,APT_PRIO_NOTICE,"Create RTSP Client [%s] [%"APR_SIZE_T_FMT"]",
|
|
id, max_connection_count);
|
|
client = apr_palloc(pool,sizeof(rtsp_client_t));
|
|
client->pool = pool;
|
|
client->obj = obj;
|
|
client->vtable = handler;
|
|
|
|
msg_pool = apt_task_msg_pool_create_dynamic(sizeof(task_msg_data_t),pool);
|
|
|
|
client->task = apt_poller_task_create(
|
|
max_connection_count,
|
|
rtsp_client_poller_signal_process,
|
|
client,
|
|
msg_pool,
|
|
pool);
|
|
if(!client->task) {
|
|
return NULL;
|
|
}
|
|
|
|
task = apt_poller_task_base_get(client->task);
|
|
if(task) {
|
|
apt_task_name_set(task,id);
|
|
}
|
|
|
|
vtable = apt_poller_task_vtable_get(client->task);
|
|
if(vtable) {
|
|
vtable->process_msg = rtsp_client_task_msg_process;
|
|
}
|
|
|
|
APR_RING_INIT(&client->connection_list, rtsp_client_connection_t, link);
|
|
client->request_timeout = (apr_uint32_t)request_timeout;
|
|
return client;
|
|
}
|
|
|
|
/** Destroy RTSP client */
|
|
RTSP_DECLARE(apt_bool_t) rtsp_client_destroy(rtsp_client_t *client)
|
|
{
|
|
apt_log(APT_LOG_MARK,APT_PRIO_NOTICE,"Destroy RTSP Client [%s]",
|
|
rtsp_client_id_get(client));
|
|
return apt_poller_task_destroy(client->task);
|
|
}
|
|
|
|
/** Start connection agent */
|
|
RTSP_DECLARE(apt_bool_t) rtsp_client_start(rtsp_client_t *client)
|
|
{
|
|
return apt_poller_task_start(client->task);
|
|
}
|
|
|
|
/** Terminate connection agent */
|
|
RTSP_DECLARE(apt_bool_t) rtsp_client_terminate(rtsp_client_t *client)
|
|
{
|
|
return apt_poller_task_terminate(client->task);
|
|
}
|
|
|
|
/** Get task */
|
|
RTSP_DECLARE(apt_task_t*) rtsp_client_task_get(const rtsp_client_t *client)
|
|
{
|
|
return apt_poller_task_base_get(client->task);
|
|
}
|
|
|
|
/** Get external object */
|
|
RTSP_DECLARE(void*) rtsp_client_object_get(const rtsp_client_t *client)
|
|
{
|
|
return client->obj;
|
|
}
|
|
|
|
/** Get object associated with the session */
|
|
RTSP_DECLARE(void*) rtsp_client_session_object_get(const rtsp_client_session_t *session)
|
|
{
|
|
return session->obj;
|
|
}
|
|
|
|
/** Set object associated with the session */
|
|
RTSP_DECLARE(void) rtsp_client_session_object_set(rtsp_client_session_t *session, void *obj)
|
|
{
|
|
session->obj = obj;
|
|
}
|
|
|
|
/** Get the session identifier */
|
|
RTSP_DECLARE(const apt_str_t*) rtsp_client_session_id_get(const rtsp_client_session_t *session)
|
|
{
|
|
return &session->id;
|
|
}
|
|
|
|
/** Signal task message */
|
|
static apt_bool_t rtsp_client_control_message_signal(
|
|
task_msg_data_type_e type,
|
|
rtsp_client_t *client,
|
|
rtsp_client_session_t *session,
|
|
rtsp_message_t *message)
|
|
{
|
|
apt_task_t *task = apt_poller_task_base_get(client->task);
|
|
apt_task_msg_t *task_msg = apt_task_msg_get(task);
|
|
if(task_msg) {
|
|
task_msg_data_t *data = (task_msg_data_t*)task_msg->data;
|
|
data->type = type;
|
|
data->client = client;
|
|
data->session = session;
|
|
data->message = message;
|
|
apt_task_msg_signal(task,task_msg);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/** Create RTSP session handle */
|
|
RTSP_DECLARE(rtsp_client_session_t*) rtsp_client_session_create(
|
|
rtsp_client_t *client,
|
|
const char *server_ip,
|
|
apr_port_t server_port,
|
|
const char *resource_location)
|
|
{
|
|
rtsp_client_session_t *session;
|
|
apr_pool_t *pool = apt_pool_create();
|
|
session = apr_palloc(pool,sizeof(rtsp_client_session_t));
|
|
session->pool = pool;
|
|
session->obj = NULL;
|
|
session->connection = NULL;
|
|
session->active_request = NULL;
|
|
session->pending_request_queue = apt_list_create(pool);
|
|
session->request_timer = apt_poller_task_timer_create(
|
|
client->task,
|
|
rtsp_client_timer_proc,
|
|
session,
|
|
pool);
|
|
session->resource_table = apr_hash_make(pool);
|
|
session->term_state = TERMINATION_STATE_NONE;
|
|
|
|
apt_string_assign(&session->server_ip,server_ip,pool);
|
|
session->server_port = server_port;
|
|
apt_string_assign(&session->resource_location,resource_location,pool);
|
|
apt_string_reset(&session->id);
|
|
apt_log(APT_LOG_MARK,APT_PRIO_NOTICE,"Create RTSP Handle "APT_PTR_FMT,session);
|
|
return session;
|
|
}
|
|
|
|
/** Destroy RTSP session handle */
|
|
RTSP_DECLARE(void) rtsp_client_session_destroy(rtsp_client_session_t *session)
|
|
{
|
|
apt_log(APT_LOG_MARK,APT_PRIO_NOTICE,"Destroy RTSP Handle "APT_PTR_FMT,session);
|
|
if(session && session->pool) {
|
|
apr_pool_destroy(session->pool);
|
|
}
|
|
}
|
|
|
|
/** Signal terminate request */
|
|
RTSP_DECLARE(apt_bool_t) rtsp_client_session_terminate(rtsp_client_t *client, rtsp_client_session_t *session)
|
|
{
|
|
return rtsp_client_control_message_signal(TASK_MSG_TERMINATE_SESSION,client,session,NULL);
|
|
}
|
|
|
|
/** Signal RTSP message */
|
|
RTSP_DECLARE(apt_bool_t) rtsp_client_session_request(rtsp_client_t *client, rtsp_client_session_t *session, rtsp_message_t *message)
|
|
{
|
|
return rtsp_client_control_message_signal(TASK_MSG_SEND_MESSAGE,client,session,message);
|
|
}
|
|
|
|
|
|
/** Create connection */
|
|
static apt_bool_t rtsp_client_connect(rtsp_client_t *client, rtsp_client_connection_t *connection, const char *ip, apr_port_t port)
|
|
{
|
|
char *local_ip = NULL;
|
|
char *remote_ip = NULL;
|
|
apr_sockaddr_t *l_sockaddr = NULL;
|
|
apr_sockaddr_t *r_sockaddr = NULL;
|
|
|
|
if(apr_sockaddr_info_get(&r_sockaddr,ip,APR_INET,port,0,connection->pool) != APR_SUCCESS) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(apr_socket_create(&connection->sock,r_sockaddr->family,SOCK_STREAM,APR_PROTO_TCP,connection->pool) != APR_SUCCESS) {
|
|
return FALSE;
|
|
}
|
|
|
|
apr_socket_opt_set(connection->sock, APR_SO_NONBLOCK, 0);
|
|
apr_socket_timeout_set(connection->sock, -1);
|
|
apr_socket_opt_set(connection->sock, APR_SO_REUSEADDR, 1);
|
|
|
|
if(apr_socket_connect(connection->sock,r_sockaddr) != APR_SUCCESS) {
|
|
apr_socket_close(connection->sock);
|
|
connection->sock = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
if(apr_socket_addr_get(&l_sockaddr,APR_LOCAL,connection->sock) != APR_SUCCESS) {
|
|
apr_socket_close(connection->sock);
|
|
connection->sock = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
apr_sockaddr_ip_get(&local_ip,l_sockaddr);
|
|
apr_sockaddr_ip_get(&remote_ip,r_sockaddr);
|
|
connection->id = apr_psprintf(connection->pool,"%s:%hu <-> %s:%hu",
|
|
local_ip,l_sockaddr->port,
|
|
remote_ip,r_sockaddr->port);
|
|
|
|
memset(&connection->sock_pfd,0,sizeof(apr_pollfd_t));
|
|
connection->sock_pfd.desc_type = APR_POLL_SOCKET;
|
|
connection->sock_pfd.reqevents = APR_POLLIN;
|
|
connection->sock_pfd.desc.s = connection->sock;
|
|
connection->sock_pfd.client_data = connection;
|
|
if(apt_poller_task_descriptor_add(client->task,&connection->sock_pfd) != TRUE) {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_WARNING,"Failed to Add to Pollset %s",connection->id);
|
|
apr_socket_close(connection->sock);
|
|
connection->sock = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
apt_log(APT_LOG_MARK,APT_PRIO_NOTICE,"Established RTSP Connection %s",connection->id);
|
|
return TRUE;
|
|
}
|
|
|
|
/** Close connection */
|
|
static apt_bool_t rtsp_client_connection_close(rtsp_client_t *client, rtsp_client_connection_t *connection)
|
|
{
|
|
if(connection->sock) {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Close RTSP Connection %s",connection->id);
|
|
apt_poller_task_descriptor_remove(client->task,&connection->sock_pfd);
|
|
apr_socket_close(connection->sock);
|
|
connection->sock = NULL;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Create RTSP connection */
|
|
static apt_bool_t rtsp_client_connection_create(rtsp_client_t *client, rtsp_client_session_t *session)
|
|
{
|
|
rtsp_client_connection_t *rtsp_connection;
|
|
apr_pool_t *pool = apt_pool_create();
|
|
if(!pool) {
|
|
return FALSE;
|
|
}
|
|
|
|
rtsp_connection = apr_palloc(pool,sizeof(rtsp_client_connection_t));
|
|
rtsp_connection->pool = pool;
|
|
rtsp_connection->sock = NULL;
|
|
APR_RING_ELEM_INIT(rtsp_connection,link);
|
|
|
|
if(rtsp_client_connect(client,rtsp_connection,session->server_ip.buf,session->server_port) == FALSE) {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_WARNING,"Failed to Connect to RTSP Server %s:%hu",
|
|
session->server_ip.buf,session->server_port);
|
|
apr_pool_destroy(pool);
|
|
return FALSE;
|
|
}
|
|
rtsp_connection->handle_table = apr_hash_make(pool);
|
|
rtsp_connection->session_table = apr_hash_make(pool);
|
|
rtsp_connection->inprogress_request_queue = apt_list_create(pool);
|
|
apt_text_stream_init(&rtsp_connection->rx_stream,rtsp_connection->rx_buffer,sizeof(rtsp_connection->rx_buffer)-1);
|
|
apt_text_stream_init(&rtsp_connection->tx_stream,rtsp_connection->tx_buffer,sizeof(rtsp_connection->tx_buffer)-1);
|
|
rtsp_connection->parser = rtsp_parser_create(pool);
|
|
rtsp_connection->generator = rtsp_generator_create(pool);
|
|
rtsp_connection->last_cseq = 0;
|
|
|
|
rtsp_connection->client = client;
|
|
APR_RING_INSERT_TAIL(&client->connection_list,rtsp_connection,rtsp_client_connection_t,link);
|
|
session->connection = rtsp_connection;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Destroy RTSP connection */
|
|
static apt_bool_t rtsp_client_connection_destroy(rtsp_client_connection_t *rtsp_connection)
|
|
{
|
|
rtsp_client_t *client = rtsp_connection->client;
|
|
APR_RING_REMOVE(rtsp_connection,link);
|
|
rtsp_client_connection_close(client,rtsp_connection);
|
|
apt_log(APT_LOG_MARK,APT_PRIO_NOTICE,"Destroy RTSP Connection %s",rtsp_connection->id);
|
|
apr_pool_destroy(rtsp_connection->pool);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Respond to session termination request */
|
|
static apt_bool_t rtsp_client_session_terminate_respond(rtsp_client_t *client, rtsp_client_session_t *session)
|
|
{
|
|
rtsp_client_connection_t *rtsp_connection = session->connection;
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Remove RTSP Handle "APT_PTR_FMT,session);
|
|
apr_hash_set(rtsp_connection->handle_table,session,sizeof(void*),NULL);
|
|
|
|
session->term_state = TERMINATION_STATE_NONE;
|
|
client->vtable->on_session_terminate_response(client,session);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Teardown session resources */
|
|
static apt_bool_t rtsp_client_session_resources_teardown(rtsp_client_t *client, rtsp_client_session_t *session)
|
|
{
|
|
void *val;
|
|
rtsp_message_t *setup_request;
|
|
rtsp_message_t *teardown_request;
|
|
apr_hash_index_t *it;
|
|
|
|
/* set termination state to in-progress and teardown remaining resources */
|
|
session->term_state = TERMINATION_STATE_INPROGRESS;
|
|
it = apr_hash_first(session->pool,session->resource_table);
|
|
for(; it; it = apr_hash_next(it)) {
|
|
apr_hash_this(it,NULL,NULL,&val);
|
|
setup_request = val;
|
|
if(!setup_request) continue;
|
|
|
|
teardown_request = rtsp_request_create(session->pool);
|
|
teardown_request->start_line.common.request_line.resource_name = setup_request->start_line.common.request_line.resource_name;
|
|
teardown_request->start_line.common.request_line.method_id = RTSP_METHOD_TEARDOWN;
|
|
rtsp_client_session_message_process(client,session,teardown_request);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Process session termination request */
|
|
static apt_bool_t rtsp_client_session_terminate_process(rtsp_client_t *client, rtsp_client_session_t *session)
|
|
{
|
|
rtsp_client_connection_t *rtsp_connection = session->connection;
|
|
if(!rtsp_connection) {
|
|
client->vtable->on_session_terminate_response(client,session);
|
|
return FALSE;
|
|
}
|
|
|
|
if(session->active_request) {
|
|
/* set termination state to requested */
|
|
session->term_state = TERMINATION_STATE_REQUESTED;
|
|
}
|
|
else {
|
|
rtsp_client_session_resources_teardown(client,session);
|
|
|
|
/* respond immediately if no resources left */
|
|
if(apr_hash_count(session->resource_table) == 0) {
|
|
rtsp_client_session_terminate_respond(client,session);
|
|
|
|
if(apr_hash_count(rtsp_connection->handle_table) == 0) {
|
|
rtsp_client_connection_destroy(rtsp_connection);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static apt_bool_t rtsp_client_session_url_generate(rtsp_client_session_t *session, rtsp_message_t *message)
|
|
{
|
|
apt_str_t *url = &message->start_line.common.request_line.url;
|
|
if(session->resource_location.length) {
|
|
url->buf = apr_psprintf(message->pool,"rtsp://%s:%hu/%s/%s",
|
|
session->server_ip.buf,
|
|
session->server_port,
|
|
session->resource_location.buf,
|
|
message->start_line.common.request_line.resource_name);
|
|
}
|
|
else {
|
|
url->buf = apr_psprintf(message->pool,"rtsp://%s:%hu/%s",
|
|
session->server_ip.buf,
|
|
session->server_port,
|
|
message->start_line.common.request_line.resource_name);
|
|
}
|
|
url->length = strlen(url->buf);
|
|
return TRUE;
|
|
}
|
|
|
|
static apt_bool_t rtsp_client_request_push(rtsp_client_connection_t *rtsp_connection, rtsp_client_session_t *session, rtsp_message_t *message)
|
|
{
|
|
/* add request to inprogress request queue */
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Push RTSP Request to In-Progress Queue "APT_PTRSID_FMT" CSeq:%"APR_SIZE_T_FMT,
|
|
session,
|
|
message->header.session_id.buf ? message->header.session_id.buf : "new",
|
|
message->header.cseq);
|
|
apt_list_push_back(rtsp_connection->inprogress_request_queue,session,session->pool);
|
|
session->active_request = message;
|
|
if(rtsp_connection->client->request_timeout) {
|
|
apt_timer_set(session->request_timer,rtsp_connection->client->request_timeout);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static apt_bool_t rtsp_client_request_pop(rtsp_client_connection_t *rtsp_connection, rtsp_message_t *response, rtsp_message_t **ret_request, rtsp_client_session_t **ret_session)
|
|
{
|
|
rtsp_client_session_t *session;
|
|
apt_list_elem_t *elem = apt_list_first_elem_get(rtsp_connection->inprogress_request_queue);
|
|
while(elem) {
|
|
session = apt_list_elem_object_get(elem);
|
|
if(session->active_request && session->active_request->header.cseq == response->header.cseq) {
|
|
if(ret_session) {
|
|
*ret_session = session;
|
|
}
|
|
if(ret_request) {
|
|
*ret_request = session->active_request;
|
|
}
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Pop In-Progress RTSP Request "APT_PTR_FMT" CSeq:%"APR_SIZE_T_FMT,
|
|
session,
|
|
response->header.cseq);
|
|
apt_list_elem_remove(rtsp_connection->inprogress_request_queue,elem);
|
|
session->active_request = NULL;
|
|
apt_timer_kill(session->request_timer);
|
|
return TRUE;
|
|
}
|
|
elem = apt_list_next_elem_get(rtsp_connection->inprogress_request_queue,elem);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Process outgoing RTSP request */
|
|
static apt_bool_t rtsp_client_session_request_process(rtsp_client_t *client, rtsp_client_session_t *session, rtsp_message_t *message)
|
|
{
|
|
if(!session->connection) {
|
|
/* create RTSP connection */
|
|
if(rtsp_client_connection_create(client,session) == FALSE) {
|
|
/* respond with error */
|
|
return FALSE;
|
|
}
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Add RTSP Handle "APT_PTR_FMT,session);
|
|
apr_hash_set(session->connection->handle_table,session,sizeof(void*),session);
|
|
}
|
|
|
|
rtsp_client_session_url_generate(session,message);
|
|
|
|
if(session->id.length) {
|
|
message->header.session_id = session->id;
|
|
rtsp_header_property_add(&message->header,RTSP_HEADER_FIELD_SESSION_ID,message->pool);
|
|
}
|
|
|
|
message->header.cseq = ++session->connection->last_cseq;
|
|
rtsp_header_property_add(&message->header,RTSP_HEADER_FIELD_CSEQ,message->pool);
|
|
|
|
if(rtsp_client_message_send(client,session->connection,message) == FALSE) {
|
|
/* respond with error */
|
|
return FALSE;
|
|
}
|
|
|
|
return rtsp_client_request_push(session->connection,session,message);
|
|
}
|
|
|
|
/* Process pending RTSP requests */
|
|
static apt_bool_t rtsp_client_session_pending_requests_process(rtsp_client_t *client, rtsp_client_session_t *session)
|
|
{
|
|
rtsp_message_t *request = apt_list_pop_front(session->pending_request_queue);
|
|
if(!request) {
|
|
/* pending queue is empty, no in-progress request */
|
|
return FALSE;
|
|
}
|
|
|
|
/* process pending request; get the next one, if current is failed */
|
|
do {
|
|
rtsp_message_t *response;
|
|
if(rtsp_client_session_request_process(client,session,request) == TRUE) {
|
|
return TRUE;
|
|
}
|
|
|
|
/* respond with error */
|
|
response = rtsp_response_create(
|
|
request,
|
|
RTSP_STATUS_CODE_INTERNAL_SERVER_ERROR,
|
|
RTSP_REASON_PHRASE_INTERNAL_SERVER_ERROR,
|
|
session->pool);
|
|
rtsp_client_session_response_process(client,session,request,response);
|
|
|
|
/* process the next pending request / if any */
|
|
request = apt_list_pop_front(session->pending_request_queue);
|
|
}
|
|
while(request);
|
|
|
|
/* no in-progress request */
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Process outgoing RTSP message */
|
|
static apt_bool_t rtsp_client_session_message_process(rtsp_client_t *client, rtsp_client_session_t *session, rtsp_message_t *message)
|
|
{
|
|
if(session->active_request) {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_DEBUG,"Push RTSP Request to Pending Queue "APT_PTR_FMT,session);
|
|
apt_list_push_back(session->pending_request_queue,message,message->pool);
|
|
return TRUE;
|
|
}
|
|
|
|
if(rtsp_client_session_request_process(client,session,message) == FALSE) {
|
|
/* respond with error in case request cannot be processed */
|
|
rtsp_message_t *response = rtsp_response_create(
|
|
message,
|
|
RTSP_STATUS_CODE_INTERNAL_SERVER_ERROR,
|
|
RTSP_REASON_PHRASE_INTERNAL_SERVER_ERROR,
|
|
session->pool);
|
|
rtsp_client_session_response_process(client,session,message,response);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Process incoming RTSP event (request) */
|
|
static apt_bool_t rtsp_client_session_event_process(rtsp_client_t *client, rtsp_client_connection_t *rtsp_connection, rtsp_message_t *message)
|
|
{
|
|
rtsp_message_t *response = NULL;
|
|
rtsp_client_session_t *session = NULL;
|
|
if(rtsp_header_property_check(&message->header,RTSP_HEADER_FIELD_SESSION_ID) == TRUE) {
|
|
/* find existing session */
|
|
session = apr_hash_get(
|
|
rtsp_connection->session_table,
|
|
message->header.session_id.buf,
|
|
message->header.session_id.length);
|
|
}
|
|
|
|
if(session) {
|
|
response = rtsp_response_create(message,RTSP_STATUS_CODE_OK,RTSP_REASON_PHRASE_OK,message->pool);
|
|
if(rtsp_header_property_check(&message->header,RTSP_HEADER_FIELD_SESSION_ID) == TRUE) {
|
|
response->header.session_id = message->header.session_id;
|
|
rtsp_header_property_add(&response->header,RTSP_HEADER_FIELD_SESSION_ID,message->pool);
|
|
}
|
|
client->vtable->on_session_event(client,session,message);
|
|
}
|
|
else {
|
|
response = rtsp_response_create(message,RTSP_STATUS_CODE_NOT_FOUND,RTSP_REASON_PHRASE_NOT_FOUND,message->pool);
|
|
}
|
|
|
|
return rtsp_client_message_send(client,rtsp_connection,response);
|
|
}
|
|
|
|
/* Process incoming RTSP response */
|
|
static apt_bool_t rtsp_client_session_response_process(rtsp_client_t *client, rtsp_client_session_t *session, rtsp_message_t *request, rtsp_message_t *response)
|
|
{
|
|
const char *resource_name;
|
|
if(request->start_line.common.request_line.method_id == RTSP_METHOD_SETUP &&
|
|
response->start_line.common.status_line.status_code == RTSP_STATUS_CODE_OK) {
|
|
|
|
if(apr_hash_count(session->resource_table) == 0) {
|
|
if(rtsp_header_property_check(&response->header,RTSP_HEADER_FIELD_SESSION_ID) == TRUE) {
|
|
session->id = response->header.session_id;
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Add RTSP Session "APT_PTRSID_FMT,
|
|
session,
|
|
session->id.buf);
|
|
apr_hash_set(session->connection->session_table,session->id.buf,session->id.length,session);
|
|
}
|
|
}
|
|
|
|
/* add resource */
|
|
resource_name = request->start_line.common.request_line.resource_name;
|
|
apr_hash_set(session->resource_table,resource_name,APR_HASH_KEY_STRING,request);
|
|
}
|
|
else if(request->start_line.common.request_line.method_id == RTSP_METHOD_TEARDOWN) {
|
|
/* remove resource */
|
|
resource_name = request->start_line.common.request_line.resource_name;
|
|
apr_hash_set(session->resource_table,resource_name,APR_HASH_KEY_STRING,NULL);
|
|
|
|
if(apr_hash_count(session->resource_table) == 0) {
|
|
if(session->connection) {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Remove RTSP Session "APT_PTRSID_FMT,
|
|
session,
|
|
session->id.buf);
|
|
apr_hash_set(session->connection->session_table,session->id.buf,session->id.length,NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(session->term_state != TERMINATION_STATE_INPROGRESS) {
|
|
client->vtable->on_session_response(client,session,request,response);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Raise RTSP session terminate event */
|
|
static apt_bool_t rtsp_client_session_terminate_raise(rtsp_client_t *client, rtsp_client_session_t *session)
|
|
{
|
|
rtsp_message_t *request;
|
|
rtsp_message_t *response;
|
|
|
|
/* cancel pending requests */
|
|
do {
|
|
request = apt_list_pop_front(session->pending_request_queue);
|
|
if(request) {
|
|
response = rtsp_response_create(
|
|
session->active_request,
|
|
RTSP_STATUS_CODE_INTERNAL_SERVER_ERROR,
|
|
RTSP_REASON_PHRASE_INTERNAL_SERVER_ERROR,
|
|
session->pool);
|
|
rtsp_client_session_response_process(client,session,request,response);
|
|
}
|
|
}
|
|
while(request);
|
|
|
|
if(session->term_state == TERMINATION_STATE_NONE) {
|
|
client->vtable->on_session_terminate_event(client,session);
|
|
}
|
|
else {
|
|
rtsp_client_session_terminate_respond(client,session);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Cancel RTSP request */
|
|
static apt_bool_t rtsp_client_request_cancel(rtsp_client_t *client, rtsp_client_session_t *session, rtsp_status_code_e status_code, rtsp_reason_phrase_e reason)
|
|
{
|
|
rtsp_message_t *request;
|
|
rtsp_message_t *response;
|
|
if(!session->active_request) {
|
|
return FALSE;
|
|
}
|
|
|
|
request = session->active_request;
|
|
response = rtsp_response_create(
|
|
request,
|
|
status_code,
|
|
reason,
|
|
session->pool);
|
|
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Cancel RTSP Request "APT_PTRSID_FMT" CSeq:%"APR_SIZE_T_FMT" [%d]",
|
|
session,
|
|
request->header.session_id.buf ? request->header.session_id.buf : "new",
|
|
request->header.cseq,
|
|
status_code);
|
|
|
|
return rtsp_client_message_handler(session->connection, response, APT_MESSAGE_STATUS_COMPLETE);
|
|
}
|
|
|
|
/* RTSP connection disconnected */
|
|
static apt_bool_t rtsp_client_on_disconnect(rtsp_client_t *client, rtsp_client_connection_t *rtsp_connection)
|
|
{
|
|
rtsp_client_session_t *session;
|
|
apr_size_t remaining_handles;
|
|
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"RTSP Peer Disconnected %s", rtsp_connection->id);
|
|
rtsp_client_connection_close(client,rtsp_connection);
|
|
|
|
/* Cancel in-progreess requests */
|
|
do {
|
|
session = apt_list_pop_front(rtsp_connection->inprogress_request_queue);
|
|
if(session) {
|
|
if(rtsp_client_request_cancel(
|
|
client,
|
|
session,
|
|
RTSP_STATUS_CODE_INTERNAL_SERVER_ERROR,
|
|
RTSP_REASON_PHRASE_INTERNAL_SERVER_ERROR) == TRUE) {
|
|
apt_timer_kill(session->request_timer);
|
|
}
|
|
}
|
|
}
|
|
while(session);
|
|
|
|
/* Walk through RTSP handles and raise termination event for them */
|
|
remaining_handles = apr_hash_count(rtsp_connection->handle_table);
|
|
if(remaining_handles) {
|
|
void *val;
|
|
apr_hash_index_t *it;
|
|
apt_log(APT_LOG_MARK,APT_PRIO_NOTICE,"Terminate Remaining RTSP Handles [%"APR_SIZE_T_FMT"]",remaining_handles);
|
|
it = apr_hash_first(rtsp_connection->pool,rtsp_connection->session_table);
|
|
for(; it; it = apr_hash_next(it)) {
|
|
apr_hash_this(it,NULL,NULL,&val);
|
|
session = val;
|
|
if(session) {
|
|
rtsp_client_session_terminate_raise(client,session);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Send RTSP message through RTSP connection */
|
|
static apt_bool_t rtsp_client_message_send(rtsp_client_t *client, rtsp_client_connection_t *rtsp_connection, rtsp_message_t *message)
|
|
{
|
|
apt_bool_t status = FALSE;
|
|
apt_text_stream_t *stream;
|
|
apt_message_status_e result;
|
|
|
|
if(!rtsp_connection || !rtsp_connection->sock) {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_WARNING,"No RTSP Connection");
|
|
return FALSE;
|
|
}
|
|
stream = &rtsp_connection->tx_stream;
|
|
|
|
do {
|
|
stream->text.length = sizeof(rtsp_connection->tx_buffer)-1;
|
|
apt_text_stream_reset(stream);
|
|
result = rtsp_generator_run(rtsp_connection->generator,message,stream);
|
|
if(result != APT_MESSAGE_STATUS_INVALID) {
|
|
stream->text.length = stream->pos - stream->text.buf;
|
|
*stream->pos = '\0';
|
|
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Send RTSP Data %s [%"APR_SIZE_T_FMT" bytes]\n%s",
|
|
rtsp_connection->id,
|
|
stream->text.length,
|
|
stream->text.buf);
|
|
if(apr_socket_send(rtsp_connection->sock,stream->text.buf,&stream->text.length) == APR_SUCCESS) {
|
|
status = TRUE;
|
|
}
|
|
else {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_WARNING,"Failed to Send RTSP Data");
|
|
}
|
|
}
|
|
else {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_WARNING,"Failed to Generate RTSP Data");
|
|
}
|
|
}
|
|
while(result == APT_MESSAGE_STATUS_INCOMPLETE);
|
|
|
|
return status;
|
|
}
|
|
|
|
/** Return TRUE to proceed with the next message in the stream (if any) */
|
|
static apt_bool_t rtsp_client_message_handler(rtsp_client_connection_t *rtsp_connection, rtsp_message_t *message, apt_message_status_e status)
|
|
{
|
|
if(status != APT_MESSAGE_STATUS_COMPLETE) {
|
|
/* message is not completely parsed, nothing to do */
|
|
return TRUE;
|
|
}
|
|
/* process parsed message */
|
|
if(message->start_line.message_type == RTSP_MESSAGE_TYPE_RESPONSE) {
|
|
rtsp_message_t *request;
|
|
rtsp_client_session_t *session;
|
|
/* at first, pop in-progress request/session */
|
|
if(rtsp_client_request_pop(rtsp_connection,message,&request,&session) == FALSE) {
|
|
apt_log(APT_LOG_MARK,APT_PRIO_WARNING,"Unexpected RTSP Response Received CSeq:%"APR_SIZE_T_FMT,
|
|
message->header.cseq);
|
|
return TRUE;
|
|
}
|
|
|
|
/* next, process session response */
|
|
rtsp_client_session_response_process(rtsp_connection->client,session,request,message);
|
|
|
|
/* process session pending requests */
|
|
if(rtsp_client_session_pending_requests_process(rtsp_connection->client,session) == FALSE) {
|
|
/* no in-progress request, check the termination state now */
|
|
if(session->term_state != TERMINATION_STATE_NONE) {
|
|
if(session->term_state == TERMINATION_STATE_REQUESTED) {
|
|
rtsp_client_session_resources_teardown(rtsp_connection->client,session);
|
|
}
|
|
|
|
/* respond if no resources left */
|
|
if(apr_hash_count(session->resource_table) == 0) {
|
|
rtsp_client_session_terminate_respond(rtsp_connection->client,session);
|
|
|
|
if(apr_hash_count(rtsp_connection->handle_table) == 0) {
|
|
rtsp_client_connection_destroy(rtsp_connection);
|
|
/* return FALSE to indicate connection has been destroyed */
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(message->start_line.message_type == RTSP_MESSAGE_TYPE_REQUEST) {
|
|
rtsp_client_session_event_process(rtsp_connection->client,rtsp_connection,message);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Receive RTSP message through RTSP connection */
|
|
static apt_bool_t rtsp_client_poller_signal_process(void *obj, const apr_pollfd_t *descriptor)
|
|
{
|
|
rtsp_client_t *client = obj;
|
|
rtsp_client_connection_t *rtsp_connection = descriptor->client_data;
|
|
apr_status_t status;
|
|
apr_size_t offset;
|
|
apr_size_t length;
|
|
apt_text_stream_t *stream;
|
|
rtsp_message_t *message;
|
|
apt_message_status_e msg_status;
|
|
|
|
if(!rtsp_connection || !rtsp_connection->sock) {
|
|
return FALSE;
|
|
}
|
|
stream = &rtsp_connection->rx_stream;
|
|
|
|
/* calculate offset remaining from the previous receive / if any */
|
|
offset = stream->pos - stream->text.buf;
|
|
/* calculate available length */
|
|
length = sizeof(rtsp_connection->rx_buffer) - 1 - offset;
|
|
|
|
status = apr_socket_recv(rtsp_connection->sock,stream->pos,&length);
|
|
if(status == APR_EOF || length == 0) {
|
|
return rtsp_client_on_disconnect(client,rtsp_connection);
|
|
}
|
|
|
|
/* calculate actual length of the stream */
|
|
stream->text.length = offset + length;
|
|
stream->pos[length] = '\0';
|
|
apt_log(APT_LOG_MARK,APT_PRIO_INFO,"Receive RTSP Data %s [%"APR_SIZE_T_FMT" bytes]\n%s",
|
|
rtsp_connection->id,
|
|
length,
|
|
stream->pos);
|
|
|
|
/* reset pos */
|
|
apt_text_stream_reset(stream);
|
|
|
|
do {
|
|
msg_status = rtsp_parser_run(rtsp_connection->parser,stream,&message);
|
|
if(rtsp_client_message_handler(rtsp_connection,message,msg_status) == FALSE) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
while(apt_text_is_eos(stream) == FALSE);
|
|
|
|
/* scroll remaining stream */
|
|
apt_text_stream_scroll(stream);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Process task message */
|
|
static apt_bool_t rtsp_client_task_msg_process(apt_task_t *task, apt_task_msg_t *task_msg)
|
|
{
|
|
apt_poller_task_t *poller_task = apt_task_object_get(task);
|
|
rtsp_client_t *client = apt_poller_task_object_get(poller_task);
|
|
|
|
task_msg_data_t *data = (task_msg_data_t*) task_msg->data;
|
|
switch(data->type) {
|
|
case TASK_MSG_SEND_MESSAGE:
|
|
rtsp_client_session_message_process(client,data->session,data->message);
|
|
break;
|
|
case TASK_MSG_TERMINATE_SESSION:
|
|
rtsp_client_session_terminate_process(client,data->session);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Timer callback */
|
|
static void rtsp_client_timer_proc(apt_timer_t *timer, void *obj)
|
|
{
|
|
rtsp_client_session_t *session = obj;
|
|
if(!session || !session->connection || !session->connection->client) {
|
|
return;
|
|
}
|
|
|
|
if(session->request_timer == timer) {
|
|
rtsp_client_request_cancel(
|
|
session->connection->client,
|
|
session,
|
|
RTSP_STATUS_CODE_REQUEST_TIMEOUT,
|
|
RTSP_REASON_PHRASE_REQUEST_TIMEOUT);
|
|
}
|
|
}
|