/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ccsip_publish.h"
#include "phntask.h"
#include "sip_common_transport.h"
#include "ccsip_task.h"
#include "ccsip_callinfo.h"
#include "ccsip_macros.h"
#include "util_string.h"
#include "cpr_rand.h"
#include "platform_api.h"
#include "ccsip_register.h"
#include "ccsip_reldev.h"
#include "debug.h"

static sll_handle_t s_PCB_list = NULL; // signly linked list handle of PCBs
static int outgoingPublishes;

static boolean sipSPISendPublish(ccsip_publish_cb_t *pcb_p, boolean authen);
static void free_pending_reqs (sll_handle_t list);

/**
 * This function will generate a new handle and return it.
 *
 * @return  a non-zero integer value
 */
static pub_handle_t generate_new_pub_handle (void)
{
    static pub_handle_t handle = 0;

    handle++;
    if (handle == NULL_PUBLISH_HANDLE) {
        handle++;
    }
    return handle;
}

/**
 * This function will check if it is the PCB we are looking for based on the key.
 * This is only invoked by sll_find().
 *
 * @param[in] key - pointer to the key.
 * @param[in] data - pointer to a node data.
 *
 * @return  SLL_MATCH_FOUND if the node matches.
 *          Otherwise, SLL_MATCH_NOT_FOUND is returned.
 *
 *  @pre     (key != NULL) and (data != NULL)
 */
static sll_match_e is_matching_pcb (void *key, void *data)
{
    pub_handle_t pub_handle = *((pub_handle_t *)key);
    ccsip_publish_cb_t *pcb_p = (ccsip_publish_cb_t *)data;

    if (pub_handle == pcb_p->pub_handle) {
        return SLL_MATCH_FOUND;
    }
    return SLL_MATCH_NOT_FOUND;
}

/**
 * This function will create a PCB. It will also create the PCB linked list.
 *
 * @return  NULL if there are no resources to create a PCB.
 *          Otherwise,  pointer to a new PCB is returned.
 *
 *  @pre     (key != NULL) and (data != NULL)
 */
static ccsip_publish_cb_t *get_new_pcb (void)
{
    ccsip_publish_cb_t *pcb_p;

    /*
     * If PCB list is not created yet, create the list.
     */
    if (s_PCB_list == NULL) {
        s_PCB_list = sll_create(is_matching_pcb);
        if (s_PCB_list == NULL) {
            return NULL;
        }
    }

    pcb_p = (ccsip_publish_cb_t *)cpr_malloc(sizeof(ccsip_publish_cb_t));
    if (pcb_p == NULL) {
        return NULL;
    }
    memset(pcb_p, 0, sizeof(ccsip_publish_cb_t));
    pcb_p->pub_handle = generate_new_pub_handle();
    pcb_p->hb.cb_type = PUBLISH_CB;
    pcb_p->hb.dn_line = 1; // for now set it to primary line. This will change when we do line based PUBLISH.
    /*
     * set up dest & src ip addr and port number.
     */
    ccsip_common_util_set_dest_ipaddr_port(&pcb_p->hb);
    ccsip_common_util_set_src_ipaddr(&pcb_p->hb);
    pcb_p->hb.local_port = sipTransportGetListenPort(pcb_p->hb.dn_line, NULL);
    pcb_p->retry_timer.timer = cprCreateTimer("PUBLISH retry timer",
                                              SIP_PUBLISH_RETRY_TIMER,
                                              TIMER_EXPIRATION,
                                              sip_msgq);
    if (pcb_p->retry_timer.timer == NULL) {
        cpr_free(pcb_p);
        return NULL;
    }
    pcb_p->pending_reqs = sll_create(NULL);
    if (pcb_p->pending_reqs == NULL) {
        (void)cprDestroyTimer(pcb_p->retry_timer.timer);
        cpr_free(pcb_p);
        return NULL;
    }
    (void) sll_append(s_PCB_list, pcb_p);

    return pcb_p;
}

/**
 * This function will find matching PCB by the key in the PCB list.
 *
 * @param[in] pub_handle - publish handle.
 *
 * @return  NULL if there is no matching PCB
 *          Otherwise, pointer to the found PCB is returned.
 *
 *  @pre    (pub_handle > 0) and (s_PCB_list != NULL)
 */
static ccsip_publish_cb_t *find_pcb (pub_handle_t pub_handle)
{
    ccsip_publish_cb_t *pcb_p;

    pcb_p = (ccsip_publish_cb_t *)sll_find(s_PCB_list, &pub_handle);

    return pcb_p;
}

