/* * This file is part of the Sofia-SIP package * * Copyright (C) 2006 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /**@CFILE nua_publish.c * @brief PUBLISH and publications * * @sa @RFC3903 * * @author Pekka Pessi * * @date Created: Wed Mar 8 17:01:32 EET 2006 ppessi */ #include "config.h" #include #include #include #include #include #include #include #include #include "nua_stack.h" /* ====================================================================== */ /* Publish usage */ struct publish_usage { sip_etag_t *pu_etag; int pu_published; }; static char const *nua_publish_usage_name(nua_dialog_usage_t const *du); static int nua_publish_usage_add(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du); static void nua_publish_usage_remove(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, nua_client_request_t *cr, nua_server_request_t *sr); static void nua_publish_usage_refresh(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, sip_time_t now); static int nua_publish_usage_shutdown(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du); static nua_usage_class const nua_publish_usage[1] = { { sizeof (struct publish_usage), sizeof nua_publish_usage, nua_publish_usage_add, nua_publish_usage_remove, nua_publish_usage_name, nua_base_usage_update_params, NULL, nua_publish_usage_refresh, nua_publish_usage_shutdown, }}; static char const *nua_publish_usage_name(nua_dialog_usage_t const *du) { return "publish"; } static int nua_publish_usage_add(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du) { if (ds->ds_has_publish) return -1; /* There can be only one */ ds->ds_has_publish = 1; return 0; } static void nua_publish_usage_remove(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, nua_client_request_t *cr, nua_server_request_t *sr ) { struct publish_usage *pu = NUA_DIALOG_USAGE_PRIVATE(du); su_free(nh->nh_home, pu->pu_etag); ds->ds_has_publish = 0; /* There can be only one */ } /* ======================================================================== */ /* PUBLISH */ /**@fn \ * void nua_publish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...); * * Send PUBLISH request to publication server. * * Request status will be delivered to the application using #nua_r_publish * event. When successful the publication will be updated periodically until * nua_unpublish() is called or handle is destroyed. Note that the periodic * updates and unpublish do not include the original message body nor the @b * Content-Type header. Instead, the periodic update will include the * @SIPIfMatch header, which was generated from the latest @SIPETag * header received in response to @b PUBLISH request. * * The handle used for publication cannot be used for any other purposes. * * @param nh Pointer to operation handle * @param tag, value, ... List of tagged parameters * * @return * nothing * * @par Related Tags: * NUTAG_URL() \n * Tags of nua_set_hparams() \n * Header tags defined in * * @par Events: * #nua_r_publish * * @sa #nua_r_publish, @RFC3903, @SIPIfMatch, * nua_unpublish(), #nua_r_unpublish, #nua_i_publish */ /** @NUA_EVENT nua_r_publish * * Response to an outgoing PUBLISH. * * The PUBLISH request may be sent explicitly by nua_publish() or implicitly * by NUA state machine. * * @param status status code of PUBLISH request * (if the request is retried, @a status is 100, the @a * sip->sip_status->st_status contain the real status code * from the response message, e.g., 302, 401, or 407) * @param phrase a short textual description of @a status code * @param nh operation handle associated with the publication * @param hmagic application context associated with the handle * @param sip response to PUBLISH request or NULL upon an error * (status code is in @a status and * descriptive message in @a phrase parameters) * @param tags empty * * @sa nua_publish(), @RFC3903, @SIPETag, @Expires, * nua_unpublish(), #nua_r_unpublish, #nua_i_publish * * @END_NUA_EVENT */ /**@fn \ void nua_unpublish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...); * * Send un-PUBLISH request to publication server. Un-PUBLISH request is just * a PUBLISH request with @Expires set to 0. It is possible to un-publish a * publication not associated with the handle by providing correct ETag in * SIPTAG_IF_MATCH() or SIPTAG_IF_MATCH_STR() tags. * * Response to the un-PUBLISH request will be delivered to the application * using #nua_r_unpublish event. * * The handle used for publication cannot be used for any other purposes. * * @param nh Pointer to operation handle * @param tag, value, ... List of tagged parameters * * @return * nothing * * @par Related Tags: * NUTAG_URL() \n * SIPTAG_IF_MATCH(), SIPTAG_IF_MATCH_STR() \n * SIPTAG_EVENT(), SIPTAG_EVENT_STR() \n * Tags of nua_set_hparams() \n * Other header tags defined in except SIPTAG_EXPIRES() or SIPTAG_EXPIRES_STR() * * @par Events: * #nua_r_unpublish * * @sa #nua_r_unpublish, @RFC3903, @SIPIfMatch, * #nua_i_publish, nua_publish(), #nua_r_publish */ /** @NUA_EVENT nua_r_unpublish * * Response to an outgoing un-PUBLISH. * * @param status response status code * (if the request is retried, @a status is 100, the @a * sip->sip_status->st_status contain the real status code * from the response message, e.g., 302, 401, or 407) * @param phrase a short textual description of @a status code * @param nh operation handle associated with the publication * @param hmagic application context associated with the handle * @param sip response to PUBLISH request or NULL upon an error * (status code is in @a status and * descriptive message in @a phrase parameters) * @param tags empty * * @sa nua_unpublish(), @RFC3903, @SIPETag, @Expires, * nua_publish(), #nua_r_publish, #nua_i_publish * * @END_NUA_EVENT */ static int nua_publish_client_template(nua_client_request_t *cr, msg_t **return_msg, tagi_t const *tags); static int nua_publish_client_init(nua_client_request_t *cr, msg_t *, sip_t *, tagi_t const *tags); static int nua_publish_client_request(nua_client_request_t *cr, msg_t *, sip_t *, tagi_t const *tags); static int nua_publish_client_check_restart(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip); static int nua_publish_client_response(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip); static nua_client_methods_t const nua_publish_client_methods = { SIP_METHOD_PUBLISH, /* crm_method, crm_method_name */ 0, /* crm_extra */ { /* crm_flags */ /* create_dialog */ 0, /* in_dialog */ 0, /* target refresh */ 0 }, nua_publish_client_template, /* crm_template */ nua_publish_client_init, /* crm_init */ nua_publish_client_request, /* crm_send */ nua_publish_client_check_restart, /* crm_check_restart */ nua_publish_client_response, /* crm_recv */ NULL, /* crm_preliminary */ NULL, /* crm_report */ NULL, /* crm_complete */ }; /**@internal Send PUBLISH. */ int nua_stack_publish(nua_t *nua, nua_handle_t *nh, nua_event_t e, tagi_t const *tags) { return nua_client_create(nh, e, &nua_publish_client_methods, tags); } static int nua_publish_client_template(nua_client_request_t *cr, msg_t **return_msg, tagi_t const *tags) { nua_dialog_usage_t *du; if (cr->cr_event == nua_r_publish) return 0; du = nua_dialog_usage_get(cr->cr_owner->nh_ds, nua_publish_usage, NULL); if (du && du->du_cr) { if (nua_client_set_target(cr, du->du_cr->cr_target) < 0) return -1; *return_msg = msg_copy(du->du_cr->cr_msg); return 1; } return 0; } static int nua_publish_client_init(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags) { nua_handle_t *nh = cr->cr_owner; nua_dialog_usage_t *du; struct publish_usage *pu; if (cr->cr_event == nua_r_publish) { du = nua_dialog_usage_add(nh, nh->nh_ds, nua_publish_usage, NULL); if (!du) return -1; pu = nua_dialog_usage_private(du); pu->pu_published = 0; if (sip->sip_if_match) { pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_if_match); if (!pu->pu_etag) return -1; sip_header_remove(msg, sip, (sip_header_t *)sip->sip_if_match); } } else du = nua_dialog_usage_get(nh->nh_ds, nua_publish_usage, NULL); cr->cr_usage = du; return 0; } static int nua_publish_client_request(nua_client_request_t *cr, msg_t *msg, sip_t *sip, tagi_t const *tags) { nua_dialog_usage_t *du = cr->cr_usage; int un, done; sip_etag_t const *etag = NULL; un = cr->cr_terminating || cr->cr_event != nua_r_publish || (du && du->du_shutdown) || (sip->sip_expires && sip->sip_expires->ex_delta == 0); nua_client_set_terminating(cr, un); done = un; if (du) { struct publish_usage *pu = nua_dialog_usage_private(du); if (nua_client_bind(cr, du) < 0) return -1; if (pu->pu_published) done = 1; etag = pu->pu_etag; } return nua_base_client_trequest(cr, msg, sip, SIPTAG_IF_MATCH(etag), TAG_IF(done, SIPTAG_PAYLOAD(NONE)), TAG_IF(done, SIPTAG_CONTENT_TYPE(NONE)), TAG_IF(un, SIPTAG_EXPIRES_STR("0")), TAG_NEXT(tags)); } static int nua_publish_client_check_restart(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip) { char const *restarting = NULL; if (cr->cr_terminating || !cr->cr_usage) ; else if (status == 412) restarting = phrase; else if (200 <= status && status < 300 && sip->sip_expires && sip->sip_expires->ex_delta == 0) restarting = "Immediate re-PUBLISH"; if (restarting) { struct publish_usage *pu = nua_dialog_usage_private(cr->cr_usage); if (pu) { pu->pu_published = 0; su_free(cr->cr_owner->nh_home, pu->pu_etag), pu->pu_etag = NULL; if (nua_client_restart(cr, 100, restarting)) return 0; } } return nua_base_client_check_restart(cr, status, phrase, sip); } static int nua_publish_client_response(nua_client_request_t *cr, int status, char const *phrase, sip_t const *sip) { nua_handle_t *nh = cr->cr_owner; nua_dialog_usage_t *du = cr->cr_usage; if (!cr->cr_terminated && du && sip) { struct publish_usage *pu = nua_dialog_usage_private(du); sip_expires_t const *ex = sip->sip_expires; /* Reset state */ pu->pu_published = 0; if (pu->pu_etag) su_free(nh->nh_home, pu->pu_etag), pu->pu_etag = NULL; if (status < 300) { pu->pu_published = 1; pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_etag); if (!ex || ex->ex_delta == 0 || !pu->pu_etag) { cr->cr_terminated = 1; if (!ex || ex->ex_delta == 0) SET_STATUS(900, "Received Invalid Expiration Time"); else SET_STATUS1(NUA_ERROR_AT(__FILE__, __LINE__)); } else nua_dialog_usage_set_refresh(du, ex->ex_delta); } } return nua_base_client_response(cr, status, phrase, sip, NULL); } static void nua_publish_usage_refresh(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du, sip_time_t now) { nua_client_request_t *cr = du->du_cr; if (cr) { if (nua_client_resend_request(cr, 0) >= 0) return; } nua_stack_event(nh->nh_nua, nh, NULL, nua_r_publish, NUA_ERROR_AT(__FILE__, __LINE__), NULL); nua_dialog_usage_remove(nh, ds, du, NULL, NULL); } /** @interal Shut down PUBLISH usage. * * @retval >0 shutdown done * @retval 0 shutdown in progress * @retval <0 try again later */ static int nua_publish_usage_shutdown(nua_handle_t *nh, nua_dialog_state_t *ds, nua_dialog_usage_t *du) { nua_client_request_t *cr = du->du_cr; if (cr) { if (nua_client_resend_request(cr, 1) >= 0) return 0; } /* XXX - report to user */ nua_dialog_usage_remove(nh, ds, du, NULL, NULL); return 200; } /* ---------------------------------------------------------------------- */ /* Server side */ /** @NUA_EVENT nua_i_publish * * Incoming PUBLISH request. * * In order to receive #nua_i_publish events, the application must enable * both the PUBLISH method with NUTAG_ALLOW() tag and the acceptable SIP * events with nua_set_params() tag NUTAG_ALLOW_EVENTS(). * * The nua_response() call responding to a PUBLISH request must have * NUTAG_WITH() (or NUTAG_WITH_THIS()/NUTAG_WITH_SAVED()) tag. Note that * a successful response to PUBLISH @b MUST include @Expires and @SIPETag * headers. * * The PUBLISH request does not create a dialog. Currently the processing * of incoming PUBLISH creates a new handle for each incoming request which * is not assiciated with an existing dialog. If the handle @a nh is not * bound, you should probably destroy it after responding to the PUBLISH * request. * * @param status status code of response sent automatically by stack * @param phrase a short textual description of @a status code * @param nh operation handle associated with the incoming request * @param hmagic application context associated with the call * (usually NULL) * @param sip incoming PUBLISH request * @param tags empty * * @sa @RFC3903, nua_respond(), * @Expires, @SIPETag, @SIPIfMatch, @Event, * nua_subscribe(), #nua_i_subscribe, * nua_notifier(), #nua_i_subscription, * * @since First used in @VERSION_1_12_4 * * @END_NUA_EVENT */ int nua_publish_server_init(nua_server_request_t *sr); nua_server_methods_t const nua_publish_server_methods = { SIP_METHOD_PUBLISH, nua_i_publish, /* Event */ { 0, /* Do not create dialog */ 0, /* Initial request */ 0, /* Not a target refresh request */ 1, /* Add Contact */ }, nua_publish_server_init, nua_base_server_preprocess, nua_base_server_params, nua_base_server_respond, nua_base_server_report, }; int nua_publish_server_init(nua_server_request_t *sr) { sip_allow_events_t *allow_events = NH_PGET(sr->sr_owner, allow_events); sip_event_t *o = sr->sr_request.sip->sip_event; char const *event = o ? o->o_type : NULL; if (!allow_events) return SR_STATUS1(sr, SIP_501_NOT_IMPLEMENTED); else if (!event || !msg_header_find_param(allow_events->k_common, event)) return SR_STATUS1(sr, SIP_489_BAD_EVENT); return 0; }