summaryrefslogtreecommitdiff
path: root/pjmedia
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-02-02 19:16:07 +0000
committerBenny Prijono <bennylp@teluu.com>2006-02-02 19:16:07 +0000
commit5125fc4191998ab13529ee59706f95116fc1725a (patch)
treee1f9b4fe1c3c3a1a734e099333c7107678065df9 /pjmedia
parent6cbba81e8937d6793705608e7e68e83ec950dd2a (diff)
Added SDP negotiator and changed SDP structs (tested)
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@129 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia')
-rw-r--r--pjmedia/build/pjmedia.dsp33
-rw-r--r--pjmedia/build/pjmedia.dsw3
-rw-r--r--pjmedia/build/pjmedia_test.dsp34
-rw-r--r--pjmedia/include/pjmedia.h5
-rw-r--r--pjmedia/include/pjmedia/errno.h250
-rw-r--r--pjmedia/include/pjmedia/sdp.h499
-rw-r--r--pjmedia/include/pjmedia/sdp_neg.h214
-rw-r--r--pjmedia/include/pjmedia/types.h67
-rw-r--r--pjmedia/src/pjmedia/errno.c139
-rw-r--r--pjmedia/src/pjmedia/jbuf.c2
-rw-r--r--pjmedia/src/pjmedia/sdp.c998
-rw-r--r--pjmedia/src/pjmedia/sdp_cmp.c292
-rw-r--r--pjmedia/src/pjmedia/sdp_neg.c815
-rw-r--r--pjmedia/src/test/main.c24
-rw-r--r--pjmedia/src/test/sdp_neg_test.c1209
-rw-r--r--pjmedia/src/test/test.c75
-rw-r--r--pjmedia/src/test/test.h37
17 files changed, 4117 insertions, 579 deletions
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 <pjmedia/types.h>
+#include <pjmedia/errno.h>
#include <pjmedia/codec.h>
#include <pjmedia/jbuf.h>
#include <pjmedia/mediamgr.h>
#include <pjmedia/rtcp.h>
#include <pjmedia/rtp.h>
-#include <pjmedia/session.h>
+//#include <pjmedia/session.h>
#include <pjmedia/sound.h>
#include <pjmedia/sdp.h>
+#include <pjmedia/sdp_neg.h>
#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 <benny@prijono.org>
+ *
+ * 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/errno.h>
+
+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 <pj/types.h>
+#include <pjmedia/types.h>
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 <benny@prijono.org>
+ *
+ * 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 <pjmedia/types.h>
+
+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 <benny@prijono.org>
+ *
+ * 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 <pj/types.h>
+
+
+/**
+ * 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 <benny@prijono.org>
+ *
+ * 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 <pjmedia/errno.h>
+#include <pj/string.h>
+
+
+
+/* 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 <pjmedia/sdp.h>
+#include <pjmedia/errno.h>
#include <pjlib-util/scanner.h>
+#include <pj/array.h>
#include <pj/except.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/string.h>
#include <pj/pool.h>
#include <pj/assert.h>
+#include <pj/ctype.h>
+
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->slen<sizeof(fmtbuf)-2, NULL);
+ fmt.ptr = fmtbuf;
+ fmt.slen = c_fmt->slen + 2;
+ fmtbuf[0] = ':';
+ pj_memcpy(fmt.ptr+1, c_fmt->ptr, c_fmt->slen);
+ fmtbuf[c_fmt->slen+1] = ' ';
+
+ }
+
+ for (i=0; i<count; ++i) {
+ if (pj_strcmp(&attr_array[i]->name, 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:<format> <format specific parameter>
+ */
-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; i<count; ++i) {
- if (attr_array[i]->type == 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; i<rtpmap->enc_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; i<rtpmap->param.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; i<m->attr_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; i<m->attr_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; i<PJ_ARRAY_SIZE(attr_map); ++i) {
- struct attr_map_rec *p = &attr_map[i];
- if (pj_strcmp(&attrname, &p->name) == 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; i<rhs->attr_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; i<rhs->media_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; i<sdp->media_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; j<m->desc.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 <benny@prijono.org>
+ *
+ * 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 <pjmedia/sdp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+
+
+/* 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; i<count1; ++i) {
+ const pjmedia_sdp_attr *a1 = attr1[i];
+
+ if (pj_strcmp(&a1->name, &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; i<sd1->desc.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; i<sd1->media_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 <benny@prijono.org>
+ *
+ * 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 <pjmedia/sdp_neg.h>
+#include <pjmedia/sdp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/ctype.h>
+#include <pj/array.h>
+
+/**
+ * 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; i<answer->desc.fmt_count; ++i) {
+ unsigned j;
+ pj_str_t *rem_fmt = &answer->desc.fmt[i];
+
+ for (j=0; j<offer->desc.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; i<offer->desc.fmt_count;) {
+ unsigned j;
+ pj_str_t *fmt = &offer->desc.fmt[i];
+
+ for (j=0; j<answer->desc.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; i<answer->desc.fmt_count; ++i) {
+ unsigned j;
+ pj_str_t *fmt = &answer->desc.fmt[i];
+
+ for (j=i; j<offer->desc.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; mi<offer->media_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; i<offer->desc.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; j<local->desc.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; j<local->desc.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; j<local->desc.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; i<pt_answer_count; ++i) {
+ unsigned j;
+ for (j=i; j<answer->desc.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; i<answer->desc.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; i<offer->media_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; j<initial->media_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 <pj/os.h>
-#include <pj/pool.h>
-#include <pjmedia/sound.h>
-
-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 <ENTER> 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 <benny@prijono.org>
+ *
+ * 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 <pjmedia/sdp.h>
+#include <pjmedia/sdp_neg.h>
+#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<test[test_index].offer_answer_count; ++i) {
+
+ rc = offer_answer_test(pool, &neg, &test[test_index].offer_answer[i]);
+ if (rc != 0) {
+ PJ_LOG(3,(THIS_FILE, " test %d offer answer %d failed with status %d",
+ test_index, i, rc));
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+int sdp_neg_test()
+{
+ unsigned i;
+ int status;
+
+ for (i=START_TEST; i<PJ_ARRAY_SIZE(test); ++i) {
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(mem, "sdp_neg_test", 4000, 4000, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ PJ_LOG(3,(THIS_FILE," test %d: %s", i, test[i].title));
+ status = perform_test(pool, i);
+
+ pj_pool_release(pool);
+
+ if (status != 0) {
+ return status;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/pjmedia/src/test/test.c b/pjmedia/src/test/test.c
new file mode 100644
index 00000000..ff183a0e
--- /dev/null
+++ b/pjmedia/src/test/test.c
@@ -0,0 +1,75 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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 <benny@prijono.org>
+ *
+ * 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 <pjmedia.h>
+#include <pjlib.h>
+
+
+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__ */