diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index b6eeda3483..d72ce3b725 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -666,6 +666,40 @@ static void on_buddy_state(pjsua_buddy_id buddy_id) } +/* + * Handler on buddy dialog event state changed. + */ +static void on_buddy_dlg_event_state(pjsua_buddy_id buddy_id) +{ + pjsua_buddy_dlg_event_info info; + pjsua_buddy_get_dlg_event_info(buddy_id, &info); + + PJ_LOG(3,(THIS_FILE, "%.*s dialog-info-state: %.*s, " + "dialog-info-entity: %.*s, dialog-id: %.*s, " + "dialog-call-id: %.*s, dialog-direction: %.*s, " + "dialog-state: %.*s, dialog-duration: %.*s, " + "local-identity: %.*s, local-target-uri: %.*s, " + "remote-identity: %.*s, remote-target-uri: %.*s, " + "dialog-local-tag: %.*s, dialog-remote-tag: %.*s, " + "subscription state: %s (last termination reason code=%d %.*s)", + (int)info.uri.slen, info.uri.ptr, + (int)info.dialog_info_state.slen, info.dialog_info_state.ptr, + (int)info.dialog_info_entity.slen, info.dialog_info_entity.ptr, + (int)info.dialog_id.slen, info.dialog_id.ptr, + (int)info.dialog_call_id.slen, info.dialog_call_id.ptr, + (int)info.dialog_direction.slen, info.dialog_direction.ptr, + (int)info.dialog_state.slen, info.dialog_state.ptr, + (int)info.dialog_duration.slen, info.dialog_duration.ptr, + (int)info.local_identity.slen, info.local_identity.ptr, + (int)info.local_target_uri.slen, info.local_target_uri.ptr, + (int)info.remote_identity.slen, info.remote_identity.ptr, + (int)info.remote_target_uri.slen, info.remote_target_uri.ptr, + (int)info.dialog_local_tag.slen, info.dialog_local_tag.ptr, + (int)info.dialog_remote_tag.slen, info.dialog_remote_tag.ptr, + info.sub_state_name, info.sub_term_code, + (int)info.sub_term_reason.slen, info.sub_term_reason.ptr)); +} + /* * Subscription state has changed. */ @@ -696,11 +730,36 @@ static void on_buddy_evsub_state(pjsua_buddy_id buddy_id, } +static void on_buddy_evsub_dlg_event_state(pjsua_buddy_id buddy_id, + pjsip_evsub *sub, + pjsip_event *event) +{ + char event_info[80]; + + PJ_UNUSED_ARG(sub); + + event_info[0] = '\0'; + + if (event->type == PJSIP_EVENT_TSX_STATE && + event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; + snprintf(event_info, sizeof(event_info), + " (RX %s)", + pjsip_rx_data_get_info(rdata)); + } + + PJ_LOG(4,(THIS_FILE, + "Buddy %d: dialog event subscription state: %s (event: %s%s)", + buddy_id, pjsip_evsub_get_state_name(sub), + pjsip_event_str(event->type), event_info)); +} + /** * Incoming IM message (i.e. MESSAGE request)! */ -static void on_pager(pjsua_call_id call_id, const pj_str_t *from, +static void on_pager(pjsua_call_id call_id, const pj_str_t *from, const pj_str_t *to, const pj_str_t *contact, const pj_str_t *mime_type, const pj_str_t *text) { @@ -1391,7 +1450,10 @@ static pj_status_t app_init(void) app_config.cfg.cb.on_reg_state = &on_reg_state; app_config.cfg.cb.on_incoming_subscribe = &on_incoming_subscribe; app_config.cfg.cb.on_buddy_state = &on_buddy_state; + app_config.cfg.cb.on_buddy_dlg_event_state = &on_buddy_dlg_event_state; app_config.cfg.cb.on_buddy_evsub_state = &on_buddy_evsub_state; + app_config.cfg.cb.on_buddy_evsub_dlg_event_state = + &on_buddy_evsub_dlg_event_state; app_config.cfg.cb.on_pager = &on_pager; app_config.cfg.cb.on_typing = &on_typing; app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status; diff --git a/pjsip-apps/src/pjsua/pjsua_app_legacy.c b/pjsip-apps/src/pjsua/pjsua_app_legacy.c index 0559827007..16184a74b9 100644 --- a/pjsip-apps/src/pjsua/pjsua_app_legacy.c +++ b/pjsip-apps/src/pjsua/pjsua_app_legacy.c @@ -236,6 +236,8 @@ static void keystroke_help() puts("| a Answer call | i Send IM | !a Modify accnt. |"); puts("| h Hangup call (ha=all) | s Subscribe presence | rr (Re-)register |"); puts("| H Hold call | u Unsubscribe presence | ru Unregister |"); + puts("| | D Subscribe dlg event | |"); + puts("| | Du Unsub dlg event | |"); puts("| v re-inVite (release hold) | t Toggle online status | > Cycle next ac.|"); puts("| U send UPDATE | T Set online status | < Cycle prev ac.|"); puts("| ],[ Select next/prev call +--------------------------+-------------------+"); @@ -991,7 +993,10 @@ static void ui_add_buddy() pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config)); buddy_cfg.uri = pj_str(buf); - buddy_cfg.subscribe = PJ_TRUE; + /* Only one subscription can be active, so we need to disable this + * to allow user to choose between presence or dialog event. + */ + // buddy_cfg.subscribe = PJ_TRUE; status = pjsua_buddy_add(&buddy_cfg, &buddy_id); if (status == PJ_SUCCESS) { @@ -1496,6 +1501,32 @@ static void ui_subscribe(char menuin[]) } } +static void ui_subscribe_dlg_event(pj_bool_t sub) +{ + char buf[128]; + input_result result; + + ui_input_url("(un)Subscribe dialog event of", buf, sizeof(buf), &result, + PJ_TRUE); + if (result.nb_result != PJSUA_APP_NO_NB) { + if (result.nb_result == -1) { + int i, count; + count = pjsua_get_buddy_count(); + for (i=0; i + + @@ -687,6 +689,8 @@ + + diff --git a/pjsip/build/pjsip_simple.vcxproj.filters b/pjsip/build/pjsip_simple.vcxproj.filters index a7e04b8f9c..c0730d21d4 100644 --- a/pjsip/build/pjsip_simple.vcxproj.filters +++ b/pjsip/build/pjsip_simple.vcxproj.filters @@ -44,6 +44,12 @@ Source Files + + Source Files + + + Source Files + @@ -82,5 +88,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/pjsip/include/pjsip-simple/dialog_info.h b/pjsip/include/pjsip-simple/dialog_info.h new file mode 100644 index 0000000000..e18e1572ba --- /dev/null +++ b/pjsip/include/pjsip-simple/dialog_info.h @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2024 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2013 Maxim Kondratenko + * + * 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 __PJSIP_SIMPLE_DIALOG_INFO_H__ +#define __PJSIP_SIMPLE_DIALOG_INFO_H__ + +/** + * @file dialog_info.h + * @brief Dialog-Info Information Data Format (RFC 4235) + */ +#include +#include + +PJ_BEGIN_DECL + + +/** + * @defgroup PJSIP_SIMPLE_DIALOG_INFO Dialog-Info Data Format (RFC 4235) + * @ingroup PJSIP_SIMPLE + * @brief Support for Dialog-Info Information Data Format (RFC 4235) + * @{ + * + * This file provides tools for manipulating Dialog-Info Information Data + * Format as described in RFC 4235. + */ +typedef struct pj_xml_node pjsip_dlg_info_dialog_info; +typedef struct pj_xml_node pjsip_dlg_info_dialog; +typedef struct pj_xml_node pjsip_dlg_info_local; +typedef struct pj_xml_node pjsip_dlg_info_remote; + + +typedef struct pjsip_dlg_info_local_op +{ + void (*construct)(pj_pool_t *, pjsip_dlg_info_local *); + + const pj_str_t * (*get_identity)(const pjsip_dlg_info_local *); + void (*set_identity)(pj_pool_t *pool, const pjsip_dlg_info_local *, + const pj_str_t *); + + const pj_str_t * (*get_identity_display)(const pjsip_dlg_info_local*); + void (*set_identity_display)(pj_pool_t *pool, const pjsip_dlg_info_local *, + const pj_str_t *); + + const pj_str_t * (*get_target_uri)(const pjsip_dlg_info_local*); + void (*set_target_uri)(pj_pool_t *pool, const pjsip_dlg_info_local *, + const pj_str_t *); +} pjsip_dlg_info_local_op; + +typedef struct pjsip_dlg_info_remote_op +{ + void (*construct)(pj_pool_t *, pjsip_dlg_info_remote *); + + const pj_str_t * (*get_identity)(const pjsip_dlg_info_remote *); + void (*set_identity)(pj_pool_t *pool, const pjsip_dlg_info_remote *, + const pj_str_t *); + + const pj_str_t * (*get_identity_display)(const pjsip_dlg_info_remote*); + void (*set_identity_display)(pj_pool_t *pool, const pjsip_dlg_info_remote*, + const pj_str_t *); + + const pj_str_t * (*get_target_uri)(const pjsip_dlg_info_remote*); + void (*set_target_uri)(pj_pool_t *pool, const pjsip_dlg_info_remote *, + const pj_str_t *); +} pjsip_dlg_info_remote_op; + +typedef struct pjsip_dlg_info_dialog_op +{ + void (*construct)(pj_pool_t *, pjsip_dlg_info_dialog *, const pj_str_t *); + + const pj_str_t * (*get_id)(const pjsip_dlg_info_dialog *); + void (*set_id)(pj_pool_t *pool, const pjsip_dlg_info_dialog *, + const pj_str_t *); + + const pj_str_t * (*get_call_id)(const pjsip_dlg_info_dialog *); + void (*set_call_id)(pj_pool_t *pool, const pjsip_dlg_info_dialog *, + const pj_str_t *); + + const pj_str_t * (*get_remote_tag)(const pjsip_dlg_info_dialog *); + void (*set_remote_tag)(pj_pool_t *pool, const pjsip_dlg_info_dialog *, + const pj_str_t *); + + const pj_str_t * (*get_local_tag)(const pjsip_dlg_info_dialog *); + void (*set_local_tag)(pj_pool_t *pool, const pjsip_dlg_info_dialog *, + const pj_str_t *); + + const pj_str_t * (*get_direction)(const pjsip_dlg_info_dialog *); + void (*set_direction)(pj_pool_t *pool, const pjsip_dlg_info_dialog *, + const pj_str_t *); + + const pj_str_t * (*get_state)(const pjsip_dlg_info_dialog *); + void (*set_state)(pj_pool_t *pool, const pjsip_dlg_info_dialog *, + const pj_str_t *); + + const pj_str_t * (*get_duration)(const pjsip_dlg_info_dialog *); + void (*set_duration)(pj_pool_t *pool, const pjsip_dlg_info_dialog *, + const pj_str_t *); + + pjsip_dlg_info_local* (*get_local)(const pjsip_dlg_info_dialog *); + pjsip_dlg_info_local* (*set_local)(pj_pool_t *pool, + const pjsip_dlg_info_dialog *, + const pjsip_dlg_info_local *); + + pjsip_dlg_info_remote* (*get_remote)(const pjsip_dlg_info_dialog *); + pjsip_dlg_info_remote* (*set_remote)(pj_pool_t *pool, + const pjsip_dlg_info_dialog *, + const pjsip_dlg_info_remote *); +} pjsip_dlg_info_dialog_op; + +typedef struct pjsip_dlg_info_dialog_info_op +{ + void (*construct)(pj_pool_t *, pjsip_dlg_info_dialog_info *, + const pj_str_t *, const pj_str_t *, const pj_str_t *); + + const pj_str_t * (*get_state)(const pjsip_dlg_info_dialog_info *); + void (*set_state)(pj_pool_t *pool, const pjsip_dlg_info_dialog_info *, + const pj_str_t *); + + const pj_str_t * (*get_version)(const pjsip_dlg_info_dialog_info *); + void (*set_version)(pj_pool_t *pool, const pjsip_dlg_info_dialog_info *, + const pj_str_t *); + + const pj_str_t * (*get_entity)(const pjsip_dlg_info_dialog_info *); + void (*set_entity)(pj_pool_t *pool, const pjsip_dlg_info_dialog_info *, + const pj_str_t *); + + pjsip_dlg_info_dialog* (*get_dialog)(const pjsip_dlg_info_dialog_info *); + void (*set_dialog)(pj_pool_t *pool, const pjsip_dlg_info_dialog_info *, + const pjsip_dlg_info_dialog * ); +} pjsip_dlg_info_dialog_info_op; + + +/****************************************************************************** + * Top level API for managing dialog-info document. + *****************************************************************************/ +PJ_DECL(pjsip_dlg_info_dialog_info *) +pjsip_dlg_info_create(pj_pool_t *pool, const pj_str_t *version, + const pj_str_t *state, const pj_str_t *entity); + +PJ_DECL(pjsip_dlg_info_dialog_info *) +pjsip_dlg_info_parse(pj_pool_t *pool, char *text, int len); + +PJ_DECL(int) +pjsip_dlg_info_print(const pjsip_dlg_info_dialog_info *dialog_info, + char *buf, int len); + +/****************************************************************************** + * API for managing Dialog-info node. + *****************************************************************************/ +PJ_DECL(void) +pjsip_dlg_info_dialog_info_construct(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *version, + const pj_str_t *state, + const pj_str_t *entity); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_info_get_state(pjsip_dlg_info_dialog_info *dialog_info); + +PJ_DECL(void) +pjsip_dlg_info_dialog_info_set_state(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *state); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_info_get_version(pjsip_dlg_info_dialog_info *dlg_info); + +PJ_DECL(void) +pjsip_dlg_info_dialog_info_set_version(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *version); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_info_get_entity(pjsip_dlg_info_dialog_info *dialog_info); + +PJ_DECL(void) +pjsip_dlg_info_dialog_info_set_entity(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *entity); + +PJ_DECL(pjsip_dlg_info_dialog *) +pjsip_dlg_info_dialog_info_add_dialog(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *id); + +PJ_DECL(pjsip_dlg_info_dialog *) +pjsip_dlg_info_dialog_info_get_dialog(pjsip_dlg_info_dialog_info *dialog_info); + +/****************************************************************************** + * API for managing Dialog node. + *****************************************************************************/ +PJ_DECL(void) +pjsip_dlg_info_dialog_construct(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *id); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_get_id(const pjsip_dlg_info_dialog *dialog); + +PJ_DECL(void) +pjsip_dlg_info_dialog_set_id(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *id); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_get_call_id(const pjsip_dlg_info_dialog *dialog); + +PJ_DECL(void) +pjsip_dlg_info_dialog_set_call_id(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *call_id); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_get_remote_tag(const pjsip_dlg_info_dialog *dialog); + +PJ_DECL(void) +pjsip_dlg_info_dialog_set_remote_tag(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *remote_tag); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_get_local_tag(const pjsip_dlg_info_dialog *dialog); +PJ_DECL(void) +pjsip_dlg_info_dialog_set_local_tag(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *local_tag); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_get_direction(const pjsip_dlg_info_dialog *dialog); + +PJ_DECL(void) +pjsip_dlg_info_dialog_set_direction(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *direction); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_get_state(pjsip_dlg_info_dialog *dialog); + +PJ_DECL(void) +pjsip_dlg_info_dialog_set_state(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *state); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_dialog_get_duration(pjsip_dlg_info_dialog *dialog); + +PJ_DECL(void) +pjsip_dlg_info_dialog_set_duration(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *duration); + +PJ_DECL(pjsip_dlg_info_local *) +pjsip_dlg_info_dialog_get_local(pjsip_dlg_info_dialog *dialog); + +PJ_DECL(pjsip_dlg_info_local *) +pjsip_dlg_info_dialog_add_local(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog); + +PJ_DECL(pjsip_dlg_info_remote *) +pjsip_dlg_info_dialog_get_remote(pjsip_dlg_info_dialog *dialog); + +PJ_DECL(pjsip_dlg_info_remote *) +pjsip_dlg_info_dialog_add_remote(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog); + +/****************************************************************************** + * API for managing Local node. + *****************************************************************************/ +PJ_DECL(void) + pjsip_dlg_info_local_construct(pj_pool_t *pool, + pjsip_dlg_info_local *local); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_local_get_identity(const pjsip_dlg_info_local *local); + +PJ_DECL(void) +pjsip_dlg_info_local_add_identity(pj_pool_t *pool, + pjsip_dlg_info_local *local, + const pj_str_t *identity); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_local_get_identity_display(const pjsip_dlg_info_local *local); + +PJ_DECL(void) +pjsip_dlg_info_local_set_identity_display(pj_pool_t *pool, + pjsip_dlg_info_local *local, + const pj_str_t *identity_display); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_local_get_target_uri(const pjsip_dlg_info_local *local); + +PJ_DECL(void) +pjsip_dlg_info_local_set_target_uri(pj_pool_t *pool, + pjsip_dlg_info_local *local, + const pj_str_t *target_uri); + +/****************************************************************************** + * API for managing Remote node. + *****************************************************************************/ +PJ_DECL(void) +pjsip_dlg_info_remote_construct(pj_pool_t *pool, + pjsip_dlg_info_remote *remote); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_remote_get_identity(const pjsip_dlg_info_remote *remote); + +PJ_DECL(void) +pjsip_dlg_info_remote_add_identity(pj_pool_t *pool, + pjsip_dlg_info_remote *remote, + const pj_str_t *identity); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_remote_get_identity_display(const pjsip_dlg_info_remote *remote); + +PJ_DECL(void) +pjsip_dlg_info_remote_set_identity_display(pj_pool_t *pool, + pjsip_dlg_info_remote *remote, + const pj_str_t *identity_display); + +PJ_DECL(const pj_str_t *) +pjsip_dlg_info_remote_get_target_uri(const pjsip_dlg_info_remote *remote); + +PJ_DECL(void) +pjsip_dlg_info_remote_set_target_uri(pj_pool_t *pool, + pjsip_dlg_info_remote *remote, + const pj_str_t *target_uri); + +PJ_END_DECL + + +#endif /* __PJSIP_SIMPLE_DIALOG_INFO_H__ */ diff --git a/pjsip/include/pjsip-simple/dlg_event.h b/pjsip/include/pjsip-simple/dlg_event.h new file mode 100644 index 0000000000..5f66a8a3a2 --- /dev/null +++ b/pjsip/include/pjsip-simple/dlg_event.h @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2024 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2013 Maxim Kondratenko + * + * 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 __PJSIP_SIMPLE_DLG_EVENT_H__ +#define __PJSIP_SIMPLE_DLG_EVENT_H__ + +/** + * @file dlg_event.h + * @brief SIP Extension for INVITE-Initiated Dialog Event (RFC 4235) + */ +#include +#include + +PJ_BEGIN_DECL + + +/** + * @defgroup PJSIP_SIMPLE_DLG_EVENT SIP Extension for Dialog Event (RFC 4235) + * @ingroup PJSIP_SIMPLE + * @brief Support for SIP Extension for Dialog Event (RFC 4235) + * @{ + * + * This module contains the implementation of INVITE-Initiated Dialog Event + * Package as described in RFC 4235. It uses the SIP Event Notification + * framework (evsub.h) and extends the framework by implementing + * "dialog-info+xml" event package. This could be useful for features such as + * Busy Lamp Field (BLF). + */ + + +/** + * Initialize the dialog event module and register it as endpoint module and + * package to the event subscription module. + * + * @param endpt The endpoint instance. + * @param mod_evsub The event subscription module instance. + * + * @return PJ_SUCCESS if the module is successfully + * initialized and registered to both endpoint + * and the event subscription module. + */ +PJ_DECL(pj_status_t) pjsip_dlg_event_init_module(pjsip_endpoint *endpt, + pjsip_module *mod_evsub); + + +/** + * Get the dialog event module instance. + * + * @return The dialog event module instance. + */ +PJ_DECL(pjsip_module*) pjsip_bdlg_event_instance(void); + + +/** + * Maximum dialog event status info items which can handled by application. + * + */ +#define PJSIP_DLG_EVENT_STATUS_MAX_INFO 8 + + +/** + * This structure describes dialog event status of an entity. + */ +struct pjsip_dlg_event_status +{ + unsigned info_cnt; /**< Number of info in the status */ + struct { + + pj_str_t dialog_info_state; /**< Dialog-Info state */ + pj_str_t dialog_info_entity; /**< Dialog-Info entity */ + pj_str_t dialog_call_id; /**< Dialog's call_id */ + pj_str_t dialog_remote_tag; /**< Dialog's remote-tag */ + pj_str_t dialog_local_tag; /**< Dialog's local-tag */ + pj_str_t dialog_direction; /**< Dialog's direction */ + pj_str_t dialog_id; /**< Dialog's id */ + pj_str_t dialog_state; /**< Dialog state */ + pj_str_t dialog_duration; /**< Dialog duration */ + + pj_xml_node *dialog_node; /**< Pointer to tuple XML node of + parsed dialog-info body received + from remote agent. Only valid + for client subscription. If the + last received NOTIFY request + does not contain any dialog-info + body, this will be set to NULL*/ + pj_str_t local_identity; + pj_str_t local_identity_display; + pj_str_t local_target_uri; + + pj_str_t remote_identity; + pj_str_t remote_identity_display; + pj_str_t remote_target_uri; + + } info[PJSIP_DLG_EVENT_STATUS_MAX_INFO]; /**< Array of info. */ +}; + + +/** + * @see pjsip_dlg_event_status + */ +typedef struct pjsip_dlg_event_status pjsip_dlg_event_status; + + +/** + * Create dialog event client subscription session. + * + * @param dlg The underlying dialog to use. + * @param user_cb Pointer to callbacks to receive dialog events. + * @param options Option flags. Currently only PJSIP_EVSUB_NO_EVENT_ID + * is recognized. + * @param p_evsub Pointer to receive the dialog event subscription + * session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjsip_dlg_event_create_uac(pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + unsigned options, + pjsip_evsub **p_evsub ); + + +/** + * Forcefully destroy the dialog event subscription. This function should only + * be called on special condition, such as when the subscription + * initialization has failed. For other conditions, application MUST terminate + * the subscription by sending the appropriate un(SUBSCRIBE) or NOTIFY. + * + * @param sub The dialog event subscription. + * @param notify Specify whether the state notification callback + * should be called. + * + * @return PJ_SUCCESS if subscription session has been destroyed. + */ +PJ_DECL(pj_status_t) pjsip_dlg_event_terminate(pjsip_evsub *sub, + pj_bool_t notify ); + + + +/** + * Call this function to create request to initiate dialog event subscription, + * to refresh subcription, or to request subscription termination. + * + * @param sub Client subscription instance. + * @param expires Subscription expiration. If the value is set to zero, + * this will request unsubscription. + * @param p_tdata Pointer to receive the request. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_dlg_event_initiate(pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata); + + +/** + * Add a list of headers to the subscription instance. The list of headers + * will be added to outgoing dialog event subscription requests. + * + * @param sub Subscription instance. + * @param hdr_list List of headers to be added. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_dlg_event_add_header(pjsip_evsub *sub, + const pjsip_hdr *hdr_list); + + +/** + * Accept the incoming subscription request by sending 2xx response to + * incoming SUBSCRIBE request. + * + * @param sub Server subscription instance. + * @param rdata The incoming subscription request message. + * @param st_code Status code, which MUST be final response. + * @param hdr_list Optional list of headers to be added in the response. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_dlg_event_accept(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ); + +/** + * Send request message. Application may also send request created with other + * functions, e.g. authentication. But the request MUST be either request + * that creates/refresh subscription or NOTIFY request. + * + * @param sub The subscription object. + * @param tdata Request message to be sent. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_dlg_event_send_request(pjsip_evsub *sub, + pjsip_tx_data *tdata); + + +/** + * Get the dialog event status. Client normally would call this function + * after receiving NOTIFY request from server. + * + * @param sub The client or server subscription. + * @param status The structure to receive dialog event status. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjsip_dlg_event_get_status(pjsip_evsub *sub, pjsip_dlg_event_status *status); + + +/** + * This is a utility function to parse XML body into PJSIP dialog event status. + * + * @param rdata The incoming SIP message containing the XML body. + * @param pool Pool to allocate memory to copy the strings into + * the dialog event status structure. + * @param status The dialog event status to be initialized. + * + * @return PJ_SUCCESS on success. + * + * @see pjsip_dlg_event_parse_dialog_info() + */ +PJ_DECL(pj_status_t) +pjsip_dlg_event_parse_dialog_info(pjsip_rx_data *rdata, + pj_pool_t *pool, + pjsip_dlg_event_status *dlg_status); + + +/** + * This is a utility function to parse XML body into PJSIP dialog event status. + * + * @param body Text body, with one extra space at the end to place + * NULL character temporarily during parsing. + * @param body_len Length of the body, not including the NULL termination + * character. + * @param pool Pool to allocate memory to copy the strings into + * the dialog event status structure. + * @param status The dialog event status to be initialized. + * + * @return PJ_SUCCESS on success. + * + * @see pjsip_dlg_event_parse_dialog_info2() + */ +PJ_DECL(pj_status_t) +pjsip_dlg_event_parse_dialog_info2(char *body, unsigned body_len, + pj_pool_t *pool, + pjsip_dlg_event_status *dlg_status); + + +/** + * @} + */ + +PJ_END_DECL + + +#endif /* __PJSIP_SIMPLE_DLG_EVENT_H__ */ diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index dd55c11a9e..ca7999bf21 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -932,6 +932,14 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void) # endif #endif +/** + * Specify the default expiration time for dialog event subscription. + * + * Default: 600 seconds (10 minutes) + */ +#ifndef PJSIP_DLG_EVENT_DEFAULT_EXPIRES +# define PJSIP_DLG_EVENT_DEFAULT_EXPIRES 600 +#endif /** * Specify the maximum number of timer entries initially allocated by @@ -1418,6 +1426,17 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void) #endif +/** + * Specify the status code value to respond to bad message body in NOTIFY + * request for dialog event. + * + * Default: 488 (Not Acceptable Here) + */ +#ifndef PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE +# define PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE 488 +#endif + + /** * Add "timestamp" information in generated PIDF document for both server * subscription and presence publication. diff --git a/pjsip/include/pjsip_simple.h b/pjsip/include/pjsip_simple.h index 95b1cea91d..e1b776383a 100644 --- a/pjsip/include/pjsip_simple.h +++ b/pjsip/include/pjsip_simple.h @@ -33,6 +33,7 @@ #ifndef __PJSIP_SIMPLE_H__ #define __PJSIP_SIMPLE_H__ +#include #include #include #include diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 0876fbdd55..d241822e5d 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -1649,6 +1649,13 @@ typedef struct pjsua_callback */ void (*on_buddy_state)(pjsua_buddy_id buddy_id); + /** + * Notify application when the buddy dialog state has changed. + * Application may then query the buddy into to get the details. + * + * @param buddy_id The buddy id. + */ + void (*on_buddy_dlg_event_state)(pjsua_buddy_id buddy_id); /** * Notify application when the state of client subscription session @@ -1664,6 +1671,20 @@ typedef struct pjsua_callback pjsip_evsub *sub, pjsip_event *event); + /** + * Notify application when the state of client subscription session + * associated with a buddy dialog state has changed. Application + * may use this callback to retrieve more detailed information about the + * state changed event. + * + * @param buddy_id The buddy id. + * @param sub Event subscription session. + * @param event The event which triggers state change event. + */ + void (*on_buddy_evsub_dlg_event_state)(pjsua_buddy_id buddy_id, + pjsip_evsub *sub, + pjsip_event *event); + /** * Notify application on incoming pager (i.e. MESSAGE request). * Argument call_id will be -1 if MESSAGE request is not related to an @@ -6408,7 +6429,14 @@ pjsua_call_get_med_transport_info(pjsua_call_id call_id, # define PJSUA_PRES_TIMER 300 #endif - +/** + * This specifies the buffer size of pjsua_buddy_dlg_event_info. + * + * Default: 1024 bytes + */ +#ifndef PJSUA_BUDDY_DLG_EVENT_INFO_BUF_SIZE +# define PJSUA_BUDDY_DLG_EVENT_INFO_BUF_SIZE 1024 +#endif /** * This structure describes buddy configuration when adding a buddy to * the buddy list with #pjsua_buddy_add(). Application MUST initialize @@ -6424,9 +6452,19 @@ typedef struct pjsua_buddy_config /** * Specify whether presence subscription should start immediately. + * Note that only one subscription (presence or dialog event) + * can be active at any time. */ pj_bool_t subscribe; + /** + * Specify whether we should immediately subscribe to the buddy's + * dialog event, such as for Busy Lamp Field (BLF) feature. + * Note that only one subscription (presence or dialog event) + * can be active at any time. + */ + pj_bool_t subscribe_dlg_event; + /** * Specify arbitrary application data to be associated with with * the buddy object. @@ -6547,6 +6585,91 @@ typedef struct pjsua_buddy_info } pjsua_buddy_info; +typedef struct pjsua_buddy_dlg_event_info +{ + /** + * The buddy ID. + */ + pjsua_buddy_id id; + + /** + * The full URI of the buddy, as specified in the configuration. + */ + pj_str_t uri; + + /* Dialog event Dialog-Info id */ + pj_str_t dialog_id; + + /* Dialog event Dialog-Info state */ + pj_str_t dialog_info_state; + + /* Dialog event Dialog-Info entity */ + pj_str_t dialog_info_entity; + + /* Dialog event Dialog call_id */ + pj_str_t dialog_call_id; + + /* Dialog event Dialog remote_tag */ + pj_str_t dialog_remote_tag; + + /* Dialog event Dialog local_tag */ + pj_str_t dialog_local_tag; + + /* Dialog event Dialog direction */ + pj_str_t dialog_direction; + + /* Dialog event dialog state */ + pj_str_t dialog_state; + + /* Dialog event dialog duration */ + pj_str_t dialog_duration; + + /* Dialog event local identity */ + pj_str_t local_identity; + + /* Dialog event local identity_display */ + pj_str_t local_identity_display; + + /* Dialog event local target uri */ + pj_str_t local_target_uri; + + /* Dialog event remote identity */ + pj_str_t remote_identity; + + /* Dialog event remote identity_display */ + pj_str_t remote_identity_display; + + /* Dialog event remote target uri */ + pj_str_t remote_target_uri; + + /** + * This specifies the last state of the dialog event subscription. + */ + pjsip_evsub_state sub_state; + + /** + * String representation of subscription state. + */ + const char *sub_state_name; + + /** + * Specifies the last dialog event subscription termination code. + */ + unsigned sub_term_code; + + /** + * Specifies the last dialog event subscription termination reason. If + * presence subscription is currently active, the value will be empty. + */ + pj_str_t sub_term_reason; + + /** + * Internal buffer. + */ + char buf_[PJSUA_BUDDY_DLG_EVENT_INFO_BUF_SIZE]; + +} pjsua_buddy_dlg_event_info; + /** * Set default values to the buddy config. */ @@ -6607,6 +6730,18 @@ PJ_DECL(pjsua_buddy_id) pjsua_buddy_find(const pj_str_t *uri); PJ_DECL(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id buddy_id, pjsua_buddy_info *info); +/** + * Get detailed buddy dialog event info. + * + * @param buddy_id The buddy identification. + * @param info Pointer to receive information about buddy. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pjsua_buddy_get_dlg_event_info(pjsua_buddy_id buddy_id, + pjsua_buddy_dlg_event_info *info); + /** * Set the user data associated with the buddy object. * @@ -6660,6 +6795,9 @@ PJ_DECL(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id); * subscribed, application will be informed about buddy's presence status * changed via \a on_buddy_state() callback. * + * Note that only one subscription (presence or dialog event) can be active + * at any time. + * * @param buddy_id Buddy identification. * @param subscribe Specify non-zero to activate presence subscription to * the specified buddy. @@ -6670,6 +6808,24 @@ PJ_DECL(pj_status_t) pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_id, pj_bool_t subscribe); +/** + * Enable/disable buddy's dialog event monitoring. Once buddy's dialog event + * is subscribed, application will be informed about buddy's dialog info + * status change via \a on_buddy_dlg_event_state() callback. + * + * Note that only one subscription (presence or dialog event) can be active + * at any time. + * + * @param buddy_id Buddy identification. + * @param subscribe Specify non-zero to activate dialog event subscription + * to the specified buddy. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_buddy_subscribe_dlg_event(pjsua_buddy_id buddy_id, + pj_bool_t subscribe); + + /** * Update the presence information for the buddy. Although the library * periodically refreshes the presence subscription for all buddies, some @@ -6693,6 +6849,30 @@ PJ_DECL(pj_status_t) pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_id, PJ_DECL(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id); +/** + * Update the dialog event information for the buddy. Although the library + * periodically refreshes the dialog event subscription for all buddies, some + * application may want to refresh the buddy's dialog event subscription + * immediately, and in this case it can use this function to accomplish + * this. + * + * Note that the buddy's dialog event subscription will only be initiated + * if dialog event monitoring is enabled for the buddy. See + * #pjsua_buddy_subscribe_dlg_event() for more info. Also if dialog event + * subscription for the buddy is already active, this function will not do + * anything. + * + * Once the dialog event subscription is activated successfully for the buddy, + * application will be notified about the buddy's dialog info status in the + * on_buddy_dlg_event_state() callback. + * + * @param buddy_id Buddy identification. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_buddy_update_dlg_event(pjsua_buddy_id buddy_id); + + /** * Send NOTIFY to inform account presence status or to terminate server * side presence subscription. If application wants to reject the incoming diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index d7ee2c68aa..0bc46be985 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -377,10 +377,12 @@ typedef struct pjsua_buddy unsigned port; /**< Buddy port. */ pj_bool_t monitor; /**< Should we monitor? */ pjsip_dialog *dlg; /**< The underlying dialog. */ - pjsip_evsub *sub; /**< Buddy presence subscription */ + pjsip_evsub *sub; /**< Buddy subscription */ + pj_bool_t presence; /**< Presence subscription? */ unsigned term_code; /**< Subscription termination code */ pj_str_t term_reason;/**< Subscription termination reason */ pjsip_pres_status status; /**< Buddy presence status. */ + pjsip_dlg_event_status dlg_ev_status;/**< Buddy dialog event status */ pj_timer_entry timer; /**< Resubscription timer */ } pjsua_buddy; diff --git a/pjsip/src/pjsip-simple/dialog_info.c b/pjsip/src/pjsip-simple/dialog_info.c new file mode 100644 index 0000000000..f5ceafd5b3 --- /dev/null +++ b/pjsip/src/pjsip-simple/dialog_info.c @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2024 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2013 Maxim Kondratenko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +static pj_str_t DIALOG_INFO = { "dialog-info", 11 }; +static pj_str_t VERSION = { "version", 7 }; +static pj_str_t DIALOG = { "dialog", 6}; +static pj_str_t STATE = { "state", 5 }; +static pj_str_t DURATION = { "duration", 8 }; +static pj_str_t ID = { "id", 2 }; +static pj_str_t LOCAL = { "local", 5 }; +static pj_str_t IDENTITY = { "identity", 8 }; +static pj_str_t DISPLAY = { "display", 7 }; +static pj_str_t TARGET = { "target", 6 }; +static pj_str_t URI = { "uri", 3 }; +static pj_str_t REMOTE = { "remote", 6 }; +static pj_str_t CALL_ID = { "call-id", 7 }; +static pj_str_t ENTITY = { "entity", 6 }; +static pj_str_t DIRECTION = { "direction", 9 }; +static pj_str_t REMOTE_TAG = { "remote-tag", 10 }; +static pj_str_t LOCAL_TAG = { "local-tag", 9 }; +static pj_str_t EMPTY_STRING = { NULL, 0 }; + +/* +static pj_str_t XMLNS = { "xmlns", 5 }; +static pj_str_t DIALOG_INFO_XMLNS = {"urn:ietf:params:xml:ns:dialog-info", 34}; +*/ + +static void xml_init_node(pj_pool_t *pool, pj_xml_node *node, + pj_str_t *name, const pj_str_t *value) +{ + pj_list_init(&node->attr_head); + pj_list_init(&node->node_head); + node->name = *name; + if (value) pj_strdup(pool, &node->content, value); + else node->content.ptr=NULL, node->content.slen=0; +} + +static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name, + const pj_str_t *value) +{ + pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr); + attr->name = *name; + pj_strdup(pool, &attr->value, value); + return attr; +} + +/* Remote */ +PJ_DEF(void) pjsip_dlg_info_remote_construct(pj_pool_t *pool, + pjsip_dlg_info_remote *remote) +{ + pj_xml_node *node; + + xml_init_node(pool, remote, &REMOTE, NULL); + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, NULL, NULL); + pj_xml_add_node(remote, node); +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_remote_get_identity(const pjsip_dlg_info_remote *remote) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)remote, &IDENTITY); + if (!node) + return &EMPTY_STRING; + return &node->content; +} + +PJ_DEF(void) pjsip_dlg_info_remote_add_identity(pj_pool_t *pool, + pjsip_dlg_info_remote *remote, + const pj_str_t *identity) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)remote, &IDENTITY); + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &IDENTITY, identity); + pj_xml_add_node(remote, node); + } else { + pj_strdup(pool, &node->content, identity); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_remote_get_identity_display(const pjsip_dlg_info_remote *remote) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)remote, &IDENTITY); + pj_xml_attr *attr; + + if (!node) + return &EMPTY_STRING; + attr = pj_xml_find_attr(node, &DISPLAY, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + +PJ_DEF(void) +pjsip_dlg_info_remote_set_identity_display(pj_pool_t *pool, + pjsip_dlg_info_remote *remote, + const pj_str_t *identity_display) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)remote, &IDENTITY); + pj_xml_attr *attr; + + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &IDENTITY, NULL); + pj_xml_add_node(remote, node); + } + attr = pj_xml_find_attr(node, &DISPLAY, NULL); + if (!attr) { + attr = xml_create_attr(pool, &DISPLAY, identity_display); + pj_xml_add_attr(node, attr); + } else { + pj_strdup(pool, &attr->value, identity_display); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_remote_get_target_uri(const pjsip_dlg_info_remote *remote) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)remote, &TARGET); + pj_xml_attr *attr; + + if (!node) + return &EMPTY_STRING; + attr = pj_xml_find_attr(node, &URI, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + +PJ_DEF(void) +pjsip_dlg_info_remote_set_target_uri(pj_pool_t *pool, + pjsip_dlg_info_remote *remote, + const pj_str_t * target_uri) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)remote, &TARGET); + pj_xml_attr *attr; + + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &TARGET, NULL); + pj_xml_add_node(remote, node); + } + attr = pj_xml_find_attr(node, &URI, NULL); + if (!attr) { + attr = xml_create_attr(pool, &URI, target_uri); + pj_xml_add_attr(node, attr); + } else { + pj_strdup(pool, &attr->value, target_uri); + } +} + + +/* Local */ +PJ_DEF(void) pjsip_dlg_info_local_construct(pj_pool_t *pool, + pjsip_dlg_info_local *local) +{ + pj_xml_node *node; + + xml_init_node(pool, local, &LOCAL, NULL); + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, NULL, NULL); + pj_xml_add_node(local, node); +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_local_get_identity(const pjsip_dlg_info_local *local) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)local, &IDENTITY); + if (!node) + return &EMPTY_STRING; + return &node->content; +} + +PJ_DEF(void) pjsip_dlg_info_local_add_identity(pj_pool_t *pool, + pjsip_dlg_info_local *local, + const pj_str_t *identity) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)local, &IDENTITY); + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &IDENTITY, identity); + pj_xml_add_node(local, node); + } else { + pj_strdup(pool, &node->content, identity); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_local_get_identity_display(const pjsip_dlg_info_local *local) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)local, &IDENTITY); + pj_xml_attr *attr; + + if (!node) + return &EMPTY_STRING; + attr = pj_xml_find_attr(node, &DISPLAY, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + +PJ_DEF(void) +pjsip_dlg_info_local_set_identity_display(pj_pool_t *pool, + pjsip_dlg_info_local *local, + const pj_str_t *identity_display) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)local, &IDENTITY); + pj_xml_attr *attr; + + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &IDENTITY, NULL); + pj_xml_add_node(local, node); + } + attr = pj_xml_find_attr(node, &DISPLAY, NULL); + if (!attr) { + attr = xml_create_attr(pool, &DISPLAY, identity_display); + pj_xml_add_attr(node, attr); + } else { + pj_strdup(pool, &attr->value, identity_display); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_local_get_target_uri(const pjsip_dlg_info_local *local) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)local, &TARGET); + pj_xml_attr *attr; + + if (!node) + return &EMPTY_STRING; + attr = pj_xml_find_attr(node, &URI, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + +PJ_DEF(void) +pjsip_dlg_info_local_set_target_uri(pj_pool_t *pool, + pjsip_dlg_info_local *local, + const pj_str_t * target_uri) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)local, &TARGET); + pj_xml_attr *attr; + + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &TARGET, NULL); + pj_xml_add_node(local, node); + } + attr = pj_xml_find_attr(node, &URI, NULL); + if (!attr) { + attr = xml_create_attr(pool, &URI, target_uri); + pj_xml_add_attr(node, attr); + } else { + pj_strdup(pool, &attr->value, target_uri); + } +} + +/* Dialog */ +PJ_DEF(void) +pjsip_dlg_info_dialog_construct(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *id) +{ + pj_xml_attr *attr; + pjsip_dlg_info_local *local; + pjsip_dlg_info_remote *remote; + + xml_init_node(pool, dialog, &DIALOG, NULL); + attr = xml_create_attr(pool, &ID, id); + pj_xml_add_attr(dialog, attr); + local = PJ_POOL_ALLOC_T(pool, pjsip_dlg_info_local); + pjsip_dlg_info_local_construct(pool, local); + pj_xml_add_node(dialog, local); + + remote = PJ_POOL_ALLOC_T(pool, pjsip_dlg_info_remote); + pjsip_dlg_info_remote_construct(pool, remote); + pj_xml_add_node(dialog, remote); +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_get_id(const pjsip_dlg_info_dialog *dialog) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog, + &ID, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_set_id(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *id) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog, &ID, NULL); + pj_assert(attr); + pj_strdup(pool, &attr->value, id); +} + +PJ_DEF(const pj_str_t*) +pjsip_dlg_info_dialog_get_call_id(const pjsip_dlg_info_dialog *dialog) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog, &CALL_ID, + NULL); + if (attr) + return &attr->value; + return &EMPTY_STRING; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_set_call_id(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *call_id) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog, &CALL_ID, NULL); + if (!attr) { + attr = xml_create_attr(pool, &CALL_ID, call_id); + pj_xml_add_attr(dialog, attr); + } else { + pj_strdup(pool, &attr->value, call_id); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_get_remote_tag(const pjsip_dlg_info_dialog *dialog) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog, + &REMOTE_TAG, NULL); + if (attr) + return &attr->value; + return &EMPTY_STRING; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_set_remote_tag(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *remote_tag) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog, &REMOTE_TAG, NULL); + if (!attr) { + attr = xml_create_attr(pool, &REMOTE_TAG, remote_tag); + pj_xml_add_attr(dialog, attr); + } else { + pj_strdup(pool, &attr->value, remote_tag); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_get_local_tag(const pjsip_dlg_info_dialog *dialog) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog, + &LOCAL_TAG, NULL); + if (attr) + return &attr->value; + return &EMPTY_STRING; +} + +PJ_DEF(void) pjsip_dlg_info_dialog_set_local_tag(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *local_tag) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog, &LOCAL_TAG, NULL); + if (!attr) { + attr = xml_create_attr(pool, &LOCAL_TAG, local_tag); + pj_xml_add_attr(dialog, attr); + } else { + pj_strdup(pool, &attr->value, local_tag); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_get_direction(const pjsip_dlg_info_dialog *dialog) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog, + &DIRECTION, NULL); + if (attr) + return &attr->value; + return &EMPTY_STRING; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_set_direction(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *direction) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog, &DIRECTION, NULL); + if (!attr) { + attr = xml_create_attr(pool, &DIRECTION, direction); + pj_xml_add_attr(dialog, attr); + } else { + pj_strdup(pool, &attr->value, direction); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_get_state(pjsip_dlg_info_dialog *dialog) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)dialog, &STATE); + if (!node) + return &EMPTY_STRING; + return &node->content; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_set_state(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *state) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)dialog, &STATE); + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &STATE, state); + pj_xml_add_node(dialog, node); + } else { + pj_strdup(pool, &node->content, state); + } +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_get_duration(pjsip_dlg_info_dialog *dialog) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)dialog, &DURATION); + if (!node) + return &EMPTY_STRING; + return &node->content; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_set_duration(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog, + const pj_str_t *duration) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)dialog, &DURATION); + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &DURATION, duration); + pj_xml_add_node(dialog, node); + } else { + pj_strdup(pool, &node->content, duration); + } +} + +PJ_DEF(pjsip_dlg_info_local *) +pjsip_dlg_info_dialog_get_local(pjsip_dlg_info_dialog *dialog) +{ + pjsip_dlg_info_local *local = (pjsip_dlg_info_local*) + pj_xml_find_node(dialog, &LOCAL); + if (local) + return local; + return NULL; +} + +PJ_DEF(pjsip_dlg_info_local *) +pjsip_dlg_info_dialog_add_local(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog) +{ + pjsip_dlg_info_local *local = PJ_POOL_ALLOC_T(pool, pjsip_dlg_info_local); + xml_init_node(pool, local, &LOCAL, NULL); + pj_xml_add_node(dialog, local); + return local; +} + +PJ_DEF(pjsip_dlg_info_remote *) +pjsip_dlg_info_dialog_get_remote(pjsip_dlg_info_dialog *dialog) +{ + pjsip_dlg_info_remote *remote = (pjsip_dlg_info_remote*) + pj_xml_find_node(dialog, &REMOTE); + if (remote) + return remote; + return NULL; +} + +PJ_DEF(pjsip_dlg_info_remote *) +pjsip_dlg_info_dialog_add_remote(pj_pool_t *pool, + pjsip_dlg_info_dialog *dialog) +{ + pjsip_dlg_info_remote *remote = PJ_POOL_ALLOC_T(pool, + pjsip_dlg_info_remote); + xml_init_node(pool, remote, &REMOTE, NULL); + pj_xml_add_node(dialog, remote); + return remote; +} + + +/* Dialog-Info */ +PJ_DEF(void) +pjsip_dlg_info_dialog_info_construct(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *version, + const pj_str_t *state, + const pj_str_t* entity) +{ + pj_xml_attr *attr; + pjsip_dlg_info_dialog *dialog; + + xml_init_node(pool, dialog_info, &DIALOG_INFO, NULL); + attr = xml_create_attr(pool, &VERSION, version); + pj_xml_add_attr(dialog_info, attr); + + attr = xml_create_attr(pool, &STATE, state); + pj_xml_add_attr(dialog_info, attr); + + attr = xml_create_attr(pool, &ENTITY, entity); + pj_xml_add_attr(dialog_info, attr); + + dialog = PJ_POOL_ALLOC_T(pool, pjsip_dlg_info_dialog); + pjsip_dlg_info_local_construct(pool, dialog); + pj_xml_add_node(dialog_info, dialog); +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_info_get_state(pjsip_dlg_info_dialog_info *dialog_info) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog_info, + &STATE, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_info_set_state(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *state) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog_info, &STATE, NULL); + pj_assert(attr); + pj_strdup(pool, &attr->value, state); +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_info_get_version(pjsip_dlg_info_dialog_info *dialog_info) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog_info, + &VERSION, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + + +PJ_DEF(void) +pjsip_dlg_info_dialog_info_set_version(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *version) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog_info, &VERSION, NULL); + pj_assert(attr); + pj_strdup(pool, &attr->value, version); +} + +PJ_DEF(const pj_str_t *) +pjsip_dlg_info_dialog_info_get_entity(pjsip_dlg_info_dialog_info *dialog_info) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)dialog_info, + &ENTITY, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + +PJ_DEF(void) +pjsip_dlg_info_dialog_info_set_entity(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *entity) +{ + pj_xml_attr *attr = pj_xml_find_attr(dialog_info, &ENTITY, NULL); + pj_assert(attr); + pj_strdup(pool, &attr->value, entity); +} + +PJ_DEF(pjsip_dlg_info_dialog *) +pjsip_dlg_info_dialog_info_get_dialog(pjsip_dlg_info_dialog_info *dialog_info) +{ + pjsip_dlg_info_dialog *dialog = (pjsip_dlg_info_dialog*) + pj_xml_find_node(dialog_info, &DIALOG); + return dialog; +} + +PJ_DEF(pjsip_dlg_info_dialog *) +pjsip_dlg_info_dialog_info_add_dialog(pj_pool_t *pool, + pjsip_dlg_info_dialog_info *dialog_info, + const pj_str_t *id) +{ + pjsip_dlg_info_dialog *dialog = PJ_POOL_ALLOC_T(pool, + pjsip_dlg_info_dialog); + pjsip_dlg_info_dialog_construct(pool, dialog, id); + pj_xml_add_node(dialog_info, dialog); + return dialog; +} + +/* Dialog-Info document */ + +PJ_DEF(pjsip_dlg_info_dialog *) +pjsip_dlg_info_create(pj_pool_t *pool, const pj_str_t *version, + const pj_str_t *state, const pj_str_t *entity) +{ + pjsip_dlg_info_dialog *dialog_info; + + dialog_info = PJ_POOL_ALLOC_T(pool, pjsip_dlg_info_dialog); + pjsip_dlg_info_dialog_info_construct(pool, dialog_info, version, + state, entity); + return dialog_info; +} + +PJ_DEF(pjsip_dlg_info_dialog *) pjsip_dlg_info_parse(pj_pool_t *pool, + char *text, + int len) +{ + pjsip_dlg_info_dialog *dialog_info = pj_xml_parse(pool, text, len); + if (dialog_info) { + if (pj_stricmp(&dialog_info->name, &DIALOG_INFO) == 0) + return dialog_info; + } + return NULL; +} + +PJ_DEF(int) pjsip_dlg_info_print(const pjsip_dlg_info_dialog *dialog_info, + char *buf, int len) +{ + return pj_xml_print(dialog_info, buf, len, PJ_TRUE); +} diff --git a/pjsip/src/pjsip-simple/dlg_event.c b/pjsip/src/pjsip-simple/dlg_event.c new file mode 100644 index 0000000000..a1a84be6b5 --- /dev/null +++ b/pjsip/src/pjsip-simple/dlg_event.c @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2024 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2013 Maxim Kondratenko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define THIS_FILE "dlg_event.c" +#define DLG_EVENT_DEFAULT_EXPIRES PJSIP_DLG_EVENT_DEFAULT_EXPIRES + +#if PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE < 200 || \ + PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE > 699 || \ + PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE/100 == 3 +# error Invalid PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE value +#endif + +/* + * Dialog event module (mod-dlg_event) + */ +static struct pjsip_module mod_dlg_event = +{ + NULL, NULL, /* prev, next. */ + { "mod-dlg_event", 12 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/* + * Dialog event message body type. + */ +typedef enum content_type_e +{ + CONTENT_TYPE_NONE, + CONTENT_TYPE_DIALOG_INFO, +} content_type_e; + +/* + * This structure describe an entity, for both subscriber and notifier. + */ +struct pjsip_dlg_event +{ + pjsip_evsub *sub; /**< Event subscription record. */ + pjsip_dialog *dlg; /**< The dialog. */ + content_type_e content_type; /**< Content-Type. */ + pj_pool_t *status_pool; /**< Pool for dlgev_status */ + pjsip_dlg_event_status status; /**< Dialog event status. */ + pj_pool_t *tmp_pool; /**< Pool for tmp_status */ + pjsip_dlg_event_status tmp_status; /**< Temp, before NOTIFY is answered */ + pj_bool_t is_ts_valid; /**< Is tmp_status valid? */ + pjsip_evsub_user user_cb; /**< The user callback. */ + pj_mutex_t *mutex; /**< Mutex */ +}; + +typedef struct pjsip_dlg_event pjsip_dlg_event; + +/* + * Forward decl for evsub callback. + */ +static void dlg_event_on_evsub_state(pjsip_evsub *sub, pjsip_event *event); +static void dlg_event_on_evsub_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event); +static void dlg_event_on_evsub_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); +static void dlg_event_on_evsub_client_refresh(pjsip_evsub *sub); + + +/* + * Event subscription callback for dialog event. + */ +static pjsip_evsub_user dlg_event_user = +{ + &dlg_event_on_evsub_state, + &dlg_event_on_evsub_tsx_state, + NULL, + &dlg_event_on_evsub_rx_notify, + &dlg_event_on_evsub_client_refresh, + NULL, +}; + + +/* + * Some static constants. + */ +const pj_str_t STR_DIALOG_EVENT = { "Event", 5 }; +const pj_str_t STR_DIALOG = { "dialog", 6 }; +const pj_str_t STR_DIALOG_APPLICATION = { "application", 11 }; +const pj_str_t STR_DIALOG_INFO_XML = { "dialog-info+xml", 15 }; +const pj_str_t STR_APP_DIALOG_INFO_XML = { "application/dialog-info+xml", 27 }; + + +/* + * Init dialog event module. + */ +PJ_DEF(pj_status_t) pjsip_dlg_event_init_module(pjsip_endpoint *endpt, + pjsip_module *mod_evsub) +{ + pj_status_t status; + pj_str_t accept[1]; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL); + + /* Must have not been registered */ + PJ_ASSERT_RETURN(mod_dlg_event.id == -1, PJ_EINVALIDOP); + + /* Register to endpoint */ + status = pjsip_endpt_register_module(endpt, &mod_dlg_event); + if (status != PJ_SUCCESS) + return status; + + accept[0] = STR_APP_DIALOG_INFO_XML; + + /* Register event package to event module. */ + status = pjsip_evsub_register_pkg(&mod_dlg_event, &STR_DIALOG, + DLG_EVENT_DEFAULT_EXPIRES, + PJ_ARRAY_SIZE(accept), accept); + if (status != PJ_SUCCESS) { + pjsip_endpt_unregister_module(endpt, &mod_dlg_event); + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Get dialog event module instance. + */ +PJ_DEF(pjsip_module*) pjsip_dlg_event_instance(void) +{ + return &mod_dlg_event; +} + + +/* + * Create client subscription. + */ +PJ_DEF(pj_status_t) +pjsip_dlg_event_create_uac(pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + unsigned options, + pjsip_evsub **p_evsub) +{ + pj_status_t status; + pjsip_dlg_event *dlgev; + char obj_name[PJ_MAX_OBJ_NAME]; + pjsip_evsub *sub; + + PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + + /* Create event subscription */ + status = pjsip_evsub_create_uac(dlg, &dlg_event_user, &STR_DIALOG, + options, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create dialog event */ + dlgev = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_dlg_event); + dlgev->dlg = dlg; + dlgev->sub = sub; + if (user_cb) + pj_memcpy(&dlgev->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + status = pj_mutex_create_recursive(dlg->pool, "dlgev_mutex", + &dlgev->mutex); + if (status != PJ_SUCCESS) + goto on_return; + + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "dlgev%p", dlg->pool); + dlgev->status_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmdlgev%p", dlg->pool); + dlgev->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_dlg_event.id, dlgev); + + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Forcefully terminate dialog event. + */ +PJ_DEF(pj_status_t) pjsip_dlg_event_terminate(pjsip_evsub *sub, + pj_bool_t notify) +{ + return pjsip_evsub_terminate(sub, notify); +} + +/* + * Create SUBSCRIBE + */ +PJ_DEF(pj_status_t) pjsip_dlg_event_initiate(pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata) +{ + return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, + p_tdata); +} + + +/* + * Add custom headers. + */ +PJ_DEF(pj_status_t) pjsip_dlg_event_add_header(pjsip_evsub *sub, + const pjsip_hdr *hdr_list ) +{ + return pjsip_evsub_add_header( sub, hdr_list ); +} + + +/* + * Accept incoming subscription. + */ +PJ_DEF(pj_status_t) pjsip_dlg_event_accept(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list) +{ + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); +} + + +/* + * Get dialog event status. + */ +PJ_DEF(pj_status_t) pjsip_dlg_event_get_status(pjsip_evsub *sub, + pjsip_dlg_event_status *status) +{ + pjsip_dlg_event *dlgev; + + PJ_ASSERT_RETURN(sub && status, PJ_EINVAL); + + dlgev = (pjsip_dlg_event*) pjsip_evsub_get_mod_data(sub, mod_dlg_event.id); + PJ_ASSERT_RETURN(dlgev!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + + pj_mutex_lock(dlgev->mutex); + + if (dlgev->is_ts_valid) { + PJ_ASSERT_RETURN(dlgev->tmp_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + pj_memcpy(status, &dlgev->tmp_status, sizeof(pjsip_dlg_event_status)); + } else { + PJ_ASSERT_RETURN(dlgev->status_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + pj_memcpy(status, &dlgev->status, sizeof(pjsip_dlg_event_status)); + } + + pj_mutex_unlock(dlgev->mutex); + + return PJ_SUCCESS; +} + + +/* + * Send request. + */ +PJ_DEF(pj_status_t) pjsip_dlg_event_send_request(pjsip_evsub *sub, + pjsip_tx_data *tdata ) +{ + return pjsip_evsub_send_request(sub, tdata); +} + + + +PJ_DEF(pj_status_t) +pjsip_dlg_event_parse_dialog_info(pjsip_rx_data *rdata, + pj_pool_t *pool, + pjsip_dlg_event_status *dlgev_st) +{ + return pjsip_dlg_event_parse_dialog_info2( + (char*)rdata->msg_info.msg->body->data, + rdata->msg_info.msg->body->len, + pool, dlgev_st); +} + +PJ_DEF(pj_status_t) +pjsip_dlg_event_parse_dialog_info2(char *body, unsigned body_len, + pj_pool_t *pool, + pjsip_dlg_event_status *dlgev_st) +{ + pjsip_dlg_info_dialog_info *dialog_info; + pjsip_dlg_info_dialog *dialog; + + dialog_info = pjsip_dlg_info_parse(pool, body, body_len); + if (dialog_info == NULL) + return PJSIP_SIMPLE_EBADPIDF; + + dlgev_st->info_cnt = 0; + + dialog = pjsip_dlg_info_dialog_info_get_dialog(dialog_info); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_info_entity, + pjsip_dlg_info_dialog_info_get_entity(dialog_info)); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_info_state, + pjsip_dlg_info_dialog_info_get_state(dialog_info)); + + if (dialog) { + pjsip_dlg_info_local * local; + pjsip_dlg_info_remote * remote; + + dlgev_st->info[dlgev_st->info_cnt].dialog_node = + pj_xml_clone(pool, dialog); + + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_id, + pjsip_dlg_info_dialog_get_id(dialog)); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_call_id, + pjsip_dlg_info_dialog_get_call_id(dialog)); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_remote_tag, + pjsip_dlg_info_dialog_get_remote_tag(dialog)); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_local_tag, + pjsip_dlg_info_dialog_get_local_tag(dialog)); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_direction, + pjsip_dlg_info_dialog_get_direction(dialog)); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_state, + pjsip_dlg_info_dialog_get_state(dialog)); + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].dialog_duration, + pjsip_dlg_info_dialog_get_duration(dialog)); + + local =pjsip_dlg_info_dialog_get_local(dialog); + if (local) { + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].local_identity, + pjsip_dlg_info_local_get_identity(local)); + pj_strdup(pool, + &dlgev_st->info[dlgev_st->info_cnt].local_identity_display, + pjsip_dlg_info_local_get_identity_display(local)); + pj_strdup(pool, + &dlgev_st->info[dlgev_st->info_cnt].local_target_uri, + pjsip_dlg_info_local_get_target_uri(local)); + } else { + dlgev_st->info[dlgev_st->info_cnt].local_identity.ptr = NULL; + dlgev_st->info[dlgev_st->info_cnt].local_identity_display.ptr = + NULL; + dlgev_st->info[dlgev_st->info_cnt].local_target_uri.ptr = NULL; + } + + remote = pjsip_dlg_info_dialog_get_remote(dialog); + if (remote) { + pj_strdup(pool, &dlgev_st->info[dlgev_st->info_cnt].remote_identity, + pjsip_dlg_info_remote_get_identity(remote)); + pj_strdup(pool, + &dlgev_st->info[dlgev_st->info_cnt].remote_identity_display, + pjsip_dlg_info_remote_get_identity_display(remote)); + pj_strdup(pool, + &dlgev_st->info[dlgev_st->info_cnt].remote_target_uri, + pjsip_dlg_info_remote_get_target_uri(remote)); + } else { + dlgev_st->info[dlgev_st->info_cnt].remote_identity.ptr = NULL; + dlgev_st->info[dlgev_st->info_cnt].remote_identity_display.ptr = + NULL; + dlgev_st->info[dlgev_st->info_cnt].remote_target_uri.ptr = NULL; + } + } else { + dlgev_st->info[dlgev_st->info_cnt].dialog_node = NULL; + } + + return PJ_SUCCESS; +} + + +/* + * This callback is called by event subscription when subscription + * state has changed. + */ +static void dlg_event_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_dlg_event *dlgev; + + dlgev = (pjsip_dlg_event*) pjsip_evsub_get_mod_data(sub, mod_dlg_event.id); + PJ_ASSERT_ON_FAIL(dlgev!=NULL, {return;}); + + if (dlgev->user_cb.on_evsub_state) + (*dlgev->user_cb.on_evsub_state)(sub, event); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + if (dlgev->status_pool) { + pj_pool_release(dlgev->status_pool); + dlgev->status_pool = NULL; + } + if (dlgev->tmp_pool) { + pj_pool_release(dlgev->tmp_pool); + dlgev->tmp_pool = NULL; + } + if (dlgev->mutex) { + pj_mutex_destroy(dlgev->mutex); + dlgev->mutex = NULL; + } + } +} + +/* + * Called when transaction state has changed. + */ +static void dlg_event_on_evsub_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsip_dlg_event *dlgev; + + dlgev = (pjsip_dlg_event*) pjsip_evsub_get_mod_data(sub, mod_dlg_event.id); + PJ_ASSERT_ON_FAIL(dlgev!=NULL, {return;}); + + if (dlgev->user_cb.on_tsx_state) + (*dlgev->user_cb.on_tsx_state)(sub, tsx, event); +} + + +/* + * Process the content of incoming NOTIFY request and update temporary + * status. + * + * return PJ_SUCCESS if incoming request is acceptable. If return value + * is not PJ_SUCCESS, res_hdr may be added with Warning header. + */ +static pj_status_t +dlg_event_process_rx_notify(pjsip_dlg_event *dlgev, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr) +{ + pjsip_ctype_hdr *ctype_hdr; + pj_status_t status = PJ_SUCCESS; + + *p_st_text = NULL; + + /* Check Content-Type and msg body are dlgevent. */ + ctype_hdr = rdata->msg_info.ctype; + + if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) + { + pjsip_warning_hdr *warn_hdr; + pj_str_t warn_text; + + *p_st_code = PJSIP_SC_BAD_REQUEST; + + warn_text = pj_str("Message body is not dlgevent"); + warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399, + pjsip_endpt_name(dlgev->dlg->endpt), + &warn_text); + pj_list_push_back(res_hdr, warn_hdr); + + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + } + + /* Parse content. */ + if (pj_stricmp(&ctype_hdr->media.type, &STR_DIALOG_APPLICATION)==0 && + pj_stricmp(&ctype_hdr->media.subtype, &STR_DIALOG_INFO_XML)==0) + { + status = pjsip_dlg_event_parse_dialog_info(rdata, dlgev->tmp_pool, + &dlgev->tmp_status); + } + else { + status = PJSIP_SIMPLE_EBADCONTENT; + } + + /* If application calls dlgev_get_status(), redirect the call to + * retrieve the temporary status. + */ + dlgev->is_ts_valid = (status == PJ_SUCCESS? PJ_TRUE: PJ_FALSE); + + if (status != PJ_SUCCESS) { + /* Unsupported or bad Content-Type */ + if (PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE >= 300) { + pjsip_accept_hdr *accept_hdr; + pjsip_warning_hdr *warn_hdr; + + *p_st_code = PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE; + + /* Add Accept header */ + accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool); + accept_hdr->values[accept_hdr->count++] = STR_APP_DIALOG_INFO_XML; + pj_list_push_back(res_hdr, accept_hdr); + + /* Add Warning header */ + warn_hdr = pjsip_warning_hdr_create_from_status( + rdata->tp_info.pool, + pjsip_endpt_name(dlgev->dlg->endpt), + status); + pj_list_push_back(res_hdr, warn_hdr); + + return status; + } else { + pj_assert(PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE/100 == 2); + PJ_PERROR(4,(THIS_FILE, status, + "Ignoring dlgev error due to " + "PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE setting [%d]", + PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE)); + *p_st_code = PJSIP_DLG_EVENT_BAD_CONTENT_RESPONSE; + status = PJ_SUCCESS; + } + } + + return PJ_SUCCESS; +} + + +/* + * Called when NOTIFY is received. + */ +static void dlg_event_on_evsub_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsip_dlg_event *dlgev; + pj_status_t status; + + dlgev = (pjsip_dlg_event*) pjsip_evsub_get_mod_data(sub, mod_dlg_event.id); + PJ_ASSERT_ON_FAIL(dlgev!=NULL, {return;}); + + if (rdata->msg_info.msg->body) { + status = dlg_event_process_rx_notify( dlgev, rdata, p_st_code, p_st_text, + res_hdr ); + if (status != PJ_SUCCESS) + return; + + } else { + unsigned i; + for (i=0; istatus.info_cnt; ++i) { + dlgev->status.info[i].dialog_node = NULL; + } + } + + /* Notify application. */ + if (dlgev->user_cb.on_rx_notify) { + (*dlgev->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + } + + + /* If application responded NOTIFY with 2xx, copy temporary status + * to main status, and mark the temporary status as invalid. + */ + pj_mutex_lock(dlgev->mutex); + + if ((*p_st_code)/100 == 2) { + pj_pool_t *tmp; + + pj_memcpy(&dlgev->status, &dlgev->tmp_status, + sizeof(pjsip_dlg_event_status)); + + /* Swap the pool */ + tmp = dlgev->tmp_pool; + dlgev->tmp_pool = dlgev->status_pool; + dlgev->status_pool = tmp; + } + + dlgev->is_ts_valid = PJ_FALSE; + pj_pool_reset(dlgev->tmp_pool); + + pj_mutex_unlock(dlgev->mutex); + + /* Done */ +} + +/* + * Called when it's time to send SUBSCRIBE. + */ +static void dlg_event_on_evsub_client_refresh(pjsip_evsub *sub) +{ + pjsip_dlg_event *dlgev; + + dlgev = (pjsip_dlg_event*) pjsip_evsub_get_mod_data(sub, mod_dlg_event.id); + PJ_ASSERT_ON_FAIL(dlgev!=NULL, {return;}); + + if (dlgev->user_cb.on_client_refresh) { + (*dlgev->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_dlg_event_initiate(sub, -1, &tdata); + if (status == PJ_SUCCESS) + pjsip_dlg_event_send_request(sub, tdata); + } +} diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index c2a20df637..85e2631612 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -1256,6 +1256,9 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, status = pjsip_pres_init_module( pjsua_var.endpt, pjsip_evsub_instance()); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + status = pjsip_dlg_event_init_module( pjsua_var.endpt, pjsip_evsub_instance()); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + /* Initialize MWI support */ status = pjsip_mwi_init_module(pjsua_var.endpt, pjsip_evsub_instance()); diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c index 45921b8b5f..b0617851a6 100644 --- a/pjsip/src/pjsua-lib/pjsua_pres.c +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -23,9 +23,8 @@ #define THIS_FILE "pjsua_pres.c" -static void subscribe_buddy_presence(pjsua_buddy_id buddy_id); -static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id); - +static void subscribe_buddy(pjsua_buddy_id buddy_id, pj_bool_t presence); +static void unsubscribe_buddy(pjsua_buddy_id buddy_id, pj_bool_t presence); /* * Find buddy. @@ -318,6 +317,193 @@ PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id, return PJ_SUCCESS; } +/* + * Get detailed buddy dialog event info. + */ +PJ_DEF(pj_status_t) +pjsua_buddy_get_dlg_event_info( pjsua_buddy_id buddy_id, + pjsua_buddy_dlg_event_info *info) +{ + unsigned total=0; + struct buddy_lock lck; + pjsua_buddy *buddy; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + pj_bzero(info, sizeof(pjsua_buddy_dlg_event_info)); + + status = lock_buddy("pjsua_buddy_get_dlg_event_info()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + buddy = lck.buddy; + info->id = buddy->index; + if (pjsua_var.buddy[buddy_id].uri.slen == 0) { + unlock_buddy(&lck); + return PJ_SUCCESS; + } + + /* uri */ + info->uri.ptr = info->buf_ + total; + pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total); + total += info->uri.slen; + + if (buddy->dlg_ev_status.info[0].dialog_info_state.slen > 0) { + info->dialog_info_state.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_info_state, + &buddy->dlg_ev_status.info[0].dialog_info_state, + buddy->dlg_ev_status.info[0].dialog_info_state.slen); + total += info->dialog_info_state.slen; + } + + if (buddy->dlg_ev_status.info[0].dialog_info_entity.slen > 0) { + info->dialog_info_entity.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_info_entity, + &buddy->dlg_ev_status.info[0].dialog_info_entity, + buddy->dlg_ev_status.info[0].dialog_info_entity.slen); + total += info->dialog_info_entity.slen; + } + if (buddy->dlg_ev_status.info[0].dialog_state.slen == 0) { + info->dialog_state.ptr = info->buf_ + total; + info->dialog_state = pj_str("NULL"); + total += info->dialog_state.slen; + } + if (buddy->dlg_ev_status.info[0].dialog_node) { + info->dialog_id.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_id, &buddy->dlg_ev_status.info[0].dialog_id, + sizeof(info->buf_)-total); + total += info->dialog_id.slen; + + info->dialog_call_id.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_call_id, + &buddy->dlg_ev_status.info[0].dialog_call_id, + sizeof(info->buf_)-total); + total += info->dialog_call_id.slen; + + info->dialog_remote_tag.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_remote_tag, + &buddy->dlg_ev_status.info[0].dialog_remote_tag, + sizeof(info->buf_)-total); + total += info->dialog_remote_tag.slen; + + info->dialog_local_tag.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_local_tag, + &buddy->dlg_ev_status.info[0].dialog_local_tag, + sizeof(info->buf_)-total); + total += info->dialog_local_tag.slen; + + info->dialog_direction.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_direction, + &buddy->dlg_ev_status.info[0].dialog_direction, + sizeof(info->buf_)-total); + total += info->dialog_direction.slen; + + info->dialog_state.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_state, + &buddy->dlg_ev_status.info[0].dialog_state, + buddy->dlg_ev_status.info[0].dialog_state.slen); + total += info->dialog_state.slen; + + info->dialog_duration.ptr = info->buf_ + total; + pj_strncpy(&info->dialog_duration, + &buddy->dlg_ev_status.info[0].dialog_duration, + buddy->dlg_ev_status.info[0].dialog_duration.slen); + total += info->dialog_duration.slen; + + if (buddy->dlg_ev_status.info[0].local_identity.ptr) { + info->local_identity.ptr = info->buf_ + total; + pj_strncpy(&info->local_identity, + &buddy->dlg_ev_status.info[0].local_identity, + buddy->dlg_ev_status.info[0].local_identity.slen); + total += info->local_identity.slen; + } else { + info->local_identity = pj_str("NULL"); + } + + if (buddy->dlg_ev_status.info[0].local_identity_display.ptr) { + info->local_identity_display.ptr = info->buf_ + total; + pj_strncpy(&info->local_identity_display, + &buddy->dlg_ev_status.info[0].local_identity_display, + buddy->dlg_ev_status.info[0].local_identity_display.slen); + total += info->local_identity_display.slen; + } else { + info->local_identity_display = pj_str("NULL"); + } + + if (buddy->dlg_ev_status.info[0].local_target_uri.ptr) { + info->local_target_uri.ptr = info->buf_ + total; + pj_strncpy(&info->local_target_uri, + &buddy->dlg_ev_status.info[0].local_target_uri, + buddy->dlg_ev_status.info[0].local_target_uri.slen); + total += info->local_target_uri.slen; + } else { + info->local_target_uri = pj_str("NULL"); + } + + if (buddy->dlg_ev_status.info[0].remote_identity.ptr) { + info->remote_identity.ptr = info->buf_ + total; + pj_strncpy(&info->remote_identity, + &buddy->dlg_ev_status.info[0].remote_identity, + buddy->dlg_ev_status.info[0].remote_identity.slen); + total += info->remote_identity.slen; + } else { + info->remote_identity = pj_str("NULL"); + } + + if (buddy->dlg_ev_status.info[0].remote_identity_display.ptr) { + info->remote_identity_display.ptr = info->buf_ + total; + pj_strncpy(&info->remote_identity_display, + &buddy->dlg_ev_status.info[0].remote_identity_display, + buddy->dlg_ev_status.info[0].remote_identity_display.slen); + total += info->remote_identity_display.slen; + } else { + info->remote_identity_display = pj_str("NULL"); + } + + if (buddy->dlg_ev_status.info[0].remote_target_uri.ptr) { + info->remote_target_uri.ptr = info->buf_ + total; + pj_strncpy(&info->remote_target_uri, + &buddy->dlg_ev_status.info[0].remote_target_uri, + buddy->dlg_ev_status.info[0].remote_target_uri.slen); + total += info->remote_target_uri.slen; + } + else { + info->remote_target_uri = pj_str("NULL"); + } + } + + /* subscription state and termination reason */ + info->sub_term_code = buddy->term_code; + if (buddy->sub) { + info->sub_state = pjsip_evsub_get_state(buddy->sub); + info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub); + if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED && + total < sizeof(info->buf_)) + { + info->sub_term_reason.ptr = info->buf_ + total; + pj_strncpy(&info->sub_term_reason, + pjsip_evsub_get_termination_reason(buddy->sub), + sizeof(info->buf_) - total); + total += info->sub_term_reason.slen; + } else { + info->sub_term_reason = pj_str(""); + } + } else if (total < sizeof(info->buf_)) { + info->sub_state_name = "NULL"; + info->sub_term_reason.ptr = info->buf_ + total; + pj_strncpy(&info->sub_term_reason, &buddy->term_reason, + sizeof(info->buf_) - total); + total += info->sub_term_reason.slen; + } else { + info->sub_state_name = "NULL"; + info->sub_term_reason = pj_str(""); + } + + unlock_buddy(&lck); + return PJ_SUCCESS; +} + /* * Set the user data associated with the buddy object. */ @@ -481,13 +667,16 @@ PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg, PJ_LOG(4,(THIS_FILE, "Buddy %d added.", index)); - pjsua_buddy_subscribe_pres(index, cfg->subscribe); + if (cfg->subscribe) { + pjsua_buddy_subscribe_pres(index, cfg->subscribe); + } else if (cfg->subscribe_dlg_event) { + pjsua_buddy_subscribe_dlg_event(index, cfg->subscribe_dlg_event); + } pj_log_pop_indent(); return PJ_SUCCESS; } - /* * Delete buddy. */ @@ -511,8 +700,12 @@ PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id) PJ_LOG(4,(THIS_FILE, "Buddy %d: deleting..", buddy_id)); pj_log_push_indent(); - /* Unsubscribe presence */ - pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE); + /* Unsubscribe */ + if (pjsua_var.buddy[buddy_id].presence) { + pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE); + } else { + pjsua_buddy_subscribe_dlg_event(buddy_id, PJ_FALSE); + } /* Not interested with further events for this buddy */ if (pjsua_var.buddy[buddy_id].sub) { @@ -538,7 +731,6 @@ PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id) return PJ_SUCCESS; } - /* * Enable/disable buddy's presence monitoring. */ @@ -565,6 +757,28 @@ PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id, return PJ_SUCCESS; } +PJ_DEF(pj_status_t) pjsua_buddy_subscribe_dlg_event(pjsua_buddy_id buddy_id, + pj_bool_t subscribe) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_subscribe_dlg_event()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + pj_log_push_indent(); + + lck.buddy->monitor = subscribe; + + pjsua_buddy_update_dlg_event(buddy_id); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} /* * Update buddy's presence. @@ -585,7 +799,7 @@ PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id) /* Is this an unsubscribe request? */ if (!lck.buddy->monitor) { - unsubscribe_buddy_presence(buddy_id); + unsubscribe_buddy(buddy_id, PJ_TRUE); unlock_buddy(&lck); pj_log_pop_indent(); return PJ_SUCCESS; @@ -599,14 +813,52 @@ PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id) } /* Initiate presence subscription */ - subscribe_buddy_presence(buddy_id); + subscribe_buddy(buddy_id, PJ_TRUE); unlock_buddy(&lck); pj_log_pop_indent(); return PJ_SUCCESS; } +/* + * Update buddy's dlg event. + */ +PJ_DEF(pj_status_t) pjsua_buddy_update_dlg_event(pjsua_buddy_id buddy_id) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_update_dlg_event()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: updating dialog event..", buddy_id)); + pj_log_push_indent(); + /* Is this an unsubscribe request? */ + if (!lck.buddy->monitor) { + unsubscribe_buddy(buddy_id, PJ_FALSE); + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* Ignore if dialog event is already active for the buddy */ + if (lck.buddy->sub) { + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* Initiate dialog event subscription */ + subscribe_buddy(buddy_id, PJ_FALSE); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} /* * Dump presence subscriptions to log file. */ @@ -1146,7 +1398,7 @@ PJ_DEF(pj_status_t) pjsua_pres_notify( pjsua_acc_id acc_id, if (b->monitor && b->sub == NULL) { PJ_LOG(4,(THIS_FILE, "Received SUBSCRIBE from buddy %d, " "activating outgoing subscription", buddy_id)); - subscribe_buddy_presence(buddy_id); + subscribe_buddy(buddy_id, PJ_TRUE); } } @@ -1566,7 +1818,8 @@ static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); if (buddy) { PJ_LOG(4,(THIS_FILE, - "Presence subscription to %.*s is %s", + "%s subscription to %.*s is %s", + buddy->presence? "Presence": "Dialog event", (int)pjsua_var.buddy[buddy->index].uri.slen, pjsua_var.buddy[buddy->index].uri.ptr, pjsip_evsub_get_state_name(sub))); @@ -1676,17 +1929,29 @@ static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) } /* Call callbacks */ - if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state) - (*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub, - event); - - if (pjsua_var.ua_cfg.cb.on_buddy_state) - (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index); + if (buddy->presence) { + if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state) { + (*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub, + event); + } + + if (pjsua_var.ua_cfg.cb.on_buddy_state) + (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index); + } else { + if (pjsua_var.ua_cfg.cb.on_buddy_evsub_dlg_event_state) { + (*pjsua_var.ua_cfg.cb.on_buddy_evsub_dlg_event_state)( + buddy->index, sub, event); + } + + if (pjsua_var.ua_cfg.cb.on_buddy_dlg_event_state) + (*pjsua_var.ua_cfg.cb.on_buddy_dlg_event_state)(buddy->index); + } /* Clear subscription */ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { buddy->sub = NULL; buddy->status.info_cnt = 0; + pj_bzero(&buddy->dlg_ev_status, sizeof(buddy->dlg_ev_status)); buddy->dlg = NULL; pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); } @@ -1695,9 +1960,8 @@ static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) } } - /* Callback when transaction state has changed. */ -static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub, +static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) { @@ -1779,11 +2043,41 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, PJ_UNUSED_ARG(p_body); } +/* Callback called when we receive dialog monitoring NOTIFY */ +static void pjsua_evsub_on_rx_dlg_event_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsua_buddy *buddy; -/* It does what it says.. */ -static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) + /* Note: #937: no need to acquire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (buddy) { + /* Update our info. */ + pjsip_dlg_event_get_status(sub, &buddy->dlg_ev_status); + } + + /* The default is to send 200 response to NOTIFY. + * Just leave it there.. + */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); +} + +static void subscribe_buddy(pjsua_buddy_id buddy_id, + pj_bool_t presence) { pjsip_evsub_user pres_callback; + pjsip_evsub_user dlg_event_callback; pj_pool_t *tmp_pool = NULL; pjsua_buddy *buddy; int acc_id; @@ -1792,6 +2086,7 @@ static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) pjsip_tx_data *tdata; pjsip_tpselector tp_sel; pj_status_t status; + const char *sub_str = presence? "presence": "dialog event"; /* Event subscription callback. */ pj_bzero(&pres_callback, sizeof(pres_callback)); @@ -1799,13 +2094,19 @@ static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) pres_callback.on_tsx_state = &pjsua_evsub_on_tsx_state; pres_callback.on_rx_notify = &pjsua_evsub_on_rx_notify; + /* Event subscription callback. */ + pj_bzero(&dlg_event_callback, sizeof(dlg_event_callback)); + dlg_event_callback.on_evsub_state = &pjsua_evsub_on_state; + dlg_event_callback.on_tsx_state = &pjsua_evsub_on_tsx_state; + dlg_event_callback.on_rx_notify = &pjsua_evsub_on_rx_dlg_event_notify; + buddy = &pjsua_var.buddy[buddy_id]; acc_id = pjsua_acc_find_for_outgoing(&buddy->uri); acc = &pjsua_var.acc[acc_id]; - PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing presence,using account %d..", - buddy_id, acc_id)); + PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing %s,using account %d..", + buddy_id, sub_str, acc_id)); pj_log_push_indent(); /* Generate suitable Contact header unless one is already set in @@ -1867,11 +2168,18 @@ static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) } - status = pjsip_pres_create_uac( buddy->dlg, &pres_callback, - PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub); + if (presence) { + status = pjsip_pres_create_uac(buddy->dlg, &pres_callback, + PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub); + } else { + status = pjsip_dlg_event_create_uac(buddy->dlg, &dlg_event_callback, + PJSIP_EVSUB_NO_EVENT_ID, + &buddy->sub); + } + if (status != PJ_SUCCESS) { buddy->sub = NULL; - pjsua_perror(THIS_FILE, "Unable to create presence client", + pjsua_perror(THIS_FILE, "Unable to create subscription client", status); /* This should destroy the dialog since there's no session * referencing it @@ -1903,8 +2211,13 @@ static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) pjsip_evsub_add_header(buddy->sub, &acc->cfg.sub_hdr_list); pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy); - status = pjsip_pres_initiate(buddy->sub, PJSIP_EXPIRES_NOT_SPECIFIED, - &tdata); + if (presence) { + status = pjsip_pres_initiate(buddy->sub, PJSIP_EXPIRES_NOT_SPECIFIED, + &tdata); + } else { + status = pjsip_dlg_event_initiate(buddy->sub, + PJSIP_EXPIRES_NOT_SPECIFIED, &tdata); + } if (status != PJ_SUCCESS) { pjsip_dlg_dec_lock(buddy->dlg); pjsip_pres_terminate(buddy->sub, PJ_FALSE); @@ -1918,7 +2231,11 @@ static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) pjsua_process_msg_data(tdata, NULL); - status = pjsip_pres_send_request(buddy->sub, tdata); + if (presence) { + status = pjsip_pres_send_request(buddy->sub, tdata); + } else { + status = pjsip_dlg_event_send_request(buddy->sub, tdata); + } if (status != PJ_SUCCESS) { pjsip_dlg_dec_lock(buddy->dlg); pjsip_pres_terminate(buddy->sub, PJ_FALSE); @@ -1930,18 +2247,19 @@ static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) return; } + buddy->presence = presence; pjsip_dlg_dec_lock(buddy->dlg); if (tmp_pool) pj_pool_release(tmp_pool); pj_log_pop_indent(); } - -/* It does what it says... */ -static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id) +static void unsubscribe_buddy(pjsua_buddy_id buddy_id, + pj_bool_t presence) { pjsua_buddy *buddy; pjsip_tx_data *tdata; pj_status_t status; + const char *sub_str = presence? "presence": "dialog event"; buddy = &pjsua_var.buddy[buddy_id]; @@ -1953,20 +2271,30 @@ static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id) return; } - PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing..", buddy_id)); + PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing %s..", buddy_id, sub_str)); pj_log_push_indent(); - status = pjsip_pres_initiate( buddy->sub, 0, &tdata); + if (presence) { + status = pjsip_pres_initiate( buddy->sub, 0, &tdata); + } else { + status = pjsip_dlg_event_initiate( buddy->sub, 0, &tdata); + } if (status == PJ_SUCCESS) { pjsua_process_msg_data(tdata, NULL); - status = pjsip_pres_send_request( buddy->sub, tdata ); + if (presence) + status = pjsip_pres_send_request( buddy->sub, tdata ); + else + status = pjsip_dlg_event_send_request(buddy->sub, tdata); } if (status != PJ_SUCCESS && buddy->sub) { - pjsip_pres_terminate(buddy->sub, PJ_FALSE); + if (presence) { + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + } else { + pjsip_dlg_event_terminate(buddy->sub, PJ_FALSE); + } buddy->sub = NULL; - pjsua_perror(THIS_FILE, "Unable to unsubscribe presence", - status); + pjsua_perror(THIS_FILE, "Unable to unsubscribe", status); } pj_log_pop_indent(); @@ -1989,10 +2317,10 @@ static pj_status_t refresh_client_subscriptions(void) return status; if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) { - subscribe_buddy_presence(i); + subscribe_buddy(i, PJ_TRUE); } else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) { - unsubscribe_buddy_presence(i); + unsubscribe_buddy(i, PJ_TRUE); }