Author: stkn Date: Mon Jul 3 22:03:31 2006 New Revision: 1745 Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/Makefile freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip.c freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip.h freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip_sdp.c freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/Makefile freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/cc-gcc.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/cc-vc.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/common.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-mingw.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-unix.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-win32.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-alpha.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-i386.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-m68k.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-mpc860.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-powerpc.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-sparc.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-darwinos.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-linux-kernel.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-linux.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-palmos.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-rtems.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-sunos.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-win32.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/rules.mak freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/configure (contents, props changed) freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/build/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/build/Makefile freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/build/output/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp.h freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/config.h freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/errno.h freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/sdp.h freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/sdp_neg.h freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/types.h freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/lib/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/errno.c freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp.c freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp_cmp.c freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp_neg.c freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/user.mak Modified: freeswitch/branches/stkn/modules.conf.in Log: Add initial version of mod_pjsip, early development version, partially working for me (tm). Download pjproject-0.5.6.tar.gz from pjproject.org and put it into the toplevel libs folder. Modified: freeswitch/branches/stkn/modules.conf.in ============================================================================== --- freeswitch/branches/stkn/modules.conf.in (original) +++ freeswitch/branches/stkn/modules.conf.in Mon Jul 3 22:03:31 2006 @@ -17,7 +17,8 @@ #dialplans/mod_dialplan_directory dialplans/mod_dialplan_xml #directories/mod_ldap -endpoints/mod_exosip +#endpoints/mod_exosip +endpoints/mod_pjsip endpoints/mod_iax endpoints/mod_dingaling endpoints/mod_portaudio Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/Makefile ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/Makefile Mon Jul 3 22:03:31 2006 @@ -0,0 +1,55 @@ +CFLAGS += -I. + +ifeq ($(OSARCH),Darwin) + LINKER=g++ +else + LINKER=$(CC) +endif + +PJ_PROJECT=pjproject-0.5.6 +PJ_DIR=$(BASE)/libs/$(PJ_PROJECT) + +ifeq ($(MAKECMDGOALS),build) +CC_DEF=-D + +include $(PJ_DIR)/build.mak +include $(PJ_DIR)/build/m-$(MACHINE_NAME).mak +include $(PJ_DIR)/build/os-$(OS_NAME).mak + +CFLAGS += -I$(PJ_DIR)/pjlib/include -I$(PJ_DIR)/pjlib-util/include -I$(PJ_DIR)/pjsip/include +CFLAGS += -I$(PJ_DIR)/pjmedia/include -I./pjsdp/pjsdp/include +CFLAGS += $(OS_CFLAGS) $(M_CFLAGS) + +LDFLAGS += -L$(PJ_DIR)/pjlib/lib -L$(PJ_DIR)/pjlib-util/lib -L$(PJ_DIR)/pjsip/lib -L./pjsdp/pjsdp/lib +LDFLAGS += -lpjsip-$(TARGET_NAME) -lpjsip-ua-$(TARGET_NAME) -lpjsip-simple-$(TARGET_NAME) +LDFLAGS += -lpjsdp-$(TARGET_NAME) -lpjlib-util-$(TARGET_NAME) -lpj-$(TARGET_NAME) +endif + +OBJS=mod_pjsip_sdp.o + +.PHONY: pjsdp + +all: depends + $(MAKE) build + +build: pjsdp $(OBJS) $(MODNAME).$(DYNAMIC_LIB_EXTEN) + +pjsdp: + $(MAKE) CFLAGS="$(filter-out -Werror,$(CFLAGS)) -fPIC" -C pjsdp + +depends: + MAKE=$(MAKE) $(BASE)/build/buildlib.sh $(BASE) $(PJ_PROJECT).tar.gz --prefix=$(PREFIX) + +%.o: %.c + $(CC) -fPIC $(CFLAGS) -c -o $@ $< + +$(MODNAME).$(DYNAMIC_LIB_EXTEN): $(MODNAME).c + $(CC) $(CFLAGS) -fPIC -c $(MODNAME).c -o $(MODNAME).o + $(LINKER) $(SOLINK) -o $(MODNAME).$(DYNAMIC_LIB_EXTEN) $(OBJS) $(MODNAME).o $(LDFLAGS) + +clean: + rm -fr *.$(DYNAMIC_LIB_EXTEN) *.o *~ + $(MAKE) -C pjsdp clean + +install: + cp -f $(MODNAME).$(DYNAMIC_LIB_EXTEN) $(DESTDIR)$(PREFIX)/mod Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip.c ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip.c Mon Jul 3 22:03:31 2006 @@ -0,0 +1,1397 @@ +/* + * Experimental PJSIP channel module for FreeSWITCH + * + * + * + * + * + * + */ +/***** ChangeLog + * + * 2006-06-18: initial version. + * 99% copy'n'paste from FreeSWITCH and pjsip example code. + * + * 2006-06-20: added parts of sdp negotiation, started writing pjsip event handler code. + * more code taken from mod_exosip for FS event handling. + */ + +#define HAVE_APR +#include <switch.h> +#include <pjsip.h> +#include <pjsdp.h> +#include <pjsip_ua.h> +#include <pjsip_simple.h> +#include <pjlib-util.h> +#include <pjlib.h> + +#include "mod_pjsip.h" + +static const char modname[] = "mod_pjsip"; + +#define MAX_CALLS 1000 + +static switch_memory_pool_t *module_pool = NULL; +static pj_caching_pool cp; +static pjsip_inv_callback invite_cb; +static pj_pool_t *pjsip_pool; + +#define PJ_MAX_STRLEN(str, pjstr) ( sizeof(str) > pjstr.slen ? pjstr.slen : sizeof(str) ) + +static struct { + /* */ + switch_hash_t *call_hash; + + pj_str_t local_uri; + pj_str_t local_contact; + pj_sockaddr_in sip_addr; //<- really needed? + + /* endpoints */ + pjsip_endpoint *sip_endpt; + pjsip_module mod_app; + + /* */ + char local_addr[128]; + char dialplan[50]; + int sip_port; + int running; +} globals; + +enum tech_flags { + TFLAG_OUTBOUND = (1 << 0), + TFLAG_INBOUND = (1 << 1), + TFLAG_IO = (1 << 2), + TFLAG_BYE = (1 << 3), + TFLAG_RINGING = (1 << 4), // not used atm + TFLAG_ANSWERED = (1 << 5), + TFLAG_USING_CODEC = (1 << 6), + + TFLAG_READING = (1 << 10), + TFLAG_WRITING = (1 << 11), + TFLAG_TIMER = (1 << 20) +}; + +static switch_status_t create_sdp( pj_pool_t *pool, + struct pjsip_tech_pvt *pvt, + pjmedia_sdp_session **p_sdp, + pjmedia_sdp_session *ref_sdp ); + +static switch_status_t start_rtp( struct pjsip_tech_pvt *pvt ); +static switch_status_t stop_rtp( struct pjsip_tech_pvt *pvt ); +//static struct pjsip_tech_pvt * find_pvt_by_callid( char *id ); + +/* BEGIN: freeswitch callbacks */ +/* event handlers */ +static switch_status_t pjsip_on_init(switch_core_session_t *session); +static switch_status_t pjsip_on_hangup(switch_core_session_t *session); +static switch_status_t pjsip_on_loopback(switch_core_session_t *session); +static switch_status_t pjsip_on_transmit(switch_core_session_t *session); + +/* io routines */ +static switch_status_t pjsip_outgoing_channel(switch_core_session_t *session, switch_caller_profile_t *outbound_profile, + switch_core_session_t **new_session, switch_memory_pool_t *pool); + +static switch_status_t pjsip_read_frame(switch_core_session_t *session, switch_frame_t **frame, int timeout, + switch_io_flag_t flags, int stream_id); // unimplemented + +static switch_status_t pjsip_write_frame(switch_core_session_t *session, switch_frame_t *frame, int timeout, + switch_io_flag_t flags, int stream_id); // unimplemented + +static switch_status_t pjsip_answer_channel(switch_core_session_t *session); +static switch_status_t pjsip_kill_channel(switch_core_session_t *session, int sig); + +static switch_status_t pjsip_waitfor_read(switch_core_session_t *session, int ms, int stream_id); +static switch_status_t pjsip_waitfor_write(switch_core_session_t *session, int ms, int stream_id); + +static switch_status_t pjsip_on_ring(switch_core_session_t *session); + +static switch_status_t pjsip_receive_message(switch_core_session_t *session, + switch_core_session_message_t *msg); + +static const switch_io_routines_t pjsip_io_routines = { + /*.outgoing_channel */ &pjsip_outgoing_channel, + /*.answer_channel */ &pjsip_answer_channel, + /*.read_frame */ &pjsip_read_frame, + /*.write_frame */ &pjsip_write_frame, + /*.kill_channel */ &pjsip_kill_channel, + /*.waitfor_read */ &pjsip_waitfor_read, + /*.waitfor_read */ &pjsip_waitfor_write, + /*.send_dtmf*/ NULL, + /*.receive_message*/ &pjsip_receive_message +}; + +static const switch_state_handler_table_t pjsip_event_handlers = { + /*.on_init */ &pjsip_on_init, + /*.on_ring */ &pjsip_on_ring, + /*.on_execute */ NULL, + /*.on_hangup */ &pjsip_on_hangup, + /*.on_loopback */ &pjsip_on_loopback, + /*.on_transmit */ &pjsip_on_transmit +}; + +static const switch_endpoint_interface_t pjsip_endpoint_interface = { + /*.interface_name */ "pjsip", + /*.io_routines */ &pjsip_io_routines, + /*.event_handlers */ &pjsip_event_handlers, + /*.private */ NULL, + /*.next */ NULL +}; + +static const switch_loadable_module_interface_t pjsip_module_interface = { + /*.module_name */ modname, + /*.endpoint_interface */ &pjsip_endpoint_interface, + /*.timer_interface */ NULL, + /*.dialplan_interface */ NULL, + /*.codec_interface */ NULL, + /*.application_interface */ NULL +}; + +static switch_status_t pjsip_on_init(switch_core_session_t *session) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + pj_status_t status; + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + /* register switch thread to pjsip core */ + status = pj_thread_register( NULL, pvt->thread_desc, &pvt->thread ); + if( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not register switch session thread to pjlib\n" ); + switch_core_session_destroy( &session ); + return SWITCH_STATUS_GENERR; + } + + if( switch_channel_test_flag( channel, CF_OUTBOUND ) ) + { + pjsip_tx_data *txdata = NULL; + pjmedia_sdp_session *sdp = NULL; +// pjsip_sip_uri *dst_uri; + char tmp[50]; + pj_str_t dst; + + pj_ansi_sprintf( tmp, "<sip:%s:%d>", pvt->caller_profile->destination_number, 2054 ); + pj_cstr( &dst, tmp ); + + pvt->sip_ua = pjsip_ua_instance(); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Dst: %s, Local: %s %s\n", tmp, globals.local_uri.ptr, globals.local_contact.ptr ); + + /* create new UAC dialog */ + status = pjsip_dlg_create_uac( pvt->sip_ua, + &globals.local_uri, + &globals.local_contact, + &dst, + &dst, + &pvt->sip_dialog ); + + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new UAC dialog for call\n" ); + switch_core_session_destroy( &session ); + return SWITCH_STATUS_GENERR; + } + + /* create SDP */ + create_sdp( pvt->sip_dialog->pool, pvt, &sdp, NULL ); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Initial SDP created\n" ); + + /* create INVITE session */ + status = pjsip_inv_create_uac( pvt->sip_dialog, sdp, 0, &pvt->sip_invite ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new INVITE session for call %s\n" ); + pjsip_dlg_terminate( pvt->sip_dialog ); + return SWITCH_STATUS_GENERR; + } + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Invite UAC created\n" ); + + /* attach private object to pjsip session data */ + pvt->sip_invite->mod_data[globals.mod_app.id] = pvt; + + /* create invite request */ + status = pjsip_inv_invite( pvt->sip_invite, &txdata ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create initial INVITE request for call %s\n" ); + pjsip_dlg_terminate( pvt->sip_dialog ); + return SWITCH_STATUS_GENERR; + } + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Initial INVITE created\n" ); + + /* send request */ + status = pjsip_inv_send_msg( pvt->sip_invite, txdata ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to send INVITE request for call %s\n" ); + pjsip_dlg_terminate( pvt->sip_dialog ); + return SWITCH_STATUS_GENERR; + } + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "INVITE sent\n" ); + + /* set call id */ + strncpy( pvt->call_id, pvt->sip_invite->dlg->call_id->id.ptr, PJ_MAX_STRLEN( pvt->call_id, pvt->sip_invite->dlg->call_id->id ) ); + } else { + pjsip_tx_data *txdata; + + /* Send 183 Session Progress */ + status = pjsip_inv_answer( pvt->sip_invite, 183, NULL, NULL, &txdata ); + if( status != PJ_SUCCESS ) { + status = pjsip_inv_answer( pvt->sip_invite, PJSIP_SC_NOT_ACCEPTABLE, NULL, NULL, &txdata ); + + if( status == PJ_SUCCESS ) + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + else + pjsip_inv_terminate( pvt->sip_invite, 500, PJ_FALSE ); + + switch_core_session_destroy( &session ); + return SWITCH_STATUS_GENERR; + } + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + } + + /* */ + switch_set_flag( pvt, TFLAG_IO ); + + /* add call to hash */ + switch_core_hash_insert( globals.call_hash, pvt->call_id, pvt ); + + switch_channel_set_state( channel, CS_RING ); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t pjsip_on_ring(switch_core_session_t *session) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + channel = switch_core_session_get_channel( session ); + + pvt = switch_core_session_get_private( session ); +#if 0 /* fix this, pjsip_inv_* is wrong here */ + if( !switch_channel_test_flag( channel, CF_OUTBOUND ) ) { + pj_status_t status; + pjsip_tx_data *txdata; + + /* start to send 180 ring every second */ + status = pjsip_inv_create_answer( pvt->sip_dialog, 180, NULL, &txdata ); + if( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create 183 RING message" ); + status = pjsip_inv_end_session( pvt->sip_invite, 500, NULL, &txdata ); + if( status == PJ_SUCCESS ) + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + else + pjsip_inv_terminate( pvt->sip_invite, 500, 1 ); + + switch_core_session_destroy( &session ); + return SWITCH_STATUS_GENERR; + } + pjsip_dlg_send_response( pvt->sip_dialog, pjsip_rdata_get_tsx(rxdata), txdata ); + switch_set_flag( pvt, TFLAG_RINGING ); + } +#endif + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "RING %s\n", switch_channel_get_name( channel ) ); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t pjsip_on_hangup(switch_core_session_t *session) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + pj_status_t status; + pjsip_tx_data *txdata; + + assert( session != NULL ); + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Hangup call %s\n", pvt->call_id ); + + /* remove call from hash */ + switch_core_hash_delete( globals.call_hash, pvt->call_id ); + + switch_set_flag( pvt, TFLAG_BYE ); + switch_clear_flag( pvt, TFLAG_IO ); + + stop_rtp( pvt ); + + status = pjsip_inv_end_session( pvt->sip_invite, 200, NULL, &txdata ); + if( status != PJ_SUCCESS ) + pjsip_inv_terminate( pvt->sip_invite, 500, PJ_TRUE ); + else + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + + + if ( switch_test_flag( pvt, TFLAG_USING_CODEC ) ) { + switch_core_codec_destroy( &pvt->read_codec ); + switch_core_codec_destroy( &pvt->write_codec ); + } + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t pjsip_on_loopback(switch_core_session_t *session) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + /* do nothing */ + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t pjsip_on_transmit(switch_core_session_t *session) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + /* do nothing */ + + return SWITCH_STATUS_SUCCESS; +} + +/* answer channel */ +static switch_status_t pjsip_answer_channel(switch_core_session_t *session) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "* Answering call %s\n", pvt->call_id ); + + if( !switch_test_flag( pvt, TFLAG_ANSWERED ) && + !switch_channel_test_flag( channel, CF_OUTBOUND ) ) + { + pjsip_tx_data *txdata; + pj_status_t status; + + assert( pvt->sip_invite != NULL ); + + /* create SIP 200 with SDP */ + status = pjsip_inv_answer( pvt->sip_invite, 200, NULL, NULL, &txdata ); + if( status != PJ_SUCCESS ) { + pjsip_inv_terminate( pvt->sip_invite, 500, PJ_TRUE ); + switch_core_session_destroy( &session ); + return SWITCH_STATUS_GENERR; + } + status = pjsip_inv_send_msg( pvt->sip_invite, txdata ); + if( status != PJ_SUCCESS ) { + pjsip_inv_terminate( pvt->sip_invite, 500, PJ_TRUE ); + switch_core_session_destroy( &session ); + return SWITCH_STATUS_GENERR; + } + switch_set_flag( pvt, TFLAG_ANSWERED ); + } + return SWITCH_STATUS_SUCCESS; +} + +/* kill channel */ +static switch_status_t pjsip_kill_channel(switch_core_session_t *session, int sig) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + switch_clear_flag( pvt, TFLAG_IO ); + + if( !switch_test_flag( pvt, TFLAG_BYE ) ) { + switch_set_flag( pvt, TFLAG_BYE ); + switch_channel_hangup( channel, SWITCH_CAUSE_NORMAL_CLEARING ); + } + + if( pvt->rtp_session ) { + switch_rtp_kill_socket( pvt->rtp_session ); + } + return SWITCH_STATUS_SUCCESS; +} + +/* wait for read */ +static switch_status_t pjsip_waitfor_read(switch_core_session_t *session, int ms, int stream_id) +{ + /* do nothing */ + + return SWITCH_STATUS_SUCCESS; +} + +/* wait for write */ +static switch_status_t pjsip_waitfor_write(switch_core_session_t *session, int ms, int stream_id) +{ + /* do nothing */ + + return SWITCH_STATUS_SUCCESS; +} + + +/* create new call */ +static switch_status_t pjsip_outgoing_channel(switch_core_session_t *session, switch_caller_profile_t *outbound_profile, + switch_core_session_t **new_session, switch_memory_pool_t *pool) +{ + struct pjsip_tech_pvt *pvt = NULL; + switch_channel_t *channel = NULL; + + if ( !( *new_session = switch_core_session_request( &pjsip_endpoint_interface, pool ) ) ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new session for call\n" ); + return SWITCH_STATUS_GENERR; + } + switch_core_session_add_stream( *new_session, NULL ); + + /* create new call */ + pvt = (struct pjsip_tech_pvt *) switch_core_session_alloc(*new_session, sizeof(struct pjsip_tech_pvt)); + if (!pvt) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new private object for call\n" ); + goto err; + } + pj_memset( pvt, 0, sizeof(struct pjsip_tech_pvt) ); + switch_core_session_set_private( *new_session, pvt ); + channel = switch_core_session_get_channel( *new_session ); + pvt->session = *new_session; + + /* get list of all available codecs */ + pvt->num_codecs = switch_loadable_module_get_codecs( switch_core_session_get_pool(pvt->session), pvt->codecs, + sizeof(pvt->codecs) / sizeof(pvt->codecs[0]) ); + + if( outbound_profile ) { + switch_caller_profile_t *caller_profile = NULL; +// pjsip_dialog *dialog = NULL; +// pj_status_t status; +// pj_str_t dst_uri; + char name[128]; + + /* create channel name, copy profile and parse destination number (?) */ + snprintf( name, sizeof(name), "pjsip/%s-%04x", outbound_profile->destination_number, rand() & 0xffff ); + switch_channel_set_name( channel, name ); + + caller_profile = switch_caller_profile_clone( *new_session, outbound_profile ); + switch_channel_set_caller_profile( channel, caller_profile ); + + pvt->caller_profile = caller_profile; + + switch_channel_set_flag( channel, CF_OUTBOUND ); + switch_channel_set_state( channel, CS_INIT ); + + return SWITCH_STATUS_SUCCESS; + } +err: + switch_core_session_destroy(new_session); + return SWITCH_STATUS_GENERR; +} + +static switch_status_t pjsip_read_frame(switch_core_session_t *session, switch_frame_t **frame, int timeout, + switch_io_flag_t flags, int stream_id) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + assert( session != NULL ); + assert( frame != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + switch_set_flag( pvt, TFLAG_READING ); + + pvt->read_frame.datalen = 0; + + if( switch_test_flag( pvt, TFLAG_IO ) ) { + switch_status_t status; + + assert( pvt->rtp_session != NULL ); + + while( !switch_test_flag( pvt, TFLAG_BYE ) && + switch_test_flag( pvt, TFLAG_IO ) && + !pvt->read_frame.datalen ) + { + status = switch_rtp_zerocopy_read_frame( pvt->rtp_session, &pvt->read_frame ); + if( status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK ) { + return SWITCH_STATUS_FALSE; + } + + + if( pvt->read_frame.datalen > 0 ) { + int samples, bytes, frames; + + bytes = pvt->read_codec.implementation->encoded_bytes_per_frame; + frames = pvt->read_frame.datalen / bytes; + samples = frames * pvt->read_codec.implementation->samples_per_frame; + + pvt->read_frame.samples = (int)samples; + break; + } + + switch_yield( 1000 ); + } + } + + switch_clear_flag( pvt, TFLAG_READING ); + + *frame = &pvt->read_frame; + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t pjsip_write_frame(switch_core_session_t *session, switch_frame_t *frame, int timeout, + switch_io_flag_t flags, int stream_id) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + assert( session != NULL ); + assert( frame != NULL ); + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + switch_set_flag( pvt, TFLAG_WRITING ); + + if( switch_test_flag( pvt, TFLAG_IO ) ) { +// switch_status_t status; + int samples, frames, bytes; + + assert( pvt->rtp_session != NULL ); + + bytes = pvt->read_codec.implementation->encoded_bytes_per_frame; + frames = (int)frame->datalen / bytes; + samples = frames * pvt->read_codec.implementation->samples_per_frame; + + switch_rtp_write_frame( pvt->rtp_session, frame, samples ); + } + + switch_clear_flag( pvt, TFLAG_WRITING ); + + return SWITCH_STATUS_SUCCESS; +} + +/* END: freeswitch callbacks */ + + +/* BEGIN: pjsip callbacks */ + +/* + * + */ +static void on_incoming_call( pjsip_rx_data *rxdata ) +{ + switch_core_session_t *session; + pj_status_t status; + + /* check call limit */ +#if 0 + if( switch_core_hash_count( &globals.call_hash ) >= MAX_CALLS ) { + const pj_str_t reason = pj_str("Too many calls"); + pjsip_endpt_respond_stateless( app.sip_endpt, rxdata, + 500, &reason, + NULL, NULL); + return; + } +#endif + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "PJSIP process id %lu, thread name %s\n", pj_getpid(), pj_thread_get_name( pj_thread_this() ) ); + + if ((session = switch_core_session_request( &pjsip_endpoint_interface, NULL ))) { + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + char name[128]; + pjmedia_sdp_session *sdp; + pjsip_dialog *dialog; + pjsip_tx_data *txdata; + pj_str_t tmp, username, host, destination; + + channel = switch_core_session_get_channel( session ); + + switch_core_session_add_stream( session, NULL ); + if ((pvt = (struct pjsip_tech_pvt *) switch_core_session_alloc( session, sizeof(struct pjsip_tech_pvt) ))) { + memset( pvt, 0, sizeof(struct pjsip_tech_pvt) ); + switch_core_session_set_private( session, pvt ); + pvt->session = session; + } else { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new channel for inbound call\n" ); + switch_core_session_destroy( &session ); + return; + } + /* give the new session a name */ + pj_strdup_with_null( pjsip_pool, &tmp, &((pjsip_sip_uri *)rxdata->msg_info.from->uri)->host ); + snprintf(name, sizeof(name), "pjsip/%s-%04x", tmp.ptr, rand() & 0xffff); + switch_channel_set_name( channel, name ); + + switch_channel_clear_flag( channel, CF_OUTBOUND ); + + /* need the invite data in pjsip_on_init */ + pvt->sip_ua = pjsip_ua_instance(); + + /* create UAS dialog */ + status = pjsip_dlg_create_uas( pvt->sip_ua, rxdata, NULL, &dialog ); + if( status != PJ_SUCCESS ) { + const pj_str_t reason = pj_str("Unable to create dialog"); + pjsip_endpt_respond_stateless( globals.sip_endpt, rxdata, + 500, &reason, + NULL, NULL); + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new pjsip dialog for inbound call\n" ); + switch_core_session_destroy( &session ); + return; + } + + /* load codecs */ + pvt->num_codecs = switch_loadable_module_get_codecs( switch_core_session_get_pool(pvt->session), pvt->codecs, + sizeof(pvt->codecs) / sizeof(pvt->codecs[0]) ); + + /* Create our initial sdp */ + create_sdp( dialog->pool, pvt, &sdp, NULL ); + + /* Create UAS invite session */ + status = pjsip_inv_create_uas( dialog, rxdata, sdp, 0, &pvt->sip_invite ); + if( status != PJ_SUCCESS ) { + pjsip_dlg_create_response( dialog, rxdata, 500, NULL, &txdata ); + pjsip_dlg_send_response( dialog, pjsip_rdata_get_tsx( rxdata ), txdata ); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new pjsip dialog for inbound call\n" ); + switch_core_session_destroy( &session ); + return; + } + + pj_strdup_with_null( pjsip_pool, &username, &((pjsip_sip_uri *)rxdata->msg_info.from->uri)->user ); + pj_strdup_with_null( pjsip_pool, &host, &((pjsip_sip_uri *)rxdata->msg_info.from->uri)->host ); + pj_strdup_with_null( pjsip_pool, &destination, &((pjsip_sip_uri *)rxdata->msg_info.to->uri)->host ); + + if ( (pvt->caller_profile = switch_caller_profile_new( switch_core_session_get_pool( session ), + username.ptr, + globals.dialplan, + username.ptr, + username.ptr, + host.ptr, + NULL, + NULL, + NULL, + (char *)modname, + NULL, + destination.ptr)) != 0 ) { + switch_channel_set_caller_profile( channel, pvt->caller_profile ); + } + + /* assign vars */ + pvt->sip_invite->mod_data[globals.mod_app.id] = pvt; + pvt->sip_dialog = dialog; + + /* set call id */ + strncpy( pvt->call_id, dialog->call_id->id.ptr, PJ_MAX_STRLEN(pvt->call_id, dialog->call_id->id) ); + + switch_set_flag( pvt, TFLAG_INBOUND ); + + /* Send 100 Trying */ + status = pjsip_inv_initial_answer( pvt->sip_invite, rxdata, 100, NULL, NULL, &txdata ); + if( status != PJ_SUCCESS ) { + status = pjsip_inv_initial_answer( pvt->sip_invite, rxdata, PJSIP_SC_NOT_ACCEPTABLE, NULL, NULL, &txdata ); + + if( status == PJ_SUCCESS ) + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + else + pjsip_inv_terminate( pvt->sip_invite, 500, PJ_FALSE ); + + switch_core_session_destroy( &session ); + return; + } + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + + /* Print the initial SDP to the console / log */ + { + char tmp_sdp[1024]; + int len = pjmedia_sdp_print( sdp, tmp_sdp, sizeof(tmp_sdp)-1 ); + if( len > 0 ) { + tmp_sdp[len] = '\0'; + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "\n** Our initial SDP **\n%s\n*********************\n", tmp_sdp ); + } + } + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Starting session thread for call %s\n", pvt->call_id ); + + /* set initial state */ + switch_channel_set_state( channel, CS_INIT ); + switch_core_session_thread_launch( session ); + return; + } else { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new channel for inbound call\n" ); + return; + } +} + +static pj_bool_t on_rx_request( pjsip_rx_data *rxdata ) +{ + pj_str_t reason; + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: received new request\n", __FUNCTION__ ); + + switch( rxdata->msg_info.msg->line.req.method.id ) { + case PJSIP_ACK_METHOD: + return PJ_FALSE; + ;; + case PJSIP_INVITE_METHOD: + on_incoming_call( rxdata ); + return PJ_TRUE; + ;; + default: + reason = rxdata->msg_info.msg->line.req.method.name; + pjsip_endpt_respond_stateless( globals.sip_endpt, rxdata, + 500, &reason, NULL, NULL ); + ;; + } + return PJ_TRUE; +} + +static void call_on_tsx_state_changed( pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e ) +{ + struct pjsip_tech_pvt *pvt; + switch_core_session_t *session; + + assert( inv != NULL && tsx != NULL ); + + pvt = inv->mod_data[globals.mod_app.id]; + assert( pvt != NULL ); + + session = pvt->session; + assert( session != NULL ); + + if( inv->state == PJSIP_INV_STATE_EARLY && + tsx->state == PJSIP_TSX_STATE_PROCEEDING && + tsx->status_code == PJSIP_SC_RINGING ) + { + switch_channel_t *channel; + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "* Ring received (call %s)\n", pvt->call_id ); + } +} + +static void call_on_forked( pjsip_inv_session *inv, + pjsip_event *evt ) { } + +/* pjsip callback for inv session state changes */ +static void call_on_state_changed( pjsip_inv_session *inv, + pjsip_event *evt ) +{ + struct pjsip_tech_pvt *pvt; + switch_core_session_t *session; + switch_channel_t *channel; + + assert( evt != NULL && inv != NULL ); + + pvt = inv->mod_data[globals.mod_app.id]; + assert( pvt != NULL ); + + session = pvt->session; + assert( session != NULL ); + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "- Call %s state has changed (event type %s)\n", pvt->call_id, pjsip_event_str( evt->type ) ); + + switch( inv->state ) + { + case PJSIP_INV_STATE_DISCONNECTED: + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "* Call %s has been disconnected\n", pvt->call_id ); + switch_channel_hangup( channel, SWITCH_CAUSE_NORMAL_CLEARING ); + /* -> hangup */ + break; + case PJSIP_INV_STATE_CONFIRMED: + /* -> connected */ + switch_channel_answer( channel ); + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "* Call %s has been connected\n", pvt->call_id ); + break; + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_CONNECTING: + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "* Call %s, state %s\n", pvt->call_id, pjsip_inv_state_name( inv->state ) ); + break; + default: + break; + } +} + +/* pjsip callback for sdp negotiation changes */ +static void call_on_media_update( pjsip_inv_session *inv, + pj_status_t status ) +{ + struct pjsip_tech_pvt *pvt; + const pjmedia_sdp_session *remote_sdp, *local_sdp; +#if 0 + pjmedia_stream_info streaminfo; + pj_in_addr rem_addr; +#endif + pj_pool_t *pool; + const switch_codec_interface_t *codec; + const switch_codec_implementation_t *imp; + switch_channel_t *channel; + switch_core_session_t *session; + switch_status_t ret; + int ms, rate; + pj_sockaddr addr; + + pvt = inv->mod_data[globals.mod_app.id]; + pool = inv->dlg->pool; + assert( pvt != NULL ); + assert( pool != NULL ); + + session = pvt->session; + assert( session != NULL ); + + channel = switch_core_session_get_channel( pvt->session ); + assert( channel != NULL ); + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "-- SDP negotiation status change %s (state %s, status %d)\n", pvt->call_id, + pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state( inv->neg )), status ); + + if( status != PJ_SUCCESS ) { + char tmp[1024]; + pj_strerror( status, tmp, sizeof(tmp) ); + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SDP negotiation failed for call %s, reason: %s\n", pvt->call_id, tmp ); + switch_channel_hangup( channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER ); + return; + } + + if( pvt->rtp_session ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SDP re-negotiation received %s\n", pvt->call_id ); + + stop_rtp( pvt ); + switch_clear_flag( pvt, TFLAG_IO ); + + /* destroy codecs */ + if ( switch_test_flag( pvt, TFLAG_USING_CODEC ) ) { + switch_core_codec_destroy( &pvt->read_codec ); + switch_core_codec_destroy( &pvt->write_codec ); + } + } + + pjmedia_sdp_neg_get_active_local( inv->neg, &local_sdp ); + pjmedia_sdp_neg_get_active_remote( inv->neg, &remote_sdp ); + + /* Print the final(?) SDPs to the console / log */ + { + char tmp_sdp[1024]; + int len; + + len = pjmedia_sdp_print( local_sdp, tmp_sdp, sizeof(tmp_sdp)-1 ); + if( len > 0 ) { + tmp_sdp[len] = '\0'; + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "\n** Our final SDP **\n%s\n*********************\n", tmp_sdp ); + } + + len = pjmedia_sdp_print( remote_sdp, tmp_sdp, sizeof(tmp_sdp)-1 ); + if( len > 0 ) { + tmp_sdp[len] = '\0'; + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, + "\n** Remote final SDP **\n%s\n*************************\n", tmp_sdp ); + } + } + + /* retrieve stream (= codec) information */ + status = mod_pjsip_codec_from_sdp( pvt, pool, local_sdp, remote_sdp, 0, &codec, &imp ); + if( status != PJ_SUCCESS ) { + /* No valid codec found */ + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SDP negotation failed, codec unknown\n" ); + switch_set_flag( pvt, TFLAG_BYE ); + switch_clear_flag( pvt, TFLAG_IO ); + switch_channel_hangup( channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER ); + return; + } + + /* valid codec + implementation found, setup codecs */ + rate = imp->bits_per_second / 8; + ms = imp->microseconds_per_frame / 1000; + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Codec %s, rate: %d, ms per frame: %d\n", codec->implementations->iananame, rate, ms ); + + /* initialize codecs */ + ret = switch_core_codec_init(&pvt->read_codec, codec->implementations->iananame, rate, ms, 1, + SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + NULL, switch_core_session_get_pool( session )); + + if( ret != SWITCH_STATUS_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize read codec\n" ); + switch_channel_hangup( channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER ); + return; + } + + ret = switch_core_codec_init(&pvt->write_codec, codec->implementations->iananame, rate, ms, 1, + SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + NULL, switch_core_session_get_pool( session )); + + if( ret != SWITCH_STATUS_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize write codec\n" ); + switch_channel_hangup( channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER ); + return; + } + + pvt->read_frame.rate = rate; + pvt->read_frame.codec = &pvt->read_codec; + + switch_core_session_set_read_codec( session, &pvt->read_codec ); + switch_core_session_set_write_codec( session, &pvt->write_codec ); + + /* update remote rtp IP and port, restart rtp */ + status = mod_pjsip_addr_from_sdp( remote_sdp, 0, &addr ); + if( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to get remote RTP endpoint address and port\n" ); + switch_channel_hangup( channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER ); + } else { + pj_in_addr tmp_addr; + + pvt->remote_sdp_audio_port = pj_sockaddr_in_get_port( (pj_sockaddr_in *)&addr ); + tmp_addr.s_addr = pj_htonl( pj_sockaddr_in_get_addr( (pj_sockaddr_in *)&addr ).s_addr ); + strncpy( pvt->remote_sdp_audio_addr, pj_inet_ntoa( tmp_addr ), + sizeof(pvt->remote_sdp_audio_addr) ); + } + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Codec %s, %s:%d\n", codec->implementations->iananame, pvt->remote_sdp_audio_addr, pvt->remote_sdp_audio_port ); + + assert( channel != NULL ); + + start_rtp( pvt ); +#if 0 + if( !switch_channel_test_flag( channel, CF_OUTBOUND ) ) + switch_channel_set_state( channel, CS_RING ); +#endif +} + +static switch_status_t pjsip_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg) +{ + struct pjsip_tech_pvt *pvt; + switch_channel_t *channel; + + pvt = switch_core_session_get_private( session ); + assert( pvt != NULL ); + + channel = switch_core_session_get_channel( session ); + assert( channel != NULL ); + + switch( msg->message_id ) + { + case SWITCH_MESSAGE_INDICATE_BRIDGE: + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "MSG Indicate Bridge\n" ); + if( pvt->rtp_session && switch_test_flag( pvt, TFLAG_TIMER ) ) { + switch_rtp_clear_flag( pvt->rtp_session, SWITCH_RTP_FLAG_USE_TIMER ); + } + break; + case SWITCH_MESSAGE_INDICATE_UNBRIDGE: + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "MSG Indicate Unbridge\n" ); + if( pvt->rtp_session && switch_test_flag( pvt, TFLAG_TIMER ) ) { + switch_rtp_set_flag( pvt->rtp_session, SWITCH_RTP_FLAG_USE_TIMER ); + } + break; + case SWITCH_MESSAGE_INDICATE_PROGRESS: + { + pj_status_t status; + pjsip_tx_data *txdata; + /* Send 183 Session Progress */ + + status = pjsip_inv_answer( pvt->sip_invite, 183, NULL, NULL, &txdata ); + if( status != PJ_SUCCESS ) { + status = pjsip_inv_answer( pvt->sip_invite, PJSIP_SC_NOT_ACCEPTABLE, NULL, NULL, &txdata ); + + if( status == PJ_SUCCESS ) + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + else + pjsip_inv_terminate( pvt->sip_invite, 500, PJ_FALSE ); + + switch_core_session_destroy( &session ); + return SWITCH_STATUS_GENERR; + } + pjsip_inv_send_msg( pvt->sip_invite, txdata ); + } + switch_channel_set_flag( channel, CF_EARLY_MEDIA ); + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + +/* END: pjsip callbacks */ + +/* BEGIN: mid-level functions */ +static switch_status_t create_sdp( pj_pool_t *pool, + struct pjsip_tech_pvt *pvt, + pjmedia_sdp_session **p_sdp, + pjmedia_sdp_session *ref_sdp ) +{ + pjmedia_sdp_session *sdp; + pjmedia_sdp_media *media; + pjmedia_sdp_attr *attr, *rtpmap_attr; + pjmedia_sdp_rtpmap rtpmap; + int i; + + if( !pool || !pvt ) + return SWITCH_STATUS_GENERR; + + /* allocate and initialize new SDP var */ + sdp = pj_pool_zalloc( pool, sizeof(pjmedia_sdp_session) ); + if( !sdp ) + return SWITCH_STATUS_GENERR; + + sdp->origin.user = pj_str( "FreeSWITCH" ); + sdp->origin.version = sdp->origin.id = 0; + sdp->origin.net_type = pj_str( "IN" ); + sdp->origin.addr_type = pj_str( "IP4" ); + sdp->origin.addr = *pj_gethostname(); + sdp->name = pj_str( "pjsip" ); + + sdp->conn = pj_pool_zalloc( pool, sizeof(pjmedia_sdp_conn) ); + sdp->conn->net_type = pj_str( "IN" ); + sdp->conn->addr_type = pj_str( "IP4" ); + sdp->conn->addr = pj_str( globals.local_addr ); + + sdp->time.start = sdp->time.stop = 0; + sdp->attr_count = 0; + + /* add media to SDP */ + sdp->media_count = 1; + media = pj_pool_zalloc( pool, sizeof(pjmedia_sdp_media) ); + sdp->media[0] = media; + + /* media info */ + media->desc.media = pj_str( "audio" ); + media->desc.port = pvt->local_sdp_audio_port = switch_rtp_request_port(); + media->desc.port_count = 1; + media->desc.transport = pj_str( "RTP/AVP" ); + + strncpy( pvt->local_sdp_audio_addr, globals.local_addr, sizeof(pvt->local_sdp_audio_addr) ); + + /* codec rtpmap list */ + media->desc.fmt_count = 0; + media->attr_count = 0; + + if( !ref_sdp ) { + for( i = 0; i < pvt->num_codecs; i++ ) { + const switch_codec_implementation_t *imp = NULL; + + imp = pvt->codecs[i]->implementations; + + /* add all incarnations of this codec... */ + while( imp != NULL ) { + char tmp[10]; + + /* copy codec id */ + sprintf( tmp, "%d", pvt->codecs[i]->implementations->ianacode ); + pj_strdup2( pool, &media->desc.fmt[media->desc.fmt_count], tmp ); + + /* other codec parameters */ + rtpmap.pt = media->desc.fmt[media->desc.fmt_count]; + rtpmap.clock_rate = imp->samples_per_second; + rtpmap.enc_name = pj_str( pvt->codecs[i]->implementations->iananame ); + rtpmap.param.slen = 0; + + /* add codec implementation to attr list */ + pjmedia_sdp_rtpmap_to_attr( pool, &rtpmap, &rtpmap_attr ); + media->attr[media->attr_count++] = rtpmap_attr; + + /* goto next implementation of this codec */ + imp = imp->next; + } + media->desc.fmt_count++; + } + } + + /* Add sendrecv attribute */ + attr = pj_pool_zalloc( pool, sizeof(pjmedia_sdp_attr) ); + attr->name = pj_str( "sendrecv" ); + media->attr[media->attr_count++] = attr; + + /* + * Add support telephony event + */ + media->desc.fmt[media->desc.fmt_count++] = pj_str( "121" ); + /* Add rtpmap. */ + attr = pj_pool_zalloc( pool, sizeof(pjmedia_sdp_attr) ); + attr->name = pj_str( "rtpmap" ); + attr->value = pj_str( ":121 telephone-event/8000" ); + media->attr[media->attr_count++] = attr; + /* Add fmtp */ + attr = pj_pool_zalloc( pool, sizeof(pjmedia_sdp_attr) ); + attr->name = pj_str( "fmtp" ); + attr->value = pj_str( ":121 0-15" ); + media->attr[media->attr_count++] = attr; + + /* all done */ + *p_sdp = sdp; + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t start_rtp( struct pjsip_tech_pvt *pvt ) +{ + int rate, ms; + const char *err; + switch_rtp_flag_t flags = 0; + switch_channel_t *channel; + + assert( pvt != NULL ); + + channel = switch_core_session_get_channel( pvt->session ); + assert( channel != NULL ); + + if( pvt->rtp_session ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "RTP already initialized %s\n", pvt->call_id ); + return SWITCH_STATUS_SUCCESS; + } + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting RTP for call %s\n", pvt->call_id ); + + rate = pvt->read_codec.implementation->bits_per_second; + ms = pvt->read_codec.implementation->microseconds_per_frame; + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "RTP parameters for call %s: src (%s:%d) -> dst (%s:%d), using codec %s @ %d b/s, framelen %d ms\n", + pvt->call_id, + pvt->local_sdp_audio_addr, + pvt->local_sdp_audio_port, + pvt->remote_sdp_audio_addr, + pvt->remote_sdp_audio_port, + pvt->read_codec.implementation->iananame, + rate, + ms ); + + + pvt->rtp_session = switch_rtp_new( pvt->local_sdp_audio_addr, + pvt->local_sdp_audio_port, + pvt->remote_sdp_audio_addr, + pvt->remote_sdp_audio_port, + pvt->read_codec.implementation->ianacode, + pvt->read_codec.implementation->encoded_bytes_per_frame, + pvt->read_codec.implementation->microseconds_per_frame / 1000, + flags, + NULL, + &err, switch_core_session_get_pool( pvt->session ) ); + + if( !pvt->rtp_session ) { + switch_channel_hangup( channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER ); + switch_set_flag( pvt, TFLAG_BYE ); + return SWITCH_STATUS_FALSE; + } + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting RTP done\n" ); + + switch_set_flag( pvt, TFLAG_IO ); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t stop_rtp( struct pjsip_tech_pvt *pvt ) +{ + int loop = 0; + + if( !pvt->rtp_session ) + return SWITCH_STATUS_SUCCESS; + + while( loop < 10 && + ( switch_test_flag( pvt, TFLAG_READING ) || + switch_test_flag( pvt, TFLAG_WRITING ) ) ) + { + switch_yield( 1000 ); + loop++; + } + + switch_rtp_destroy( &pvt->rtp_session ); + pvt->rtp_session = NULL; + + return SWITCH_STATUS_SUCCESS; +} + +#if 0 // unused for now +static struct pjsip_tech_pvt * find_pvt_by_callid( char *id ) +{ + struct pjsip_tech_pvt *pvt = NULL; + + assert( id != NULL ); + + pvt = (struct pjsip_tech_pvt *) switch_core_hash_find( globals.call_hash, id ); + + return pvt; +} +#endif +/* END: mid-level functions */ + + +/* BEGIN: module managment */ + +SWITCH_MOD_DECLARE(switch_status_t) switch_module_shutdown(void) +{ + if ( globals.running ) { + globals.running = -1; + while ( globals.running ) { + switch_yield( 100000 ); + } + } + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MOD_DECLARE(switch_status_t) switch_module_load(const switch_loadable_module_interface_t **module_interface, char *filename) +{ + + /* NOTE: **interface is **_interface because the common lib redefines interface to struct in some situations */ + if (switch_core_new_memory_pool( &module_pool ) != SWITCH_STATUS_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "OH OH no pool\n" ); + return SWITCH_STATUS_TERM; + } + + switch_core_hash_init( &globals.call_hash, module_pool ); + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = &pjsip_module_interface; + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MOD_DECLARE(switch_status_t) switch_module_runtime(void) +{ + pj_status_t status; + + globals.sip_port = 5060; + + pj_memset( &globals.mod_app, 0, sizeof(globals.mod_app) ); + globals.mod_app.name = pj_str( "mod-pjsip" ); /* Name. */ + globals.mod_app.id = -1; /* Id */ + globals.mod_app.priority = PJSIP_MOD_PRIORITY_APPLICATION; /* Priority */ + globals.mod_app.on_rx_request = &on_rx_request; /* on_rx_request() */ + + /* Set dialplan */ + strncpy( globals.dialplan, "XML", sizeof(globals.dialplan) - 1 ); + + /* init pjlib */ + status = pj_init(); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize pjlib\n" ); + return SWITCH_STATUS_TERM; + } + + /* next: pjlib_util */ + status = pjlib_util_init(); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize pjlib_util\n" ); + return SWITCH_STATUS_TERM; + } + + /* initialize pjlib pool factory */ + pj_caching_pool_init( &cp, &pj_pool_factory_default_policy, 0 ); + + /* create endpoint */ + status = pjsip_endpt_create( &cp.factory, "FreeSWITCH", &globals.sip_endpt ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize sip endpoint\n" ); + return SWITCH_STATUS_TERM; + } + + /* allocate pool for pjsip functions */ + pjsip_pool = pjsip_endpt_create_pool( globals.sip_endpt, "pjsip-pool", 1024, 1024 ); + + /* add udp transport */ + globals.sip_addr.sin_family = PJ_AF_INET; + globals.sip_addr.sin_addr.s_addr = 0; + globals.sip_addr.sin_port = pj_htons( 5060 ); + + status = pjsip_udp_transport_start( globals.sip_endpt, &globals.sip_addr, NULL, 1, NULL ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create UDP transport\n" ); + return SWITCH_STATUS_TERM; + } + + /* init transaction handling */ + status = pjsip_tsx_layer_init_module( globals.sip_endpt ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize transaction handling\n" ); + return SWITCH_STATUS_TERM; + } + + /* init UA dialog handling */ + status = pjsip_ua_init_module( globals.sip_endpt, NULL ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to initialize UA dialog handling\n" ); + return SWITCH_STATUS_TERM; + } + + pj_memset( &invite_cb, 0, sizeof(invite_cb) ); + invite_cb.on_state_changed = &call_on_state_changed; + invite_cb.on_new_session = &call_on_forked; + invite_cb.on_media_update = &call_on_media_update; + invite_cb.on_tsx_state_changed = &call_on_tsx_state_changed; + + /* initialize invite session module */ + status = pjsip_inv_usage_init( globals.sip_endpt, &invite_cb ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to register invite session module to pjsip core\n" ); + return SWITCH_STATUS_TERM; + } + + /* */ + { + const pj_str_t *hostname; + pj_sockaddr_in tmp_addr; + char *addr; + char local_uri[64]; + + hostname = pj_gethostname(); + pj_sockaddr_in_init( &tmp_addr, hostname, 0 ); + addr = pj_inet_ntoa( tmp_addr.sin_addr ); + pj_ansi_strcpy( globals.local_addr, addr ); + pj_ansi_sprintf( local_uri, "sip:%s:%d", hostname->ptr, globals.sip_port ); + pj_strdup2( pjsip_pool, &globals.local_uri, local_uri ); + pj_strdup2( pjsip_pool, &globals.local_contact, local_uri ); + } + + /* register application to pjsip core */ + status = pjsip_endpt_register_module( globals.sip_endpt, &globals.mod_app ); + if ( status != PJ_SUCCESS ) { + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to register mod_pjsip application to pjsip core\n" ); + return SWITCH_STATUS_TERM; + } + + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Monitor thread running\n" ); + + /* main loop */ + globals.running = 1; + while ( globals.running > 0 ) { + pj_time_val timeout = { 1, 0 }; + pjsip_endpt_handle_events( globals.sip_endpt, &timeout ); + } + + globals.running = 0; + switch_log_printf( SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Monitor thread exiting\n" ); + + return SWITCH_STATUS_TERM; +} Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip.h ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip.h Mon Jul 3 22:03:31 2006 @@ -0,0 +1,78 @@ +#ifndef _MOD_PJSIP_H_ +#define _MOD_PJSIP_H_ + +struct pjsip_tech_pvt { + /* switch */ + switch_core_session_t *session; + switch_caller_profile_t *caller_profile; + char call_id[50]; + int cid; + + /* flags */ + unsigned int flags; + + /* net */ + int local_sdp_audio_port; + char local_sdp_audio_addr[50]; + int remote_sdp_audio_port; + char remote_sdp_audio_addr[50]; + switch_rtp_t *rtp_session; + + /* pjsip */ + pjsip_user_agent *sip_ua; /* */ + pjsip_dialog *sip_dialog; + pjsip_inv_session *sip_invite; + pj_thread_desc thread_desc; + pj_thread_t *thread; + + /* codec information */ + switch_codec_t read_codec; + switch_codec_t write_codec; + switch_codec_interface_t *codecs[SWITCH_MAX_CODECS]; + int num_codecs; + + switch_frame_t read_frame; +}; + +/* + * Get the final codec from SDP after successfull negotiation + */ +pj_status_t mod_pjsip_codec_from_sdp( struct pjsip_tech_pvt *pvt, + pj_pool_t *pool, + const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, + const unsigned int stream_idx, + const switch_codec_interface_t **codec, + const switch_codec_implementation_t **imp ); + +/* + * Get address from sdp + */ +pj_status_t mod_pjsip_addr_from_sdp( const pjmedia_sdp_session *sdp, + const int stream_idx, + pj_sockaddr *addr ); + +/* + * + */ +pj_status_t mod_pjsip_media_direction_from_sdp( + const pjmedia_sdp_session *sdp, + const int stream_idx, + int *dir ); + +/* + * + */ +pj_status_t mod_pjsip_media_type_from_sdp( + const pjmedia_sdp_session *sdp, + const int stream_idx, + int *type ); +/* + * + */ +pj_status_t mod_pjsip_telephone_type_from_sdp( + const pjmedia_sdp_session *sdp, + const int stream_idx, + int *event_pt ); + +#endif Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip_sdp.c ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/mod_pjsip_sdp.c Mon Jul 3 22:03:31 2006 @@ -0,0 +1,307 @@ +/* $Id: session.c 452 2006-05-17 17:17:39Z bennylp $ */ +/* + * 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 + */ + +#define HAVE_APR +#include <switch.h> +#include <pjlib.h> +#include <pjsip_ua.h> +#include <pjsdp.h> + +#include "mod_pjsip.h" + +#define THIS_FILE "mod_pjsip_sdp.c" + +static const pj_str_t ID_AUDIO = { "audio", 5 }; +static const pj_str_t ID_VIDEO = { "video", 5 }; +static const pj_str_t ID_IN = { "IN", 2 }; +static const pj_str_t ID_IP4 = { "IP4", 3 }; +static const pj_str_t ID_IP6 = { "IP6", 3 }; +static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; +static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 }; +static const pj_str_t ID_RTPMAP = { "rtpmap", 6 }; +static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 }; + +static const pj_str_t STR_INACTIVE = { "inactive", 8 }; +static const pj_str_t STR_SENDRECV = { "sendrecv", 8 }; +static const pj_str_t STR_SENDONLY = { "sendonly", 8 }; +static const pj_str_t STR_RECVONLY = { "recvonly", 8 }; + +static pj_status_t find_codec( + struct pjsip_tech_pvt *pvt, + unsigned int pt, + unsigned int clock_rate, + const switch_codec_interface_t **codec, + const switch_codec_implementation_t **imp ) +{ + const switch_codec_implementation_t *tmp_imp; + int i; + + assert( codec != NULL ); + assert( pvt != NULL ); + assert( imp != NULL ); + + tmp_imp = NULL; + for( i = 0; i < pvt->num_codecs; i++ ) { + if( pvt->codecs[i]->implementations->ianacode == pt ) { + /* codec found, find implementation too */ + tmp_imp = pvt->codecs[i]->implementations; + + while( tmp_imp != NULL ) { + if ( tmp_imp->samples_per_second == clock_rate ) { + *codec = pvt->codecs[i]; + *imp = tmp_imp; + return PJ_SUCCESS; + } + + tmp_imp = tmp_imp->next; + } + break; + } + } + return PJ_ENOTFOUND; +} + +pj_status_t mod_pjsip_codec_from_sdp( + struct pjsip_tech_pvt *pvt, + pj_pool_t *pool, + const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, + const unsigned int stream_idx, + const switch_codec_interface_t **codec, + const switch_codec_implementation_t **imp ) +{ + const pjmedia_sdp_attr *attr; + const pjmedia_sdp_media *local_m; + const pjmedia_sdp_media *rem_m; + pjmedia_sdp_rtpmap *rtpmap; + unsigned int pt; + pj_status_t status; +// const switch_codec_implementation_t *tmp_imp; + + assert( pvt != NULL ); + assert( pool != NULL ); + assert( local != NULL ); + assert( remote != NULL ); + assert( codec != NULL ); + assert( imp != NULL ); + + assert( stream_idx < local->media_count ); + assert( stream_idx < remote->media_count ); + + /* Keep SDP shortcuts */ + local_m = local->media[stream_idx]; + rem_m = remote->media[stream_idx]; + + /* And codec must be numeric! */ + if (!pj_isdigit(*local_m->desc.fmt[0].ptr) || + !pj_isdigit(*rem_m->desc.fmt[0].ptr)) + { + return PJMEDIA_EINVALIDPT; + } + + /* Get the payload number for receive channel. */ + pt = pj_strtoul(&local_m->desc.fmt[0]); + + /* Get codec info. + * For static payload types, get the info from codec manager. + * For dynamic payload types, MUST get the rtpmap. + */ + if (pt < 96) { + pj_bool_t has_rtpmap; + + rtpmap = NULL; + has_rtpmap = PJ_TRUE; + + attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, + &local_m->desc.fmt[0]); + if (attr == NULL) + has_rtpmap = PJ_FALSE; + else { + status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap); + if (status != PJ_SUCCESS) + has_rtpmap = PJ_FALSE; + } + + /* Build codec format info: */ + if (has_rtpmap) { + status = find_codec( pvt, pt, rtpmap->clock_rate, codec, imp ); + if( status != PJ_SUCCESS ) + return PJ_ENOTFOUND; + } else { + /* + * No rtpmap, use default clock_rate (8000 Hz) + */ + status = find_codec( pvt, pt, 8000, codec, imp ); + if( status != PJ_SUCCESS ) + return PJ_ENOTFOUND; + } + } else + return PJ_ENOTSUP; + + + return PJ_SUCCESS; +} + + +pj_status_t mod_pjsip_addr_from_sdp( + const pjmedia_sdp_session *sdp, + const int stream_idx, + pj_sockaddr *addr ) +{ + const pjmedia_sdp_media *m; +// const pjmedia_sdp_attr *attr; + const pjmedia_sdp_conn *conn; + pj_sockaddr_in *ip4addr; + + assert( sdp != NULL ); + assert( addr != NULL ); + + assert( stream_idx < sdp->media_count ); + + /* Keep SDP shortcuts */ + m = sdp->media[stream_idx]; + + conn = m->conn ? m->conn : sdp->conn; + if (conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + + /* only IPv4 for now */ + ip4addr = (pj_sockaddr_in *)addr; + + /* Set address: */ + ip4addr->sin_family = PJ_AF_INET; + ip4addr->sin_port = pj_htons(m->desc.port); + if (pj_inet_aton(&conn->addr, &ip4addr->sin_addr) == 0) { + + /* Invalid IP address. */ + return PJMEDIA_EINVALIDIP; + } + return PJ_SUCCESS; +} + + +pj_status_t mod_pjsip_media_direction_from_sdp( + const pjmedia_sdp_session *sdp, + const int stream_idx, + int *dir ) +{ + const pjmedia_sdp_media *m; +// const pjmedia_sdp_attr *attr; + const pjmedia_sdp_conn *conn; + + assert( sdp != NULL ); + assert( dir != NULL ); + + assert( stream_idx < sdp->media_count ); + + /* Keep SDP shortcuts */ + m = sdp->media[stream_idx]; + + conn = m->conn ? m->conn : sdp->conn; + if (conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + + /* Media direction: */ + if (m->desc.port == 0 || + pj_inet_addr(&conn->addr).s_addr==0 || + pjmedia_sdp_media_find_attr(m, &STR_INACTIVE, NULL)!=NULL) + { + /* Inactive stream. */ + *dir = PJMEDIA_DIR_NONE; + } else if (pjmedia_sdp_media_find_attr(m, &STR_SENDONLY, NULL)!=NULL) { + + /* Send only stream. */ + *dir = PJMEDIA_DIR_ENCODING; + } else if (pjmedia_sdp_media_find_attr(m, &STR_RECVONLY, NULL)!=NULL) { + + /* Recv only stream. */ + *dir = PJMEDIA_DIR_DECODING; + } else { + + /* Send and receive stream. */ + *dir = PJMEDIA_DIR_ENCODING_DECODING; + } + return PJ_SUCCESS; +} + + +pj_status_t mod_pjsip_media_type_from_sdp( + const pjmedia_sdp_session *sdp, + const int stream_idx, + int *type ) +{ + const pjmedia_sdp_media *m; +// const pjmedia_sdp_attr *attr; + + assert( sdp != NULL ); + assert( type != NULL ); + + assert( stream_idx < sdp->media_count ); + + /* Keep SDP shortcuts */ + m = sdp->media[stream_idx]; + + /* Media type: */ + if (pj_stricmp(&m->desc.media, &ID_AUDIO) == 0) { + *type = PJMEDIA_TYPE_AUDIO; + + } else if (pj_stricmp(&m->desc.media, &ID_VIDEO) == 0) { + *type = PJMEDIA_TYPE_VIDEO; + + } else { + *type = PJMEDIA_TYPE_UNKNOWN; + return PJMEDIA_EINVALIMEDIATYPE; + } + return PJ_SUCCESS; +} + +pj_status_t mod_pjsip_telephone_type_from_sdp( + const pjmedia_sdp_session *sdp, + const int stream_idx, + int *event_pt ) +{ + const pjmedia_sdp_media *m; + const pjmedia_sdp_attr *attr; + int i; + + assert( sdp != NULL ); + assert( event_pt != NULL ); + + assert( stream_idx < sdp->media_count ); + + /* Keep SDP shortcuts */ + m = sdp->media[stream_idx]; + + /* Get payload type for telephone-events */ + *event_pt = -1; + for (i=0; i<m->attr_count; ++i) { + pjmedia_sdp_rtpmap r; + + attr = m->attr[i]; + if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0) + continue; + if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS) + continue; + if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) { + *event_pt = pj_strtoul(&r.pt); + break; + } + } + return PJ_SUCCESS; +} Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/Makefile ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/Makefile Mon Jul 3 22:03:31 2006 @@ -0,0 +1,54 @@ +include build.mak +include build/host-$(HOST_NAME).mak + +DIRS = pjsdp + +ifdef MINSIZE +MAKE_FLAGS := MINSIZE=1 +endif + +all clean dep depend distclean doc print realclean: + for dir in $(DIRS); do \ + if [ -d $$dir ]; then \ + if make $(MAKE_FLAGS) -C $$dir/build $@; then \ + true; \ + else \ + exit 1; \ + fi; \ + fi; \ + done + +LIBS = pjsdp/lib/libpjsdp-$(TARGET_NAME).a +BINS = + +size: + @echo -n 'Date: ' + @date + @echo + @for lib in $(LIBS); do \ + echo "$$lib:"; \ + ar tv $$lib | awk '{print $$3 "\t" $$8}' | sort -n; \ + echo -n 'Total: '; \ + ar tv $$lib | awk '{print " + " $$3}' | xargs expr 0; \ + echo; \ + done + @echo + @for bin in $(BINS); do \ + echo "size $$bin:"; \ + size $$bin; \ + done + +#dos2unix: +# for f in `find . | egrep '(mak|h|c|S|s|Makefile)$$'`; do \ +# dos2unix "$$f" > dos2unix.tmp; \ +# cp dos2unix.tmp "$$f"; \ +# done +# rm -f dos2unix.tmp + +xhdrid: + for f in `find . | egrep '\.(h|c|S|s|cpp|hpp)$$'`; do \ + echo Processing $$f...; \ + cat $$f | sed 's/.*\$$Author\$$/ */' > /tmp/id; \ + cp /tmp/id $$f; \ + done + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,8 @@ +# Auto-generated build.mak +export MACHINE_NAME := i386 +export OS_NAME := linux +export HOST_NAME := unix +export CC_NAME := gcc +export TARGET_NAME := i386-linux-gcc +export CROSS_COMPILE := +export LINUX_POLL := select Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/cc-gcc.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/cc-gcc.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,22 @@ +export CC = $(CROSS_COMPILE)gcc -c +export AR = $(CROSS_COMPILE)ar rv +export LD = $(CROSS_COMPILE)gcc +export LDOUT = -o +export RANLIB = $(CROSS_COMPILE)ranlib + +export OBJEXT := .o +export LIBEXT := .a +export LIBEXT2 := + +export CC_OUT := -o +export CC_INC := -I +export CC_DEF := -D +export CC_OPTIMIZE := -O2 +export CC_LIB := -l + +export CC_SOURCES := +export CC_CFLAGS := -Wall +#export CC_CFLAGS += -Wdeclaration-after-statement +#export CC_CXXFLAGS := -Wdeclaration-after-statement +export CC_LDFLAGS := + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/cc-vc.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/cc-vc.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,20 @@ +export CC := cl /c /nologo +export AR := lib /NOLOGO /OUT: +export LD := cl /nologo +export LDOUT := /Fe +export RANLIB := echo ranlib + +export OBJEXT := .obj +export LIBEXT := .lib +export LIBEXT2 := .LIB + +export CC_OUT := /Fo +export CC_INC := /I +export CC_DEF := /D +export CC_OPTIMIZE := /Ox +export CC_LIB := + +export CC_SOURCES := +export CC_CFLAGS := /W4 /MT +export CC_CXXFLAGS := /GX +export CC_LDFLAGS := /MT Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/common.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/common.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,54 @@ +# +# Include host/target/compiler selection. +# This will export CC_NAME, MACHINE_NAME, OS_NAME, and HOST_NAME variables. +# +include ../../build.mak + +# +# Include global compiler specific definitions +# +include ../../build/cc-$(CC_NAME).mak + +# +# (Optionally) Include compiler specific configuration that is +# specific to this project. This configuration file is +# located in this directory. +# +-include cc-$(CC_NAME).mak + +# +# Include global machine specific definitions +# +include ../../build/m-$(MACHINE_NAME).mak +-include m-$(MACHINE_NAME).mak + +# +# Include target OS specific definitions +# +include ../../build/os-$(OS_NAME).mak + +# +# (Optionally) Include target OS specific configuration that is +# specific to this project. This configuration file is +# located in this directory. +# +-include os-$(OS_NAME).mak + +# +# Include host specific definitions +# +include ../../build/host-$(HOST_NAME).mak + +# +# (Optionally) Include host specific configuration that is +# specific to this project. This configuration file is +# located in this directory. +# +-include host-$(HOST_NAME).mak + +# +# Include global user configuration, if any +# +-include ../../user.mak + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-mingw.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-mingw.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,13 @@ +export HOST_MV := mv +export HOST_RM := rm -f @@ +export HOST_RMR := rm -rf @@ +export HOST_RMDIR := rm -rf @@ +export HOST_MKDIR := mkdir @@ +export HOST_EXE := .exe +export HOST_PSEP := / + +export HOST_SOURCES := +export HOST_CFLAGS := +export HOST_CXXFLAGS := +export HOST_LDFLAGS := $(CC_LIB)stdc++$(LIBEXT2) + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-unix.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-unix.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,13 @@ +export HOST_MV := mv +export HOST_RM := rm -f @@ +export HOST_RMR := rm -rf @@ +export HOST_RMDIR := rm -rf @@ +export HOST_MKDIR := mkdir -p @@ +export HOST_EXE := +export HOST_PSEP := / + +export HOST_SOURCES := +export HOST_CFLAGS := +export HOST_CXXFLAGS := +export HOST_LDFLAGS := + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-win32.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/host-win32.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,12 @@ +export HOST_MV := ren +export HOST_RM := if exist @@; del /F /Q @@ +export HOST_RMR := if exist @@; del /F /Q @@ +export HOST_RMDIR := if exist @@; rmdir @@ +export HOST_MKDIR := if not exist @@; mkdir @@ +export HOST_EXE := .exe +export HOST_PSEP := \\ + +export HOST_SOURCES := +export HOST_CFLAGS := +export HOST_CXXFLAGS := +export HOST_LDFLAGS := Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-alpha.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-alpha.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,4 @@ +export M_CFLAGS := $(CC_DEF)PJ_M_ALPHA=1 +export M_CXXFLAGS := +export M_LDFLAGS := +export M_SOURCES := Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-i386.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-i386.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,4 @@ +export M_CFLAGS := $(CC_DEF)PJ_M_I386=1 +export M_CXXFLAGS := +export M_LDFLAGS := +export M_SOURCES := Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-m68k.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-m68k.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,4 @@ +export M_CFLAGS := $(CC_DEF)PJ_M_M68K=1 +export M_CXXFLAGS := +export M_LDFLAGS := +export M_SOURCES := Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-mpc860.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-mpc860.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,9 @@ +# +# PowerPC MPC860 specific. +# It's a PowerPC without floating point support. +# +export M_CFLAGS := $(CC_DEF)PJ_M_POWERPC=1 $(CC_DEF)PJ_HAS_FLOATING_POINT=0 -mcpu=860 +export M_CXXFLAGS := +export M_LDFLAGS := -mcpu=860 +export M_SOURCES := + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-powerpc.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-powerpc.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,4 @@ +export M_CFLAGS := $(CC_DEF)PJ_M_POWERPC=1 +export M_CXXFLAGS := +export M_LDFLAGS := +export M_SOURCES := Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-sparc.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/m-sparc.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,4 @@ +export M_CFLAGS := $(CC_DEF)PJ_M_SPARC=1 +export M_CXXFLAGS := +export M_LDFLAGS := +export M_SOURCES := Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-darwinos.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-darwinos.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,9 @@ +export OS_CFLAGS := $(CC_DEF)PJ_DARWINOS=1 + +export OS_CXXFLAGS := + +export OS_LDFLAGS := $(CC_LIB)pthread$(LIBEXT2) -framework CoreAudio + +export OS_SOURCES := + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-linux-kernel.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-linux-kernel.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,43 @@ + +include $(KERNEL_DIR)/.config + +# +# Basic kernel compilation flags. +# +export OS_CFLAGS := $(CC_DEF)PJ_LINUX_KERNEL=1 -D__KERNEL__ \ + -I$(KERNEL_DIR)/include -iwithprefix include \ + -nostdinc -msoft-float + +# +# Additional kernel compilation flags are taken from the kernel Makefile +# itself. +# + +KERNEL_CFLAGS := \ + $(shell cd $(KERNEL_DIR) ; \ + make script SCRIPT='@echo $$(CFLAGS) $$(CFLAGS_MODULE)' $(KERNEL_ARCH)) + +export OS_CFLAGS += $(KERNEL_CFLAGS) + +# -DMODULE -I$(KERNEL_DIR)/include -nostdinc \ +# -Wstrict-prototypes \ +# -Wno-trigraphs -fno-strict-aliasing -fno-common \ +# -msoft-float -m32 -fno-builtin-sprintf -fno-builtin-log2\ +# -fno-builtin-puts -mpreferred-stack-boundary=2 \ +# -fno-unit-at-a-time -march=i686 -mregparm=3 \ +# -iwithprefix include + +#export OS_CFLAGS += -U__i386__ -Ui386 -D__arch_um__ -DSUBARCH=\"i386\" \ +# -D_LARGEFILE64_SOURCE -I$(KERNEL_DIR)/arch/um/include \ +# -Derrno=kernel_errno \ +# -I$(KERNEL_DIR)/arch/um/kernel/tt/include \ +# -I$(KERNEL_DIR)/arch/um/kernel/skas/include \ + + +export OS_CXXFLAGS := + +export OS_LDFLAGS := + +export OS_SOURCES := + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-linux.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-linux.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,9 @@ +export OS_CFLAGS := $(CC_DEF)PJ_LINUX=1 + +export OS_CXXFLAGS := + +export OS_LDFLAGS := $(CC_LIB)pthread$(LIBEXT2) + +export OS_SOURCES := + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-palmos.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-palmos.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,32 @@ +# +# make-mingw.inc: Mingw specific compilation switches. +# +PALM_OS_SDK_VER := 0x06000000 +PALM_OS_TARGET_HOST := TARGET_HOST_PALMOS +PALM_OS_TARGET_PLATFORM := TARGET_PLATFORM_PALMSIM_WIN32 +PALM_OS_BUILD_TYPE := BUILD_TYPE_DEBUG +PALM_OS_TRACE_OUTPUT := TRACE_OUTPUT_ON +PALM_OS_CPU_TYPE := CPU_ARM + +export CROSS_COMPILE := + +ifeq ($(CC_NAME),gcc) + export CFLAGS += -mno-cygwin -fexceptions -frtti +endif + +export OS_CFLAGS := $(CC_DEF)PJ_PALMOS=1 \ + $(CC_DEF)__PALMOS_KERNEL__=1 \ + $(CC_DEF)__PALMOS__=$(PALM_OS_SDK_VER) \ + $(CC_DEF)BUILD_TYPE=$(PALM_OS_BUILD_TYPE) \ + $(CC_DEF)TRACE_OUTPUT=$(PALM_OS_TRACE_OUTPUT) \ + $(CC_DEF)_SUPPORTS_NAMESPACE=0 \ + $(CC_DEF)_SUPPORTS_RTTI=0 \ + $(CC_DEF)TARGET_HOST=$(PALM_OS_TRAGET_HOST) \ + $(CC_DEF)TARGET_PLATFORM=$(PALM_OS_TARGET_PLATFORM) + +export OS_CXXFLAGS := + +export OS_LDFLAGS := + +export OS_SOURCES := + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-rtems.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-rtems.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,17 @@ +# +# Global OS specific configurations for RTEMS OS. +# +# Thanks Zetron, Inc and Phil Torre <ptorre@zetron.com> for donating PJLIB +# port to RTEMS. +# +export RTEMS_DEBUG := -ggdb3 -DRTEMS_DEBUG -DDEBUG -qrtems_debug + +export OS_CFLAGS := $(CC_DEF)PJ_RTEMS=1 \ + -B$(RTEMS_LIBRARY_PATH)/lib/ -specs bsp_specs -qrtems + +export OS_CXXFLAGS := + +export OS_LDFLAGS := -B$(RTEMS_LIBRARY_PATH)/lib/ -specs bsp_specs -qrtems + +export OS_SOURCES := + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-sunos.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-sunos.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,12 @@ +export OS_CFLAGS := $(CC_DEF)PJ_SUNOS=1 + +export OS_CXXFLAGS := + +export OS_LDFLAGS := $(CC_LIB)pthread$(LIBEXT2) \ + $(CC_LIB)socket$(LIBEXT2) \ + $(CC_LIB)rt$(LIBEXT2) \ + $(CC_LIB)nsl$(LIBEXT2) + +export OS_SOURCES := + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-win32.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/os-win32.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,11 @@ +export OS_CFLAGS := $(CC_DEF)PJ_WIN32=1 + +export OS_CXXFLAGS := + +export OS_LDFLAGS := $(CC_LIB)wsock32$(LIBEXT2) \ + $(CC_LIB)ws2_32$(LIBEXT2)\ + $(CC_LIB)ole32$(LIBEXT2) + +export OS_SOURCES := + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/rules.mak ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/build/rules.mak Mon Jul 3 22:03:31 2006 @@ -0,0 +1,170 @@ +LIBDIR = ../lib +BINDIR = ../bin + +# +# The full path of output lib file (e.g. ../lib/libapp.a). +# +LIB = $($(APP)_LIB) + +# +# The full path of output executable file (e.g. ../bin/app.exe). +# +EXE = $($(APP)_EXE) + +# +# Source directory +# +SRCDIR = $($(APP)_SRCDIR) + +# +# Output directory for object files (i.e. output/target) +# +OBJDIR = output/$(app)-$(TARGET_NAME) + +ifeq ($(OS_NAME),linux-kernel) +export $(APP)_CFLAGS += -DKBUILD_MODNAME=$(app) -DKBUILD_BASENAME=$(app) +endif + + +# +# OBJS is ./output/target/file.o +# +OBJS = $(foreach file, $($(APP)_OBJS), $(OBJDIR)/$(file)) +OBJDIRS := $(sort $(dir $(OBJS))) + +# +# FULL_SRCS is ../src/app/file1.c ../src/app/file1.S +# +FULL_SRCS = $(foreach file, $($(APP)_OBJS), $(SRCDIR)/$(basename $(file)).c $(SRCDIR)/$(basename $(file)).cpp $(SRCDIR)/$(basename $(file)).S) + +# +# When generating dependency (gcc -MM), ideally we use only either +# CFLAGS or CXXFLAGS (not both). But I just couldn't make if/ifeq to work. +# +DEPFLAGS = $($(APP)_CXXFLAGS) $($(APP)_CFLAGS) + +# Dependency file +DEP_FILE := .$(app)-$(TARGET_NAME).depend + + +print_common: + @echo "###" + @echo "### DUMPING MAKE VARIABLES (I WON'T DO ANYTHING ELSE):" + @echo "###" + @echo APP=$(APP) + @echo OBJDIR=$(OBJDIR) + @echo OBJDIRS=$(OBJDIRS) + @echo OBJS=$(OBJS) + @echo SRCDIR=$(SRCDIR) + @echo FULL_SRCS=$(FULL_SRCS) + @echo $(APP)_CFLAGS=$($(APP)_CFLAGS) + @echo $(APP)_CXXFLAGS=$($(APP)_CXXFLAGS) + @echo $(APP)_LDFLAGS=$($(APP)_LDFLAGS) + @echo DEPFLAGS=$(DEPFLAGS) + +print_bin: print_common + @echo EXE=$(EXE) + @echo BINDIR=$(BINDIR) + +print_lib: print_common + @echo LIB=$(LIB) + @echo LIBDIR=$(LIBDIR) + +$(LIB): $(LIBDIR) $(OBJDIRS) $(OBJS) $($(APP)_EXTRA_DEP) + $(AR)$(LIB) $(OBJS) + $(RANLIB) $(LIB) + +$(EXE): $(BINDIR) $(OBJDIRS) $(OBJS) $($(APP)_EXTRA_DEP) + $(LD) $(LDOUT)$(subst /,$(HOST_PSEP),$(EXE)) \ + $(subst /,$(HOST_PSEP),$(OBJS)) $($(APP)_LDFLAGS) + +$(OBJDIR)/$(app).o: $(OBJDIRS) $(OBJS) + $(CROSS_COMPILE)ld -r -o $@ $(OBJS) + +$(OBJDIR)/$(app).ko: $(OBJDIR)/$(app).o + @echo Creating kbuild Makefile... + @echo "# Our module name:" > $(OBJDIR)/Makefile + @echo 'obj-m += $(app).o' >> $(OBJDIR)/Makefile + @echo >> $(OBJDIR)/Makefile + @echo "# Object members:" >> $(OBJDIR)/Makefile + @echo -n '$(app)-objs += ' >> $(OBJDIR)/Makefile + @for file in $($(APP)_OBJS); do \ + echo -n "$$file " >> $(OBJDIR)/Makefile; \ + done + @echo >> $(OBJDIR)/Makefile + @echo >> $(OBJDIR)/Makefile + @echo "# Prevent .o files to be built by kbuild:" >> $(OBJDIR)/Makefile + @for file in $($(APP)_OBJS); do \ + echo ".PHONY: `pwd`/$(OBJDIR)/$$file" >> $(OBJDIR)/Makefile; \ + done + @echo >> $(OBJDIR)/Makefile + @echo all: >> $(OBJDIR)/Makefile + @echo -e "\tmake -C $(KERNEL_DIR) M=`pwd`/$(OBJDIR) modules $(KERNEL_ARCH)" >> $(OBJDIR)/Makefile + @echo Invoking kbuild... + make -C $(OBJDIR) + +../lib/$(app).ko: $(LIB) $(OBJDIR)/$(app).ko + cp $(OBJDIR)/$(app).ko ../lib + +$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.c + $(CC) $($(APP)_CFLAGS) \ + $(CC_OUT)$(subst /,$(HOST_PSEP),$@) \ + $(subst /,$(HOST_PSEP),$<) + +$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.S + $(CC) $($(APP)_CFLAGS) \ + $(CC_OUT)$(subst /,$(HOST_PSEP),$@) \ + $(subst /,$(HOST_PSEP),$<) + +$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cpp + $(CC) $($(APP)_CXXFLAGS) \ + $(CC_OUT)$(subst /,$(HOST_PSEP),$@) \ + $(subst /,$(HOST_PSEP),$<) + +$(OBJDIRS): + $(subst @@,$(subst /,$(HOST_PSEP),$@),$(HOST_MKDIR)) + +$(LIBDIR): + $(subst @@,$(subst /,$(HOST_PSEP),$(LIBDIR)),$(HOST_MKDIR)) + +$(BINDIR): + $(subst @@,$(subst /,$(HOST_PSEP),$(BINDIR)),$(HOST_MKDIR)) + +clean: + $(subst @@,$(subst /,$(HOST_PSEP),$(OBJDIR)/*),$(HOST_RMR)) + $(subst @@,$(subst /,$(HOST_PSEP),$(OBJDIR)),$(HOST_RMDIR)) +ifeq ($(OS_NAME),linux-kernel) + rm -f ../lib/$(app).o +endif + +gcov-report: + for file in $(FULL_SRCS); do \ + gcov $$file -n -o $(OBJDIR); \ + done + +realclean: clean + $(subst @@,$(subst /,$(HOST_PSEP),$(LIB)) $(subst /,$(HOST_PSEP),$(EXE)),$(HOST_RM)) + $(subst @@,$(DEP_FILE),$(HOST_RM)) +ifeq ($(OS_NAME),linux-kernel) + rm -f ../lib/$(app).ko +endif + +depend: + $(subst @@,$(DEP_FILE),$(HOST_RM)) + for F in $(FULL_SRCS); do \ + if test -f $$F; then \ + echo "$(OBJDIR)/" | tr -d '\n' >> $(DEP_FILE); \ + if $(CC) -M $(DEPFLAGS) $$F | sed '/^#/d' >> $(DEP_FILE); then \ + true; \ + else \ + echo 'err:' >> $(DEP_FILE); \ + rm -f $(DEP_FILE); \ + exit 1; \ + fi; \ + fi; \ + done; + +dep: depend + +-include $(DEP_FILE) + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/configure ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/configure Mon Jul 3 22:03:31 2006 @@ -0,0 +1,119 @@ +#!/bin/sh + +# +# Detect machine, unless the choice has been made already. +# +if [ "$MACHINE" = "" ]; then + MACHINE=`uname -m` +fi + +if echo $MACHINE | grep sun4u > /dev/null; then + MACHINE_NAME=sparc +elif echo $MACHINE | grep i.86 > /dev/null; then + MACHINE_NAME=i386 +elif echo $MACHINE | grep alpha > /dev/null; then + MACHINE_NAME=alpha +elif echo $MACHINE | grep Mac > /dev/null; then + MACHINE_NAME=powerpc +else + echo "Unable to detect processor type ('uname -m' == '$MACHINE')" + exit 1 +fi + +# +# Detect OS and host, unless the choice has been made already +# +if [ "$SYSTEM" = "" ]; then + SYSTEM=`uname -s` +fi + + +if echo $SYSTEM | grep -i sunos > /dev/null; then + OS_NAME=sunos + HOST_NAME=unix +elif echo $SYSTEM | grep -i linux > /dev/null; then + OS_NAME=linux + HOST_NAME=unix + # More on linux version + KERNEL_VER=`uname -r` + if echo $KERNEL_VER | grep '^2\.4' > /dev/null; then + LINUX_POLL=select + elif echo $KERNEL_VER | grep '^2\.2' > /dev/null; then + LINUX_POLL=select + elif echo $KERNEL_VER | grep '^2\.0' > /dev/null; then + LINUX_EPOLL=select + else +# LINUX_POLL=epoll + LINUX_POLL=select + fi +elif echo $SYSTEM | grep -i mingw > /dev/null; then + OS_NAME=win32 + HOST_NAME=mingw +elif echo $SYSTEM | grep -i cygwin > /dev/null; then + OS_NAME=win32 + HOST_NAME=mingw +elif echo $SYSTEM | grep -i darwin > /dev/null; then + OS_NAME=darwinos + HOST_NAME=unix +elif echo $SYSTEM | grep -i rtems > /dev/null; then + OS_NAME=rtems + HOST_NAME=unix +else + echo "Unable to detect system type ('uname -s' == '$SYSTEM')" + exit 1 +fi + +# +# Detect gcc, unless it has been chosen already +# +if [ "$CC_NAME" = "" ]; then + if gcc --version 2>&1 > /dev/null; then + CC_NAME=gcc + else + echo "Unable to find gcc" + exit 1 + fi +fi + + +# +# Specify TARGET_NAME, if not already choosen. +# +if [ "$TARGET_NAME" = "" ]; then + TARGET_NAME=$MACHINE_NAME-$OS_NAME-$CC_NAME +fi + + +if test -f build.mak; then + echo 'Saving build.mak --> build.mak.old' + cp -f build.mak build.mak.old +fi + +echo 'Writing build.mak as follows:' +echo " MACHINE_NAME = $MACHINE_NAME" +echo " OS_NAME = $OS_NAME" +echo " HOST_NAME = $HOST_NAME" +echo " CC_NAME = $CC_NAME" +echo " TARGET_NAME = $TARGET_NAME" +echo " CROSS_COMPILE= $CROSS_COMPILE" +echo " LINUX_POLL = $LINUX_POLL" + +echo "# Auto-generated build.mak" > build.mak +echo "export MACHINE_NAME := $MACHINE_NAME" >> build.mak +echo "export OS_NAME := $OS_NAME" >> build.mak +echo "export HOST_NAME := $HOST_NAME" >> build.mak +echo "export CC_NAME := $CC_NAME" >> build.mak +echo "export TARGET_NAME := $TARGET_NAME" >> build.mak +echo "export CROSS_COMPILE := $CROSS_COMPILE" >> build.mak +echo "export LINUX_POLL := $LINUX_POLL" >> build.mak + +touch user.mak + +echo +echo "The configuration for current host has been written to 'build.mak'." +echo "Customizations can be put in:" +echo " - 'user.mak'" +echo " - 'pjlib/include/pj/config_site.h'" +echo +echo "Next, run 'make dep && make clean && make'" + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/build/Makefile ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/build/Makefile Mon Jul 3 22:03:31 2006 @@ -0,0 +1,74 @@ +include ../../build/common.mak + +RULES_MAK := ../../build/rules.mak + +PJLIB_LIB:=/usr/lib/libpj-$(TARGET_NAME)$(LIBEXT) +PJLIB_UTIL_LIB:=/usr/lib/libpjlib-util-$(TARGET_NAME)$(LIBEXT) + +export PJSDP_LIB:=../lib/libpjsdp-$(TARGET_NAME)$(LIBEXT) + +############################################################################### +# Gather all flags. +# +export _CFLAGS := $(CC_CFLAGS) $(OS_CFLAGS) $(HOST_CFLAGS) $(M_CFLAGS) \ + $(CFLAGS) $(CC_INC)../include $(CC_INC)../../pjlib/include \ + $(CC_INC)../../pjlib-util/include +export _CXXFLAGS:= $(_CFLAGS) $(CC_CXXFLAGS) $(OS_CXXFLAGS) $(M_CXXFLAGS) \ + $(HOST_CXXFLAGS) $(CXXFLAGS) +export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJSDP_LIB)) \ + $(subst /,$(HOST_PSEP),$(PJLIB_LIB)) \ + $(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \ + $(CC_LDFLAGS) $(OS_LDFLAGS) $(M_LDFLAGS) $(HOST_LDFLAGS) \ + $(LDFLAGS) + +############################################################################### +# Defines for building PJMEDIA library +# +export PJSDP_SRCDIR = ../src +export PJSDP_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ + errno.o sdp.o sdp_cmp.o sdp_neg.o + +export PJSDP_CFLAGS += $(_CFLAGS) + +export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT +############################################################################### +# Main entry +# +# $(TARGET) is defined in os-$(OS_NAME).mak file in current directory. +# +TARGETS := pjsdp + +all: $(TARGETS) + +doc: + cd .. && \ + if test -f docs/doxygen.cfg; then \ + doxygen docs/doxygen.cfg; \ + fi + +dep: depend +distclean: realclean + +.PHONY: dep depend pjsdp clean realclean distclean + +pjsdp: + $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $(PJSDP_LIB) + +.PHONY: ../lib/pjsdp.ko +../lib/pjsdp.ko: + echo Making $@ + $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@ + +clean: + $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@ + +realclean: + $(subst @@,$(subst /,$(HOST_PSEP),.pjsdp-$(TARGET_NAME).depend),$(HOST_RMR)) + + $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@ + +depend: + $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@ + echo '$(PJSDP_LIB): .pjsdp-$(TARGET_NAME).depend' >> .pjsdp-$(TARGET_NAME).depend; \ + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp.h ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp.h Mon Jul 3 22:03:31 2006 @@ -0,0 +1,33 @@ +/* $Id: pjmedia.h 518 2006-06-18 02:02:36Z bennylp $ */ +/* + * 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 __PJSDP_H__ +#define __PJSDP_H__ + +/** + * @file pjsdp.h + * @brief PJSDP main header file. + */ + +#include <pjsdp/types.h> +#include <pjsdp/errno.h> +#include <pjsdp/sdp.h> +#include <pjsdp/sdp_neg.h> + +#endif /* __PJSDP_H__ */ + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/config.h ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/config.h Mon Jul 3 22:03:31 2006 @@ -0,0 +1,72 @@ +/* $Id: config.h 518 2006-06-18 02:02:36Z bennylp $ */ +/* + * 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_CONFIG_H__ +#define __PJMEDIA_CONFIG_H__ + +/** + * @file pjmedia/config.h Compile time config + * @brief Contains some compile time constants. + */ +#include <pj/config.h> + +/** + * @defgroup PJMEDIA_BASE Base Types and Configurations + * @ingroup PJMEDIA + */ + +/** + * @defgroup PJMEDIA_CONFIG Compile time configuration + * @ingroup PJMEDIA_BASE + * @brief Some compile time configuration settings. + * @{ + */ + +/** + * Maximum frame duration (in msec) to be supported. + * This (among other thing) will affect the size of buffers to be allocated + * for outgoing packets. + */ +#ifndef PJMEDIA_MAX_FRAME_DURATION_MS +# define PJMEDIA_MAX_FRAME_DURATION_MS 200 +#endif + + +/** + * Max packet size to support. + */ +#ifndef PJMEDIA_MAX_MTU +# define PJMEDIA_MAX_MTU 1500 +#endif + + +/** + * DTMF/telephone-event duration, in timestamp. + */ +#ifndef PJMEDIA_DTMF_DURATION +# define PJMEDIA_DTMF_DURATION 1600 /* in timestamp */ +#endif + +/** + * @} + */ + + +#endif /* __PJMEDIA_CONFIG_H__ */ + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/errno.h ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/errno.h Mon Jul 3 22:03:31 2006 @@ -0,0 +1,503 @@ +/* $Id: errno.h 518 2006-06-18 02:02:36Z bennylp $ */ +/* + * 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__ + +/** + * @file errno.h Error Codes + * @brief PJMEDIA specific error codes. + */ + +#include <pjsdp/types.h> +#include <pj/errno.h> + +/** + * @defgroup PJMEDIA_ERRNO Error Codes + * @ingroup PJMEDIA_BASE + * @brief PJMEDIA specific error codes. + * @{ + */ + + +PJ_BEGIN_DECL + + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + */ +#define PJMEDIA_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE) + + +/** + * Mapping from PortAudio error codes to pjmedia error space. + */ +#define PJMEDIA_PORTAUDIO_ERRNO_START (PJMEDIA_ERRNO_START+PJ_ERRNO_SPACE_SIZE-1000) + +/** + * Convert PortAudio error code to PJMEDIA error code. + */ +#define PJMEDIA_ERRNO_FROM_PORTAUDIO(err) (err+PJMEDIA_PORTAUDIO_ERRNO_START) + + +/************************************************************ + * 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 initial local SDP. + */ +#define PJMEDIA_SDPNEG_ENOINITIAL (PJMEDIA_ERRNO_START+41) /* 220041 */ +/** + * @hideinitializer + * No currently active SDP. + */ +#define PJMEDIA_SDPNEG_ENOACTIVE (PJMEDIA_ERRNO_START+42) /* 220042 */ +/** + * @hideinitializer + * No current offer or answer. + */ +#define PJMEDIA_SDPNEG_ENONEG (PJMEDIA_ERRNO_START+43) /* 220043 */ +/** + * @hideinitializer + * Media count mismatch in offer and answer. + */ +#define PJMEDIA_SDPNEG_EMISMEDIA (PJMEDIA_ERRNO_START+44) /* 220044 */ +/** + * @hideinitializer + * Media type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSMEDIA (PJMEDIA_ERRNO_START+45) /* 220045 */ +/** + * @hideinitializer + * Transport type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSTP (PJMEDIA_ERRNO_START+46) /* 220046 */ +/** + * @hideinitializer + * No common media payload is provided in the answer. + */ +#define PJMEDIA_SDPNEG_EANSNOMEDIA (PJMEDIA_ERRNO_START+47) /* 220047 */ +/** + * @hideinitializer + * No media is active after negotiation. + */ +#define PJMEDIA_SDPNEG_ENOMEDIA (PJMEDIA_ERRNO_START+48) /* 220048 */ + + +/************************************************************ + * 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 */ + + +/************************************************************ + * CODEC + ***********************************************************/ +/** + * @hideinitializer + * Unsupported codec. + */ +#define PJMEDIA_CODEC_EUNSUP (PJMEDIA_ERRNO_START+80) /* 220080 */ +/** + * @hideinitializer + * Codec internal creation error. + */ +#define PJMEDIA_CODEC_EFAILED (PJMEDIA_ERRNO_START+81) /* 220081 */ +/** + * @hideinitializer + * Codec frame is too short. + */ +#define PJMEDIA_CODEC_EFRMTOOSHORT (PJMEDIA_ERRNO_START+82) /* 220082 */ +/** + * @hideinitializer + * PCM buffer is too short. + */ +#define PJMEDIA_CODEC_EPCMTOOSHORT (PJMEDIA_ERRNO_START+83) /* 220083 */ +/** + * @hideinitializer + * Invalid codec frame length. + */ +#define PJMEDIA_CODEC_EFRMINLEN (PJMEDIA_ERRNO_START+84) /* 220084 */ + + +/************************************************************ + * MEDIA + ***********************************************************/ +/** + * @hideinitializer + * Invalid remote IP address (in SDP). + */ +#define PJMEDIA_EINVALIDIP (PJMEDIA_ERRNO_START+100) /* 220100 */ +/** + * @hideinitializer + * Asymetric codec is not supported. + */ +#define PJMEDIA_EASYMCODEC (PJMEDIA_ERRNO_START+101) /* 220101 */ +/** + * @hideinitializer + * Invalid payload type. + */ +#define PJMEDIA_EINVALIDPT (PJMEDIA_ERRNO_START+102) /* 220102 */ +/** + * @hideinitializer + * Missing rtpmap. + */ +#define PJMEDIA_EMISSINGRTPMAP (PJMEDIA_ERRNO_START+103) /* 220103 */ +/** + * @hideinitializer + * Invalid media type. + */ +#define PJMEDIA_EINVALIMEDIATYPE (PJMEDIA_ERRNO_START+104) /* 220104 */ +/** + * @hideinitializer + * Remote does not support DTMF. + */ +#define PJMEDIA_EREMOTENODTMF (PJMEDIA_ERRNO_START+105) /* 220105 */ +/** + * @hideinitializer + * Invalid DTMF digit. + */ +#define PJMEDIA_RTP_EINDTMF (PJMEDIA_ERRNO_START+106) /* 220106 */ +/** + * @hideinitializer + * Remote does not support RFC 2833 + */ +#define PJMEDIA_RTP_EREMNORFC2833 (PJMEDIA_ERRNO_START+107) /* 220107 */ + + + +/************************************************************ + * RTP SESSION ERRORS + ***********************************************************/ +/** + * @hideinitializer + * General invalid RTP packet error. + */ +#define PJMEDIA_RTP_EINPKT (PJMEDIA_ERRNO_START+120) /* 220120 */ +/** + * @hideinitializer + * Invalid RTP packet packing. + */ +#define PJMEDIA_RTP_EINPACK (PJMEDIA_ERRNO_START+121) /* 220121 */ +/** + * @hideinitializer + * Invalid RTP packet version. + */ +#define PJMEDIA_RTP_EINVER (PJMEDIA_ERRNO_START+122) /* 220122 */ +/** + * @hideinitializer + * RTP SSRC id mismatch. + */ +#define PJMEDIA_RTP_EINSSRC (PJMEDIA_ERRNO_START+123) /* 220123 */ +/** + * @hideinitializer + * RTP payload type mismatch. + */ +#define PJMEDIA_RTP_EINPT (PJMEDIA_ERRNO_START+124) /* 220124 */ +/** + * @hideinitializer + * Invalid RTP packet length. + */ +#define PJMEDIA_RTP_EINLEN (PJMEDIA_ERRNO_START+125) /* 220125 */ +/** + * @hideinitializer + * RTP session restarted. + */ +#define PJMEDIA_RTP_ESESSRESTART (PJMEDIA_ERRNO_START+130) /* 220130 */ +/** + * @hideinitializer + * RTP session in probation + */ +#define PJMEDIA_RTP_ESESSPROBATION (PJMEDIA_ERRNO_START+131) /* 220131 */ +/** + * @hideinitializer + * Bad RTP sequence number + */ +#define PJMEDIA_RTP_EBADSEQ (PJMEDIA_ERRNO_START+132) /* 220132 */ +/** + * @hideinitializer + * RTP media port destination is not configured + */ +#define PJMEDIA_RTP_EBADDEST (PJMEDIA_ERRNO_START+133) /* 220133 */ +/** + * @hideinitializer + * RTP is not configured. + */ +#define PJMEDIA_RTP_ENOCONFIG (PJMEDIA_ERRNO_START+134) /* 220134 */ + + +/************************************************************ + * PORT ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Generic incompatible port error. + */ +#define PJMEDIA_ENOTCOMPATIBLE (PJMEDIA_ERRNO_START+160) /* 220160 */ +/** + * @hideinitializer + * Incompatible clock rate + */ +#define PJMEDIA_ENCCLOCKRATE (PJMEDIA_ERRNO_START+161) /* 220161 */ +/** + * @hideinitializer + * Incompatible samples per frame + */ +#define PJMEDIA_ENCSAMPLESPFRAME (PJMEDIA_ERRNO_START+162) /* 220162 */ +/** + * @hideinitializer + * Incompatible media type + */ +#define PJMEDIA_ENCTYPE (PJMEDIA_ERRNO_START+163) /* 220163 */ +/** + * @hideinitializer + * Incompatible bits per sample + */ +#define PJMEDIA_ENCBITS (PJMEDIA_ERRNO_START+164) /* 220164 */ +/** + * @hideinitializer + * Incompatible bytes per frame + */ +#define PJMEDIA_ENCBYTES (PJMEDIA_ERRNO_START+165) /* 220165 */ +/** + * @hideinitializer + * Incompatible number of channels + */ +#define PJMEDIA_ENCCHANNEL (PJMEDIA_ERRNO_START+166) /* 220166 */ + + +/************************************************************ + * FILE ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Not a valid WAVE file. + */ +#define PJMEDIA_ENOTVALIDWAVE (PJMEDIA_ERRNO_START+180) /* 220180 */ +/** + * @hideinitializer + * Unsupported WAVE file. + */ +#define PJMEDIA_EWAVEUNSUPP (PJMEDIA_ERRNO_START+181) /* 220181 */ +/** + * @hideinitializer + * Wave file too short. + */ +#define PJMEDIA_EWAVETOOSHORT (PJMEDIA_ERRNO_START+182) /* 220182 */ +/** + * @hideinitializer + * Sound frame is too large for file buffer. + */ +#define PJMEDIA_EFRMFILETOOBIG (PJMEDIA_ERRNO_START+183) /* 220183 */ + + +/************************************************************ + * SOUND DEVICE ERRORS + ***********************************************************/ +/** + * @hideinitializer + * No suitable audio capture device. + */ +#define PJMEDIA_ENOSNDREC (PJMEDIA_ERRNO_START+200) /* 220200 */ +/** + * @hideinitializer + * No suitable audio playback device. + */ +#define PJMEDIA_ENOSNDPLAY (PJMEDIA_ERRNO_START+201) /* 220201 */ +/** + * @hideinitializer + * Invalid sound device ID. + */ +#define PJMEDIA_ESNDINDEVID (PJMEDIA_ERRNO_START+202) /* 220202 */ +/** + * @hideinitializer + * Invalid sample format for sound device. + */ +#define PJMEDIA_ESNDINSAMPLEFMT (PJMEDIA_ERRNO_START+203) /* 220203 */ + + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_ERRNO_H__ */ + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/sdp.h ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/sdp.h Mon Jul 3 22:03:31 2006 @@ -0,0 +1,599 @@ +/* $Id: sdp.h 518 2006-06-18 02:02:36Z bennylp $ */ +/* + * 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_H__ +#define __PJMEDIA_SDP_H__ + +/** + * @file sdp.h + * @brief SDP header file. + */ +#include <pjsdp/types.h> + + +/** + * @defgroup PJMEDIA_SDP SDP Parsing and Data Structure + * @ingroup PJMEDIA_SESSION + * @{ + * + * The basic SDP session descriptor and elements are described in header + * file <b><pjsdp/sdp.h></b>. This file contains declaration for + * SDP session descriptor and SDP media descriptor, along with their + * attributes. This file also declares functions to parse SDP message. + */ + + +PJ_BEGIN_DECL + +/** + * The PJMEDIA_MAX_SDP_FMT macro defines maximum format in a media line. + */ +#ifndef PJMEDIA_MAX_SDP_FMT +# define PJMEDIA_MAX_SDP_FMT 32 +#endif + +/** + * The PJMEDIA_MAX_SDP_ATTR macro defines maximum SDP attributes in media and + * session descriptor. + */ +#ifndef PJMEDIA_MAX_SDP_ATTR +# define PJMEDIA_MAX_SDP_ATTR (PJMEDIA_MAX_SDP_FMT*2 + 4) +#endif + +/** + * The PJMEDIA_MAX_SDP_MEDIA macro defines maximum SDP media lines in a + * SDP session descriptor. + */ +#ifndef PJMEDIA_MAX_SDP_MEDIA +# define PJMEDIA_MAX_SDP_MEDIA 16 +#endif + + +/* ************************************************************************** + * SDP ATTRIBUTES + *************************************************************************** + */ + +/** + * Generic representation of attribute. + */ +struct pjmedia_sdp_attr +{ + pj_str_t name; /**< Attribute name. */ + pj_str_t value; /**< Attribute value. */ +}; + +/** + * @see pjmedia_sdp_attr + */ +typedef struct pjmedia_sdp_attr pjmedia_sdp_attr; + + +/** + * Create SDP attribute. + * + * @param pool Pool to create the attribute. + * @param name Attribute name. + * @param value Optional attribute value. + * + * @return The new SDP attribute. + */ +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); + +/** + * 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 \a rtpmap and \a fmt attributes. For other + * types of attributes, the value should be NULL. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find2, pjmedia_sdp_media_find_attr, + * pjmedia_sdp_media_find_attr2 + */ +PJ_DECL(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find(unsigned count, + 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 \a rtpmap and \a fmt attributes. For other + * types of attributes, the value should be NULL. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find, pjmedia_sdp_media_find_attr, + * pjmedia_sdp_media_find_attr2 + */ +PJ_DECL(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find2(unsigned count, + pjmedia_sdp_attr *const attr_array[], + const char *name, const pj_str_t *fmt); + +/** + * 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 + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr); + +/** + * Remove all attributes with the specified name in array of attributes. + * + * @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 + */ +PJ_DECL(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, + pjmedia_sdp_attr *attr_array[], + const char *name); + + +/** + * Remove the specified attribute from the attribute array. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param attr The attribute instance to remove. + * + * @return PJ_SUCCESS when attribute has been removed, or + * PJ_ENOTFOUND when the attribute can not be found. + * + * @see pjmedia_sdp_media_remove_attr + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_remove(unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr); + + +/** + * This structure declares SDP \a rtpmap attribute. + */ +struct pjmedia_sdp_rtpmap +{ + pj_str_t pt; /**< Payload type. */ + pj_str_t enc_name; /**< Encoding name. */ + unsigned clock_rate; /**< Clock rate. */ + pj_str_t param; /**< Parameter. */ +}; + +/** + * @see pjmedia_sdp_rtpmap + */ +typedef struct pjmedia_sdp_rtpmap pjmedia_sdp_rtpmap; + + +/** + * Convert generic attribute to SDP \a rtpmap. This function allocates + * a new attribute and call #pjmedia_sdp_attr_get_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 if the attribute can be successfully + * converted to \a rtpmap type. + * + * @see pjmedia_sdp_attr_get_rtpmap + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, + const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap **p_rtpmap); + + +/** + * 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 \a rtpmap attribute to be initialized. + * + * @return PJ_SUCCESS if the attribute can be successfully + * converted to \a rtpmap attribute. + * + * @see pjmedia_sdp_attr_to_rtpmap + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtpmap(const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap *rtpmap); + + +/** + * Convert \a rtpmap attribute to generic attribute. + * + * @param pool Pool to be used. + * @param rtpmap The \a 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); + + +/** + * This structure describes SDP \a fmtp attribute. + */ +struct pjmedia_sdp_fmtp +{ + pj_str_t fmt; /**< Format type. */ + pj_str_t fmt_param; /**< Format specific parameter. */ +}; + + +/** + * @see pjmedia_sdp_fmtp + */ +typedef struct pjmedia_sdp_fmtp pjmedia_sdp_fmtp; + + + +/** + * 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); + + +/* ************************************************************************** + * SDP CONNECTION INFO + **************************************************************************** + */ + +/** + * This structure describes SDP connection info ("c=" line). + */ +struct pjmedia_sdp_conn +{ + pj_str_t net_type; /**< Network type ("IN"). */ + pj_str_t addr_type; /**< Address type ("IP4", "IP6"). */ + pj_str_t addr; /**< The address. */ +}; + + +/** + * @see pjmedia_sdp_conn + */ +typedef struct pjmedia_sdp_conn pjmedia_sdp_conn; + + +/** + * Clone connection info. + * + * @param pool Pool to allocate memory for the new connection info. + * @param rhs The connection into to clone. + * + * @return The new connection info. + */ +PJ_DECL(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone(pj_pool_t *pool, + const pjmedia_sdp_conn *rhs); + + + +/* ************************************************************************** + * SDP MEDIA INFO/LINE + **************************************************************************** + */ + +/** + * This structure describes SDP media descriptor. A SDP media descriptor + * starts with "m=" line and contains the media attributes and optional + * connection line. + */ +struct pjmedia_sdp_media +{ + /** Media descriptor line ("m=" line) */ + struct + { + 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[PJMEDIA_MAX_SDP_FMT]; /**< Media formats. */ + } desc; + + pjmedia_sdp_conn *conn; /**< Optional connection info. */ + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr*attr[PJMEDIA_MAX_SDP_ATTR]; /**< Attributes. */ + +}; + + +/** + * @see pjmedia_sdp_media + */ +typedef struct pjmedia_sdp_media pjmedia_sdp_media; + + +/** + * Clone SDP media description. + * + * @param pool Pool to allocate memory for the new media description. + * @param rhs The media descriptin to clone. + * + * @return New media description. + */ +PJ_DECL(pjmedia_sdp_media*) +pjmedia_sdp_media_clone( pj_pool_t *pool, + const pjmedia_sdp_media *rhs); + +/** + * Find the first occurence of the specified attribute name in the media + * descriptor. Optionally the format may be specified. + * + * @param m The SDP media description. + * @param name Attribute name to find. + * @param fmt Optional payload type to match in the + * attribute list, when the attribute is \a rtpmap + * or \a fmtp. For other types of SDP attributes, this + * value should be NULL. + * + * @return The first instance of the specified attribute or NULL. + */ +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 the first occurence of the specified attribute name in the SDP media + * descriptor. Optionally the format may be specified. + * + * @param m The SDP media description. + * @param name Attribute name to find. + * @param fmt Optional payload type to match in the + * attribute list, when the attribute is \a rtpmap + * or \a fmtp. For other types of SDP attributes, this + * value should be NULL. + * + * @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 attr 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 from the SDP media + * descriptor. + * + * @param m The SDP media description. + * @param name Attribute name to remove. + * + * @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 from the SDP media + * descriptor. + * + * @param m The SDP media descriptor. + * @param attr The attribute to find and remove. + * + * @return PJ_SUCCESS if the attribute can be found and has + * been removed from the array. + */ +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, which should be zero for now. + * + * @return PJ_SUCCESS when both SDP medias are equal, or the + * appropriate status code describing which part of + * the descriptors that are not 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 + **************************************************************************** + */ + + +/** + * This structure describes SDP session description. A SDP session descriptor + * contains complete information about a session, and normally is exchanged + * with remote media peer using signaling protocol such as SIP. + */ +struct pjmedia_sdp_session +{ + /** Session origin (o= line) */ + struct + { + 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; /**< Subject line (s=) */ + pjmedia_sdp_conn *conn; /**< Connection line (c=) */ + + /** Session time (t= line) */ + struct + { + pj_uint32_t start; /**< Start time. */ + pj_uint32_t stop; /**< Stop time. */ + } time; + + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr *attr[PJMEDIA_MAX_SDP_ATTR]; /**< Attributes array. */ + + unsigned media_count; /**< Number of media. */ + pjmedia_sdp_media *media[PJMEDIA_MAX_SDP_MEDIA]; /**< Media array. */ + +}; + +/** + * @see pjmedia_sdp_session + */ +typedef struct pjmedia_sdp_session pjmedia_sdp_session; + + + +/** + * Parse SDP message. + * + * @param pool The pool to allocate SDP session description. + * @param buf The message buffer. + * @param len The length of the message. + * @param p_sdp Pointer to receive the SDP session descriptor. + * + * @return PJ_SUCCESS if message was successfully parsed into + * SDP session descriptor. + */ +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. + * + * @param sdp The SDP session description. + * @param buf The buffer. + * @param size The buffer length. + * + * @return the length printed, or -1 if the buffer is too + * short. + */ +PJ_DECL(int) pjmedia_sdp_print( const pjmedia_sdp_session *sdp, + char *buf, pj_size_t size); + + +/** + * Perform semantic validation for the specified SDP session descriptor. + * This function perform validation beyond just syntactic verification, + * such as to verify the value of network type and address type, check + * the connection line, and verify that \a rtpmap attribute is present + * when dynamic payload type is used. + * + * @param sdp The SDP session descriptor to validate. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp); + + +/** + * Clone SDP session descriptor. + * + * @param pool The pool used to clone the session. + * @param sdp 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 *sdp); + + +/** + * Compare two SDP session for equality. + * + * @param sd1 The first SDP session to compare. + * @param sd2 The second SDP session to compare. + * @param option Must be zero for now. + * + * @return PJ_SUCCESS when both SDPs are equal, or otherwise + * the status code indicates which part of the session + * descriptors are not 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 /* __PJMEDIA_SDP_H__ */ + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/sdp_neg.h ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/sdp_neg.h Mon Jul 3 22:03:31 2006 @@ -0,0 +1,613 @@ +/* $Id: sdp_neg.h 518 2006-06-18 02:02:36Z bennylp $ */ +/* + * 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__ + + +/** + * @file sdp_neg.h + * @brief SDP negotiator header file. + */ +/** + * @defgroup PJMEDIA_SDP_NEG SDP Negotiator + * @ingroup PJMEDIA_SESSION + * @{ + * + * The header file <b><pjsdp/sdp_neg.h></b> contains the declaration + * of SDP offer and answer negotiator. SDP offer and answer model is described + * in RFC 3264 <b>"An Offer/Answer Model with Session Description Protocol + * (SDP)"</b>. + * + * The SDP negotiator is represented with opaque type \a pjmedia_sdp_neg. + * This structure contains negotiation state and several SDP session + * descriptors currently being used in the negotiation. + * + * + * \section sdpneg_state_dia SDP Negotiator State Diagram + * + * The following diagram describes the state transition diagram of the + * SDP negotiator. + * + * <pre> + * + * modify_local_offer() + * create_w_local_offer() +-------------+ send_local_offer() + * ----------------------->| LOCAL_OFFER |<----------------------- + * | +-------------+ | + * | | | + * | set_remote_answer() | | + * | V | + * +--+---+ +-----------+ negotiate() +------+ + * | NULL | | WAIT_NEGO |-------------------->| DONE | + * +------+ +-----------+ +------+ + * | A | + * | set_local_answer() | | + * | | | + * | +--------------+ set_remote_offer() | + * ----------------------->| REMOTE_OFFER |<---------------------- + * create_w_remote_offer() +--------------+ + * + * </pre> + * + * + * + * \section sdpneg_offer_answer SDP Offer/Answer Model with Negotiator + * + * \subsection sdpneg_create_offer Creating Initial Offer + * + * Application creates an offer by manualy building the SDP session descriptor + * (pjmedia_sdp_session), or request PJMEDIA endpoint (pjmedia_endpt) to + * create SDP session descriptor based on capabilities that present in the + * endpoint by calling #pjmedia_endpt_create_sdp(). + * + * Application then creates SDP negotiator instance by calling + * #pjmedia_sdp_neg_create_w_local_offer(), passing the SDP offer in the + * function arguments. The SDP negotiator keeps a copy of current local offer, + * and update its state to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER. + * + * Application can then send the initial SDP offer that it creates to + * remote peer using signaling protocol such as SIP. + * + * + * \subsection sdpneg_subseq_offer Generating Subsequent Offer + * + * The negotiator can only create subsequent offer after it has finished + * the negotiation process of previous offer/answer session (i.e. the + * negotiator state is PJMEDIA_SDP_NEG_STATE_DONE). + * + * If any previous negotiation process was successfull (i.e. the return + * value of #pjmedia_sdp_neg_negotiate() was PJ_SUCCESS), the negotiator + * keeps both active local and active remote SDP. + * + * If application does not want send modified offer, it can just send + * the active local SDP as the offer. In this case, application calls + * #pjmedia_sdp_neg_send_local_offer() to get the active local SDP. + * + * If application wants to modify it's local offer, it MUST inform + * the negotiator about the modified SDP by calling + * #pjmedia_sdp_neg_modify_local_offer(). + * + * In both cases, the negotiator will internally create a copy of the offer, + * and move it's state to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it + * waits until application passes the remote answer. + * + * + * \subsection sdpneg_receive_offer Receiving Initial Offer + * + * Application receives an offer in the incoming request from remote to + * establish multimedia session, such as incoming INVITE message with SDP + * body. + * + * Initially, when the initial offer is received, application creates the + * SDP negotiator by calling #pjmedia_sdp_neg_create_w_remote_offer(), + * specifying the remote SDP offer in one of the argument. + * + * At this stage, application may or may not ready to create an answer. + * For example, a SIP B2BUA needs to make outgoing call and receive SDP + * from the outgoing call leg in order to create a SDP answer to the + * incoming call leg. + * + * If application is not ready to create an answer, it passes NULL as + * the local SDP when it calls #pjmedia_sdp_neg_create_w_remote_offer(). + * + * The section @ref sdpneg_create_answer describes the case when + * application is ready to create a SDP answer. + * + * + * \subsection sdpneg_subseq_offer Receiving Subsequent Offer + * + * Application passes subsequent SDP offer received from remote by + * calling #pjmedia_sdp_neg_set_remote_offer(). + * + * The negotiator can only receive subsequent offer after it has finished + * the negotiation process of previous offer/answer session (i.e. the + * negotiator state is PJMEDIA_SDP_NEG_STATE_DONE). + * + * + * \subsection sdpneg_recv_answer Receiving SDP Answer + * + * When application receives SDP answer from remote, it informs the + * negotiator by calling #pjmedia_sdp_neg_set_remote_answer(). The + * negotiator validates the answer (#pjmedia_sdp_validate()), and if + * succeeds, it moves it's state to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO. + * + * Application then instruct the negotiator to negotiate the remote + * answer by calling #pjmedia_sdp_neg_negotiate(). The purpose of + * this negotiation is to verify remote answer, and update the initial + * offer according to the answer. For example, the initial offer may + * specify that a stream is \a sendrecv, while the answer specifies + * that remote stream is \a inactive. In this case, the negotiator + * will update the stream in the local active media as \a inactive + * too. + * + * If #pjmedia_sdp_neg_negotiate() returns PJ_SUCCESS, the negotiator will + * keep the updated local answer and remote answer internally. These two + * SDPs are called active local SDP and active remote SDP, as it describes + * currently active session. + * + * Application can retrieve the active local SDP by calling + * #pjmedia_sdp_neg_get_active_local(), and active remote SDP by calling + * #pjmedia_sdp_neg_get_active_remote(). + * + * If #pjmedia_sdp_neg_negotiate() returns failure (i.e. not PJ_SUCCESS), + * it WILL NOT update its active local and active remote SDP. + * + * Regardless of the return status of the #pjmedia_sdp_neg_negotiate(), + * the negotiator state will move to PJMEDIA_SDP_NEG_STATE_DONE. + * + * + * \subsection sdpneg_create_answer Generating SDP Answer + * + * After remote offer has been set in the negotiator, application can + * request the SDP negotiator to generate appropriate answer based on local + * capability. + * + * To do this, first the application MUST have an SDP describing its local + * capabilities. This SDP can be built manually, or application can generate + * SDP to describe local media endpoint capability by calling + * #pjmedia_endpt_create_sdp(). When the application is a SIP B2BUA, + * application can treat the SDP received from the outgoing call leg as if + * it was it's local capability. + * + * The local SDP session descriptor DOES NOT have to match the SDP offer. + * For example, it can have more or less media lines than the offer, or + * their order may be different than the offer. The negotiator is capable + * to match and reorder local SDP according to remote offer, and create + * an answer that is suitable for the offer. + * + * After local SDP capability has been acquired, application can create + * a SDP answer. + * + * If application does not already have the negotiator instance, it creates + * one by calling #pjmedia_sdp_neg_create_w_remote_offer(), specifying + * both remote SDP offer and local SDP as the arguments. The SDP negotiator + * validates both remote and local SDP by calling #pjmedia_sdp_validate(), + * and if both SDPs are valid, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO where it is ready to negotiate the + * offer and answer. + * + * If application already has the negotiator instance, it sets the local + * SDP in the negotiator by calling #pjmedia_sdp_neg_set_local_answer(). + * The SDP negotiator then validates local SDP (#pjmedia_sdp_validate() ), + * and if it is valid, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO where it is ready to negotiate the + * offer and answer. + * + * After the SDP negotiator state has moved to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + * application calls #pjmedia_sdp_neg_negotiate() to instruct the SDP + * negotiator to negotiate both offer and answer. This function returns + * PJ_SUCCESS if an answer can be generated AND at least one media stream + * is active in the session. + * + * If #pjmedia_sdp_neg_negotiate() returns PJ_SUCCESS, the negotiator will + * keep the remote offer and local answer internally. These two SDPs are + * called active local SDP and active remote SDP, as it describes currently + * active session. + * + * Application can retrieve the active local SDP by calling + * #pjmedia_sdp_neg_get_active_local(), and send this SDP to remote as the + * SDP answer. + * + * If #pjmedia_sdp_neg_negotiate() returns failure (i.e. not PJ_SUCCESS), + * it WILL NOT update its active local and active remote SDP. + * + * Regardless of the return status of the #pjmedia_sdp_neg_negotiate(), + * the negotiator state will move to PJMEDIA_SDP_NEG_STATE_DONE. + * + * + */ + +#include <pjsdp/sdp.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 SDP negotiator has received offer from remote + * and currently waiting for local answer. + */ + PJMEDIA_SDP_NEG_STATE_REMOTE_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. Application can call #pjmedia_sdp_neg_negotiate() + * immediately to begin negotiation process. + */ + PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + + /** + * This state occurs when SDP negotiation has completed, either + * successfully or not. + */ + PJMEDIA_SDP_NEG_STATE_DONE, +}; + + +/** + * @see pjmedia_sdp_neg_state + */ +typedef enum pjmedia_sdp_neg_state pjmedia_sdp_neg_state; + + +/** + * Opaque declaration of SDP negotiator. + */ +typedef struct pjmedia_sdp_neg pjmedia_sdp_neg; + + +/** + * Get the state string description of the specified state. + * + * @param state Negotiator state. + * + * @return String description of the state. + */ +PJ_DECL(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state); + + +/** + * 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 must call #pjmedia_sdp_neg_set_remote_answer(). + * + * After calling this function, application should send the local SDP offer + * to remote party using signaling protocol such as SIP and wait for SDP + * answer. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param local The initial local capability. + * @param p_neg Pointer to receive the negotiator instance. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +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 remote offer, and optionally + * specify the initial local capability, if known. Application normally + * calls this function when it receives initial offer from remote. + * + * If local media capability is specified, this capability will be set as + * initial local capability of the negotiator, and after this function is + * called, the SDP negotiator state will move to state + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and the negotiation function can be + * called. + * + * If local SDP is not specified, the negotiator will not have initial local + * capability, and after this function is called the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER state. Application MUST supply + * local answer later with #pjmedia_sdp_neg_set_local_answer(), before + * calling the negotiation function. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param initial Optional initial local capability. + * @param remote The remote offer. + * @param p_neg Pointer to receive the negotiator instance. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, + const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *remote, + pjmedia_sdp_neg **p_neg); + +/** + * Get SDP negotiator state. + * + * @param neg The SDP negotiator instance. + * + * @return The 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. + * + * @param neg The SDP negotiator instance. + * @param local Pointer to receive the local active SDP. + * + * @return PJ_SUCCESS if local active SDP is present. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_active_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. + * + * @param neg The SDP negotiator instance. + * @param remote Pointer to receive the remote active SDP. + * + * @return PJ_SUCCESS if remote active SDP is present. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote); + + +/** + * Determine whether remote sent answer (as opposed to offer) on the + * last negotiation. This function can only be called in state + * PJMEDIA_SDP_NEG_STATE_DONE. + * + * @param neg The SDP negotiator instance. + * + * @return Non-zero if it was remote who sent answer, + * otherwise zero if it was local who supplied + * answer. + */ +PJ_DECL(pj_bool_t) +pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg); + + +/** + * Get the current remote SDP offer or answer. Application can only + * call this function in state PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER or + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be remote + * SDP offer/answer. Calling this function will not change the state + * of the negotiator. + * + * @param neg The SDP negotiator instance. + * @param remote Pointer to receive the current remote offer or + * answer. + * + * @return PJ_SUCCESS if the negotiator currently has + * remote offer or answer. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote); + + +/** + * Get the current local SDP offer or answer. Application can only + * call this function in state PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER or + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be local + * SDP offer/answer. Calling this function will not change the state + * of the negotiator. + * + * @param neg The SDP negotiator instance. + * @param local Pointer to receive the current local offer or + * answer. + * + * @return PJ_SUCCESS if the negotiator currently has + * local offer or answer. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **local); + +/** + * Modify local session with a new SDP and treat this as a new offer. + * This function can only be called in state PJMEDIA_SDP_NEG_STATE_DONE. + * After calling this function, application can send the SDP as offer + * to remote party, using signaling protocol such as SIP. + * The negotiator state will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + * where it waits for SDP answer from remote. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param local The new local SDP. + * + * @return PJ_SUCCESS on success, or the appropriate + * error code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *local); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. + * Application calls this function to retrieve currently active + * local SDP, and then send the SDP to remote as an offer. The negotiator + * state will then move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it waits + * for SDP answer from remote. + * + * When SDP answer has been received from remote, application must call + * #pjmedia_sdp_neg_set_remote_answer(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param offer Pointer to receive active local SDP to be + * offered to remote. + * + * @return PJ_SUCCESS if local offer can be created. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_send_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_LOCAL_OFFER + * state, i.e. after application calls #pjmedia_sdp_neg_send_local_offer() + * function. 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(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param remote The remote answer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote); + + + +/** + * 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_REMOTE_OFFER, and application MUST call the + * #pjmedia_sdp_neg_set_local_answer() to set local answer before it can + * call the negotiation function. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param remote The remote offer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_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_REMOTE_OFFER + * state, i.e. after application calls #pjmedia_sdp_neg_set_remote_offer() + * function. 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(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param local Optional local answer. If negotiator has initial + * local capability, application can specify NULL on + * this argument; in this case, the negotiator will + * create answer by by negotiating remote offer with + * initial local capability. If negotiator doesn't have + * initial local capability, application MUST specify + * local answer here. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *local); + + +/** + * Call this function when the negotiator is in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO + * state to see if it was local who is answering the offer (instead of + * remote). + * + * @param neg The negotiator. + * + * @return PJ_TRUE if it is local is answering an offer, PJ_FALSE + * if remote has answered local offer. + */ +PJ_DECL(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg); + + +/** + * 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 regardless whether the negotiation has + * been successfull or not. + * + * If the negotiation succeeds (i.e. the return value is PJ_SUCCESS), + * the active local and remote SDP will be replaced with the new SDP + * from the negotiation process. + * + * If the negotiation fails, the active local and remote SDP will not + * change. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param allow_asym Should be zero. + * + * @return PJ_SUCCESS when there is at least one media + * is actuve common in both offer and answer, or + * failure code when negotiation has failed. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + pj_bool_t allow_asym); + + + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_SDP_NEG_H__ */ + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/types.h ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/include/pjsdp/types.h Mon Jul 3 22:03:31 2006 @@ -0,0 +1,123 @@ +/* $Id: types.h 518 2006-06-18 02:02:36Z bennylp $ */ +/* + * 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__ + +/** + * @file pjmedia/types.h Basic Types + * @brief Basic PJMEDIA types. + */ + +#include <pjsdp/config.h> +#include <pj/sock.h> /* pjmedia_sock_info */ +#include <pj/string.h> /* pj_memcpy(), pj_memset() */ + + +/** + * @defgroup PJMEDIA_FRAME_OP Frame Operations + * @ingroup PJMEDIA + */ + +/** + * @defgroup PJMEDIA_MISC Misc + * @ingroup PJMEDIA + */ + +/** + * @defgroup PJMEDIA_TYPES Basic Types + * @ingroup PJMEDIA_BASE + * @brief Basic PJMEDIA types and operations. + * @{ + */ + +/** + * Top most media type. + */ +typedef enum pjmedia_type +{ + /** No type. */ + PJMEDIA_TYPE_NONE = 0, + + /** The media is audio */ + PJMEDIA_TYPE_AUDIO = 1, + + /** The media is video. */ + PJMEDIA_TYPE_VIDEO = 2, + + /** Unknown media type, in this case the name will be specified in + * encoding_name. + */ + PJMEDIA_TYPE_UNKNOWN = 3, + +} pjmedia_type; + + + +/** + * Media direction. + */ +typedef enum pjmedia_dir +{ + /** None */ + PJMEDIA_DIR_NONE = 0, + + /** Encoding (outgoing to network) stream */ + PJMEDIA_DIR_ENCODING = 1, + + /** Decoding (incoming from network) stream. */ + PJMEDIA_DIR_DECODING = 2, + + /** Incoming and outgoing stream. */ + PJMEDIA_DIR_ENCODING_DECODING = 3, + +} pjmedia_dir; + + + +/* Alternate names for media direction: */ + +/** + * Direction is capturing audio frames. + */ +#define PJMEDIA_DIR_CAPTURE PJMEDIA_DIR_ENCODING + +/** + * Direction is playback of audio frames. + */ +#define PJMEDIA_DIR_PLAYBACK PJMEDIA_DIR_DECODING + +/** + * Direction is both capture and playback. + */ +#define PJMEDIA_DIR_CAPTURE_PLAYBACK PJMEDIA_DIR_ENCODING_DECODING + + +/** + * Create 32bit port signature from ASCII characters. + */ +#define PJMEDIA_PORT_SIGNATURE(a,b,c,d) \ + (a<<24 | b<<16 | c<<8 | d) + +/** + * @} + */ + + +#endif /* __PJMEDIA_TYPES_H__ */ + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/errno.c ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/errno.c Mon Jul 3 22:03:31 2006 @@ -0,0 +1,155 @@ +/* $Id: errno.c 438 2006-05-13 22:46:23Z bennylp $ */ +/* + * 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 <pjmedia/types.h> +#include <pj/string.h> + + +/* PJMEDIA's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + +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_ENOINITIAL, "No initial local SDP in SDP negotiator" }, + { PJMEDIA_SDPNEG_ENOACTIVE, "No active SDP in SDP negotiator" }, + { PJMEDIA_SDPNEG_ENONEG, "No current local/remote offer/answer" }, + { 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" }, + { PJMEDIA_SDPNEG_ENOMEDIA, "No active media stream after negotiation" }, + + /* 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" }, + + /* Media errors. */ + { PJMEDIA_EINVALIDIP, "Invalid remote media (IP) address" }, + { PJMEDIA_EASYMCODEC, "Asymetric media codec is not supported" }, + { PJMEDIA_EINVALIDPT, "Invalid media payload type" }, + { PJMEDIA_EMISSINGRTPMAP, "Missing rtpmap in media description" }, + { PJMEDIA_EINVALIMEDIATYPE, "Invalid media type" }, + { PJMEDIA_EREMOTENODTMF, "Remote does not support DTMF" }, + { PJMEDIA_RTP_EINDTMF, "Invalid DTMF digit" }, + { PJMEDIA_RTP_EREMNORFC2833, "Remote does not support RFC 2833" }, +}; + +#endif /* PJ_HAS_ERROR_STRING */ + + + +/* + * pjmedia_strerror() + */ +PJ_DEF(pj_str_t) pjmedia_strerror( pj_status_t statcode, + char *buf, pj_size_t bufsize ) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + 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_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, + "Unknown pjmedia error %d", + statcode); + + return errstr; +} Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp.c ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp.c Mon Jul 3 22:03:31 2006 @@ -0,0 +1,1181 @@ +/* $Id: sdp.c 366 2006-03-30 16:44:28Z bennylp $ */ +/* + * 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 <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, + SYNTAX_ERROR = 1, +}; +#define TOKEN "-.!%*_=`'~" +#define NTP_OFFSET ((pj_uint32_t)2208988800) +#define THIS_FILE "sdp.c" + +typedef struct parse_context +{ + pj_status_t last_error; +} parse_context; + + +/* + * Prototypes for line parser. + */ +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); + + +/* + * Scanner character specification. + */ +static int is_initialized; +static pj_cis_buf_t cis_buf; +static pj_cis_t cs_token; + +static void init_sdp_parser(void) +{ + if (is_initialized == 0) { + is_initialized = 1; + if (is_initialized != 1) { + return; + } + } + + pj_cis_buf_init(&cis_buf); + pj_cis_init(&cis_buf, &cs_token); + pj_cis_add_alpha(&cs_token); + pj_cis_add_num(&cs_token); + pj_cis_add_str(&cs_token, TOKEN); +} + +PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_create( pj_pool_t *pool, + const char *name, + const pj_str_t *value) +{ + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && name, NULL); + + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); + pj_strdup2(pool, &attr->name, name); + + if (value) + pj_strdup(pool, &attr->value, value); + else { + attr->value.ptr = NULL; + attr->value.slen = 0; + } + + return attr; +} + +PJ_DEF(pjmedia_sdp_attr*) pjmedia_sdp_attr_clone(pj_pool_t *pool, + const pjmedia_sdp_attr *rhs) +{ + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + attr = pj_pool_alloc(pool, sizeof(pjmedia_sdp_attr)); + + pj_strdup(pool, &attr->name, &rhs->name); + pj_strdup(pool, &attr->value, &rhs->value); + + return attr; +} + +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find (unsigned count, + pjmedia_sdp_attr *const attr_array[], + const pj_str_t *name, + const pj_str_t *c_fmt) +{ + char fmtbuf[16]; + pj_str_t fmt = { NULL, 0}; + unsigned i; + + 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; + } + } + return NULL; +} + +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_attr_find2(unsigned count, + pjmedia_sdp_attr *const attr_array[], + const char *c_name, + const pj_str_t *c_fmt) +{ + pj_str_t name; + + name.ptr = (char*)c_name; + name.slen = pj_ansi_strlen(c_name); + + return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt); +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr) +{ + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(*count < PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY); + + attr_array[*count] = attr; + (*count)++; + + return PJ_SUCCESS; +} + + +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_ansi_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; +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove( unsigned *count, + pjmedia_sdp_attr *attr_array[], + pjmedia_sdp_attr *attr ) +{ + 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; + } + } + + return removed ? PJ_SUCCESS : PJ_ENOTFOUND; +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap( const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap *rtpmap) +{ + 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 (p != end && pj_isdigit(*p)) + ++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 != end && *p != '/') + return PJMEDIA_SDP_EINRTPMAP; + + if (p != end) { + ++p; + token.ptr = (char*)p; + token.slen = end-p; + rtpmap->param = token; + } else { + rtpmap->param.ptr = NULL; + rtpmap->param.slen = 0; + } + + return PJ_SUCCESS; + +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp( const pjmedia_sdp_attr *attr, + pjmedia_sdp_fmtp *fmtp) +{ + 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, "fmtp")==0, PJ_EINVALIDOP); + + /* fmtp BNF: + * a=fmtp:<format> <format specific parameter> + */ + + /* Eat the first ':' */ + if (*p != ':') return PJMEDIA_SDP_EINFMTP; + + /* Get ':' */ + ++p; + + /* 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; + + fmtp->fmt = token; + + /* Expecting space after format. */ + if (*p != ' ') return PJMEDIA_SDP_EINFMTP; + + /* Get space. */ + ++p; + + /* Set the remaining string as fmtp format parameter. */ + fmtp->fmt_param.ptr = (char*)p; + fmtp->fmt_param.slen = end - p; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, + const pjmedia_sdp_attr *attr, + pjmedia_sdp_rtpmap **p_rtpmap) +{ + PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL); + + *p_rtpmap = pj_pool_alloc(pool, sizeof(pjmedia_sdp_rtpmap)); + PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM); + + return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap); +} + + +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; + + /* 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; + } + + 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 colon */ + *p++ = ':'; + + /* 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( pjmedia_sdp_conn *c, char *buf, int len) +{ + char *p = buf; + + if (len < 8+c->net_type.slen+c->addr_type.slen+c->addr.slen) { + return -1; + } + *p++ = 'c'; + *p++ = '='; + pj_memcpy(p, c->net_type.ptr, c->net_type.slen); + p += c->net_type.slen; + *p++ = ' '; + pj_memcpy(p, c->addr_type.ptr, c->addr_type.slen); + p += c->addr_type.slen; + *p++ = ' '; + pj_memcpy(p, c->addr.ptr, c->addr.slen); + p += c->addr.slen; + *p++ = '\r'; + *p++ = '\n'; + + return p-buf; +} + +PJ_DEF(pjmedia_sdp_conn*) pjmedia_sdp_conn_clone (pj_pool_t *pool, + const pjmedia_sdp_conn *rhs) +{ + 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; + if (!pj_strdup (pool, &c->addr_type, &rhs->addr_type)) return NULL; + if (!pj_strdup (pool, &c->addr, &rhs->addr)) return NULL; + + return c; +} + +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; + unsigned i; + int printed; + + /* check length for the "m=" line. */ + if (len < m->desc.media.slen+m->desc.transport.slen+12+24) { + return -1; + } + *p++ = 'm'; /* m= */ + *p++ = '='; + pj_memcpy(p, m->desc.media.ptr, m->desc.media.slen); + p += m->desc.media.slen; + *p++ = ' '; + printed = pj_utoa(m->desc.port, p); + p += printed; + if (m->desc.port_count > 1) { + *p++ = '/'; + printed = pj_utoa(m->desc.port_count, p); + p += printed; + } + *p++ = ' '; + pj_memcpy(p, m->desc.transport.ptr, m->desc.transport.slen); + p += m->desc.transport.slen; + for (i=0; i<m->desc.fmt_count; ++i) { + *p++ = ' '; + pj_memcpy(p, m->desc.fmt[i].ptr, m->desc.fmt[i].slen); + p += m->desc.fmt[i].slen; + } + *p++ = '\r'; + *p++ = '\n'; + + /* print connection info, if present. */ + if (m->conn) { + printed = print_connection_info(m->conn, p, end-p); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* print attributes. */ + for (i=0; i<m->attr_count; ++i) { + printed = print_attr(m->attr[i], p, end-p); + if (printed < 0) { + return -1; + } + p += printed; + } + + return p-buf; +} + +PJ_DEF(pjmedia_sdp_media*) pjmedia_sdp_media_clone( + pj_pool_t *pool, + const pjmedia_sdp_media *rhs) +{ + unsigned int i; + 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; + m->desc.port_count = rhs->desc.port_count; + pj_strdup (pool, &m->desc.transport, &rhs->desc.transport); + m->desc.fmt_count = rhs->desc.fmt_count; + for (i=0; i<rhs->desc.fmt_count; ++i) + pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]); + + if (rhs->conn) { + 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] = pjmedia_sdp_attr_clone (pool, rhs->attr[i]); + PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL); + } + + return m; +} + +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) +{ + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt); +} + + + +PJ_DEF(pjmedia_sdp_attr*) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, + const char *name, const pj_str_t *fmt) +{ + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt); +} + + +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; + unsigned i; + int printed; + + /* Check length for v= and o= lines. */ + if (len < 5+ + 2+ses->origin.user.slen+18+ + ses->origin.net_type.slen+ses->origin.addr.slen + 2) + { + return -1; + } + + /* SDP version (v= line) */ + pj_memcpy(p, "v=0\r\n", 5); + p += 5; + + /* Owner (o=) line. */ + *p++ = 'o'; + *p++ = '='; + pj_memcpy(p, ses->origin.user.ptr, ses->origin.user.slen); + p += ses->origin.user.slen; + *p++ = ' '; + printed = pj_utoa(ses->origin.id, p); + p += printed; + *p++ = ' '; + printed = pj_utoa(ses->origin.version, p); + p += printed; + *p++ = ' '; + pj_memcpy(p, ses->origin.net_type.ptr, ses->origin.net_type.slen); + p += ses->origin.net_type.slen; + *p++ = ' '; + pj_memcpy(p, ses->origin.addr_type.ptr, ses->origin.addr_type.slen); + p += ses->origin.addr_type.slen; + *p++ = ' '; + pj_memcpy(p, ses->origin.addr.ptr, ses->origin.addr.slen); + p += ses->origin.addr.slen; + *p++ = '\r'; + *p++ = '\n'; + + /* Session name (s=) line. */ + if ((end-p) < 8+ses->name.slen) { + return -1; + } + *p++ = 's'; + *p++ = '='; + pj_memcpy(p, ses->name.ptr, ses->name.slen); + p += ses->name.slen; + *p++ = '\r'; + *p++ = '\n'; + + /* Connection line (c=) if exist. */ + if (ses->conn) { + printed = print_connection_info(ses->conn, p, end-p); + if (printed < 1) { + return -1; + } + p += printed; + } + + + /* Time */ + if ((end-p) < 24) { + return -1; + } + *p++ = 't'; + *p++ = '='; + printed = pj_utoa(ses->time.start, p); + p += printed; + *p++ = ' '; + printed = pj_utoa(ses->time.stop, p); + p += printed; + *p++ = '\r'; + *p++ = '\n'; + + /* Print all attribute (a=) lines. */ + for (i=0; i<ses->attr_count; ++i) { + printed = print_attr(ses->attr[i], p, end-p); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* Print media (m=) lines. */ + for (i=0; i<ses->media_count; ++i) { + printed = print_media_desc(ses->media[i], p, end-p); + if (printed < 0) { + return -1; + } + p += printed; + } + + return p-buf; +} + +/****************************************************************************** + * PARSERS + */ + +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, 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); + + /* username. */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.user); + pj_scan_get_char(scanner); + + /* id */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->origin.id = pj_strtoul(&str); + pj_scan_get_char(scanner); + + /* version */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->origin.version = pj_strtoul(&str); + pj_scan_get_char(scanner); + + /* network-type */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.net_type); + pj_scan_get_char(scanner); + + /* addr-type */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.addr_type); + pj_scan_get_char(scanner); + + /* address */ + pj_scan_get_until_ch(scanner, '\r', &ses->origin.addr); + + /* newline */ + pj_scan_get_newline(scanner); +} + +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); + + /* start time */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->time.start = pj_strtoul(&str); + + pj_scan_get_char(scanner); + + /* stop time */ + pj_scan_get_until_ch(scanner, '\r', &str); + ses->time.stop = pj_strtoul(&str); + + /* newline */ + pj_scan_get_newline(scanner); +} + +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); + + /* get anything until newline. */ + pj_scan_get_until_ch(scanner, '\r', str); + + /* newline. */ + pj_scan_get_newline(scanner); +} + +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); + + /* network-type */ + pj_scan_get_until_ch(scanner, ' ', &conn->net_type); + pj_scan_get_char(scanner); + + /* addr-type */ + pj_scan_get_until_ch(scanner, ' ', &conn->addr_type); + pj_scan_get_char(scanner); + + /* address. */ + pj_scan_get_until_ch(scanner, '\r', &conn->addr); + + /* newline */ + pj_scan_get_newline(scanner); +} + +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); + + /* type */ + pj_scan_get_until_ch(scanner, ' ', &med->desc.media); + pj_scan_get_char(scanner); + + /* port */ + pj_scan_get(scanner, &cs_token, &str); + med->desc.port = (unsigned short)pj_strtoul(&str); + if (*scanner->curptr == '/') { + /* port count */ + pj_scan_get_char(scanner); + pj_scan_get(scanner, &cs_token, &str); + med->desc.port_count = pj_strtoul(&str); + + } else { + med->desc.port_count = 0; + } + + if (pj_scan_get_char(scanner) != ' ') { + PJ_THROW(SYNTAX_ERROR); + } + + /* transport */ + pj_scan_get_until_ch(scanner, ' ', &med->desc.transport); + + /* format list */ + med->desc.fmt_count = 0; + while (*scanner->curptr == ' ') { + pj_scan_get_char(scanner); + pj_scan_get(scanner, &cs_token, &med->desc.fmt[med->desc.fmt_count++]); + } + + /* newline */ + pj_scan_get_newline(scanner); +} + +static void on_scanner_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + + PJ_THROW(SYNTAX_ERROR); +} + +static pjmedia_sdp_attr *parse_attr( pj_pool_t *pool, pj_scanner *scanner, + parse_context *ctx) +{ + pjmedia_sdp_attr *attr; + + ctx->last_error = PJMEDIA_SDP_EINATTR; + + 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, &attr->name); + + 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; + } + + /* newline */ + pj_scan_get_newline(scanner); + + return attr; +} + +/* + * Parse SDP message. + */ +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; + pjmedia_sdp_session *session; + pjmedia_sdp_media *media = NULL; + void *attr; + 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(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, &ctx); + if (attr) { + if (media) { + media->attr[media->attr_count++] = attr; + } else { + session->attr[session->attr_count++] = attr; + } + } + break; + case 'o': + parse_origin(&scanner, session, &ctx); + break; + case 's': + parse_generic_line(&scanner, &session->name, &ctx); + break; + case 'c': + conn = pj_pool_calloc(pool, 1, sizeof(*conn)); + parse_connection_info(&scanner, conn, &ctx); + if (media) { + media->conn = conn; + } else { + session->conn = conn; + } + break; + case 't': + parse_time(&scanner, session, &ctx); + break; + case 'm': + media = pj_pool_calloc(pool, 1, sizeof(*media)); + parse_media(&scanner, media, &ctx); + session->media[ session->media_count++ ] = media; + break; + case 'v': + parse_version(&scanner, &ctx); + break; + default: + parse_generic_line(&scanner, &dummy, &ctx); + break; + } + } + + ctx.last_error = PJ_SUCCESS; + + } + PJ_CATCH(SYNTAX_ERROR) { + + char errmsg[PJ_ERR_MSG_SIZE]; + pj_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); + + *p_sdp = session; + return ctx.last_error; +} + +/* + * Print SDP description. + */ +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; + const pj_str_t STR_RTPMAP = { "rtpmap", 6 }; + + 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) { + + /* Arrgh noo!! Payload type can be non-numeric!! + * RTC based programs sends "null" for instant messaging! + */ + if (pj_isdigit(*m->desc.fmt[j].ptr)) { + 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_attr(m, &STR_RTPMAP, + &m->desc.fmt[j]); + CHECK( a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP); + } + } + } + } + + /* Looks good. */ + return PJ_SUCCESS; +} + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp_cmp.c ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp_cmp.c Mon Jul 3 22:03:31 2006 @@ -0,0 +1,292 @@ +/* $Id: sdp_cmp.c 295 2006-03-06 13:58:54Z bennylp $ */ +/* + * 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, + pjmedia_sdp_attr *const attr1[], + unsigned count2, + 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, + pjmedia_sdp_attr *const attr1[], + unsigned count2, + 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; +} + + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp_neg.c ============================================================================== --- (empty file) +++ freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/pjsdp/src/sdp_neg.c Mon Jul 3 22:03:31 2006 @@ -0,0 +1,988 @@ +/* $Id: sdp_neg.c 527 2006-06-19 14:48:06Z bennylp $ */ +/* + * 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; + pj_bool_t answer_was_remote; + + 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. */ +}; + +static const char *state_str[] = +{ + "STATE_NULL", + "STATE_LOCAL_OFFER", + "STATE_REMOTE_OFFER", + "STATE_WAIT_NEGO", + "STATE_DONE", +}; + +/* + * Get string representation of negotiator state. + */ +PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state) +{ + if (state >=0 && state < PJ_ARRAY_SIZE(state_str)) + return state_str[state]; + + return "<?UNKNOWN?>"; +} + + +/* + * 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 *initial, + 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 && remote && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate remote offer and initial answer */ + status = pjmedia_sdp_validate(remote); + if (status != PJ_SUCCESS) + return status; + + /* Create and initialize negotiator. */ + neg = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_neg)); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + if (initial) { + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS, + status); + + neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial); + + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + + } else { + + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + + } + + *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_active_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_active_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; +} + + +PJ_DEF(pj_bool_t) +pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_FALSE); + + return neg->answer_was_remote; +} + + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG); + + *remote = neg->neg_remote_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG); + + *local = neg->neg_local_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_send_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_set_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 = PJ_TRUE; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_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); + + /* State now is STATE_REMOTE_OFFER. */ + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_local_answer( 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_REMOTE_OFFER. + * 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_REMOTE_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + /* State now is STATE_WAIT_NEGO. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + if (local) + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + else { + PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg) +{ + pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO); + return !neg->has_remote_answer; +} + + +/* 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 pt; + pj_uint32_t j; + pj_str_t *fmt = &offer->desc.fmt[i]; + + + /* Find matching answer */ + pt = pj_strtoul(fmt); + + if (pt < 96) { + for (j=0; j<answer->desc.fmt_count; ++j) { + if (pj_strcmp(fmt, &answer->desc.fmt[j])==0) + 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; + + /* Get the rtpmap for the payload type in the offer. */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(a, &or); + + /* Find paylaod in answer SDP with matching + * encoding name and clock rate. + */ + for (j=0; j<answer->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", + &answer->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap ar; + pjmedia_sdp_attr_get_rtpmap(a, &ar); + + /* See if encoding name, clock rate, and channel + * count match + */ + if (!pj_strcmp(&or.enc_name, &ar.enc_name) && + or.clock_rate == ar.clock_rate && + pj_strcmp(&or.param, &ar.param)==0) + { + /* Match! */ + 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_bool_t has_active = PJ_FALSE; + 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; + + if (offer->media[mi]->desc.port != 0) + has_active = PJ_TRUE; + } + + *p_active = offer; + + return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA; +} + +/* 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[PJMEDIA_MAX_SDP_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 and clock rate. + */ + 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); + + /* See if encoding name, clock rate, and + * channel count match + */ + if (!pj_strcmp(&or.enc_name, &lr.enc_name) && + or.clock_rate == lr.clock_rate && + pj_strcmp(&or.param, &lr.param)==0) + { + /* 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 1 + 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; + } +#else + PJ_TODO(FULL_MATCHING_WITH_TELEPHONE_EVENTS); + if (!found_matching_codec && + !found_matching_telephone_event && + !found_matching_other) + { + /* Some of the payload in the offer has no matching local sdp */ + return PJ_FALSE; + } +#endif + + /* 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; + pj_bool_t has_active = PJ_FALSE; + pjmedia_sdp_session *answer; + char media_used[PJMEDIA_MAX_SDP_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; + + /* Check if this media is active.*/ + if (am->desc.port != 0) + has_active = PJ_TRUE; + } + + *p_answer = answer; + + return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA; + //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 = NULL; + + status = create_answer(pool, neg->neg_local_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; + + /* Save state */ + neg->answer_was_remote = neg->has_remote_answer; + + /* Clear temporary SDP */ + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + return status; +} + Added: freeswitch/branches/stkn/src/mod/endpoints/mod_pjsip/pjsdp/user.mak ==============================================================================