/**
 * This function will find matching PCB by the SIP Call-ID in the PCB list.
 *
 * @param[in] callID_p - SIP Call-ID
 *
 * @return  NULL if there is no matching PCB
 *          Otherwise, pointer to the found PCB is returned.
 *
 *  @pre    (callID_p != NULL)
 */
static ccsip_publish_cb_t *find_pcb_by_sip_callid (const char *callID_p)
{
    ccsip_publish_cb_t *pcb_p;

    pcb_p = (ccsip_publish_cb_t *)sll_next(s_PCB_list, NULL);
    while (pcb_p != NULL) {
        if (strncmp(callID_p, pcb_p->hb.sipCallID, (sizeof(pcb_p->hb.sipCallID) -1)) == 0) {
            return pcb_p;
        }
        pcb_p = (ccsip_publish_cb_t *)sll_next(s_PCB_list, pcb_p);
    }
    return NULL;
}

/**
 * This function will free up the PCB resources and removes it from the PCB list.
 *
 * @param[in] pcb_p -  pointer to a PCB.
 *
 * @return  none
 *
 *  @pre    (pcb_p != NULL)
 */
static void free_pcb (ccsip_publish_cb_t *pcb_p)
{
   if (pcb_p->hb.authen.authorization != NULL) {
       cpr_free(pcb_p->hb.authen.authorization);
   }
   if (pcb_p->hb.authen.sip_authen != NULL) {
       sippmh_free_authen(pcb_p->hb.authen.sip_authen);
   }

    cpr_free(pcb_p->entity_tag);
    free_pending_reqs(pcb_p->pending_reqs);
    (void)cprDestroyTimer(pcb_p->retry_timer.timer);
    free_event_data(pcb_p->hb.event_data_p);
    (void)sll_remove(s_PCB_list, (void *)pcb_p);
    cpr_free(pcb_p);
}

/**
 * This function will append the application request in a pending list.
 *
 * @param[in] pcb_p -  pointer to a PCB.
 * @param[in] msg_p -  pointer to an application request.
 *
 * @return  TRUE if it is successful.
 *          Otherwise, FALSE is returned.
 *
 *  @pre    (pcb_p != NULL) and (msg_p != NULL)
 *  @post   ((slink_list_t *)s_pres_req_list->count++)
 */
static boolean append_pending_reqs (ccsip_publish_cb_t *pcb_p, pub_req_t *msg_p)
{
    pub_req_t *temp_msg_p;

    temp_msg_p = (pub_req_t *)cpr_malloc(sizeof(pub_req_t));
    if (temp_msg_p == NULL) {
        return FALSE;
    }
    (*temp_msg_p) = (*msg_p);
    (void) sll_append(pcb_p->pending_reqs, temp_msg_p);
    return TRUE;
}

/**
 * This function will free up entire list of pending requests
 *
 * @param[in] list -  pending requests list handle
 *
 * @return none
 *
 */
static void free_pending_reqs (sll_handle_t list)
{
    pub_req_t *msg_p;

    if (list == NULL)  {
        return;
    }

    msg_p = (pub_req_t *)sll_next(list, NULL);
    while (msg_p != NULL) {
        free_event_data(msg_p->event_data_p);
        (void)sll_remove(list, (void *)msg_p);
        cpr_free(msg_p);
        msg_p = (pub_req_t *)sll_next(list, NULL);
    }
    sll_destroy(list);
}

/**
 * This function will send the PUBLSIH request response to the application.
 *
 * @param[in] resp_code -  this also includes error responses that start at 1000
 * @param[in] pub_handle - handle to a PCB
 * @param[in] app_handle - application generated handle to this PUBLISH context.
 * @param[in] callback_task - task in which the application resides.
 * @param[in] resp_msg_id - messageID posted to the task.
 *
 * @return none
 */
static void send_resp_to_app (int resp_code, pub_handle_t pub_handle, pub_handle_t app_handle,
                              cc_srcs_t callback_task, int resp_msg_id)
{
    static const char fname[] = "send_resp_to_app";
    pub_rsp_t rsp;

    rsp.resp_code = resp_code;
    rsp.pub_handle = pub_handle;
    rsp.app_handle = app_handle;
    if (publish_int_response(&rsp, callback_task, resp_msg_id) != CC_RC_SUCCESS) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Failed to post PUBLISH response to the application", fname);
    }
}

