From 5125fc4191998ab13529ee59706f95116fc1725a Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 2 Feb 2006 19:16:07 +0000 Subject: Added SDP negotiator and changed SDP structs (tested) git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@129 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/build/pjmedia.dsp | 33 + pjmedia/build/pjmedia.dsw | 3 + pjmedia/build/pjmedia_test.dsp | 34 +- pjmedia/include/pjmedia.h | 5 +- pjmedia/include/pjmedia/errno.h | 250 ++++++++ pjmedia/include/pjmedia/sdp.h | 499 ++++++++++----- pjmedia/include/pjmedia/sdp_neg.h | 214 +++++++ pjmedia/include/pjmedia/types.h | 67 ++ pjmedia/src/pjmedia/errno.c | 139 +++++ pjmedia/src/pjmedia/jbuf.c | 2 +- pjmedia/src/pjmedia/sdp.c | 998 ++++++++++++++++++------------ pjmedia/src/pjmedia/sdp_cmp.c | 292 +++++++++ pjmedia/src/pjmedia/sdp_neg.c | 815 +++++++++++++++++++++++++ pjmedia/src/test/main.c | 24 +- pjmedia/src/test/sdp_neg_test.c | 1209 +++++++++++++++++++++++++++++++++++++ pjmedia/src/test/test.c | 75 +++ pjmedia/src/test/test.h | 37 ++ 17 files changed, 4117 insertions(+), 579 deletions(-) create mode 100644 pjmedia/include/pjmedia/errno.h create mode 100644 pjmedia/include/pjmedia/sdp_neg.h create mode 100644 pjmedia/include/pjmedia/types.h create mode 100644 pjmedia/src/pjmedia/errno.c create mode 100644 pjmedia/src/pjmedia/sdp_cmp.c create mode 100644 pjmedia/src/pjmedia/sdp_neg.c create mode 100644 pjmedia/src/test/sdp_neg_test.c create mode 100644 pjmedia/src/test/test.c create mode 100644 pjmedia/src/test/test.h (limited to 'pjmedia') diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp index c8f464b1..3f62d5ad 100644 --- a/pjmedia/build/pjmedia.dsp +++ b/pjmedia/build/pjmedia.dsp @@ -96,6 +96,10 @@ SOURCE=..\src\pjmedia\dsound.c # End Source File # Begin Source File +SOURCE=..\src\pjmedia\errno.c +# End Source File +# Begin Source File + SOURCE=..\src\pjmedia\g711.c # End Source File # Begin Source File @@ -129,7 +133,24 @@ SOURCE=..\src\pjmedia\sdp.c # End Source File # Begin Source File +SOURCE=..\src\pjmedia\sdp_cmp.c +# End Source File +# Begin Source File + +SOURCE=..\src\pjmedia\sdp_neg.c +# End Source File +# Begin Source File + SOURCE=..\src\pjmedia\session.c + +!IF "$(CFG)" == "pjmedia - Win32 Release" + +!ELSEIF "$(CFG)" == "pjmedia - Win32 Debug" + +# PROP Exclude_From_Build 1 + +!ENDIF + # End Source File # Begin Source File @@ -149,6 +170,10 @@ SOURCE=..\include\pjmedia\config.h # End Source File # Begin Source File +SOURCE=..\include\pjmedia\errno.h +# End Source File +# Begin Source File + SOURCE=..\include\pjmedia\jbuf.h # End Source File # Begin Source File @@ -173,6 +198,10 @@ SOURCE=..\include\pjmedia\sdp.h # End Source File # Begin Source File +SOURCE=..\include\pjmedia\sdp_neg.h +# End Source File +# Begin Source File + SOURCE=..\include\pjmedia\session.h # End Source File # Begin Source File @@ -183,6 +212,10 @@ SOURCE=..\include\pjmedia\sound.h SOURCE=..\include\pjmedia\stream.h # End Source File +# Begin Source File + +SOURCE=..\include\pjmedia\types.h +# End Source File # End Group # Begin Group "PortAudio" diff --git a/pjmedia/build/pjmedia.dsw b/pjmedia/build/pjmedia.dsw index 9b01973b..b51b3ce3 100644 --- a/pjmedia/build/pjmedia.dsw +++ b/pjmedia/build/pjmedia.dsw @@ -77,6 +77,9 @@ Package=<4> Begin Project Dependency Project_Dep_Name pjsdp End Project Dependency + Begin Project Dependency + Project_Dep_Name pjlib_util + End Project Dependency }}} ############################################################################### diff --git a/pjmedia/build/pjmedia_test.dsp b/pjmedia/build/pjmedia_test.dsp index ec3d4afe..eb073323 100644 --- a/pjmedia/build/pjmedia_test.dsp +++ b/pjmedia/build/pjmedia_test.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../pjlib/src" /I "../src" /I "../../pjsdp/src" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../pjlib/include" /I "../include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../pjlib/src" /I "../src" /I "../../pjsdp/src" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../../pjlib/include" /I "../include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe @@ -99,16 +99,46 @@ SOURCE=..\src\test\rtp_test.c # End Source File # Begin Source File +SOURCE=..\src\test\sdp_neg_test.c +# End Source File +# Begin Source File + SOURCE=..\src\test\sdptest.c + +!IF "$(CFG)" == "pjmedia_test - Win32 Release" + +!ELSEIF "$(CFG)" == "pjmedia_test - Win32 Debug" + +# PROP Exclude_From_Build 1 + +!ENDIF + # End Source File # Begin Source File SOURCE=..\src\test\session_test.c + +!IF "$(CFG)" == "pjmedia_test - Win32 Release" + +!ELSEIF "$(CFG)" == "pjmedia_test - Win32 Debug" + +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\src\test\test.c # End Source File # End Group # Begin Group "Header Files" # PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\src\test\test.h +# End Source File # End Group # Begin Group "Resource Files" diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h index b0310f24..7587b73e 100644 --- a/pjmedia/include/pjmedia.h +++ b/pjmedia/include/pjmedia.h @@ -19,14 +19,17 @@ #ifndef __PJMEDIA_H__ #define __PJMEDIA_H__ +#include +#include #include #include #include #include #include -#include +//#include #include #include +#include #endif /* __PJMEDIA_H__ */ diff --git a/pjmedia/include/pjmedia/errno.h b/pjmedia/include/pjmedia/errno.h new file mode 100644 index 00000000..1fe045e0 --- /dev/null +++ b/pjmedia/include/pjmedia/errno.h @@ -0,0 +1,250 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_ERRNO_H__ +#define __PJMEDIA_ERRNO_H__ + +#include + +PJ_BEGIN_DECL + + +/** + * Guidelines on error message length. + */ +#define PJMEDIA_ERR_MSG_SIZE 64 + +/** + * Get error message for the specified error code. + * + * @param status The error code. + * @param buffer The buffer where to put the error message. + * @param bufsize Size of the buffer. + * + * @return The error message as NULL terminated string, + * wrapped with pj_str_t. + */ +PJ_DECL(pj_str_t) pjmedia_strerror( pj_status_t status, char *buffer, + pj_size_t bufsize); + + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + */ +#define PJMEDIA_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE) + + +/************************************************************ + * GENERIC/GENERAL PJMEDIA ERRORS + ***********************************************************/ +/** + * @hideinitializer + * General/unknown PJMEDIA error. + */ +#define PJMEDIA_ERROR (PJMEDIA_ERRNO_START+1) /* 220001 */ + + +/************************************************************ + * SDP ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Generic invalid SDP descriptor. + */ +#define PJMEDIA_SDP_EINSDP (PJMEDIA_ERRNO_START+20) /* 220020 */ +/** + * @hideinitializer + * Invalid SDP version. + */ +#define PJMEDIA_SDP_EINVER (PJMEDIA_ERRNO_START+21) /* 220021 */ +/** + * @hideinitializer + * Invalid SDP origin (o=) line. + */ +#define PJMEDIA_SDP_EINORIGIN (PJMEDIA_ERRNO_START+22) /* 220022 */ +/** + * @hideinitializer + * Invalid SDP time (t=) line. + */ +#define PJMEDIA_SDP_EINTIME (PJMEDIA_ERRNO_START+23) /* 220023 */ +/** + * @hideinitializer + * Empty SDP subject/name (s=) line. + */ +#define PJMEDIA_SDP_EINNAME (PJMEDIA_ERRNO_START+24) /* 220024 */ +/** + * @hideinitializer + * Invalid SDP connection info (c=) line. + */ +#define PJMEDIA_SDP_EINCONN (PJMEDIA_ERRNO_START+25) /* 220025 */ +/** + * @hideinitializer + * Missing SDP connection info line. + */ +#define PJMEDIA_SDP_EMISSINGCONN (PJMEDIA_ERRNO_START+26) /* 220026 */ +/** + * @hideinitializer + * Invalid attribute (a=) line. + */ +#define PJMEDIA_SDP_EINATTR (PJMEDIA_ERRNO_START+27) /* 220027 */ +/** + * @hideinitializer + * Invalid rtpmap attribute. + */ +#define PJMEDIA_SDP_EINRTPMAP (PJMEDIA_ERRNO_START+28) /* 220028 */ +/** + * @hideinitializer + * rtpmap attribute is too long. + */ +#define PJMEDIA_SDP_ERTPMAPTOOLONG (PJMEDIA_ERRNO_START+29) /* 220029 */ +/** + * @hideinitializer + * rtpmap is missing for dynamic payload type. + */ +#define PJMEDIA_SDP_EMISSINGRTPMAP (PJMEDIA_ERRNO_START+30) /* 220030 */ +/** + * @hideinitializer + * Invalid SDP media (m=) line. + */ +#define PJMEDIA_SDP_EINMEDIA (PJMEDIA_ERRNO_START+31) /* 220031 */ +/** + * @hideinitializer + * No payload format in the media stream. + */ +#define PJMEDIA_SDP_ENOFMT (PJMEDIA_ERRNO_START+32) /* 220032 */ +/** + * @hideinitializer + * Invalid payload type in media. + */ +#define PJMEDIA_SDP_EINPT (PJMEDIA_ERRNO_START+33) /* 220033 */ +/** + * @hideinitializer + * Invalid fmtp attribute. + */ +#define PJMEDIA_SDP_EINFMTP (PJMEDIA_ERRNO_START+34) /* 220034 */ + + +/************************************************************ + * SDP NEGOTIATOR ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Invalid state to perform the specified operation. + */ +#define PJMEDIA_SDPNEG_EINSTATE (PJMEDIA_ERRNO_START+40) /* 220040 */ +/** + * @hideinitializer + * No currently active SDP. + */ +#define PJMEDIA_SDPNEG_ENOACTIVE (PJMEDIA_ERRNO_START+41) /* 220041 */ +/** + * @hideinitializer + * Media count mismatch in offer and answer. + */ +#define PJMEDIA_SDPNEG_EMISMEDIA (PJMEDIA_ERRNO_START+42) /* 220042 */ +/** + * @hideinitializer + * Media type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSMEDIA (PJMEDIA_ERRNO_START+43) /* 220043 */ +/** + * @hideinitializer + * Transport type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSTP (PJMEDIA_ERRNO_START+44) /* 220044 */ +/** + * @hideinitializer + * No common media payload is provided in the answer. + */ +#define PJMEDIA_SDPNEG_EANSNOMEDIA (PJMEDIA_ERRNO_START+45) /* 220045 */ + + +/************************************************************ + * SDP COMPARISON STATUS + ***********************************************************/ +/** + * @hideinitializer + * SDP media stream not equal. + */ +#define PJMEDIA_SDP_EMEDIANOTEQUAL (PJMEDIA_ERRNO_START+60) /* 220060 */ +/** + * @hideinitializer + * Port number in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_EPORTNOTEQUAL (PJMEDIA_ERRNO_START+61) /* 220061 */ +/** + * @hideinitializer + * Transport in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_ETPORTNOTEQUAL (PJMEDIA_ERRNO_START+62) /* 220062 */ +/** + * @hideinitializer + * Media format in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_EFORMATNOTEQUAL (PJMEDIA_ERRNO_START+63) /* 220063 */ +/** + * @hideinitializer + * SDP connection description not equal. + */ +#define PJMEDIA_SDP_ECONNNOTEQUAL (PJMEDIA_ERRNO_START+64) /* 220064 */ +/** + * @hideinitializer + * SDP attributes not equal. + */ +#define PJMEDIA_SDP_EATTRNOTEQUAL (PJMEDIA_ERRNO_START+65) /* 220065 */ +/** + * @hideinitializer + * SDP media direction not equal. + */ +#define PJMEDIA_SDP_EDIRNOTEQUAL (PJMEDIA_ERRNO_START+66) /* 220066 */ +/** + * @hideinitializer + * SDP fmtp attribute not equal. + */ +#define PJMEDIA_SDP_EFMTPNOTEQUAL (PJMEDIA_ERRNO_START+67) /* 220067 */ +/** + * @hideinitializer + * SDP ftpmap attribute not equal. + */ +#define PJMEDIA_SDP_ERTPMAPNOTEQUAL (PJMEDIA_ERRNO_START+68) /* 220068 */ +/** + * @hideinitializer + * SDP session descriptor not equal. + */ +#define PJMEDIA_SDP_ESESSNOTEQUAL (PJMEDIA_ERRNO_START+69) /* 220069 */ +/** + * @hideinitializer + * SDP origin not equal. + */ +#define PJMEDIA_SDP_EORIGINNOTEQUAL (PJMEDIA_ERRNO_START+70) /* 220070 */ +/** + * @hideinitializer + * SDP name/subject not equal. + */ +#define PJMEDIA_SDP_ENAMENOTEQUAL (PJMEDIA_ERRNO_START+71) /* 220071 */ +/** + * @hideinitializer + * SDP time not equal. + */ +#define PJMEDIA_SDP_ETIMENOTEQUAL (PJMEDIA_ERRNO_START+72) /* 220072 */ + + +PJ_END_DECL + +#endif /* __PJMEDIA_ERRNO_H__ */ + diff --git a/pjmedia/include/pjmedia/sdp.h b/pjmedia/include/pjmedia/sdp.h index 1b45744c..71f00817 100644 --- a/pjmedia/include/pjmedia/sdp.h +++ b/pjmedia/include/pjmedia/sdp.h @@ -16,8 +16,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef __PJSDP_SDP_H__ -#define __PJSDP_SDP_H__ +#ifndef __PJMEDIA_SDP_H__ +#define __PJMEDIA_SDP_H__ /** * @defgroup PJSDP SDP Library @@ -35,7 +35,7 @@ * print back the structure as SDP message. */ -#include +#include PJ_BEGIN_DECL @@ -43,160 +43,222 @@ PJ_BEGIN_DECL #define PJSDP_MAX_ATTR 32 #define PJSDP_MAX_MEDIA 16 -/** - * This enumeration describes the attribute type. + +/**************************************************************************** + * SDP ATTRIBUTES + **************************************************************************** + */ + +/** + * SDP generic attribute. */ -typedef enum pjsdp_attr_type_e +struct pjmedia_sdp_attr { - PJSDP_ATTR_RTPMAP, - PJSDP_ATTR_CAT, - PJSDP_ATTR_KEYWORDS, - PJSDP_ATTR_TOOL, - PJSDP_ATTR_PTIME, - PJSDP_ATTR_RECV_ONLY, - PJSDP_ATTR_SEND_ONLY, - PJSDP_ATTR_SEND_RECV, - PJSDP_ATTR_ORIENT, - PJSDP_ATTR_TYPE, - PJSDP_ATTR_CHARSET, - PJSDP_ATTR_SDP_LANG, - PJSDP_ATTR_LANG, - PJSDP_ATTR_FRAME_RATE, - PJSDP_ATTR_QUALITY, - PJSDP_ATTR_FMTP, - PJSDP_ATTR_INACTIVE, - PJSDP_ATTR_GENERIC, - PJSDP_END_OF_ATTR, -} pjsdp_attr_type_e; + pj_str_t name; /**< Attribute name. */ + pj_str_t value; /**< Attribute value. */ +}; /** - * This structure keeps the common attributes that all 'descendants' - * will have. + * Create SDP attribute. + * + * @param pool Pool to create the attribute. + * @param name Attribute name. + * @param value Optional attribute value. + * + * @return The new SDP attribute. */ -typedef struct pjsdp_attr -{ - pjsdp_attr_type_e type; /**< Attribute type. */ -} pjsdp_attr; +PJ_DECL(pjmedia_sdp_attr*) pjmedia_sdp_attr_create(pj_pool_t *pool, + const char *name, + const pj_str_t *value); +/** + * Clone attribute + * + * @param pool Pool to be used. + * @param attr The attribute to clone. + * + * @return New attribute as cloned from the attribute. + */ +PJ_DECL(pjmedia_sdp_attr*) pjmedia_sdp_attr_clone(pj_pool_t *pool, + const pjmedia_sdp_attr*attr); -/** - * This is the structure to represent generic attribute which has a - * string value. +/** + * Find the first attribute with the specified type. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * @param fmt Optional string to indicate which payload format + * to find for rtpmap and fmt attributes. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find2, pjmedia_sdp_media_find_attr */ -typedef struct pjsdp_attr_string -{ - pjsdp_attr_type_e type; - pj_str_t value; -} pjsdp_attr_string; +PJ_DECL(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find(unsigned count, + const pjmedia_sdp_attr *const attr_array[], + const pj_str_t *name, const pj_str_t *fmt); +/** + * Find the first attribute with the specified type. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * @param fmt Optional string to indicate which payload format + * to find for rtpmap and fmt attributes. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find, pjmedia_sdp_media_find_attr2 + */ +PJ_DECL(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find2(unsigned count, + const pjmedia_sdp_attr *const attr_array[], + const char *name, const pj_str_t *fmt); /** - * This is the structure to represent generic SDP attribute which has - * a numeric value. + * Add a new attribute to array of attributes. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param attr The attribute to add. + * + * @return PJ_SUCCESS or the error code. + * + * @see pjmedia_sdp_media_add_attr */ -typedef struct pjsdp_attr_num -{ - pjsdp_attr_type_e type; - pj_uint32_t value; -} pjsdp_attr_num; - +PJ_DECL(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr); /** - * SDP \a rtpmap attribute. + * Remove all attributes with the specified name when they present. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * + * @return Number of attributes removed. + * + * @see pjmedia_sdp_media_remove_all_attr */ -typedef struct pjsdp_rtpmap_attr -{ - pjsdp_attr_type_e type; - unsigned payload_type; - pj_str_t encoding_name; - unsigned clock_rate; - pj_str_t parameter; -} pjsdp_rtpmap_attr; +PJ_DECL(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, + pjmedia_sdp_attr *attr_array[], + const char *name); /** - * SDP \a fmtp attribute. + * Remove the specified attribute from the attribute array. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * + * @return PJ_SUCCESS when attribute has been removed, or + * PJ_ENOTFOUND when the attribute can not be found. + * + * @see pjmedia_sdp_media_remove_attr */ -typedef struct pjsdp_fmtp_attr -{ - pjsdp_attr_type_e type; - pj_str_t format; - pj_str_t param; -} pjsdp_fmtp_attr; +PJ_DECL(pj_status_t) pjmedia_sdp_attr_remove(unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr); -/** - * SDP generic attribute. +/** + * SDP \a rtpmap attribute. */ -typedef struct pjsdp_generic_attr +struct pjmedia_sdp_rtpmap { - pjsdp_attr_type_e type; - pj_str_t name; - pj_str_t value; -} pjsdp_generic_attr; - - -/** SDP \a cat attribute. */ -typedef struct pjsdp_attr_string pjsdp_cat_attr; - -/** SDP \a keywds attribute. */ -typedef struct pjsdp_attr_string pjsdp_keywds_attr; - -/** SDP \a tool attribute. */ -typedef struct pjsdp_attr_string pjsdp_tool_attr; - -/** SDP \a ptime attribute. */ -typedef struct pjsdp_attr_num pjsdp_ptime_attr; + pj_str_t pt; /**< Payload type. */ + pj_str_t enc_name; /**< Encoding name. */ + unsigned clock_rate; /**< Clock rate. */ + pj_str_t param; /**< Parameter. */ +}; -/** SDP \a recvonly attribute. */ -typedef struct pjsdp_attr pjsdp_recv_only_attr; -/** SDP \a sendonly attribute. */ -typedef struct pjsdp_attr pjsdp_send_only_attr; - -/** SDP \a sendrecv attribute. */ -typedef struct pjsdp_attr pjsdp_send_recv_attr; +/** + * Convert generic attribute to SDP rtpmap. + * + * @param pool Pool used to create the rtpmap attribute. + * @param attr Generic attribute to be converted to rtpmap, which + * name must be "rtpmap". + * @param p_rtpmap Pointer to receive SDP rtpmap attribute. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, + const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap **p_rtpmap); -/** SDP \a orient attribute. */ -typedef struct pjsdp_attr_string pjsdp_orient_attr; -/** SDP \a type attribute. */ -typedef struct pjsdp_attr_string pjsdp_type_attr; +/** + * Get the rtpmap representation of the same SDP attribute. + * + * @param attr Generic attribute to be converted to rtpmap, which + * name must be "rtpmap". + * @param rtpmap SDP rtpmap attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtpmap(const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap *rtpmap); -/** SDP \a charset attribute. */ -typedef struct pjsdp_attr_string pjsdp_charset_attr; -/** SDP \a sdplang attribute. */ -typedef struct pjsdp_attr_string pjsdp_sdp_lang_attr; +/** + * Convert rtpmap attribute to generic attribute. + * + * @param pool Pool to be used. + * @param rtpmap The rtpmap attribute. + * @param p_attr Pointer to receive the generic SDP attribute. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, + const pjmedia_sdp_rtpmap *rtpmap, + pjmedia_sdp_attr **p_attr); -/** SDP \a lang attribute. */ -typedef struct pjsdp_attr_string pjsdp_lang_attr; -/** SDP \a framerate attribute. */ -typedef struct pjsdp_attr_string pjsdp_frame_rate_attr; +/** + * SDP \a fmtp attribute. + */ +struct pjmedia_sdp_fmtp +{ + pj_str_t fmt; /**< Format type. */ + pj_str_t fmt_param; /**< Format specific parameter. */ +}; -/** SDP \a quality attribute. */ -typedef struct pjsdp_attr_num pjsdp_quality_attr; -/** SDP \a inactive attribute. */ -typedef struct pjsdp_attr pjsdp_inactive_attr; +/** + * Get the fmtp representation of the same SDP attribute. + * + * @param attr Generic attribute to be converted to fmtp, which + * name must be "fmtp". + * @param fmtp SDP fmtp attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_fmtp(const pjmedia_sdp_attr *attr, + pjmedia_sdp_fmtp *fmtp); -/** Clone attribute */ -PJ_DECL(pjsdp_attr*) pjsdp_attr_clone (pj_pool_t *pool, const pjsdp_attr *rhs); -/** Find attribute */ -PJ_DECL(const pjsdp_attr*) pjsdp_attr_find (int count, const pjsdp_attr *attr_array[], int type); +/**************************************************************************** + * SDP CONNECTION INFO + **************************************************************************** + */ /** * SDP connection info. */ -typedef struct pjsdp_conn_info +struct pjmedia_sdp_conn { - pj_str_t net_type; - pj_str_t addr_type; - pj_str_t addr; -} pjsdp_conn_info; + pj_str_t net_type; /**< Network type ("IN"). */ + pj_str_t addr_type; /**< Address type ("IP4", "IP6"). */ + pj_str_t addr; /**< The address. */ +}; + /** *Clone connection info. @@ -206,29 +268,37 @@ typedef struct pjsdp_conn_info * * @return the new connection info. */ -PJ_DECL(pjsdp_conn_info*) pjsdp_conn_info_clone (pj_pool_t *pool, - const pjsdp_conn_info *rhs); +PJ_DECL(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone(pj_pool_t *pool, + const pjmedia_sdp_conn *rhs); + + + +/**************************************************************************** + * SDP MEDIA INFO/LINE + **************************************************************************** + */ /** * SDP media description. */ -typedef struct pjsdp_media_desc +struct pjmedia_sdp_media { struct { - pj_str_t media; - pj_uint16_t port; - unsigned port_count; - pj_str_t transport; - unsigned fmt_count; - pj_str_t fmt[PJSDP_MAX_FMT]; + pj_str_t media; /**< Media type ("audio", "video") */ + pj_uint16_t port; /**< Port number. */ + unsigned port_count; /**< Port count, used only when >2 */ + pj_str_t transport; /**< Transport ("RTP/AVP") */ + unsigned fmt_count; /**< Number of formats. */ + pj_str_t fmt[PJSDP_MAX_FMT]; /**< Media formats. */ } desc; - pjsdp_conn_info *conn; - unsigned attr_count; - pjsdp_attr *attr[PJSDP_MAX_ATTR]; + pjmedia_sdp_conn *conn; /**< Optional connection info. */ + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr*attr[PJSDP_MAX_ATTR]; /**< Attributes. */ + +}; -} pjsdp_media_desc; /** * Clone SDP media description. @@ -238,63 +308,127 @@ typedef struct pjsdp_media_desc * * @return a new media description. */ -PJ_DECL(pjsdp_media_desc*) pjsdp_media_desc_clone (pj_pool_t *pool, - const pjsdp_media_desc *rhs); +PJ_DECL(pjmedia_sdp_media*) +pjmedia_sdp_media_clone( pj_pool_t *pool, + const pjmedia_sdp_media *rhs); -/** - * Check if the media description has the specified attribute. +/** + * Find the first occurence of the specified attribute name. * * @param m The SDP media description. - * @param attr_type The attribute type. + * @param name Attribute name to find. + * @param fmt Optional payload format type to find in the + * attribute list. The payload format type will be + * compared for attributes such as rtpmap and fmtp. * - * @return nonzero if true. + * @return The first instance of the specified attribute or NULL. */ -PJ_DECL(pj_bool_t) pjsdp_media_desc_has_attr (const pjsdp_media_desc *m, - pjsdp_attr_type_e attr_type); +PJ_DECL(pjmedia_sdp_attr*) +pjmedia_sdp_media_find_attr(const pjmedia_sdp_media *m, + const pj_str_t *name, const pj_str_t *fmt); -/** - * Find rtpmap attribute for the specified payload type. + +/** + * Find the first occurence of the specified attribute name. + * + * @param m The SDP media description. + * @param name Attribute name to find. + * @param fmt Optional payload format type to find in the + * attribute list. The payload format type will be + * compared for attributes such as rtpmap and fmtp. + * + * @return The first instance of the specified attribute or NULL. + */ +PJ_DECL(pjmedia_sdp_attr*) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, + const char *name, const pj_str_t *fmt); + +/** + * Add new attribute to the media descriptor. * - * @param m The SDP media description. - * @param pt RTP payload type. + * @param m The SDP media description. + * @param name Attribute to add. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_add_attr(pjmedia_sdp_media *m, + pjmedia_sdp_attr *attr); + +/** + * Remove all attributes with the specified name. + * + * @param m The SDP media description. + * @param name Attribute name to remove. * - * @return the SDP rtpmap attribute for the payload type, or NULL if not found. + * @return The number of attributes removed. + */ +PJ_DECL(unsigned) +pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m, + const char *name); + + +/** + * Remove the occurence of the specified attribute. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m, + pjmedia_sdp_attr *attr); + + +/** + * Compare two SDP media for equality. + * + * @param sd1 The first SDP media to compare. + * @param sd2 The second SDP media to compare. + * @param option Comparison option. + * + * @return PJ_SUCCESS when both SDP medias are equal. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_cmp(const pjmedia_sdp_media *sd1, + const pjmedia_sdp_media *sd2, + unsigned option); + + + +/**************************************************************************** + * SDP SESSION DESCRIPTION + **************************************************************************** */ -PJ_DECL(const pjsdp_rtpmap_attr*) -pjsdp_media_desc_find_rtpmap (const pjsdp_media_desc *m, unsigned pt); /** * This structure describes SDP session description. */ -typedef struct pjsdp_session_desc +struct pjmedia_sdp_session { + /** Origin (o= line) */ struct { - pj_str_t user; - pj_uint32_t id; - pj_uint32_t version; - pj_str_t net_type; - pj_str_t addr_type; - pj_str_t addr; + pj_str_t user; /**< User */ + pj_uint32_t id; /**< Session ID */ + pj_uint32_t version; /**< Session version */ + pj_str_t net_type; /**< Network type ("IN") */ + pj_str_t addr_type; /**< Address type ("IP4", "IP6") */ + pj_str_t addr; /**< The address. */ } origin; - pj_str_t name; - pjsdp_conn_info *conn; + pj_str_t name; /**< Subject line (s=) */ + pjmedia_sdp_conn *conn; /**< Connection line (c=) */ + /** Session time (t= line) */ struct { - pj_uint32_t start; - pj_uint32_t stop; + pj_uint32_t start; /**< Start time. */ + pj_uint32_t stop; /**< Stop time. */ } time; - unsigned attr_count; - pjsdp_attr *attr[PJSDP_MAX_ATTR]; + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr *attr[PJSDP_MAX_ATTR]; /**< Attributes array. */ - unsigned media_count; - pjsdp_media_desc *media[PJSDP_MAX_MEDIA]; + unsigned media_count; /**< Number of media. */ + pjmedia_sdp_media *media[PJSDP_MAX_MEDIA]; /**< Media array. */ -} pjsdp_session_desc; +}; /** @@ -306,8 +440,9 @@ typedef struct pjsdp_session_desc * * @return SDP session description. */ -PJ_DECL(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, - pj_pool_t *pool); +PJ_DECL(pj_status_t) pjmedia_sdp_parse( pj_pool_t *pool, + char *buf, pj_size_t len, + pjmedia_sdp_session **p_sdp ); /** * Print SDP description to a buffer. @@ -318,15 +453,47 @@ PJ_DECL(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, * * @return the length printed, or -1. */ -PJ_DECL(int) pjsdp_print( const pjsdp_session_desc *desc, - char *buf, pj_size_t size); +PJ_DECL(int) pjmedia_sdp_print( const pjmedia_sdp_session *desc, + char *buf, pj_size_t size); /** - * @} + * Validate SDP descriptor. */ +PJ_DECL(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp); + + +/** + * Clone SDP session. + * + * @param pool The pool used to clone the session. + * @param sess The SDP session to clone. + * + * @return New SDP session. + */ +PJ_DECL(pjmedia_sdp_session*) +pjmedia_sdp_session_clone( pj_pool_t *pool, + const pjmedia_sdp_session *sess); + + +/** + * Compare two SDP session for equality. + * + * @param sd1 The first SDP session to compare. + * @param sd2 The second SDP session to compare. + * + * @return PJ_SUCCESS when both SDPs are equal. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_session_cmp(const pjmedia_sdp_session *sd1, + const pjmedia_sdp_session *sd2, + unsigned option); + PJ_END_DECL -#endif /* __PJSDP_SDP_H__ */ +/** + * @} + */ + +#endif /* __PJMEDIA_SDP_H__ */ diff --git a/pjmedia/include/pjmedia/sdp_neg.h b/pjmedia/include/pjmedia/sdp_neg.h new file mode 100644 index 00000000..7d0cc086 --- /dev/null +++ b/pjmedia/include/pjmedia/sdp_neg.h @@ -0,0 +1,214 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_SDP_NEG_H__ +#define __PJMEDIA_SDP_NEG_H__ + + +/** + * @defgroup PJSDP SDP Library + */ +/** + * @file sdp_neg.h + * @brief SDP negotiator header file. + */ +/** + * @defgroup PJ_SDP_NEG SDP Negotiator. + * @ingroup PJSDP + * @{ + *. + */ + +#include + +PJ_BEGIN_DECL + +/** + * This enumeration describes SDP negotiation state. + */ +enum pjmedia_sdp_neg_state +{ + /** + * This is the state of SDP negoator before it is initialized. + */ + PJMEDIA_SDP_NEG_STATE_NULL, + + /** + * This state occurs when SDP negotiator has sent our offer to remote and + * it is waiting for answer. + */ + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + + /** + * This state occurs when an offer (either local or remote) has been + * provided with answer. The SDP negotiator is ready to negotiate both + * session descriptors. + */ + PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + + /** + * This state occurs when SDP negotiation has completed, either + * successfully or not. + */ + PJMEDIA_SDP_NEG_STATE_DONE, +}; + +/* Negotiator state: + * + * reinit_local_offer() + * modify_local_offer() + * create_w_local_offer() +-------------+ tx_local_offer() + * /------------------------->| LOCAL_OFFER |<----------------------\ + * | +-------------+ | + * | | | + * | rx_remote_answer() | | + * | V | + * +--+---+ +-----------+ negotiate() +------+ + * + NULL |------------------------>| WAIT_NEGO |-------------------->| DONE | + * +------+ create_w_remote_offer() +-----------+ +------+ + * A | + * | rx_remote_offer() | + * \-----------------------------/ + */ + +/** + * Create the SDP negotiator with local offer. The SDP negotiator then + * will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER state, where it waits + * until it receives answer from remote. When SDP answer from remote is + * received, application should call #pjmedia_sdp_neg_rx_remote_answer(). + * + * After calling this function, application should send the local SDP offer + * to remote party and wait for SDP answer. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool, + const pjmedia_sdp_session *local, + pjmedia_sdp_neg **p_neg); + +/** + * Initialize the SDP negotiator with both local and remote offer. + * Application normally calls this function when it receives initial offer + * from remote. Application must also provide initial local offer when + * calling this function. After this function is called, the SDP negotiator + * state will move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and the negotiation + * function can be called. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, + const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, + pjmedia_sdp_neg **p_neg); + +/** + * Get SDP negotiator state. + */ +PJ_DECL(pjmedia_sdp_neg_state) +pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg ); + +/** + * Get the currently active local SDP. Application can only call this + * function after negotiation has been done, or otherwise there won't be + * active SDPs. Calling this function will not change the state of the + * negotiator. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_local( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **local); + +/** + * Get the currently active remote SDP. Application can only call this + * function after negotiation has been done, or otherwise there won't be + * active SDPs. Calling this function will not change the state of the + * negotiator. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote); + + +/** + * Completely replaces local offer with new SDP. After calling + * This function can only be called in state PJMEDIA_SDP_NEG_STATE_DONE. + * this function, application can send the modified offer to remote. + * The negotiator state will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + * where it waits for SDP answer from remote. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *local); + +/** + * Negotiate local and remote answer. Before calling this function, the + * SDP negotiator must be in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO state. + * After calling this function, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_DONE. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + pj_bool_t allow_asym); + + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. + * Application calls this function to retrieve currently active + * local SDP to be sent to remote. The negotiator state will then move + * to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it waits for SDP answer + * from remote. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_tx_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **offer); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. + * Application calls this function when it receives SDP offer from remote. + * After this function is called, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the + * negotiation function #pjmedia_sdp_neg_negotiate(). + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_rx_remote_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote); + + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER + * state. Application calls this function when it receives SDP answer + * from remote. After this function is called, the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the + * negotiation function #pjmedia_sdp_neg_negotiate(). + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_rx_remote_answer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote); + + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_SDP_NEG_H__ */ + diff --git a/pjmedia/include/pjmedia/types.h b/pjmedia/include/pjmedia/types.h new file mode 100644 index 00000000..217a001b --- /dev/null +++ b/pjmedia/include/pjmedia/types.h @@ -0,0 +1,67 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_TYPES_H__ +#define __PJMEDIA_TYPES_H__ + +#include + + +/** + * Forward declaration for SDP attribute (sdp.h) + */ +typedef struct pjmedia_sdp_attr pjmedia_sdp_attr; + +/** + * Forward declaration for SDP rtpmap attribute (sdp.h) + */ +typedef struct pjmedia_sdp_rtpmap pjmedia_sdp_rtpmap; + +/** + * Forward declaration for SDP fmtp attribute (sdp.h) + */ +typedef struct pjmedia_sdp_fmtp pjmedia_sdp_fmtp; + +/** + * Forward declaration for SDP connection info (sdp.h) + */ +typedef struct pjmedia_sdp_conn pjmedia_sdp_conn; + +/** + * Forward declaration for SDP media line (sdp.h) + */ +typedef struct pjmedia_sdp_media pjmedia_sdp_media; + +/** + * Forward declaration for SDP session (sdp.h) + */ +typedef struct pjmedia_sdp_session pjmedia_sdp_session; + +/** + * Forward declaration for SDP negotiator state (sdp_neg.h). + */ +typedef enum pjmedia_sdp_neg_state pjmedia_sdp_neg_state; + +/** + * Forward declaration for SDP negotiator (sdp_neg.h). + */ +typedef struct pjmedia_sdp_neg pjmedia_sdp_neg; + + +#endif /* __PJMEDIA_TYPES_H__ */ + diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c new file mode 100644 index 00000000..ccf5c8d8 --- /dev/null +++ b/pjmedia/src/pjmedia/errno.c @@ -0,0 +1,139 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + + + +/* PJMEDIA's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ +static const struct +{ + int code; + const char *msg; +} err_str[] = +{ + /* Generic PJMEDIA errors, shouldn't be used! */ + { PJMEDIA_ERROR, "Unspecified PJMEDIA error" }, + + /* SDP error. */ + { PJMEDIA_SDP_EINSDP, "Invalid SDP descriptor" }, + { PJMEDIA_SDP_EINVER, "Invalid SDP version line" }, + { PJMEDIA_SDP_EINORIGIN, "Invalid SDP origin line" }, + { PJMEDIA_SDP_EINTIME, "Invalid SDP time line"}, + { PJMEDIA_SDP_EINNAME, "SDP name/subject line is empty"}, + { PJMEDIA_SDP_EINCONN, "Invalid SDP connection line"}, + { PJMEDIA_SDP_EMISSINGCONN, "Missing SDP connection info line"}, + { PJMEDIA_SDP_EINATTR, "Invalid SDP attributes"}, + { PJMEDIA_SDP_EINRTPMAP, "Invalid SDP rtpmap attribute"}, + { PJMEDIA_SDP_ERTPMAPTOOLONG, "SDP rtpmap attribute too long"}, + { PJMEDIA_SDP_EMISSINGRTPMAP, "Missing SDP rtpmap for dynamic payload type"}, + { PJMEDIA_SDP_EINMEDIA, "Invalid SDP media line" }, + { PJMEDIA_SDP_ENOFMT, "No SDP payload format in the media line" }, + { PJMEDIA_SDP_EINPT, "Invalid SDP payload type in media line" }, + { PJMEDIA_SDP_EINFMTP, "Invalid SDP fmtp attribute" }, + + /* SDP negotiator errors. */ + { PJMEDIA_SDPNEG_EINSTATE, "Invalid SDP negotiator state for operation" }, + { PJMEDIA_SDPNEG_ENOACTIVE, "No active SDP in SDP negotiator" }, + { PJMEDIA_SDPNEG_EMISMEDIA, "SDP media count mismatch in offer/answer" }, + { PJMEDIA_SDPNEG_EINVANSMEDIA, "SDP media type mismatch in offer/answer" }, + { PJMEDIA_SDPNEG_EINVANSTP, "SDP media transport type mismatch in offer/answer" }, + { PJMEDIA_SDPNEG_EANSNOMEDIA, "No common SDP media payload in answer" }, + + /* SDP comparison results */ + { PJMEDIA_SDP_EMEDIANOTEQUAL, "SDP media descriptor not equal" }, + { PJMEDIA_SDP_EPORTNOTEQUAL, "Port in SDP media descriptor not equal" }, + { PJMEDIA_SDP_ETPORTNOTEQUAL, "Transport in SDP media descriptor not equal" }, + { PJMEDIA_SDP_EFORMATNOTEQUAL, "Format in SDP media descriptor not equal" }, + { PJMEDIA_SDP_ECONNNOTEQUAL, "SDP connection line not equal" }, + { PJMEDIA_SDP_EATTRNOTEQUAL, "SDP attributes not equal" }, + { PJMEDIA_SDP_EDIRNOTEQUAL, "SDP media direction not equal" }, + { PJMEDIA_SDP_EFMTPNOTEQUAL, "SDP fmtp attribute not equal" }, + { PJMEDIA_SDP_ERTPMAPNOTEQUAL, "SDP rtpmap attribute not equal" }, + { PJMEDIA_SDP_ESESSNOTEQUAL, "SDP session descriptor not equal" }, + { PJMEDIA_SDP_EORIGINNOTEQUAL, "SDP origin line not equal" }, + { PJMEDIA_SDP_ENAMENOTEQUAL, "SDP name/subject line not equal" }, + { PJMEDIA_SDP_ETIMENOTEQUAL, "SDP time line not equal" }, +}; + + + +/* + * pjmedia_strerror() + */ +PJ_DEF(pj_str_t) pjmedia_strerror( pj_status_t statcode, + char *buf, pj_size_t bufsize ) +{ + pj_str_t errstr; + + if (statcode >= PJMEDIA_ERRNO_START && + statcode < PJMEDIA_ERRNO_START + PJ_ERRNO_SPACE_SIZE) + { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n/2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid+1; + n -= (half+1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char*)err_str[first].msg; + msg.slen = pj_native_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } else { + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_snprintf(buf, bufsize, + "Unknown error %d", + statcode); + + return errstr; + } + } + else { + /* Not our code. Give it to PJLIB. */ + return pj_strerror(statcode, buf, bufsize); + } + +} + diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c index ce00d970..7661bcce 100644 --- a/pjmedia/src/pjmedia/jbuf.c +++ b/pjmedia/src/pjmedia/jbuf.c @@ -300,7 +300,7 @@ PJ_DEF(pj_status_t) pj_jb_put( JB *jb, pj_uint32_t extseq, void *buf ) jb->level++; } - if (jb->lst.count > jb->max_level) + if ((int)jb->lst.count > jb->max_level) jb->max_level++; jb->last_op = JB_PUT; diff --git a/pjmedia/src/pjmedia/sdp.c b/pjmedia/src/pjmedia/sdp.c index afd746ee..2f6dcfe3 100644 --- a/pjmedia/src/pjmedia/sdp.c +++ b/pjmedia/src/pjmedia/sdp.c @@ -17,13 +17,17 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include #include +#include #include #include #include #include #include #include +#include + enum { SKIP_WS = 0, @@ -31,95 +35,32 @@ enum { }; #define TOKEN "-.!%*_=`'~" #define NTP_OFFSET ((pj_uint32_t)2208988800) -#define LOG_THIS "sdp" +#define THIS_FILE "sdp.c" -/* - * Prototypes for line parser. - */ -static void parse_version(pj_scanner *scanner); -static void parse_origin(pj_scanner *scanner, pjsdp_session_desc *ses); -static void parse_time(pj_scanner *scanner, pjsdp_session_desc *ses); -static void parse_generic_line(pj_scanner *scanner, pj_str_t *str); -static void parse_connection_info(pj_scanner *scanner, pjsdp_conn_info *conn); -static pjsdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner); -static void parse_media(pj_scanner *scanner, pjsdp_media_desc *med); - -/* - * Prototypes for attribute parsers. - */ -static pjsdp_rtpmap_attr * parse_rtpmap_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_attr_string * parse_generic_string_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_attr_num * parse_generic_num_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_attr * parse_name_only_attr( pj_pool_t *pool, pj_scanner *scanner ); -static pjsdp_fmtp_attr * parse_fmtp_attr( pj_pool_t *pool, pj_scanner *scanner ); - - -/* - * Prototypes for functions to print attribute. - * All of them returns integer for the length printed, or -1 on error. - */ -static int print_rtpmap_attr(const pjsdp_rtpmap_attr *attr, - char *buf, int length); -static int print_generic_string_attr(const pjsdp_attr_string *attr, - char *buf, int length); -static int print_generic_num_attr(const pjsdp_attr_num *attr, - char *buf, int length); -static int print_name_only_attr(const pjsdp_attr *attr, - char *buf, int length); -static int print_fmtp_attr(const pjsdp_fmtp_attr *attr, - char *buf, int length); - -/* - * Prototypes for cloning attributes. - */ -static pjsdp_attr* clone_rtpmap_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_generic_string_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_generic_num_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_name_only_attr (pj_pool_t *pool, const pjsdp_attr *rhs); -static pjsdp_attr* clone_fmtp_attr (pj_pool_t *pool, const pjsdp_attr *rhs); +typedef struct parse_context +{ + pj_status_t last_error; +} parse_context; /* - * Prototypes + * Prototypes for line parser. */ -static void init_sdp_parser(void); +static void parse_version(pj_scanner *scanner, parse_context *ctx); +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx); +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx); +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, + parse_context *ctx); +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, + parse_context *ctx); +static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner, + parse_context *ctx); +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, + parse_context *ctx); -typedef void * (*FPARSE)(pj_pool_t *pool, pj_scanner *scanner); -typedef int (*FPRINT)(const void *attr, char *buf, int length); -typedef pjsdp_attr* (*FCLONE)(pj_pool_t *pool, const pjsdp_attr *rhs); - -/* - * Array of functions to print attribute. - */ -static struct attr_map_rec -{ - pj_str_t name; - FPARSE parse_attr; - FPRINT print_attr; - FCLONE clone; -} attr_map[] = -{ - {{"rtpmap", 6}, (FPARSE)&parse_rtpmap_attr, (FPRINT)&print_rtpmap_attr, (FCLONE)&clone_rtpmap_attr}, - {{"cat", 3}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"keywds", 6}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"tool", 4}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"ptime", 5}, (FPARSE)&parse_generic_num_attr, (FPRINT)&print_generic_num_attr, (FCLONE)&clone_generic_num_attr}, - {{"recvonly", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"sendonly", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"sendrecv", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"orient", 6}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"type", 4}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"charset", 7}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"sdplang", 7}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"lang", 4}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"framerate", 9}, (FPARSE)&parse_generic_string_attr, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr}, - {{"quality", 7}, (FPARSE)&parse_generic_num_attr, (FPRINT)&print_generic_num_attr, (FCLONE)&clone_generic_num_attr}, - {{"fmtp", 4}, (FPARSE)&parse_fmtp_attr, (FPRINT)&print_fmtp_attr, (FCLONE)&clone_fmtp_attr}, - {{"inactive", 8}, (FPARSE)&parse_name_only_attr, (FPRINT)&print_name_only_attr, (FCLONE)&clone_name_only_attr}, - {{"", 0}, NULL, (FPRINT)&print_generic_string_attr, (FCLONE)&clone_generic_string_attr} -}; - /* * Scanner character specification. */ @@ -143,213 +84,358 @@ static void init_sdp_parser(void) pj_cis_add_str(&cs_token, TOKEN); } -static int print_rtpmap_attr(const pjsdp_rtpmap_attr *rtpmap, - char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create( pj_pool_t *pool, + const char *name, + const pj_str_t *value) { - char *p = buf; - - if (len < 16+rtpmap->encoding_name.slen+rtpmap->parameter.slen) { - return -1; - } - - /* colon and payload type. */ - *p++ = ':'; - len = pj_utoa(rtpmap->payload_type, p); - p += len; + pjmedia_sdp_attr *attr; - /* space, encoding name */ - *p++ = ' '; - pj_memcpy(p, rtpmap->encoding_name.ptr, rtpmap->encoding_name.slen); - p += rtpmap->encoding_name.slen; + PJ_ASSERT_RETURN(pool && name, NULL); - /* slash, clock-rate. */ - *p++ = '/'; - len = pj_utoa(rtpmap->clock_rate, p); - p += len; + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); + pj_strdup2(pool, &attr->name, name); - /* optionally add encoding parameter. */ - if (rtpmap->parameter.slen) { - *p++ = '/'; - pj_memcpy(p, rtpmap->parameter.ptr, rtpmap->parameter.slen); - p += rtpmap->parameter.slen; + if (value) + pj_strdup(pool, &attr->value, value); + else { + attr->value.ptr = NULL; + attr->value.slen = 0; } - return p-buf; + return attr; } -static int print_generic_string_attr(const pjsdp_attr_string *attr, - char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_clone(pj_pool_t *pool, + const pjmedia_sdp_attr *rhs) { - char *p = buf; + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && rhs, NULL); - if (len < attr->value.slen + 4) { - return -1; - } + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); - /* colon and attribute value. */ - *p++ = ':'; - pj_memcpy(p, attr->value.ptr, attr->value.slen); - p += attr->value.slen; + pj_strdup(pool, &attr->name, &rhs->name); + pj_strdup(pool, &attr->value, &rhs->value); - return p-buf; + return attr; } -static int print_generic_num_attr(const pjsdp_attr_num *attr, char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find (unsigned count, + const pjmedia_sdp_attr *const attr_array[], + const pj_str_t *name, + const pj_str_t *c_fmt) { - char *p = buf; + char fmtbuf[16]; + pj_str_t fmt = { NULL, 0}; + unsigned i; - if (len < 10) { - return -1; + if (c_fmt) { + /* To search the format, we prepend the string with a colon and + * append space + */ + PJ_ASSERT_RETURN(c_fmt->slenslen + 2; + fmtbuf[0] = ':'; + pj_memcpy(fmt.ptr+1, c_fmt->ptr, c_fmt->slen); + fmtbuf[c_fmt->slen+1] = ' '; + + } + + for (i=0; iname, name) == 0) { + const pjmedia_sdp_attr *a = attr_array[i]; + if (c_fmt) { + if (a->value.slen > fmt.slen && + pj_strncmp(&a->value, &fmt, fmt.slen)==0) + { + return (pjmedia_sdp_attr*)a; + } + } else + return (pjmedia_sdp_attr*)a; + } } - *p++ = ':'; - return pj_utoa(attr->value, p); + return NULL; } -static int print_name_only_attr(const pjsdp_attr *attr, char *buf, int len) +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find2(unsigned count, + const pjmedia_sdp_attr *const attr_array[], + const char *c_name, + const pj_str_t *c_fmt) { - PJ_UNUSED_ARG(attr); - PJ_UNUSED_ARG(buf); - PJ_UNUSED_ARG(len); - return 0; + pj_str_t name; + + name.ptr = (char*)c_name; + name.slen = pj_native_strlen(c_name); + + return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt); } -static int print_fmtp_attr(const pjsdp_fmtp_attr *fmtp, char *buf, int len) + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr) { - char *p = buf; + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(*count < PJSDP_MAX_ATTR, PJ_ETOOMANY); - if (len < 4+fmtp->format.slen+fmtp->param.slen) { - return -1; - } + attr_array[*count] = attr; + (*count)++; - /* colon and format. */ - *p++ = ':'; - pj_memcpy(p, fmtp->format.ptr, fmtp->format.slen); - p += fmtp->format.slen; + return PJ_SUCCESS; +} - /* space and parameter. */ - *p++ = ' '; - pj_memcpy(p, fmtp->param.ptr, fmtp->param.slen); - p += fmtp->param.slen; - return p-buf; +PJ_DEF(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, + pjmedia_sdp_attr *attr_array[], + const char *name) +{ + unsigned i, removed = 0; + pj_str_t attr_name; + + PJ_ASSERT_RETURN(count && attr_array && name, PJ_EINVAL); + + attr_name.ptr = (char*)name; + attr_name.slen = pj_native_strlen(name); + + for (i=0; i<*count; ) { + if (pj_strcmp(&attr_array[i]->name, &attr_name)==0) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*), + *count, i); + --(*count); + ++removed; + } else { + ++i; + } + } + + return removed; } -static int print_attr(const pjsdp_attr *attr, char *buf, int len) +PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove( unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr ) { - char *p = buf; - struct attr_map_rec *desc = &attr_map[attr->type]; - - if (len < 16) { - return -1; + unsigned i, removed=0; + + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + + for (i=0; i<*count; ) { + if (attr_array[i] == attr) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*), + *count, i); + --(*count); + ++removed; + } else { + ++i; + } } - *p++ = 'a'; - *p++ = '='; - pj_memcpy(p, desc->name.ptr, desc->name.slen); - p += desc->name.slen; - - len = (*desc->print_attr)(attr, p, (buf+len)-p); - if (len < 0) { - return -1; - } - p += len; - *p++ = '\r'; - *p++ = '\n'; - return p-buf; + return removed ? PJ_SUCCESS : PJ_ENOTFOUND; } -static pjsdp_attr* clone_rtpmap_attr (pj_pool_t *pool, const pjsdp_attr *p) + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap( const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap *rtpmap) { - const pjsdp_rtpmap_attr *rhs = (const pjsdp_rtpmap_attr*)p; - pjsdp_rtpmap_attr *attr = pj_pool_alloc (pool, sizeof(pjsdp_rtpmap_attr)); - if (!attr) - return NULL; + const char *p = attr->value.ptr; + const char *end = attr->value.ptr + attr->value.slen; + pj_str_t token; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtpmap")==0, PJ_EINVALIDOP); + + /* rtpmap sample: + * a=rtpmap:98 L16/16000/2. + */ + + /* Eat the first ':' */ + if (*p != ':') return PJMEDIA_SDP_EINRTPMAP; + + /* Get ':' */ + ++p; + + /* Get payload type. */ + token.ptr = (char*)p; + while (pj_isdigit(*p) && p!=end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINRTPMAP; + + rtpmap->pt = token; + + /* Expecting space after payload type. */ + if (*p != ' ') return PJMEDIA_SDP_EINRTPMAP; + + /* Get space. */ + ++p; + + /* Get encoding name. */ + token.ptr = (char*)p; + while (*p != '/' && p != end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINRTPMAP; + rtpmap->enc_name = token; + + /* Expecting '/' after encoding name. */ + if (*p != '/') return PJMEDIA_SDP_EINRTPMAP; + + /* Get '/' */ + ++p; + + /* Get the clock rate. */ + token.ptr = (char*)p; + while (pj_isdigit(*p) && p != end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINRTPMAP; + + rtpmap->clock_rate = pj_strtoul(&token); + + /* Expecting either '/' or EOF */ + if (*p != '/' && p != end) + return PJMEDIA_SDP_EINRTPMAP; + + if (*p == '/') { + ++p; + token.ptr = (char*)p; + token.slen = end-p; + rtpmap->param = token; + } else { + rtpmap->param.ptr = NULL; + rtpmap->param.slen = 0; + } - attr->type = rhs->type; - attr->payload_type = rhs->payload_type; - if (!pj_strdup (pool, &attr->encoding_name, &rhs->encoding_name)) return NULL; - attr->clock_rate = rhs->clock_rate; - if (!pj_strdup (pool, &attr->parameter, &rhs->parameter)) return NULL; + return PJ_SUCCESS; - return (pjsdp_attr*)attr; } -static pjsdp_attr* clone_generic_string_attr (pj_pool_t *pool, const pjsdp_attr *p) +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp( const pjmedia_sdp_attr *attr, + pjmedia_sdp_fmtp *fmtp) { - const pjsdp_attr_string* rhs = (const pjsdp_attr_string*) p; - pjsdp_attr_string *attr = pj_pool_alloc (pool, sizeof(pjsdp_attr_string)); - if (!attr) - return NULL; + const char *p = attr->value.ptr; + const char *end = attr->value.ptr + attr->value.slen; + pj_str_t token; - attr->type = rhs->type; - if (!pj_strdup (pool, &attr->value, &rhs->value)) return NULL; + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "fmtp")==0, PJ_EINVALIDOP); - return (pjsdp_attr*)attr; -} + /* fmtp BNF: + * a=fmtp: + */ -static pjsdp_attr* clone_generic_num_attr (pj_pool_t *pool, const pjsdp_attr *p) -{ - const pjsdp_attr_num* rhs = (const pjsdp_attr_num*) p; - pjsdp_attr_num *attr = pj_pool_alloc (pool, sizeof(pjsdp_attr_num)); - if (!attr) - return NULL; + /* Eat the first ':' */ + if (*p != ':') return PJMEDIA_SDP_EINFMTP; - attr->type = rhs->type; - attr->value = rhs->value; + /* Get ':' */ + ++p; - return (pjsdp_attr*)attr; -} + /* Get format. */ + token.ptr = (char*)p; + while (pj_isdigit(*p) && p!=end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINFMTP; -static pjsdp_attr* clone_name_only_attr (pj_pool_t *pool, const pjsdp_attr *rhs) -{ - pjsdp_attr *attr = pj_pool_alloc (pool, sizeof(pjsdp_attr)); - if (!attr) - return NULL; + fmtp->fmt = token; - attr->type = rhs->type; - return attr; -} + /* Expecting space after format. */ + if (*p != ' ') return PJMEDIA_SDP_EINFMTP; -static pjsdp_attr* clone_fmtp_attr (pj_pool_t *pool, const pjsdp_attr *p) -{ - const pjsdp_fmtp_attr* rhs = (const pjsdp_fmtp_attr*) p; - pjsdp_fmtp_attr *attr = pj_pool_alloc (pool, sizeof(pjsdp_fmtp_attr)); - if (!attr) - return NULL; + /* Get space. */ + ++p; - attr->type = rhs->type; - if (!pj_strdup (pool, &attr->format, &rhs->format)) return NULL; - if (!pj_strdup (pool, &attr->param, &rhs->param)) return NULL; + /* Set the remaining string as fmtp format parameter. */ + fmtp->fmt_param.ptr = (char*)p; + fmtp->fmt_param.slen = end - p; - return (pjsdp_attr*)attr; + return PJ_SUCCESS; } -PJ_DEF(pjsdp_attr*) pjsdp_attr_clone (pj_pool_t *pool, const pjsdp_attr *rhs) +PJ_DEF(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, + const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap **p_rtpmap) { - struct attr_map_rec *desc; + PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL); - if (rhs->type >= PJSDP_END_OF_ATTR) { - pj_assert(0); - return NULL; - } + *p_rtpmap = pj_pool_alloc(pool, sizeof(pjmedia_sdp_rtpmap)); + PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM); - desc = &attr_map[rhs->type]; - return (*desc->clone) (pool, rhs); + return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap); } -PJ_DEF(const pjsdp_attr*) pjsdp_attr_find (int count, const pjsdp_attr *attr_array[], int type) + +PJ_DEF(pj_status_t) pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, + const pjmedia_sdp_rtpmap *rtpmap, + pjmedia_sdp_attr **p_attr) { + pjmedia_sdp_attr *attr; + char tempbuf[64], *p, *endbuf; int i; - for (i=0; itype == type) - return attr_array[i]; + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && rtpmap && p_attr, PJ_EINVAL); + + /* Check that mandatory attributes are specified. */ + PJ_ASSERT_RETURN(rtpmap->enc_name.slen && rtpmap->clock_rate, + PJMEDIA_SDP_EINRTPMAP); + + /* Check size. */ + i = rtpmap->enc_name.slen + rtpmap->param.slen + 32; + if (i >= sizeof(tempbuf)-1) { + pj_assert(!"rtpmap attribute is too long"); + return PJMEDIA_SDP_ERTPMAPTOOLONG; } - return NULL; + + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); + PJ_ASSERT_RETURN(attr != NULL, PJ_ENOMEM); + + attr->name.ptr = "rtpmap"; + attr->name.slen = 6; + + p = tempbuf; + endbuf = tempbuf+sizeof(tempbuf); + + /* Add payload type. */ + pj_memcpy(p, rtpmap->pt.ptr, rtpmap->pt.slen); + p += rtpmap->pt.slen; + *p++ = ' '; + + /* Add encoding name. */ + for (i=0; ienc_name.slen; ++i) + p[i] = rtpmap->enc_name.ptr[i]; + p += rtpmap->enc_name.slen; + *p++ = '/'; + + /* Add clock rate. */ + p += pj_utoa(rtpmap->clock_rate, p); + + /* Add parameter if necessary. */ + if (rtpmap->param.slen > 0) { + *p++ = '/'; + for (i=0; iparam.slen; ++i) + p[i] = rtpmap->param.ptr[i]; + p += rtpmap->param.slen; + } + + *p = '\0'; + + attr->value.slen = p-tempbuf; + attr->value.ptr = pj_pool_alloc(pool, attr->value.slen); + pj_memcpy(attr->value.ptr, tempbuf, attr->value.slen); + + *p_attr = attr; + return PJ_SUCCESS; } -static int print_connection_info( pjsdp_conn_info *c, char *buf, int len) + +static int print_connection_info( pjmedia_sdp_conn *c, char *buf, int len) { char *p = buf; @@ -372,9 +458,10 @@ static int print_connection_info( pjsdp_conn_info *c, char *buf, int len) return p-buf; } -PJ_DEF(pjsdp_conn_info*) pjsdp_conn_info_clone (pj_pool_t *pool, const pjsdp_conn_info *rhs) +PJ_DEF(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone (pj_pool_t *pool, + const pjmedia_sdp_conn *rhs) { - pjsdp_conn_info *c = pj_pool_alloc (pool, sizeof(pjsdp_conn_info)); + pjmedia_sdp_conn *c = pj_pool_alloc (pool, sizeof(pjmedia_sdp_conn)); if (!c) return NULL; if (!pj_strdup (pool, &c->net_type, &rhs->net_type)) return NULL; @@ -384,7 +471,31 @@ PJ_DEF(pjsdp_conn_info*) pjsdp_conn_info_clone (pj_pool_t *pool, const pjsdp_con return c; } -static int print_media_desc( pjsdp_media_desc *m, char *buf, int len) +static pj_ssize_t print_attr(const pjmedia_sdp_attr *attr, + char *buf, pj_size_t len) +{ + char *p = buf; + + if ((int)len < attr->name.slen + attr->value.slen + 10) + return -1; + + *p++ = 'a'; + *p++ = '='; + pj_memcpy(p, attr->name.ptr, attr->name.slen); + p += attr->name.slen; + + + if (attr->value.slen) { + pj_memcpy(p, attr->value.ptr, attr->value.slen); + p += attr->value.slen; + } + + *p++ = '\r'; + *p++ = '\n'; + return p-buf; +} + +static int print_media_desc( pjmedia_sdp_media *m, char *buf, int len) { char *p = buf; char *end = buf+len; @@ -439,13 +550,13 @@ static int print_media_desc( pjsdp_media_desc *m, char *buf, int len) return p-buf; } -PJ_DEF(pjsdp_media_desc*) pjsdp_media_desc_clone (pj_pool_t *pool, - const pjsdp_media_desc *rhs) +PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone( + pj_pool_t *pool, + const pjmedia_sdp_media *rhs) { unsigned int i; - pjsdp_media_desc *m = pj_pool_alloc (pool, sizeof(pjsdp_media_desc)); - if (!m) - return NULL; + pjmedia_sdp_media *m = pj_pool_alloc (pool, sizeof(pjmedia_sdp_media)); + PJ_ASSERT_RETURN(m != NULL, NULL); pj_strdup (pool, &m->desc.media, &rhs->desc.media); m->desc.port = rhs->desc.port; @@ -456,54 +567,62 @@ PJ_DEF(pjsdp_media_desc*) pjsdp_media_desc_clone (pj_pool_t *pool, m->desc.fmt[i] = rhs->desc.fmt[i]; if (rhs->conn) { - m->conn = pjsdp_conn_info_clone (pool, rhs->conn); - if (!m->conn) - return NULL; + m->conn = pjmedia_sdp_conn_clone (pool, rhs->conn); + PJ_ASSERT_RETURN(m->conn != NULL, NULL); } else { m->conn = NULL; } m->attr_count = rhs->attr_count; for (i=0; i < rhs->attr_count; ++i) { - m->attr[i] = pjsdp_attr_clone (pool, rhs->attr[i]); - if (!m->attr[i]) - return NULL; + m->attr[i] = pjmedia_sdp_attr_clone (pool, rhs->attr[i]); + PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL); } return m; } -/** Check if the media description has the specified attribute. */ -PJ_DEF(pj_bool_t) pjsdp_media_desc_has_attr (const pjsdp_media_desc *m, - pjsdp_attr_type_e attr_type) +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_media_find_attr(const pjmedia_sdp_media *m, + const pj_str_t *name, const pj_str_t *fmt) { - unsigned i; - for (i=0; iattr_count; ++i) { - pjsdp_attr *attr = m->attr[i]; - if (attr->type == attr_type) - return 1; - } - return 0; + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt); } -/** Find rtpmap attribute for the specified payload type. */ -PJ_DEF(const pjsdp_rtpmap_attr*) -pjsdp_media_desc_find_rtpmap (const pjsdp_media_desc *m, unsigned pt) + + +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, + const char *name, const pj_str_t *fmt) { - unsigned i; - for (i=0; iattr_count; ++i) { - pjsdp_attr *attr = m->attr[i]; - if (attr->type == PJSDP_ATTR_RTPMAP) { - const pjsdp_rtpmap_attr* rtpmap = (const pjsdp_rtpmap_attr*)attr; - if (rtpmap->payload_type == pt) - return rtpmap; - } - } - return NULL; + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt); } -static int print_session(const pjsdp_session_desc *ses, char *buf, pj_ssize_t len) +PJ_DEF(pj_status_t) pjmedia_sdp_media_add_attr( pjmedia_sdp_media *m, + pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); +} + +PJ_DEF(unsigned) +pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m, + const char *name) +{ + return pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, name); +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m, + pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); +} + +static int print_session(const pjmedia_sdp_session *ses, + char *buf, pj_ssize_t len) { char *p = buf; char *end = buf+len; @@ -604,16 +723,21 @@ static int print_session(const pjsdp_session_desc *ses, char *buf, pj_ssize_t le * PARSERS */ -static void parse_version(pj_scanner *scanner) +static void parse_version(pj_scanner *scanner, parse_context *ctx) { + ctx->last_error = PJMEDIA_SDP_EINVER; + pj_scan_advance_n(scanner, 3, SKIP_WS); pj_scan_get_newline(scanner); } -static void parse_origin(pj_scanner *scanner, pjsdp_session_desc *ses) +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx) { pj_str_t str; + ctx->last_error = PJMEDIA_SDP_EINORIGIN; + /* o= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -646,10 +770,13 @@ static void parse_origin(pj_scanner *scanner, pjsdp_session_desc *ses) pj_scan_get_newline(scanner); } -static void parse_time(pj_scanner *scanner, pjsdp_session_desc *ses) +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, + parse_context *ctx) { pj_str_t str; + ctx->last_error = PJMEDIA_SDP_EINTIME; + /* t= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -667,8 +794,11 @@ static void parse_time(pj_scanner *scanner, pjsdp_session_desc *ses) pj_scan_get_newline(scanner); } -static void parse_generic_line(pj_scanner *scanner, pj_str_t *str) +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, + parse_context *ctx) { + ctx->last_error = PJMEDIA_SDP_EINSDP; + /* x= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -679,8 +809,11 @@ static void parse_generic_line(pj_scanner *scanner, pj_str_t *str) pj_scan_get_newline(scanner); } -static void parse_connection_info(pj_scanner *scanner, pjsdp_conn_info *conn) +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, + parse_context *ctx) { + ctx->last_error = PJMEDIA_SDP_EINCONN; + /* c= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -699,10 +832,13 @@ static void parse_connection_info(pj_scanner *scanner, pjsdp_conn_info *conn) pj_scan_get_newline(scanner); } -static void parse_media(pj_scanner *scanner, pjsdp_media_desc *med) +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, + parse_context *ctx) { pj_str_t str; + ctx->last_error = PJMEDIA_SDP_EINMEDIA; + /* m= */ pj_scan_advance_n(scanner, 2, SKIP_WS); @@ -741,152 +877,73 @@ static void parse_media(pj_scanner *scanner, pjsdp_media_desc *med) pj_scan_get_newline(scanner); } -static pjsdp_rtpmap_attr * parse_rtpmap_attr( pj_pool_t *pool, pj_scanner *scanner ) -{ - pjsdp_rtpmap_attr *rtpmap; - pj_str_t str; - - rtpmap = pj_pool_calloc(pool, 1, sizeof(*rtpmap)); - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, ' ', &str); - rtpmap->payload_type = pj_strtoul(&str); - pj_scan_get_char(scanner); - - pj_scan_get_until_ch(scanner, '/', &rtpmap->encoding_name); - pj_scan_get_char(scanner); - pj_scan_get(scanner, &cs_token, &str); - rtpmap->clock_rate = pj_strtoul(&str); - - if (*scanner->curptr == '/') { - pj_scan_get_char(scanner); - pj_scan_get_until_ch(scanner, '\r', &rtpmap->parameter); - } - - return rtpmap; -} - -static pjsdp_attr_string * parse_generic_string_attr( pj_pool_t *pool, pj_scanner *scanner ) -{ - pjsdp_attr_string *attr; - attr = pj_pool_calloc(pool, 1, sizeof(*attr)); - - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, '\r', &attr->value); - return attr; -} - -static pjsdp_attr_num * parse_generic_num_attr( pj_pool_t *pool, pj_scanner *scanner ) -{ - pjsdp_attr_num *attr; - pj_str_t str; - - attr = pj_pool_calloc(pool, 1, sizeof(*attr)); - - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, '\r', &str); - attr->value = pj_strtoul(&str); - return attr; -} - -static pjsdp_attr * parse_name_only_attr( pj_pool_t *pool, pj_scanner *scanner ) +static void on_scanner_error(pj_scanner *scanner) { - pjsdp_attr *attr; - PJ_UNUSED_ARG(scanner); - attr = pj_pool_calloc(pool, 1, sizeof(*attr)); - return attr; + + PJ_THROW(SYNTAX_ERROR); } -static pjsdp_fmtp_attr * parse_fmtp_attr( pj_pool_t *pool, pj_scanner *scanner ) +static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner, + parse_context *ctx) { - pjsdp_fmtp_attr *fmtp; + pjmedia_sdp_attr *attr; - fmtp = pj_pool_calloc(pool, 1, sizeof(*fmtp)); - - if (pj_scan_get_char(scanner) != ':') { - PJ_THROW(SYNTAX_ERROR); - } - pj_scan_get_until_ch(scanner, ' ', &fmtp->format); - pj_scan_get_char(scanner); - pj_scan_get_until_ch(scanner, '\r', &fmtp->param); - return fmtp; -} + ctx->last_error = PJMEDIA_SDP_EINATTR; -static pjsdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner) -{ - void * (*parse_func)(pj_pool_t *pool, pj_scanner *scanner) = NULL; - pj_str_t attrname; - unsigned i; - pjsdp_attr *attr; + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); /* skip a= */ pj_scan_advance_n(scanner, 2, SKIP_WS); /* get attr name. */ - pj_scan_get(scanner, &cs_token, &attrname); - - /* find entry to handle attrname */ - for (i=0; iname) == 0) { - parse_func = p->parse_attr; - break; - } - } + pj_scan_get(scanner, &cs_token, &attr->name); - /* fallback to generic string parser. */ - if (parse_func == NULL) { - parse_func = &parse_generic_string_attr; + if (*scanner->curptr != '\r' && *scanner->curptr != '\n') { + /* get value */ + pj_scan_get_until_ch(scanner, '\r', &attr->value); + } else { + attr->value.ptr = NULL; + attr->value.slen = 0; } - attr = (*parse_func)(pool, scanner); - attr->type = i; - /* newline */ pj_scan_get_newline(scanner); return attr; } -static void on_scanner_error(pj_scanner *scanner) -{ - PJ_UNUSED_ARG(scanner); - - PJ_THROW(SYNTAX_ERROR); -} - /* * Parse SDP message. */ -PJ_DEF(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, - pj_pool_t *pool) +PJ_DEF(pj_status_t) pjmedia_sdp_parse( pj_pool_t *pool, + char *buf, pj_size_t len, + pjmedia_sdp_session **p_sdp) { pj_scanner scanner; - pjsdp_session_desc *session; - pjsdp_media_desc *media = NULL; + pjmedia_sdp_session *session; + pjmedia_sdp_media *media = NULL; void *attr; - pjsdp_conn_info *conn; + pjmedia_sdp_conn *conn; pj_str_t dummy; int cur_name = 254; + parse_context ctx; PJ_USE_EXCEPTION; + ctx.last_error = PJ_SUCCESS; + init_sdp_parser(); pj_scan_init(&scanner, buf, len, 0, &on_scanner_error); - session = pj_pool_calloc(pool, 1, sizeof(*session)); + session = pj_pool_calloc(pool, 1, sizeof(pjmedia_sdp_session)); + PJ_ASSERT_RETURN(session != NULL, PJ_ENOMEM); PJ_TRY { while (!pj_scan_is_eof(&scanner)) { cur_name = *scanner.curptr; switch (cur_name) { case 'a': - attr = parse_attr(pool, &scanner); + attr = parse_attr(pool, &scanner, &ctx); if (attr) { if (media) { media->attr[media->attr_count++] = attr; @@ -896,14 +953,14 @@ PJ_DEF(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, } break; case 'o': - parse_origin(&scanner, session); + parse_origin(&scanner, session, &ctx); break; case 's': - parse_generic_line(&scanner, &session->name); + parse_generic_line(&scanner, &session->name, &ctx); break; case 'c': conn = pj_pool_calloc(pool, 1, sizeof(*conn)); - parse_connection_info(&scanner, conn); + parse_connection_info(&scanner, conn, &ctx); if (media) { media->conn = conn; } else { @@ -911,44 +968,201 @@ PJ_DEF(pjsdp_session_desc*) pjsdp_parse( char *buf, pj_size_t len, } break; case 't': - parse_time(&scanner, session); + parse_time(&scanner, session, &ctx); break; case 'm': media = pj_pool_calloc(pool, 1, sizeof(*media)); - parse_media(&scanner, media); + parse_media(&scanner, media, &ctx); session->media[ session->media_count++ ] = media; break; case 'v': - parse_version(&scanner); + parse_version(&scanner, &ctx); break; default: - parse_generic_line(&scanner, &dummy); + parse_generic_line(&scanner, &dummy, &ctx); break; } } + + ctx.last_error = PJ_SUCCESS; + } PJ_CATCH(SYNTAX_ERROR) { - PJ_LOG(2, (LOG_THIS, "Syntax error in SDP parser '%c' line %d col %d", - cur_name, scanner.line, pj_scan_get_col(&scanner))); - if (!pj_scan_is_eof(&scanner)) { - if (*scanner.curptr != '\r') { - pj_scan_get_until_ch(&scanner, '\r', &dummy); - } - pj_scan_get_newline(&scanner); - } + + char errmsg[PJMEDIA_ERR_MSG_SIZE]; + pjmedia_strerror(ctx.last_error, errmsg, sizeof(errmsg)); + + PJ_LOG(4, (THIS_FILE, "Error parsing SDP in line %d col %d: %s", + scanner.line, pj_scan_get_col(&scanner), + errmsg)); + + session = NULL; + } PJ_END; pj_scan_fini(&scanner); - return session; + + *p_sdp = session; + return ctx.last_error; } /* * Print SDP description. */ -PJ_DEF(int) pjsdp_print( const pjsdp_session_desc *desc, char *buf, pj_size_t size) +PJ_DEF(int) pjmedia_sdp_print( const pjmedia_sdp_session *desc, + char *buf, pj_size_t size) { return print_session(desc, buf, size); } +/* + * Clone session + */ +PJ_DEF(pjmedia_sdp_session*) +pjmedia_sdp_session_clone( pj_pool_t *pool, + const pjmedia_sdp_session *rhs) +{ + pjmedia_sdp_session *sess; + unsigned i; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + sess = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_session)); + PJ_ASSERT_RETURN(sess != NULL, NULL); + + /* Clone origin line. */ + pj_strdup(pool, &sess->origin.user, &rhs->origin.user); + sess->origin.id = rhs->origin.id; + sess->origin.version = rhs->origin.version; + pj_strdup(pool, &sess->origin.net_type, &rhs->origin.net_type); + pj_strdup(pool, &sess->origin.addr_type, &rhs->origin.addr_type); + pj_strdup(pool, &sess->origin.addr, &rhs->origin.addr); + + /* Clone subject line. */ + pj_strdup(pool, &sess->name, &rhs->name); + + /* Clone connection line */ + if (rhs->conn) { + sess->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(sess->conn != NULL, NULL); + } + + /* Clone time line. */ + sess->time.start = rhs->time.start; + sess->time.stop = rhs->time.stop; + + /* Duplicate session attributes. */ + sess->attr_count = rhs->attr_count; + for (i=0; iattr_count; ++i) { + sess->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]); + } + + /* Duplicate media descriptors. */ + sess->media_count = rhs->media_count; + for (i=0; imedia_count; ++i) { + sess->media[i] = pjmedia_sdp_media_clone(pool, rhs->media[i]); + } + + return sess; +} + + +#define CHECK(exp,ret) do { \ + pj_assert(exp); \ + if (!(exp)) \ + return ret; \ + } while (0) + +/* Validate SDP connetion info. */ +static pj_status_t validate_sdp_conn(const pjmedia_sdp_conn *c) +{ + CHECK( c, PJ_EINVAL); + CHECK( pj_strcmp2(&c->net_type, "IN")==0, PJMEDIA_SDP_EINCONN); + CHECK( pj_strcmp2(&c->addr_type, "IP4")==0 || + pj_strcmp2(&c->addr_type, "IP6")==0, + PJMEDIA_SDP_EINCONN); + CHECK( c->addr.slen != 0, PJMEDIA_SDP_EINCONN); + + return PJ_SUCCESS; +} + + +/* Validate SDP session descriptor. */ +PJ_DEF(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp) +{ + unsigned i; + + CHECK( sdp != NULL, PJ_EINVAL); + + /* Validate origin line. */ + CHECK( sdp->origin.user.slen != 0, PJMEDIA_SDP_EINORIGIN); + CHECK( pj_strcmp2(&sdp->origin.net_type, "IN")==0, + PJMEDIA_SDP_EINORIGIN); + CHECK( pj_strcmp2(&sdp->origin.addr_type, "IP4")==0 || + pj_strcmp2(&sdp->origin.addr_type, "IP6")==0, + PJMEDIA_SDP_EINORIGIN); + CHECK( sdp->origin.addr.slen != 0, PJMEDIA_SDP_EINORIGIN); + + /* Validate subject line. */ + CHECK( sdp->name.slen != 0, PJMEDIA_SDP_EINNAME); + + /* Ignore start and stop time. */ + + /* If session level connection info is present, validate it. */ + if (sdp->conn) { + pj_status_t status = validate_sdp_conn(sdp->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* Validate each media. */ + for (i=0; imedia_count; ++i) { + const pjmedia_sdp_media *m = sdp->media[i]; + unsigned j; + + /* Validate the m= line. */ + CHECK( m->desc.media.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK( m->desc.transport.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK( m->desc.fmt_count != 0, PJMEDIA_SDP_ENOFMT); + + /* If media level connection info is present, validate it. */ + if (m->conn) { + pj_status_t status = validate_sdp_conn(m->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* If media doesn't have connection info, then connection info + * must be present in the session. + */ + if (m->conn == NULL) { + if (sdp->conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + } + + /* Verify payload type. */ + for (j=0; jdesc.fmt_count; ++j) { + unsigned pt = pj_strtoul(&m->desc.fmt[j]); + + /* Payload type is between 0 and 127. */ + CHECK( pt <= 127, PJMEDIA_SDP_EINPT); + + /* If port is not zero, then for each dynamic payload type, an + * rtpmap attribute must be specified. + */ + if (m->desc.port != 0 && pt >= 96) { + const pjmedia_sdp_attr *a; + + a = pjmedia_sdp_media_find_attr2(m, "rtpmap", &m->desc.fmt[j]); + CHECK( a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP); + } + } + } + + /* Looks good. */ + return PJ_SUCCESS; +} + + diff --git a/pjmedia/src/pjmedia/sdp_cmp.c b/pjmedia/src/pjmedia/sdp_cmp.c new file mode 100644 index 00000000..f8817445 --- /dev/null +++ b/pjmedia/src/pjmedia/sdp_cmp.c @@ -0,0 +1,292 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + + +/* Compare connection line. */ +static pj_status_t compare_conn(const pjmedia_sdp_conn *c1, + const pjmedia_sdp_conn *c2) +{ + /* Compare network type. */ + if (pj_strcmp(&c1->net_type, &c2->net_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address type. */ + if (pj_strcmp(&c1->addr_type, &c2->addr_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address. */ + if (pj_strcmp(&c1->addr, &c2->addr) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + return PJ_SUCCESS; +} + +/* Compare attributes array. */ +static pj_status_t compare_attr_imp(unsigned count1, + const pjmedia_sdp_attr *const attr1[], + unsigned count2, + const pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + unsigned i; + const pj_str_t inactive = { "inactive", 8 }; + const pj_str_t sendrecv = { "sendrecv", 8 }; + const pj_str_t sendonly = { "sendonly", 8 }; + const pj_str_t recvonly = { "recvonly", 8 }; + const pj_str_t fmtp = { "fmtp", 4 }; + const pj_str_t rtpmap = { "rtpmap", 6 }; + + /* For simplicity, we only compare the following attributes, and ignore + * the others: + * - direction, eg. inactive, sendonly, recvonly, sendrecv + * - fmtp for each payload. + * - rtpmap for each payload. + */ + for (i=0; iname, &inactive) == 0 || + pj_strcmp(&a1->name, &sendrecv) == 0 || + pj_strcmp(&a1->name, &sendonly) == 0 || + pj_strcmp(&a1->name, &recvonly) == 0) + { + /* For inactive, sendrecv, sendonly, and recvonly attributes, + * the same attribute must be present on the other SDP. + */ + const pjmedia_sdp_attr *a2; + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, NULL); + if (!a2) + return PJMEDIA_SDP_EDIRNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &fmtp) == 0) { + /* For fmtp attribute, find the fmtp attribute in the other SDP + * for the same payload type, and compare the fmtp param/value. + */ + pjmedia_sdp_fmtp fmtp1, fmtp2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_fmtp(a1, &fmtp1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &fmtp1.fmt); + if (!a2) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + status = pjmedia_sdp_attr_get_fmtp(a2, &fmtp2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + if (pj_strcmp(&fmtp1.fmt_param, &fmtp2.fmt_param) != 0) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &rtpmap) == 0) { + /* For rtpmap attribute, find rtpmap attribute on the other SDP + * for the same payload type, and compare both rtpmap atribute + * values. + */ + pjmedia_sdp_rtpmap r1, r2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_rtpmap(a1, &r1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &r1.pt); + if (!a2) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + status = pjmedia_sdp_attr_get_rtpmap(a2, &r2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + if (pj_strcmp(&r1.pt, &r2.pt) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.enc_name, &r2.enc_name) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (r1.clock_rate != r2.clock_rate) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.param, &r2.param) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + } + } + + return PJ_SUCCESS; +} + + +/* Compare attributes array. */ +static pj_status_t compare_attr(unsigned count1, + const pjmedia_sdp_attr *const attr1[], + unsigned count2, + const pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + + status = compare_attr_imp(count1, attr1, count2, attr2); + if (status != PJ_SUCCESS) + return status; + + status = compare_attr_imp(count2, attr2, count1, attr1); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + +/* Compare media descriptor */ +PJ_DEF(pj_status_t) pjmedia_sdp_media_cmp( const pjmedia_sdp_media *sd1, + const pjmedia_sdp_media *sd2, + unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare media type. */ + if (pj_strcmp(&sd1->desc.media, &sd2->desc.media) != 0) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + /* Compare port number. */ + if (sd1->desc.port != sd2->desc.port) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare port count. */ + if (sd1->desc.port_count != sd2->desc.port_count) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare transports. */ + if (pj_strcmp(&sd1->desc.transport, &sd2->desc.transport) != 0) + return PJMEDIA_SDP_ETPORTNOTEQUAL; + + /* Compare number of formats. */ + if (sd1->desc.fmt_count != sd2->desc.fmt_count) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + + /* Compare formats, in order. */ + for (i=0; idesc.fmt_count; ++i) { + if (pj_strcmp(&sd1->desc.fmt[i], &sd2->desc.fmt[i]) != 0) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Compare connection line, if they exist. */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + } else { + if (sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + } + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, + sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Looks equal */ + return PJ_SUCCESS; +} + +/* + * Compare two SDP session for equality. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_session_cmp( const pjmedia_sdp_session *sd1, + const pjmedia_sdp_session *sd2, + unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare the origin line. */ + if (pj_strcmp(&sd1->origin.user, &sd2->origin.user) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.id != sd2->origin.id) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.version != sd2->origin.version) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.net_type, &sd2->origin.net_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr_type, &sd2->origin.addr_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr, &sd2->origin.addr) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + + /* Compare the subject line. */ + if (pj_strcmp(&sd1->name, &sd2->name) != 0) + return PJMEDIA_SDP_ENAMENOTEQUAL; + + /* Compare connection line, when they exist */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + if (status != PJ_SUCCESS) + return status; + } else { + if (sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + } + + /* Compare time line. */ + if (sd1->time.start != sd2->time.start) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + if (sd1->time.stop != sd2->time.stop) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, + sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Compare media lines. */ + if (sd1->media_count != sd2->media_count) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + for (i=0; imedia_count; ++i) { + status = pjmedia_sdp_media_cmp(sd1->media[i], sd2->media[i], 0); + if (status != PJ_SUCCESS) + return status; + } + + /* Looks equal. */ + return PJ_SUCCESS; +} + + diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c new file mode 100644 index 00000000..313fff99 --- /dev/null +++ b/pjmedia/src/pjmedia/sdp_neg.c @@ -0,0 +1,815 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * This structure describes SDP media negotiator. + */ +struct pjmedia_sdp_neg +{ + pjmedia_sdp_neg_state state; /**< Negotiator state. */ + pj_bool_t has_remote_answer; + + pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */ + *active_local_sdp, /**< Currently active local SDP. */ + *active_remote_sdp, /**< Currently active remote's. */ + *neg_local_sdp, /**< Temporary local SDP. */ + *neg_remote_sdp; /**< Temporary remote SDP. */ +}; + + +/* + * Create with local offer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool, + const pjmedia_sdp_session *local, + pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate local offer. */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status); + + /* Create and initialize negotiator. */ + neg = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_neg)); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Create with remote offer and initial local offer/answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, + const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, + pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && local && remote && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate remote offer and local answer */ + status = pjmedia_sdp_validate(remote); + if (status != PJ_SUCCESS) + return status; + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status); + + /* Create and initialize negotiator. */ + neg = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_neg)); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(neg->initial_sdp))==PJ_SUCCESS, + status); + + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(neg->neg_remote_sdp))==PJ_SUCCESS, + status); + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Get SDP negotiator state. + */ +PJ_DEF(pjmedia_sdp_neg_state) +pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg ) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL); + return neg->state; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_get_local( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *local = neg->active_local_sdp; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_get_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *remote = neg->active_remote_sdp; + return PJ_SUCCESS; +} + + +/* + * Modify local SDP and wait for remote answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *local) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_DONE. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, + PJMEDIA_SDPNEG_EINSTATE); + + /* Change state to STATE_LOCAL_OFFER */ + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_tx_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **offer) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL); + + *offer = NULL; + + /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE || + neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) { + /* If in STATE_DONE, set the active SDP as the offer. */ + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, + neg->active_local_sdp); + *offer = neg->active_local_sdp; + + } else { + /* We assume that we're in STATE_LOCAL_OFFER. + * In this case set the neg_local_sdp as the offer. + */ + *offer = neg->neg_local_sdp; + } + + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_rx_remote_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_DONE. + * If we already provide local offer, then rx_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, + PJMEDIA_SDPNEG_EINSTATE); + + /* We're ready to negotiate. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_rx_remote_answer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_LOCAL_OFFER. + * If we haven't provided local offer, then rx_remote_offer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + /* We're ready to negotiate. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->has_remote_answer = 1; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +/* Swap string. */ +static void str_swap(pj_str_t *str1, pj_str_t *str2) +{ + pj_str_t tmp = *str1; + *str1 = *str2; + *str2 = tmp; +} + +static void remove_all_media_directions(pjmedia_sdp_media *m) +{ + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); +} + +/* Update media direction based on peer's media direction */ +static void update_media_direction(pj_pool_t *pool, + const pjmedia_sdp_media *remote, + pjmedia_sdp_media *local) +{ + if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) { + /* If remote has "a=inactive", then local is inactive too */ + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else if (pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) { + /* If remote has "a=sendonly", then set local to "recvonly" if + * it is currently "sendrecv". + */ + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else if (pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) { + /* If remote has "a=recvonly", then set local to "sendonly" if + * it is currently "sendrecv". + */ + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else if (pjmedia_sdp_media_find_attr2(remote, "sendrecv", NULL) != NULL) { + + pjmedia_sdp_attr *a; + + remove_all_media_directions(local); + + a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL); + pjmedia_sdp_media_add_attr(local, a); + + } else { + /* Otherwise remote is set to "sendrecv". + */ + remove_all_media_directions(local); + } +} + +/* Update single local media description to after receiving answer + * from remote. + */ +static pj_status_t process_m_answer( pj_pool_t *pool, + pjmedia_sdp_media *offer, + pjmedia_sdp_media *answer, + pj_bool_t allow_asym) +{ + unsigned i; + + /* Check that the media type match our offer. */ + + if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) { + /* The media type in the answer is different than the offer! */ + return PJMEDIA_SDPNEG_EINVANSMEDIA; + } + + + /* Chec that transport in the answer match our offer. */ + + if (pj_strcmp(&answer->desc.transport, + &offer->desc.transport)!=0) + { + /* The transport in the answer is different than the offer! */ + return PJMEDIA_SDPNEG_EINVANSTP; + } + + /* Check if remote has rejected our offer */ + + if (answer->desc.port == 0) { + + /* Remote has rejected our offer. + * Set our port to zero too in active SDP. + */ + offer->desc.port = 0; + } + + + /* Process direction attributes */ + update_media_direction(pool, answer, offer); + + /* If asymetric media is allowed, then just check that remote answer has + * codecs that are within the offer. + * + * Otherwise if asymetric media is not allowed, then we will choose only + * one codec in our initial offer to match the answer. + */ + if (allow_asym) { + for (i=0; idesc.fmt_count; ++i) { + unsigned j; + pj_str_t *rem_fmt = &answer->desc.fmt[i]; + + for (j=0; jdesc.fmt_count; ++j) { + if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0) + break; + } + + if (j != offer->desc.fmt_count) { + /* Found at least one common codec. */ + break; + } + } + + if (i == answer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED); + + } else { + /* Remove all format in the offer that has no matching answer */ + for (i=0; idesc.fmt_count;) { + unsigned j; + pj_str_t *fmt = &offer->desc.fmt[i]; + + for (j=0; jdesc.fmt_count; ++j) { + if (pj_strcmp(fmt, &answer->desc.fmt[j])==0) + break; + } + + if (j == answer->desc.fmt_count) { + /* This format has no matching answer. + * Remove it from our offer. + */ + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove this format from offer's array */ + pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]), + offer->desc.fmt_count, i); + --offer->desc.fmt_count; + + } else { + ++i; + } + } + + /* Arrange format in the offer so the order match the priority + * in the answer + */ + for (i=0; idesc.fmt_count; ++i) { + unsigned j; + pj_str_t *fmt = &answer->desc.fmt[i]; + + for (j=i; jdesc.fmt_count; ++j) { + if (pj_strcmp(fmt, &offer->desc.fmt[j])==0) { + str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]); + break; + } + } + } + } + + /* Looks okay */ + return PJ_SUCCESS; +} + + +/* Update local media session (offer) to create active local session + * after receiving remote answer. + */ +static pj_status_t process_answer(pj_pool_t *pool, + pjmedia_sdp_session *offer, + pjmedia_sdp_session *answer, + pj_bool_t allow_asym, + pjmedia_sdp_session **p_active) +{ + unsigned mi; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL); + + /* Check that media count match between offer and answer */ + if (offer->media_count != answer->media_count) + return PJMEDIA_SDPNEG_EMISMEDIA; + + /* Now update each media line in the offer with the answer. */ + for (mi=0; mimedia_count; ++mi) { + status = process_m_answer(pool, offer->media[mi], answer->media[mi], + allow_asym); + if (status != PJ_SUCCESS) + return status; + } + + *p_active = offer; + return PJ_SUCCESS; +} + +/* Try to match offer with answer. */ +static pj_bool_t match_offer(pj_pool_t *pool, + const pjmedia_sdp_media *offer, + const pjmedia_sdp_media *local, + pjmedia_sdp_media **p_answer) +{ + unsigned i; + pj_bool_t offer_has_codec = 0, + offer_has_telephone_event = 0, + offer_has_other = 0, + found_matching_codec = 0, + found_matching_telephone_event = 0, + found_matching_other = 0; + unsigned pt_answer_count = 0; + pj_str_t pt_answer[PJSDP_MAX_FMT]; + pjmedia_sdp_media *answer; + + /* With the addition of telephone-event and dodgy MS RTC SDP, + * the answer generation algorithm looks really shitty... + */ + for (i=0; idesc.fmt_count; ++i) { + unsigned j; + + if (pj_isdigit(*offer->desc.fmt[i].ptr)) { + /* This is normal/standard payload type, where it's identified + * by payload number. + */ + unsigned pt; + + pt = pj_strtoul(&offer->desc.fmt[i]); + + if (pt < 96) { + /* For static payload type, it's enough to compare just + * the payload number. + */ + + offer_has_codec = 1; + + /* We just need to select one codec. + * Continue if we have selected matching codec for previous + * payload. + */ + if (found_matching_codec) + continue; + + /* Find matching codec in local descriptor. */ + for (j=0; jdesc.fmt_count; ++j) { + unsigned p; + p = pj_strtoul(&local->desc.fmt[j]); + if (p == pt && pj_isdigit(*local->desc.fmt[j].ptr)) { + found_matching_codec = 1; + pt_answer[pt_answer_count++] = local->desc.fmt[j]; + break; + } + } + + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or; + pj_bool_t is_codec; + + /* Get the rtpmap for the payload type in the offer. */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", &offer->desc.fmt[i]); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJ_FALSE; + } + pjmedia_sdp_attr_get_rtpmap(a, &or); + + if (!pj_strcmp2(&or.enc_name, "telephone-event")) { + offer_has_telephone_event = 1; + if (found_matching_telephone_event) + continue; + is_codec = 0; + } else { + offer_has_codec = 1; + if (found_matching_codec) + continue; + is_codec = 1; + } + + /* Find paylaod in our initial SDP with matching + * encoding name. + */ + for (j=0; jdesc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(local, "rtpmap", &local->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap lr; + pjmedia_sdp_attr_get_rtpmap(a, &lr); + if (!pj_strcmp(&or.enc_name, &lr.enc_name)) { + /* Match! */ + if (is_codec) + found_matching_codec = 1; + else + found_matching_telephone_event = 1; + pt_answer[pt_answer_count++] = local->desc.fmt[j]; + break; + } + } + } + } + + } else { + /* This is a non-standard, brain damaged SDP where the payload + * type is non-numeric. It exists e.g. in Microsoft RTC based + * UA, to indicate instant messaging capability. + * Example: + * - m=x-ms-message 5060 sip null + */ + offer_has_other = 1; + if (found_matching_other) + continue; + + for (j=0; jdesc.fmt_count; ++j) { + if (!pj_strcmp(&offer->desc.fmt[i], &local->desc.fmt[j])) { + /* Match */ + found_matching_other = 1; + pt_answer[pt_answer_count++] = local->desc.fmt[j]; + break; + } + } + } + } + + /* See if all types of offer can be matched. */ + if ((offer_has_codec && !found_matching_codec) || + (offer_has_telephone_event && !found_matching_telephone_event) || + (offer_has_other && !found_matching_other)) + { + /* Some of the payload in the offer has no matching local sdp */ + return PJ_FALSE; + } + + /* Seems like everything is in order. + * Build the answer by cloning from local media, but rearrange the payload + * to suit the offer. + */ + answer = pjmedia_sdp_media_clone(pool, local); + for (i=0; idesc.fmt_count; ++j) { + if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i])) + break; + } + pj_assert(j != answer->desc.fmt_count); + str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]); + } + + /* Remove unwanted local formats. */ + for (i=pt_answer_count; idesc.fmt_count; ++i) { + pjmedia_sdp_attr *a; + + /* Remove rtpmap for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", + &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + + /* Remove fmtp for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", + &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + } + answer->desc.fmt_count = pt_answer_count; + + /* If offer has zero port, set our answer with zero port too */ + if (offer->desc.port==0) + answer->desc.port = 0; + + /* Update media direction. */ + update_media_direction(pool, offer, answer); + + *p_answer = answer; + return PJ_TRUE; +} + +/* Create complete answer for remote's offer. */ +static pj_status_t create_answer( pj_pool_t *pool, + const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *offer, + pjmedia_sdp_session **p_answer) +{ + pj_status_t status; + pjmedia_sdp_session *answer; + char media_used[PJSDP_MAX_MEDIA]; + unsigned i; + + /* Validate remote offer. + * This should have been validated before. + */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status); + + /* Create initial answer by duplicating initial SDP, + * but clear all media lines. The media lines will be filled up later. + */ + answer = pjmedia_sdp_session_clone(pool, initial); + PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM); + + answer->media_count = 0; + + pj_memset(media_used, 0, sizeof(media_used)); + + /* For each media line, create our answer based on our initial + * capability. + */ + for (i=0; imedia_count; ++i) { + const pjmedia_sdp_media *om; /* offer */ + const pjmedia_sdp_media *im; /* initial media */ + pjmedia_sdp_media *am = NULL; /* answer/result */ + unsigned j; + + om = offer->media[i]; + + /* Find media description in our initial capability that matches + * the media type and transport type of offer's media, has + * matching codec, and has not been used to answer other offer. + */ + for (im=NULL, j=0; jmedia_count; ++j) { + im = initial->media[j]; + if (pj_strcmp(&om->desc.media, &im->desc.media)==0 && + pj_strcmp(&om->desc.transport, &im->desc.transport)==0 && + media_used[j] == 0) + { + /* See if it has matching codec. */ + pj_bool_t match; + + match = match_offer(pool, om, im, &am); + if (match) { + /* Mark media as used. */ + media_used[j] = 1; + break; + } + } + } + + if (j==initial->media_count) { + /* No matching media. + * Reject the offer by setting the port to zero in the answer. + */ + /* For simplicity in the construction of the answer, we'll + * just clone the media from the offer. Anyway receiver will + * ignore anything in the media once it sees that the port + * number is zero. + */ + am = pjmedia_sdp_media_clone(pool, om); + am->desc.port = 0; + + /* Match direction */ + update_media_direction(pool, om, am); + + } else { + /* The answer is in am */ + pj_assert(am != NULL); + } + + /* Add the media answer */ + answer->media[answer->media_count++] = am; + } + + *p_answer = answer; + return PJ_SUCCESS; +} + +/* The best bit: SDP negotiation function! */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + pj_bool_t allow_asym) +{ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL); + + /* Must be in STATE_WAIT_NEGO state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + PJMEDIA_SDPNEG_EINSTATE); + + /* Must have remote offer. */ + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG); + + if (neg->has_remote_answer) { + pjmedia_sdp_session *active; + status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp, + allow_asym, &active); + if (status == PJ_SUCCESS) { + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = active; + neg->active_remote_sdp = neg->neg_remote_sdp; + + } + } else { + pjmedia_sdp_session *answer; + + status = create_answer(pool, neg->initial_sdp, neg->neg_remote_sdp, + &answer); + if (status == PJ_SUCCESS) { + pj_uint32_t active_ver; + + if (neg->active_local_sdp) + active_ver = neg->active_local_sdp->origin.version; + else + active_ver = neg->initial_sdp->origin.version; + + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = answer; + neg->active_remote_sdp = neg->neg_remote_sdp; + + /* Increment SDP version */ + neg->active_local_sdp->origin.version = ++active_ver; + } + } + + /* State is DONE regardless */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + /* Clear temporary SDP */ + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = 0; + + return status; +} + diff --git a/pjmedia/src/test/main.c b/pjmedia/src/test/main.c index 31ca3c32..31acaec7 100644 --- a/pjmedia/src/test/main.c +++ b/pjmedia/src/test/main.c @@ -16,27 +16,17 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include -#include -#include - -pj_status_t session_test (pj_pool_factory *pf); -pj_status_t rtp_test (pj_pool_factory *pf); -pj_status_t sdp_test(pj_pool_factory *pf); -int jbuf_main(pj_pool_factory *pf); +#include "test.h" int main() { - pj_caching_pool caching_pool; + int rc; + char s[10]; - pj_init(); - pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); + rc = test_main(); - sdp_test (&caching_pool.factory); - rtp_test(&caching_pool.factory); - session_test (&caching_pool.factory); - //jbuf_main(&caching_pool.factory); + puts("\nPress to quit"); + fgets(s, sizeof(s), stdin); - pj_caching_pool_destroy(&caching_pool); - return 0; + return rc; } diff --git a/pjmedia/src/test/sdp_neg_test.c b/pjmedia/src/test/sdp_neg_test.c new file mode 100644 index 00000000..ff474a9a --- /dev/null +++ b/pjmedia/src/test/sdp_neg_test.c @@ -0,0 +1,1209 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms oa the GNU General Public License as published by + * the Free Software Foundation; either version 2 oa the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty oa + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy oa the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include "test.h" + + +#define THIS_FILE "sdp_neg_test.c" +#define START_TEST 0 + +enum session_type +{ + REMOTE_OFFER, + LOCAL_OFFER, +}; + +struct offer_answer +{ + enum session_type type; /* LOCAL_OFFER: REMOTE_OFFER: */ + char *sdp1; /* local offer remote offer */ + char *sdp2; /* remote answer initial local */ + char *sdp3; /* local active media local answer */ +}; + +struct test +{ + const char *title; + unsigned offer_answer_count; + struct offer_answer offer_answer[4]; +} test[] = +{ + /* test 0: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.1 (Alice's view) + * + * Difference from the example: + * - Bob's port number of the third media stream in the first answer + * is changed (make it different than Alice's) + * - in the second offer/answer exchange, Alice can't accept the + * additional line since she didn't specify the capability + * in the initial negotiator creation. + */ + + "RFC 3264 example 10.1 (Alice's view)", + 2, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 51372 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Received Bob's answer: */ + "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Alice's SDP now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + }, + { + REMOTE_OFFER, + /* Bob wants to change his local SDP + * (change local port for the first stream and add new stream) + * Received SDP from Bob: + */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 65422 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 51434 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=recvonly\r\n", + NULL, + /* Alice's SDP now */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" /* <-- this is not necessary */ + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 0 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=sendonly\r\n" + } + } + }, + + /* test 1: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.1. (Bob's view) + * + * Difference: + * - the SDP version in Bob's capability is changed to ver-1. + */ + + "RFC 3264 example 10.1 (Bob's view)", + 2, + { + { + REMOTE_OFFER, + /* Remote offer from Alice: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 51372 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's capability: */ + "v=0\r\n" + "o=bob 2890844730 2890844729 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* This's how Bob's answer should look like: */ + "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + }, + { + LOCAL_OFFER, + /* Bob wants to change his local SDP + * (change local port for the first stream and add new stream) + */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 65422 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 51434 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=recvonly\r\n", + /* Got answer from Alice */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 53122 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=sendonly\r\n", + /* This is how Bob's SDP should look like after negotiation */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 65422 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "m=video 53000 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + "m=audio 51434 RTP/AVP 110\r\n" + "a=rtpmap:110 telephone-events/8000\r\n" + "a=recvonly\r\n" + } + } + }, + + /* test 2: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.2. + * This is from Alice's point of view. + */ + + "RFC 3264 example 10.2 (Alice's view)", + 2, + { + { + LOCAL_OFFER, + /* The initial offer from Alice to Bob indicates a single audio + * stream with the three audio codecs that are available in the + * DSP. The stream is marked as inactive, + */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 0 4 18\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=rtpmap:18 G729/8000\r\n" + "a=inactive\r\n", + /* Bob can support dynamic switching between PCMU and G.723. So, + * he sends the following answer: + */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 0 4\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=inactive\r\n", + /* This is how Alice's media should look like after negotiation */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 0 4\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=inactive\r\n", + }, + { + LOCAL_OFFER, + /* Alice sends an updated offer with a sendrecv stream: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + /* Bob accepts the single codec: */ + "v=0\r\n" + "o=bob 2890844730 2890844732 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + /* This is how Alice's media should look like after negotiation */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n" + } + } + }, + + /* test 3: */ + { + /********************************************************************* + * RFC 3264 examples, section 10.2. + * This is from Bob's point of view. + * + * Difference: + * - The SDP version number in Bob's initial capability is ver-1 + */ + + "RFC 3264 example 10.2 (Bob's view)", + 2, + { + { + REMOTE_OFFER, + /* Bob received offer from Alice: + */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 0 4 18\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=rtpmap:18 G729/8000\r\n" + "a=inactive\r\n", + /* Bob's capability: + */ + "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 0 4\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=inactive\r\n", + /* This is how Bob's media should look like after negotiation */ + "v=0\r\n" + "o=bob 2890844730 2890844731 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=inactive\r\n" + }, + { + REMOTE_OFFER, + /* Received updated Alice's SDP: offer with a sendrecv stream: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.anywhere.com\r\n" + "s=-\r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 62986 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + /* Bob accepts the single codec: */ + NULL, + /* This is how Bob's media should look like after negotiation */ + "v=0\r\n" + "o=bob 2890844730 2890844732 IN IP4 host.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 54344 RTP/AVP 4\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=sendrecv\r\n", + } + } + }, + + /* test 4: */ + { + /********************************************************************* + * RFC 4317 Sample 2.1: Audio and Video 1 (Alice's view) + * + * This common scenario shows a video and audio session in which + * multiple codecs are offered but only one is accepted. As a result of + * the exchange shown below, Alice and Bob may send only PCMU audio and + * MPV video. Note: Dynamic payload type 97 is used for iLBC codec + */ + "RFC 4317 section 2.1: Audio and Video 1 (Alice's view)", + 1, + { + { + LOCAL_OFFER, + /* Alice's local offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Received answer from Bob: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 49170 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* This is how Alice's media should look like now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 51372 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + } + } + }, + + /* test 5: */ + { + /********************************************************************* + * RFC 4317 Sample 2.1: Audio and Video 1 (Bob's view) + * + * This common scenario shows a video and audio session in which + * multiple codecs are offered but only one is accepted. As a result of + * the exchange shown below, Alice and Bob may send only PCMU audio and + * MPV video. Note: Dynamic payload type 97 is used for iLBC codec + * + * Difference: + * - Bob's initial capability version number + */ + "RFC 4317 section 2.1: Audio and Video 1 (Bob's view)", + 1, + { + { + REMOTE_OFFER, + /* Received Alice's local offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's capability: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 49170 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* This is how Bob's media should look like now: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=-\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 49170 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n" + } + } + }, + + /* test 6: */ + { + /********************************************************************* + * RFC 4317 Sample 2.2: Audio and Video 2 (Alice's view) + * + * Difference: + * - Bob's initial capability version number + */ + "RFC 4317 section 2.2: Audio and Video 2 (Alice's view)", + 2, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0 8\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* This is how Alice's media should look like now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + }, + { + LOCAL_OFFER, + /* Alice sends updated offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 51372 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* Bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* This is how Alice's SDP should look like: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 51372 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + } + } + }, + + /* test 7: */ + { + /********************************************************************* + * RFC 4317 Sample 2.2: Audio and Video 2 (Bob's view) + * + * Difference: + * - Bob's initial capability version number + */ + "RFC 4317 section 2.2: Audio and Video 2 (Bob's view)", + 2, + { + { + REMOTE_OFFER, + /* Received offer from alice: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 8 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=video 51372 RTP/AVP 31 32\r\n" + "a=rtpmap:31 H261/90000\r\n" + "a=rtpmap:32 MPV/90000\r\n", + /* Bob's initial capability: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0 8\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* This is how Bob's answer should look like now: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + }, + { + REMOTE_OFFER, + /* Received updated offer from Alice: */ + "v=0\r\n" + "o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 51372 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* Bob's answer: */ + NULL, + /* This is how Bob's answer should look like: */ + "v=0\r\n" + "o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n" + } + } + }, + + /* test 8: */ + { + /********************************************************************* + * RFC 4317 Sample 2.4: Audio and Telephone-Events (Alice's view) + * + */ + + "RFC 4317 section 2.4: Audio and Telephone-Events (Alice's view)", + 1, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49172 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=sendonly\r\n", + /* Received Bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 97\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49174 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=recvonly\r\n", + /* Alice's SDP now: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 97\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49172 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=sendonly\r\n" + } + } + }, + + + /* test 9: */ + { + /********************************************************************* + * RFC 4317 Sample 2.4: Audio and Telephone-Events (Bob's view) + * + * Difference: + * - Bob's initial SDP version number + * - Bob's capability are added with more formats, and the + * stream order is interchanged to test the negotiator. + */ + + "RFC 4317 section 2.4: Audio and Telephone-Events (Bob's view)", + 1, + { + { + REMOTE_OFFER, + /* Received Alice's offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0 97\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49172 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=sendonly\r\n", + /* Bob's initial capability: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49174 RTP/AVP 4 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "m=audio 49172 RTP/AVP 97 8 99\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:99 telephone-event/8000\r\n" + "a=recvonly\r\n", + /* Bob's answer should be: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49172 RTP/AVP 97\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "m=audio 49174 RTP/AVP 98\r\n" + "a=rtpmap:98 telephone-event/8000\r\n" + "a=recvonly\r\n" + } + } + }, + + /* test 10: */ + { + /********************************************************************* + * RFC 4317 Sample 2.6: Audio with Telephone-Events (Alice's view) + * + */ + + "RFC 4317 section 2.6: Audio with Telephone-Events (Alice's view)", + 1, + { + { + LOCAL_OFFER, + /* Alice sends offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 51372 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + /* Received bob's answer: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 49170 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + /* Alice's local SDP should be: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 51372 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n" + } + } + }, + + /* test 11: */ + { + /********************************************************************* + * RFC 4317 Sample 2.6: Audio with Telephone-Events (Bob's view) + * + * Difference: + * - Bob's SDP version number + * - Bob's initial capability are expanded with multiple m lines + * and more formats + */ + + "RFC 4317 section 2.6: Audio with Telephone-Events (Bob's view)", + 1, + { + { + REMOTE_OFFER, + /* Received Alice's offer: */ + "v=0\r\n" + "o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\r\n" + "s=alice\r\n" + "c=IN IP4 host.atlanta.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 51372 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + /* Bob's initial capability also has video: */ + "v=0\r\n" + "o=bob 2808844564 2808844563 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 4 97 101\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n" + "m=video 1000 RTP/AVP 31\r\n" + "a=rtpmap:31 H261/90000\r\n", + /* Bob's answer should be: */ + "v=0\r\n" + "o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\r\n" + "s=bob\r\n" + "c=IN IP4 host.biloxi.example.com\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=audio 49170 RTP/AVP 97 101\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:101 telephone-event/8000\r\n", + } + } + }, + +}; + +static const char *find_diff(const char *s1, const char *s2, + int *line) +{ + *line = 1; + while (*s2 && *s1) { + if (*s2 != *s1) + return s2; + if (*s2 == '\n') + ++*line; + ++s2, ++s1; + } + + return s2; +} + +static int compare_sdp_string(const char *cmp_title, + const char *title1, + const pjmedia_sdp_session *sdp1, + const char *title2, + const pjmedia_sdp_session *sdp2, + pj_status_t logical_cmp) +{ + char sdpbuf1[1024], sdpbuf2[1024]; + pj_ssize_t len1, len2; + + len1 = pjmedia_sdp_print(sdp1, sdpbuf1, sizeof(sdpbuf1)); + if (len1 < 1) { + PJ_LOG(3,(THIS_FILE," error: printing sdp1")); + return -1; + } + sdpbuf1[len1] = '\0'; + + len2 = pjmedia_sdp_print(sdp2, sdpbuf2, sizeof(sdpbuf2)); + if (len2 < 1) { + PJ_LOG(3,(THIS_FILE," error: printing sdp2")); + return -1; + } + sdpbuf2[len2] = '\0'; + + if (logical_cmp != PJ_SUCCESS) { + char errbuf[80]; + + pjmedia_strerror(logical_cmp, errbuf, sizeof(errbuf)); + + PJ_LOG(3,(THIS_FILE,"%s mismatch: %s\n" + "%s:\n" + "%s\n" + "%s:\n" + "%s\n", + cmp_title, + errbuf, + title1, sdpbuf1, + title2, sdpbuf2)); + + return -1; + + } else if (strcmp(sdpbuf1, sdpbuf2) != 0) { + int line; + const char *diff; + + PJ_LOG(3,(THIS_FILE,"%s mismatch:\n" + "%s:\n" + "%s\n" + "%s:\n" + "%s\n", + cmp_title, + title1, sdpbuf1, + title2, sdpbuf2)); + + diff = find_diff(sdpbuf1, sdpbuf2, &line); + PJ_LOG(3,(THIS_FILE,"Difference: line %d:\n" + "%s", + line, diff)); + return -1; + + } else { + return 0; + } + +} + +static int offer_answer_test(pj_pool_t *pool, pjmedia_sdp_neg **p_neg, + struct offer_answer *oa) +{ + pjmedia_sdp_session *sdp1; + pjmedia_sdp_neg *neg; + pj_status_t status; + + status = pjmedia_sdp_parse(pool, oa->sdp1, pj_native_strlen(oa->sdp1), + &sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: unexpected parse status for sdp1"); + return -10; + } + + status = pjmedia_sdp_validate(sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp1 validation failed"); + return -15; + } + + neg = *p_neg; + + if (oa->type == LOCAL_OFFER) { + + /* + * Local creates offer first. + */ + pjmedia_sdp_session *sdp2, *sdp3; + const pjmedia_sdp_session *active; + + if (neg == NULL) { + /* Create negotiator with local offer. */ + status = pjmedia_sdp_neg_create_w_local_offer(pool, sdp1, &neg); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_create_w_local_offer"); + return -20; + } + *p_neg = neg; + + } else { + /* Modify local offer */ + status = pjmedia_sdp_neg_modify_local_offer(pool, neg, sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_modify_local_offer"); + return -30; + } + } + + /* Parse and validate remote answer */ + status = pjmedia_sdp_parse(pool, oa->sdp2, pj_native_strlen(oa->sdp2), + &sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp2"); + return -40; + } + + status = pjmedia_sdp_validate(sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp2 validation failed"); + return -50; + } + + /* Give the answer to negotiator. */ + status = pjmedia_sdp_neg_rx_remote_answer(pool, neg, sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_rx_remote_answer"); + return -60; + } + + /* Negotiate remote answer with local answer */ + status = pjmedia_sdp_neg_negotiate(pool, neg, 0); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_negotiate"); + return -70; + } + + /* Get the local active media. */ + status = pjmedia_sdp_neg_get_local(neg, &active); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_get_local"); + return -80; + } + + /* Parse and validate the correct active media. */ + status = pjmedia_sdp_parse(pool, oa->sdp3, pj_native_strlen(oa->sdp3), + &sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp3"); + return -90; + } + + status = pjmedia_sdp_validate(sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp3 validation failed"); + return -100; + } + + /* Compare active with sdp3 */ + status = pjmedia_sdp_session_cmp(active, sdp3, 0); + if (status != PJ_SUCCESS) { + app_perror(status, " error: active local comparison mismatch"); + compare_sdp_string("Logical cmp after negotiatin remote answer", + "Active local sdp from negotiator", active, + "The correct active local sdp", sdp3, + status); + return -110; + } + + /* Compare the string representation oa both sdps */ + status = compare_sdp_string("String cmp after negotiatin remote answer", + "Active local sdp from negotiator", active, + "The correct active local sdp", sdp3, + PJ_SUCCESS); + if (status != 0) + return -120; + + } else { + /* + * Remote creates offer first. + */ + + pjmedia_sdp_session *sdp2, *sdp3; + const pjmedia_sdp_session *answer; + + if (neg == NULL) { + /* Parse and validate initial local capability */ + status = pjmedia_sdp_parse(pool, oa->sdp2, pj_native_strlen(oa->sdp2), + &sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp2"); + return -200; + } + + status = pjmedia_sdp_validate(sdp2); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp2 validation failed"); + return -210; + } + + /* Create negotiator with remote offer. */ + status = pjmedia_sdp_neg_create_w_remote_offer(pool, sdp2, sdp1, &neg); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_create_w_remote_offer"); + return -220; + } + *p_neg = neg; + + } else { + /* Received subsequent offer from remote. */ + status = pjmedia_sdp_neg_rx_remote_offer(pool, neg, sdp1); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_rx_remote_offer"); + return -230; + } + } + + /* Negotiate. */ + status = pjmedia_sdp_neg_negotiate(pool, neg, 0); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_negotiate"); + return -240; + } + + /* Get our answer. */ + status = pjmedia_sdp_neg_get_local(neg, &answer); + if (status != PJ_SUCCESS) { + app_perror(status, " error: pjmedia_sdp_neg_get_local"); + return -250; + } + + /* Parse the correct answer. */ + status = pjmedia_sdp_parse(pool, oa->sdp3, pj_native_strlen(oa->sdp3), + &sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: parsing sdp3"); + return -260; + } + + /* Validate the correct answer. */ + status = pjmedia_sdp_validate(sdp3); + if (status != PJ_SUCCESS) { + app_perror(status, " error: sdp3 validation failed"); + return -270; + } + + /* Compare answer from negotiator and the correct answer */ + status = pjmedia_sdp_session_cmp(sdp3, answer, 0); + if (status != PJ_SUCCESS) { + compare_sdp_string("Logical cmp after negotiating remote offer", + "Local answer from negotiator", answer, + "The correct local answer", sdp3, + status); + + return -280; + } + + /* Compare the string representation oa both answers */ + status = compare_sdp_string("String cmp after negotiating remote offer", + "Local answer from negotiator", answer, + "The correct local answer", sdp3, + PJ_SUCCESS); + if (status != 0) + return -290; + + } + + return 0; +} + +static int perform_test(pj_pool_t *pool, int test_index) +{ + pjmedia_sdp_neg *neg = NULL; + unsigned i; + int rc; + + for (i=0; i + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" + +#define THIS_FILE "test.c" + +#define DO_TEST(test) do { \ + PJ_LOG(3, (THIS_FILE, "Running %s...", #test)); \ + rc = test; \ + PJ_LOG(3, (THIS_FILE, \ + "%s(%d)", \ + (rc ? "..ERROR" : "..success"), rc)); \ + if (rc!=0) goto on_return; \ + } while (0) + + +pj_pool_factory *mem; + + +void app_perror(pj_status_t status, const char *msg) +{ + char errbuf[PJMEDIA_ERR_MSG_SIZE]; + + pjmedia_strerror(status, errbuf, sizeof(errbuf)); + + PJ_LOG(3,(THIS_FILE, "%s: %s", msg, errbuf)); +} + +int test_main(void) +{ + int rc; + pj_caching_pool caching_pool; + + pj_init(); + pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); + + pj_log_set_decor(PJ_LOG_HAS_NEWLINE); + + mem = &caching_pool.factory; + + DO_TEST(sdp_neg_test()); + //sdp_test (&caching_pool.factory); + //rtp_test(&caching_pool.factory); + //session_test (&caching_pool.factory); + //jbuf_main(&caching_pool.factory); + + PJ_LOG(3,(THIS_FILE," ")); + +on_return: + + if (rc != 0) { + PJ_LOG(3,(THIS_FILE,"Test completed with error(s)!")); + } else { + PJ_LOG(3,(THIS_FILE,"Looks like everything is okay!")); + } + + pj_caching_pool_destroy(&caching_pool); + return rc; +} diff --git a/pjmedia/src/test/test.h b/pjmedia/src/test/test.h new file mode 100644 index 00000000..6f5276e6 --- /dev/null +++ b/pjmedia/src/test/test.h @@ -0,0 +1,37 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_TEST_H__ +#define __PJMEDIA_TEST_H__ + +#include +#include + + +int session_test(void); +int rtp_test(void); +int sdp_test(void); +int jbuf_main(void); +int sdp_neg_test(void); + +extern pj_pool_factory *mem; +void app_perror(pj_status_t status, const char *title); + +int test_main(void); + +#endif /* __PJMEDIA_TEST_H__ */ -- cgit v1.2.3