/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2022
 *					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>

#ifndef GPAC_DISABLE_STREAMING

#include <gpac/token.h>


GF_Err gf_rtsp_read_reply(GF_RTSPSession *sess)
{
	GF_Err e;
	u32 res, body_size = 0;
	u32 BodyStart = 0;

	//fetch more data on the socket if needed
	while (1) {
		//Locate header / body
		if (!BodyStart) gf_rtsp_get_body_info(sess, &BodyStart, &body_size, GF_FALSE);

		if (BodyStart) {
			//enough data
			res = sess->CurrentSize - sess->CurrentPos;
			if (!body_size || (res >= body_size + BodyStart)) {
				//done
				break;
			}
		}
		//this is the tricky part: if we do NOT have a body start -> we refill
		e = gf_rtsp_refill_buffer(sess);
		if (e) {
			//we have a buffer, nothing to follow and this is an http tunnel, this means no '=' at the end of the base64 string
			//assume we got the entire message
			if ((e==GF_IP_NETWORK_EMPTY) && (sess->CurrentPos<sess->CurrentSize) && (sess->tunnel_mode==RTSP_HTTP_SERVER)) {
				return GF_OK;
			}
			return e;
		}
	}
	return GF_OK;
}

void gf_rtsp_get_body_info(GF_RTSPSession *sess, u32 *body_start, u32 *body_size, Bool skip_tunnel)
{
	s32 start;
	Bool is_post=GF_FALSE;
	char *buffer;
	char *cl_str;

	*body_start = *body_size = 0;

	buffer = sess->tcp_buffer + sess->CurrentPos;
	if (!skip_tunnel && (sess->tunnel_mode==RTSP_HTTP_SERVER)) {
		start = gf_token_find(buffer, 0, sess->CurrentSize - sess->CurrentPos, "=");
		if (start<=0) return;
		*body_start = start + 1;
		if (buffer[start+1]=='=')
			*body_start += 1;
		return;
	} else {
		start = gf_token_find(buffer, 0, sess->CurrentSize - sess->CurrentPos, "\r\n\r\n");
		if (start<=0) return;
		//if found add the 2 "\r\n" and parse it
		*body_start = start + 4;
	}
	if (!strncmp(buffer, "POST ", 5)) {
		is_post = GF_TRUE;
	}

	//get the content length
	cl_str = strstr(buffer, "Content-Length: ");
	if (!cl_str) cl_str = strstr(buffer, "Content-length: ");

	if (cl_str) {
		char *sep;
		cl_str += 16;
		sep = strchr(cl_str, '\r');
		if (sep) {
			sep[0] = 0;
			*body_size = atoi(cl_str);
			sep[0] = '\r';
		}
	}
	if (is_post) *body_size = 0;
}


GF_Err gf_rstp_do_read_sock(GF_RTSPSession *sess, GF_Socket *sock, u8 *data, u32 data_size, u32 *out_read)
{
	GF_Err e;
#ifdef GPAC_HAS_SSL
	SSL *ssl_sock = (sock == sess->http) ? sess->ssl_http : sess->ssl;
	if (ssl_sock) {
		//receive on null buffer (select only, check if data available)
		e = gf_sk_receive(sock, NULL, 0, NULL);
		//empty and no pending bytes in SSL, network empty
		if ((e==GF_IP_NETWORK_EMPTY) && !SSL_pending(sess->ssl))
			return GF_IP_NETWORK_EMPTY;

		int size = SSL_read(ssl_sock, data, data_size);
		if (size < 0) {
			*out_read = 0;
			e = gf_sk_probe(sock);
			if (!e) {
				int err = SSL_get_error(ssl_sock, size);
				if (err==SSL_ERROR_SSL) {
					e = GF_IO_ERR;
				} else if ((err==SSL_ERROR_WANT_READ) || (err==SSL_ERROR_WANT_WRITE)) {
					e = GF_IP_NETWORK_EMPTY;
				} else {
					e = GF_IP_NETWORK_FAILURE;
				}
			}
		} else if (!size) {
			e = GF_IP_NETWORK_EMPTY;
		} else {
			e = GF_OK;
			data[size] = 0;
			*out_read = size;
		}
	} else
#endif
	{
		e = gf_sk_receive(sock, data, data_size, out_read);
	}
	return e;
}