/**
 * This function will process SIPSPI_EV_CC_PUBLISH posted by applications.
 * If there is an outstanding transaction, it will hold the request in the pending list.
 *
 * @param[in] buf -  inter-process message buffer.
 *
 * @return SIP_OK if it successfully processes the request.
 *         SIP_ERROR if it fails to process the request.
 *         SIP_DEFER if it defers the processing.
 *
 * @note  This will not free buf.
 *
 * @pre    (buf != NULL)
 */
int publish_handle_ev_app_publish (cprBuffer_t buf)
{
    static const char fname[] = "publish_handle_ev_app_publish";
    pub_req_t *msg_p = (pub_req_t *)buf;
    ccsip_publish_cb_t *pcb_p;


    /*
     * If this is initial PUBLISH, allocate a PCB.
     * Otherwise, look up for PCB based on pub_handle.
     */
    if (msg_p->pub_handle != NULL_PUBLISH_HANDLE) {
        pcb_p = find_pcb(msg_p->pub_handle);

        if (pcb_p == NULL) {
            send_resp_to_app(PUBLISH_FAILED_NOCONTEXT, msg_p->pub_handle, msg_p->app_handle,
                             msg_p->callback_task, msg_p->resp_msg_id);
            free_event_data(msg_p->event_data_p);
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX
                              "Modification PUBLISH cannot be sent as the PCB is missing\n",
                              fname);
            return SIP_ERROR;
        }

        /*
         * Check if there is an outstanding transaction.
         * if so, put the request in pending request queue.
         */
        if (pcb_p->outstanding_trxn == TRUE) {
            if (append_pending_reqs(pcb_p, msg_p) == TRUE) {
                CCSIP_DEBUG_TASK(DEB_F_PREFIX"deffering as there is an outstanding transaction",
                                 DEB_F_PREFIX_ARGS(SIP_PUB, fname));
                return SIP_DEFER;
            }
            /* free up PCB and respond with error */
            free_pcb (pcb_p);
            send_resp_to_app(PUBLISH_FAILED_NORESOURCE, msg_p->pub_handle, msg_p->app_handle,
                             msg_p->callback_task, msg_p->resp_msg_id);
            free_event_data(msg_p->event_data_p);
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Queueing outgoing PUBLISH request failed", fname);
            return SIP_ERROR;
        }
        /*
         * if event_data_p is NULL, this is terminating PUBLISH.
         * otherwise, it is a modifying PUBLISH.
         */
        free_event_data(pcb_p->hb.event_data_p);
        pcb_p->hb.event_data_p = msg_p->event_data_p;
        if ((msg_p->event_data_p == NULL) && (msg_p->expires == 0)) { // removing PUBLISH
            pcb_p->hb.orig_expiration = 0;
        }
    } else {
        pcb_p = get_new_pcb();
        if (pcb_p == NULL) {
            send_resp_to_app(PUBLISH_FAILED_NORESOURCE, msg_p->pub_handle, msg_p->app_handle,
                             msg_p->callback_task, msg_p->resp_msg_id);
            free_event_data(msg_p->event_data_p);
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX"PCB allocation failed", fname);
            return SIP_ERROR;
        }
        pcb_p->app_handle = msg_p->app_handle;
        sstrncpy(pcb_p->ruri, msg_p->ruri, MAX_URI_LENGTH);
        sstrncpy(pcb_p->esc, msg_p->esc, MAX_URI_LENGTH);
        pcb_p->hb.orig_expiration = msg_p->expires;
        pcb_p->hb.event_type = msg_p->event_type;
        pcb_p->hb.event_data_p = msg_p->event_data_p;
        pcb_p->callback_task = msg_p->callback_task;
        pcb_p->resp_msg_id = msg_p->resp_msg_id;
    }

    pcb_p->hb.authen.cred_type = 0;

    if (sipSPISendPublish(pcb_p, FALSE) == TRUE) {
        pcb_p->outstanding_trxn = TRUE;
        outgoingPublishes++;
        CCSIP_DEBUG_TASK(DEB_F_PREFIX"PUBLISH request sent successfully", DEB_F_PREFIX_ARGS(SIP_PUB, fname));
        return SIP_OK;
    }

    /* free up PCB and respond with error */
    free_pcb (pcb_p);
    send_resp_to_app(PUBLISH_FAILED_SEND, msg_p->pub_handle, msg_p->app_handle,
                     msg_p->callback_task, msg_p->resp_msg_id);
    CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Failed to send PUBLISH request", fname);
    return SIP_ERROR;

}


