summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsua
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-02-19 01:38:06 +0000
committerBenny Prijono <bennylp@teluu.com>2006-02-19 01:38:06 +0000
commit49a3b60593925562cbeb836a5885e034d2f78997 (patch)
tree87ae5502ec663309e0c78ad97cb22fd776896fc9 /pjsip/src/pjsua
parent4e0f563feccb847c57739e48c91b0f5190938e9d (diff)
Initial SIMPLE implementation
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@197 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src/pjsua')
-rw-r--r--pjsip/src/pjsua/main.c277
-rw-r--r--pjsip/src/pjsua/pjsua.h70
-rw-r--r--pjsip/src/pjsua/pjsua_core.c89
-rw-r--r--pjsip/src/pjsua/pjsua_inv.c24
-rw-r--r--pjsip/src/pjsua/pjsua_opt.c13
-rw-r--r--pjsip/src/pjsua/pjsua_pres.c471
6 files changed, 863 insertions, 81 deletions
diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c
index 37fde773..4a40d324 100644
--- a/pjsip/src/pjsua/main.c
+++ b/pjsip/src/pjsua/main.c
@@ -22,7 +22,8 @@
#define THIS_FILE "main.c"
-static pjsip_inv_session *inv_session;
+/* Current dialog */
+static struct pjsua_inv_data *inv_session;
/*
* Notify UI when invite state has changed.
@@ -35,12 +36,16 @@ void pjsua_ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
pjsua_inv_state_names[inv->state]));
if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
- if (inv == inv_session)
- inv_session = NULL;
+ if (inv == inv_session->inv) {
+ inv_session = inv_session->next;
+ if (inv_session == &pjsua.inv_list)
+ inv_session = pjsua.inv_list.next;
+ }
} else {
- inv_session = inv;
+ if (inv_session == &pjsua.inv_list || inv_session == NULL)
+ inv_session = inv->mod_data[pjsua.mod.id];
}
}
@@ -56,24 +61,71 @@ void pjsua_ui_regc_on_state_changed(int code)
}
+/*
+ * Print buddy list.
+ */
+static void print_buddy_list(void)
+{
+ unsigned i;
+
+ puts("Buddy list:");
+ //puts("-------------------------------------------------------------------------------");
+ if (pjsua.buddy_cnt == 0)
+ puts(" -none-");
+ else {
+ for (i=0; i<pjsua.buddy_cnt; ++i) {
+ const char *status;
+
+ if (pjsua.buddies[i].sub == NULL ||
+ pjsua.buddies[i].status.info_cnt==0)
+ {
+ status = " ? ";
+ }
+ else if (pjsua.buddies[i].status.info[0].basic_open)
+ status = " Online";
+ else
+ status = "Offline";
+
+ printf(" [%2d] <%s> %s\n",
+ i+1, status, pjsua.buddies[i].uri.ptr);
+ }
+ }
+ puts("");
+}
/*
* Show a bit of help.
*/
-static void ui_help(void)
+static void keystroke_help(void)
{
- puts("");
- puts("Console keys:");
- puts(" m Make a call/another call");
- puts(" d Dump application states");
- puts(" a Answer incoming call");
- puts(" h Hangup current call");
- puts(" q Quit");
- puts("");
+
+ printf(">>>>\nOnline status: %s\n",
+ (pjsua.online_status ? "Online" : "Invisible"));
+ print_buddy_list();
+
+ //puts("Commands:");
+ puts("+=============================================================================+");
+ puts("| Call Commands: | IM & Presence: | Misc: |");
+ puts("| | | |");
+ puts("| m Make new call | i Send IM | o Send OPTIONS |");
+ puts("| a Answer call | s Subscribe presence | d Dump status |");
+ puts("| h Hangup call | u Unsubscribe presence | d1 Dump detailed |");
+ puts("| ] Select next dialog | t Toggle Online status | |");
+ puts("| [ Select previous dialog | | |");
+ puts("+-----------------------------------------------------------------------------+");
+ puts("| q QUIT |");
+ puts("+=============================================================================+");
+ printf(">>> ");
+
+
fflush(stdout);
}
-static pj_bool_t input(const char *title, char *buf, pj_size_t len)
+
+/*
+ * Input simple string
+ */
+static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
{
char *p;
@@ -92,48 +144,131 @@ static pj_bool_t input(const char *title, char *buf, pj_size_t len)
return PJ_TRUE;
}
+
+#define NO_NB -2
+struct input_result
+{
+ int nb_result;
+ char *uri_result;
+};
+
+
+/*
+ * Input URL.
+ */
+static void ui_input_url(const char *title, char *buf, int len,
+ struct input_result *result)
+{
+ result->nb_result = NO_NB;
+ result->uri_result = NULL;
+
+ print_buddy_list();
+
+ printf("Choices:\n"
+ " 0 For current dialog.\n"
+ " -1 All %d buddies in buddy list\n"
+ " [1 -%2d] Select from buddy list\n"
+ " URL An URL\n"
+ " <Enter> Empty input (or 'q') to cancel\n"
+ , pjsua.buddy_cnt, pjsua.buddy_cnt);
+ printf("%s: ", title);
+
+ fflush(stdout);
+ fgets(buf, len, stdin);
+ len = strlen(buf);
+
+ /* Left trim */
+ while (isspace(*buf)) {
+ ++buf;
+ --len;
+ }
+
+ /* Remove trailing newlines */
+ while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
+ buf[--len] = '\0';
+
+ if (len == 0 || buf[0]=='q')
+ return;
+
+ if (isdigit(*buf) || *buf=='-') {
+
+ int i;
+
+ if (*buf=='-')
+ i = 1;
+ else
+ i = 0;
+
+ for (; i<len; ++i) {
+ if (!isdigit(buf[i])) {
+ puts("Invalid input");
+ return;
+ }
+ }
+
+ result->nb_result = atoi(buf);
+
+ if (result->nb_result > 0 && result->nb_result <= (int)pjsua.buddy_cnt) {
+ --result->nb_result;
+ return;
+ }
+ if (result->nb_result == -1)
+ return;
+
+ puts("Invalid input");
+ result->nb_result = NO_NB;
+ return;
+
+ } else {
+ pj_status_t status;
+
+ if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
+ pjsua_perror("Invalid URL", status);
+ return;
+ }
+
+ result->uri_result = buf;
+ }
+}
+
static void ui_console_main(void)
{
+ char menuin[10];
char buf[128];
pjsip_inv_session *inv;
+ struct input_result result;
- //ui_help();
+ //keystroke_help();
for (;;) {
- ui_help();
- fgets(buf, sizeof(buf), stdin);
+ keystroke_help();
+ fgets(menuin, sizeof(menuin), stdin);
- switch (buf[0]) {
+ switch (menuin[0]) {
case 'm':
- if (inv_session != NULL) {
- puts("Can not make call while another one is in progress");
- fflush(stdout);
- continue;
- }
-
-#if 1
/* Make call! : */
- if (!input("Enter URL to call", buf, sizeof(buf)))
- continue;
- pjsua_invite(buf, &inv);
-
-#else
-
- pjsua_invite("sip:localhost:5061", &inv);
-#endif
+ if (pj_list_size(&pjsua.inv_list))
+ printf("(You have %d calls)\n", pj_list_size(&pjsua.inv_list));
+
+ ui_input_url("Make call", buf, sizeof(buf), &result);
+ if (result.nb_result != NO_NB) {
+ if (result.nb_result == -1)
+ puts("You can't do that with make call!");
+ else
+ pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, &inv);
+ } else if (result.uri_result)
+ pjsua_invite(result.uri_result, &inv);
+
break;
- case 'd':
- pjsua_dump();
- break;
-
case 'a':
- if (inv_session == NULL || inv_session->role != PJSIP_ROLE_UAS ||
- inv_session->state >= PJSIP_INV_STATE_CONNECTING)
+ if (inv_session == &pjsua.inv_list ||
+ inv_session->inv->role != PJSIP_ROLE_UAS ||
+ inv_session->inv->state >= PJSIP_INV_STATE_CONNECTING)
{
puts("No pending incoming call");
fflush(stdout);
@@ -143,16 +278,16 @@ static void ui_console_main(void)
pj_status_t status;
pjsip_tx_data *tdata;
- if (!input("Answer with code (100-699)", buf, sizeof(buf)))
+ if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
continue;
if (atoi(buf) < 100)
continue;
- status = pjsip_inv_answer(inv_session, atoi(buf), NULL, NULL,
- &tdata);
+ status = pjsip_inv_answer(inv_session->inv, atoi(buf),
+ NULL, NULL, &tdata);
if (status == PJ_SUCCESS)
- status = pjsip_inv_send_msg(inv_session, tdata, NULL);
+ status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
if (status != PJ_SUCCESS)
pjsua_perror("Unable to create/send response", status);
@@ -160,9 +295,10 @@ static void ui_console_main(void)
break;
+
case 'h':
- if (inv_session == NULL) {
+ if (inv_session == &pjsua.inv_list) {
puts("No current call");
fflush(stdout);
continue;
@@ -171,22 +307,62 @@ static void ui_console_main(void)
pj_status_t status;
pjsip_tx_data *tdata;
- status = pjsip_inv_end_session(inv_session, PJSIP_SC_DECLINE,
- NULL, &tdata);
+ status = pjsip_inv_end_session(inv_session->inv,
+ PJSIP_SC_DECLINE, NULL, &tdata);
if (status != PJ_SUCCESS) {
pjsua_perror("Failed to create end session message", status);
continue;
}
- status = pjsip_inv_send_msg(inv_session, tdata, NULL);
+ status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
if (status != PJ_SUCCESS) {
pjsua_perror("Failed to send end session message", status);
continue;
}
}
+ break;
+
+ case ']':
+ inv_session = inv_session->next;
+ if (inv_session == &pjsua.inv_list)
+ inv_session = pjsua.inv_list.next;
+ break;
+
+ case '[':
+ inv_session = inv_session->prev;
+ if (inv_session == &pjsua.inv_list)
+ inv_session = pjsua.inv_list.prev;
+ break;
+
+ case 's':
+ case 'u':
+ ui_input_url("Subscribe presence of", buf, sizeof(buf), &result);
+ if (result.nb_result != NO_NB) {
+ if (result.nb_result == -1) {
+ unsigned i;
+ for (i=0; i<pjsua.buddy_cnt; ++i)
+ pjsua.buddies[i].monitor = (menuin[0]=='s');
+ } else {
+ pjsua.buddies[result.nb_result].monitor = (menuin[0]=='s');
+ }
+
+ pjsua_pres_refresh();
+
+ } else if (result.uri_result) {
+ puts("Sorry, can only subscribe to buddy's presence, not arbitrary URL (for now)");
+ }
break;
+ case 't':
+ pjsua.online_status = !pjsua.online_status;
+ pjsua_pres_refresh();
+ break;
+
+ case 'd':
+ pjsua_dump();
+ break;
+
case 'q':
goto on_exit;
}
@@ -254,7 +430,7 @@ static pj_status_t console_on_tx_msg(pjsip_tx_data *tdata)
static pjsip_module console_msg_logger =
{
NULL, NULL, /* prev, next. */
- { "mod-console-msg-logger", 22 }, /* Name. */
+ { "mod-pjsua-log", 13 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
NULL, /* User data. */
@@ -385,6 +561,11 @@ int main(int argc, char *argv[])
pj_thread_sleep(500);
+ /* No current call initially: */
+
+ inv_session = &pjsua.inv_list;
+
+
/* Start UI console main loop: */
ui_console_main();
diff --git a/pjsip/src/pjsua/pjsua.h b/pjsip/src/pjsua/pjsua.h
index ebb97a0d..202c2840 100644
--- a/pjsip/src/pjsua/pjsua.h
+++ b/pjsip/src/pjsua/pjsua.h
@@ -31,6 +31,9 @@
/* Include all PJSIP-UA headers */
#include <pjsip_ua.h>
+/* Include all PJSIP-SIMPLE headers */
+#include <pjsip_simple.h>
+
/* Include all PJLIB-UTIL headers. */
#include <pjlib-util.h>
@@ -61,6 +64,33 @@ struct pjsua_inv_data
};
+/**
+ * Buddy data.
+ */
+struct pjsua_buddy
+{
+ pj_str_t uri; /**< Buddy URI */
+ pj_bool_t monitor; /**< Should we monitor? */
+ pjsip_evsub *sub; /**< Buddy presence subscription */
+ pjsip_pres_status status; /**< Buddy presence status. */
+};
+
+typedef struct pjsua_buddy pjsua_buddy;
+
+
+/**
+ * Server presence subscription list head.
+ */
+struct pjsua_srv_pres
+{
+ PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres);
+ pjsip_evsub *sub;
+ char *remote;
+};
+
+typedef struct pjsua_srv_pres pjsua_srv_pres;
+
+
/* PJSUA application variables. */
struct pjsua
@@ -141,10 +171,13 @@ struct pjsua
struct pjsua_inv_data inv_list;
- /* Buddy list: */
+ /* SIMPLE and buddy status: */
+
+ pj_bool_t online_status; /**< Out online status. */
+ pjsua_srv_pres pres_srv_list; /**< Server subscription list. */
unsigned buddy_cnt;
- pj_str_t buddies[PJSUA_MAX_BUDDIES];
+ pjsua_buddy buddies[PJSUA_MAX_BUDDIES];
};
@@ -235,6 +268,12 @@ void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e);
void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status);
+/**
+ * Terminate all calls.
+ */
+void pjsua_inv_shutdown(void);
+
+
/*****************************************************************************
* PJSUA Client Registration API (defined in pjsua_reg.c).
*/
@@ -253,6 +292,33 @@ pj_status_t pjsua_regc_init(void);
void pjsua_regc_update(pj_bool_t renew);
+
+
+/*****************************************************************************
+ * PJSUA Presence (pjsua_pres.c)
+ */
+
+/**
+ * Init presence.
+ */
+pj_status_t pjsua_pres_init();
+
+/**
+ * Refresh both presence client and server subscriptions.
+ */
+void pjsua_pres_refresh(void);
+
+/**
+ * Terminate all subscriptions
+ */
+void pjsua_pres_shutdown(void);
+
+/**
+ * Dump presence subscriptions.
+ */
+void pjsua_pres_dump(void);
+
+
/*****************************************************************************
* User Interface API.
*
diff --git a/pjsip/src/pjsua/pjsua_core.c b/pjsip/src/pjsua/pjsua_core.c
index f97c8f43..2d059d92 100644
--- a/pjsip/src/pjsua/pjsua_core.c
+++ b/pjsip/src/pjsua/pjsua_core.c
@@ -79,6 +79,11 @@ void pjsua_default(void)
/* Init invite session list: */
pj_list_init(&pjsua.inv_list);
+
+ /* Init server presence subscription list: */
+
+ pj_list_init(&pjsua.pres_srv_list);
+
}
@@ -391,14 +396,14 @@ on_error:
}
-static int PJ_THREAD_FUNC pjsua_worker_thread(void *arg)
+static int PJ_THREAD_FUNC pjsua_poll(void *arg)
{
PJ_UNUSED_ARG(arg);
- while (!pjsua.quit_flag) {
+ do {
pj_time_val timeout = { 0, 10 };
pjsip_endpt_handle_events (pjsua.endpt, &timeout);
- }
+ } while (!pjsua.quit_flag);
return 0;
}
@@ -435,7 +440,7 @@ pj_status_t pjsua_init(void)
pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL);
- /* Init PJSIP and all the modules: */
+ /* Init PJSIP : */
status = init_stack();
if (status != PJ_SUCCESS) {
@@ -445,6 +450,20 @@ pj_status_t pjsua_init(void)
}
+ /* Init core SIMPLE module : */
+
+ pjsip_evsub_init_module(pjsua.endpt);
+
+ /* Init presence module: */
+
+ pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
+
+
+ /* Init pjsua presence handler: */
+
+ pjsua_pres_init();
+
+
/* Init media endpoint: */
status = pjmedia_endpt_create(&pjsua.cp.factory, &pjsua.med_endpt);
@@ -609,7 +628,7 @@ pj_status_t pjsua_start(void)
/* Create worker thread(s), if required: */
for (i=0; i<pjsua.thread_cnt; ++i) {
- status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_worker_thread,
+ status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_poll,
NULL, 0, 0, &pjsua.threads[i]);
if (status != PJ_SUCCESS) {
pjsua.quit_flag = 1;
@@ -635,11 +654,26 @@ pj_status_t pjsua_start(void)
}
-
+ PJ_LOG(3,(THIS_FILE, "PJSUA version %s started", PJ_VERSION));
return PJ_SUCCESS;
}
+/* Sleep with polling */
+static void busy_sleep(unsigned msec)
+{
+ pj_time_val timeout, now;
+
+ pj_gettimeofday(&timeout);
+ timeout.msec += msec;
+ pj_time_val_normalize(&timeout);
+
+ do {
+ pjsua_poll(NULL);
+ pj_gettimeofday(&now);
+ } while (PJ_TIME_VAL_LT(now, timeout));
+}
+
/*
* Destroy pjsua.
*/
@@ -647,42 +681,43 @@ pj_status_t pjsua_destroy(void)
{
int i;
- /* Unregister, if required: */
- if (pjsua.regc) {
+ /* Signal threads to quit: */
+ pjsua.quit_flag = 1;
- pjsua_regc_update(0);
+ /* Wait worker threads to quit: */
+ for (i=0; i<pjsua.thread_cnt; ++i) {
+
+ if (pjsua.threads[i]) {
+ pj_thread_join(pjsua.threads[i]);
+ pj_thread_destroy(pjsua.threads[i]);
+ pjsua.threads[i] = NULL;
+ }
+ }
- /* Wait for some time to allow unregistration to complete: */
- pj_thread_sleep(500);
- }
+ /* Terminate all calls. */
+ pjsua_inv_shutdown();
- /* Signal threads to quit: */
+ /* Terminate all presence subscriptions. */
+ pjsua_pres_shutdown();
- pjsua.quit_flag = 1;
+ /* Unregister, if required: */
+ if (pjsua.regc) {
+ pjsua_regc_update(0);
+ }
+ /* Wait for some time to allow unregistration to complete: */
+ PJ_LOG(4,(THIS_FILE, "Shutting down..."));
+ busy_sleep(1000);
/* Shutdown pjmedia-codec: */
-
pjmedia_codec_deinit();
-
/* Destroy sound framework:
* (this should be done in pjmedia_shutdown())
*/
pj_snd_deinit();
- /* Wait worker threads to quit: */
-
- for (i=0; i<pjsua.thread_cnt; ++i) {
-
- if (pjsua.threads[i]) {
- pj_thread_join(pjsua.threads[i]);
- pj_thread_destroy(pjsua.threads[i]);
- pjsua.threads[i] = NULL;
- }
- }
-
/* Destroy endpoint. */
pjsip_endpt_destroy(pjsua.endpt);
diff --git a/pjsip/src/pjsua/pjsua_inv.c b/pjsip/src/pjsua/pjsua_inv.c
index 7c6dbf26..6f9607b5 100644
--- a/pjsip/src/pjsua/pjsua_inv.c
+++ b/pjsip/src/pjsua/pjsua_inv.c
@@ -80,6 +80,7 @@ pj_status_t pjsua_invite(const char *cstr_dest_uri,
inv_data = pj_pool_zalloc( dlg->pool, sizeof(struct pjsua_inv_data));
inv_data->inv = inv;
dlg->mod_data[pjsua.mod.id] = inv_data;
+ inv->mod_data[pjsua.mod.id] = inv_data;
/* Set dialog Route-Set: */
@@ -221,6 +222,7 @@ pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata)
inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data));
inv_data->inv = inv;
dlg->mod_data[pjsua.mod.id] = inv_data;
+ inv->mod_data[pjsua.mod.id] = inv_data;
pj_list_push_back(&pjsua.inv_list, inv_data);
@@ -345,3 +347,25 @@ void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status)
PJ_LOG(3,(THIS_FILE,"Media has been started successfully"));
}
}
+
+
+/*
+ * Terminate all calls.
+ */
+void pjsua_inv_shutdown()
+{
+ struct pjsua_inv_data *inv_data, *next;
+
+ inv_data = pjsua.inv_list.next;
+ while (inv_data != &pjsua.inv_list) {
+ pjsip_tx_data *tdata;
+
+ next = inv_data->next;
+
+ if (pjsip_inv_end_session(inv_data->inv, 410, NULL, &tdata)==0)
+ pjsip_inv_send_msg(inv_data->inv, tdata, NULL);
+
+ inv_data = next;
+ }
+}
+
diff --git a/pjsip/src/pjsua/pjsua_opt.c b/pjsip/src/pjsua/pjsua_opt.c
index 61e5adba..5ec1a56a 100644
--- a/pjsip/src/pjsua/pjsua_opt.c
+++ b/pjsip/src/pjsua/pjsua_opt.c
@@ -403,7 +403,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[])
printf("Error: too many buddies in buddy list.\n");
return -1;
}
- pjsua.buddies[pjsua.buddy_cnt++] = pj_str(optarg);
+ pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(optarg);
break;
}
}
@@ -510,6 +510,7 @@ void pjsua_dump(void)
pjsip_endpt_dump(pjsua.endpt, 1);
pjmedia_endpt_dump(pjsua.med_endpt);
+ pjsip_tsx_layer_dump();
pjsip_ua_dump();
@@ -536,6 +537,9 @@ void pjsua_dump(void)
}
}
+ /* Dump presence status */
+ pjsua_pres_dump();
+
pj_log_set_decor(old_decor);
PJ_LOG(3,(THIS_FILE, "Dump complete"));
}
@@ -547,8 +551,9 @@ void pjsua_dump(void)
pj_status_t pjsua_load_settings(const char *filename)
{
int argc = 3;
- char *argv[] = { "pjsua", "--config-file", (char*)filename, NULL};
+ char *argv[4] = { "pjsua", "--config-file", NULL, NULL};
+ argv[3] = (char*)filename;
return pjsua_parse_args(argc, argv);
}
@@ -654,8 +659,8 @@ pj_status_t pjsua_save_settings(const char *filename)
/* Add buddies. */
for (i=0; i<pjsua.buddy_cnt; ++i) {
pj_ansi_sprintf(line, "--add-buddy %.*s\n",
- (int)pjsua.buddies[i].slen,
- pjsua.buddies[i].ptr);
+ (int)pjsua.buddies[i].uri.slen,
+ pjsua.buddies[i].uri.ptr);
pj_strcat2(&cfg, line);
}
diff --git a/pjsip/src/pjsua/pjsua_pres.c b/pjsip/src/pjsua/pjsua_pres.c
new file mode 100644
index 00000000..db009eed
--- /dev/null
+++ b/pjsip/src/pjsua/pjsua_pres.c
@@ -0,0 +1,471 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "pjsua.h"
+
+/*
+ * pjsua_pres.c
+ *
+ * Presence related stuffs.
+ */
+
+#define THIS_FILE "pjsua_pres.c"
+
+
+
+/* **************************************************************************
+ * THE FOLLOWING PART HANDLES SERVER SUBSCRIPTION
+ * **************************************************************************
+ */
+
+/* Proto */
+static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata);
+
+/* The module instance. */
+static pjsip_module mod_pjsua_pres =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-pjsua-pres", 14 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* User data. */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &pres_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+/* Callback called when *server* subscription state has changed. */
+static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsua_srv_pres *uapres = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+
+ PJ_UNUSED_ARG(event);
+
+ if (uapres) {
+ PJ_LOG(3,(THIS_FILE, "Server subscription to %s is %s",
+ uapres->remote, pjsip_evsub_get_state_name(sub)));
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+ pj_list_erase(uapres);
+ }
+ }
+}
+
+/* This is called when request is received.
+ * We need to check for incoming SUBSCRIBE request.
+ */
+static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
+{
+ pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
+ pjsua_srv_pres *uapres;
+ pjsip_evsub *sub;
+ pjsip_evsub_user pres_cb;
+ pjsip_tx_data *tdata;
+ pjsip_pres_status pres_status;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ if (pjsip_method_cmp(req_method, &pjsip_subscribe_method) != 0)
+ return PJ_FALSE;
+
+ /* Incoming SUBSCRIBE: */
+
+ /* Create UAS dialog: */
+ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
+ &pjsua.contact_uri, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to create UAS dialog for subscription", status);
+ return PJ_FALSE;
+ }
+
+ /* Init callback: */
+ pj_memset(&pres_cb, 0, sizeof(pres_cb));
+ pres_cb.on_evsub_state = &pres_evsub_on_srv_state;
+
+ /* Create server presence subscription: */
+ status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub);
+ if (status != PJ_SUCCESS) {
+ PJ_TODO(DESTROY_DIALOG);
+ pjsua_perror("Unable to create server subscription", status);
+ return PJ_FALSE;
+ }
+
+ /* Attach our data to the subscription: */
+ uapres = pj_pool_alloc(dlg->pool, sizeof(pjsua_srv_pres));
+ uapres->sub = sub;
+ uapres->remote = pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
+ status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri,
+ uapres->remote, PJSIP_MAX_URL_SIZE);
+ if (status < 1)
+ pj_ansi_strcpy(uapres->remote, "<-- url is too long-->");
+ else
+ uapres->remote[status] = '\0';
+
+ pjsip_evsub_set_mod_data(sub, pjsua.mod.id, uapres);
+
+ /* Add server subscription to the list: */
+ pj_list_push_back(&pjsua.pres_srv_list, uapres);
+
+
+ /* Create and send 200 (OK) to the SUBSCRIBE request: */
+ status = pjsip_pres_accept(sub, rdata, 200, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to accept presence subscription", status);
+ pj_list_erase(uapres);
+ return PJ_FALSE;
+ }
+
+
+ /* Set our online status: */
+ pj_memset(&pres_status, 0, sizeof(pres_status));
+ pres_status.info_cnt = 1;
+ pres_status.info[0].basic_open = pjsua.online_status;
+ //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">"
+ //causing XML parsing to fail.
+ //pres_status.info[0].contact = pjsua.local_uri;
+
+ pjsip_pres_set_status(sub, &pres_status);
+
+ /* Create and send the first NOTIFY to active subscription: */
+ status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, NULL,
+ NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_pres_send_request( sub, tdata);
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to create/send NOTIFY", status);
+ pj_list_erase(uapres);
+ return PJ_FALSE;
+ }
+
+
+ /* Done: */
+
+ return PJ_TRUE;
+}
+
+
+/* Refresh subscription (e.g. when our online status has changed) */
+static void refresh_server_subscription()
+{
+ pjsua_srv_pres *uapres;
+
+ uapres = pjsua.pres_srv_list.next;
+
+ while (uapres != &pjsua.pres_srv_list) {
+
+ pjsip_pres_status pres_status;
+ pjsip_tx_data *tdata;
+
+ pjsip_pres_get_status(uapres->sub, &pres_status);
+ if (pres_status.info[0].basic_open != pjsua.online_status) {
+ pres_status.info[0].basic_open = pjsua.online_status;
+ pjsip_pres_set_status(uapres->sub, &pres_status);
+
+ if (pjsua.quit_flag) {
+ pj_str_t reason = { "noresource", 10 };
+ if (pjsip_pres_notify(uapres->sub,
+ PJSIP_EVSUB_STATE_TERMINATED, NULL,
+ &reason, &tdata)==PJ_SUCCESS)
+ {
+ pjsip_pres_send_request(uapres->sub, tdata);
+ }
+ } else {
+ if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS)
+ pjsip_pres_send_request(uapres->sub, tdata);
+ }
+ }
+
+ uapres = uapres->next;
+ }
+}
+
+
+
+/* **************************************************************************
+ * THE FOLLOWING PART HANDLES CLIENT SUBSCRIPTION
+ * **************************************************************************
+ */
+
+/* Callback called when *client* subscription state has changed. */
+static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsua_buddy *buddy;
+
+ PJ_UNUSED_ARG(event);
+
+ buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+ if (buddy) {
+ PJ_LOG(3,(THIS_FILE,
+ "Presence subscription to %s is %s",
+ buddy->uri.ptr,
+ pjsip_evsub_get_state_name(sub)));
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ buddy->sub = NULL;
+ buddy->status.info_cnt = 0;
+ pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+ }
+ }
+}
+
+/* Callback called when we receive NOTIFY */
+static void pjsua_evsub_on_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)
+{
+ pjsua_buddy *buddy;
+
+ buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+ if (buddy) {
+ /* Update our info. */
+ pjsip_pres_get_status(sub, &buddy->status);
+
+ if (buddy->status.info_cnt) {
+ PJ_LOG(3,(THIS_FILE, "%s is %s",
+ buddy->uri.ptr,
+ (buddy->status.info[0].basic_open?"online":"offline")));
+ } else {
+ PJ_LOG(3,(THIS_FILE, "No presence info for %s",
+ buddy->uri.ptr));
+ }
+ }
+
+ /* 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);
+}
+
+
+/* Event subscription callback. */
+static pjsip_evsub_user pres_callback =
+{
+ &pjsua_evsub_on_state,
+
+ NULL, /* on_tsx_state: don't care about transaction state. */
+
+ NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless
+ * we want to authenticate
+ */
+
+ &pjsua_evsub_on_rx_notify,
+
+ NULL, /* on_client_refresh: Use default behaviour, which is to
+ * refresh client subscription. */
+
+ NULL, /* on_server_timeout: Use default behaviour, which is to send
+ * NOTIFY to terminate.
+ */
+};
+
+
+/* It does what it says.. */
+static void subscribe_buddy_presence(unsigned index)
+{
+ pjsip_dialog *dlg;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &pjsua.local_uri,
+ &pjsua.contact_uri,
+ &pjsua.buddies[index].uri,
+ NULL, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to create dialog", status);
+ return;
+ }
+
+ status = pjsip_pres_create_uac( dlg, &pres_callback,
+ &pjsua.buddies[index].sub);
+ if (status != PJ_SUCCESS) {
+ pjsua.buddies[index].sub = NULL;
+ pjsua_perror("Unable to create presence client", status);
+ return;
+ }
+
+ pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
+ &pjsua.buddies[index]);
+
+ status = pjsip_pres_initiate(pjsua.buddies[index].sub, 60, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua.buddies[index].sub = NULL;
+ pjsua_perror("Unable to create initial SUBSCRIBE", status);
+ return;
+ }
+
+ status = pjsip_pres_send_request(pjsua.buddies[index].sub, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua.buddies[index].sub = NULL;
+ pjsua_perror("Unable to send initial SUBSCRIBE", status);
+ return;
+ }
+
+ PJ_TODO(DESTROY_DIALOG_ON_ERROR);
+}
+
+
+/* It does what it says... */
+static void unsubscribe_buddy_presence(unsigned index)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (pjsua.buddies[index].sub == NULL)
+ return;
+
+ if (pjsip_evsub_get_state(pjsua.buddies[index].sub) ==
+ PJSIP_EVSUB_STATE_TERMINATED)
+ {
+ pjsua.buddies[index].sub = NULL;
+ return;
+ }
+
+ status = pjsip_pres_initiate( pjsua.buddies[index].sub, 0, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_pres_send_request( pjsua.buddies[index].sub, tdata );
+
+ if (status == PJ_SUCCESS) {
+
+ //pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
+ // NULL);
+ //pjsua.buddies[index].sub = NULL;
+
+ } else {
+ pjsua_perror("Unable to unsubscribe presence", status);
+ }
+}
+
+
+/* It does what it says.. */
+static void refresh_client_subscription(void)
+{
+ unsigned i;
+
+ for (i=0; i<pjsua.buddy_cnt; ++i) {
+
+ if (pjsua.buddies[i].monitor && !pjsua.buddies[i].sub) {
+ subscribe_buddy_presence(i);
+
+ } else if (!pjsua.buddies[i].monitor && pjsua.buddies[i].sub) {
+ unsubscribe_buddy_presence(i);
+
+ }
+ }
+}
+
+
+/*
+ * Init presence
+ */
+pj_status_t pjsua_pres_init()
+{
+ pj_status_t status;
+
+ status = pjsip_endpt_register_module( pjsua.endpt, &mod_pjsua_pres);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to register pjsua presence module", status);
+ }
+
+ return status;
+}
+
+/*
+ * Refresh presence
+ */
+void pjsua_pres_refresh(void)
+{
+ refresh_client_subscription();
+ refresh_server_subscription();
+}
+
+
+/*
+ * Shutdown presence.
+ */
+void pjsua_pres_shutdown(void)
+{
+ unsigned i;
+
+ pjsua.online_status = 0;
+ for (i=0; i<pjsua.buddy_cnt; ++i) {
+ pjsua.buddies[i].monitor = 0;
+ }
+ pjsua_pres_refresh();
+}
+
+/*
+ * Dump presence status.
+ */
+void pjsua_pres_dump(void)
+{
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
+ if (pj_list_empty(&pjsua.pres_srv_list)) {
+ PJ_LOG(3,(THIS_FILE, " - none - "));
+ } else {
+ struct pjsua_srv_pres *uapres;
+
+ uapres = pjsua.pres_srv_list.next;
+ while (uapres != &pjsua.pres_srv_list) {
+
+ PJ_LOG(3,(THIS_FILE, " %10s %s",
+ pjsip_evsub_get_state_name(uapres->sub),
+ uapres->remote));
+
+ uapres = uapres->next;
+ }
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
+ if (pjsua.buddy_cnt == 0) {
+ PJ_LOG(3,(THIS_FILE, " - no buddy list - "));
+ } else {
+ for (i=0; i<pjsua.buddy_cnt; ++i) {
+
+ if (pjsua.buddies[i].sub) {
+ PJ_LOG(3,(THIS_FILE, " %10s %s",
+ pjsip_evsub_get_state_name(pjsua.buddies[i].sub),
+ pjsua.buddies[i].uri.ptr));
+ } else {
+ PJ_LOG(3,(THIS_FILE, " %10s %s",
+ "(null)",
+ pjsua.buddies[i].uri.ptr));
+ }
+ }
+ }
+}
+