GF_Err gf_rtsp_refill_buffer(GF_RTSPSession *sess)
{
	GF_Err e;
	u32 res;
	char *ptr;

	if (!sess) return GF_BAD_PARAM;
	if (!sess->connection) return GF_IP_NETWORK_EMPTY;

	res = sess->CurrentSize - sess->CurrentPos;
	if (!res) return gf_rtsp_fill_buffer(sess);

	ptr = (char *)gf_malloc(sizeof(char) * res);
	memcpy(ptr, sess->tcp_buffer + sess->CurrentPos, res);
	memcpy(sess->tcp_buffer, ptr, res);
	gf_free(ptr);

	sess->CurrentPos = 0;
	sess->CurrentSize = res;

	//now read from current pos
	e = gf_rstp_do_read_sock(sess, sess->connection, sess->tcp_buffer + sess->CurrentSize, sess->SockBufferSize - sess->CurrentSize, &res);

	if (!e) {
		sess->CurrentSize += res;
	}
	return e;
}

GF_Err gf_rtsp_fill_buffer(GF_RTSPSession *sess)
{
	GF_Err e = GF_OK;

	if (!sess->connection) return GF_IP_NETWORK_EMPTY;

	if (sess->CurrentSize == sess->CurrentPos) {
		if (sess->tunnel_mode==RTSP_HTTP_SERVER) {
			e = gf_rstp_do_read_sock(sess, sess->http, sess->tcp_buffer, sess->SockBufferSize, &sess->CurrentSize);
		} else {
			e = gf_rstp_do_read_sock(sess, sess->connection, sess->tcp_buffer, sess->SockBufferSize, &sess->CurrentSize);
		}
		sess->CurrentPos = 0;
		sess->tcp_buffer[sess->CurrentSize] = 0;
		if (e) sess->CurrentSize = 0;
	} else if (!sess->CurrentSize) e = GF_IP_NETWORK_EMPTY;
	return e;
}


