blob: 975b474c028d28322c9c49d7d97f0e0218e311d5 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2026
* All rights reserved
*
* This file is part of GPAC / IETF RTP/RTSP/SDP sub-project
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/internal/ietf_dev.h>
#include <gpac/base_coding.h>
#ifndef GPAC_DISABLE_STREAMING
static GF_Err gf_rtsp_write_sock(GF_RTSPSession *sess, u8 *data, u32 len);
static GF_Err gf_rstp_flush_buffer(GF_RTSPSession *sess);
static GF_Err gf_rtsp_http_tunnel_setup(GF_RTSPSession *sess);
#ifdef GPAC_HAS_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/rand.h>
void *gf_ssl_new(void *ssl_server_ctx, GF_Socket *client_sock, GF_Err *e);
void gf_ssl_del(void *ssl);
Bool gf_ssl_check_cert(SSL *ssl, const char *server_name);
#endif
/*default packet size to use when storing incomplete packets*/
#define RTSP_PCK_SIZE 1000
GF_Err RTSP_UnpackURL(char *sURL, char Server[1024], u16 *Port, char Service[1024], Bool *useTCP, char User[1024], char Pass[1024])
{
char schema[10], *test, text[1024], *retest, *sep, *service_start;
u32 i, len;
Bool is_ipv6;
if (!sURL) return GF_BAD_PARAM;
Server[0] = 0;
Service[0] = 0;
User[0] = 0;
Pass[0] = 0;
*Port = 0;
*useTCP = GF_FALSE;
if (!strchr(sURL, ':')) return GF_BAD_PARAM;
sep = strchr(sURL, '?');
if (sep) sep[0] = 0;
//extract the schema
i = 0;
while (i<=strlen(sURL)) {
if (i==10) return GF_BAD_PARAM;
if (sURL[i] == ':') goto found;
schema[i] = sURL[i];
i += 1;
}
if (sep) sep[0] = '?';
return GF_BAD_PARAM;
found:
schema[i] = 0;
if (stricmp(schema, "rtsp") && stricmp(schema, "rtspu") && stricmp(schema, "rtsph") && stricmp(schema, "rtsps") && stricmp(schema, "satip")) return GF_URL_ERROR;
//check for user/pass - not allowed
/*
test = strstr(sURL, "@");
if (test) return GF_NOT_SUPPORTED;
*/
test = strstr(sURL, "://");
if (!test) {
if (sep) sep[0] = '?';
return GF_URL_ERROR;
}
test += 3;
//check for service
retest = strstr(test, "/");
if (!retest) {
if (sep) sep[0] = '?';
return GF_URL_ERROR;
}
if (!stricmp(schema, "rtsp") || !stricmp(schema, "satip") || !stricmp(schema, "rtsph") || !stricmp(schema, "rtsps"))
*useTCP = GF_TRUE;
service_start = retest;
//check for port
char *port = strrchr(test, ':');
retest = (port<retest) ? port : NULL;
/*IPV6 address*/
if (retest && strchr(retest, ']')) retest = NULL;
if (retest && strstr(retest, "/")) {
retest += 1;
i=0;
while (i<strlen(retest) && i<1023) {
if (retest[i] == '/') break;
text[i] = retest[i];
i += 1;
}
text[i] = 0;
*Port = atoi(text);
}
char *sep_auth = strchr(test, '@');
if (sep_auth>service_start) sep_auth=NULL;
if (sep_auth) {
sep_auth[0] = 0;
char *psep = strchr(test, ':');
if (psep) psep[0] = 0;
gf_strlcpy(User, test, 1024);
if (psep) {
gf_strlcpy(Pass, psep+1, 1024);
if (psep) psep[0] = ':';
}
sep_auth[0] = '@';
test = sep_auth+1;
}
//get the server name
is_ipv6 = GF_FALSE;
len = (u32) strlen(test);
i=0;
while (i<len) {
if (test[i]=='[') is_ipv6 = GF_TRUE;
else if (test[i]==']') is_ipv6 = GF_FALSE;
if ( (test[i] == '/') || (!is_ipv6 && (test[i] == ':')) ) break;
if (i>=GF_ARRAY_LENGTH(text)) break;
text[i] = test[i];
i += 1;
}
text[MIN(i, GF_ARRAY_LENGTH(text)-1)] = 0;
gf_strlcpy(Server, text, 1024);
if (sep) sep[0] = '?';
if (service_start) {
gf_strlcpy(Service, service_start+1, 1024);
} else {
Service[0]=0;
}
return GF_OK;
}
//create a new GF_RTSPSession from URL - DO NOT USE WITH SDP
GF_EXPORT
GF_RTSPSession *gf_rtsp_session_new(char *sURL, u16 DefaultPort)
{
GF_RTSPSession *sess;
char server[1024], service[1024], user[1024], pass[1024];
GF_Err e;
u16 Port;
Bool UseTCP;
if (!sURL) return NULL;
e = RTSP_UnpackURL(sURL, server, &Port, service, &UseTCP, user, pass);
if (e) return NULL;
GF_SAFEALLOC(sess, GF_RTSPSession);
if (!sess) return NULL;
sess->ConnectionType = UseTCP ? GF_SOCK_TYPE_TCP : GF_SOCK_TYPE_UDP;
#ifdef GPAC_HAS_SSL
if (!strncmp(sURL, "rtsps://", 8)
|| (!strncmp(sURL, "rtsph://", 8) && ((Port==443) || (Port == 8443)))
) {
sess->use_ssl = GF_TRUE;
sess->ConnectionType = GF_SOCK_TYPE_TCP;
}
#endif
if (Port) sess->Port = Port;
#ifdef GPAC_HAS_SSL
else if (sess->use_ssl) sess->Port = 322;
#endif
else if (DefaultPort) sess->Port = DefaultPort;
else sess->Port = 554;
//HTTP tunnel
if ((sess->Port == 80) || (sess->Port == 8080) || !strnicmp(sURL, "rtsph://", 8)){
sess->ConnectionType = GF_SOCK_TYPE_TCP;
sess->tunnel_mode = RTSP_HTTP_CLIENT;
}
gf_rtsp_set_buffer_size(sess, RTSP_PCK_SIZE);
sess->Server = gf_strdup(server);
sess->Service = gf_strdup(service);
if (user[0])
sess->User = gf_strdup(user);
if (pass[0])
sess->Pass = gf_strdup(pass);
sess->TCPChannels = gf_list_new();
gf_rtsp_session_reset(sess, GF_FALSE);
return sess;
}
GF_EXPORT
void gf_rtsp_session_set_netcap_id(GF_RTSPSession *sess, const char *netcap_id)
{
if (sess) sess->netcap_id = netcap_id;
}
GF_EXPORT
void gf_rtsp_reset_aggregation(GF_RTSPSession *sess)
{
if (!sess) return;
if (sess->RTSP_State == GF_RTSP_STATE_WAIT_FOR_CONTROL) {
gf_strcpy(sess->RTSPLastRequest, "RESET");
//skip all we haven't received
sess->CSeq += sess->NbPending;
sess->NbPending = 0;
}
sess->RTSP_State = GF_RTSP_STATE_INIT;
}
void RemoveTCPChannels(GF_RTSPSession *sess)
{
while (gf_list_count(sess->TCPChannels)) {
GF_TCPChan *ch = (GF_TCPChan*)gf_list_get(sess->TCPChannels, 0);
gf_free(ch);
gf_list_rem(sess->TCPChannels, 0);
}
}
GF_EXPORT
u32 gf_rtsp_session_reset(GF_RTSPSession *sess, Bool ResetConnection)
{
sess->last_session_id = NULL;
sess->NeedConnection = 1;
if (ResetConnection) {
if (sess->connection) gf_sk_del(sess->connection);
sess->connection = NULL;
if (sess->http) {
gf_sk_del(sess->http);
sess->http = NULL;
if (sess->tunnel_mode<RTSP_HTTP_DISABLE)
sess->tunnel_mode = 0;
}
sess->tunnel_state = 0;
#ifdef GPAC_HAS_SSL
if (sess->ssl) {
gf_ssl_del(sess->ssl);
sess->ssl = NULL;
}
if (sess->ssl_http) {
gf_ssl_del(sess->ssl_http);
sess->ssl_http = NULL;
}
#endif
}
sess->RTSP_State = GF_RTSP_STATE_INIT;
sess->InterID = (u8) -1;
sess->pck_start = sess->payloadSize = 0;
sess->CurrentPos = sess->CurrentSize = 0;
gf_strcpy(sess->RTSPLastRequest, "");
RemoveTCPChannels(sess);
//CSeq 1 is for regular rtsp -> rtsps switch, 0 is for rtsph->rtsph+tls
if (sess->CSeq<=1) {
sess->nb_retry++;
#ifdef GPAC_HAS_SSL
if ((sess->nb_retry>2)
&& !sess->use_ssl
&& !gf_opts_get_bool("core", "no-tls-rcfg")
) {
sess->use_ssl = GF_TRUE;
GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSP] Connection closed by server %s, retrying with TLS\n", sess->Server));
}
#endif
return sess->nb_retry;
}
return 0;
}
#ifdef GPAC_HAS_SSL
Bool gf_rtsp_session_needs_ssl(GF_RTSPSession *sess)
{
if (!sess) return GF_FALSE;
if (sess->use_ssl && !sess->ssl_ctx) return GF_TRUE;
return GF_FALSE;
}
#endif
GF_EXPORT
void gf_rtsp_session_del(GF_RTSPSession *sess)
{
if (!sess) return;
gf_rtsp_session_reset(sess, GF_FALSE);
if (sess->connection) gf_sk_del(sess->connection);
if (sess->http) gf_sk_del(sess->http);
if (sess->Server) gf_free(sess->Server);
if (sess->Service) gf_free(sess->Service);
gf_list_del(sess->TCPChannels);
if (sess->rtsp_pck_buf) gf_free(sess->rtsp_pck_buf);
gf_free(sess->tcp_buffer);
if (sess->HTTP_Cookie) gf_free(sess->HTTP_Cookie);
if (sess->User) gf_free(sess->User);
if (sess->Pass) gf_free(sess->Pass);
if (sess->async_buf) gf_free(sess->async_buf);
#ifdef GPAC_HAS_SSL
if (sess->ssl) gf_ssl_del(sess->ssl);
if (sess->ssl_http) gf_ssl_del(sess->ssl_http);
#endif
gf_free(sess);
}
#ifdef GPAC_HAS_SSL
GF_Err gf_rtsp_set_ssl_ctx(GF_RTSPSession *sess, void *ssl_CTX)
{
if (!sess) return GF_BAD_PARAM;
sess->ssl_ctx = ssl_CTX;
return GF_OK;
}
#endif
GF_EXPORT
u32 gf_rtsp_get_session_state(GF_RTSPSession *sess)
{
u32 state;
if (!sess) return GF_RTSP_STATE_INVALIDATED;
state = sess->RTSP_State;
return state;
}
#if 0 //unused
char *gf_rtsp_get_last_request(GF_RTSPSession *sess)
{
if (!sess) return NULL;
return sess->RTSPLastRequest;
}
#endif
//check whether the url contains server and service name
//no thread protection as this is const throughout the session
GF_EXPORT
Bool gf_rtsp_is_my_session(GF_RTSPSession *sess, char *url)
{
if (!sess) return GF_FALSE;
if (!strstr(url, sess->Server)) return GF_FALSE;
//same url or sub-url
if (strstr(url, sess->Service)) return GF_TRUE;
return GF_FALSE;
}
#if 0 //unused
const char *gf_rtsp_get_last_session_id(GF_RTSPSession *sess)
{
if (!sess) return NULL;
return sess->last_session_id;
}
#endif
GF_EXPORT
const char *gf_rtsp_get_server_name(GF_RTSPSession *sess)
{
if (!sess) return NULL;
return sess->Server;
}
GF_EXPORT
u16 gf_rtsp_get_session_port(GF_RTSPSession *sess)
{
return (sess ? sess->Port : 0);
}
GF_EXPORT
Bool gf_rtsp_use_tls(GF_RTSPSession *sess)
{
#ifdef GPAC_HAS_TLS
return (sess ? sess->use_tls : GF_FALSE);
#endif
return GF_FALSE;
}
GF_Err gf_rtsp_check_connection(GF_RTSPSession *sess)
{
GF_Err e;
if (!sess) return GF_BAD_PARAM;
//active, return
if (!sess->NeedConnection) {
if (sess->tunnel_state) return gf_rtsp_http_tunnel_setup(sess);
return gf_rstp_flush_buffer(sess);
}
//socket is destroyed, recreate
if (!sess->connection) {
sess->connection = gf_sk_new_ex(sess->ConnectionType, sess->netcap_id);
if (!sess->connection) return GF_OUT_OF_MEM;
//work in non-blocking mode
gf_sk_set_block_mode(sess->connection, GF_TRUE);
sess->timeout_in = gf_opts_get_int("core", "tcp-timeout");
if (!sess->timeout_in) sess->timeout_in = 5000;
sess->timeout_in += gf_sys_clock();
if (sess->SockBufferSize) gf_sk_set_buffer_size(sess->connection, GF_FALSE, sess->SockBufferSize);
}
//the session is down, reconnect
#ifdef GPAC_HAS_SSL
if (!sess->ssl_connect_pending)
#endif
{
e = gf_sk_connect(sess->connection, sess->Server, sess->Port, NULL);
if (e) {
if (e==GF_IP_NETWORK_EMPTY) {
if (sess->timeout_in < gf_sys_clock())
e = GF_IP_CONNECTION_FAILURE;
}
return e;
}
}
#ifdef GPAC_HAS_SSL
if (sess->use_ssl) {
if (!sess->ssl_ctx)
return GF_IP_CONNECTION_FAILURE;
if (!sess->ssl) {
sess->ssl = SSL_new(sess->ssl_ctx);
SSL_set_fd(sess->ssl, gf_sk_get_handle(sess->connection));
SSL_ctrl(sess->ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void*) sess->Server);
SSL_set_connect_state(sess->ssl);
SSL_set_mode(sess->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_alpn_protos(sess->ssl, NULL, 0);
}
sess->ssl_connect_pending = 0;
int ret = SSL_connect(sess->ssl);
if (ret<=0) {
ret = SSL_get_error(sess->ssl, ret);
if (ret==SSL_ERROR_SSL) {
char msg[1024];
SSL_load_error_strings();
ERR_error_string_n(ERR_get_error(), msg, 1023);
msg[1023]=0;
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot connect, error %s\n", msg));
return GF_IP_CONNECTION_FAILURE;
} else if ((ret==SSL_ERROR_WANT_READ) || (ret==SSL_ERROR_WANT_WRITE)) {
sess->ssl_connect_pending = 1;
return GF_IP_NETWORK_EMPTY;
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot connect, error %d\n", ret));
return GF_REMOTE_SERVICE_ERROR;
}
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[SSL] connected\n"));
}
Bool success = gf_ssl_check_cert(sess->ssl, sess->Server);
if (!success) {
return GF_AUTHENTICATION_FAILURE;
}
}
#endif
sess->NeedConnection = 0;
if (!sess->http && (sess->tunnel_mode==RTSP_HTTP_CLIENT)) {
e = gf_rtsp_http_tunnel_setup(sess);
if (e) return e;
}
return gf_rstp_flush_buffer(sess);
}
GF_Err gf_rtsp_send_data(GF_RTSPSession *sess, u8 *buffer, u32 Size)
{
GF_Err e;
u32 Size64;
e = gf_rtsp_check_connection(sess);
if (e) return e;
//RTSP requests on HTTP are base 64 encoded
if (sess->tunnel_mode==RTSP_HTTP_CLIENT) {
char *buf64 = gf_malloc(sizeof(char)*Size*2);
if (!buf64) return GF_OUT_OF_MEM;
Size64 = gf_base64_encode(buffer, Size, buf64, Size*2);
//send on http connection
e = gf_rtsp_write_sock(sess, buf64, Size64);
gf_free(buf64);
return e;
} else {
return gf_rtsp_write_sock(sess, buffer, Size);
}
}
static GF_TCPChan *GetTCPChannel(GF_RTSPSession *sess, u8 rtpID, u8 rtcpID, Bool RemoveIt)
{
GF_TCPChan *ptr;
u32 i, count = gf_list_count(sess->TCPChannels);
for (i=0; i<count; i++) {
ptr = (GF_TCPChan *)gf_list_get(sess->TCPChannels, i);
if (ptr->rtpID == rtpID) goto exit;
if (ptr->rtcpID == rtcpID) goto exit;
}
return NULL;
exit:
if (RemoveIt) gf_list_rem(sess->TCPChannels, i);
return ptr;
}
GF_Err gf_rtsp_do_deinterleave(GF_RTSPSession *sess)
{
GF_TCPChan *ch;
Bool IsRTCP;
u8 InterID;
u16 paySize;
u32 res, Size;
char *buffer;
if (!sess) return GF_SERVICE_ERROR;
Size = sess->CurrentSize - sess->CurrentPos;
buffer = sess->tcp_buffer + sess->CurrentPos;
//we do not work with just a header -> force a refill
if (!Size)
return gf_rtsp_fill_buffer(sess);
if (Size <= 4)
return gf_rtsp_refill_buffer(sess);
//break if we get RTSP response on the wire
if (!strncmp(buffer, "RTSP", 4) || !strncmp(buffer, "HTTP", 4))
return GF_EOS;
//new packet
if (!sess->pck_start && (buffer[0] == '$')) {
InterID = buffer[1];
paySize = ((buffer[2] << 8) & 0xFF00) | (buffer[3] & 0xFF);
/*this may be NULL (data fetched after a teardown) - resync and return*/
ch = GetTCPChannel(sess, InterID, InterID, GF_FALSE);
/*then check whether this is a full packet or a split*/
if (paySize <= Size-4) {
if (ch) {
IsRTCP = (ch->rtcpID == InterID) ? GF_TRUE : GF_FALSE;
sess->RTSP_SignalData(sess, ch->ch_ptr, buffer+4, paySize, IsRTCP);
}
sess->CurrentPos += paySize+4;
gf_fatal_assert(sess->CurrentPos <= sess->CurrentSize);
} else {
/*missed end of pck ?*/
if (sess->payloadSize) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP over RTSP] Missed end of packet (%d bytes) in stream %d\n", sess->payloadSize - sess->pck_start, sess->InterID));
ch = GetTCPChannel(sess, sess->InterID, sess->InterID, GF_FALSE);
if (ch) {
IsRTCP = (ch->rtcpID == sess->InterID) ? GF_TRUE : GF_FALSE;
sess->RTSP_SignalData(sess, ch->ch_ptr, sess->rtsp_pck_buf, sess->payloadSize, IsRTCP);
}
}
sess->InterID = InterID;
sess->payloadSize = paySize;
sess->pck_start = Size-4;
if (sess->rtsp_pck_size < paySize) {
sess->rtsp_pck_buf = (char *)gf_realloc(sess->rtsp_pck_buf, sizeof(char)*paySize);
sess->rtsp_pck_size = paySize;
}
memcpy(sess->rtsp_pck_buf, buffer+4, Size-4);
sess->CurrentPos += Size;
gf_fatal_assert(sess->CurrentPos <= sess->CurrentSize);
}
}
/*end of packet*/
else if (sess->payloadSize - sess->pck_start <= Size) {
// GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP over RTSP] Missed beginning of packet (%d bytes) in stream %d\n", Size, sess->InterID));
res = sess->payloadSize - sess->pck_start;
if (res)
memcpy(sess->rtsp_pck_buf + sess->pck_start, buffer, res);
//flush - same as above, don't complain if channel not found
ch = GetTCPChannel(sess, sess->InterID, sess->InterID, GF_FALSE);
if (ch) {
IsRTCP = (ch->rtcpID == sess->InterID) ? GF_TRUE : GF_FALSE;
sess->RTSP_SignalData(sess, ch->ch_ptr, sess->rtsp_pck_buf, sess->payloadSize, IsRTCP);
}
sess->payloadSize = 0;
sess->pck_start = 0;
sess->InterID = (u8) -1;
sess->CurrentPos += res;
gf_fatal_assert(sess->CurrentPos <= sess->CurrentSize);
}
/*middle of packet*/
else {
// GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP over RTSP] Missed beginning of RTP packet in stream %d\n", sess->InterID));
memcpy(sess->rtsp_pck_buf + sess->pck_start, buffer, Size);
sess->pck_start += Size;
sess->CurrentPos += Size;
gf_fatal_assert(sess->CurrentPos <= sess->CurrentSize);
}
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtsp_session_read(GF_RTSPSession *sess)
{
GF_Err e;
if (!sess) return GF_BAD_PARAM;
e = gf_rtsp_fill_buffer(sess);
if (!e) {
//read as much as possible (will break if no data or if "RTSP" is found)
while (1) {
e = gf_rtsp_do_deinterleave(sess);
if (e) return e;
}
}
return e;
}
GF_EXPORT
u32 gf_rtsp_unregister_interleave(GF_RTSPSession *sess, u8 LowInterID)
{
u32 res;
GF_TCPChan *ptr;
if (!sess) return 0;
ptr = GetTCPChannel(sess, LowInterID, LowInterID, GF_TRUE);
if (ptr) gf_free(ptr);
res = gf_list_count(sess->TCPChannels);
if (!res) sess->interleaved = GF_FALSE;
return res;
}
GF_EXPORT
GF_Err gf_rtsp_register_interleave(GF_RTSPSession *sess, void *the_ch, u8 LowInterID, u8 HighInterID)
{
GF_TCPChan *ptr;
if (!sess) return GF_BAD_PARAM;
//do NOT register twice
ptr = GetTCPChannel(sess, LowInterID, HighInterID, GF_FALSE);
if (!ptr) {
ptr = (GF_TCPChan *)gf_malloc(sizeof(GF_TCPChan));
ptr->ch_ptr = the_ch;
ptr->rtpID = LowInterID;
ptr->rtcpID = HighInterID;
gf_list_add(sess->TCPChannels, ptr);
}
sess->interleaved=GF_TRUE;
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtsp_set_interleave_callback(GF_RTSPSession *sess, gf_rtsp_interleave_callback SignalData)
{
if (!sess) return GF_BAD_PARAM;
//only if existing
if (SignalData) sess->RTSP_SignalData = SignalData;
if (!sess->rtsp_pck_buf || (sess->rtsp_pck_size != RTSP_PCK_SIZE) ) {
if (!sess->rtsp_pck_buf)
sess->pck_start = 0;
sess->rtsp_pck_size = RTSP_PCK_SIZE;
sess->rtsp_pck_buf = (char *)gf_realloc(sess->rtsp_pck_buf, sizeof(char)*sess->rtsp_pck_size);
}
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtsp_set_buffer_size(GF_RTSPSession *sess, u32 BufferSize)
{
if (!sess) return GF_BAD_PARAM;
if (sess->SockBufferSize >= BufferSize) return GF_OK;
sess->SockBufferSize = BufferSize;
sess->tcp_buffer = gf_realloc(sess->tcp_buffer, (BufferSize+1));
return GF_OK;
}
static GF_Err rstp_do_write_sock(GF_RTSPSession *sess, GF_Socket *sock, const u8 *buffer, u32 size, u32 *written)
{
#ifdef GPAC_HAS_SSL
SSL *ssl_sock = (sock==sess->http) ? sess->ssl_http : sess->ssl;
if (ssl_sock) {
u32 idx=0;
s32 nb_tls_blocks = size/16000;
if (written)
*written = 0;
while (nb_tls_blocks>=0) {
u32 len, to_write = 16000;
if (nb_tls_blocks==0)
to_write = size - idx*16000;
len = SSL_write(ssl_sock, buffer + idx*16000, to_write);
nb_tls_blocks--;
idx++;
if (len != to_write) {
int err = SSL_get_error(ssl_sock, len);
if ((err==SSL_ERROR_WANT_READ) || (err==SSL_ERROR_WANT_WRITE)) {
return GF_IP_NETWORK_EMPTY;
}
if (err==SSL_ERROR_SSL) {
char msg[1024];
SSL_load_error_strings();
ERR_error_string_n(ERR_get_error(), msg, 1023);
msg[1023]=0;
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot send, error %s\n", msg));
}
return GF_IP_NETWORK_FAILURE;
}
if (written)
*written += to_write;
}
return GF_OK;
}
#endif
return gf_sk_send_ex(sock, buffer, size, written);
}
static Bool HTTP_RandInit = GF_TRUE;
#define HTTP10_RSP_OK "HTTP/1.0 200 OK"
#define HTTP11_RSP_OK "HTTP/1.1 200 OK"
static GF_Err gf_rtsp_http_tunnel_setup(GF_RTSPSession *sess)
{
GF_Err e;
u32 size;
s32 pos;
const char *ua;
char buffer[GF_RTSP_DEFAULT_BUFFER];
if (!sess) return GF_BAD_PARAM;
if (!sess->tunnel_state) {
//generate http cookie
if (HTTP_RandInit) {
gf_rand_init(GF_FALSE);
HTTP_RandInit = GF_FALSE;
}
if (!sess->HTTP_Cookie) {
char szBuf[30];
u32 r = gf_rand();
snprintf(szBuf, 29, "GPAC_%x_"LLX, r, gf_sys_clock_high_res() );
szBuf[29]=0;
sess->HTTP_Cookie = gf_strdup(szBuf);
if (!sess->HTTP_Cookie) return GF_OUT_OF_MEM;
}
ua = gf_opts_get_key("core", "user-agent");
if (!ua) ua = "GPAC " GPAC_VERSION;
// 1. send "GET /sample.mov HTTP/1.0\r\n ..."
memset(buffer, 0, GF_RTSP_DEFAULT_BUFFER);
pos = 0;
pos += sprintf(buffer + pos, "GET /%s HTTP/1.0\r\n", sess->Service);
pos += sprintf(buffer + pos, "User-Agent: %s\r\n", ua);
pos += sprintf(buffer + pos, "x-sessioncookie: %s\r\n", sess->HTTP_Cookie);
pos += sprintf(buffer + pos, "Accept: application/x-rtsp-tunnelled\r\n" );
pos += sprintf(buffer + pos, "Pragma: no-cache\r\n" );
/*pos += */sprintf(buffer + pos, "Cache-Control: no-cache\r\n\r\n" );
sess->tunnel_state = 1;
GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSPTunnel] Sending %s", buffer));
// send it - we assume this will fit the socket buffer
e = rstp_do_write_sock(sess, sess->connection, buffer, (u32) strlen(buffer), NULL);
if (e) return e;
}
if (sess->tunnel_state == 1) {
// 2. wait for "HTTP/1.0 200 OK"
e = gf_rstp_do_read_sock(sess, sess->connection, buffer, GF_RTSP_DEFAULT_BUFFER, &size);
if (e) {
if ((e==GF_IP_NETWORK_EMPTY) && (sess->timeout_in < gf_sys_clock()))
e = GF_IP_CONNECTION_FAILURE;
return e;
}
gf_assert(size);
sess->tunnel_state = 2;
GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSPTunnel] Got reply %s", buffer));
//get HTTP/1.0 200 OK
if (strncmp(buffer, HTTP10_RSP_OK, strlen(HTTP10_RSP_OK)) && strncmp(buffer, HTTP11_RSP_OK, strlen(HTTP11_RSP_OK))) {
sess->tunnel_state = 0;
return GF_REMOTE_SERVICE_ERROR;
}
// 3. send "POST /sample.mov HTTP/1.0\r\n ..."
sess->http = gf_sk_new_ex(GF_SOCK_TYPE_TCP, sess->netcap_id);
if (!sess->http) {
sess->tunnel_state = 0;
return GF_IP_NETWORK_FAILURE;
}
gf_sk_set_block_mode(sess->http, GF_TRUE);
}
#ifdef GPAC_HAS_SSL
if (!sess->ssl_connect_pending)
#endif
{
e = gf_sk_connect(sess->http, sess->Server, sess->Port, NULL);
if (e) {
if ((e==GF_IP_NETWORK_EMPTY) && (sess->timeout_in < gf_sys_clock()))
e = GF_IP_CONNECTION_FAILURE;
return e;
}
}
#ifdef GPAC_HAS_SSL
if (sess->use_ssl) {
if (!sess->ssl_http) {
sess->ssl_http = SSL_new(sess->ssl_ctx);
SSL_set_fd(sess->ssl_http, gf_sk_get_handle(sess->http));
SSL_ctrl(sess->ssl_http, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void*) sess->Server);
SSL_set_connect_state(sess->ssl_http);
SSL_set_mode(sess->ssl_http, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_alpn_protos(sess->ssl_http, NULL, 0);
}
sess->ssl_connect_pending = 0;
int ret = SSL_connect(sess->ssl_http);
if (ret<=0) {
ret = SSL_get_error(sess->ssl_http, ret);
if (ret==SSL_ERROR_SSL) {
char msg[1024];
SSL_load_error_strings();
ERR_error_string_n(ERR_get_error(), msg, 1023);
msg[1023]=0;
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot connect, error %s\n", msg));
return GF_IP_CONNECTION_FAILURE;
} else if ((ret==SSL_ERROR_WANT_READ) || (ret==SSL_ERROR_WANT_WRITE)) {
sess->ssl_connect_pending = 1;
return GF_IP_NETWORK_EMPTY;
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot connect, error %d\n", ret));
return GF_REMOTE_SERVICE_ERROR;
}
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[SSL] connected\n"));
}
}
#endif
GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSPTunnel] POST channel connected"));
ua = gf_opts_get_key("core", "user-agent");
if (!ua) ua = "GPAC " GPAC_VERSION;
memset(buffer, 0, GF_RTSP_DEFAULT_BUFFER);
pos = 0;
pos += sprintf(buffer + pos, "POST /%s HTTP/1.0\r\n", sess->Service);
pos += sprintf(buffer + pos, "User-Agent: %s\r\n", ua);
pos += sprintf(buffer + pos, "x-sessioncookie: %s\r\n", sess->HTTP_Cookie);
pos += sprintf(buffer + pos, "Accept: application/x-rtsp-tunnelled\r\n");
pos += sprintf(buffer + pos, "Pragma: no-cache\r\n");
pos += sprintf(buffer + pos, "Cache-Control: no-cache\r\n");
pos += sprintf(buffer + pos, "Content-Length: 32767\r\n");
/*pos += */sprintf(buffer + pos, "Expires: Sun. 9 Jan 1972 00:00:00 GMT\r\n\r\n");
sess->tunnel_state = 0;
GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSPTunnel] Sending request %s", buffer));
// send it, no need to wait for answer
return rstp_do_write_sock(sess, sess->http, buffer, (u32) strlen(buffer), NULL);
}
/*server-side RTSP sockets*/
static u32 SessionID_RandInit = 0;
GF_EXPORT
GF_RTSPSession *gf_rtsp_session_new_server(GF_Socket *rtsp_listener, Bool allow_http_tunnel, void *ssl_ctx)
{
GF_RTSPSession *sess;
GF_Socket *new_conn;
GF_Err e;
u32 fam;
u16 port;
#ifdef GPAC_HAS_SSL
SSL *ssl = NULL;
#endif
if (!rtsp_listener) return NULL;
e = gf_sk_accept(rtsp_listener, &new_conn);
if (!new_conn || e) return NULL;
#ifdef GPAC_HAS_SSL
if (ssl_ctx) {
ssl = gf_ssl_new(ssl_ctx, new_conn, &e);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[SSL] Failed to create TLS session: %s\n", gf_error_to_string(e) ));
gf_sk_del(new_conn);
return NULL;
}
SSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE);
}
#endif
e = gf_sk_get_local_info(new_conn, &port, &fam);
if (e) {
gf_sk_del(new_conn);
return NULL;
}
e = gf_sk_set_block_mode(new_conn, GF_TRUE);
if (e) {
gf_sk_del(new_conn);
return NULL;
}
e = gf_sk_server_mode(new_conn, GF_TRUE);
if (e) {
gf_sk_del(new_conn);
return NULL;
}
//OK create a new session
GF_SAFEALLOC(sess, GF_RTSPSession);
if (!sess) return NULL;
sess->connection = new_conn;
sess->Port = port;
sess->ConnectionType = fam;
#ifdef GPAC_HAS_SSL
sess->ssl_ctx = ssl_ctx;
sess->ssl = ssl;
#endif
const char *name = gf_opts_get_key("core", "user-agent");
if (name) {
sess->Server = gf_strdup(name);
} else {
sess->Server = gf_strdup("GPAC-");
gf_dynstrcat(&sess->Server, gf_gpac_version(), NULL);
}
gf_rtsp_set_buffer_size(sess, 4096);
sess->TCPChannels = gf_list_new();
if (!allow_http_tunnel)
sess->tunnel_mode = RTSP_HTTP_DISABLE;
return sess;
}
#if 0 //unused
GF_Err gf_rtsp_load_service_name(GF_RTSPSession *sess, char *URL)
{
char server[1024], service[1024], user[1024], pass[1024];
GF_Err e;
u16 Port;
Bool UseTCP;
u32 type;
if (!sess || !URL) return GF_BAD_PARAM;
e = RTSP_UnpackURL(URL, server, &Port, service, &UseTCP, user, pass);
if (e) return e;
type = UseTCP ? GF_SOCK_TYPE_TCP : GF_SOCK_TYPE_UDP;
//check the network type matches, otherwise deny client
if (sess->ConnectionType != type) return GF_URL_ERROR;
if (sess->Port != Port) return GF_URL_ERROR;
//ok
sess->Server = gf_strdup(server);
sess->Service = gf_strdup(service);
return GF_OK;
}
#endif
GF_EXPORT
char *gf_rtsp_generate_session_id(GF_RTSPSession *sess)
{
u32 one;
u64 res;
char buffer[30];
if (!sess) return NULL;
if (!SessionID_RandInit) {
SessionID_RandInit = 1;
gf_rand_init(GF_FALSE);
}
one = gf_rand();
res = one;
res <<= 32;
res+= (PTR_TO_U_CAST sess) + sess->CurrentPos + sess->CurrentSize;
sprintf(buffer, LLU, res);
return gf_strdup(buffer);
}
GF_EXPORT
GF_Err gf_rtsp_get_session_ip(GF_RTSPSession *sess, char buffer[GF_MAX_IP_NAME_LEN])
{
if (!sess || !sess->connection) return GF_BAD_PARAM;
if (gf_sk_get_local_ip(sess->connection, buffer) != GF_OK)
gf_strlcpy(buffer, "127.0.0.1", GF_MAX_IP_NAME_LEN);
return GF_OK;
}
#if 0 //unused
u8 gf_rtsp_get_next_interleave_id(GF_RTSPSession *sess)
{
u32 i;
u8 id;
GF_TCPChan *ch;
id = 0;
i=0;
while ((ch = (GF_TCPChan *)gf_list_enum(sess->TCPChannels, &i))) {
if (ch->rtpID >= id) id = ch->rtpID + 1;
if (ch->rtcpID >= id) id = ch->rtcpID + 1;
}
return id;
}
#endif
GF_EXPORT
GF_Err gf_rtsp_get_remote_address(GF_RTSPSession *sess, char *buf)
{
if (!sess || !sess->connection) return GF_BAD_PARAM;
return gf_sk_get_remote_address(sess->connection, buf);
}
static GF_Err gf_rtsp_write_sock(GF_RTSPSession *sess, u8 *data, u32 len)
{
u32 remain, written=0;
if (!sess->async_buf_size) {
GF_Err e = rstp_do_write_sock(sess, (sess->http && (sess->tunnel_mode==RTSP_HTTP_CLIENT)) ? sess->http : sess->connection, data, len, &written);
if (e && (e!= GF_IP_NETWORK_EMPTY))
return e;
if (written==len) return GF_OK;
}
remain = len - written;
if (sess->async_buf_size + remain > sess->async_buf_alloc) {
sess->async_buf_alloc = sess->async_buf_size+remain;
sess->async_buf = gf_realloc(sess->async_buf, sess->async_buf_alloc);
if (!sess->async_buf) return GF_OUT_OF_MEM;
}
memcpy(sess->async_buf + sess->async_buf_size, data+written, remain);
sess->async_buf_size += remain;
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtsp_session_write_interleaved(GF_RTSPSession *sess, u32 idx, u8 *pck, u32 pck_size)
{
GF_Err e;
char streamID[4];
if (!sess || !sess->connection) return GF_BAD_PARAM;
streamID[0] = '$';
streamID[1] = (u8) idx;
streamID[2] = (pck_size>>8) & 0xFF;
streamID[3] = pck_size & 0xFF;
e = gf_rtsp_write_sock(sess, streamID, 4);
e |= gf_rtsp_write_sock(sess, pck, pck_size);
return e;
}
static GF_Err gf_rstp_flush_buffer(GF_RTSPSession *sess)
{
while (sess->async_buf_size) {
u32 written = 0;
GF_Err e = rstp_do_write_sock(sess, (sess->tunnel_mode==RTSP_HTTP_CLIENT) ? sess->http : sess->connection, sess->async_buf, sess->async_buf_size, &written);
if (e) {
if (e!= GF_IP_NETWORK_EMPTY)
sess->async_buf_size = 0;
return e;
}
if (written==sess->async_buf_size) {
sess->async_buf_size = 0;
return GF_OK;
}
memmove(sess->async_buf, sess->async_buf + written, sess->async_buf_size - written);
sess->async_buf_size -= written;
}
return GF_OK;
}
const char *gf_rtsp_get_session_cookie(GF_RTSPSession *sess)
{
return sess ? sess->HTTP_Cookie : NULL;
}
GF_Err gf_rtsp_merge_tunnel(GF_RTSPSession *sess, GF_RTSPSession *post_sess)
{
if (!sess || !post_sess) return GF_BAD_PARAM;
if (!sess->HTTP_Cookie || !post_sess->HTTP_Cookie) return GF_BAD_PARAM;
if (strcmp(sess->HTTP_Cookie, post_sess->HTTP_Cookie)) return GF_BAD_PARAM;
if (!post_sess->connection) return GF_BAD_PARAM;
//move any pending data
if (post_sess->CurrentPos<post_sess->CurrentSize) {
u8 *buf = post_sess->tcp_buffer + post_sess->CurrentPos;
u32 remain = post_sess->CurrentSize - post_sess->CurrentPos;
if (sess->CurrentSize + remain > sess->SockBufferSize) {
sess->SockBufferSize = sess->CurrentSize + remain;
sess->tcp_buffer = gf_realloc(sess->tcp_buffer, sess->SockBufferSize);
if (!sess->tcp_buffer) return GF_OUT_OF_MEM;
}
memcpy(sess->tcp_buffer+sess->CurrentPos, buf, remain);
sess->CurrentSize += remain;
}
sess->tunnel_mode = RTSP_HTTP_SERVER;
sess->http = post_sess->connection;
post_sess->connection = NULL;
#ifdef GPAC_HAS_SSL
sess->ssl_http = post_sess->ssl;
post_sess->ssl = NULL;
#endif
return GF_OK;
}
const char *gf_rtsp_get_user(GF_RTSPSession *sess)
{
return sess ? sess->User : NULL;
}
const char *gf_rtsp_get_password(GF_RTSPSession *sess)
{
return sess ? sess->Pass : NULL;
}
#endif /*GPAC_DISABLE_STREAMING*/