From 32b524a5a9aebb00287118dbd6ffa1a946c579e6 Mon Sep 17 00:00:00 2001
From: Eloi BAIL <eloi.bail@savoirfairelinux.com>
Date: Wed, 3 Jul 2013 17:02:04 -0400
Subject: [PATCH] Presence : missing files

Some files were not added in the previous commit
---
 daemon/src/sip/sipbuddy.cpp     | 554 ++++++++++++++++++++++++++++++++
 daemon/src/sip/sipbuddy.h       | 120 +++++++
 daemon/src/sip/sipvoip_pres.cpp | 341 ++++++++++++++++++++
 daemon/src/sip/sipvoip_pres.h   | 101 ++++++
 4 files changed, 1116 insertions(+)
 create mode 100644 daemon/src/sip/sipbuddy.cpp
 create mode 100644 daemon/src/sip/sipbuddy.h
 create mode 100644 daemon/src/sip/sipvoip_pres.cpp
 create mode 100644 daemon/src/sip/sipvoip_pres.h

diff --git a/daemon/src/sip/sipbuddy.cpp b/daemon/src/sip/sipbuddy.cpp
new file mode 100644
index 0000000000..6ed316e1c8
--- /dev/null
+++ b/daemon/src/sip/sipbuddy.cpp
@@ -0,0 +1,554 @@
+/*
+ *  Copyright (C) 2012, 2013 LOTES TM LLC
+ *  Author : Andrey Loukhnov <aol.nnov@gmail.com>
+ *
+ *  This file is a part of pult5-voip
+ *
+ *  pult5-voip 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify pult5-voip, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+#include <pj/log.h>
+#include <pj/rand.h>
+#include <pj/log.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_types.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_endpoint.h>
+#include <string>
+#include <pj/pool.h>
+#include <pjsip/sip_ua_layer.h>
+#include <pjsip-simple/evsub.h>
+
+#include "sipbuddy.h"
+#include "sipaccount.h"
+#include "sipvoiplink.h"
+
+#define PJSUA_BUDDY_SUB_TERM_REASON_LEN 32
+#define PJSUA_PRES_TIMER 300
+#define THIS_FILE "sipbuddy.cpp"
+
+#include "logger.h"
+//extern pjsip_module mod_ua_;
+//extern pjsip_endpoint *endpt_;
+
+int modId;
+static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry) {
+    (void) th;
+    SIPBuddy *b = (SIPBuddy *) entry->user_data;
+    b->updatePresence();
+}
+
+/* Callback called when *client* subscription state has changed. */
+static void sflphoned_evsub_on_state(pjsip_evsub *sub, pjsip_event *event) {
+    SIPBuddy *buddy;
+
+    PJ_UNUSED_ARG(event);
+
+    /* Note: #937: no need to acuire 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 = (SIPBuddy *) pjsip_evsub_get_mod_data(sub, modId);
+    if (buddy) {
+        buddy->incLock();
+        PJ_LOG(4,
+                (THIS_FILE, "Presence subscription to '%s' is '%s'", buddy->getURI().c_str(), pjsip_evsub_get_state_name(sub)?pjsip_evsub_get_state_name(sub):"null"));
+//	pj_log_push_indent();
+
+        if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+            int resub_delay = -1;
+
+//            const pj_str_t *pjTermReason = pjsip_evsub_get_termination_reason(sub);
+//            std::string termReason(pjTermReason->ptr,
+//                    pjTermReason->slen > PJSUA_BUDDY_SUB_TERM_REASON_LEN?
+//                        PJSUA_BUDDY_SUB_TERM_REASON_LEN:
+//                        pjTermReason->slen
+//            );
+            pj_strdup_with_null(buddy->pool, &buddy->term_reason, pjsip_evsub_get_termination_reason(sub));
+//            buddy->setTermReason(termReason);
+//            buddy->setTermCode(200);
+            buddy->term_code = 200;
+
+            /* Determine whether to resubscribe automatically */
+            if (event && event->type == PJSIP_EVENT_TSX_STATE) {
+                const pjsip_transaction *tsx = event->body.tsx_state.tsx;
+                if (pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) {
+//		    buddy->setTermCode(tsx->status_code);
+                    buddy->term_code = tsx->status_code;
+                    switch (tsx->status_code) {
+                        case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST:
+                            /* 481: we refreshed too late? resubscribe
+                             * immediately.
+                             */
+                            /* But this must only happen when the 481 is received
+                             * on subscription refresh request. We MUST NOT try to
+                             * resubscribe automatically if the 481 is received
+                             * on the initial SUBSCRIBE (if server returns this
+                             * response for some reason).
+                             */
+                            if (buddy->dlg->remote.contact)
+                                resub_delay = 500;
+                            break;
+                    }
+                } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {
+                    if (buddy->isTermReason("deactivated") || buddy->isTermReason("timeout")) {
+                        /* deactivated: The subscription has been terminated,
+                         * but the subscriber SHOULD retry immediately with
+                         * a new subscription.
+                         */
+                        /* timeout: The subscription has been terminated
+                         * because it was not refreshed before it expired.
+                         * Clients MAY re-subscribe immediately. The
+                         * "retry-after" parameter has no semantics for
+                         * "timeout".
+                         */
+                        resub_delay = 500;
+                    } else if (buddy->isTermReason("probation") || buddy->isTermReason("giveup")) {
+                        /* probation: The subscription has been terminated,
+                         * but the client SHOULD retry at some later time.
+                         * If a "retry-after" parameter is also present, the
+                         * client SHOULD wait at least the number of seconds
+                         * specified by that parameter before attempting to re-
+                         * subscribe.
+                         */
+                        /* giveup: The subscription has been terminated because
+                         * the notifier could not obtain authorization in a
+                         * timely fashion.  If a "retry-after" parameter is
+                         * also present, the client SHOULD wait at least the
+                         * number of seconds specified by that parameter before
+                         * attempting to re-subscribe; otherwise, the client
+                         * MAY retry immediately, but will likely get put back
+                         * into pending state.
+                         */
+                        const pjsip_sub_state_hdr *sub_hdr;
+                        pj_str_t sub_state = {
+                                "Subscription-State",
+                                18 };
+                        const pjsip_msg *msg;
+
+                        msg = event->body.tsx_state.src.rdata->msg_info.msg;
+                        sub_hdr = (const pjsip_sub_state_hdr*) pjsip_msg_find_hdr_by_name(msg, &sub_state, NULL);
+                        if (sub_hdr && sub_hdr->retry_after > 0)
+                            resub_delay = sub_hdr->retry_after * 1000;
+                    }
+
+                }
+            }
+
+            /* For other cases of subscription termination, if resubscribe
+             * timer is not set, schedule with default expiration (plus minus
+             * some random value, to avoid sending SUBSCRIBEs all at once)
+             */
+            if (resub_delay == -1) {
+//		pj_assert(PJSUA_PRES_TIMER >= 3);
+                resub_delay = PJSUA_PRES_TIMER * 1000;// - 2500 + (pj_rand() % 5000);
+            }
+            buddy->sub = sub;
+            buddy->rescheduleTimer(PJ_TRUE, resub_delay);
+        }/* else {
+             This will clear the last termination code/reason
+            buddy->term_code = 0;
+            buddy->term_reason.ptr = NULL;
+        }*/
+
+        /* Clear subscription */
+       /* if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+            pjsip_evsub_terminate(buddy->sub, PJ_FALSE); // = NULL;
+            buddy->status.info_cnt = 0;
+            buddy->dlg = NULL;
+            buddy->rescheduleTimer(PJ_FALSE, 0);
+//            pjsip_evsub_set_mod_data(sub, modId, NULL);
+        }*/
+
+//	pj_log_pop_indent();
+        buddy->decLock();
+    }
+}
+
+/* Callback when transaction state has changed. */
+static void sflphoned_evsub_on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) {
+
+    SIPBuddy *buddy;
+    pjsip_contact_hdr *contact_hdr;
+
+    /* Note: #937: no need to acuire 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 = (SIPBuddy *) pjsip_evsub_get_mod_data(sub, modId);
+    if (!buddy) {
+        return;
+    }
+    buddy->incLock();
+
+    /* We only use this to update buddy's Contact, when it's not
+     * set.
+     */
+    if (buddy->contact.slen != 0) {
+        /* Contact already set */
+        buddy->decLock();
+        return;
+    }
+
+    /* Only care about 2xx response to outgoing SUBSCRIBE */
+    if (tsx->status_code / 100 != 2 || tsx->role != PJSIP_UAC_ROLE || event->type != PJSIP_EVENT_RX_MSG
+            || pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method()) != 0) {
+        buddy->decLock();
+        return;
+    }
+
+    /* Find contact header. */
+    contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, PJSIP_H_CONTACT,
+            NULL);
+    if (!contact_hdr || !contact_hdr->uri) {
+        buddy->decLock();
+        return;
+    }
+
+    buddy->contact.ptr = (char*) pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE);
+    buddy->contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, contact_hdr->uri, buddy->contact.ptr,
+            PJSIP_MAX_URL_SIZE);
+    if (buddy->contact.slen < 0)
+        buddy->contact.slen = 0;
+
+    buddy->decLock();
+}
+
+/* Callback called when we receive NOTIFY */
+static void sflphoned_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) {
+    SIPBuddy *buddy;
+
+    /* Note: #937: no need to acuire 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 = (SIPBuddy *) pjsip_evsub_get_mod_data(sub, modId);
+    if (!buddy) {
+        return;
+    }
+    buddy->incLock();
+    /* Update our info. */
+    pjsip_pres_get_status(sub, &buddy->status);
+//    if (buddy->status.info[0] != NULL ) {
+#if 0 // ELOI no existing chanState element in info
+    std::string chanState(buddy->status.info[0].chanState.ptr, buddy->status.info[0].chanState.slen);
+#endif
+    std::string basic(buddy->status.info[0].basic_open ? "open" : "closed");
+//ELOI Call here the callback for presence changement
+    ERROR("\n-----------------\n presenceStateChange for %s to %s \n-----------------\n", buddy->getURI().c_str(),basic.c_str());
+//    }
+
+    /* 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);
+
+    buddy->decLock();
+}
+
+SIPBuddy::SIPBuddy(const std::string& uri_, SIPAccount *acc_) :
+        acc(acc_),
+        uri(pj_str(strdup(uri_.c_str()))),
+        //buddy_id(-1)
+        contact(pj_str(strdup(acc->getFromUri().c_str()))),
+        display(),
+        dlg(NULL),
+//        host ()
+        monitor(false),
+        name(),
+        cp_(),
+        pool(0),
+//        port(0)
+        status(),
+        sub(NULL),
+        term_code(0),
+        term_reason(),
+        timer(),
+        user_data(NULL),
+        lock_count(0) {
+    pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0);
+    pool = pj_pool_create(&cp_.factory, "buddy", 512, 512, NULL);
+}
+
+SIPBuddy::~SIPBuddy() {
+    while(lock_count >0) {
+        usleep(200);
+    }
+    PJ_LOG(4, ("Destroying buddy object with uri %s", uri.ptr));
+    monitor = false;
+    rescheduleTimer(PJ_FALSE, 0);
+    updatePresence();
+
+    pj_pool_release(pool);
+}
+
+bool SIPBuddy::isSubscribed() {
+    return this->monitor;
+}
+
+std::string SIPBuddy::getURI() {
+    std::string buddyURI(uri.ptr, uri.slen);
+    return buddyURI;
+}
+
+bool SIPBuddy::isTermReason(std::string reason) {
+    std::string myReason(term_reason.ptr, term_reason.slen);
+    return !myReason.compare(reason);
+}
+
+void SIPBuddy::rescheduleTimer(bool reschedule, unsigned msec) {
+    if (timer.id) {
+        //	pjsua_cancel_timer(&timer);
+        pjsip_endpt_cancel_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer);
+        timer.id = PJ_FALSE;
+    }
+
+    if (reschedule) {
+        pj_time_val delay;
+
+        PJ_LOG(4,
+                (THIS_FILE, "Resubscribing buddy  %.*s in %u ms (reason: %.*s)", uri.slen, uri.ptr, msec, (int) term_reason.slen, term_reason.ptr));
+        monitor = PJ_TRUE;
+        pj_timer_entry_init(&timer, 0, this, &buddy_timer_cb);
+        delay.sec = 0;
+        delay.msec = msec;
+        pj_time_val_normalize(&delay);
+
+        //	if (pjsua_schedule_timer(&timer, &delay)==PJ_SUCCESS)
+
+        if (pjsip_endpt_schedule_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer, &delay) == PJ_SUCCESS) {
+//            timer.id = PJ_TRUE;
+        }
+    }
+}
+
+pj_status_t SIPBuddy::updatePresence() {
+
+    if (!monitor) {
+        /* unsubscribe */
+        pjsip_tx_data *tdata;
+        pj_status_t retStatus;
+
+        if (sub == NULL) {
+            return PJ_SUCCESS;
+        }
+
+        if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+            pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL;
+            return PJ_SUCCESS;
+        }
+
+        PJ_LOG(5, (THIS_FILE, "Buddy %s: unsubscribing..", uri.ptr));
+
+        retStatus = pjsip_pres_initiate(sub, 300, &tdata);
+        if (retStatus == PJ_SUCCESS) {
+            //	pjsua_process_msg_data(tdata, NULL);
+            if (/*pjsua_var.ua_cfg.user_agent.slen &&*/
+            tdata->msg->type == PJSIP_REQUEST_MSG) {
+                const pj_str_t STR_USER_AGENT = {
+                        "User-Agent",
+                        10 };
+                pj_str_t ua = pj_str(strdup(acc->getUserAgentName().c_str()));
+                pjsip_hdr *h;
+                h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &ua);
+                pjsip_msg_add_hdr(tdata->msg, h);
+            }
+            retStatus = pjsip_pres_send_request(sub, tdata);
+        }
+
+        if (retStatus != PJ_SUCCESS && sub) {
+            pjsip_pres_terminate(sub, PJ_FALSE);
+            pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL;
+            PJ_LOG(4, (THIS_FILE, "Unable to unsubscribe presence", status));
+        }
+//        pjsip_evsub_set_mod_data(sub, modId, NULL);
+        return PJ_SUCCESS;
+    }
+
+#if 0
+    if (sub && sub->dlg) { //do not bother if already subscribed
+//        PJ_LOG(4, (THIS_FILE, "Buddy %s: already subscribed", uri.ptr));
+//        return PJ_SUCCESS;
+        pjsip_evsub_terminate(sub, PJ_FALSE);
+    }
+#endif
+
+    //subscribe
+    pjsip_evsub_user pres_callback;
+//    pj_pool_t *tmp_pool = NULL;
+
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    /* Event subscription callback. */
+    pj_bzero(&pres_callback, sizeof(pres_callback));
+    pres_callback.on_evsub_state = &sflphoned_evsub_on_state;
+    pres_callback.on_tsx_state = &sflphoned_evsub_on_tsx_state;
+    pres_callback.on_rx_notify = &sflphoned_evsub_on_rx_notify;
+
+    PJ_LOG(4, (THIS_FILE, "Buddy %s: subscribing presence,using account %s..", uri.ptr, acc->getAccountID().c_str()));
+
+    /* Generate suitable Contact header unless one is already set in
+     * the account
+     */
+#if 0
+    if (acc->contact.slen) {
+        contact = acc->contact;
+    } else {
+        tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256);
+
+        status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
+                acc_id, &buddy->uri);
+        if (status != PJ_SUCCESS) {
+            pjsua_perror(THIS_FILE, "Unable to generate Contact header",
+                    status);
+            pj_pool_release(tmp_pool);
+            pj_log_pop_indent();
+            return;
+        }
+    }
+#endif
+    /* Create UAC dialog */
+    pj_str_t from = pj_str(strdup(acc->getFromUri().c_str()));
+    status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from, &contact, &uri, NULL, &dlg);
+#if 1
+    if (status != PJ_SUCCESS) {
+        //pjsua_perror(THIS_FILE, "Unable to create dialog",
+         //       status);
+        ERROR("Unable to create dialog \n");
+        //if (tmp_pool) pj_pool_release(tmp_pool);
+        //pj_log_pop_indent();
+        return PJ_FALSE;
+    }
+#endif
+    //ELOI add credential for auth - otherwise subscription was failing
+    if (acc->hasCredentials() and pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) {
+      ERROR("Could not initialize credentials for invite session authentication");
+    }
+    // E.B add credential for auth
+
+    /* Increment the dialog's lock otherwise when presence session creation
+     * fails the dialog will be destroyed prematurely.
+     */
+    pjsip_dlg_inc_lock(dlg);
+    DEBUG("ELOI > pjsip_pres_create_uac ");
+
+    status = pjsip_pres_create_uac(dlg, &pres_callback, PJSIP_EVSUB_NO_EVENT_ID, &sub);
+    if (status != PJ_SUCCESS) {
+        DEBUG("ELOI > pjsip_pres_create_uac : KO");
+        pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL;
+        PJ_LOG(4, (THIS_FILE, "Unable to create presence client", status));
+        /* This should destroy the dialog since there's no session
+         * referencing it
+         */
+        if (dlg) {
+            pjsip_dlg_dec_lock(dlg);
+        }
+//        if (tmp_pool) pj_pool_release(tmp_pool);
+//        pj_log_pop_indent();
+        return PJ_SUCCESS;
+    }
+    DEBUG("ELOI > pjsip_pres_create_uac : OK");
+#if 0
+    /* If account is locked to specific transport, then lock dialog
+     * to this transport too.
+     */
+    if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+        pjsip_tpselector tp_sel;
+
+        pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+        pjsip_dlg_set_transport(buddy->dlg, &tp_sel);
+    }
+
+    /* Set route-set */
+    if (!pj_list_empty(&acc->route_set)) {
+        pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set);
+    }
+
+    /* Set credentials */
+    if (acc->cred_cnt) {
+        pjsip_auth_clt_set_credentials(&buddy->dlg->auth_sess,
+                acc->cred_cnt, acc->cred);
+    }
+
+    /* Set authentication preference */
+    pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref);
+#endif
+    modId = ((SIPVoIPLink*) acc->getVoIPLink())->getModId();
+    pjsip_evsub_set_mod_data(sub, modId, this);
+
+    status = pjsip_pres_initiate(sub, -1, &tdata);
+    if (status != PJ_SUCCESS) {
+        if (dlg)
+            pjsip_dlg_dec_lock(dlg);
+        if (sub) {
+            pjsip_pres_terminate(sub, PJ_FALSE);
+        }
+        pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL;
+        PJ_LOG(4, (THIS_FILE, "Unable to create initial SUBSCRIBE", status));
+//        if (tmp_pool) pj_pool_release(tmp_pool);
+//        pj_log_pop_indent();
+        return PJ_SUCCESS;
+    }
+
+//    pjsua_process_msg_data(tdata, NULL);
+
+    status = pjsip_pres_send_request(sub, tdata);
+    if (status != PJ_SUCCESS) {
+        if (dlg)
+            pjsip_dlg_dec_lock(dlg);
+        if (sub) {
+            pjsip_pres_terminate(sub, PJ_FALSE);
+            sub = NULL;
+        }
+
+        PJ_LOG(4, (THIS_FILE, "Unable to send initial SUBSCRIBE", status));
+//        if (tmp_pool) pj_pool_release(tmp_pool);
+//        pj_log_pop_indent();
+        return PJ_SUCCESS;
+    }
+
+    pjsip_dlg_dec_lock(dlg);
+//    if (tmp_pool) pj_pool_release(tmp_pool);
+    return PJ_SUCCESS;
+}
+
+void SIPBuddy::subscribe() {
+    monitor = true;
+    updatePresence();
+}
+
+void SIPBuddy::unsubscribe() {
+    monitor = false;
+    updatePresence();
+}
diff --git a/daemon/src/sip/sipbuddy.h b/daemon/src/sip/sipbuddy.h
new file mode 100644
index 0000000000..b1b2798dfe
--- /dev/null
+++ b/daemon/src/sip/sipbuddy.h
@@ -0,0 +1,120 @@
+/*
+ *  Copyright (C) 2012, 2013 LOTES TM LLC
+ *  Author : Andrey Loukhnov <aol.nnov@gmail.com>
+ *
+ *  This file is a part of pult5-voip
+ *
+ *  pult5-voip 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify pult5-voip, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+#ifndef SIPBUDDY_H
+#define	SIPBUDDY_H
+
+//#define THIS_FILE sipbuddy.cpp
+#include <pjsip-simple/presence.h>
+#include <pj/timer.h>
+#include <pj/pool.h>
+#include <string>
+// ELOI #include "misc/noncopyable.h"
+
+#include <pjsip-simple/evsub.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_transport.h>
+
+
+class SIPAccount;
+
+static void sflphoned_evsub_on_state(pjsip_evsub *sub, pjsip_event *event);
+static void sflphoned_evsub_on_tsx_state(pjsip_evsub *sub,
+        pjsip_transaction *tsx,
+        pjsip_event *event);
+static void sflphoned_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);
+static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry);
+
+class SIPBuddy {
+public:
+    SIPBuddy(const std::string &uri, SIPAccount *acc);
+    ~SIPBuddy();
+    void subscribe();
+    void unsubscribe();
+    bool isSubscribed();
+    std::string getURI();
+
+    friend void sflphoned_evsub_on_state( pjsip_evsub *sub, pjsip_event *event);
+    friend void sflphoned_evsub_on_tsx_state(pjsip_evsub *sub,
+				     pjsip_transaction *tsx,
+				     pjsip_event *event);
+    friend void sflphoned_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);
+    friend void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry);
+    void incLock() {
+        lock_count++;
+    }
+    void decLock() {
+        lock_count--;
+    }
+
+private:
+    //ELOI FIXME NON_COPYABLE(SIPBuddy);
+
+    void rescheduleTimer(bool reschedule, unsigned msec);
+
+    pj_status_t updatePresence();
+
+    bool isTermReason(std::string);
+    unsigned getTermCode();
+
+    SIPAccount          *acc;
+    pj_str_t		 uri;	    /**< Buddy URI.			*/
+//    unsigned		 buddy_id;	    /**< Buddy index.			*/
+    pj_str_t		 contact;   /**< Contact learned from subscrp.	*/
+    pj_str_t		 display;   /**< Buddy display name.		*/
+    pjsip_dialog	*dlg;	    /**< The underlying dialog.		*/
+//    pj_str_t		 host;	    /**< Buddy host.			*/
+    pj_bool_t		 monitor;   /**< Should we monitor?		*/
+    pj_str_t		 name;	    /**< Buddy name.			*/
+    pj_caching_pool      cp_;
+    pj_pool_t		*pool;	    /**< Pool for this buddy.		*/
+//    unsigned		 port;	    /**< Buddy port.			*/
+    pjsip_pres_status	 status;    /**< Buddy presence status.		*/
+    pjsip_evsub		*sub;	    /**< Buddy presence subscription	*/
+    unsigned		 term_code; /**< Subscription termination code	*/
+    pj_str_t		 term_reason;/**< Subscription termination reason */
+    pj_timer_entry	 timer;	    /**< Resubscription timer		*/
+    void		*user_data; /**< Application data. */
+    int lock_count;
+};
+
+#endif	/* SIPBUDDY_H */
diff --git a/daemon/src/sip/sipvoip_pres.cpp b/daemon/src/sip/sipvoip_pres.cpp
new file mode 100644
index 0000000000..a203a6a8b5
--- /dev/null
+++ b/daemon/src/sip/sipvoip_pres.cpp
@@ -0,0 +1,341 @@
+/*
+ *  Copyright (C) 2012б 2013 LOTES TM LLC
+ *  Author : Andrey Loukhnov <aol.nnov@gmail.com>
+ *
+ *  This file is a part of pult5-voip
+ *
+ *  pult5-voip 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify pult5-voip, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string>
+#include <bits/stringfwd.h>
+#include <bits/basic_string.h>
+
+#define THIS_FILE "sipvoip_pres.cpp"
+#include "pjsip/sip_dialog.h"
+#include "pjsip/sip_msg.h"
+#include "pjsip/sip_ua_layer.h"
+#include "pjsip/sip_transaction.h"
+
+#include"pjsip-simple/evsub.h"
+#include"pjsip-simple/presence.h"
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/compat/string.h>
+
+#include "sipvoip_pres.h"
+#include "logger.h"
+#include "sipaccount.h"
+#include "sipcall.h"
+#include "sipvoiplink.h"
+#include "manager.h"
+
+
+/* Callback called when *server* subscription state has changed. */
+void pres_evsub_on_srv_state(pjsip_evsub *sub, pjsip_event *event) {
+    /*****************/
+#if 0 // ELOI : useless code ?
+    pjsip_rx_data *rdata = event->body.rx_msg.rdata;
+    if(!rdata) {
+        PJ_LOG(4, (THIS_FILE, "no rdata in presence"));
+        ERROR("no rdata in presence");
+        return;
+    }
+//    std::string name(rdata->msg_info.to->name.ptr, rdata->msg_info.to->name.slen);
+//    std::string servername(rdata->msg_info.from->name.ptr, rdata->msg_info.from->name.slen);
+#if 0 //ELOI modify account management
+    std::string accountId = "IP2IP"; //Manager::instance().getAccountIdFromNameAndServer(name, servername);
+    SIPAccount *acc = (SIPAccount *) Phone::instance().getAccountById(accountId);
+#else
+    std::string accountId = "IP2IP";//ELOI TODO is it proper accountId ?
+    SIPAccount *acc = Manager::instance().getSipAccount(accountId);
+#endif
+    /******************/
+    PJ_UNUSED_ARG(event);
+//#if 0
+    ServerPresenceSub *server;
+//    PJSUA_LOCK();
+    server = (ServerPresenceSub *) pjsip_evsub_get_mod_data(sub,
+            ((SIPVoIPLink*) (acc->getVoIPLink()))->getModId() /*my_mod_pres.id*/);
+    PJ_LOG(4, (THIS_FILE, "Server subscription to %s is %s", server->remote, pjsip_evsub_get_state_name(sub)));
+
+    if (server) {
+        pjsip_evsub_state state;
+
+        PJ_LOG(4, (THIS_FILE, "Server subscription to %s is %s", server->remote, pjsip_evsub_get_state_name(sub)));
+
+        state = pjsip_evsub_get_state(sub);
+
+#if 0
+        if (false pjsua_var.ua_cfg.cb.on_srv_subscribe_state) {
+            pj_str_t from;
+
+            from = server->dlg->remote.info_str;
+            (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id,
+                    uapres, &from,
+                    state, event);
+        }
+#endif
+
+        if (state == PJSIP_EVSUB_STATE_TERMINATED) {
+            pjsip_evsub_set_mod_data(sub, ((SIPVoIPLink*) (acc->getVoIPLink()))->getModId(), NULL);
+//	    pj_list_erase(uapres);
+#if 0 //ELOI modify account management
+            ((SIPAccount*) (Phone::instance().getAccountById(server->accId)))->removerServerSubscription(server);
+#else
+            // ELOI ((SIPAccount*) (Manager::instance().getSipAccount(server->accId)))->removerServerSubscription(server);
+#endif
+        }
+    }
+//    PJSUA_UNLOCK();
+#endif
+}
+
+pj_bool_t my_pres_on_rx_request(pjsip_rx_data *rdata) {
+  return PJ_FALSE;
+
+#if 0 // ELOI useless code ?
+    pjsip_method *method = &rdata->msg_info.msg->line.req.method;
+    pj_str_t *str = &method->name;
+    std::string request(str->ptr, str->slen);
+    DEBUG("MY PRESENCE: %s ", request.c_str());
+    DEBUG("\n----------------------BUFFER--------------- \n %s \n ------------------------------------- ",rdata->msg_info.msg_buf);
+    PJ_LOG(1, (THIS_FILE, "> my_pres_on_rx_request" ));
+    pj_str_t contact;
+    pj_status_t status;
+    pjsip_dialog *dlg;
+    pjsip_evsub *sub;
+    pjsip_evsub_user pres_cb;
+    pjsip_pres_status pres_status;
+    pjsip_tx_data *tdata;
+    pjsip_expires_hdr *expires_hdr;
+//    int expires;
+    pjsip_status_code st_code;
+    pj_str_t reason;
+    pjsua_msg_data msg_data;
+    pjsip_evsub_state ev_state;
+
+    if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method()) != 0){
+        ERROR("pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method()) != 0)" );
+        ERROR("Method id = %d",rdata->msg_info.msg->line.req.method.id);
+        ERROR("Method name = %s",rdata->msg_info.msg->line.req.method.name);
+        return PJ_FALSE;
+    }
+
+    std::string name(rdata->msg_info.to->name.ptr, rdata->msg_info.to->name.slen);
+    std::string server(rdata->msg_info.from->name.ptr, rdata->msg_info.from->name.slen);
+
+    //std::string accountId = "IP2IP"; //Manager::instance().getAccountIdFromNameAndServer(name, server);
+    std::string accountId = "Account:1371589892"; //Manager::instance().getAccountIdFromNameAndServer(name, server);
+#if 0 //ELOI modify  account management
+    SIPAccount *acc = (SIPAccount *) Phone::instance().getAccountById(accountId);
+#else
+    SIPAccount *acc = (SIPAccount *) Manager::instance().getSipAccount(accountId);
+#endif
+    pjsip_endpoint *endpt = ((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint();
+
+    contact = pj_str(strdup(acc->getContactHeader().c_str()));
+
+    /* Create UAS dialog: */
+    status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, &contact, &dlg);
+    if (status != PJ_SUCCESS) {
+
+        char errmsg[PJ_ERR_MSG_SIZE];
+
+        pj_strerror(status, errmsg, sizeof(errmsg));
+        PJ_LOG(1, (THIS_FILE, "Unable to create UAS dialog for subscription: %s [status=%d]", errmsg, status));
+        //	PJSUA_UNLOCK();
+        pjsip_endpt_respond_stateless(endpt, rdata, 400, NULL, NULL, NULL);
+        return PJ_TRUE;
+    }
+
+    /* Init callback: */
+    pj_bzero(&pres_cb, 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) {
+        int code = PJSIP_ERRNO_TO_SIP_STATUS(status);
+        pjsip_tx_data *tdata;
+
+        PJ_LOG(1, (THIS_FILE, "Unable to create server subscription %d", status));
+
+        if (code == 599 || code > 699 || code < 300) {
+            code = 400;
+        }
+
+        status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata);
+        if (status == PJ_SUCCESS) {
+            status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
+        }
+
+//	PJSUA_UNLOCK();
+        return PJ_TRUE;
+    }
+
+    /* Attach our data to the subscription: */
+
+//    uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres);
+//    uapres->sub = sub;
+//    uapres->
+    char* remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
+//    uapres->acc_id = acc_id;
+//    uapres->dlg = dlg;
+    status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri, remote, PJSIP_MAX_URL_SIZE);
+
+    //ACHTUNG !!
+    pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, dlg->local.info->uri, contact.ptr, PJSIP_MAX_URL_SIZE);
+    ServerPresenceSub *serverSub = new ServerPresenceSub(sub, remote, accountId, dlg);
+
+    if (status < 1)
+        pj_ansi_strcpy(remote, "<-- url is too long-->");
+    else
+        remote[status] = '\0';
+
+//    pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list);
+    int modId = ((SIPVoIPLink*) (acc->getVoIPLink()))->getModId();
+    pjsip_evsub_set_mod_data(sub, modId/*my_mod_pres.id*/, serverSub);
+    PJ_LOG(1, (THIS_FILE, "addServerSubscription" ));
+    // ELOI acc->addServerSubscription(serverSub);
+    /* Add server subscription to the list: */
+//    pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres);
+    /* Capture the value of Expires header. */
+    expires_hdr = (pjsip_expires_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+    if (expires_hdr)
+        serverSub->setExpires(expires_hdr->ivalue);
+    else
+        serverSub->setExpires(-1);
+
+    st_code = (pjsip_status_code) 200;
+    reason = pj_str("OK");
+//    pjsua_msg_data_init(&msg_data);
+    pj_bzero(&msg_data, sizeof(msg_data));
+    pj_list_init(&msg_data.hdr_list);
+    pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL);
+    pj_list_init(&msg_data.multipart_parts);
+
+    /* Create and send 2xx response to the SUBSCRIBE request: */
+    status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list);
+    if (status != PJ_SUCCESS) {
+        PJ_LOG(1, (THIS_FILE, "Unable to accept presence subscription %d", status));
+//	pj_list_erase(uapres);
+        pjsip_pres_terminate(sub, PJ_FALSE);
+//	PJSUA_UNLOCK();
+        return PJ_FALSE;
+    }
+//TODO: handle rejection case pjsua_pers.c:956
+
+    /* If code is 200, send NOTIFY now */
+#if 0
+    if (st_code == 200) {
+        pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE,
+                NULL, NULL, PJ_TRUE, &msg_data);
+    }
+#endif
+    pj_bzero(&pres_status, sizeof(pres_status));
+    pres_status.info_cnt = 1;
+    pres_status.info[0].basic_open = true;
+    /*std::string currState = ((SIPVoIPLink*) acc->getVoIPLink())->getChannelState();
+    pres_status.info[0].chanState = pj_str(strdup(currState.c_str()));*/
+
+    std::string contactWithAngles =  acc->getFromUri();
+    contactWithAngles.erase(contactWithAngles.find('>'));
+
+    int semicolon = contactWithAngles.find_first_of(":");
+    std::string contactWithoutAngles = contactWithAngles.substr(semicolon + 1);
+    pj_str_t contt = pj_str(strdup(contactWithoutAngles.c_str()));
+//    acc->get
+
+    pj_memcpy(&pres_status.info[0].contact, &contt, sizeof(pj_str_t));
+
+//    pres_status.info[0].id = pj_str("000005"); //TODO: tuple id
+
+    pjsip_pres_set_status(sub, &pres_status);
+
+    ev_state = PJSIP_EVSUB_STATE_ACTIVE;
+    if (serverSub->expires == 0)
+        ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+
+    /* Create and send the NOTIFY to active subscription: */
+    pj_str_t stateStr = pj_str("");
+    tdata = NULL;
+    status = pjsip_pres_notify(sub, ev_state, &stateStr, &reason, &tdata);
+    if (status == PJ_SUCCESS) {
+        /* Force removal of message body if msg_body==FALSE */
+
+        //pjsua_process_msg_data(tdata, msg_data);
+        if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+            const pj_str_t STR_USER_AGENT = {
+                    "User-Agent",
+                    10 };
+            pjsip_hdr *h;
+            pj_str_t ua = pj_str("SFLPhone");
+            h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &ua);
+            pjsip_msg_add_hdr(tdata->msg, h);
+        }
+
+        const pjsip_hdr *hdr;
+        hdr = msg_data.hdr_list.next;
+        while (hdr && hdr != &msg_data.hdr_list) {
+            pjsip_hdr *new_hdr;
+
+            new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr);
+            PJ_LOG(1, ("adding header", new_hdr->name.ptr));
+            pjsip_msg_add_hdr(tdata->msg, new_hdr);
+
+            hdr = hdr->next;
+        }
+        if (msg_data.content_type.slen && msg_data.msg_body.slen) {
+//            pjsip_media_type ctype;
+            pjsip_msg_body *body;
+            pj_str_t type = pj_str("application");
+            pj_str_t subtype = pj_str("pidf+xml");
+
+//            pjsua_parse_media_type(tdata->pool, &msg_data->content_type, &ctype);
+            body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data.msg_body);
+            tdata->msg->body = body;
+        }
+        // process_msg_data
+
+        status = pjsip_pres_send_request(sub, tdata);
+    }
+
+    if (status != PJ_SUCCESS) {
+        PJ_LOG(1, (THIS_FILE, "Unable to create/send NOTIFY %d", status));
+//	pj_list_erase(srv_pres);
+        pjsip_pres_terminate(sub, PJ_FALSE);
+//	PJSUA_UNLOCK();
+        return status;
+    }
+
+    return PJ_TRUE;
+#endif
+}
diff --git a/daemon/src/sip/sipvoip_pres.h b/daemon/src/sip/sipvoip_pres.h
new file mode 100644
index 0000000000..537c2667c4
--- /dev/null
+++ b/daemon/src/sip/sipvoip_pres.h
@@ -0,0 +1,101 @@
+/*
+ *  Copyright (C) 2012б 2013 LOTES TM LLC
+ *  Author : Andrey Loukhnov <aol.nnov@gmail.com>
+ *
+ *  This file is a part of pult5-voip
+ *
+ *  pult5-voip 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  Additional permission under GNU GPL version 3 section 7:
+ *
+ *  If you modify pult5-voip, or any covered work, by linking or
+ *  combining it with the OpenSSL project's OpenSSL library (or a
+ *  modified version of that library), containing parts covered by the
+ *  terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC
+ *  grants you additional permission to convey the resulting work.
+ *  Corresponding Source for a non-source form of such a combination
+ *  shall include the source code for the parts of OpenSSL used as well
+ *  as that of the covered work.
+ */
+
+#ifndef SIPVOIP_PRES_H
+#define	SIPVOIP_PRES_H
+#include "pjsip/sip_types.h"
+#include "pjsip/sip_module.h"
+#include "pjsip/sip_msg.h"
+#include "pjsip/sip_multipart.h"
+
+//PJ_BEGIN_DECL
+
+
+ extern pj_bool_t my_pres_on_rx_request(pjsip_rx_data *rdata);
+
+ static pjsip_module my_mod_pres = {
+    NULL, NULL, /* prev, next.		*/
+    { "mod-lotes-presence", 18}, /* Name.		*/
+    -1, /* Id			*/
+    //        PJSIP_MOD_PRIORITY_APPLICATION, /* Priority	        */
+    PJSIP_MOD_PRIORITY_DIALOG_USAGE,
+    NULL, /* load()		*/
+    NULL, /* start()		*/
+    NULL, /* stop()		*/
+    NULL, /* unload()		*/
+    &my_pres_on_rx_request, /* on_rx_request()	*/
+    NULL, /* on_rx_response()	*/
+    NULL, /* on_tx_request.	*/
+    NULL, /* on_tx_response()	*/
+    NULL, /* on_tsx_state()	*/
+
+};
+
+ struct pjsua_msg_data
+{
+    /**
+     * Additional message headers as linked list. Application can add
+     * headers to the list by creating the header, either from the heap/pool
+     * or from temporary local variable, and add the header using
+     * linked list operation. See pjsip_apps.c for some sample codes.
+     */
+    pjsip_hdr	hdr_list;
+
+    /**
+     * MIME type of optional message body.
+     */
+    pj_str_t	content_type;
+
+    /**
+     * Optional message body to be added to the message, only when the
+     * message doesn't have a body.
+     */
+    pj_str_t	msg_body;
+
+    /**
+     * Content type of the multipart body. If application wants to send
+     * multipart message bodies, it puts the parts in \a parts and set
+     * the content type in \a multipart_ctype. If the message already
+     * contains a body, the body will be added to the multipart bodies.
+     */
+    pjsip_media_type  multipart_ctype;
+
+    /**
+     * List of multipart parts. If application wants to send multipart
+     * message bodies, it puts the parts in \a parts and set the content
+     * type in \a multipart_ctype. If the message already contains a body,
+     * the body will be added to the multipart bodies.
+     */
+    pjsip_multipart_part multipart_parts;
+};
+
+//PJ_END_DECL
+#endif	/* SIPVOIP_PRES_H */
-- 
GitLab