/**
 * This function will create the PUBLISH message and send it. it also starts the retry timer.
 *
 * @param[in] pcb_p -  pointer to PCB
 * @param[in] authen - boolean that indicates whether to add authorization header.
 *
 * @return TRUE if it successfully sent PUBLISH
 *         Otherwise, FALSE is returned
 *
 * @pre    (pcb_p != NULL)
 */
static boolean sipSPISendPublish (ccsip_publish_cb_t *pcb_p, boolean authen)
{
    static const char fname[] = "sipSPISendPublish";
    static uint32_t   cseq = 0;
    char              dest_sip_addr_str[MAX_IPADDR_STR_LEN];
    char             *domainloc;
    char              src_addr_str[MAX_IPADDR_STR_LEN];
    char              sip_temp_str[MAX_SIP_URL_LENGTH];
    char              sip_temp_tag[MAX_SIP_URL_LENGTH];
    uint8_t           mac_address[MAC_ADDRESS_LENGTH];
    char              via[SIP_MAX_VIA_LENGTH];
    int               max_forwards_value = 70;
    static uint16_t   count = 1;
    sipMessage_t     *request = NULL;
    int               timeout = 0;

    request = GET_SIP_MESSAGE();
    if (!request) {
        return FALSE;
    }

    /*
     * Populate full RURI if it is not yet. Sometimes, applications may only provide user part.
     */
    if (pcb_p->full_ruri[0] == 0) {
        sstrncpy(pcb_p->full_ruri, "sip:", MAX_SIP_URL_LENGTH);
        sstrncat(pcb_p->full_ruri, pcb_p->ruri, MAX_SIP_URL_LENGTH - sizeof("sip:"));
        /* check if it has host part */
        domainloc = strchr(pcb_p->full_ruri, '@');
        if (domainloc == NULL) {
            domainloc = pcb_p->full_ruri + strlen(pcb_p->full_ruri);
            if ((domainloc - pcb_p->full_ruri) < (MAX_SIP_URL_LENGTH - 1)) {
                /* Do not include @ when there is no user part */
                if (pcb_p->ruri[0] != '\0') {
                    *domainloc++ = '@';
                }
                ipaddr2dotted(dest_sip_addr_str, &pcb_p->hb.dest_sip_addr);
                sstrncpy(domainloc, dest_sip_addr_str,
                         MAX_SIP_URL_LENGTH - (domainloc - (pcb_p->full_ruri)));
            }
        }
    }

    ipaddr2dotted(src_addr_str, &pcb_p->hb.src_addr);

    // Add request line
    if (HSTATUS_SUCCESS != sippmh_add_request_line(request,
                                                   sipGetMethodString(sipMethodPublish),
                                                   pcb_p->full_ruri, SIP_VERSION)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Request line", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add local Via
    snprintf(via, sizeof(via), "SIP/2.0/%s %s:%d;%s=%s%.8x",
             sipTransportGetTransportType(1, TRUE, NULL),
             src_addr_str, pcb_p->hb.local_port, VIA_BRANCH,
             VIA_BRANCH_START, (unsigned int) cpr_rand());
    if (HSTATUS_SUCCESS != sippmh_add_text_header(request, SIP_HEADER_VIA, via)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding VIA header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add To Header
    snprintf(sip_temp_str, MAX_SIP_URL_LENGTH, "<%s>", pcb_p->full_ruri);
    if (HSTATUS_SUCCESS != sippmh_add_text_header(request, SIP_HEADER_TO, sip_temp_str)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding TO header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add From Header.
    sstrncat(sip_temp_str, ";tag=", MAX_SIP_URL_LENGTH - strlen(sip_temp_str));
    sip_util_make_tag(sip_temp_tag);
    sstrncat(sip_temp_str, sip_temp_tag, MAX_SIP_URL_LENGTH - strlen(sip_temp_str));
    if (HSTATUS_SUCCESS != sippmh_add_text_header(request, SIP_HEADER_FROM, sip_temp_str)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding FROM header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add Call-ID Header.
    platform_get_wired_mac_address(mac_address);
    count++;
    snprintf(pcb_p->hb.sipCallID, MAX_SIP_CALL_ID, "%.4x%.4x-%.4x%.4x-%.8x-%.8x@%s", // was MAX_SIP_URL_LENGTH
                 mac_address[0] * 256 + mac_address[1],
                 mac_address[2] * 256 + mac_address[3],
                 mac_address[4] * 256 + mac_address[5], count,
                 (unsigned int) cpr_rand(),
                 (unsigned int) cpr_rand(),
                 src_addr_str);
    if (HSTATUS_SUCCESS != sippmh_add_text_header(request, SIP_HEADER_CALLID, pcb_p->hb.sipCallID)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding CALLID header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add Contact header. Contact header is not needed as per RFC. BUT CCM needs it.
    snprintf(sip_temp_str, MAX_SIP_URL_LENGTH, "<sip:%.4x%.4x%.4x@%s:%d>",
             mac_address[0] * 256 + mac_address[1],
             mac_address[2] * 256 + mac_address[3],
             mac_address[4] * 256 + mac_address[5],
             src_addr_str, pcb_p->hb.local_port);
    if (HSTATUS_SUCCESS != sippmh_add_text_header(request, SIP_HEADER_CONTACT, sip_temp_str)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Contact header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add Cseq.
    cseq++;
    if (cseq == 0) {
        cseq = 1;
    }
    if (HSTATUS_SUCCESS != sippmh_add_cseq(request, sipGetMethodString(sipMethodPublish), cseq)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding CSEQ header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add UserAgent header
    (void) sippmh_add_text_header(request, SIP_HEADER_USER_AGENT,
                                  sipHeaderUserAgent);


    // Add SIP-If-Match header.
    if (pcb_p->entity_tag != NULL) {
        if (HSTATUS_SUCCESS != sippmh_add_text_header(request, SIP_HEADER_SIPIFMATCH,
                                                      pcb_p->entity_tag)) {
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Event header", fname);
            free_sip_message(request);
            return (FALSE);
        }
    }

    // Add Expires Header
    if (HSTATUS_SUCCESS != sippmh_add_int_header(request, SIP_HEADER_EXPIRES,
                pcb_p->hb.orig_expiration)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Expires header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // Add max-forwards header
    config_get_value(CFGID_SIP_MAX_FORWARDS, &max_forwards_value,
                     sizeof(max_forwards_value));
    if (HSTATUS_SUCCESS !=
        sippmh_add_int_header(request, SIP_HEADER_MAX_FORWARDS,
            max_forwards_value)) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Max-Forwards header", fname);
        free_sip_message(request);
        return (FALSE);
    }

    // add Authorization header
    if (authen) {
        if (HSTATUS_SUCCESS != sippmh_add_text_header(request, AUTHOR_HDR(pcb_p->hb.authen.status_code),
                                                      pcb_p->hb.authen.authorization)) {
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Authorization header", fname);
            free_sip_message(request);
            return (FALSE);
        }
    }

    // Add content, if any
    if (pcb_p->hb.event_data_p) {
        if (add_content(pcb_p->hb.event_data_p, request, fname) == FALSE) {
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Content", fname);
            free_sip_message(request);
            return (FALSE);
        }
    } else {
        if (HSTATUS_SUCCESS != sippmh_add_int_header(request, SIP_HEADER_CONTENT_LENGTH, 0)) {
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Error in adding Content-Len", fname);
            free_sip_message(request);
            return (FALSE);
        }
    }

    ccsip_common_util_set_retry_settings(&pcb_p->hb, &timeout);
    if (sipTransportCreateSendMessage(NULL, request, sipMethodPublish,
                                      &(pcb_p->hb.dest_sip_addr),
                                      (int16_t) pcb_p->hb.dest_sip_port,
                                      FALSE, TRUE, timeout, pcb_p,
                                      RELDEV_NO_STORED_MSG) < 0) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"failed to send PUBLISH message", fname);
        return (FALSE);
    }

    return (TRUE);

}

/**
 * This function will handle retry-timer expiration
 *
 * @param[in] handle -  handle to PCB
 *
 * @return 0 if it is successful in handling the timer
 *         Otherwise, -1 is returned
 *
 * @pre    (handle != 0)
 */
int publish_handle_retry_timer_expire (uint32_t handle)
{
    static const char fname[] = "publish_handle_retry_timer_expire";
    pub_handle_t pub_handle = handle;
    ccsip_publish_cb_t *pcb_p;
    uint32_t        max_retx = 0;
    uint32_t        time_t1 = 0;
    uint32_t        time_t2 = 0;
    uint32_t        timeout = 0;

    /*
     * find the PCB
     */
    pcb_p = find_pcb(pub_handle);
    if (pcb_p == NULL) {
        /* No PCB. So do nothing. */
        return 0;
    }
    if (pcb_p->hb.retx_flag == FALSE) {
        /* probably we got some response. so do nothing */
        return 0;
    }
    config_get_value(CFGID_SIP_RETX, &max_retx, sizeof(max_retx));
    if (max_retx > MAX_NON_INVITE_RETRY_ATTEMPTS) {
        max_retx = MAX_NON_INVITE_RETRY_ATTEMPTS;
    }
    if (pcb_p->hb.retx_counter < max_retx) {
        pcb_p->hb.retx_counter++;
        config_get_value(CFGID_TIMER_T1, &time_t1, sizeof(time_t1));
        timeout = time_t1 * (1 << pcb_p->hb.retx_counter);
        config_get_value(CFGID_TIMER_T2, &time_t2, sizeof(time_t2));
        if (timeout > time_t2) {
            timeout = time_t2;
        }
        CCSIP_DEBUG_TASK(DEB_F_PREFIX"Resending message #%d",
                         DEB_F_PREFIX_ARGS(SIP_PUB, fname), pcb_p->hb.retx_counter);
            if (sipTransportSendMessage(NULL,
                                        pcb_p->retry_timer.message_buffer,
                                        pcb_p->retry_timer.message_buffer_len,
                                        pcb_p->retry_timer.message_type,
                                        &(pcb_p->retry_timer.ipaddr),
                                        pcb_p->retry_timer.port,
                                        FALSE, TRUE, timeout, pcb_p) < 0) {
                /* free up PCB and respond with error */
                send_resp_to_app(PUBLISH_FAILED_SEND, pcb_p->pub_handle, pcb_p->app_handle,
                                 pcb_p->callback_task, pcb_p->resp_msg_id);
                free_pcb (pcb_p);
                CCSIP_DEBUG_ERROR(SIP_F_PREFIX"failed to send message", fname);
                return (-1);
            }

    } else {
        /*
         * send timeout response and free up PCB
         */
        send_resp_to_app(SIP_CLI_ERR_REQ_TIMEOUT, pcb_p->pub_handle, pcb_p->app_handle,
                         pcb_p->callback_task, pcb_p->resp_msg_id);
        free_pcb (pcb_p);
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"reached MAX retries", fname);
    }
    return 0;
}

/**
 * This function will check if wee need to send refresh PUBLISH.
 * If so, it will send refresh PUBLISH
 *
 * @param[in] none
 *
 * @return none
 */
void publish_handle_periodic_timer_expire (void)
{
    static const char fname[] = "publish_handle_periodic_timer_expire";
    int                 delta = 0;
    ccsip_publish_cb_t *pcb_p;
    pub_req_t           msg;

    config_get_value(CFGID_TIMER_SUBSCRIBE_DELTA, &delta,
                     sizeof(delta));
    pcb_p = (ccsip_publish_cb_t *)sll_next(s_PCB_list, NULL);
    while (pcb_p != NULL) {
        if (pcb_p->outstanding_trxn == FALSE) {
            if (pcb_p->hb.expires >= TMR_PERIODIC_PUBLISH_INTERVAL) {
                pcb_p->hb.expires -= TMR_PERIODIC_PUBLISH_INTERVAL;
            }
            if (pcb_p->hb.expires <= (delta + TMR_PERIODIC_PUBLISH_INTERVAL)) {
                CCSIP_DEBUG_TASK(DEB_F_PREFIX"sending REFRESH PUBLISH", DEB_F_PREFIX_ARGS(SIP_PUB, fname));
                memset (&msg, 0, sizeof(msg));
                /* refresh is triggered by NULL event data and non-zero expires value */
                msg.pub_handle = pcb_p->pub_handle;
                msg.expires = pcb_p->hb.orig_expiration;
                (void)publish_handle_ev_app_publish(&msg);
            }
        }
        pcb_p = (ccsip_publish_cb_t *)sll_next(s_PCB_list, pcb_p);
    }
}

/**
 * This function will process the response to PUBLISH request sent by us.
 *
 * @param[in] pSipMessage -  pointer to received SIP response msg
 *
 * @return SIP_OK if it successfully processes the response.
 *         SIP_ERROR if it fails to process the response.
 *
 * @pre    (pSipMessage != NULL)
 */
int publish_handle_ev_sip_response (sipMessage_t *pSipMessage)
{
    static const char fname[] = "publish_handle_ev_sip_response";
    const char     *callID_p = NULL;
    int             response_code = 0;
    const char     *expires = NULL;
    const char     *sip_etag = NULL;
    long            expiry_time;
    pub_req_t      *msg_p;
    ccsip_publish_cb_t *pcb_p;
    int entity_tag_size;

    callID_p = sippmh_get_cached_header_val(pSipMessage, CALLID);
    if (!callID_p) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"Cannot obtain SIP Call ID.", fname);
        return SIP_ERROR;
    }

    /*
     * Find PCB by Call-ID. The Call-IDs generated by the phone ae unique.
     */
    pcb_p = find_pcb_by_sip_callid(callID_p);
    if (pcb_p == NULL) {
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"No matching PCB found", fname);
        return SIP_ERROR;
    }

    /*
     * Cancel the retry_timer and set the retx_flag to FALSE.
     */
    sip_platform_msg_timer_subnot_stop(&(pcb_p->retry_timer));
    pcb_p->hb.retx_flag = FALSE;

    // Parse the return code
    (void) sipGetResponseCode(pSipMessage, &response_code);

    if (response_code >= 200) {
        pcb_p->outstanding_trxn = FALSE;
    }


    if ((response_code == SIP_CLI_ERR_UNAUTH) ||
        (response_code == SIP_CLI_ERR_PROXY_REQD)) {
        CCSIP_DEBUG_TASK(DEB_F_PREFIX"Authentication Required", DEB_F_PREFIX_ARGS(SIP_PUB, fname));
        if (ccsip_common_util_generate_auth(pSipMessage, &pcb_p->hb, SIP_METHOD_PUBLISH,
                                            response_code, pcb_p->full_ruri) == TRUE) {
            if (sipSPISendPublish(pcb_p, TRUE) == TRUE) {
                pcb_p->outstanding_trxn = TRUE;
                CCSIP_DEBUG_TASK(DEB_F_PREFIX"sent request with Auth header", DEB_F_PREFIX_ARGS(SIP_PUB, fname));
                return SIP_OK;
             }
        }
        /*
         * Since we failed to resend the PUBLISH request, free up the PCB and let the app know.
         */
        send_resp_to_app(PUBLISH_FAILED_SEND, pcb_p->pub_handle, pcb_p->app_handle,
                         pcb_p->callback_task, pcb_p->resp_msg_id);
        free_pcb (pcb_p);
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"failed to respond to auth challenge", fname);
        return SIP_ERROR;
    }

    /*
     * if response code is 423, grab Min-Expires and send new PUBLISH with new expires value
     */
    if (response_code == SIP_CLI_ERR_INTERVAL_TOO_SMALL) {
        expires = sippmh_get_header_val(pSipMessage,
                                        (const char *)SIP_HEADER_MIN_EXPIRES,
                                        NULL);
        if (expires) {
            expiry_time = strtoul(expires, NULL, 10);
            //ensure new Min-Expires is > what we set before in Expires
            if ((long) expiry_time > pcb_p->hb.expires) {
                pcb_p->hb.expires = expiry_time;
                pcb_p->hb.orig_expiration = expiry_time;
            }
            if (sipSPISendPublish(pcb_p, FALSE) == TRUE) {
                pcb_p->outstanding_trxn = TRUE;
                CCSIP_DEBUG_TASK(DEB_F_PREFIX"sent request with increased expires", DEB_F_PREFIX_ARGS(SIP_PUB, fname));
                return SIP_OK;
             }
        }
        /*
         * Since we failed to resend the PUBLISH request, free up the PCB and let the app know.
         */
        send_resp_to_app(PUBLISH_FAILED_SEND, pcb_p->pub_handle, pcb_p->app_handle,
                         pcb_p->callback_task, pcb_p->resp_msg_id);
        free_pcb (pcb_p);
        CCSIP_DEBUG_ERROR(SIP_F_PREFIX"failed to respond to 423", fname);
        return SIP_ERROR;
    }

    /*
     * if the response_code is > 299, free up the PCB and let the app know.
     */
    if (response_code > 299) {
        send_resp_to_app(response_code, pcb_p->pub_handle, pcb_p->app_handle,
                         pcb_p->callback_task, pcb_p->resp_msg_id);
        free_pcb (pcb_p);
        CCSIP_DEBUG_TASK(DEB_F_PREFIX"received %d response", DEB_F_PREFIX_ARGS(SIP_PUB, fname), response_code);
        return SIP_OK;
    }

    /*
     * if the response is < 200, do nothing.
     */
    if (response_code < 200) {
        CCSIP_DEBUG_TASK(DEB_F_PREFIX"received %d response", DEB_F_PREFIX_ARGS(SIP_PUB, fname), response_code);
        return SIP_OK;
    }

    /*
     * If it is PUBLISH remove operation, free up PCB
     */
    if (pcb_p->hb.orig_expiration == 0) {
        send_resp_to_app(response_code, pcb_p->pub_handle, pcb_p->app_handle,
                         pcb_p->callback_task, pcb_p->resp_msg_id);
        free_pcb (pcb_p);
        CCSIP_DEBUG_TASK(DEB_F_PREFIX"removed PCB as this was a terminating PUBLISH", DEB_F_PREFIX_ARGS(SIP_PUB, fname));
        return SIP_OK;
    }

    /*
     * extract Expires and SIP-ETag headers and save them.
     */
    expires = sippmh_get_header_val(pSipMessage, SIP_HEADER_EXPIRES, NULL);
    if (expires) {
        expiry_time = strtoul(expires, NULL, 10);
        pcb_p->hb.expires = expiry_time;
    }
    sip_etag = sippmh_get_header_val(pSipMessage, SIP_HEADER_SIPETAG, NULL);
    if (sip_etag != NULL) {
        cpr_free(pcb_p->entity_tag);
        entity_tag_size = strlen(sip_etag) + 1;
        pcb_p->entity_tag = cpr_malloc(entity_tag_size);
        if (pcb_p->entity_tag != NULL) {
            sstrncpy(pcb_p->entity_tag, sip_etag, entity_tag_size);
        } else {
            free_pcb (pcb_p);
            send_resp_to_app(PUBLISH_FAILED_NORESOURCE, pcb_p->pub_handle, pcb_p->app_handle,
                             pcb_p->callback_task, pcb_p->resp_msg_id);
            CCSIP_DEBUG_ERROR(SIP_F_PREFIX"memory allocation failed", fname);
            return SIP_ERROR;

        }
    }

    /*
     * If there are no pending requests, provide the response to the application.
     */
    msg_p = (pub_req_t *)sll_next(pcb_p->pending_reqs, NULL);
    if (msg_p != NULL) {
        (void)sll_remove(pcb_p->pending_reqs, msg_p);
        (void)publish_handle_ev_app_publish(msg_p);
        cpr_free(msg_p);
        return SIP_OK;
    }
    send_resp_to_app(response_code, pcb_p->pub_handle, pcb_p->app_handle,
                     pcb_p->callback_task, pcb_p->resp_msg_id);
    CCSIP_DEBUG_TASK(DEB_F_PREFIX"sent response %d to app", DEB_F_PREFIX_ARGS(SIP_PUB, fname), response_code);
    return SIP_OK;
}