GF_RTSPTransport *gf_rtsp_transport_parse(u8 *buffer)
{
	Bool IsFirst;
	char buf[100], param_name[100], param_val[100];
	s32 pos, nPos;
	u32 v1, v2;
	GF_RTSPTransport *tmp;
	if (!buffer) return NULL;
	//only support for RTP/AVP for now
	if (strnicmp(buffer, "RTP/AVP", 7) && strnicmp(buffer, "RTP/SAVP", 8)) return NULL;

	GF_SAFEALLOC(tmp, GF_RTSPTransport);
	if (!tmp) return NULL;

	IsFirst = GF_TRUE;
	pos = 0;
	while (1) {
		pos = gf_token_get(buffer, pos, " ;", buf, 100);
		if (pos <= 0) break;
		if (strstr(buf, "=")) {
			nPos = gf_token_get(buf, 0, "=", param_name, 100);
			/*nPos = */gf_token_get(buf, nPos, "=", param_val, 100);
		} else {
			strcpy(param_name, buf);
		}

		//very first param is the profile
		if (IsFirst) {
			tmp->Profile = gf_strdup(param_name);
			IsFirst = GF_FALSE;
			continue;
		}

		if (!stricmp(param_name, "destination")) {
			if (tmp->destination) gf_free(tmp->destination);
			tmp->destination = gf_strdup(param_val);
		}
		else if (!stricmp(param_name, "source")) {
			if (tmp->source) gf_free(tmp->source);
			tmp->source = gf_strdup(param_val);
		}
		else if (!stricmp(param_name, "unicast")) tmp->IsUnicast = GF_TRUE;
		else if (!stricmp(param_name, "RECORD")) tmp->IsRecord = GF_TRUE;
		else if (!stricmp(param_name, "append")) tmp->Append = GF_TRUE;
		else if (!stricmp(param_name, "interleaved")) {
			u32 rID, rcID;
			tmp->IsInterleaved = GF_TRUE;
			if (sscanf(param_val, "%u-%u", &rID, &rcID) == 1) {
				sscanf(param_val, "%u", &rID);
				tmp->rtcpID = tmp->rtpID = (u8) rID;
			} else {
				tmp->rtpID = (u8) rID;
				tmp->rtcpID = (u8) rcID;
			}
		}
		else if (!stricmp(param_name, "layers")) sscanf(param_val, "%u", &tmp->MulticastLayers);
		else if (!stricmp(param_name, "ttl")) sscanf(param_val, "%c	", &tmp->TTL);
		else if (!stricmp(param_name, "port")) {
			sscanf(param_val, "%u-%u", &v1, &v2);
			tmp->port_first = (u16) v1;
			tmp->port_last = (u16) v2;
		}
		/*do not use %hud here, broken on Win32 (sscanf returns 1)*/
		else if (!stricmp(param_name, "server_port")) {
			sscanf(param_val, "%d-%d", &v1, &v2);
			tmp->port_first = (u16) v1;
			tmp->port_last = (u16) v2;
		}
		/*do not use %hud here, broken on Win32 (sscanf returns 1)*/
		else if (!stricmp(param_name, "client_port")) {
			sscanf(param_val, "%d-%d", &v1, &v2);
			tmp->client_port_first = (u16) v1;
			tmp->client_port_last = (u16) v2;
		}
		else if (!stricmp(param_name, "ssrc")) sscanf(param_val, "%X", &tmp->SSRC);
	}
	return tmp;
}



GF_Err gf_rtsp_parse_header(u8 *buffer, u32 BufferSize, u32 BodyStart, GF_RTSPCommand *com, GF_RTSPResponse *rsp)
{
	char LineBuffer[1024];
	char HeaderBuf[100], ValBuf[1024], temp[400];
	s32 Pos, LinePos;
	u32 HeaderLine;

	//then parse the full header
	LinePos = 0;
	strcpy(HeaderBuf, "");
	while (1) {
		LinePos = gf_token_get_line(buffer, LinePos, BufferSize, LineBuffer, 1024);
		if (LinePos <= 0) return GF_REMOTE_SERVICE_ERROR;

		//extract field header and value. Warning: some params (transport, ..) may be on several lines
		Pos = gf_token_get(LineBuffer, 0, ":\r\n", temp, 400);

		//end of header
		if (Pos <= 0) {
			HeaderLine = 2;
		}
		//this is a header
		else if (LineBuffer[0] != ' ') {
			HeaderLine = 1;
		} else {
			Pos = gf_token_get(LineBuffer, 0, ", \r\n", temp, 400);
			//end of header - process any pending one
			if (Pos <= 0) {
				HeaderLine = 2;
			} else {
				//n-line value - append
				strcat(ValBuf, "\r\n");
				strcat(ValBuf, temp);
				continue;
			}
		}
		//process current value
		if (HeaderLine && strlen(HeaderBuf)) {
			if (rsp) {
				gf_rtsp_set_response_value(rsp, HeaderBuf, ValBuf);
			}
			else {
				gf_rtsp_set_command_value(com, HeaderBuf, ValBuf);
			}
		}
		//done with the header
		if ( (HeaderLine == 2) || ((u32) LinePos >= BodyStart) ) return GF_OK;

		//process current line
		strcpy(HeaderBuf, temp);

		//skip ':'
		Pos += 1;
		//a server should normally reply with a space, but check it
		if (LineBuffer[Pos] == ' ') Pos += 1;
		/*!! empty value !! - DSS may send these for CSeq if something goes wrong*/
		if (!strcmp(LineBuffer+Pos, "\r\n")) {
			HeaderBuf[0] = 0;
			continue;
		}
		Pos = gf_token_get(LineBuffer, Pos, "\r\n", ValBuf, 400);
		if (Pos <= 0) break;

	}
	//if we get here we haven't reached the BodyStart
	return GF_REMOTE_SERVICE_ERROR;
}