/**
 * This function will inform the application that phone is either
 * 1. restarting or
 * 2. failing over/ falling back
 *
 * @note detection of CCM reboot will be handled by PUBLISH ETag mechanism.
 *
 * @param[in] none
 *
 * @return none
 */
void publish_reset (void)
{
    ccsip_publish_cb_t *pcb_p;

    pcb_p = (ccsip_publish_cb_t *)sll_next(s_PCB_list, NULL);
    while (pcb_p != NULL) {
        send_resp_to_app(PUBLISH_FAILED_RESET, pcb_p->pub_handle, pcb_p->app_handle,
                         pcb_p->callback_task, pcb_p->resp_msg_id);
        free_pcb(pcb_p);
        pcb_p = (ccsip_publish_cb_t *)sll_next(s_PCB_list, NULL);
    }
}

/**
 * This function will print the stats (invoked by show command)
 *
 * @param[in] none
 *
 * @return 0 always
 */
cc_int32_t show_publish_stats (cc_int32_t argc, const char *argv[])
{
    debugif_printf("------ Current PUBLISH Statistics ------\n");
    if (s_PCB_list != NULL) {
        debugif_printf("Number of PCBs allocated: %d\n", sll_count(s_PCB_list));
    } else {
        debugif_printf("Number of PCBs allocated: 0\n");
    }
    debugif_printf("Total outgoing PUBLISH requests: %d\n", outgoingPublishes);
    return 0;
}