GF_EXPORT
const char *gf_rtsp_nc_to_string(u32 ErrCode)
{
	switch (ErrCode) {
	case NC_RTSP_Continue:
		return "Continue";
	case NC_RTSP_OK:
		return "OK";
	case NC_RTSP_Created:
		return "Created";
	case NC_RTSP_Low_on_Storage_Space:
		return "Low on Storage Space";
	case NC_RTSP_Multiple_Choice:
		return "Multiple Choice";
	case NC_RTSP_Moved_Permanently:
		return "Moved Permanently";
	case NC_RTSP_Moved_Temporarily:
		return "Moved Temporarily";
	case NC_RTSP_See_Other:
		return "See Other";
	case NC_RTSP_Use_Proxy:
		return "Use Proxy";
	case NC_RTSP_Bad_Request:
		return "Bad Request";
	case NC_RTSP_Unauthorized:
		return "Unauthorized";
	case NC_RTSP_Payment_Required:
		return "Payment Required";
	case NC_RTSP_Forbidden:
		return "Forbidden";
	case NC_RTSP_Not_Found:
		return "Not Found";
	case NC_RTSP_Method_Not_Allowed:
		return "Method Not Allowed";
	case NC_RTSP_Not_Acceptable:
		return "Not Acceptable";
	case NC_RTSP_Proxy_Authentication_Required:
		return "Proxy Authentication Required";
	case NC_RTSP_Request_Timeout:
		return "Request Timeout";
	case NC_RTSP_Gone:
		return "Gone";
	case NC_RTSP_Length_Required:
		return "Length Required";
	case NC_RTSP_Precondition_Failed:
		return "Precondition Failed";
	case NC_RTSP_Request_Entity_Too_Large:
		return "Request Entity Too Large";
	case NC_RTSP_Request_URI_Too_Long:
		return "Request URI Too Long";
	case NC_RTSP_Unsupported_Media_Type:
		return "Unsupported Media Type";
	case NC_RTSP_Invalid_parameter:
		return "Invalid parameter";
	case NC_RTSP_Illegal_Conference_Identifier:
		return "Illegal Conference Identifier";
	case NC_RTSP_Not_Enough_Bandwidth:
		return "Not Enough Bandwidth";
	case NC_RTSP_Session_Not_Found:
		return "Session Not Found";
	case NC_RTSP_Method_Not_Valid_In_This_State:
		return "Method Not Valid In This State";
	case NC_RTSP_Header_Field_Not_Valid:
		return "Header Field Not Valid";
	case NC_RTSP_Invalid_Range:
		return "Invalid Range";
	case NC_RTSP_Parameter_Is_ReadOnly:
		return "Parameter Is Read-Only";
	case NC_RTSP_Aggregate_Operation_Not_Allowed:
		return "Aggregate Operation Not Allowed";
	case NC_RTSP_Only_Aggregate_Operation_Allowed:
		return "Only Aggregate Operation Allowed";
	case NC_RTSP_Unsupported_Transport:
		return "Unsupported Transport";
	case NC_RTSP_Destination_Unreachable:
		return "Destination Unreachable";
	case NC_RTSP_Internal_Server_Error:
		return "Internal Server Error";
	case NC_RTSP_Bad_Gateway:
		return "Bad Gateway";
	case NC_RTSP_Service_Unavailable:
		return "Service Unavailable";
	case NC_RTSP_Gateway_Timeout:
		return "Gateway Timeout";
	case NC_RTSP_RTSP_Version_Not_Supported:
		return "RTSP Version Not Supported";
	case NC_RTSP_Option_not_support:
		return "Option not support";

	case NC_RTSP_Not_Implemented:
	default:
		return "Not Implemented";
	}
}

#endif /*GPAC_DISABLE_STREAMING*/
