Asterisk - The Open Source Telephony Project GIT-master-754dea3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
res_pjsip_pubsub.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2013, Digium, Inc.
5 *
6 * Mark Michelson <mmichelson@digium.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18/*!
19 * \brief Opaque structure representing an RFC 3265 SIP subscription
20 */
21
22/*** MODULEINFO
23 <depend>pjproject</depend>
24 <depend>res_pjsip</depend>
25 <support_level>core</support_level>
26 ***/
27
28#include "asterisk.h"
29
30#include <pjsip.h>
31#include <pjsip_simple.h>
32#include <pjlib.h>
33
34#include "asterisk/mwi.h"
36#include "asterisk/module.h"
38#include "asterisk/astobj2.h"
39#include "asterisk/datastore.h"
40#include "asterisk/uuid.h"
42#include "asterisk/sched.h"
43#include "asterisk/res_pjsip.h"
44#include "asterisk/callerid.h"
45#include "asterisk/manager.h"
46#include "asterisk/cli.h"
47#include "asterisk/test.h"
50
51/*** DOCUMENTATION
52 <manager name="PJSIPShowSubscriptionsInbound" language="en_US">
53 <since>
54 <version>12.0.0</version>
55 </since>
56 <synopsis>
57 Lists inbound subscriptions.
58 </synopsis>
59 <syntax />
60 <description>
61 <para>
62 Provides a listing of all inbound subscriptions. An event <literal>InboundSubscriptionDetail</literal>
63 is issued for each subscription object. Once all detail events are completed an
64 <literal>InboundSubscriptionDetailComplete</literal> event is issued.
65 </para>
66 </description>
67 </manager>
68 <managerEvent language="en_US" name="InboundSubscriptionDetail">
69 <managerEventInstance class="EVENT_FLAG_COMMAND">
70 <since>
71 <version>12.0.0</version>
72 </since>
73 <synopsis>
74 Provides details about an inbound subscription - one in which Asterisk
75 handles SUBSCRIBE requests and periodically sends NOTIFYs to its
76 subscribers.
77 </synopsis>
78 <syntax>
79 <parameter name="Role">
80 <para>Asterisk's role for this subscription. This will always be
81 <literal>Notifier</literal>.</para>
82 </parameter>
83 <parameter name="Endpoint">
84 <para>The name of the endpoint associated with this
85 subscription.</para>
86 </parameter>
87 <parameter name="Callid">
88 <para>The CallID of the dialog associated with this
89 subscription.</para>
90 </parameter>
91 <parameter name="State">
92 <para>The current state of the subscription.</para>
93 </parameter>
94 <parameter name="Callerid">
95 <para>The Caller ID of the endpoint associated with this
96 subscription.</para>
97 </parameter>
98 <parameter name="SubscriptionType">
99 <para>Asterisk currently supports the following subscription types, but
100 this could also be extended by third-party modules so this list may not
101 be exhaustive:</para>
102 <enumlist>
103 <enum name="mwi"/>
104 <enum name="extension_state"/>
105 </enumlist>
106 </parameter>
107 <parameter name="Extension">
108 <para>If the <literal>SubscriptionType</literal> is
109 <literal>extension_state</literal> this will be the monitored
110 extension.</para>
111 </parameter>
112 <parameter name="ExtensionStates">
113 <para>If the <literal>SubscriptionType</literal> is
114 <literal>extension_state</literal> this will be the monitored
115 extension's state.</para>
116 <enumlist>
117 <enum name="Idle"/>
118 <enum name="InUse"/>
119 <enum name="Busy"/>
120 <enum name="Unavailable"/>
121 <enum name="Ringing"/>
122 <enum name="InUse&amp;Ringing"/>
123 <enum name="Hold"/>
124 <enum name="InUse&amp;Hold"/>
125 <enum name="Unknown"/>
126 </enumlist>
127 </parameter>
128 <parameter name="Mailboxes">
129 <para>If the <literal>SubscriptionType</literal> is
130 <literal>mwi</literal> this will be a comma-separated list of
131 mailboxes.</para>
132 </parameter>
133 </syntax>
134 </managerEventInstance>
135 </managerEvent>
136 <manager name="PJSIPShowSubscriptionsOutbound" language="en_US">
137 <since>
138 <version>12.0.0</version>
139 </since>
140 <synopsis>
141 Lists outbound subscriptions.
142 </synopsis>
143 <syntax />
144 <description>
145 <para>
146 Provides a listing of all outbound subscriptions. An event <literal>OutboundSubscriptionDetail</literal>
147 is issued for each subscription object. Once all detail events are completed an
148 <literal>OutboundSubscriptionDetailComplete</literal> event is issued.
149 </para>
150 </description>
151 </manager>
152 <managerEvent language="en_US" name="OutboundSubscriptionDetail">
153 <managerEventInstance class="EVENT_FLAG_COMMAND">
154 <since>
155 <version>12.0.0</version>
156 </since>
157 <synopsis>
158 Provides details about an outbound subscription - one in which Asterisk
159 sends SUBSCRIBE requests and periodically receives NOTIFYs.
160 </synopsis>
161 <syntax>
162 <parameter name="Role">
163 <para>Asterisk's role for this subscription. This will always be
164 <literal>Subscriber</literal>.</para>
165 </parameter>
166 <parameter name="Endpoint">
167 <para>The name of the endpoint associated with this
168 subscription.</para>
169 </parameter>
170 <parameter name="Callid">
171 <para>The CallID of the dialog associated with this
172 subscription.</para>
173 </parameter>
174 <parameter name="State">
175 <para>The current state of the subscription.</para>
176 </parameter>
177 <parameter name="Callerid">
178 <para>The Caller ID of the endpoint associated with this
179 subscription.</para>
180 </parameter>
181 <parameter name="SubscriptionType">
182 <para>Asterisk currently supports the following subscription types, but
183 this could also be extended by third-party modules so this list may not
184 be exhaustive:</para>
185 <enumlist>
186 <enum name="mwi"/>
187 <enum name="extension_state"/>
188 </enumlist>
189 </parameter>
190 <parameter name="Extension">
191 <para>If the <literal>SubscriptionType</literal> is
192 <literal>extension_state</literal> this will be the monitored
193 extension.</para>
194 </parameter>
195 <parameter name="ExtensionStates">
196 <para>If the <literal>SubscriptionType</literal> is
197 <literal>extension_state</literal> this will be the monitored
198 extension's state.</para>
199 <enumlist>
200 <enum name="Idle"/>
201 <enum name="InUse"/>
202 <enum name="Busy"/>
203 <enum name="Unavailable"/>
204 <enum name="Ringing"/>
205 <enum name="InUse&amp;Ringing"/>
206 <enum name="Hold"/>
207 <enum name="InUse&amp;Hold"/>
208 <enum name="Unknown"/>
209 </enumlist>
210 </parameter>
211 <parameter name="Mailboxes">
212 <para>If the <literal>SubscriptionType</literal> is
213 <literal>mwi</literal> this will be a comma-separated list of
214 mailboxes.</para>
215 </parameter>
216 </syntax>
217 </managerEventInstance>
218 </managerEvent>
219 <manager name="PJSIPShowResourceLists" language="en_US">
220 <since>
221 <version>13.0.0</version>
222 </since>
223 <synopsis>
224 Displays settings for configured resource lists.
225 </synopsis>
226 <syntax />
227 <description>
228 <para>
229 Provides a listing of all resource lists. An event <literal>ResourceListDetail</literal>
230 is issued for each resource list object. Once all detail events are completed a
231 <literal>ResourceListDetailComplete</literal> event is issued.
232 </para>
233 </description>
234 </manager>
235 <managerEvent language="en_US" name="ResourceListDetail">
236 <managerEventInstance class="EVENT_FLAG_COMMAND">
237 <since>
238 <version>13.0.0</version>
239 </since>
240 <synopsis>Provides details about a resource list.</synopsis>
241 <syntax>
242 <parameter name="ObjectType">
243 <para>The object's type. This will always be 'resource_list'.</para>
244 </parameter>
245 <parameter name="ObjectName">
246 <para>The name of this object.</para>
247 </parameter>
248 <parameter name="Event">
249 <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip_pubsub']/configFile[@name='pjsip.conf']/configObject[@name='resource_list']/configOption[@name='event']/synopsis/node())"/></para>
250 </parameter>
251 <parameter name="FullState">
252 <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip_pubsub']/configFile[@name='pjsip.conf']/configObject[@name='resource_list']/configOption[@name='full_state']/synopsis/node())"/></para>
253 </parameter>
254 <parameter name="ListItem">
255 <para>A comma-separated list of resources that belong to this resource list.</para>
256 </parameter>
257 <parameter name="NotificationBatchInterval">
258 <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip_pubsub']/configFile[@name='pjsip.conf']/configObject[@name='resource_list']/configOption[@name='notification_batch_interval']/synopsis/node())"/></para>
259 </parameter>
260 <parameter name="ResourceDisplayName">
261 <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip_pubsub']/configFile[@name='pjsip.conf']/configObject[@name='resource_list']/configOption[@name='resource_display_name']/synopsis/node())"/></para>
262 </parameter>
263 </syntax>
264 </managerEventInstance>
265 </managerEvent>
266
267 <configInfo name="res_pjsip_pubsub" language="en_US">
268 <synopsis>Module that implements publish and subscribe support.</synopsis>
269 <configFile name="pjsip.conf">
270 <configObject name="subscription_persistence">
271 <synopsis>Persists SIP subscriptions so they survive restarts.</synopsis>
272 <configOption name="packet">
273 <synopsis>Entire SIP SUBSCRIBE packet that created the subscription</synopsis>
274 </configOption>
275 <configOption name="src_name">
276 <synopsis>The source address of the subscription</synopsis>
277 </configOption>
278 <configOption name="src_port">
279 <synopsis>The source port of the subscription</synopsis>
280 </configOption>
281 <configOption name="transport_key">
282 <synopsis>The type of transport the subscription was received on</synopsis>
283 </configOption>
284 <configOption name="local_name">
285 <synopsis>The local address the subscription was received on</synopsis>
286 </configOption>
287 <configOption name="local_port">
288 <synopsis>The local port the subscription was received on</synopsis>
289 </configOption>
290 <configOption name="cseq">
291 <synopsis>The sequence number of the next NOTIFY to be sent</synopsis>
292 </configOption>
293 <configOption name="tag">
294 <synopsis>The local tag of the dialog for the subscription</synopsis>
295 </configOption>
296 <configOption name="endpoint">
297 <synopsis>The name of the endpoint that subscribed</synopsis>
298 </configOption>
299 <configOption name="expires">
300 <synopsis>The time at which the subscription expires</synopsis>
301 </configOption>
302 <configOption name="contact_uri">
303 <synopsis>The Contact URI of the dialog for the subscription</synopsis>
304 </configOption>
305 <configOption name="prune_on_boot">
306 <synopsis>If set, indicates that the contact used a reliable transport
307 and therefore the subscription must be deleted after an asterisk restart.
308 </synopsis>
309 </configOption>
310 <configOption name="generator_data">
311 <synopsis>If set, contains persistence data for all generators of content
312 for the subscription.
313 </synopsis>
314 </configOption>
315 </configObject>
316 <configObject name="resource_list">
317 <since>
318 <version>13.0.0</version>
319 </since>
320 <synopsis>Resource list configuration parameters.</synopsis>
321 <description>
322 <para>This configuration object allows for RFC 4662 resource list subscriptions
323 to be specified. This can be useful to decrease the amount of subscription traffic
324 that a server has to process.</para>
325 <note>
326 <para>Current limitations limit the size of SIP NOTIFY requests that Asterisk sends
327 to double that of the PJSIP maximum packet length. If your resource list notifications
328 are larger than this maximum, you will need to make adjustments.</para>
329 </note>
330 </description>
331 <configOption name="type">
332 <since>
333 <version>13.0.0</version>
334 </since>
335 <synopsis>Must be of type 'resource_list'</synopsis>
336 </configOption>
337 <configOption name="event">
338 <since>
339 <version>13.0.0</version>
340 </since>
341 <synopsis>The SIP event package that the list resource belong to.</synopsis>
342 <description><para>
343 The SIP event package describes the types of resources that Asterisk reports
344 the state of.
345 </para>
346 <enumlist>
347 <enum name="presence"><para>
348 Device state and presence reporting.
349 </para></enum>
350 <enum name="dialog"><para>
351 This is identical to <replaceable>presence</replaceable>.
352 </para></enum>
353 <enum name="message-summary"><para>
354 Message-waiting indication (MWI) reporting.
355 </para></enum>
356 </enumlist>
357 </description>
358 </configOption>
359 <configOption name="list_item">
360 <since>
361 <version>13.0.0</version>
362 </since>
363 <synopsis>The name of a resource to report state on</synopsis>
364 <description>
365 <para>In general Asterisk looks up list items in the following way:</para>
366 <para>1. Check if the list item refers to another configured resource list.</para>
367 <para>2. Pass the name of the resource off to event-package-specific handlers
368 to find the specified resource.</para>
369 <para>The second part means that the way the list item is specified depends
370 on what type of list this is. For instance, if you have the <replaceable>event</replaceable>
371 set to <literal>presence</literal>, then list items should be in the form of
372 dialplan_extension@dialplan_context. For <literal>message-summary</literal> mailbox
373 names should be listed.</para>
374 </description>
375 </configOption>
376 <configOption name="full_state" default="no">
377 <since>
378 <version>13.0.0</version>
379 </since>
380 <synopsis>Indicates if the entire list's state should be sent out.</synopsis>
381 <description>
382 <para>If this option is enabled, and a resource changes state, then Asterisk will construct
383 a notification that contains the state of all resources in the list. If the option is
384 disabled, Asterisk will construct a notification that only contains the states of
385 resources that have changed.</para>
386 <note>
387 <para>Even with this option disabled, there are certain situations where Asterisk is forced
388 to send a notification with the states of all resources in the list. When a subscriber
389 renews or terminates its subscription to the list, Asterisk MUST send a full state
390 notification.</para>
391 </note>
392 </description>
393 </configOption>
394 <configOption name="notification_batch_interval" default="0">
395 <since>
396 <version>13.0.0</version>
397 </since>
398 <synopsis>Time Asterisk should wait, in milliseconds, before sending notifications.</synopsis>
399 <description>
400 <para>When a resource's state changes, it may be desired to wait a certain amount before Asterisk
401 sends a notification to subscribers. This allows for other state changes to accumulate, so that
402 Asterisk can communicate multiple state changes in a single notification instead of rapidly sending
403 many notifications.</para>
404 </description>
405 </configOption>
406 <configOption name="resource_display_name" default="no">
407 <since>
408 <version>16.25.0</version>
409 <version>18.11.0</version>
410 <version>19.3.0</version>
411 </since>
412 <synopsis>Indicates whether display name of resource or the resource name being reported.</synopsis>
413 <description>
414 <para>If this option is enabled, the Display Name will be reported as resource name.
415 If the <replaceable>event</replaceable> set to <literal>presence</literal> or <literal>dialog</literal>,
416 the non-empty HINT name will be set as the Display Name.
417 The <literal>message-summary</literal> is not supported yet.</para>
418 </description>
419 </configOption>
420 </configObject>
421 <configObject name="inbound-publication">
422 <since>
423 <version>13.0.0</version>
424 </since>
425 <synopsis>The configuration for inbound publications</synopsis>
426 <configOption name="endpoint" default="">
427 <since>
428 <version>13.0.0</version>
429 </since>
430 <synopsis>Optional name of an endpoint that is only allowed to publish to this resource</synopsis>
431 </configOption>
432 <configOption name="type">
433 <since>
434 <version>13.0.0</version>
435 </since>
436 <synopsis>Must be of type 'inbound-publication'.</synopsis>
437 </configOption>
438 </configObject>
439 </configFile>
440 </configInfo>
441 ***/
442
443static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata);
444
445static struct pjsip_module pubsub_module = {
446 .name = { "PubSub Module", 13 },
447 .priority = PJSIP_MOD_PRIORITY_APPLICATION,
448 .on_rx_request = pubsub_on_rx_request,
449};
450
451#define MOD_DATA_PERSISTENCE "sub_persistence"
452#define MOD_DATA_MSG "sub_msg"
453
454static const pj_str_t str_event_name = { "Event", 5 };
455
456/*! \brief Scheduler used for automatically expiring publications */
458
459/*! \brief Number of buckets for publications (on a per handler) */
460#define PUBLICATIONS_BUCKETS 37
461
462/*! \brief Default expiration time for PUBLISH if one is not specified */
463#define DEFAULT_PUBLISH_EXPIRES 3600
464
465/*! \brief Number of buckets for subscription datastore */
466#define DATASTORE_BUCKETS 53
467
468/*! \brief Default expiration for subscriptions */
469#define DEFAULT_EXPIRES 3600
470
471/*! \brief Defined method for PUBLISH */
472const pjsip_method pjsip_publish_method =
473{
474 PJSIP_OTHER_METHOD,
475 { "PUBLISH", 7 }
476};
477
478/*!
479 * \brief The types of PUBLISH messages defined in RFC 3903
480 */
482 /*!
483 * \brief Unknown
484 *
485 * \details
486 * This actually is not defined in RFC 3903. We use this as a constant
487 * to indicate that an incoming PUBLISH does not fit into any of the
488 * other categories and is thus invalid.
489 */
491
492 /*!
493 * \brief Initial
494 *
495 * \details
496 * The first PUBLISH sent. This will contain a non-zero Expires header
497 * as well as a body that indicates the current state of the endpoint
498 * that has sent the message. The initial PUBLISH is the only type
499 * of PUBLISH to not contain a Sip-If-Match header in it.
500 */
502
503 /*!
504 * \brief Refresh
505 *
506 * \details
507 * Used to keep a published state from expiring. This will contain a
508 * non-zero Expires header but no body since its purpose is not to
509 * update state.
510 */
512
513 /*!
514 * \brief Modify
515 *
516 * \details
517 * Used to change state from its previous value. This will contain
518 * a body updating the published state. May or may not contain an
519 * Expires header.
520 */
522
523 /*!
524 * \brief Remove
525 *
526 * \details
527 * Used to remove published state from an ESC. This will contain
528 * an Expires header set to 0 and likely no body.
529 */
531};
532
533/*!
534 * \brief A vector of strings commonly used throughout this module
535 */
536AST_VECTOR(resources, const char *);
537
538/*!
539 * \brief Resource list configuration item
540 */
543 /*! SIP event package the list uses. */
544 char event[32];
545 /*! Strings representing resources in the list. */
547 /*! Indicates if Asterisk sends full or partial state on notifications. */
548 unsigned int full_state;
549 /*! Time, in milliseconds Asterisk waits before sending a batched notification.*/
551 /*! Indicates whether display name of resource or the resource name being reported.*/
553};
554
555/*!
556 * Used to create new entity IDs by ESCs.
557 */
559
560/*!
561 * \brief Structure representing a SIP publication
562 */
564 /*! Publication datastores set up by handlers */
566 /*! \brief Entity tag for the publication */
568 /*! \brief Handler for this publication */
570 /*! \brief The endpoint with which the subscription is communicating */
572 /*! \brief Expiration time of the publication */
573 unsigned int expires;
574 /*! \brief Scheduled item for expiration of publication */
576 /*! \brief The resource the publication is to */
577 char *resource;
578 /*! \brief The name of the event type configuration */
580 /*! \brief Data containing the above */
581 char data[0];
582};
583
584
585/*!
586 * \brief Structure used for persisting an inbound subscription
587 */
589 /*! Sorcery object details */
591 /*! The name of the endpoint involved in the subscription */
592 char *endpoint;
593 /*! SIP message that creates the subscription */
594 char packet[PJSIP_MAX_PKT_LEN];
595 /*! Source address of the message */
596 char src_name[PJ_INET6_ADDRSTRLEN];
597 /*! Source port of the message */
599 /*! Local transport type (UDP,TCP,TLS)*/
601 /*! Local transport address */
602 char local_name[PJ_INET6_ADDRSTRLEN];
603 /*! Local transport port */
605 /*! Next CSeq to use for message */
606 unsigned int cseq;
607 /*! Local tag of the dialog */
608 char *tag;
609 /*! When this subscription expires */
610 struct timeval expires;
611 /*! Contact URI */
612 char contact_uri[PJSIP_MAX_URL_SIZE];
613 /*! Prune subscription on restart */
615 /*! Body generator specific persistence data */
617};
618
619/*!
620 * \brief The state of the subscription tree
621 */
623 /*! Normal operation */
625 /*! A terminate has been requested by Asterisk, the client, or pjproject */
627 /*! The terminate is in progress */
629 /*! The terminate process has finished and the subscription tree is no longer valid */
631};
632
634 "Normal",
635 "TerminatePending",
636 "TerminateInProgress",
637 "Terminated"
638};
639
640/*!
641 * \brief A tree of SIP subscriptions
642 *
643 * Because of the ability to subscribe to resource lists, a SIP
644 * subscription can result in a tree of subscriptions being created.
645 * This structure represents the information relevant to the subscription
646 * as a whole, to include the underlying PJSIP structure for the
647 * subscription.
648 */
650 /*! The endpoint with which the subscription is communicating */
652 /*! Serializer on which to place operations for this subscription */
654 /*! The role for this subscription */
656 /*! Persistence information */
658 /*! The underlying PJSIP event subscription structure */
659 pjsip_evsub *evsub;
660 /*! The underlying PJSIP dialog */
661 pjsip_dialog *dlg;
662 /*! Interval to use for batching notifications */
664 /*! Scheduler ID for batched notification */
666 /*! Indicator if scheduled batched notification should be sent */
668 /*! The root of the subscription tree */
670 /*! Is this subscription to a list? */
672 /*! Next item in the list */
674 /*! Subscription tree state */
676 /*! On asterisk restart, this is the task data used
677 * to restart the expiration timer if pjproject isn't
678 * capable of restarting the timer.
679 */
681 /*! The transport the subscription was received on.
682 * Only used for reliable transports.
683 */
685 /*! Indicator if initial notify should be generated.
686 * Used to refresh modified RLS.
687 */
689};
690
691/*!
692 * \brief Structure representing a "virtual" SIP subscription.
693 *
694 * This structure serves a dual purpose. Structurally, it is
695 * the constructed tree of subscriptions based on the resources
696 * being subscribed to. API-wise, this serves as the handle that
697 * subscription handlers use in order to interact with the pubsub API.
698 */
700 /*! Subscription datastores set up by handlers */
702 /*! The handler for this subscription */
704 /*! Pointer to the base of the tree */
706 /*! Body generator for NOTIFYs */
708 /*! Vector of child subscriptions */
710 /*! Saved NOTIFY body text for this subscription */
712 /*! Indicator that the body text has changed since the last notification */
714 /*! The current state of the subscription */
715 pjsip_evsub_state subscription_state;
716 /*! For lists, the current version to place in the RLMI body */
717 unsigned int version;
718 /*! For lists, indicates if full state should always be communicated. */
719 unsigned int full_state;
720 /*! URI associated with the subscription */
721 pjsip_sip_uri *uri;
722 /*! Data to be persisted with the subscription */
724 /*! Display Name of resource */
726 /*! Name of resource being subscribed to */
727 char resource[0];
728};
729
730/*!
731 * \brief Structure representing a publication resource
732 */
734 /*! \brief Sorcery object details */
736 /*! \brief Optional name of an endpoint that is only allowed to publish to this resource */
737 char *endpoint;
738 /*! \brief Mapping for event types to configuration */
740};
741
742static const char *sip_subscription_roles_map[] = {
743 [AST_SIP_SUBSCRIBER] = "Subscriber",
744 [AST_SIP_NOTIFIER] = "Notifier"
745};
746
748 /*! Called from send request */
750 /*! Subscription created from initial client request */
752 /*! Subscription recreated by asterisk on startup */
754 /*! Subscription created from client refresh */
756};
757
759
762
763static pjsip_media_type rlmi_media_type;
764
765static void pubsub_on_evsub_state(pjsip_evsub *sub, pjsip_event *event);
766static void pubsub_on_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata,
767 int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
768static void pubsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code,
769 pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
770static void pubsub_on_client_refresh(pjsip_evsub *sub);
771static void pubsub_on_server_timeout(pjsip_evsub *sub);
772
773static pjsip_evsub_user pubsub_cb = {
774 .on_evsub_state = pubsub_on_evsub_state,
775 .on_rx_refresh = pubsub_on_rx_refresh,
776 .on_rx_notify = pubsub_on_rx_notify,
777 .on_client_refresh = pubsub_on_client_refresh,
778 .on_server_timeout = pubsub_on_server_timeout,
779};
780
781/*! \brief Destructor for publication resource */
782static void publication_resource_destroy(void *obj)
783{
784 struct ast_sip_publication_resource *resource = obj;
785
786 ast_free(resource->endpoint);
787 ast_variables_destroy(resource->events);
788}
789
790/*! \brief Allocator for publication resource */
791static void *publication_resource_alloc(const char *name)
792{
794}
795
797{
798 struct sip_subscription_tree *sub_tree = data;
799
800 if (!sub_tree->evsub) {
801 /* Something else already terminated the subscription. */
802 ao2_ref(sub_tree, -1);
803 return 0;
804 }
805
806 ast_debug(3, "Transport destroyed. Removing subscription '%s->%s' prune on boot: %d\n",
807 sub_tree->persistence->endpoint, sub_tree->root->resource,
808 sub_tree->persistence->prune_on_boot);
809
811 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
812
813 ao2_ref(sub_tree, -1);
814 return 0;
815}
816
817/*!
818 * \internal
819 * \brief The reliable transport we used as a subscription contact has shutdown.
820 *
821 * \param data What subscription needs to be terminated.
822 *
823 * \note Normally executed by the pjsip monitor thread.
824 */
825static void sub_tree_transport_cb(void *data)
826{
827 struct sip_subscription_tree *sub_tree = data;
828
829 /*
830 * Push off the subscription termination to the serializer to
831 * avoid deadlock. Another thread could be trying to send a
832 * message on the subscription that can deadlock with this
833 * thread.
834 */
835 ao2_ref(sub_tree, +1);
837 sub_tree)) {
838 ao2_ref(sub_tree, -1);
839 }
840}
841
842/*! \brief Destructor for subscription persistence */
844{
845 struct subscription_persistence *persistence = obj;
846
847 ast_free(persistence->endpoint);
848 ast_free(persistence->tag);
849 ast_json_unref(persistence->generator_data);
850}
851
852/*! \brief Allocator for subscription persistence */
853static void *subscription_persistence_alloc(const char *name)
854{
856}
857
858/*! \brief Function which creates initial persistence information of a subscription in sorcery */
860{
861 char tag[PJ_GUID_STRING_LENGTH + 1];
862
863 /* The id of this persistence object doesn't matter as we keep it on the subscription and don't need to
864 * look it up by id at all.
865 */
867 "subscription_persistence", NULL);
868
869 pjsip_dialog *dlg = sub_tree->dlg;
870
871 if (!persistence) {
872 return NULL;
873 }
874
875 persistence->endpoint = ast_strdup(ast_sorcery_object_get_id(sub_tree->endpoint));
876 ast_copy_pj_str(tag, &dlg->local.info->tag, sizeof(tag));
877 persistence->tag = ast_strdup(tag);
878
880 return persistence;
881}
882
883/*! \brief Function which updates persistence information of a subscription in sorcery */
885 pjsip_rx_data *rdata, enum sip_persistence_update_type type)
886{
887 pjsip_dialog *dlg;
888
889 if (!sub_tree->persistence) {
890 return;
891 }
892
893 ast_debug(3, "Updating persistence for '%s->%s' prune on boot: %s\n",
894 sub_tree->persistence->endpoint, sub_tree->root->resource,
895 sub_tree->persistence->prune_on_boot ? "yes" : "no");
896
897 dlg = sub_tree->dlg;
898 sub_tree->persistence->cseq = dlg->local.cseq;
899
900 if (rdata) {
901 unsigned int expires;
902 pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
903 pjsip_contact_hdr *contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL);
904
905 expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
907
908 if (contact_hdr) {
909 if (contact_hdr) {
911 sub_tree->persistence->prune_on_boot =
913 (pjsip_sip_uri *)pjsip_uri_get_uri(contact_hdr->uri),
914 sub_tree->endpoint, rdata);
915
916 if (sub_tree->persistence->prune_on_boot) {
917 ast_debug(3, "adding transport monitor on %s for '%s->%s' prune on boot: %d\n",
918 rdata->tp_info.transport->obj_name,
919 sub_tree->persistence->endpoint, sub_tree->root->resource,
920 sub_tree->persistence->prune_on_boot);
921 AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(rdata->tp_info.transport,
922 sub_tree->transport_key);
924 sub_tree_transport_cb, sub_tree);
925 /*
926 * FYI: ast_sip_transport_monitor_register holds a reference to the sub_tree
927 */
928 }
929 }
930 }
931
932 pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, contact_hdr->uri,
933 sub_tree->persistence->contact_uri, sizeof(sub_tree->persistence->contact_uri));
934 } else {
935 ast_log(LOG_WARNING, "Contact not updated due to missing contact header\n");
936 }
937
938 /* When receiving a packet on an streaming transport, it's possible to receive more than one SIP
939 * message at a time into the rdata->pkt_info.packet buffer. However, the rdata->msg_info.msg_buf
940 * will always point to the proper SIP message that is to be processed. When updating subscription
941 * persistence that is pulled from persistent storage, though, the rdata->pkt_info.packet will
942 * only ever have a single SIP message on it, and so we base persistence on that.
943 */
946 if (rdata->msg_info.msg_buf) {
947 ast_copy_string(sub_tree->persistence->packet, rdata->msg_info.msg_buf,
948 MIN(sizeof(sub_tree->persistence->packet), rdata->msg_info.len + 1));
949 } else {
950 ast_copy_string(sub_tree->persistence->packet, rdata->pkt_info.packet,
951 sizeof(sub_tree->persistence->packet));
952 }
953 }
954 ast_copy_string(sub_tree->persistence->src_name, rdata->pkt_info.src_name,
955 sizeof(sub_tree->persistence->src_name));
956 sub_tree->persistence->src_port = rdata->pkt_info.src_port;
957 ast_copy_string(sub_tree->persistence->transport_type, rdata->tp_info.transport->type_name,
958 sizeof(sub_tree->persistence->transport_type));
959 ast_copy_pj_str(sub_tree->persistence->local_name, &rdata->tp_info.transport->local_name.host,
960 sizeof(sub_tree->persistence->local_name));
961 sub_tree->persistence->local_port = rdata->tp_info.transport->local_name.port;
962 }
963
965}
966
967/*! \brief Function which removes persistence of a subscription from sorcery */
969{
970 if (!sub_tree->persistence) {
971 return;
972 }
973
974 if (sub_tree->persistence->prune_on_boot && !ast_strlen_zero(sub_tree->transport_key)) {
975 ast_debug(3, "Unregistering transport monitor on %s '%s->%s'\n",
976 sub_tree->transport_key,
977 sub_tree->endpoint ? ast_sorcery_object_get_id(sub_tree->endpoint) : "Unknown",
978 sub_tree->root ? sub_tree->root->resource : "Unknown");
980 sub_tree_transport_cb, sub_tree, NULL);
981 }
982
984 ao2_ref(sub_tree->persistence, -1);
985 sub_tree->persistence = NULL;
986}
987
988
991 size_t num_accept, const char *body_type);
992
993/*! \brief Retrieve a handler using the Event header of an rdata message */
994static struct ast_sip_subscription_handler *subscription_get_handler_from_rdata(pjsip_rx_data *rdata, const char *endpoint)
995{
996 pjsip_event_hdr *event_header;
997 char event[32];
999
1000 event_header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_event_name, rdata->msg_info.msg->hdr.next);
1001 if (!event_header) {
1002 ast_log(LOG_WARNING, "Incoming SUBSCRIBE request from %s with no Event header\n",
1003 endpoint ? endpoint : "Unknown");
1004 return NULL;
1005 }
1006 ast_copy_pj_str(event, &event_header->event_type, sizeof(event));
1007
1009 if (!handler) {
1010 ast_log(LOG_WARNING, "No registered subscribe handler for event %s from %s\n", event,
1011 endpoint ? endpoint : "Unknown");
1012 }
1013
1014 return handler;
1015}
1016
1017/*!
1018 * \brief Accept headers that are exceptions to the rule
1019 *
1020 * Typically, when a SUBSCRIBE arrives, we attempt to find a
1021 * body generator that matches one of the Accept headers in
1022 * the request. When subscribing to a single resource, this works
1023 * great. However, when subscribing to a list, things work
1024 * differently. Most Accept header values are fine, but there
1025 * are a couple that are endemic to resource lists that need
1026 * to be ignored when searching for a body generator to use
1027 * for the individual resources of the subscription.
1028 */
1029const char *accept_exceptions[] = {
1030 "multipart/related",
1031 "application/rlmi+xml",
1032};
1033
1034/*!
1035 * \brief Is the Accept header from the SUBSCRIBE in the list of exceptions?
1036 *
1037 * \retval 1 This Accept header value is an exception to the rule.
1038 * \retval 0 This Accept header is not an exception to the rule.
1039 */
1040static int exceptional_accept(const pj_str_t *accept)
1041{
1042 int i;
1043
1044 for (i = 0; i < ARRAY_LEN(accept_exceptions); ++i) {
1045 if (!pj_strcmp2(accept, accept_exceptions[i])) {
1046 return 1;
1047 }
1048 }
1049
1050 return 0;
1051}
1052
1053/*! \brief Retrieve a body generator using the Accept header of an rdata message */
1056{
1057 pjsip_accept_hdr *accept_header = (pjsip_accept_hdr *) &rdata->msg_info.msg->hdr;
1058 char accept[AST_SIP_MAX_ACCEPT][64];
1059 size_t num_accept_headers = 0;
1060
1061 while ((accept_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, accept_header->next)) &&
1062 (num_accept_headers < AST_SIP_MAX_ACCEPT)) {
1063 int i;
1064
1065 for (i = 0; i < accept_header->count && num_accept_headers < AST_SIP_MAX_ACCEPT; ++i) {
1066 if (!exceptional_accept(&accept_header->values[i])) {
1067 ast_copy_pj_str(accept[num_accept_headers], &accept_header->values[i], sizeof(accept[num_accept_headers]));
1068 ++num_accept_headers;
1069 }
1070 }
1071 }
1072
1073 if (num_accept_headers == 0) {
1074 /* If a SUBSCRIBE contains no Accept headers, then we must assume that
1075 * the default accept type for the event package is to be used.
1076 */
1077 ast_copy_string(accept[0], handler->notifier->default_accept, sizeof(accept[0]));
1078 num_accept_headers = 1;
1079 }
1080
1081 return find_body_generator(accept, num_accept_headers, handler->body_type);
1082}
1083
1084/*! \brief Check if the rdata has a Supported header containing 'eventlist'
1085 *
1086 * \retval 1 rdata has an eventlist containing supported header
1087 * \retval 0 rdata doesn't have an eventlist containing supported header
1088 */
1089static int ast_sip_pubsub_has_eventlist_support(pjsip_rx_data *rdata)
1090{
1091 pjsip_supported_hdr *supported_header = (pjsip_supported_hdr *) &rdata->msg_info.msg->hdr;
1092
1093 while ((supported_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_SUPPORTED, supported_header->next))) {
1094 int i;
1095
1096 for (i = 0; i < supported_header->count; i++) {
1097 if (!pj_stricmp2(&supported_header->values[i], "eventlist")) {
1098 return 1;
1099 }
1100 }
1101 }
1102
1103 return 0;
1104}
1105
1106struct resource_tree;
1107
1108/*!
1109 * \brief A node for a resource tree.
1110 */
1113 unsigned int full_state;
1115 char resource[0];
1116};
1117
1118/*!
1119 * \brief Helper function for retrieving a resource list for a given event.
1120 *
1121 * This will retrieve a resource list that corresponds to the resource and event provided.
1122 *
1123 * \param resource The name of the resource list to retrieve
1124 * \param event The expected event name on the resource list
1125 */
1126static struct resource_list *retrieve_resource_list(const char *resource, const char *event)
1127{
1128 struct resource_list *list;
1129
1130 list = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "resource_list", resource);
1131 if (!list) {
1132 return NULL;
1133 }
1134
1135 if (strcmp(list->event, event)) {
1136 ast_log(LOG_WARNING, "Found resource list %s, but its event type (%s) does not match SUBSCRIBE's (%s)\n",
1137 resource, list->event, event);
1138 ao2_cleanup(list);
1139 return NULL;
1140 }
1141
1142 return list;
1143}
1144
1145/*!
1146 * \brief Allocate a tree node
1147 *
1148 * In addition to allocating and initializing the tree node, the node is also added
1149 * to the vector of visited resources. See \ref build_resource_tree for more information
1150 * on the visited resources.
1151 *
1152 * \param resource The name of the resource for this tree node.
1153 * \param visited The vector of resources that have been visited.
1154 * \param full_state if allocating a list, indicate whether full state is requested in notifications.
1155 * \param display_name the display name to include with this tree node.
1156 *
1157 * \retval NULL Allocation failure.
1158 * \retval non-NULL The newly-allocated tree_node
1159 */
1160static struct tree_node *tree_node_alloc(const char *resource, struct resources *visited, unsigned int full_state, const char *display_name)
1161{
1162 struct tree_node *node;
1163
1164 node = ast_calloc(1, sizeof(*node) + strlen(resource) + 1);
1165 if (!node) {
1166 return NULL;
1167 }
1168
1169 strcpy(node->resource, resource);
1170 if (AST_VECTOR_INIT(&node->children, 4)) {
1171 ast_free(node);
1172 return NULL;
1173 }
1174 node->full_state = full_state;
1175 node->display_name = ast_strdup(display_name);
1176
1177 if (visited) {
1178 AST_VECTOR_APPEND(visited, resource);
1179 }
1180 return node;
1181}
1182
1183/*!
1184 * \brief Destructor for a tree node
1185 *
1186 * This function calls recursively in order to destroy
1187 * all nodes lower in the tree from the given node in
1188 * addition to the node itself.
1189 *
1190 * \param node The node to destroy.
1191 */
1193{
1194 int i;
1195 if (!node) {
1196 return;
1197 }
1198
1199 for (i = 0; i < AST_VECTOR_SIZE(&node->children); ++i) {
1200 tree_node_destroy(AST_VECTOR_GET(&node->children, i));
1201 }
1202 AST_VECTOR_FREE(&node->children);
1203 ast_free(node->display_name);
1204 ast_free(node);
1205}
1206
1207/*!
1208 * \brief Determine if this resource has been visited already
1209 *
1210 * See \ref build_resource_tree for more information
1211 *
1212 * \param resource The resource currently being visited
1213 * \param visited The resources that have previously been visited
1214 */
1215static int have_visited(const char *resource, struct resources *visited)
1216{
1217 int i;
1218
1219 for (i = 0; i < AST_VECTOR_SIZE(visited); ++i) {
1220 if (!strcmp(resource, AST_VECTOR_GET(visited, i))) {
1221 return 1;
1222 }
1223 }
1224
1225 return 0;
1226}
1227
1228#define NEW_SUBSCRIBE(notifier, endpoint, resource, rdata) notifier->new_subscribe_with_rdata ? notifier->new_subscribe_with_rdata(endpoint, resource, rdata) : notifier->new_subscribe(endpoint, resource)
1229
1230/*!
1231 * \brief Build child nodes for a given parent.
1232 *
1233 * This iterates through the items on a resource list and creates tree nodes for each one. The
1234 * tree nodes created are children of the supplied parent node. If an item in the resource
1235 * list is itself a list, then this function is called recursively to provide children for
1236 * the new node.
1237 *
1238 * If an item in a resource list is not a list, then the supplied subscription handler is
1239 * called into as if a new SUBSCRIBE for the list item were presented. The handler's response
1240 * is used to determine if the node can be added to the tree or not.
1241 *
1242 * If a parent node ends up having no child nodes added under it, then the parent node is
1243 * pruned from the tree.
1244 *
1245 * \param endpoint The endpoint that sent the inbound SUBSCRIBE.
1246 * \param handler The subscription handler for leaf nodes in the tree.
1247 * \param list The configured resource list from which the child node is being built.
1248 * \param parent The parent node for these children.
1249 * \param visited The resources that have already been visited.
1250 */
1252 struct resource_list *list, struct tree_node *parent, struct resources *visited, pjsip_rx_data *rdata)
1253{
1254 int i;
1255
1256 for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
1257 struct tree_node *current;
1258 struct resource_list *child_list;
1259 const char *resource = AST_VECTOR_GET(&list->items, i);
1260
1261 if (have_visited(resource, visited)) {
1262 ast_debug(1, "Already visited resource %s. Avoiding duplicate resource or potential loop.\n", resource);
1263 continue;
1264 }
1265
1266 child_list = retrieve_resource_list(resource, list->event);
1267 if (!child_list) {
1268 int resp = NEW_SUBSCRIBE(handler->notifier, endpoint, resource, rdata);
1269 if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
1270 char display_name[AST_MAX_EXTENSION] = "";
1271 if (list->resource_display_name && handler->notifier->get_resource_display_name) {
1272 handler->notifier->get_resource_display_name(endpoint, resource, display_name, sizeof(display_name));
1273 }
1274 current = tree_node_alloc(resource, visited, 0, ast_strlen_zero(display_name) ? NULL : display_name);
1275 if (!current) {
1276 ast_debug(1,
1277 "Subscription to leaf resource %s was successful, but encountered allocation error afterwards\n",
1278 resource);
1279 continue;
1280 }
1281 ast_debug(2, "Subscription to leaf resource %s resulted in success. Adding to parent %s\n",
1282 resource, parent->resource);
1283 if (AST_VECTOR_APPEND(&parent->children, current)) {
1285 }
1286 } else {
1287 ast_debug(2, "Subscription to leaf resource %s resulted in error response %d\n",
1288 resource, resp);
1289 }
1290 } else {
1291 ast_debug(2, "Resource %s (child of %s) is a list\n", resource, parent->resource);
1292 current = tree_node_alloc(resource, visited, child_list->full_state, NULL);
1293 if (!current) {
1294 ast_debug(1, "Cannot build children of resource %s due to allocation failure\n", resource);
1295 continue;
1296 }
1297 build_node_children(endpoint, handler, child_list, current, visited, rdata);
1298 if (AST_VECTOR_SIZE(&current->children) > 0) {
1299 ast_debug(1, "List %s had no successful children.\n", resource);
1300 if (AST_VECTOR_APPEND(&parent->children, current)) {
1302 }
1303 } else {
1304 ast_debug(2, "List %s had successful children. Adding to parent %s\n",
1305 resource, parent->resource);
1307 }
1308 ao2_cleanup(child_list);
1309 }
1310 }
1311}
1312
1313/*!
1314 * \brief A resource tree
1315 *
1316 * When an inbound SUBSCRIBE arrives, the resource being subscribed to may
1317 * be a resource list. If this is the case, the resource list may contain resources
1318 * that are themselves lists. The structure needed to hold the resources is
1319 * a tree.
1320 *
1321 * Upon receipt of the SUBSCRIBE, the tree is built by determining if subscriptions
1322 * to the individual resources in the tree would be successful or not. Any successful
1323 * subscriptions result in a node in the tree being created. Any unsuccessful subscriptions
1324 * result in no node being created.
1325 *
1326 * This tree can be seen as a bare-bones analog of the tree of ast_sip_subscriptions that
1327 * will end up being created to actually carry out the duties of a SIP SUBSCRIBE dialog.
1328 */
1332};
1333
1334/*!
1335 * \brief Destroy a resource tree.
1336 *
1337 * This function makes no assumptions about how the tree itself was
1338 * allocated and does not attempt to free the tree itself. Callers
1339 * of this function are responsible for freeing the tree.
1340 *
1341 * \param tree The tree to destroy.
1342 */
1343static void resource_tree_destroy(struct resource_tree *tree)
1344{
1345 if (tree) {
1346 tree_node_destroy(tree->root);
1347 }
1348}
1349
1350/*!
1351 * \brief Build a resource tree
1352 *
1353 * This function builds a resource tree based on the requested resource in a SUBSCRIBE request.
1354 *
1355 * This function also creates a container that has all resources that have been visited during
1356 * creation of the tree, whether those resources resulted in a tree node being created or not.
1357 * Keeping this container of visited resources allows for misconfigurations such as loops in
1358 * the tree or duplicated resources to be detected.
1359 *
1360 * \param endpoint The endpoint that sent the SUBSCRIBE request.
1361 * \param handler The subscription handler for leaf nodes in the tree.
1362 * \param resource The resource requested in the SUBSCRIBE request.
1363 * \param tree The tree that is to be built.
1364 * \param has_eventlist_support
1365 *
1366 * \retval 200-299 Successfully subscribed to at least one resource.
1367 * \retval 300-699 Failure to subscribe to requested resource.
1368 */
1370 const char *resource, struct resource_tree *tree, int has_eventlist_support, pjsip_rx_data *rdata)
1371{
1372 RAII_VAR(struct resource_list *, list, NULL, ao2_cleanup);
1373 struct resources visited;
1374
1375 int not_eventlist_but_needs_children = !strcmp(handler->body_type, AST_SIP_DEVICE_FEATURE_SYNC_DATA);
1376
1377 if ((!has_eventlist_support && !not_eventlist_but_needs_children) || !(list = retrieve_resource_list(resource, handler->event_name))) {
1378 ast_debug(2, "Subscription '%s->%s' is not to a list\n",
1379 ast_sorcery_object_get_id(endpoint), resource);
1380 tree->root = tree_node_alloc(resource, NULL, 0, NULL);
1381 if (!tree->root) {
1382 return 500;
1383 }
1384 return NEW_SUBSCRIBE(handler->notifier, endpoint, resource, rdata);
1385 }
1386
1387 ast_debug(2, "Subscription '%s->%s' is a list\n",
1388 ast_sorcery_object_get_id(endpoint), resource);
1389 if (AST_VECTOR_INIT(&visited, AST_VECTOR_SIZE(&list->items))) {
1390 return 500;
1391 }
1392
1393 tree->root = tree_node_alloc(resource, &visited, list->full_state, NULL);
1394 if (!tree->root) {
1395 AST_VECTOR_FREE(&visited);
1396 return 500;
1397 }
1398
1399 tree->notification_batch_interval = list->notification_batch_interval;
1400
1401 build_node_children(endpoint, handler, list, tree->root, &visited, rdata);
1402 AST_VECTOR_FREE(&visited);
1403
1404 if (AST_VECTOR_SIZE(&tree->root->children) > 0) {
1405 return 200;
1406 } else {
1407 return 500;
1408 }
1409}
1410
1412{
1416}
1417
1419{
1420 struct sip_subscription_tree *i;
1421
1424 if (i == obj) {
1426 if (i->root) {
1427 ast_debug(2, "Removing subscription '%s->%s' from list of subscriptions\n",
1429 }
1430 break;
1431 }
1432 }
1435}
1436
1438{
1439 ast_debug(3, "Destroying SIP subscription from '%s->%s'\n",
1440 sub->tree && sub->tree->endpoint ? ast_sorcery_object_get_id(sub->tree->endpoint) : "Unknown",
1441 sub->resource);
1442
1443 ast_free(sub->body_text);
1444
1445 AST_VECTOR_FREE(&sub->children);
1446 ao2_cleanup(sub->datastores);
1447 ast_json_unref(sub->persistence_data);
1448 ast_free(sub->display_name);
1449 ast_free(sub);
1450}
1451
1453{
1454 int i;
1455
1456 if (!root) {
1457 return;
1458 }
1459
1460 for (i = 0; i < AST_VECTOR_SIZE(&root->children); ++i) {
1461 struct ast_sip_subscription *child;
1462
1463 child = AST_VECTOR_GET(&root->children, i);
1464 destroy_subscriptions(child);
1465 }
1466
1468}
1469
1471 const char *resource, const char *display_name, struct sip_subscription_tree *tree)
1472{
1473 struct ast_sip_subscription *sub;
1474 pjsip_msg *msg;
1475 pjsip_sip_uri *request_uri;
1476
1478 if (!msg) {
1479 ast_log(LOG_ERROR, "No dialog message saved for SIP subscription. Cannot allocate subscription for resource %s\n", resource);
1480 return NULL;
1481 }
1482
1483 sub = ast_calloc(1, sizeof(*sub) + strlen(resource) + 1);
1484 if (!sub) {
1485 return NULL;
1486 }
1487 strcpy(sub->resource, resource); /* Safe */
1488
1489 sub->display_name = ast_strdup(display_name);
1490
1491 sub->datastores = ast_datastores_alloc();
1492 if (!sub->datastores) {
1494 return NULL;
1495 }
1496
1497 sub->body_text = ast_str_create(128);
1498 if (!sub->body_text) {
1500 return NULL;
1501 }
1502
1503 sub->uri = pjsip_sip_uri_create(tree->dlg->pool, PJ_FALSE);
1504 request_uri = pjsip_uri_get_uri(msg->line.req.uri);
1505 pjsip_sip_uri_assign(tree->dlg->pool, sub->uri, request_uri);
1506 pj_strdup2(tree->dlg->pool, &sub->uri->user, resource);
1507
1508 /* If there is any persistence information available for this subscription that was persisted
1509 * then make it available so that the NOTIFY has the correct state.
1510 */
1511
1514 }
1515
1516 sub->handler = handler;
1517 sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE;
1518 sub->tree = ao2_bump(tree);
1519
1520 return sub;
1521}
1522
1523/*!
1524 * \brief Create a tree of virtual subscriptions based on a resource tree node.
1525 *
1526 * \param handler The handler to supply to leaf subscriptions.
1527 * \param resource The requested resource for this subscription.
1528 * \param generator Body generator to use for leaf subscriptions.
1529 * \param tree The root of the subscription tree.
1530 * \param current The tree node that corresponds to the subscription being created.
1531 */
1533 const char *resource, struct ast_sip_pubsub_body_generator *generator,
1535{
1536 int i;
1537 struct ast_sip_subscription *sub;
1538
1540 if (!sub) {
1541 return NULL;
1542 }
1543
1544 sub->full_state = current->full_state;
1545 sub->body_generator = generator;
1546 AST_VECTOR_INIT(&sub->children, AST_VECTOR_SIZE(&current->children));
1547
1548 for (i = 0; i < AST_VECTOR_SIZE(&current->children); ++i) {
1549 struct ast_sip_subscription *child;
1550 struct tree_node *child_node = AST_VECTOR_GET(&current->children, i);
1551
1552 child = create_virtual_subscriptions(handler, child_node->resource, generator,
1553 tree, child_node);
1554
1555 if (!child) {
1556 ast_debug(1, "Child subscription to resource %s could not be created\n",
1557 child_node->resource);
1558 continue;
1559 }
1560
1561 if (AST_VECTOR_APPEND(&sub->children, child)) {
1562 ast_debug(1, "Child subscription to resource %s could not be appended\n",
1563 child_node->resource);
1564 destroy_subscription(child);
1565 /* Have to release tree here too because a ref was added
1566 * to child that destroy_subscription() doesn't release. */
1567 ao2_cleanup(tree);
1568 }
1569 }
1570
1571 return sub;
1572}
1573
1575{
1576 int i;
1577
1578 if (!sub) {
1579 return;
1580 }
1581
1582 if (AST_VECTOR_SIZE(&sub->children) > 0) {
1583 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
1585 }
1586 return;
1587 }
1588
1589 /* We notify subscription shutdown only on the tree leaves. */
1590 if (sub->handler->subscription_shutdown) {
1591 sub->handler->subscription_shutdown(sub);
1592 }
1593}
1594
1596{
1597 struct sip_subscription_tree *sub_tree = obj;
1598
1599 /* This is why we keep the dialog on the subscription. When the subscription
1600 * is destroyed, there is no guarantee that the underlying dialog is ready
1601 * to be destroyed. Furthermore, there's no guarantee in the opposite direction
1602 * either. The dialog could be destroyed before our subscription is. We fix
1603 * this problem by keeping a reference to the dialog until it is time to
1604 * destroy the subscription. We need to have the dialog available when the
1605 * subscription is destroyed so that we can guarantee that our attempt to
1606 * remove the serializer will be successful.
1607 */
1608 pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module);
1609 sub_tree->dlg = NULL;
1610
1611 return 0;
1612}
1613
1614static void subscription_tree_destructor(void *obj)
1615{
1616 struct sip_subscription_tree *sub_tree = obj;
1617
1618 ast_debug(3, "Destroying subscription tree %p '%s->%s'\n",
1619 sub_tree,
1620 sub_tree->endpoint ? ast_sorcery_object_get_id(sub_tree->endpoint) : "Unknown",
1621 sub_tree->root ? sub_tree->root->resource : "Unknown");
1622
1623 destroy_subscriptions(sub_tree->root);
1624
1625 if (sub_tree->dlg) {
1628 }
1629
1630 ao2_cleanup(sub_tree->endpoint);
1631
1634}
1635
1637{
1638 ast_debug(3, "Removing subscription %p '%s->%s' reference to subscription tree %p\n",
1639 sub, ast_sorcery_object_get_id(sub->tree->endpoint), sub->resource, sub->tree);
1640 ao2_cleanup(sub->tree);
1641}
1642
1643static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg)
1644{
1645 sub_tree->dlg = dlg;
1648 pjsip_evsub_set_mod_data(sub_tree->evsub, pubsub_module.id, sub_tree);
1649 pjsip_dlg_inc_session(dlg, &pubsub_module);
1650}
1651
1653{
1654 struct sip_subscription_tree *sub_tree;
1655
1656 sub_tree = ao2_alloc(sizeof *sub_tree, subscription_tree_destructor);
1657 if (!sub_tree) {
1658 return NULL;
1659 }
1660
1662
1663 if (rdata) {
1664 /*
1665 * We must continue using the serializer that the original
1666 * SUBSCRIBE came in on for the dialog. There may be
1667 * retransmissions already enqueued in the original
1668 * serializer that can result in reentrancy and message
1669 * sequencing problems.
1670 */
1672 } else {
1673 char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
1674
1675 /* Create name with seq number appended. */
1676 ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/pubsub/%s",
1678
1679 sub_tree->serializer = ast_sip_create_serializer(tps_name);
1680 }
1681 if (!sub_tree->serializer) {
1682 ao2_ref(sub_tree, -1);
1683 return NULL;
1684 }
1685
1686 sub_tree->endpoint = ao2_bump(endpoint);
1687 sub_tree->notify_sched_id = -1;
1688
1689 return sub_tree;
1690}
1691
1692/*!
1693 * \brief Create a subscription tree based on a resource tree.
1694 *
1695 * Using the previously-determined valid resources in the provided resource tree,
1696 * a corresponding tree of ast_sip_subscriptions are created. The root of the
1697 * subscription tree is a real subscription, and the rest in the tree are
1698 * virtual subscriptions.
1699 *
1700 * \param handler The handler to use for leaf subscriptions
1701 * \param endpoint The endpoint that sent the SUBSCRIBE request
1702 * \param rdata The SUBSCRIBE content
1703 * \param resource The requested resource in the SUBSCRIBE request
1704 * \param generator The body generator to use in leaf subscriptions
1705 * \param tree The resource tree on which the subscription tree is based
1706 * \param[out] dlg_status The result of attempting to create a dialog
1707 * \param persistence
1708 *
1709 * \retval NULL Could not create the subscription tree
1710 * \retval non-NULL The root of the created subscription tree
1711 */
1712
1714 struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *resource,
1715 struct ast_sip_pubsub_body_generator *generator, struct resource_tree *tree,
1716 pj_status_t *dlg_status, struct subscription_persistence *persistence)
1717{
1718 struct sip_subscription_tree *sub_tree;
1719 pjsip_dialog *dlg;
1720
1721 sub_tree = allocate_subscription_tree(endpoint, rdata);
1722 if (!sub_tree) {
1723 *dlg_status = PJ_ENOMEM;
1724 return NULL;
1725 }
1726 sub_tree->role = AST_SIP_NOTIFIER;
1727
1728 dlg = ast_sip_create_dialog_uas_locked(endpoint, rdata, dlg_status);
1729 if (!dlg) {
1730 if (*dlg_status != PJ_EEXISTS) {
1731 ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
1732 }
1733 ao2_ref(sub_tree, -1);
1734 return NULL;
1735 }
1736
1737 persistence = ast_sip_mod_data_get(rdata->endpt_info.mod_data,
1739 if (persistence) {
1740 /* Update the created dialog with the persisted information */
1741 pjsip_ua_unregister_dlg(pjsip_ua_instance(), dlg);
1742 pj_strdup2(dlg->pool, &dlg->local.info->tag, persistence->tag);
1743 dlg->local.tag_hval = pj_hash_calc_tolower(0, NULL, &dlg->local.info->tag);
1744 pjsip_ua_register_dlg(pjsip_ua_instance(), dlg);
1745 dlg->local.cseq = persistence->cseq;
1746 }
1747
1748 pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub_tree->evsub);
1749
1750 subscription_setup_dialog(sub_tree, dlg);
1751
1752 /*
1753 * The evsub and subscription setup both add dialog refs, so the dialog ref that
1754 * was added when the dialog was created (see ast_sip_create_dialog_uas_lock) can
1755 * now be removed. The lock should no longer be needed so can be removed too.
1756 */
1757 pjsip_dlg_dec_lock(dlg);
1758
1759#ifdef HAVE_PJSIP_EVSUB_GRP_LOCK
1760 pjsip_evsub_add_ref(sub_tree->evsub);
1761#endif
1762
1764 pjsip_msg_clone(dlg->pool, rdata->msg_info.msg));
1765
1767
1768 /* Persistence information needs to be available for all the subscriptions */
1769 sub_tree->persistence = ao2_bump(persistence);
1770
1771 sub_tree->root = create_virtual_subscriptions(handler, resource, generator, sub_tree, tree->root);
1772 if (AST_VECTOR_SIZE(&sub_tree->root->children) > 0) {
1773 sub_tree->is_list = 1;
1774 }
1775
1776 add_subscription(sub_tree);
1777
1778 return sub_tree;
1779}
1780
1781/*! Wrapper structure for initial_notify_task */
1784 unsigned int expires;
1785};
1786
1787static int initial_notify_task(void *obj);
1788static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state);
1789
1790/*! Persistent subscription recreation continuation under distributor serializer data */
1793 pjsip_rx_data *rdata;
1794};
1795
1796/*!
1797 * \internal
1798 * \brief subscription_persistence_recreate continuation under distributor serializer.
1799 * \since 13.10.0
1800 *
1801 * \retval 0 on success.
1802 * \retval -1 on error.
1803 */
1804static int sub_persistence_recreate(void *obj)
1805{
1806 struct persistence_recreate_data *recreate_data = obj;
1807 struct subscription_persistence *persistence = recreate_data->persistence;
1808 pjsip_rx_data *rdata = recreate_data->rdata;
1809 struct ast_sip_endpoint *endpoint;
1810 struct sip_subscription_tree *sub_tree;
1811 struct ast_sip_pubsub_body_generator *generator;
1813 char *resource;
1814 size_t resource_size;
1815 int resp;
1816 struct resource_tree tree;
1817 pjsip_expires_hdr *expires_header;
1818 int64_t expires;
1819 const pj_str_t *user;
1820
1821 user = ast_sip_pjsip_uri_get_username(rdata->msg_info.msg->line.req.uri);
1822 resource_size = pj_strlen(user) + 1;
1823 resource = ast_alloca(resource_size);
1824 ast_copy_pj_str(resource, user, resource_size);
1825
1826 /*
1827 * We may want to match without any user options getting
1828 * in the way.
1829 */
1831
1833 if (!handler || !handler->notifier) {
1834 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not get subscription handler.\n",
1835 persistence->endpoint);
1837 return 0;
1838 }
1839
1841 if (!generator) {
1842 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Body generator not available.\n",
1843 persistence->endpoint);
1845 return 0;
1846 }
1847
1848 ast_sip_mod_data_set(rdata->tp_info.pool, rdata->endpt_info.mod_data,
1849 pubsub_module.id, MOD_DATA_PERSISTENCE, persistence);
1850
1851 /* Getting the endpoint may take some time that can affect the expiration. */
1852 endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",
1853 persistence->endpoint);
1854 if (!endpoint) {
1855 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The endpoint was not found\n",
1856 persistence->endpoint);
1858 return 0;
1859 }
1860
1861 /* Update the expiration header with the new expiration */
1862 expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES,
1863 rdata->msg_info.msg->hdr.next);
1864 if (!expires_header) {
1865 expires_header = pjsip_expires_hdr_create(rdata->tp_info.pool, 0);
1866 if (!expires_header) {
1867 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not update expires header.\n",
1868 persistence->endpoint);
1870 ao2_ref(endpoint, -1);
1871 return 0;
1872 }
1873 pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr *) expires_header);
1874 }
1875
1876 expires = (ast_tvdiff_ms(persistence->expires, ast_tvnow()) / 1000);
1877 if (expires <= 0) {
1878 /* The subscription expired since we started recreating the subscription. */
1879 ast_debug(3, "Expired subscription retrived from persistent store '%s' %s\n",
1880 persistence->endpoint, persistence->tag);
1882 ao2_ref(endpoint, -1);
1883 return 0;
1884 }
1885 expires_header->ivalue = expires;
1886
1887 memset(&tree, 0, sizeof(tree));
1888 resp = build_resource_tree(endpoint, handler, resource, &tree,
1890 if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
1891 pj_status_t dlg_status;
1892
1893 sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator,
1894 &tree, &dlg_status, persistence);
1895 if (!sub_tree) {
1896 if (dlg_status != PJ_EEXISTS) {
1897 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not create subscription tree.\n",
1898 persistence->endpoint);
1900 }
1901 } else {
1902 struct initial_notify_data *ind = ast_malloc(sizeof(*ind));
1903
1904 if (!ind) {
1905 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
1906 goto error;
1907 }
1908
1909 ind->sub_tree = ao2_bump(sub_tree);
1910 ind->expires = expires_header->ivalue;
1911
1914 /* Could not send initial subscribe NOTIFY */
1915 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
1916 ao2_ref(sub_tree, -1);
1917 ast_free(ind);
1918 }
1919 }
1920 } else {
1922 }
1923
1924error:
1925 resource_tree_destroy(&tree);
1926 ao2_ref(endpoint, -1);
1927
1928 return 0;
1929}
1930
1931/*! \brief Callback function to perform the actual recreation of a subscription */
1932static int subscription_persistence_recreate(void *obj, void *arg, int flags)
1933{
1934 struct subscription_persistence *persistence = obj;
1935 pj_pool_t *pool = arg;
1937 pjsip_rx_data rdata;
1938 struct persistence_recreate_data recreate_data;
1939
1940 /* If this subscription used a reliable transport it can't be reestablished so remove it */
1942 ast_debug(3, "Deleting subscription marked as 'prune' from persistent store '%s' %s\n",
1945 return 0;
1946 }
1947
1948 /* If this subscription has already expired remove it */
1950 ast_debug(3, "Expired subscription retrived from persistent store '%s' %s\n",
1953 return 0;
1954 }
1955
1956 memset(&rdata, 0, sizeof(rdata));
1957 pj_pool_reset(pool);
1958 rdata.tp_info.pool = pool;
1959
1963 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The message could not be parsed\n",
1966 return 0;
1967 }
1968
1969 if (rdata.msg_info.msg->type != PJSIP_REQUEST_MSG) {
1970 ast_log(LOG_NOTICE, "Failed recreating '%s' subscription: Stored a SIP response instead of a request.\n",
1973 return 0;
1974 }
1975
1976 /* Continue the remainder in the distributor serializer */
1978 if (!serializer) {
1979 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not get distributor serializer.\n",
1982 return 0;
1983 }
1984 recreate_data.persistence = persistence;
1985 recreate_data.rdata = &rdata;
1987 &recreate_data)) {
1988 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not continue under distributor serializer.\n",
1991 }
1993
1994 return 0;
1995}
1996
1997/*! \brief Function which loads and recreates persisted subscriptions upon startup when the system is fully booted */
1998static int subscription_persistence_load(void *data)
1999{
2000 struct ao2_container *persisted_subscriptions = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),
2001 "subscription_persistence", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
2002 pj_pool_t *pool;
2003
2004 pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "rtd%p", PJSIP_POOL_RDATA_LEN,
2005 PJSIP_POOL_RDATA_INC);
2006 if (!pool) {
2007 ast_log(LOG_WARNING, "Could not create a memory pool for recreating SIP subscriptions\n");
2008 return 0;
2009 }
2010
2011 ao2_callback(persisted_subscriptions, OBJ_NODATA, subscription_persistence_recreate, pool);
2012
2013 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
2014
2015 ao2_ref(persisted_subscriptions, -1);
2016 return 0;
2017}
2018
2019/*! \brief Event callback which fires subscription persistence recreation when the system is fully booted */
2021{
2022 struct ast_json_payload *payload;
2023 const char *type;
2024
2026 return;
2027 }
2028
2029 payload = stasis_message_data(message);
2030 type = ast_json_string_get(ast_json_object_get(payload->json, "type"));
2031
2032 /* This subscription only responds to the FullyBooted event so that all modules have been loaded when we
2033 * recreate SIP subscriptions.
2034 */
2035 if (strcmp(type, "FullyBooted")) {
2036 return;
2037 }
2038
2039 /* This has to be here so the subscription is recreated when the body generator is available */
2041
2042 /* Once the system is fully booted we don't care anymore */
2044}
2045
2046typedef int (*on_subscription_t)(struct sip_subscription_tree *sub, void *arg);
2047
2048static int for_each_subscription(on_subscription_t on_subscription, void *arg)
2049{
2050 int num = 0;
2051 struct sip_subscription_tree *i;
2052
2053 if (!on_subscription) {
2054 return num;
2055 }
2056
2059 if (on_subscription(i, arg)) {
2060 break;
2061 }
2062 ++num;
2063 }
2065 return num;
2066}
2067
2069 struct ast_str **buf)
2070{
2071 char str[256];
2072 struct ast_sip_endpoint_id_configuration *id = &sub_tree->endpoint->id;
2073
2074 ast_str_append(buf, 0, "Role: %s\r\n",
2075 sip_subscription_roles_map[sub_tree->role]);
2076 ast_str_append(buf, 0, "Endpoint: %s\r\n",
2078
2079 if (sub_tree->dlg) {
2080 ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str));
2081 } else {
2082 ast_copy_string(str, "<unknown>", sizeof(str));
2083 }
2084 ast_str_append(buf, 0, "Callid: %s\r\n", str);
2085
2086 ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(sub_tree->evsub));
2087
2088 ast_callerid_merge(str, sizeof(str),
2089 S_COR(id->self.name.valid, id->self.name.str, NULL),
2090 S_COR(id->self.number.valid, id->self.number.str, NULL),
2091 "Unknown");
2092
2093 ast_str_append(buf, 0, "Callerid: %s\r\n", str);
2094
2095 /* XXX This needs to be done recursively for lists */
2096 if (sub_tree->root->handler->to_ami) {
2097 sub_tree->root->handler->to_ami(sub_tree->root, buf);
2098 }
2099}
2100
2101
2103{
2104 pjsip_dialog *dlg;
2105 pjsip_msg *msg;
2106 pj_str_t name;
2107
2108 dlg = sub->tree->dlg;
2109 msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
2110 pj_cstr(&name, header);
2111
2112 return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
2113}
2114
2115/* XXX This function is not used. */
2117 struct ast_sip_endpoint *endpoint, const char *resource)
2118{
2119 struct ast_sip_subscription *sub;
2120 pjsip_dialog *dlg;
2121 struct ast_sip_contact *contact;
2122 pj_str_t event;
2123 pjsip_tx_data *tdata;
2124 pjsip_evsub *evsub;
2125 struct sip_subscription_tree *sub_tree = NULL;
2126
2128 if (!sub_tree) {
2129 return NULL;
2130 }
2131
2132 sub = allocate_subscription(handler, resource, NULL, sub_tree);
2133 if (!sub) {
2134 ao2_cleanup(sub_tree);
2135 return NULL;
2136 }
2137
2139 if (!contact || ast_strlen_zero(contact->uri)) {
2140 ast_log(LOG_WARNING, "No contacts configured for endpoint %s. Unable to create SIP subsription\n",
2142 ao2_ref(sub_tree, -1);
2143 ao2_cleanup(contact);
2144 return NULL;
2145 }
2146
2148 ao2_cleanup(contact);
2149 if (!dlg) {
2150 ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
2151 ao2_ref(sub_tree, -1);
2152 return NULL;
2153 }
2154
2155 pj_cstr(&event, handler->event_name);
2156 pjsip_evsub_create_uac(dlg, &pubsub_cb, &event, 0, &sub_tree->evsub);
2157 subscription_setup_dialog(sub_tree, dlg);
2158
2159 evsub = sub_tree->evsub;
2160
2161 if (pjsip_evsub_initiate(evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
2162 pjsip_evsub_send_request(sub_tree->evsub, tdata);
2163 } else {
2164 /* pjsip_evsub_terminate will result in pubsub_on_evsub_state,
2165 * being called and terminating the subscription. Therefore, we don't
2166 * need to decrease the reference count of sub here.
2167 */
2168 pjsip_evsub_terminate(evsub, PJ_TRUE);
2169 ao2_ref(sub_tree, -1);
2170 return NULL;
2171 }
2172
2173 add_subscription(sub_tree);
2174
2175 return sub;
2176}
2177
2179{
2180 ast_assert(sub->tree->dlg != NULL);
2181 return sub->tree->dlg;
2182}
2183
2185{
2186 ast_assert(sub->tree->endpoint != NULL);
2187 return ao2_bump(sub->tree->endpoint);
2188}
2189
2191{
2192 ast_assert(sub->tree->serializer != NULL);
2193 return sub->tree->serializer;
2194}
2195
2196/*!
2197 * \brief Pre-allocate a buffer for the transmission
2198 *
2199 * Typically, we let PJSIP do this step for us when we send a request. PJSIP's buffer
2200 * allocation algorithm is to allocate a buffer of PJSIP_MAX_PKT_LEN bytes and attempt
2201 * to write the packet to the allocated buffer. If the buffer is too small to hold the
2202 * packet, then we get told the message is too long to be sent.
2203 *
2204 * When dealing with SIP NOTIFY, especially with RLS, it is possible to exceed
2205 * PJSIP_MAX_PKT_LEN. Rather than accepting the limitation imposed on us by default,
2206 * we instead take the strategy of pre-allocating the buffer, testing for ourselves
2207 * if the message will fit, and resizing the buffer as required.
2208 *
2209 * The limit we impose is double that of the maximum packet length.
2210 *
2211 * \param tdata The tdata onto which to allocate a buffer
2212 * \retval 0 Success
2213 * \retval -1 The message is too large
2214 */
2215static int allocate_tdata_buffer(pjsip_tx_data *tdata)
2216{
2217 int buf_size;
2218 int size = -1;
2219 char *buf;
2220
2221 for (buf_size = PJSIP_MAX_PKT_LEN; size == -1 && buf_size < (PJSIP_MAX_PKT_LEN * 2); buf_size *= 2) {
2222 buf = pj_pool_alloc(tdata->pool, buf_size);
2223 size = pjsip_msg_print(tdata->msg, buf, buf_size);
2224 }
2225
2226 if (size == -1) {
2227 return -1;
2228 }
2229
2230 tdata->buf.start = buf;
2231 tdata->buf.cur = tdata->buf.start;
2232 tdata->buf.end = tdata->buf.start + buf_size;
2233
2234 return 0;
2235}
2236
2237static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree, pjsip_tx_data *tdata)
2238{
2239#ifdef TEST_FRAMEWORK
2240 struct ast_sip_endpoint *endpoint = sub_tree->endpoint;
2241 pjsip_evsub *evsub = sub_tree->evsub;
2242#endif
2243 int res;
2244
2245 if (allocate_tdata_buffer(tdata)) {
2246 ast_log(LOG_ERROR, "SIP request %s is too large to send.\n", tdata->info);
2247 pjsip_tx_data_dec_ref(tdata);
2248 return -1;
2249 }
2250
2251 res = pjsip_evsub_send_request(sub_tree->evsub, tdata);
2252
2254
2255 ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
2256 "StateText: %s\r\n"
2257 "Endpoint: %s\r\n",
2258 pjsip_evsub_get_state_name(evsub),
2259 ast_sorcery_object_get_id(endpoint));
2260
2261 return (res == PJ_SUCCESS ? 0 : -1);
2262}
2263
2264/*!
2265 * \brief Add a resource XML element to an RLMI body
2266 *
2267 * Each resource element represents a subscribed resource in the list. This function currently
2268 * will unconditionally add an instance element to each created resource element. Instance
2269 * elements refer to later parts in the multipart body.
2270 *
2271 * \param pool PJLIB allocation pool
2272 * \param rlmi
2273 * \param cid Content-ID header of the resource
2274 * \param resource_name Name of the resource
2275 * \param resource_uri URI of the resource
2276 * \param state State of the subscribed resource
2277 */
2278static void add_rlmi_resource(pj_pool_t *pool, pj_xml_node *rlmi, const pjsip_generic_string_hdr *cid,
2279 const char *resource_name, const pjsip_sip_uri *resource_uri, pjsip_evsub_state state)
2280{
2281 static pj_str_t cid_name = { "cid", 3 };
2282 pj_xml_node *resource;
2283 pj_xml_node *name;
2284 pj_xml_node *instance;
2285 pj_xml_attr *cid_attr;
2286 char id[6];
2287 char uri[PJSIP_MAX_URL_SIZE];
2288 char name_sanitized[PJSIP_MAX_URL_SIZE];
2289
2290 /* This creates a string representing the Content-ID without the enclosing < > */
2291 const pj_str_t cid_stripped = {
2292 .ptr = cid->hvalue.ptr + 1,
2293 .slen = cid->hvalue.slen - 2,
2294 };
2295
2296 resource = ast_sip_presence_xml_create_node(pool, rlmi, "resource");
2297 name = ast_sip_presence_xml_create_node(pool, resource, "name");
2298 instance = ast_sip_presence_xml_create_node(pool, resource, "instance");
2299
2300 pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, resource_uri, uri, sizeof(uri));
2301 ast_sip_presence_xml_create_attr(pool, resource, "uri", uri);
2302
2303 ast_sip_sanitize_xml(resource_name, name_sanitized, sizeof(name_sanitized));
2304 pj_strdup2(pool, &name->content, name_sanitized);
2305
2306 ast_generate_random_string(id, sizeof(id));
2307
2308 ast_sip_presence_xml_create_attr(pool, instance, "id", id);
2309 ast_sip_presence_xml_create_attr(pool, instance, "state",
2310 state == PJSIP_EVSUB_STATE_TERMINATED ? "terminated" : "active");
2311
2312 /* Use the PJLIB-util XML library directly here since we are using a
2313 * pj_str_t
2314 */
2315
2316 cid_attr = pj_xml_attr_new(pool, &cid_name, &cid_stripped);
2317 pj_xml_add_attr(instance, cid_attr);
2318}
2319
2320/*!
2321 * \brief A multipart body part and meta-information
2322 *
2323 * When creating a multipart body part, the end result (the
2324 * pjsip_multipart_part) is hard to inspect without undoing
2325 * a lot of what was done to create it. Therefore, we use this
2326 * structure to store meta-information about the body part.
2327 *
2328 * The main consumer of this is the creator of the RLMI body
2329 * part of a multipart resource list body.
2330 */
2332 /*! Content-ID header for the body part */
2333 pjsip_generic_string_hdr *cid;
2334 /*! Subscribed resource represented in the body part */
2335 const char *resource;
2336 /*! URI for the subscribed body part */
2337 pjsip_sip_uri *uri;
2338 /*! Subscription state of the resource represented in the body part */
2339 pjsip_evsub_state state;
2340 /*! The actual body part that will be present in the multipart body */
2341 pjsip_multipart_part *part;
2342 /*! Display name for the resource */
2343 const char *display_name;
2344};
2345
2346/*!
2347 * \brief Type declaration for container of body part structures
2348 */
2350
2351/*!
2352 * \brief Create a Content-ID header
2353 *
2354 * Content-ID headers are required by RFC2387 for multipart/related
2355 * bodies. They serve as identifiers for each part of the multipart body.
2356 *
2357 * \param pool PJLIB allocation pool
2358 * \param sub Subscription to a resource
2359 */
2360static pjsip_generic_string_hdr *generate_content_id_hdr(pj_pool_t *pool,
2361 const struct ast_sip_subscription *sub)
2362{
2363 static const pj_str_t cid_name = { "Content-ID", 10 };
2364 pjsip_generic_string_hdr *cid;
2365 char id[6];
2366 size_t alloc_size;
2367 pj_str_t cid_value;
2368
2369 /* '<' + '@' + '>' = 3. pj_str_t does not require a null-terminator */
2370 alloc_size = sizeof(id) + pj_strlen(&sub->uri->host) + 3;
2371 cid_value.ptr = pj_pool_alloc(pool, alloc_size);
2372 cid_value.slen = sprintf(cid_value.ptr, "<%s@%.*s>",
2373 ast_generate_random_string(id, sizeof(id)),
2374 (int) pj_strlen(&sub->uri->host), pj_strbuf(&sub->uri->host));
2375 cid = pjsip_generic_string_hdr_create(pool, &cid_name, &cid_value);
2376
2377 return cid;
2378}
2379
2380static int rlmi_print_body(struct pjsip_msg_body *msg_body, char *buf, pj_size_t size)
2381{
2382 int num_printed;
2383 pj_xml_node *rlmi = msg_body->data;
2384
2385 num_printed = pj_xml_print(rlmi, buf, size, PJ_TRUE);
2386 if (num_printed <= AST_PJSIP_XML_PROLOG_LEN) {
2387 return -1;
2388 }
2389
2390 return num_printed;
2391}
2392
2393static void *rlmi_clone_data(pj_pool_t *pool, const void *data, unsigned len)
2394{
2395 const pj_xml_node *rlmi = data;
2396
2397 return pj_xml_clone(pool, rlmi);
2398}
2399
2400/*!
2401 * \brief Create an RLMI body part for a multipart resource list body
2402 *
2403 * RLMI (Resource list meta information) is a special body type that lists
2404 * the subscribed resources and tells subscribers the number of subscribed
2405 * resources and what other body parts are in the multipart body. The
2406 * RLMI body also has a version number that a subscriber can use to ensure
2407 * that the locally-stored state corresponds to server state.
2408 *
2409 * \param pool The allocation pool
2410 * \param sub The subscription representing the subscribed resource list
2411 * \param body_parts A container of body parts that RLMI will refer to
2412 * \param full_state Indicates whether this is a full or partial state notification
2413 * \return The multipart part representing the RLMI body
2414 */
2415static pjsip_multipart_part *build_rlmi_body(pj_pool_t *pool, struct ast_sip_subscription *sub,
2416 struct body_part_list *body_parts, unsigned int full_state)
2417{
2418 pj_xml_node *rlmi;
2419 pj_xml_node *name;
2420 pjsip_multipart_part *rlmi_part;
2421 char version_str[32];
2422 char uri[PJSIP_MAX_URL_SIZE];
2423 pjsip_generic_string_hdr *cid;
2424 int i;
2425
2426 rlmi = ast_sip_presence_xml_create_node(pool, NULL, "list");
2427 ast_sip_presence_xml_create_attr(pool, rlmi, "xmlns", "urn:ietf:params:xml:ns:rlmi");
2428
2429 ast_sip_subscription_get_local_uri(sub, uri, sizeof(uri));
2430 ast_sip_presence_xml_create_attr(pool, rlmi, "uri", uri);
2431
2432 snprintf(version_str, sizeof(version_str), "%u", sub->version++);
2433 ast_sip_presence_xml_create_attr(pool, rlmi, "version", version_str);
2434 ast_sip_presence_xml_create_attr(pool, rlmi, "fullState", full_state ? "true" : "false");
2435
2436 name = ast_sip_presence_xml_create_node(pool, rlmi, "name");
2437 pj_strdup2(pool, &name->content, ast_sip_subscription_get_resource_name(sub));
2438
2439 for (i = 0; i < AST_VECTOR_SIZE(body_parts); ++i) {
2440 const struct body_part *part = AST_VECTOR_GET(body_parts, i);
2441
2442 add_rlmi_resource(pool, rlmi, part->cid, S_OR(part->display_name, part->resource), part->uri, part->state);
2443 }
2444
2445 rlmi_part = pjsip_multipart_create_part(pool);
2446
2447 rlmi_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
2448 pjsip_media_type_cp(pool, &rlmi_part->body->content_type, &rlmi_media_type);
2449
2450 rlmi_part->body->data = pj_xml_clone(pool, rlmi);
2451 rlmi_part->body->clone_data = rlmi_clone_data;
2452 rlmi_part->body->print_body = rlmi_print_body;
2453
2455 pj_list_insert_before(&rlmi_part->hdr, cid);
2456
2457 return rlmi_part;
2458}
2459
2460static pjsip_msg_body *generate_notify_body(pj_pool_t *pool, struct ast_sip_subscription *root,
2461 unsigned int force_full_state);
2462
2463/*!
2464 * \brief Destroy a list of body parts
2465 *
2466 * \param parts The container of parts to destroy
2467 */
2468static void free_body_parts(struct body_part_list *parts)
2469{
2470 int i;
2471
2472 for (i = 0; i < AST_VECTOR_SIZE(parts); ++i) {
2473 struct body_part *part = AST_VECTOR_GET(parts, i);
2474 ast_free(part);
2475 }
2476
2477 AST_VECTOR_FREE(parts);
2478}
2479
2480/*!
2481 * \brief Allocate and initialize a body part structure
2482 *
2483 * \param pool PJLIB allocation pool
2484 * \param sub Subscription representing a subscribed resource
2485 */
2486static struct body_part *allocate_body_part(pj_pool_t *pool, const struct ast_sip_subscription *sub)
2487{
2488 struct body_part *bp;
2489
2490 bp = ast_calloc(1, sizeof(*bp));
2491 if (!bp) {
2492 return NULL;
2493 }
2494
2495 bp->cid = generate_content_id_hdr(pool, sub);
2496 bp->resource = sub->resource;
2497 bp->state = sub->subscription_state;
2498 bp->uri = sub->uri;
2499 bp->display_name = sub->display_name;
2500
2501 return bp;
2502}
2503
2504/*!
2505 * \brief Create a multipart body part for a subscribed resource
2506 *
2507 * \param pool PJLIB allocation pool
2508 * \param sub The subscription representing a subscribed resource
2509 * \param parts A vector of parts to append the created part to.
2510 * \param use_full_state Unused locally, but may be passed to other functions
2511 */
2512static void build_body_part(pj_pool_t *pool, struct ast_sip_subscription *sub,
2513 struct body_part_list *parts, unsigned int use_full_state)
2514{
2515 struct body_part *bp;
2516 pjsip_msg_body *body;
2517
2518 bp = allocate_body_part(pool, sub);
2519 if (!bp) {
2520 return;
2521 }
2522
2523 body = generate_notify_body(pool, sub, use_full_state);
2524 if (!body) {
2525 /* Partial state was requested and the resource has not changed state */
2526 ast_free(bp);
2527 return;
2528 }
2529
2530 bp->part = pjsip_multipart_create_part(pool);
2531 bp->part->body = body;
2532 pj_list_insert_before(&bp->part->hdr, bp->cid);
2533
2534 if (AST_VECTOR_APPEND(parts, bp)) {
2535 ast_free(bp);
2536 }
2537}
2538
2539/*!
2540 * \brief Create and initialize the PJSIP multipart body structure for a resource list subscription
2541 *
2542 * \param pool
2543 * \return The multipart message body
2544 */
2545static pjsip_msg_body *create_multipart_body(pj_pool_t *pool)
2546{
2547 pjsip_media_type media_type;
2548 pjsip_param *media_type_param;
2549 char boundary[6];
2550 pj_str_t pj_boundary;
2551
2552 pjsip_media_type_init2(&media_type, "multipart", "related");
2553
2554 media_type_param = pj_pool_alloc(pool, sizeof(*media_type_param));
2555 pj_list_init(media_type_param);
2556
2557 pj_strdup2(pool, &media_type_param->name, "type");
2558 pj_strdup2(pool, &media_type_param->value, "\"application/rlmi+xml\"");
2559
2560 pj_list_insert_before(&media_type.param, media_type_param);
2561
2562 pj_cstr(&pj_boundary, ast_generate_random_string(boundary, sizeof(boundary)));
2563 return pjsip_multipart_create(pool, &media_type, &pj_boundary);
2564}
2565
2566/*!
2567 * \brief Create a resource list body for NOTIFY requests
2568 *
2569 * Resource list bodies are multipart/related bodies. The first part of the multipart body
2570 * is an RLMI body that describes the rest of the parts to come. The other parts of the body
2571 * convey state of individual subscribed resources.
2572 *
2573 * \param pool PJLIB allocation pool
2574 * \param sub Subscription details from which to generate body
2575 * \param force_full_state If true, ignore resource list settings and send a full state notification
2576 * \return The generated multipart/related body
2577 */
2578static pjsip_msg_body *generate_list_body(pj_pool_t *pool, struct ast_sip_subscription *sub,
2579 unsigned int force_full_state)
2580{
2581 int i;
2582 pjsip_multipart_part *rlmi_part;
2583 pjsip_msg_body *multipart;
2584 struct body_part_list body_parts;
2585 unsigned int use_full_state = force_full_state ? 1 : sub->full_state;
2586
2587 if (AST_VECTOR_INIT(&body_parts, AST_VECTOR_SIZE(&sub->children))) {
2588 return NULL;
2589 }
2590
2591 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
2592 build_body_part(pool, AST_VECTOR_GET(&sub->children, i), &body_parts, use_full_state);
2593 }
2594
2595 /* This can happen if issuing partial state and no children of the list have changed state */
2596 if (AST_VECTOR_SIZE(&body_parts) == 0) {
2597 free_body_parts(&body_parts);
2598 return NULL;
2599 }
2600
2601 multipart = create_multipart_body(pool);
2602
2603 rlmi_part = build_rlmi_body(pool, sub, &body_parts, use_full_state);
2604 if (!rlmi_part) {
2605 free_body_parts(&body_parts);
2606 return NULL;
2607 }
2608 pjsip_multipart_add_part(pool, multipart, rlmi_part);
2609
2610 for (i = 0; i < AST_VECTOR_SIZE(&body_parts); ++i) {
2611 pjsip_multipart_add_part(pool, multipart, AST_VECTOR_GET(&body_parts, i)->part);
2612 }
2613
2614 free_body_parts(&body_parts);
2615 return multipart;
2616}
2617
2618/*!
2619 * \brief Create the body for a NOTIFY request.
2620 *
2621 * \param pool The pool used for allocations
2622 * \param root The root of the subscription tree
2623 * \param force_full_state If true, ignore resource list settings and send a full state notification
2624 */
2625static pjsip_msg_body *generate_notify_body(pj_pool_t *pool, struct ast_sip_subscription *root,
2626 unsigned int force_full_state)
2627{
2628 pjsip_msg_body *body;
2629
2630 if (AST_VECTOR_SIZE(&root->children) == 0) {
2631 if (force_full_state || root->body_changed) {
2632 /* Not a list. We've already generated the body and saved it on the subscription.
2633 * Use that directly.
2634 */
2635 pj_str_t type;
2636 pj_str_t subtype;
2637 pj_str_t text;
2638
2640 pj_cstr(&subtype, ast_sip_subscription_get_body_subtype(root));
2641 pj_cstr(&text, ast_str_buffer(root->body_text));
2642
2643 body = pjsip_msg_body_create(pool, &type, &subtype, &text);
2644 root->body_changed = 0;
2645 } else {
2646 body = NULL;
2647 }
2648 } else {
2649 body = generate_list_body(pool, root, force_full_state);
2650 }
2651
2652 return body;
2653}
2654
2655/*!
2656 * \brief Shortcut method to create a Require: eventlist header
2657 */
2658static pjsip_require_hdr *create_require_eventlist(pj_pool_t *pool)
2659{
2660 pjsip_require_hdr *require;
2661
2662 require = pjsip_require_hdr_create(pool);
2663 pj_strdup2(pool, &require->values[0], "eventlist");
2664 require->count = 1;
2665
2666 return require;
2667}
2668
2670{
2671 int i;
2672
2673 sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
2674 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
2675 set_state_terminated(AST_VECTOR_GET(&sub->children, i));
2676 }
2677}
2678
2679/*!
2680 * \brief Send a NOTIFY request to a subscriber
2681 *
2682 * \pre sub_tree->dlg is locked
2683 *
2684 * \param sub_tree The subscription tree representing the subscription
2685 * \param force_full_state If true, ignore resource list settings and send full resource list state.
2686 * \retval 0 Success
2687 * \retval non-zero Failure
2688 */
2689static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state)
2690{
2691 pjsip_evsub *evsub = sub_tree->evsub;
2692 pjsip_tx_data *tdata;
2693
2694 if (ast_shutdown_final()
2695 && sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED
2696 && sub_tree->persistence) {
2697 return 0;
2698 }
2699
2700 if (pjsip_evsub_notify(evsub, sub_tree->root->subscription_state,
2701 NULL, NULL, &tdata) != PJ_SUCCESS) {
2702 return -1;
2703 }
2704
2705 tdata->msg->body = generate_notify_body(tdata->pool, sub_tree->root, force_full_state);
2706 if (!tdata->msg->body) {
2707 pjsip_tx_data_dec_ref(tdata);
2708 return -1;
2709 }
2710
2711 if (sub_tree->is_list) {
2712 pjsip_require_hdr *require = create_require_eventlist(tdata->pool);
2713 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) require);
2714 }
2715
2716 if (sub_tree->root->handler->notifier->notify_created) {
2717 /* The module for this event wants a callback to the pjsip_tx_data,
2718 * e.g. so it can add custom headers or do something custom to the response. */
2719 sub_tree->root->handler->notifier->notify_created(sub_tree->root, tdata);
2720 }
2721
2722 if (sip_subscription_send_request(sub_tree, tdata)) {
2723 /* do not call pjsip_tx_data_dec_ref(tdata). The pjsip_dlg_send_request deletes the message on error */
2724 return -1;
2725 }
2726
2727 sub_tree->send_scheduled_notify = 0;
2728
2729 return 0;
2730}
2731
2732static int serialized_send_notify(void *userdata)
2733{
2734 struct sip_subscription_tree *sub_tree = userdata;
2735 pjsip_dialog *dlg = sub_tree->dlg;
2736
2737 pjsip_dlg_inc_lock(dlg);
2738
2739 sub_tree->notify_sched_id = -1;
2740
2741 /* It's possible that between when the notification was scheduled
2742 * and now a new SUBSCRIBE arrived requiring full state to be
2743 * sent out in an immediate NOTIFY. It's also possible that we're
2744 * already processing a terminate. If that has happened, we need to
2745 * bail out here instead of sending the batched NOTIFY.
2746 */
2747
2749 || !sub_tree->send_scheduled_notify) {
2750 pjsip_dlg_dec_lock(dlg);
2751 ao2_cleanup(sub_tree);
2752 return 0;
2753 }
2754
2755 if (sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED) {
2757 }
2758
2759 send_notify(sub_tree, 0);
2760
2762 sub_tree->state == SIP_SUB_TREE_TERMINATED
2763 ? "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_STATE_CHANGED",
2764 "Resource: %s", sub_tree->root->resource);
2765
2766 pjsip_dlg_dec_lock(dlg);
2767 ao2_cleanup(sub_tree);
2768 return 0;
2769}
2770
2771static int sched_cb(const void *data)
2772{
2773 struct sip_subscription_tree *sub_tree = (struct sip_subscription_tree *) data;
2774
2775 /* We don't need to bump the refcount of sub_tree since we bumped it when scheduling this task */
2776 if (ast_sip_push_task(sub_tree->serializer, serialized_send_notify, sub_tree)) {
2777 ao2_cleanup(sub_tree);
2778 }
2779
2780 return 0;
2781}
2782
2784{
2785 /* There's already a notification scheduled */
2786 if (sub_tree->notify_sched_id > -1) {
2787 return 0;
2788 }
2789
2790 sub_tree->send_scheduled_notify = 1;
2792 if (sub_tree->notify_sched_id < 0) {
2793 ao2_cleanup(sub_tree);
2794 return -1;
2795 }
2796
2797 return 0;
2798}
2799
2801 int terminate)
2802{
2803 int res;
2804 pjsip_dialog *dlg = sub->tree->dlg;
2805
2806 pjsip_dlg_inc_lock(dlg);
2807
2808 if (sub->tree->state != SIP_SUB_TREE_NORMAL) {
2809 pjsip_dlg_dec_lock(dlg);
2810 return 0;
2811 }
2812
2815 pjsip_dlg_dec_lock(dlg);
2816 return -1;
2817 }
2818
2819 sub->body_changed = 1;
2820 if (terminate) {
2821 sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
2822 sub->tree->state = SIP_SUB_TREE_TERMINATE_PENDING;
2823 }
2824
2825 if (sub->tree->notification_batch_interval) {
2826 res = schedule_notification(sub->tree);
2827 } else {
2828 /* See the note in pubsub_on_rx_refresh() for why sub->tree is refbumped here */
2829 ao2_ref(sub->tree, +1);
2830 if (terminate) {
2832 }
2833 res = send_notify(sub->tree, 0);
2834 ast_test_suite_event_notify(terminate ? "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_STATE_CHANGED",
2835 "Resource: %s",
2836 sub->tree->root->resource);
2837 ao2_ref(sub->tree, -1);
2838 }
2839
2840 pjsip_dlg_dec_lock(dlg);
2841 return res;
2842}
2843
2845{
2846 return sub->uri;
2847}
2848
2850{
2851 pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, sub->uri, buf, size);
2852}
2853
2855{
2856 pjsip_dialog *dlg;
2857 pjsip_sip_uri *uri;
2858
2859 dlg = sub->tree->dlg;
2860 uri = pjsip_uri_get_uri(dlg->remote.info->uri);
2861
2862 if (pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, uri, buf, size) < 0) {
2863 *buf = '\0';
2864 }
2865}
2866
2868{
2869 return sub->resource;
2870}
2871
2873{
2874 return sub->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ? 1 : 0;
2875}
2876
2877static int sip_subscription_accept(struct sip_subscription_tree *sub_tree, pjsip_rx_data *rdata, int response)
2878{
2879 pjsip_hdr res_hdr;
2880
2881 /* If this is a persistence recreation the subscription has already been accepted */
2882 if (ast_sip_mod_data_get(rdata->endpt_info.mod_data, pubsub_module.id, MOD_DATA_PERSISTENCE)) {
2883 return 0;
2884 }
2885
2886 pj_list_init(&res_hdr);
2887 if (sub_tree->is_list) {
2888 /* If subscribing to a list, our response has to have a Require: eventlist header in it */
2889 pj_list_insert_before(&res_hdr, create_require_eventlist(rdata->tp_info.pool));
2890 }
2891
2892 return pjsip_evsub_accept(sub_tree->evsub, rdata, response, &res_hdr) == PJ_SUCCESS ? 0 : -1;
2893}
2894
2896{
2898}
2899
2901{
2902 return ast_datastores_add(subscription->datastores, datastore);
2903}
2904
2906{
2907 return ast_datastores_find(subscription->datastores, name);
2908}
2909
2911{
2912 ast_datastores_remove(subscription->datastores, name);
2913}
2914
2916{
2917 return subscription->datastores;
2918}
2919
2921{
2922 return ast_datastores_add(publication->datastores, datastore);
2923}
2924
2926{
2927 return ast_datastores_find(publication->datastores, name);
2928}
2929
2931{
2932 ast_datastores_remove(publication->datastores, name);
2933}
2934
2936{
2937 return publication->datastores;
2938}
2939
2940void ast_sip_subscription_set_persistence_data(struct ast_sip_subscription *subscription, struct ast_json *persistence_data)
2941{
2942 ast_json_unref(subscription->persistence_data);
2943 subscription->persistence_data = persistence_data;
2944
2945 if (subscription->tree->persistence) {
2946 if (!subscription->tree->persistence->generator_data) {
2948 if (!subscription->tree->persistence->generator_data) {
2949 return;
2950 }
2951 }
2952 ast_json_object_set(subscription->tree->persistence->generator_data, subscription->resource,
2953 ast_json_ref(persistence_data));
2954 }
2955}
2956
2958{
2959 return subscription->persistence_data;
2960}
2961
2963
2964static int publication_hash_fn(const void *obj, const int flags)
2965{
2966 const struct ast_sip_publication *publication = obj;
2967 const int *entity_tag = obj;
2968
2969 return flags & OBJ_KEY ? *entity_tag : publication->entity_tag;
2970}
2971
2972static int publication_cmp_fn(void *obj, void *arg, int flags)
2973{
2974 const struct ast_sip_publication *publication1 = obj;
2975 const struct ast_sip_publication *publication2 = arg;
2976 const int *entity_tag = arg;
2977
2978 return (publication1->entity_tag == (flags & OBJ_KEY ? *entity_tag : publication2->entity_tag) ?
2979 CMP_MATCH | CMP_STOP : 0);
2980}
2981
2983{
2987}
2988
2990{
2991 if (ast_strlen_zero(handler->event_name)) {
2992 ast_log(LOG_ERROR, "No event package specified for publish handler. Cannot register\n");
2993 return -1;
2994 }
2995
2998 if (!handler->publications) {
2999 ast_log(LOG_ERROR, "Could not allocate publications container for event '%s'\n",
3000 handler->event_name);
3001 return -1;
3002 }
3003
3005
3006 return 0;
3007}
3008
3010{
3011 struct ast_sip_publish_handler *iter;
3012
3015 if (handler == iter) {
3017 ao2_cleanup(handler->publications);
3018 break;
3019 }
3020 }
3023}
3024
3026
3028{
3032}
3033
3035{
3036 struct ast_sip_subscription_handler *iter;
3037
3040 if (!strcmp(iter->event_name, event_name)) {
3041 break;
3042 }
3043 }
3045 return iter;
3046}
3047
3049{
3050 pj_str_t event;
3051 pj_str_t accept[AST_SIP_MAX_ACCEPT] = { {0, }, };
3052 struct ast_sip_subscription_handler *existing;
3053 int i = 0;
3054
3055 if (ast_strlen_zero(handler->event_name)) {
3056 ast_log(LOG_ERROR, "No event package specified for subscription handler. Cannot register\n");
3057 return -1;
3058 }
3059
3060 existing = find_sub_handler_for_event_name(handler->event_name);
3061 if (existing) {
3063 "Unable to register subscription handler for event %s. A handler is already registered\n",
3064 handler->event_name);
3065 return -1;
3066 }
3067
3068 for (i = 0; i < AST_SIP_MAX_ACCEPT && !ast_strlen_zero(handler->accept[i]); ++i) {
3069 pj_cstr(&accept[i], handler->accept[i]);
3070 }
3071
3072 pj_cstr(&event, handler->event_name);
3073
3074 pjsip_evsub_register_pkg(&pubsub_module, &event, DEFAULT_EXPIRES, i, accept);
3075
3077
3078 return 0;
3079}
3080
3082{
3083 struct ast_sip_subscription_handler *iter;
3084
3087 if (handler == iter) {
3089 break;
3090 }
3091 }
3094}
3095
3097{
3099
3101 if (!strcmp(gen->type, type)
3102 && !strcmp(gen->subtype, subtype)) {
3103 break;
3104 }
3105 }
3106
3107 return gen;
3108}
3109
3111{
3113
3117 return gen;
3118}
3119
3121{
3122 char *accept_copy = ast_strdupa(accept);
3123 char *subtype = accept_copy;
3124 char *type = strsep(&subtype, "/");
3125
3127 return NULL;
3128 }
3129
3131}
3132
3134 size_t num_accept, const char *body_type)
3135{
3136 int i;
3137 struct ast_sip_pubsub_body_generator *generator = NULL;
3138
3139 for (i = 0; i < num_accept; ++i) {
3140 generator = find_body_generator_accept(accept[i]);
3141 if (generator) {
3142 ast_debug(3, "Body generator %p found for accept type %s\n", generator, accept[i]);
3143 if (strcmp(generator->body_type, body_type)) {
3144 ast_log(LOG_WARNING, "Body generator '%s/%s'(%p) does not accept the type of data this event generates\n",
3145 generator->type, generator->subtype, generator);
3146 generator = NULL;
3147 continue;
3148 }
3149 break;
3150 } else {
3151 ast_debug(3, "No body generator found for accept type %s\n", accept[i]);
3152 }
3153 }
3154
3155 return generator;
3156}
3157
3159{
3160 void *notify_data;
3161 int res;
3162 struct ast_sip_body_data data = {
3163 .body_type = sub->handler->body_type,
3164 };
3165
3166 if (AST_VECTOR_SIZE(&sub->children) > 0) {
3167 int i;
3168
3169 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
3170 if (generate_initial_notify(AST_VECTOR_GET(&sub->children, i))) {
3171 return -1;
3172 }
3173 }
3174
3175 return 0;
3176 }
3177
3178 /* We notify subscription establishment only on the tree leaves. */
3179 if (sub->handler->notifier->subscription_established(sub)) {
3180 return -1;
3181 }
3182
3183 notify_data = sub->handler->notifier->get_notify_data(sub);
3184 if (!notify_data) {
3185 ast_debug(3, "No notify data, not generating any body content\n");
3186 return -1;
3187 }
3188
3189 data.body_data = notify_data;
3190
3192 ast_sip_subscription_get_body_subtype(sub), &data, &sub->body_text);
3193
3195
3196 return res;
3197}
3198
3199static int pubsub_on_refresh_timeout(void *userdata);
3200
3201static int initial_notify_task(void * obj)
3202{
3203 struct initial_notify_data *ind = obj;
3204
3206 pjsip_evsub_terminate(ind->sub_tree->evsub, PJ_TRUE);
3207 } else {
3208 send_notify(ind->sub_tree, 1);
3209 ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
3210 "Resource: %s",
3211 ind->sub_tree->root->resource);
3212 }
3213
3215 char *name = ast_alloca(strlen("->/ ") +
3216 strlen(ind->sub_tree->persistence->endpoint) +
3217 strlen(ind->sub_tree->root->resource) +
3218 strlen(ind->sub_tree->root->handler->event_name) +
3219 ind->sub_tree->dlg->call_id->id.slen + 1);
3220
3221 sprintf(name, "%s->%s/%s %.*s", ind->sub_tree->persistence->endpoint,
3223 (int)ind->sub_tree->dlg->call_id->id.slen, ind->sub_tree->dlg->call_id->id.ptr);
3224
3225 ast_debug(3, "Scheduling timer: %s\n", name);
3229 if (!ind->sub_tree->expiration_task) {
3230 ast_log(LOG_ERROR, "Unable to create expiration timer of %d seconds for %s\n",
3231 ind->expires, name);
3232 }
3233 }
3234
3235 ao2_ref(ind->sub_tree, -1);
3236 ast_free(ind);
3237
3238 return 0;
3239}
3240
3241static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
3242{
3243 pjsip_expires_hdr *expires_header;
3245 RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
3246 struct sip_subscription_tree *sub_tree;
3247 struct ast_sip_pubsub_body_generator *generator;
3248 char *resource;
3249 pjsip_uri *request_uri;
3250 size_t resource_size;
3251 int resp;
3252 struct resource_tree tree;
3253 pj_status_t dlg_status;
3254 const pj_str_t *user;
3255
3256 endpoint = ast_pjsip_rdata_get_endpoint(rdata);
3257 ast_assert(endpoint != NULL);
3258
3259 if (!endpoint->subscription.allow) {
3260 ast_log(LOG_WARNING, "Subscriptions not permitted for endpoint %s.\n", ast_sorcery_object_get_id(endpoint));
3261 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 603, NULL, NULL, NULL);
3262 return PJ_TRUE;
3263 }
3264
3265 request_uri = rdata->msg_info.msg->line.req.uri;
3266
3267 if (!ast_sip_is_uri_sip_sips(request_uri)) {
3268 char uri_str[PJSIP_MAX_URL_SIZE];
3269
3270 pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, uri_str, sizeof(uri_str));
3271 ast_log(LOG_WARNING, "Request URI '%s' is not a sip: or sips: URI.\n", uri_str);
3272 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
3273 return PJ_TRUE;
3274 }
3275
3276 user = ast_sip_pjsip_uri_get_username(request_uri);
3277 resource_size = pj_strlen(user) + 1;
3278 resource = ast_alloca(resource_size);
3279 ast_copy_pj_str(resource, user, resource_size);
3280
3281 /*
3282 * We may want to match without any user options getting
3283 * in the way.
3284 */
3286
3287 expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, rdata->msg_info.msg->hdr.next);
3288 if (expires_header) {
3289 if (expires_header->ivalue == 0) {
3290 ast_debug(1, "Subscription request from endpoint %s rejected. Expiration of 0 is invalid\n",
3291 ast_sorcery_object_get_id(endpoint));
3292 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
3293 return PJ_TRUE;
3294 }
3295 if (expires_header->ivalue < endpoint->subscription.minexpiry) {
3296 ast_log(LOG_WARNING, "Subscription expiration %d is too brief for endpoint %s. Minimum is %u\n",
3297 expires_header->ivalue, ast_sorcery_object_get_id(endpoint), endpoint->subscription.minexpiry);
3298 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 423, NULL, NULL, NULL);
3299 return PJ_TRUE;
3300 }
3301 }
3302
3304 if (!handler) {
3305 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3306 return PJ_TRUE;
3307 }
3308
3310 if (!generator) {
3311 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3312 return PJ_TRUE;
3313 }
3314
3315 memset(&tree, 0, sizeof(tree));
3316 resp = build_resource_tree(endpoint, handler, resource, &tree,
3318 if (!PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
3319 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, resp, NULL, NULL, NULL);
3320 resource_tree_destroy(&tree);
3321 return PJ_TRUE;
3322 }
3323
3324 sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, &tree, &dlg_status, NULL);
3325 if (!sub_tree) {
3326 if (dlg_status != PJ_EEXISTS) {
3327 ast_debug(3, "No dialog exists, rejecting\n");
3328 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
3329 }
3330 } else {
3331 struct initial_notify_data *ind = ast_malloc(sizeof(*ind));
3332
3333 if (!ind) {
3334 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
3335 resource_tree_destroy(&tree);
3336 return PJ_TRUE;
3337 }
3338
3339 ind->sub_tree = ao2_bump(sub_tree);
3340 /* Since this is a normal subscribe, pjproject takes care of the timer */
3342
3345 sip_subscription_accept(sub_tree, rdata, resp);
3347 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
3348 ao2_ref(sub_tree, -1);
3349 ast_free(ind);
3350 }
3351 }
3352
3353 resource_tree_destroy(&tree);
3354 return PJ_TRUE;
3355}
3356
3358{
3359 struct ast_sip_publish_handler *iter = NULL;
3360
3363 if (strcmp(event, iter->event_name)) {
3364 ast_debug(3, "Event %s does not match %s\n", event, iter->event_name);
3365 continue;
3366 }
3367 ast_debug(3, "Event name match: %s = %s\n", event, iter->event_name);
3368 break;
3369 }
3371
3372 return iter;
3373}
3374
3375static enum sip_publish_type determine_sip_publish_type(pjsip_rx_data *rdata,
3376 pjsip_generic_string_hdr *etag_hdr, unsigned int *expires, int *entity_id)
3377{
3378 pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
3379
3380 if (etag_hdr) {
3381 char etag[pj_strlen(&etag_hdr->hvalue) + 1];
3382
3383 ast_copy_pj_str(etag, &etag_hdr->hvalue, sizeof(etag));
3384
3385 if (sscanf(etag, "%30d", entity_id) != 1) {
3386 return SIP_PUBLISH_UNKNOWN;
3387 }
3388 }
3389
3390 *expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
3391
3392 if (!(*expires)) {
3393 return SIP_PUBLISH_REMOVE;
3394 } else if (!etag_hdr && rdata->msg_info.msg->body) {
3395 return SIP_PUBLISH_INITIAL;
3396 } else if (etag_hdr && !rdata->msg_info.msg->body) {
3397 return SIP_PUBLISH_REFRESH;
3398 } else if (etag_hdr && rdata->msg_info.msg->body) {
3399 return SIP_PUBLISH_MODIFY;
3400 }
3401
3402 return SIP_PUBLISH_UNKNOWN;
3403}
3404
3405/*! \brief Internal destructor for publications */
3406static void publication_destroy_fn(void *obj)
3407{
3408 struct ast_sip_publication *publication = obj;
3409
3410 ast_debug(3, "Destroying SIP publication\n");
3411
3412 ao2_cleanup(publication->datastores);
3413 ao2_cleanup(publication->endpoint);
3414
3416}
3417
3418static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
3419 const char *resource, const char *event_configuration_name)
3420{
3421 struct ast_sip_publication *publication;
3422 pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
3423 size_t resource_len = strlen(resource) + 1, event_configuration_name_len = strlen(event_configuration_name) + 1;
3424 char *dst;
3425
3427
3428 if (!(publication = ao2_alloc(sizeof(*publication) + resource_len + event_configuration_name_len, publication_destroy_fn))) {
3429 return NULL;
3430 }
3431
3433
3434 if (!(publication->datastores = ast_datastores_alloc())) {
3435 ao2_ref(publication, -1);
3436 return NULL;
3437 }
3438
3440 ao2_ref(endpoint, +1);
3441 publication->endpoint = endpoint;
3442 publication->expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
3443 publication->sched_id = -1;
3444 dst = publication->data;
3445 publication->resource = strcpy(dst, resource);
3446 dst += resource_len;
3447 publication->event_configuration_name = strcpy(dst, event_configuration_name);
3448
3449 return publication;
3450}
3451
3452static int sip_publication_respond(struct ast_sip_publication *pub, int status_code,
3453 pjsip_rx_data *rdata)
3454{
3455 pjsip_tx_data *tdata;
3456 pjsip_transaction *tsx;
3457
3458 if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, status_code, NULL, &tdata) != PJ_SUCCESS) {
3459 return -1;
3460 }
3461
3462 if (PJSIP_IS_STATUS_IN_CLASS(status_code, 200)) {
3463 char buf[30];
3464
3465 snprintf(buf, sizeof(buf), "%d", pub->entity_tag);
3466 ast_sip_add_header(tdata, "SIP-ETag", buf);
3467
3468 snprintf(buf, sizeof(buf), "%d", pub->expires);
3469 ast_sip_add_header(tdata, "Expires", buf);
3470 }
3471
3472 if (pjsip_tsx_create_uas(&pubsub_module, rdata, &tsx) != PJ_SUCCESS) {
3473 pjsip_tx_data_dec_ref(tdata);
3474 return -1;
3475 }
3476
3477 pjsip_tsx_recv_msg(tsx, rdata);
3478
3479 if (pjsip_tsx_send_msg(tsx, tdata) != PJ_SUCCESS) {
3480 pjsip_tx_data_dec_ref(tdata);
3481 return -1;
3482 }
3483
3484 return 0;
3485}
3486
3487static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
3489{
3490 struct ast_sip_publication *publication;
3491 char *resource_name;
3492 size_t resource_size;
3494 struct ast_variable *event_configuration_name = NULL;
3495 pjsip_uri *request_uri;
3496 int resp;
3497 const pj_str_t *user;
3498
3499 request_uri = rdata->msg_info.msg->line.req.uri;
3500
3501 if (!ast_sip_is_uri_sip_sips(request_uri)) {
3502 char uri_str[PJSIP_MAX_URL_SIZE];
3503
3504 pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, uri_str, sizeof(uri_str));
3505 ast_log(LOG_WARNING, "Request URI '%s' is not a sip: or sips: URI.\n", uri_str);
3506 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
3507 return NULL;
3508 }
3509
3510 user = ast_sip_pjsip_uri_get_username(request_uri);
3511 resource_size = pj_strlen(user) + 1;
3512 resource_name = ast_alloca(resource_size);
3513 ast_copy_pj_str(resource_name, user, resource_size);
3514
3515 /*
3516 * We may want to match without any user options getting
3517 * in the way.
3518 */
3520
3521 resource = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "inbound-publication", resource_name);
3522 if (!resource) {
3523 ast_debug(1, "No 'inbound-publication' defined for resource '%s'\n", resource_name);
3524 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
3525 return NULL;
3526 }
3527
3528 if (!ast_strlen_zero(resource->endpoint) && strcmp(resource->endpoint, ast_sorcery_object_get_id(endpoint))) {
3529 ast_debug(1, "Resource %s has a defined endpoint '%s', but does not match endpoint '%s' that received the request\n",
3530 resource_name, resource->endpoint, ast_sorcery_object_get_id(endpoint));
3531 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
3532 return NULL;
3533 }
3534
3535 for (event_configuration_name = resource->events; event_configuration_name; event_configuration_name = event_configuration_name->next) {
3536 if (!strcmp(event_configuration_name->name, handler->event_name)) {
3537 break;
3538 }
3539 }
3540
3541 if (!event_configuration_name) {
3542 ast_debug(1, "Event '%s' is not configured for '%s'\n", handler->event_name, resource_name);
3543 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
3544 return NULL;
3545 }
3546
3547 resp = handler->new_publication(endpoint, resource_name, event_configuration_name->value);
3548
3549 if (!PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
3550 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, resp, NULL, NULL, NULL);
3551 return NULL;
3552 }
3553
3554 publication = sip_create_publication(endpoint, rdata, S_OR(resource_name, ""), event_configuration_name->value);
3555
3556 if (!publication) {
3557 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 503, NULL, NULL, NULL);
3558 return NULL;
3559 }
3560
3561 publication->handler = handler;
3562 if (publication->handler->publication_state_change(publication, rdata->msg_info.msg->body,
3564 ast_debug(3, "Publication state change failed\n");
3565 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
3566 ao2_cleanup(publication);
3567 return NULL;
3568 }
3569
3570 sip_publication_respond(publication, resp, rdata);
3571
3572 return publication;
3573}
3574
3575static int publish_expire_callback(void *data)
3576{
3577 RAII_VAR(struct ast_sip_publication *, publication, data, ao2_cleanup);
3578
3579 if (publication->handler->publish_expire) {
3580 publication->handler->publish_expire(publication);
3581 }
3582
3583 return 0;
3584}
3585
3586static int publish_expire(const void *data)
3587{
3588 struct ast_sip_publication *publication = (struct ast_sip_publication*)data;
3589
3590 ao2_unlink(publication->handler->publications, publication);
3591 publication->sched_id = -1;
3592
3593 if (ast_sip_push_task(NULL, publish_expire_callback, publication)) {
3594 ao2_cleanup(publication);
3595 }
3596
3597 return 0;
3598}
3599
3600static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
3601{
3602 pjsip_event_hdr *event_header;
3604 RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
3605 char event[32];
3606 static const pj_str_t str_sip_if_match = { "SIP-If-Match", 12 };
3607 pjsip_generic_string_hdr *etag_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_sip_if_match, NULL);
3608 enum sip_publish_type publish_type;
3609 RAII_VAR(struct ast_sip_publication *, publication, NULL, ao2_cleanup);
3610 unsigned int expires = 0;
3611 int entity_id, response = 0;
3612
3613 endpoint = ast_pjsip_rdata_get_endpoint(rdata);
3614 ast_assert(endpoint != NULL);
3615
3616 event_header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_event_name, rdata->msg_info.msg->hdr.next);
3617 if (!event_header) {
3618 ast_log(LOG_WARNING, "Incoming PUBLISH request from %s with no Event header\n",
3619 ast_sorcery_object_get_id(endpoint));
3620 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3621 return PJ_TRUE;
3622 }
3623 ast_copy_pj_str(event, &event_header->event_type, sizeof(event));
3624
3626 if (!handler) {
3627 ast_log(LOG_WARNING, "No registered publish handler for event %s from %s\n", event,
3628 ast_sorcery_object_get_id(endpoint));
3629 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3630 return PJ_TRUE;
3631 }
3632
3633 publish_type = determine_sip_publish_type(rdata, etag_hdr, &expires, &entity_id);
3634
3635 /* If this is not an initial publish ensure that a publication is present */
3636 if ((publish_type != SIP_PUBLISH_INITIAL) && (publish_type != SIP_PUBLISH_UNKNOWN)) {
3637 if (!(publication = ao2_find(handler->publications, &entity_id, OBJ_KEY | OBJ_UNLINK))) {
3638 static const pj_str_t str_conditional_request_failed = { "Conditional Request Failed", 26 };
3639
3640 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 412, &str_conditional_request_failed,
3641 NULL, NULL);
3642 return PJ_TRUE;
3643 }
3644
3645 /* Per the RFC every response has to have a new entity tag */
3646 publication->entity_tag = ast_atomic_fetchadd_int(&esc_etag_counter, +1);
3647
3648 /* Update the expires here so that the created responses will contain the correct value */
3649 publication->expires = expires;
3650 }
3651
3652 switch (publish_type) {
3654 publication = publish_request_initial(endpoint, rdata, handler);
3655 break;
3657 case SIP_PUBLISH_MODIFY:
3658 if (handler->publication_state_change(publication, rdata->msg_info.msg->body,
3660 /* If an error occurs we want to terminate the publication */
3661 expires = 0;
3662 }
3663 response = 200;
3664 break;
3665 case SIP_PUBLISH_REMOVE:
3666 handler->publication_state_change(publication, rdata->msg_info.msg->body,
3668 response = 200;
3669 break;
3671 default:
3672 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
3673 break;
3674 }
3675
3676 if (publication) {
3677 if (expires) {
3678 ao2_link(handler->publications, publication);
3679
3680 AST_SCHED_REPLACE_UNREF(publication->sched_id, sched, expires * 1000, publish_expire, publication,
3681 ao2_ref(_data, -1), ao2_ref(publication, -1), ao2_ref(publication, +1));
3682 } else {
3683 AST_SCHED_DEL_UNREF(sched, publication->sched_id, ao2_ref(publication, -1));
3684 }
3685 }
3686
3687 if (response) {
3688 sip_publication_respond(publication, response, rdata);
3689 }
3690
3691 return PJ_TRUE;
3692}
3693
3695{
3696 return pub->endpoint;
3697}
3698
3700{
3701 return pub->resource;
3702}
3703
3705{
3706 return pub->event_configuration_name;
3707}
3708
3709int ast_sip_pubsub_is_body_generator_registered(const char *type, const char *subtype)
3710{
3711 return !!find_body_generator_type_subtype(type, subtype);
3712}
3713
3715{
3716 struct ast_sip_pubsub_body_generator *existing;
3717 pj_str_t accept;
3718 pj_size_t accept_len;
3719
3721 existing = find_body_generator_type_subtype_nolock(generator->type, generator->subtype);
3722 if (existing) {
3724 ast_log(LOG_WARNING, "A body generator for %s/%s is already registered.\n",
3725 generator->type, generator->subtype);
3726 return -1;
3727 }
3730
3731 /* Lengths of type and subtype plus a slash. */
3732 accept_len = strlen(generator->type) + strlen(generator->subtype) + 1;
3733
3734 /* Add room for null terminator that sprintf() will set. */
3735 pj_strset(&accept, ast_alloca(accept_len + 1), accept_len);
3736 sprintf((char *) pj_strbuf(&accept), "%s/%s", generator->type, generator->subtype);/* Safe */
3737
3738 pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module,
3739 PJSIP_H_ACCEPT, NULL, 1, &accept);
3740
3741 return 0;
3742}
3743
3745{
3746 struct ast_sip_pubsub_body_generator *iter;
3747
3750 if (iter == generator) {
3752 break;
3753 }
3754 }
3757}
3758
3760{
3764
3765 return 0;
3766}
3767
3769{
3770 struct ast_sip_pubsub_body_supplement *iter;
3771
3774 if (iter == supplement) {
3776 break;
3777 }
3778 }
3781}
3782
3784{
3785 return sub->body_generator->type;
3786}
3787
3789{
3790 return sub->body_generator->subtype;
3791}
3792
3794 struct ast_sip_body_data *data, struct ast_str **str)
3795{
3796 struct ast_sip_pubsub_body_supplement *supplement;
3797 struct ast_sip_pubsub_body_generator *generator;
3798 int res = 0;
3799 void *body;
3800
3802 if (!generator) {
3803 ast_log(LOG_WARNING, "Unable to find a body generator for %s/%s\n",
3804 type, subtype);
3805 return -1;
3806 }
3807
3808 if (strcmp(data->body_type, generator->body_type)) {
3809 ast_log(LOG_WARNING, "%s/%s body generator does not accept the type of data provided\n",
3810 type, subtype);
3811 return -1;
3812 }
3813
3814 body = generator->allocate_body(data->body_data);
3815 if (!body) {
3816 ast_log(LOG_WARNING, "%s/%s body generator could not to allocate a body\n",
3817 type, subtype);
3818 return -1;
3819 }
3820
3821 if (generator->generate_body_content(body, data->body_data)) {
3822 res = -1;
3823 goto end;
3824 }
3825
3828 if (!strcmp(generator->type, supplement->type) &&
3829 !strcmp(generator->subtype, supplement->subtype)) {
3830 res = supplement->supplement_body(body, data->body_data);
3831 if (res) {
3832 break;
3833 }
3834 }
3835 }
3837
3838 if (!res) {
3839 generator->to_string(body, str);
3840 }
3841
3842end:
3843 if (generator->destroy_body) {
3844 generator->destroy_body(body);
3845 }
3846
3847 return res;
3848}
3849
3856 char message_account[PJSIP_MAX_URL_SIZE];
3857};
3858
3859static int parse_simple_message_summary(char *body,
3860 struct simple_message_summary *summary)
3861{
3862 char *line;
3863 char *buffer;
3864 int found_counts = 0;
3865
3866 if (ast_strlen_zero(body) || !summary) {
3867 return -1;
3868 }
3869
3870 buffer = ast_strdupa(body);
3871 memset(summary, 0, sizeof(*summary));
3872
3873 while ((line = ast_read_line_from_buffer(&buffer))) {
3874 line = ast_str_to_lower(line);
3875
3876 if (sscanf(line, "voice-message: %d/%d (%d/%d)",
3877 &summary->voice_messages_new, &summary->voice_messages_old,
3879 found_counts = 1;
3880 } else {
3881 sscanf(line, "message-account: %s", summary->message_account);
3882 }
3883 }
3884
3885 return !found_counts;
3886}
3887
3888static pj_bool_t pubsub_on_rx_mwi_notify_request(pjsip_rx_data *rdata)
3889{
3890 RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
3891 struct simple_message_summary summary;
3892 const char *endpoint_name;
3893 char *atsign;
3894 char *context;
3895 char *body;
3896 char *mailbox;
3897 int rc;
3898
3899 endpoint = ast_pjsip_rdata_get_endpoint(rdata);
3900 if (!endpoint) {
3901 ast_debug(1, "Incoming MWI: Endpoint not found in rdata (%p)\n", rdata);
3902 rc = 404;
3903 goto error;
3904 }
3905
3906 endpoint_name = ast_sorcery_object_get_id(endpoint);
3907 ast_debug(1, "Incoming MWI: Found endpoint: %s\n", endpoint_name);
3908 if (ast_strlen_zero(endpoint->incoming_mwi_mailbox)) {
3909 ast_debug(1, "Incoming MWI: No incoming mailbox specified for endpoint '%s'\n", endpoint_name);
3910 ast_test_suite_event_notify("PUBSUB_NO_INCOMING_MWI_MAILBOX",
3911 "Endpoint: %s", endpoint_name);
3912 rc = 404;
3913 goto error;
3914 }
3915
3916 mailbox = ast_strdupa(endpoint->incoming_mwi_mailbox);
3917 atsign = strchr(mailbox, '@');
3918 if (!atsign) {
3919 ast_debug(1, "Incoming MWI: No '@' found in endpoint %s's incoming mailbox '%s'. Can't parse context\n",
3920 endpoint_name, endpoint->incoming_mwi_mailbox);
3921 rc = 404;
3922 goto error;
3923 }
3924
3925 *atsign = '\0';
3926 context = atsign + 1;
3927
3928 body = ast_alloca(rdata->msg_info.msg->body->len + 1);
3929 rdata->msg_info.msg->body->print_body(rdata->msg_info.msg->body, body,
3930 rdata->msg_info.msg->body->len + 1);
3931
3932 if (parse_simple_message_summary(body, &summary) != 0) {
3933 ast_debug(1, "Incoming MWI: Endpoint: '%s' There was an issue getting message info from body '%s'\n",
3934 ast_sorcery_object_get_id(endpoint), body);
3935 rc = 404;
3936 goto error;
3937 }
3938
3940 summary.voice_messages_new, summary.voice_messages_old)) {
3941 ast_log(LOG_ERROR, "Incoming MWI: Endpoint: '%s' Could not publish MWI to stasis. "
3942 "Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n",
3943 endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,
3944 summary.voice_messages_new, summary.voice_messages_old,
3946 rc = 404;
3947 } else {
3948 ast_debug(1, "Incoming MWI: Endpoint: '%s' Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n",
3949 endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,
3950 summary.voice_messages_new, summary.voice_messages_old,
3952 ast_test_suite_event_notify("PUBSUB_INCOMING_MWI_PUBLISH",
3953 "Endpoint: %s\r\n"
3954 "Mailbox: %s\r\n"
3955 "MessageAccount: %s\r\n"
3956 "VoiceMessagesNew: %d\r\n"
3957 "VoiceMessagesOld: %d\r\n"
3958 "VoiceMessagesUrgentNew: %d\r\n"
3959 "VoiceMessagesUrgentOld: %d",
3960 endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,
3961 summary.voice_messages_new, summary.voice_messages_old,
3963 rc = 200;
3964 }
3965
3966error:
3967 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, rc, NULL, NULL, NULL);
3968 return PJ_TRUE;
3969}
3970
3971static pj_bool_t pubsub_on_rx_notify_request(pjsip_rx_data *rdata)
3972{
3973 if (rdata->msg_info.msg->body &&
3974 ast_sip_is_content_type(&rdata->msg_info.msg->body->content_type,
3975 "application", "simple-message-summary")) {
3976 return pubsub_on_rx_mwi_notify_request(rdata);
3977 }
3978 return PJ_FALSE;
3979}
3980
3981static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)
3982{
3983 if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method())) {
3984 return pubsub_on_rx_subscribe_request(rdata);
3985 } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_publish_method)) {
3986 return pubsub_on_rx_publish_request(rdata);
3987 } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_notify_method)) {
3988 return pubsub_on_rx_notify_request(rdata);
3989 }
3990
3991 return PJ_FALSE;
3992}
3993
3994/*!
3995 * \brief Callback sequence for subscription terminate:
3996 *
3997 * * Please note that the descriptions below represent pjproject behavior on versions
3998 * >= 2.13.
3999 * * Client initiated:
4000 * pjproject receives SUBSCRIBE on the subscription's serializer thread
4001 * calls pubsub_evsub_set_state with state = TERMINATED
4002 * pubsub_on_evsub_state checks the event and finds it is due to a received
4003 * SUBSCRIBE with an expires of 0 and so does nothing.
4004 * calls pubsub_on_rx_refresh with dialog locked
4005 * pubsub_on_rx_refresh sets TERMINATE_PENDING
4006 * calls pubsub_on_refresh_timeout to push final NOTIFY to pjproject
4007 * checks state == TERMINATE_PENDING
4008 * sets TERMINATE_IN_PROGRESS
4009 * calls send_notify (2)
4010 * send_notify ultimately calls pjsip_evsub_send_request
4011 * pjsip_evsub_send_request calls evsub's set_state
4012 * set_state calls pubsub_evsub_set_state
4013 * pubsub_on_evsub_state checks state == TERMINATE_IN_PROGRESS
4014 * removes the subscriptions
4015 * cleans up references to evsub
4016 * sets state = TERMINATED
4017 * pubsub_on_refresh_timeout unlocks dialog
4018 * returns to pjproject
4019 * pjproject unlocks dialog
4020 *
4021 * * Subscription timer expires:
4022 * pjproject timer expires
4023 * locks dialog
4024 * calls pubsub_on_server_timeout
4025 * pubsub_on_server_timeout checks state == NORMAL
4026 * sets TERMINATE_PENDING
4027 * pushes serialized_pubsub_on_refresh_timeout
4028 * returns to pjproject
4029 * pjproject unlocks dialog
4030 * serialized_pubsub_on_refresh_timeout starts (1)
4031 * locks dialog
4032 * checks state == TERMINATE_PENDING
4033 * sets TERMINATE_IN_PROGRESS
4034 * calls send_notify (2)
4035 * send_notify ultimately calls pjsip_evsub_send_request
4036 * pjsip_evsub_send_request calls evsub's set_state
4037 * set_state calls pubsub_evsub_set_state
4038 * pubsub_on_evsub_state checks state == TERMINATE_IN_PROGRESS
4039 * checks that the event is not due to un-SUBSCRIBE
4040 * removes the subscriptions
4041 * cleans up references to evsub
4042 * sets state = TERMINATED
4043 * serialized_pubsub_on_refresh_timeout unlocks dialog
4044 *
4045 * * Transmission failure sending NOTIFY or error response from client
4046 * pjproject transaction timer expires or non OK response
4047 * pjproject locks dialog
4048 * calls pubsub_on_evsub_state with event TSX_STATE
4049 * pubsub_on_evsub_state checks event == TSX_STATE
4050 * removes the subscriptions
4051 * cleans up references to evsub
4052 * sets state = TERMINATED
4053 * pjproject unlocks dialog
4054 *
4055 * * ast_sip_subscription_notify is called
4056 * checks state == NORMAL
4057 * if not batched...
4058 * sets TERMINATE_IN_PROGRESS (if terminate is requested)
4059 * calls send_notify
4060 * See (2) Above
4061 * if batched...
4062 * sets TERMINATE_PENDING
4063 * schedules task
4064 * scheduler runs sched_task
4065 * sched_task pushes serialized_send_notify
4066 * serialized_send_notify starts
4067 * checks state <= TERMINATE_PENDING
4068 * if state == TERMINATE_PENDING set state = TERMINATE_IN_PROGRESS
4069 * call send_notify
4070 * See (2) Above
4071 *
4072 */
4073
4074
4075/* The code in this function was previously in pubsub_on_evsub_state. */
4076static void clean_sub_tree(pjsip_evsub *evsub)
4077{
4078
4079 struct sip_subscription_tree *sub_tree;
4080 sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4081
4082 ast_debug(3, "Cleaning subscription %p\n", evsub);
4083
4084 if (sub_tree->expiration_task) {
4085 char task_name[256];
4086
4087 ast_sip_sched_task_get_name(sub_tree->expiration_task, task_name, sizeof(task_name));
4088 ast_debug(3, "Cancelling timer: %s\n", task_name);
4090 ao2_cleanup(sub_tree->expiration_task);
4091 sub_tree->expiration_task = NULL;
4092 }
4093
4094 remove_subscription(sub_tree);
4095
4096 pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL);
4097
4098#ifdef HAVE_PJSIP_EVSUB_GRP_LOCK
4099 pjsip_evsub_dec_ref(sub_tree->evsub);
4100#endif
4101
4102 sub_tree->evsub = NULL;
4103
4106
4108 shutdown_subscriptions(sub_tree->root);
4109
4110 sub_tree->state = SIP_SUB_TREE_TERMINATED;
4111 /* Remove evsub's reference to the sub_tree */
4112 ao2_ref(sub_tree, -1);
4113}
4114
4115/* This functionality appeared in pjsip 2.13 */
4116#if PJ_VERSION_NUM >= 0x020D0000
4117# define HAVE_PJSIP_EVSUB_PENDING_NOTIFY 1
4118#endif
4119
4120/*!
4121 * \brief PJSIP callback when underlying SIP subscription changes state
4122 *
4123 * Although this function is called for every state change, we only care
4124 * about the TERMINATED state, and only when we're actually processing the final
4125 * notify (SIP_SUB_TREE_TERMINATE_IN_PROGRESS) OR when a transmission failure
4126 * occurs (PJSIP_EVENT_TSX_STATE). In this case, we do all the subscription tree
4127 * cleanup tasks and decrement the evsub reference.
4128 */
4129static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
4130{
4131 struct sip_subscription_tree *sub_tree =
4132 pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4133
4134 ast_debug(3, "evsub %p state %s event %s sub_tree %p sub_tree state %s\n", evsub,
4135 pjsip_evsub_get_state_name(evsub), pjsip_event_str(event->type), sub_tree,
4136 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4137
4138 if (!sub_tree || pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
4139 return;
4140 }
4141
4142 /* It's easier to write this as what we WANT to process, then negate it. */
4143 if (!(sub_tree->state == SIP_SUB_TREE_TERMINATE_IN_PROGRESS
4144 || (event->type == PJSIP_EVENT_TSX_STATE && sub_tree->state == SIP_SUB_TREE_NORMAL)
4145 )) {
4146 ast_debug(3, "Do nothing.\n");
4147 return;
4148 }
4149
4150#ifdef HAVE_PJSIP_EVSUB_PENDING_NOTIFY
4151 /* This check looks for re-subscribes with an expires of 0. If we receive one of those,
4152 we don't want to clean the evsub because we still need it to send the final NOTIFY.
4153 This was previously handled by pubsub_on_rx_refresh setting:
4154 'sub_tree->state = SIP_SUB_TREE_TERMINATE_PENDING' */
4155 if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
4156 !pjsip_method_cmp(&event->body.tsx_state.tsx->method, &pjsip_subscribe_method) &&
4157 pjsip_evsub_get_expires(evsub) == 0) {
4158 ast_debug(3, "Subscription ending, do nothing.\n");
4159 return;
4160 }
4161#endif
4162 /* If we made it this far, we want to clean the sub tree. For pjproject <2.13, the sub_tree
4163 state check makes sure the evsub is not cleaned at the wrong time */
4165}
4166
4167static int pubsub_on_refresh_timeout(void *userdata)
4168{
4169 struct sip_subscription_tree *sub_tree = userdata;
4170 pjsip_dialog *dlg = sub_tree->dlg;
4171
4172 ast_debug(3, "sub_tree %p sub_tree state %s\n", sub_tree,
4173 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4174
4175 pjsip_dlg_inc_lock(dlg);
4176 if (sub_tree->state >= SIP_SUB_TREE_TERMINATE_IN_PROGRESS) {
4177 pjsip_dlg_dec_lock(dlg);
4178 return 0;
4179 }
4180
4181 if (sub_tree->state == SIP_SUB_TREE_TERMINATE_PENDING) {
4183 set_state_terminated(sub_tree->root);
4184 }
4185
4186 if (sub_tree->generate_initial_notify) {
4187 sub_tree->generate_initial_notify = 0;
4188 if (generate_initial_notify(sub_tree->root)) {
4189 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
4190 pjsip_dlg_dec_lock(dlg);
4191 return 0;
4192 }
4193 }
4194
4195 send_notify(sub_tree, 1);
4196
4197 ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ?
4198 "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED",
4199 "Resource: %s", sub_tree->root->resource);
4200
4201 pjsip_dlg_dec_lock(dlg);
4202
4203 return 0;
4204}
4205
4207{
4208 struct sip_subscription_tree *sub_tree = userdata;
4209
4210 ast_debug(3, "sub_tree %p sub_tree state %s\n", sub_tree,
4211 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4212
4213 pubsub_on_refresh_timeout(userdata);
4214 ao2_cleanup(sub_tree);
4215
4216 return 0;
4217}
4218
4219/*!
4220 * \brief Compare strings for equality checking for NULL.
4221 *
4222 * This function considers NULL values as empty strings.
4223 * This means NULL or empty strings are equal.
4224 *
4225 * \retval 0 The strings are equal
4226 * \retval 1 The strings are not equal
4227 */
4228static int cmp_strings(char *s1, char *s2)
4229{
4230 if (!ast_strlen_zero(s1) && !ast_strlen_zero(s2)) {
4231 return strcmp(s1, s2);
4232 }
4233
4234 return ast_strlen_zero(s1) == ast_strlen_zero(s2) ? 0 : 1;
4235}
4236
4237/*!
4238 * \brief compares the childrens of two ast_sip_subscription s1 and s2
4239 *
4240 * \retval 0 The s1 childrens match the s2 childrens
4241 * \retval 1 The s1 childrens do not match the s2 childrens
4242 */
4244{
4245 int i;
4246
4247 if (AST_VECTOR_SIZE(&s1->children) != AST_VECTOR_SIZE(&s2->children)) {
4248 return 1;
4249 }
4250
4251 for (i = 0; i < AST_VECTOR_SIZE(&s1->children); ++i) {
4252 struct ast_sip_subscription *c1 = AST_VECTOR_GET(&s1->children, i);
4253 struct ast_sip_subscription *c2 = AST_VECTOR_GET(&s2->children, i);
4254
4255 if (cmp_strings(c1->resource, c2->resource)
4256 || cmp_strings(c1->display_name, c2->display_name)) {
4257
4258 return 1;
4259 }
4260 }
4261
4262 return 0;
4263}
4264
4265static int destroy_subscriptions_task(void *obj)
4266{
4267 struct ast_sip_subscription *sub = (struct ast_sip_subscription *) obj;
4268
4270
4271 return 0;
4272}
4273
4274/*!
4275 * \brief Called whenever an in-dialog SUBSCRIBE is received
4276 *
4277 * This includes both SUBSCRIBE requests that actually refresh the subscription
4278 * as well as SUBSCRIBE requests that end the subscription.
4279 *
4280 * In either case we push an appropriate NOTIFY via pubsub_on_refresh_timeout.
4281 */
4282static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
4283 int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
4284{
4285 struct sip_subscription_tree *sub_tree;
4287
4288 sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4289 ast_debug(3, "evsub %p sub_tree %p sub_tree state %s\n", evsub, sub_tree,
4290 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4291
4292 if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) {
4293 return;
4294 }
4295
4296 if (sub_tree->expiration_task) {
4297 char task_name[256];
4298
4299 ast_sip_sched_task_get_name(sub_tree->expiration_task, task_name, sizeof(task_name));
4300 ast_debug(3, "Cancelling timer: %s\n", task_name);
4302 ao2_cleanup(sub_tree->expiration_task);
4303 sub_tree->expiration_task = NULL;
4304 }
4305
4306 /* PJSIP will set the evsub's state to terminated before calling into this function
4307 * if the Expires value of the incoming SUBSCRIBE is 0.
4308 */
4309
4310 if (pjsip_evsub_get_state(sub_tree->evsub) == PJSIP_EVSUB_STATE_TERMINATED) {
4312 }
4313
4315
4316 /* If the handler wants a callback on refresh, then do it (some protocols require this). */
4317 if (sub_tree->state == SIP_SUB_TREE_NORMAL && sub_tree->root->handler->notifier->refresh_subscribe) {
4318 if (!sub_tree->root->handler->notifier->refresh_subscribe(sub_tree->root, rdata)) {
4319 return; /* If the callback handled it, we're done. */
4320 }
4321 }
4322
4323 if (sub_tree->state == SIP_SUB_TREE_NORMAL && sub_tree->is_list) {
4324 /* update RLS */
4325 const char *resource = sub_tree->root->resource;
4326 struct ast_sip_subscription *old_root = sub_tree->root;
4327 struct ast_sip_subscription *new_root = NULL;
4328
4329 struct ast_sip_pubsub_body_generator *generator = NULL;
4330
4331 if (endpoint && (generator = subscription_get_generator_from_rdata(rdata, sub_tree->root->handler))) {
4332
4333 struct resource_tree tree;
4334 int resp;
4335
4336 memset(&tree, 0, sizeof(tree));
4337 resp = build_resource_tree(endpoint, sub_tree->root->handler, resource, &tree,
4339 if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
4340 new_root = create_virtual_subscriptions(sub_tree->root->handler, resource, generator, sub_tree, tree.root);
4341 if (new_root) {
4342 if (cmp_subscription_childrens(old_root, new_root)) {
4343 ast_debug(1, "RLS '%s->%s' was modified, regenerate it\n", ast_sorcery_object_get_id(endpoint), old_root->resource);
4344 new_root->version = old_root->version;
4345 sub_tree->root = new_root;
4346 sub_tree->generate_initial_notify = 1;
4347
4348 /* If there is scheduled notification need to delete it to avoid use old subscriptions */
4349 if (sub_tree->notify_sched_id > -1) {
4350 AST_SCHED_DEL_UNREF(sched, sub_tree->notify_sched_id, ao2_ref(sub_tree, -1));
4351 sub_tree->send_scheduled_notify = 0;
4352 }
4353
4354 /* Terminate old subscriptions to stop sending NOTIFY messages on exten/device state changes */
4355 set_state_terminated(old_root);
4356
4357 /* Shutdown old subscriptions to remove exten/device state change callbacks
4358 that can queue tasks for old subscriptions */
4359 shutdown_subscriptions(old_root);
4360
4361 /* Postpone destruction until all already queued tasks that may be using old subscriptions have completed */
4362 if (ast_sip_push_task(sub_tree->serializer, destroy_subscriptions_task, old_root)) {
4363 ast_log(LOG_ERROR, "Failed to push task to destroy old subscriptions for RLS '%s->%s'.\n",
4364 ast_sorcery_object_get_id(endpoint), old_root->resource);
4365 }
4366 } else {
4367 destroy_subscriptions(new_root);
4368 }
4369 }
4370 } else {
4372 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
4373 }
4374
4375 resource_tree_destroy(&tree);
4376 }
4377 }
4378
4380
4381#ifdef HAVE_PJSIP_EVSUB_PENDING_NOTIFY
4382 /* As of pjsip 2.13, the NOTIFY has to be sent within this function as pjproject now
4383 requires it. Previously this would have caused an early NOTIFY to go out before the
4384 SUBSCRIBE's 200 OK. The previous solution was to push the NOTIFY, but now pjproject
4385 looks for the NOTIFY to be sent from this function and caches it to send after it
4386 auto-replies to the SUBSCRIBE. */
4387 pubsub_on_refresh_timeout(sub_tree);
4388#else
4390 /* If we can't push the NOTIFY refreshing task...we'll just go with it. */
4391 ast_log(LOG_ERROR, "Failed to push task to send NOTIFY.\n");
4392 sub_tree->state = SIP_SUB_TREE_NORMAL;
4393 ao2_ref(sub_tree, -1);
4394 }
4395#endif
4396
4397 if (sub_tree->is_list) {
4398 pj_list_insert_before(res_hdr, create_require_eventlist(rdata->tp_info.pool));
4399 }
4400}
4401
4402static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code,
4403 pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
4404{
4405 struct ast_sip_subscription *sub;
4406
4407 if (!(sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id))) {
4408 return;
4409 }
4410
4411 sub->handler->subscriber->state_change(sub, rdata->msg_info.msg->body,
4412 pjsip_evsub_get_state(evsub));
4413}
4414
4415static int serialized_pubsub_on_client_refresh(void *userdata)
4416{
4417 struct sip_subscription_tree *sub_tree = userdata;
4418 pjsip_tx_data *tdata;
4419
4420 if (!sub_tree->evsub) {
4421 ao2_cleanup(sub_tree);
4422 return 0;
4423 }
4424
4425 if (pjsip_evsub_initiate(sub_tree->evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
4426 pjsip_evsub_send_request(sub_tree->evsub, tdata);
4427 } else {
4428 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
4429 }
4430
4431 ao2_cleanup(sub_tree);
4432 return 0;
4433}
4434
4435static void pubsub_on_client_refresh(pjsip_evsub *evsub)
4436{
4437 struct sip_subscription_tree *sub_tree;
4438
4439 if (!(sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id))) {
4440 return;
4441 }
4442
4444 ao2_cleanup(sub_tree);
4445 }
4446}
4447
4448static void pubsub_on_server_timeout(pjsip_evsub *evsub)
4449{
4450 struct sip_subscription_tree *sub_tree;
4451
4452 /* PJSIP does not terminate the server timeout timer when a SUBSCRIBE
4453 * with Expires: 0 arrives to end a subscription, nor does it terminate
4454 * this timer when we send a NOTIFY request in response to receiving such
4455 * a SUBSCRIBE. PJSIP does not stop the server timeout timer until the
4456 * NOTIFY transaction has finished (either through receiving a response
4457 * or through a transaction timeout).
4458 *
4459 * Therefore, it is possible that we can be told that a server timeout
4460 * occurred after we already thought that the subscription had been
4461 * terminated. In such a case, we will have already removed the sub_tree
4462 * from the evsub's mod_data array.
4463 */
4464
4465 sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4466 if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) {
4467 return;
4468 }
4469
4472 sub_tree->state = SIP_SUB_TREE_NORMAL;
4473 ao2_cleanup(sub_tree);
4474 }
4475}
4476
4478 struct ast_sip_ami *ami,
4479 const char *event)
4480{
4481 struct ast_str *buf;
4482
4484 if (!buf) {
4485 return -1;
4486 }
4487
4488 sip_subscription_to_ami(sub_tree, &buf);
4489 astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
4490 ast_free(buf);
4491
4492 ++ami->count;
4493 return 0;
4494}
4495
4496static int ami_subscription_detail_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4497{
4498 return sub_tree->role == AST_SIP_NOTIFIER ? ami_subscription_detail(
4499 sub_tree, arg, "InboundSubscriptionDetail") : 0;
4500}
4501
4502static int ami_subscription_detail_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4503{
4504 return sub_tree->role == AST_SIP_SUBSCRIBER ? ami_subscription_detail(
4505 sub_tree, arg, "OutboundSubscriptionDetail") : 0;
4506}
4507
4508static int ami_show_subscriptions_inbound(struct mansession *s, const struct message *m)
4509{
4510 struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
4511
4512 astman_send_listack(s, m, "Following are Events for each inbound Subscription",
4513 "start");
4514
4516
4517 astman_send_list_complete_start(s, m, "InboundSubscriptionDetailComplete", ami.count);
4519 return 0;
4520}
4521
4522static int ami_show_subscriptions_outbound(struct mansession *s, const struct message *m)
4523{
4524 struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
4525
4526 astman_send_listack(s, m, "Following are Events for each outbound Subscription",
4527 "start");
4528
4530
4531 astman_send_list_complete_start(s, m, "OutboundSubscriptionDetailComplete", ami.count);
4533 return 0;
4534}
4535
4536static int format_ami_resource_lists(void *obj, void *arg, int flags)
4537{
4538 struct resource_list *list = obj;
4539 struct ast_sip_ami *ami = arg;
4540 struct ast_str *buf;
4541
4542 buf = ast_sip_create_ami_event("ResourceListDetail", ami);
4543 if (!buf) {
4544 return CMP_STOP;
4545 }
4546
4547 if (ast_sip_sorcery_object_to_ami(list, &buf)) {
4548 ast_free(buf);
4549 return CMP_STOP;
4550 }
4551 astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
4552 ast_free(buf);
4553
4554 ++ami->count;
4555 return 0;
4556}
4557
4558static int ami_show_resource_lists(struct mansession *s, const struct message *m)
4559{
4560 struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
4561 struct ao2_container *lists;
4562
4563 lists = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "resource_list",
4565
4566 if (!lists || !ao2_container_count(lists)) {
4567 astman_send_error(s, m, "No resource lists found\n");
4568 return 0;
4569 }
4570
4571 astman_send_listack(s, m, "A listing of resource lists follows, presented as ResourceListDetail events",
4572 "start");
4573
4575
4576 astman_send_list_complete_start(s, m, "ResourceListDetailComplete", ami.count);
4578 return 0;
4579}
4580
4581#define AMI_SHOW_SUBSCRIPTIONS_INBOUND "PJSIPShowSubscriptionsInbound"
4582#define AMI_SHOW_SUBSCRIPTIONS_OUTBOUND "PJSIPShowSubscriptionsOutbound"
4583
4584#define MAX_REGEX_ERROR_LEN 128
4585
4587 /*! CLI handler entry e parameter */
4589 /*! CLI handler entry a parameter */
4591 /*! CLI subscription entry output line(s) */
4592 struct ast_str *buf;
4593 /*! Compiled regular expression to select if buf is written to CLI when not NULL. */
4594 regex_t *like;
4596};
4597
4600 /*! Found callid for search position */
4601 char *callid;
4604};
4605
4607{
4608 pj_str_t *callid;
4609
4610 if (!sub_tree->dlg) {
4611 return 0;
4612 }
4613
4614 callid = &sub_tree->dlg->call_id->id;
4615 if (cli->wordlen <= pj_strlen(callid)
4616 && !strncasecmp(cli->a->word, pj_strbuf(callid), cli->wordlen)
4617 && (++cli->which > cli->a->n)) {
4618 cli->callid = ast_malloc(pj_strlen(callid) + 1);
4619 if (cli->callid) {
4620 ast_copy_pj_str(cli->callid, callid, pj_strlen(callid) + 1);
4621 }
4622 return -1;
4623 }
4624 return 0;
4625}
4626
4627static int cli_complete_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4628{
4629 return sub_tree->role == AST_SIP_NOTIFIER
4630 ? cli_complete_subscription_common(sub_tree, arg) : 0;
4631}
4632
4633static int cli_complete_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4634{
4635 return sub_tree->role == AST_SIP_SUBSCRIBER
4636 ? cli_complete_subscription_common(sub_tree, arg) : 0;
4637}
4638
4640{
4642 on_subscription_t on_subscription;
4643
4644 if (a->pos != 4) {
4645 return NULL;
4646 }
4647
4648 if (!strcasecmp(a->argv[3], "inbound")) {
4649 on_subscription = cli_complete_subscription_inbound;
4650 } else if (!strcasecmp(a->argv[3], "outbound")) {
4651 on_subscription = cli_complete_subscription_outbound;
4652 } else {
4653 /* Should never get here */
4654 ast_assert(0);
4655 return NULL;
4656 }
4657
4658 cli.a = a;
4659 cli.callid = NULL;
4660 cli.wordlen = strlen(a->word);
4661 cli.which = 0;
4662 for_each_subscription(on_subscription, &cli);
4663
4664 return cli.callid;
4665}
4666
4667static unsigned int cli_subscription_expiry(struct sip_subscription_tree *sub_tree)
4668{
4669 int expiry;
4670
4671 expiry = sub_tree->persistence
4672 ? ast_tvdiff_ms(sub_tree->persistence->expires, ast_tvnow()) / 1000
4673 : 0;
4674 if (expiry < 0) {
4675 /* Subscription expired */
4676 expiry = 0;
4677 }
4678 return expiry;
4679}
4680
4682{
4683 const char *callid = (const char *) cli->buf;/* Member repurposed to pass in callid */
4684 pj_str_t *sub_callid;
4685 struct ast_str *buf;
4686 char *src;
4687 char *dest;
4688 char *key;
4689 char *value;
4690 char *value_end;
4691 int key_len;
4692 int key_filler_width;
4693 int value_len;
4694
4695 if (!sub_tree->dlg) {
4696 return 0;
4697 }
4698 sub_callid = &sub_tree->dlg->call_id->id;
4699 if (pj_strcmp2(sub_callid, callid)) {
4700 return 0;
4701 }
4702
4703 buf = ast_str_create(512);
4704 if (!buf) {
4705 return -1;
4706 }
4707
4708 ast_cli(cli->a->fd,
4709 "%-20s: %s\n"
4710 "===========================================================================\n",
4711 "ParameterName", "ParameterValue");
4712
4713 ast_str_append(&buf, 0, "Resource: %s\n", sub_tree->root->resource);
4714 ast_str_append(&buf, 0, "Event: %s\n", sub_tree->root->handler->event_name);
4715 ast_str_append(&buf, 0, "Expiry: %u\n", cli_subscription_expiry(sub_tree));
4716
4717 sip_subscription_to_ami(sub_tree, &buf);
4718
4719 /* Convert AMI \r\n to \n line terminators. */
4720 src = strchr(ast_str_buffer(buf), '\r');
4721 if (src) {
4722 dest = src;
4723 ++src;
4724 while (*src) {
4725 if (*src == '\r') {
4726 ++src;
4727 continue;
4728 }
4729 *dest++ = *src++;
4730 }
4731 *dest = '\0';
4733 }
4734
4735 /* Reformat AMI key value pairs to pretty columns */
4736 key = ast_str_buffer(buf);
4737 do {
4738 value = strchr(key, ':');
4739 if (!value) {
4740 break;
4741 }
4742 value_end = strchr(value, '\n');
4743 if (!value_end) {
4744 break;
4745 }
4746
4747 /* Calculate field lengths */
4748 key_len = value - key;
4749 key_filler_width = 20 - key_len;
4750 if (key_filler_width < 0) {
4751 key_filler_width = 0;
4752 }
4753 value_len = value_end - value;
4754
4755 ast_cli(cli->a->fd, "%.*s%*s%.*s\n",
4756 key_len, key, key_filler_width, "",
4757 value_len, value);
4758
4759 key = value_end + 1;
4760 } while (*key);
4761 ast_cli(cli->a->fd, "\n");
4762
4763 ast_free(buf);
4764
4765 return -1;
4766}
4767
4768static int cli_show_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4769{
4770 return sub_tree->role == AST_SIP_NOTIFIER
4771 ? cli_show_subscription_common(sub_tree, arg) : 0;
4772}
4773
4774static int cli_show_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4775{
4776 return sub_tree->role == AST_SIP_SUBSCRIBER
4777 ? cli_show_subscription_common(sub_tree, arg) : 0;
4778}
4779
4780static char *cli_show_subscription_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
4781{
4782 on_subscription_t on_subscription;
4783 struct cli_sub_parms cli;
4784
4785 switch (cmd) {
4786 case CLI_INIT:
4787 e->command = "pjsip show subscription {inbound|outbound}";
4788 e->usage = "Usage:\n"
4789 " pjsip show subscription inbound <call-id>\n"
4790 " pjsip show subscription outbound <call-id>\n"
4791 " Show active subscription with the dialog call-id\n";
4792 return NULL;
4793 case CLI_GENERATE:
4795 }
4796
4797 if (a->argc != 5) {
4798 return CLI_SHOWUSAGE;
4799 }
4800
4801 if (!strcasecmp(a->argv[3], "inbound")) {
4802 on_subscription = cli_show_subscription_inbound;
4803 } else if (!strcasecmp(a->argv[3], "outbound")) {
4804 on_subscription = cli_show_subscription_outbound;
4805 } else {
4806 /* Should never get here */
4807 ast_assert(0);
4808 return NULL;
4809 }
4810
4811 /* Find the subscription with the specified call-id */
4812 cli.a = a;
4813 cli.e = e;
4814 cli.buf = (void *) a->argv[4];/* Repurpose the buf member to pass in callid */
4815 for_each_subscription(on_subscription, &cli);
4816
4817 return CLI_SUCCESS;
4818}
4819
4820#define CLI_SHOW_SUB_FORMAT_HEADER \
4821 "Endpoint: <Endpoint/Caller-ID.............................................>\n" \
4822 "Resource: <Resource/Event.................................................>\n" \
4823 " Expiry: <Expiry> <Call-id..............................................>\n" \
4824 "===========================================================================\n\n"
4825#define CLI_SHOW_SUB_FORMAT_ENTRY \
4826 "Endpoint: %s/%s\n" \
4827 "Resource: %s/%s\n" \
4828 " Expiry: %8d %s\n\n"
4829
4831{
4832 char caller_id[256];
4833 char callid[256];
4834
4835 ast_callerid_merge(caller_id, sizeof(caller_id),
4836 S_COR(sub_tree->endpoint->id.self.name.valid,
4837 sub_tree->endpoint->id.self.name.str, NULL),
4838 S_COR(sub_tree->endpoint->id.self.number.valid,
4839 sub_tree->endpoint->id.self.number.str, NULL),
4840 "<none>");
4841
4842 /* Call-id */
4843 if (sub_tree->dlg) {
4844 ast_copy_pj_str(callid, &sub_tree->dlg->call_id->id, sizeof(callid));
4845 } else {
4846 ast_copy_string(callid, "<unknown>", sizeof(callid));
4847 }
4848
4850 ast_sorcery_object_get_id(sub_tree->endpoint), caller_id,
4851 sub_tree->root->resource, sub_tree->root->handler->event_name,
4852 cli_subscription_expiry(sub_tree), callid);
4853
4854 if (cli->like) {
4855 if (regexec(cli->like, ast_str_buffer(cli->buf), 0, NULL, 0)) {
4856 /* Output line did not match the regex */
4857 return 0;
4858 }
4859 }
4860
4861 ast_cli(cli->a->fd, "%s", ast_str_buffer(cli->buf));
4862 ++cli->count;
4863
4864 return 0;
4865}
4866
4867static int cli_show_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4868{
4869 return sub_tree->role == AST_SIP_NOTIFIER
4870 ? cli_show_subscriptions_detail(sub_tree, arg) : 0;
4871}
4872
4873static int cli_show_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4874{
4875 return sub_tree->role == AST_SIP_SUBSCRIBER
4876 ? cli_show_subscriptions_detail(sub_tree, arg) : 0;
4877}
4878
4879static char *cli_show_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
4880{
4881 on_subscription_t on_subscription;
4882 struct cli_sub_parms cli;
4883 regex_t like;
4884 const char *regex;
4885
4886 switch (cmd) {
4887 case CLI_INIT:
4888 e->command = "pjsip show subscriptions {inbound|outbound} [like]";
4889 e->usage = "Usage:\n"
4890 " pjsip show subscriptions inbound [like <regex>]\n"
4891 " Show active inbound subscriptions\n"
4892 " pjsip show subscriptions outbound [like <regex>]\n"
4893 " Show active outbound subscriptions\n"
4894 "\n"
4895 " The regex selects a subscriptions output that matches.\n"
4896 " i.e., All output lines for a subscription are checked\n"
4897 " as a block by the regex.\n";
4898 return NULL;
4899 case CLI_GENERATE:
4900 return NULL;
4901 }
4902
4903 if (a->argc != 4 && a->argc != 6) {
4904 return CLI_SHOWUSAGE;
4905 }
4906 if (!strcasecmp(a->argv[3], "inbound")) {
4907 on_subscription = cli_show_subscriptions_inbound;
4908 } else if (!strcasecmp(a->argv[3], "outbound")) {
4909 on_subscription = cli_show_subscriptions_outbound;
4910 } else {
4911 /* Should never get here */
4912 ast_assert(0);
4913 return CLI_SHOWUSAGE;
4914 }
4915 if (a->argc == 6) {
4916 int rc;
4917
4918 if (strcasecmp(a->argv[4], "like")) {
4919 return CLI_SHOWUSAGE;
4920 }
4921
4922 /* Setup regular expression */
4923 memset(&like, 0, sizeof(like));
4924 cli.like = &like;
4925 regex = a->argv[5];
4926 rc = regcomp(cli.like, regex, REG_EXTENDED | REG_NOSUB);
4927 if (rc) {
4928 char *regerr = ast_alloca(MAX_REGEX_ERROR_LEN);
4929
4930 regerror(rc, cli.like, regerr, MAX_REGEX_ERROR_LEN);
4931 ast_cli(a->fd, "Regular expression '%s' failed to compile: %s\n",
4932 regex, regerr);
4933 return CLI_FAILURE;
4934 }
4935 } else {
4936 cli.like = NULL;
4937 regex = NULL;
4938 }
4939
4940 cli.a = a;
4941 cli.e = e;
4942 cli.count = 0;
4943 cli.buf = ast_str_create(256);
4944 if (!cli.buf) {
4945 if (cli.like) {
4946 regfree(cli.like);
4947 }
4948 return CLI_FAILURE;
4949 }
4950
4952 for_each_subscription(on_subscription, &cli);
4953 ast_cli(a->fd, "%d active subscriptions%s%s%s\n",
4954 cli.count,
4955 regex ? " matched \"" : "",
4956 regex ?: "",
4957 regex ? "\"" : "");
4958
4959 ast_free(cli.buf);
4960 if (cli.like) {
4961 regfree(cli.like);
4962 }
4963
4964 return CLI_SUCCESS;
4965}
4966
4967#define CLI_LIST_SUB_FORMAT_HEADER "%-30.30s %-30.30s %6.6s %s\n"
4968#define CLI_LIST_SUB_FORMAT_ENTRY "%-30.30s %-30.30s %6d %s\n"
4969
4971{
4972 char ep_cid_buf[50];
4973 char res_evt_buf[50];
4974 char callid[256];
4975
4976 /* Endpoint/CID column */
4977 snprintf(ep_cid_buf, sizeof(ep_cid_buf), "%s/%s",
4979 S_COR(sub_tree->endpoint->id.self.name.valid, sub_tree->endpoint->id.self.name.str,
4980 S_COR(sub_tree->endpoint->id.self.number.valid,
4981 sub_tree->endpoint->id.self.number.str, "<none>")));
4982
4983 /* Resource/Event column */
4984 snprintf(res_evt_buf, sizeof(res_evt_buf), "%s/%s",
4985 sub_tree->root->resource,
4986 sub_tree->root->handler->event_name);
4987
4988 /* Call-id column */
4989 if (sub_tree->dlg) {
4990 ast_copy_pj_str(callid, &sub_tree->dlg->call_id->id, sizeof(callid));
4991 } else {
4992 ast_copy_string(callid, "<unknown>", sizeof(callid));
4993 }
4994
4996 ep_cid_buf,
4997 res_evt_buf,
4998 cli_subscription_expiry(sub_tree),
4999 callid);
5000
5001 if (cli->like) {
5002 if (regexec(cli->like, ast_str_buffer(cli->buf), 0, NULL, 0)) {
5003 /* Output line did not match the regex */
5004 return 0;
5005 }
5006 }
5007
5008 ast_cli(cli->a->fd, "%s", ast_str_buffer(cli->buf));
5009 ++cli->count;
5010
5011 return 0;
5012}
5013
5014static int cli_list_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
5015{
5016 return sub_tree->role == AST_SIP_NOTIFIER
5017 ? cli_list_subscriptions_detail(sub_tree, arg) : 0;
5018}
5019
5020static int cli_list_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
5021{
5022 return sub_tree->role == AST_SIP_SUBSCRIBER
5023 ? cli_list_subscriptions_detail(sub_tree, arg) : 0;
5024}
5025
5026static char *cli_list_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
5027{
5028 on_subscription_t on_subscription;
5029 struct cli_sub_parms cli;
5030 regex_t like;
5031 const char *regex;
5032
5033 switch (cmd) {
5034 case CLI_INIT:
5035 e->command = "pjsip list subscriptions {inbound|outbound} [like]";
5036 e->usage = "Usage:\n"
5037 " pjsip list subscriptions inbound [like <regex>]\n"
5038 " List active inbound subscriptions\n"
5039 " pjsip list subscriptions outbound [like <regex>]\n"
5040 " List active outbound subscriptions\n"
5041 "\n"
5042 " The regex selects output lines that match.\n";
5043 return NULL;
5044 case CLI_GENERATE:
5045 return NULL;
5046 }
5047
5048 if (a->argc != 4 && a->argc != 6) {
5049 return CLI_SHOWUSAGE;
5050 }
5051 if (!strcasecmp(a->argv[3], "inbound")) {
5052 on_subscription = cli_list_subscriptions_inbound;
5053 } else if (!strcasecmp(a->argv[3], "outbound")) {
5054 on_subscription = cli_list_subscriptions_outbound;
5055 } else {
5056 /* Should never get here */
5057 ast_assert(0);
5058 return CLI_SHOWUSAGE;
5059 }
5060 if (a->argc == 6) {
5061 int rc;
5062
5063 if (strcasecmp(a->argv[4], "like")) {
5064 return CLI_SHOWUSAGE;
5065 }
5066
5067 /* Setup regular expression */
5068 memset(&like, 0, sizeof(like));
5069 cli.like = &like;
5070 regex = a->argv[5];
5071 rc = regcomp(cli.like, regex, REG_EXTENDED | REG_NOSUB);
5072 if (rc) {
5073 char *regerr = ast_alloca(MAX_REGEX_ERROR_LEN);
5074
5075 regerror(rc, cli.like, regerr, MAX_REGEX_ERROR_LEN);
5076 ast_cli(a->fd, "Regular expression '%s' failed to compile: %s\n",
5077 regex, regerr);
5078 return CLI_FAILURE;
5079 }
5080 } else {
5081 cli.like = NULL;
5082 regex = NULL;
5083 }
5084
5085 cli.a = a;
5086 cli.e = e;
5087 cli.count = 0;
5088 cli.buf = ast_str_create(256);
5089 if (!cli.buf) {
5090 if (cli.like) {
5091 regfree(cli.like);
5092 }
5093 return CLI_FAILURE;
5094 }
5095
5097 "Endpoint/CLI", "Resource/Event", "Expiry", "Call-id");
5098 for_each_subscription(on_subscription, &cli);
5099 ast_cli(a->fd, "\n%d active subscriptions%s%s%s\n",
5100 cli.count,
5101 regex ? " matched \"" : "",
5102 regex ?: "",
5103 regex ? "\"" : "");
5104
5105 ast_free(cli.buf);
5106 if (cli.like) {
5107 regfree(cli.like);
5108 }
5109
5110 return CLI_SUCCESS;
5111}
5112
5113static struct ast_cli_entry cli_commands[] = {
5114 AST_CLI_DEFINE(cli_list_subscriptions_inout, "List active inbound/outbound subscriptions"),
5115 AST_CLI_DEFINE(cli_show_subscription_inout, "Show active subscription details"),
5116 AST_CLI_DEFINE(cli_show_subscriptions_inout, "Show active inbound/outbound subscriptions"),
5117};
5118
5119static int persistence_endpoint_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5120{
5121 struct subscription_persistence *persistence = obj;
5122
5123 persistence->endpoint = ast_strdup(var->value);
5124 return 0;
5125}
5126
5127static int persistence_endpoint_struct2str(const void *obj, const intptr_t *args, char **buf)
5128{
5129 const struct subscription_persistence *persistence = obj;
5130
5131 *buf = ast_strdup(persistence->endpoint);
5132 return 0;
5133}
5134
5135static int persistence_tag_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5136{
5137 struct subscription_persistence *persistence = obj;
5138
5139 persistence->tag = ast_strdup(var->value);
5140 return 0;
5141}
5142
5143static int persistence_tag_struct2str(const void *obj, const intptr_t *args, char **buf)
5144{
5145 const struct subscription_persistence *persistence = obj;
5146
5147 *buf = ast_strdup(persistence->tag);
5148 return 0;
5149}
5150
5151static int persistence_generator_data_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5152{
5153 struct subscription_persistence *persistence = obj;
5154 struct ast_json_error error;
5155
5156 /* We tolerate a failure of the JSON to load and instead start fresh, since this field
5157 * originates from the persistence code and not a user.
5158 */
5159 persistence->generator_data = ast_json_load_string(var->value, &error);
5160
5161 return 0;
5162}
5163
5164static int persistence_generator_data_struct2str(const void *obj, const intptr_t *args, char **buf)
5165{
5166 const struct subscription_persistence *persistence = obj;
5167 char *value;
5168
5169 if (!persistence->generator_data) {
5170 return 0;
5171 }
5172
5174 if (!value) {
5175 return -1;
5176 }
5177
5178 *buf = ast_strdup(value);
5180
5181 return 0;
5182}
5183
5184static int persistence_expires_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5185{
5186 struct subscription_persistence *persistence = obj;
5187 return ast_get_timeval(var->value, &persistence->expires, ast_tv(0, 0), NULL);
5188}
5189
5190static int persistence_expires_struct2str(const void *obj, const intptr_t *args, char **buf)
5191{
5192 const struct subscription_persistence *persistence = obj;
5193 char secs[AST_TIME_T_LEN];
5194
5195 ast_time_t_to_string(persistence->expires.tv_sec, secs, sizeof(secs));
5196
5197 return (ast_asprintf(buf, "%s", secs) < 0) ? -1 : 0;
5198}
5199
5200#define RESOURCE_LIST_INIT_SIZE 4
5201
5202static void resource_list_destructor(void *obj)
5203{
5204 struct resource_list *list = obj;
5205 int i;
5206
5207 for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
5208 ast_free((char *) AST_VECTOR_GET(&list->items, i));
5209 }
5210
5211 AST_VECTOR_FREE(&list->items);
5212}
5213
5214static void *resource_list_alloc(const char *name)
5215{
5216 struct resource_list *list;
5217
5219 if (!list) {
5220 return NULL;
5221 }
5222
5224 ao2_cleanup(list);
5225 return NULL;
5226 }
5227
5228 return list;
5229}
5230
5231static int item_in_vector(const struct resource_list *list, const char *item)
5232{
5233 int i;
5234
5235 for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
5236 if (!strcmp(item, AST_VECTOR_GET(&list->items, i))) {
5237 return 1;
5238 }
5239 }
5240
5241 return 0;
5242}
5243
5244static int list_item_handler(const struct aco_option *opt,
5245 struct ast_variable *var, void *obj)
5246{
5247 struct resource_list *list = obj;
5248 char *items = ast_strdupa(var->value);
5249 char *item;
5250
5251 while ((item = ast_strip(strsep(&items, ",")))) {
5252 if (ast_strlen_zero(item)) {
5253 continue;
5254 }
5255
5256 if (item_in_vector(list, item)) {
5257 ast_log(LOG_WARNING, "Ignoring duplicated list item '%s'\n", item);
5258 continue;
5259 }
5260
5261 item = ast_strdup(item);
5262 if (!item || AST_VECTOR_APPEND(&list->items, item)) {
5263 ast_free(item);
5264 return -1;
5265 }
5266 }
5267
5268 return 0;
5269}
5270
5271static int list_item_to_str(const void *obj, const intptr_t *args, char **buf)
5272{
5273 const struct resource_list *list = obj;
5274 int i;
5275 struct ast_str *str = ast_str_create(32);
5276
5277 for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
5278 ast_str_append(&str, 0, "%s,", AST_VECTOR_GET(&list->items, i));
5279 }
5280
5281 /* Chop off trailing comma */
5282 ast_str_truncate(str, -1);
5284 ast_free(str);
5285 return 0;
5286}
5287
5288static int resource_list_apply_handler(const struct ast_sorcery *sorcery, void *obj)
5289{
5290 struct resource_list *list = obj;
5291
5292 if (ast_strlen_zero(list->event)) {
5293 ast_log(LOG_WARNING, "Resource list '%s' has no event set\n",
5295 return -1;
5296 }
5297
5298 if (AST_VECTOR_SIZE(&list->items) == 0) {
5299 ast_log(LOG_WARNING, "Resource list '%s' has no list items\n",
5301 return -1;
5302 }
5303
5304 return 0;
5305}
5306
5308{
5309 ast_sorcery_apply_default(sorcery, "resource_list", "config",
5310 "pjsip.conf,criteria=type=resource_list");
5313 return -1;
5314 }
5315
5316 ast_sorcery_object_field_register(sorcery, "resource_list", "type", "",
5317 OPT_NOOP_T, 0, 0);
5318 ast_sorcery_object_field_register(sorcery, "resource_list", "event", "",
5320 ast_sorcery_object_field_register(sorcery, "resource_list", "full_state", "no",
5322 ast_sorcery_object_field_register(sorcery, "resource_list", "notification_batch_interval",
5324 ast_sorcery_object_field_register_custom(sorcery, "resource_list", "list_item",
5326 ast_sorcery_object_field_register(sorcery, "resource_list", "resource_display_name", "no",
5328
5329 ast_sorcery_reload_object(sorcery, "resource_list");
5330
5331 return 0;
5332}
5333
5334#ifdef TEST_FRAMEWORK
5335
5336/*!
5337 * \brief "bad" resources
5338 *
5339 * These are resources that the test handler will reject subscriptions to.
5340 */
5341const char *bad_resources[] = {
5342 "coconut",
5343 "cilantro",
5344 "olive",
5345 "cheese",
5346};
5347
5348/*!
5349 * \brief new_subscribe callback for unit tests
5350 *
5351 * Will give a 200 OK response to any resource except the "bad" ones.
5352 */
5353static int test_new_subscribe(struct ast_sip_endpoint *endpoint, const char *resource)
5354{
5355 int i;
5356
5357 for (i = 0; i < ARRAY_LEN(bad_resources); ++i) {
5358 if (!strcmp(resource, bad_resources[i])) {
5359 return 400;
5360 }
5361 }
5362
5363 return 200;
5364}
5365
5366/*!
5367 * \brief Subscription notifier for unit tests.
5368 *
5369 * Since unit tests are only concerned with building a resource tree,
5370 * only the new_subscribe callback needs to be defined.
5371 */
5372struct ast_sip_notifier test_notifier = {
5373 .new_subscribe = test_new_subscribe,
5374};
5375
5376/*!
5377 * \brief Subscription handler for unit tests.
5378 */
5380 .event_name = "test",
5381 .body_type = "",
5382 .notifier = &test_notifier,
5383};
5384
5385/*!
5386 * \brief Set properties on an allocated resource list
5387 *
5388 * \param list The list to set details on.
5389 * \param event The list's event.
5390 * \param resources Array of resources to add to the list.
5391 * \param num_resources Number of resources in the array.
5392 * \retval 0 Success
5393 * \retval non-zero Failure
5394 */
5395static int populate_list(struct resource_list *list, const char *event, const char **resources, size_t num_resources)
5396{
5397 int i;
5398
5399 ast_copy_string(list->event, event, sizeof(list->event));
5400
5401 for (i = 0; i < num_resources; ++i) {
5402 char *resource = ast_strdup(resources[i]);
5403
5404 if (!resource || AST_VECTOR_APPEND(&list->items, resource)) {
5405 ast_free(resource);
5406 return -1;
5407 }
5408 }
5409 return 0;
5410}
5411
5412/*!
5413 * \brief RAII callback to destroy a resource list
5414 */
5415static void cleanup_resource_list(struct resource_list *list)
5416{
5417 if (!list) {
5418 return;
5419 }
5420
5422 ao2_cleanup(list);
5423}
5424
5425/*!
5426 * \brief allocate a resource list, store it in sorcery, and set its details
5427 *
5428 * \param test The unit test. Used for logging status messages.
5429 * \param list_name The name of the list to create.
5430 * \param event The event the list services
5431 * \param resources Array of resources to apply to the list
5432 * \param num_resources The number of resources in the array
5433 * \retval NULL Failed to allocate or populate list
5434 * \retval non-NULL The created list
5435 */
5436static struct resource_list *create_resource_list(struct ast_test *test,
5437 const char *list_name, const char *event, const char **resources, size_t num_resources)
5438{
5439 struct resource_list *list;
5440
5441 list = ast_sorcery_alloc(ast_sip_get_sorcery(), "resource_list", list_name);
5442 if (!list) {
5443 ast_test_status_update(test, "Could not allocate resource list in sorcery\n");
5444 return NULL;
5445 }
5446
5448 ast_test_status_update(test, "Could not store the resource list in sorcery\n");
5449 ao2_cleanup(list);
5450 return NULL;
5451 }
5452
5453 if (populate_list(list, event, resources, num_resources)) {
5454 ast_test_status_update(test, "Could not add resources to the resource list\n");
5455 cleanup_resource_list(list);
5456 return NULL;
5457 }
5458
5459 return list;
5460}
5461
5462/*!
5463 * \brief Check the integrity of a tree node against a set of resources.
5464 *
5465 * The tree node's resources must be in the same order as the resources in
5466 * the supplied resources array. Because of this constraint, tests can misrepresent
5467 * the size of the resources array as being smaller than it really is if resources
5468 * at the end of the array should not be present in the tree node.
5469 *
5470 * \param test The unit test. Used for printing status messages.
5471 * \param node The constructed tree node whose integrity is under question.
5472 * \param resources Array of expected resource values
5473 * \param num_resources The number of resources to check in the array.
5474 */
5475static int check_node(struct ast_test *test, struct tree_node *node,
5476 const char **resources, size_t num_resources)
5477{
5478 int i;
5479
5480 if (AST_VECTOR_SIZE(&node->children) != num_resources) {
5481 ast_test_status_update(test, "Unexpected number of resources in tree. Expected %zu, got %zu\n",
5482 num_resources, AST_VECTOR_SIZE(&node->children));
5483 return -1;
5484 }
5485
5486 for (i = 0; i < num_resources; ++i) {
5487 if (strcmp(resources[i], AST_VECTOR_GET(&node->children, i)->resource)) {
5488 ast_test_status_update(test, "Mismatched resources. Expected '%s' but got '%s'\n",
5489 resources[i], AST_VECTOR_GET(&node->children, i)->resource);
5490 return -1;
5491 }
5492 }
5493
5494 return 0;
5495}
5496
5497/*!
5498 * \brief RAII_VAR callback to destroy an allocated resource tree
5499 */
5500static void test_resource_tree_destroy(struct resource_tree *tree)
5501{
5503 ast_free(tree);
5504}
5505
5506static int ineligible_configuration(void)
5507{
5508 struct ast_config *config;
5509 struct ast_flags flags = {0,};
5510 const char *value;
5511
5512 config = ast_config_load("sorcery.conf", flags);
5513 if (!config) {
5514 return 1;
5515 }
5516
5517 value = ast_variable_retrieve(config, "res_pjsip_pubsub", "resource_list");
5518 if (ast_strlen_zero(value)) {
5520 return 1;
5521 }
5522
5523 if (strcasecmp(value, "memory") && strcasecmp(value, "astdb")) {
5525 return 1;
5526 }
5527
5528 return 0;
5529}
5530
5532{
5533 RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
5534 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5535 const char *resources[] = {
5536 "huey",
5537 "dewey",
5538 "louie",
5539 };
5540 int resp;
5541
5542 switch (cmd) {
5543 case TEST_INIT:
5544 info->name = "resource_tree";
5545 info->category = "/res/res_pjsip_pubsub/";
5546 info->summary = "Basic resource tree integrity check";
5547 info->description =
5548 "Create a resource list and ensure that our attempt to build a tree works as expected.";
5549 return AST_TEST_NOT_RUN;
5550 case TEST_EXECUTE:
5551 break;
5552 }
5553
5554 if (ineligible_configuration()) {
5555 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5556 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5557 return AST_TEST_NOT_RUN;
5558 }
5559
5560 list = create_resource_list(test, "foo", "test", resources, ARRAY_LEN(resources));
5561 if (!list) {
5562 return AST_TEST_FAIL;
5563 }
5564
5565 tree = ast_calloc(1, sizeof(*tree));
5566 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5567 if (resp != 200) {
5568 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5569 return AST_TEST_FAIL;
5570 }
5571
5572 if (!tree->root) {
5573 ast_test_status_update(test, "Resource tree has no root\n");
5574 return AST_TEST_FAIL;
5575 }
5576
5577 if (check_node(test, tree->root, resources, ARRAY_LEN(resources))) {
5578 return AST_TEST_FAIL;
5579 }
5580
5581 return AST_TEST_PASS;
5582}
5583
5584AST_TEST_DEFINE(complex_resource_tree)
5585{
5586 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5587 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5588 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5589 const char *resources_1[] = {
5590 "huey",
5591 "dewey",
5592 "louie",
5593 "dwarves",
5594 };
5595 const char *resources_2[] = {
5596 "happy",
5597 "grumpy",
5598 "doc",
5599 "bashful",
5600 "dopey",
5601 "sneezy",
5602 "sleepy",
5603 };
5604 int resp;
5605 struct tree_node *node;
5606
5607 switch (cmd) {
5608 case TEST_INIT:
5609 info->name = "complex_resource_tree";
5610 info->category = "/res/res_pjsip_pubsub/";
5611 info->summary = "Complex resource tree integrity check";
5612 info->description =
5613 "Create a complex resource list and ensure that our attempt to build a tree works as expected.";
5614 return AST_TEST_NOT_RUN;
5615 case TEST_EXECUTE:
5616 break;
5617 }
5618
5619 if (ineligible_configuration()) {
5620 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5621 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5622 return AST_TEST_NOT_RUN;
5623 }
5624
5625 list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
5626 if (!list_1) {
5627 return AST_TEST_FAIL;
5628 }
5629
5630 list_2 = create_resource_list(test, "dwarves", "test", resources_2, ARRAY_LEN(resources_2));
5631 if (!list_2) {
5632 return AST_TEST_FAIL;
5633 }
5634
5635 tree = ast_calloc(1, sizeof(*tree));
5636 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5637 if (resp != 200) {
5638 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5639 return AST_TEST_FAIL;
5640 }
5641
5642 if (!tree->root) {
5643 ast_test_status_update(test, "Resource tree has no root\n");
5644 return AST_TEST_FAIL;
5645 }
5646
5647 node = tree->root;
5648 if (check_node(test, node, resources_1, ARRAY_LEN(resources_1))) {
5649 return AST_TEST_FAIL;
5650 }
5651
5652 /* The embedded list is at index 3 in the root node's children */
5653 node = AST_VECTOR_GET(&node->children, 3);
5654 if (check_node(test, node, resources_2, ARRAY_LEN(resources_2))) {
5655 return AST_TEST_FAIL;
5656 }
5657
5658 return AST_TEST_PASS;
5659}
5660
5661AST_TEST_DEFINE(bad_resource)
5662{
5663 RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
5664 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5665 const char *resources[] = {
5666 "huey",
5667 "dewey",
5668 "louie",
5669 "coconut", /* A "bad" resource */
5670 };
5671 int resp;
5672
5673 switch (cmd) {
5674 case TEST_INIT:
5675 info->name = "bad_resource";
5676 info->category = "/res/res_pjsip_pubsub/";
5677 info->summary = "Ensure bad resources do not end up in the tree";
5678 info->description =
5679 "Create a resource list with a single bad resource. Ensure the bad resource does not end up in the tree.";
5680 return AST_TEST_NOT_RUN;
5681 case TEST_EXECUTE:
5682 break;
5683 }
5684
5685 if (ineligible_configuration()) {
5686 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5687 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5688 return AST_TEST_NOT_RUN;
5689 }
5690
5691 list = create_resource_list(test, "foo", "test", resources, ARRAY_LEN(resources));
5692 if (!list) {
5693 return AST_TEST_FAIL;
5694 }
5695
5696 tree = ast_calloc(1, sizeof(*tree));
5697 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5698 if (resp != 200) {
5699 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5700 return AST_TEST_FAIL;
5701 }
5702
5703 if (!tree->root) {
5704 ast_test_status_update(test, "Resource tree has no root\n");
5705 return AST_TEST_FAIL;
5706 }
5707
5708 /* We check against all but the final resource since we expect it not to be in the tree */
5709 if (check_node(test, tree->root, resources, ARRAY_LEN(resources) - 1)) {
5710 return AST_TEST_FAIL;
5711 }
5712
5713 return AST_TEST_PASS;
5714
5715}
5716
5717AST_TEST_DEFINE(bad_branch)
5718{
5719 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5720 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5721 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5722 const char *resources_1[] = {
5723 "huey",
5724 "dewey",
5725 "louie",
5726 "gross",
5727 };
5728 /* This list has nothing but bad resources */
5729 const char *resources_2[] = {
5730 "coconut",
5731 "cilantro",
5732 "olive",
5733 "cheese",
5734 };
5735 int resp;
5736
5737 switch (cmd) {
5738 case TEST_INIT:
5739 info->name = "bad_branch";
5740 info->category = "/res/res_pjsip_pubsub/";
5741 info->summary = "Ensure bad branches are pruned from the tree";
5742 info->description =
5743 "Create a resource list that makes a tree with an entire branch of bad resources.\n"
5744 "Ensure the bad branch is pruned from the tree.";
5745 return AST_TEST_NOT_RUN;
5746 case TEST_EXECUTE:
5747 break;
5748 }
5749
5750 if (ineligible_configuration()) {
5751 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5752 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5753 return AST_TEST_NOT_RUN;
5754 }
5755
5756 list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
5757 if (!list_1) {
5758 return AST_TEST_FAIL;
5759 }
5760 list_2 = create_resource_list(test, "gross", "test", resources_2, ARRAY_LEN(resources_2));
5761 if (!list_2) {
5762 return AST_TEST_FAIL;
5763 }
5764
5765 tree = ast_calloc(1, sizeof(*tree));
5766 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5767 if (resp != 200) {
5768 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5769 return AST_TEST_FAIL;
5770 }
5771
5772 if (!tree->root) {
5773 ast_test_status_update(test, "Resource tree has no root\n");
5774 return AST_TEST_FAIL;
5775 }
5776
5777 /* We check against all but the final resource of the list since the entire branch should
5778 * be pruned from the tree
5779 */
5780 if (check_node(test, tree->root, resources_1, ARRAY_LEN(resources_1) - 1)) {
5781 return AST_TEST_FAIL;
5782 }
5783
5784 return AST_TEST_PASS;
5785
5786}
5787
5788AST_TEST_DEFINE(duplicate_resource)
5789{
5790 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5791 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5792 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5793 const char *resources_1[] = {
5794 "huey",
5795 "ducks",
5796 "dewey",
5797 "louie",
5798 };
5799 const char *resources_2[] = {
5800 "donald",
5801 "daisy",
5802 "scrooge",
5803 "dewey",
5804 "louie",
5805 "huey",
5806 };
5807 int resp;
5808 struct tree_node *node;
5809
5810 switch (cmd) {
5811 case TEST_INIT:
5812 info->name = "duplicate_resource";
5813 info->category = "/res/res_pjsip_pubsub/";
5814 info->summary = "Ensure duplicated resources do not end up in the tree";
5815 info->description =
5816 "Create a resource list with a single duplicated resource. Ensure the duplicated resource does not end up in the tree.";
5817 return AST_TEST_NOT_RUN;
5818 case TEST_EXECUTE:
5819 break;
5820 }
5821
5822 if (ineligible_configuration()) {
5823 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5824 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5825 return AST_TEST_NOT_RUN;
5826 }
5827
5828 list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
5829 if (!list_1) {
5830 return AST_TEST_FAIL;
5831 }
5832
5833 list_2 = create_resource_list(test, "ducks", "test", resources_2, ARRAY_LEN(resources_2));
5834 if (!list_2) {
5835 return AST_TEST_FAIL;
5836 }
5837
5838 tree = ast_calloc(1, sizeof(*tree));
5839 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5840 if (resp != 200) {
5841 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5842 return AST_TEST_FAIL;
5843 }
5844
5845 if (!tree->root) {
5846 ast_test_status_update(test, "Resource tree has no root\n");
5847 return AST_TEST_FAIL;
5848 }
5849
5850 node = tree->root;
5851 /* This node should have "huey" and "ducks". "dewey" and "louie" should not
5852 * be present since they were found in the "ducks" list.
5853 */
5854 if (check_node(test, node, resources_1, ARRAY_LEN(resources_1) - 2)) {
5855 return AST_TEST_FAIL;
5856 }
5857
5858 /* This node should have "donald", "daisy", "scrooge", "dewey", and "louie".
5859 * "huey" is not here since that was already encountered in the parent list
5860 */
5861 node = AST_VECTOR_GET(&node->children, 1);
5862 if (check_node(test, node, resources_2, ARRAY_LEN(resources_2) - 1)) {
5863 return AST_TEST_FAIL;
5864 }
5865
5866 return AST_TEST_PASS;
5867}
5868
5869AST_TEST_DEFINE(loop)
5870{
5871 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5872 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5873 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5874 const char *resources_1[] = {
5875 "derp",
5876 };
5877 const char *resources_2[] = {
5878 "herp",
5879 };
5880 int resp;
5881
5882 switch (cmd) {
5883 case TEST_INIT:
5884 info->name = "loop";
5885 info->category = "/res/res_pjsip_pubsub/";
5886 info->summary = "Test that loops are properly detected.";
5887 info->description =
5888 "Create two resource lists that refer to each other. Ensure that attempting to build a tree\n"
5889 "results in an empty tree.";
5890 return AST_TEST_NOT_RUN;
5891 case TEST_EXECUTE:
5892 break;
5893 }
5894
5895 if (ineligible_configuration()) {
5896 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5897 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5898 return AST_TEST_NOT_RUN;
5899 }
5900
5901 list_1 = create_resource_list(test, "herp", "test", resources_1, ARRAY_LEN(resources_1));
5902 if (!list_1) {
5903 return AST_TEST_FAIL;
5904 }
5905 list_2 = create_resource_list(test, "derp", "test", resources_2, ARRAY_LEN(resources_2));
5906 if (!list_2) {
5907 return AST_TEST_FAIL;
5908 }
5909
5910 tree = ast_calloc(1, sizeof(*tree));
5911 resp = build_resource_tree(NULL, &test_handler, "herp", tree, 1, NULL);
5912 if (resp == 200) {
5913 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5914 return AST_TEST_FAIL;
5915 }
5916
5917 return AST_TEST_PASS;
5918}
5919
5920AST_TEST_DEFINE(bad_event)
5921{
5922 RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
5923 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5924 const char *resources[] = {
5925 "huey",
5926 "dewey",
5927 "louie",
5928 };
5929 int resp;
5930
5931 switch (cmd) {
5932 case TEST_INIT:
5933 info->name = "bad_event";
5934 info->category = "/res/res_pjsip_pubsub/";
5935 info->summary = "Ensure that list with wrong event specified is not retrieved";
5936 info->description =
5937 "Create a simple resource list for event 'tsetse'. Ensure that trying to retrieve the list for event 'test' fails.";
5938 return AST_TEST_NOT_RUN;
5939 case TEST_EXECUTE:
5940 break;
5941 }
5942
5943 if (ineligible_configuration()) {
5944 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5945 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5946 return AST_TEST_NOT_RUN;
5947 }
5948
5949 list = create_resource_list(test, "foo", "tsetse", resources, ARRAY_LEN(resources));
5950 if (!list) {
5951 return AST_TEST_FAIL;
5952 }
5953
5954 tree = ast_calloc(1, sizeof(*tree));
5955 /* Since the test_handler is for event "test", this should not build a list, but
5956 * instead result in a single resource being created, called "foo"
5957 */
5958 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5959 if (resp != 200) {
5960 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5961 return AST_TEST_FAIL;
5962 }
5963
5964 if (!tree->root) {
5965 ast_test_status_update(test, "Resource tree has no root\n");
5966 return AST_TEST_FAIL;
5967 }
5968
5969 if (strcmp(tree->root->resource, "foo")) {
5970 ast_test_status_update(test, "Unexpected resource %s found in tree\n", tree->root->resource);
5971 return AST_TEST_FAIL;
5972 }
5973
5974 return AST_TEST_PASS;
5975}
5976
5977#endif
5978
5979static int resource_endpoint_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
5980{
5981 struct ast_sip_publication_resource *resource = obj;
5982
5983 ast_free(resource->endpoint);
5984 resource->endpoint = ast_strdup(var->value);
5985
5986 return 0;
5987}
5988
5989static int resource_event_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
5990{
5991 struct ast_sip_publication_resource *resource = obj;
5992 /* The event configuration name starts with 'event_' so skip past it to get the real name */
5993 const char *event = var->name + 6;
5994 struct ast_variable *item;
5995
5996 if (ast_strlen_zero(event) || ast_strlen_zero(var->value)) {
5997 return -1;
5998 }
5999
6000 item = ast_variable_new(event, var->value, "");
6001 if (!item) {
6002 return -1;
6003 }
6004
6005 if (resource->events) {
6006 item->next = resource->events;
6007 }
6008 resource->events = item;
6009
6010 return 0;
6011}
6012
6013static int load_module(void)
6014{
6015 static const pj_str_t str_PUBLISH = { "PUBLISH", 7 };
6016 struct ast_sorcery *sorcery;
6017
6019
6020 if (!(sched = ast_sched_context_create())) {
6021 ast_log(LOG_ERROR, "Could not create scheduler for publication expiration\n");
6023 }
6024
6026 ast_log(LOG_ERROR, "Could not start scheduler thread for publication expiration\n");
6029 }
6030
6031 ast_sorcery_apply_config(sorcery, "res_pjsip_pubsub");
6032 ast_sorcery_apply_default(sorcery, "subscription_persistence", "astdb", "subscription_persistence");
6034 NULL, NULL)) {
6035 ast_log(LOG_ERROR, "Could not register subscription persistence object support\n");
6038 }
6039 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "packet", "", OPT_CHAR_ARRAY_T, 0,
6040 CHARFLDSET(struct subscription_persistence, packet));
6041 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "src_name", "", OPT_CHAR_ARRAY_T, 0,
6042 CHARFLDSET(struct subscription_persistence, src_name));
6043 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "src_port", "0", OPT_UINT_T, 0,
6044 FLDSET(struct subscription_persistence, src_port));
6045 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "transport_key", "0", OPT_CHAR_ARRAY_T, 0,
6046 CHARFLDSET(struct subscription_persistence, transport_type));
6047 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "local_name", "", OPT_CHAR_ARRAY_T, 0,
6048 CHARFLDSET(struct subscription_persistence, local_name));
6049 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "local_port", "0", OPT_UINT_T, 0,
6050 FLDSET(struct subscription_persistence, local_port));
6051 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "cseq", "0", OPT_UINT_T, 0,
6052 FLDSET(struct subscription_persistence, cseq));
6053 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "endpoint", "",
6055 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "tag", "",
6057 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "expires", "",
6059 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "contact_uri", "", OPT_CHAR_ARRAY_T, 0,
6060 CHARFLDSET(struct subscription_persistence, contact_uri));
6061 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "prune_on_boot", "no", OPT_YESNO_T, 1,
6062 FLDSET(struct subscription_persistence, prune_on_boot));
6063 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "generator_data", "",
6065
6069 }
6070
6071 ast_sorcery_apply_default(sorcery, "inbound-publication", "config", "pjsip.conf,criteria=type=inbound-publication");
6073 NULL, NULL)) {
6074 ast_log(LOG_ERROR, "Could not register subscription persistence object support\n");
6077 }
6078 ast_sorcery_object_field_register(sorcery, "inbound-publication", "type", "", OPT_NOOP_T, 0, 0);
6079 ast_sorcery_object_field_register_custom(sorcery, "inbound-publication", "endpoint", "",
6081 ast_sorcery_object_fields_register(sorcery, "inbound-publication", "^event_", resource_event_handler, NULL);
6082 ast_sorcery_reload_object(sorcery, "inbound-publication");
6083
6085 ast_log(LOG_ERROR, "Could not register pubsub service\n");
6088 }
6089
6090 if (pjsip_evsub_init_module(ast_sip_get_pjsip_endpoint()) != PJ_SUCCESS) {
6091 ast_log(LOG_ERROR, "Could not initialize pjsip evsub module.\n");
6095 }
6096
6097 /* Once pjsip_evsub_init_module succeeds we cannot unload.
6098 * Keep all module_load errors above this point. */
6100
6101 pjsip_media_type_init2(&rlmi_media_type, "application", "rlmi+xml");
6102
6103 pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &str_PUBLISH);
6104
6107 } else {
6108 struct stasis_subscription *sub;
6109
6113 }
6114
6119 ast_manager_register_xml("PJSIPShowResourceLists", EVENT_FLAG_SYSTEM,
6121
6123
6125 AST_TEST_REGISTER(complex_resource_tree);
6126 AST_TEST_REGISTER(bad_resource);
6127 AST_TEST_REGISTER(bad_branch);
6128 AST_TEST_REGISTER(duplicate_resource);
6129 AST_TEST_REGISTER(loop);
6130 AST_TEST_REGISTER(bad_event);
6131
6133}
6134
6135static int unload_module(void)
6136{
6138 AST_TEST_UNREGISTER(complex_resource_tree);
6139 AST_TEST_UNREGISTER(bad_resource);
6140 AST_TEST_UNREGISTER(bad_branch);
6141 AST_TEST_UNREGISTER(duplicate_resource);
6142 AST_TEST_UNREGISTER(loop);
6143 AST_TEST_UNREGISTER(bad_event);
6144
6146
6148
6151 ast_manager_unregister("PJSIPShowResourceLists");
6152
6154 if (sched) {
6156 }
6157
6158 return 0;
6159}
6160
6162 .support_level = AST_MODULE_SUPPORT_CORE,
6163 .load = load_module,
6164 .unload = unload_module,
6165 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
6166 .requires = "res_pjsip",
static struct ast_generator gen
const char * str
Definition: app_jack.c:150
char * text
Definition: app_queue.c:1809
enum queue_result id
Definition: app_queue.c:1808
#define var
Definition: ast_expr2f.c:605
if(!yyg->yy_init)
Definition: ast_expr2f.c:854
while(1)
Definition: ast_expr2f.c:880
char * strsep(char **str, const char *delims)
Asterisk main include file. File version handling, generic pbx functions.
int ast_shutdown_final(void)
Definition: asterisk.c:1878
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
#define ast_log
Definition: astobj2.c:42
#define ao2_link(container, obj)
Add an object to a container.
Definition: astobj2.h:1532
@ CMP_MATCH
Definition: astobj2.h:1027
@ CMP_STOP
Definition: astobj2.h:1028
#define OBJ_KEY
Definition: astobj2.h:1151
@ AO2_ALLOC_OPT_LOCK_MUTEX
Definition: astobj2.h:363
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container,...
Definition: astobj2.h:1693
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_unlink(container, obj)
Remove an object from a container.
Definition: astobj2.h:1578
#define ao2_find(container, arg, flags)
Definition: astobj2.h:1736
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
@ OBJ_NODATA
Definition: astobj2.h:1044
@ OBJ_UNLINK
Definition: astobj2.h:1039
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:409
#define ao2_container_alloc_hash(ao2_options, container_options, n_buckets, hash_fn, sort_fn, cmp_fn)
Allocate and initialize a hash container with the desired number of buckets.
Definition: astobj2.h:1303
CallerID (and other GR30) management and generation Includes code and algorithms from the Zapata libr...
char * ast_callerid_merge(char *buf, int bufsiz, const char *name, const char *num, const char *unknown)
Definition: callerid.c:1273
static const char type[]
Definition: chan_ooh323.c:109
static const char config[]
Definition: chan_ooh323.c:111
#define AST_MAX_EXTENSION
Definition: channel.h:134
Standard Command Line Interface.
#define CLI_SHOWUSAGE
Definition: cli.h:45
#define CLI_SUCCESS
Definition: cli.h:44
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
#define CLI_FAILURE
Definition: cli.h:46
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
static struct ast_cli_entry cli[]
Definition: codec_dahdi.c:265
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
#define CHARFLDSET(type, field)
A helper macro to pass the appropriate arguments to aco_option_register for OPT_CHAR_ARRAY_T.
@ OPT_UINT_T
Type for default option handler for unsigned integers.
@ OPT_NOOP_T
Type for a default handler that should do nothing.
@ OPT_BOOL_T
Type for default option handler for bools (ast_true/ast_false)
@ OPT_CHAR_ARRAY_T
Type for default option handler for character array strings.
@ OPT_YESNO_T
Type for default option handler for bools (ast_true/ast_false)
Asterisk datastore objects.
void ast_datastores_remove(struct ao2_container *datastores, const char *name)
Remove a data store from a container.
Definition: datastore.c:118
struct ao2_container * ast_datastores_alloc(void)
Allocate a specialized data stores container.
Definition: datastore.c:99
int ast_datastores_add(struct ao2_container *datastores, struct ast_datastore *datastore)
Add a data store to a container.
Definition: datastore.c:105
struct ast_datastore * ast_datastores_find(struct ao2_container *datastores, const char *name)
Find a data store in a container.
Definition: datastore.c:123
struct ast_datastore * ast_datastores_alloc_datastore(const struct ast_datastore_info *info, const char *uid)
Allocate a datastore for use with the datastores container.
Definition: datastore.c:142
char * end
Definition: eagi_proxy.c:73
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static const char name[]
Definition: format_mp3.c:68
static int regex(struct ast_channel *chan, const char *cmd, char *parse, char *buf, size_t len)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
void astman_send_listack(struct mansession *s, const struct message *m, char *msg, char *listflag)
Send ack in manager transaction to begin a list.
Definition: manager.c:2028
void astman_send_error(struct mansession *s, const struct message *m, char *error)
Send error in manager transaction.
Definition: manager.c:1986
void astman_send_list_complete_start(struct mansession *s, const struct message *m, const char *event_name, int count)
Start the list complete event.
Definition: manager.c:2064
struct stasis_topic * ast_manager_get_topic(void)
Get the Stasis Message Bus API topic for AMI.
Definition: manager.c:454
const char * astman_get_header(const struct message *m, char *var)
Get header from manager transaction.
Definition: manager.c:1647
void astman_send_list_complete_end(struct mansession *s)
End the list complete event.
Definition: manager.c:2072
void astman_append(struct mansession *s, const char *fmt,...)
Definition: manager.c:1907
int ast_manager_unregister(const char *action)
Unregister a registered manager command.
Definition: manager.c:7697
struct ast_flags ast_options
Definition: options.c:61
@ AST_OPT_FLAG_FULLY_BOOTED
Definition: options.h:58
struct ast_taskprocessor * ast_sip_get_distributor_serializer(pjsip_rx_data *rdata)
Determine the distributor serializer for the SIP message.
int ast_sip_push_task(struct ast_taskprocessor *serializer, int(*sip_task)(void *), void *task_data)
Pushes a task to SIP servants.
Definition: res_pjsip.c:2099
struct ast_taskprocessor * ast_sip_create_serializer(const char *name)
Create a new serializer for SIP tasks.
Definition: res_pjsip.c:2094
struct ast_sip_sched_task * ast_sip_schedule_task(struct ast_taskprocessor *serializer, int interval, ast_sip_task sip_task, const char *name, void *task_data, enum ast_sip_scheduler_task_flags flags)
Schedule a task to run in the res_pjsip thread pool.
void ast_sip_dialog_set_endpoint(pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint)
Set an endpoint on a SIP dialog so in-dialog requests do not undergo endpoint lookup.
int ast_sip_sched_task_cancel(struct ast_sip_sched_task *schtd)
Cancels the next invocation of a task.
int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen)
Gets the task name.
void ast_sip_dialog_set_serializer(pjsip_dialog *dlg, struct ast_taskprocessor *serializer)
Set a serializer on a SIP dialog so requests and responses are automatically serialized.
int ast_sip_push_task_wait_servant(struct ast_taskprocessor *serializer, int(*sip_task)(void *), void *task_data)
Push a task to SIP servants and wait for it to complete.
Definition: res_pjsip.c:2165
int ast_sip_push_task_wait_serializer(struct ast_taskprocessor *serializer, int(*sip_task)(void *), void *task_data)
Push a task to the serializer and wait for it to complete.
Definition: res_pjsip.c:2179
@ AST_SIP_SCHED_TASK_FIXED
Definition: res_pjsip.h:2194
@ AST_SIP_SCHED_TASK_DATA_AO2
Definition: res_pjsip.h:2217
#define ast_config_load(filename, flags)
Load a config file.
#define ast_variable_new(name, value, filename)
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:869
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
void ast_json_free(void *p)
Asterisk's custom JSON allocator. Exposed for use by unit tests.
Definition: json.c:52
struct ast_json * ast_json_object_create(void)
Create a new JSON object.
Definition: json.c:399
#define ast_json_dump_string(root)
Encode a JSON value to a compact string.
Definition: json.h:810
struct ast_json * ast_json_load_string(const char *input, struct ast_json_error *error)
Parse null terminated string into a JSON object or array.
Definition: json.c:567
struct ast_json * ast_json_ref(struct ast_json *value)
Increase refcount on value.
Definition: json.c:67
int ast_json_object_set(struct ast_json *object, const char *key, struct ast_json *value)
Set a field in a JSON object.
Definition: json.c:414
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
A set of macros to manage forward-linked lists.
#define AST_RWLIST_REMOVE_CURRENT
Definition: linkedlists.h:570
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:78
#define AST_RWLIST_TRAVERSE_SAFE_BEGIN
Definition: linkedlists.h:545
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:52
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:151
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized.
Definition: linkedlists.h:333
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_RWLIST_TRAVERSE_SAFE_END
Definition: linkedlists.h:617
#define AST_RWLIST_TRAVERSE
Definition: linkedlists.h:494
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
Definition: linkedlists.h:711
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:741
int ast_atomic_fetchadd_int(volatile int *p, int v)
Atomically add v to *p and return the previous value of *p.
Definition: lock.h:761
size_t current
Definition: main/cli.c:113
The AMI - Asterisk Manager Interface - is a TCP protocol created to manage Asterisk with third-party ...
#define EVENT_FLAG_SYSTEM
Definition: manager.h:75
#define ast_manager_register_xml(action, authority, func)
Register a manager callback using XML documentation to describe the manager.
Definition: manager.h:192
struct stasis_message_type * ast_manager_get_generic_type(void)
Get the stasis_message_type for generic messages.
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:331
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition: module.h:330
#define ast_module_unref(mod)
Release a reference to the module.
Definition: module.h:483
#define ast_module_ref(mod)
Hold a reference to the module.
Definition: module.h:457
#define ast_module_shutdown_ref(mod)
Prevent unload of the module before shutdown.
Definition: module.h:478
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODPRI_CHANNEL_DEPEND
Definition: module.h:340
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
Asterisk MWI API.
#define ast_publish_mwi_state(mailbox, context, new_msgs, old_msgs)
Publish a MWI state update via stasis.
Definition: mwi.h:378
def info(msg)
static char user[512]
struct stasis_forward * sub
Definition: res_corosync.c:240
int ast_sip_will_uri_survive_restart(pjsip_sip_uri *uri, struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
Definition: res_pjsip.c:525
const pj_str_t * ast_sip_pjsip_uri_get_username(pjsip_uri *uri)
Get the user portion of the pjsip_uri.
Definition: res_pjsip.c:3477
void ast_sip_unregister_service(pjsip_module *module)
Definition: res_pjsip.c:133
struct ast_sip_contact * ast_sip_location_retrieve_contact_from_aor_list(const char *aor_list)
Retrieve the first bound contact from a list of AORs.
Definition: location.c:304
int ast_sip_register_service(pjsip_module *module)
Register a SIP service in Asterisk.
Definition: res_pjsip.c:117
pjsip_dialog * ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint, const char *aor_name, const char *request_user)
General purpose method for creating a UAC dialog with an endpoint.
Definition: res_pjsip.c:964
enum ast_transport_monitor_reg ast_sip_transport_monitor_register_key(const char *transport_key, ast_transport_monitor_shutdown_cb cb, void *ao2_data)
Register a reliable transport shutdown monitor callback.
pjsip_endpoint * ast_sip_get_pjsip_endpoint(void)
Get a pointer to the PJSIP endpoint.
Definition: res_pjsip.c:520
#define AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(_transport, _dest)
Fill a buffer with a pjsip transport's remote ip address and port.
Definition: res_pjsip.h:91
#define AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(str)
Truncate the URI user field options string if enabled.
Definition: res_pjsip.h:3504
struct ast_sip_endpoint * ast_pjsip_rdata_get_endpoint(pjsip_rx_data *rdata)
Get the looked-up endpoint on an out-of dialog request or response.
int ast_sip_is_uri_sip_sips(pjsip_uri *uri)
Check whether a pjsip_uri is SIP/SIPS or not.
Definition: res_pjsip.c:3467
void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size)
Copy a pj_str_t into a standard character buffer.
Definition: res_pjsip.c:2201
void ast_sip_transport_monitor_unregister_all(ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches)
Unregister a transport shutdown monitor from all reliable transports.
pjsip_dialog * ast_sip_create_dialog_uas_locked(const struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pj_status_t *status)
General purpose method for creating a UAS dialog with an endpoint.
Definition: res_pjsip.c:1192
#define ast_sip_mod_data_set(pool, mod_data, id, key, val)
Utilizing a mod_data array for a given id, set the value associated with the given key.
Definition: res_pjsip.h:3099
#define IP6ADDR_COLON_PORT_BUFLEN
Definition: res_pjsip.h:83
#define ast_sip_mod_data_get(mod_data, id, key)
Using the dictionary stored in mod_data array at a given id, retrieve the value associated with the g...
Definition: res_pjsip.h:3067
int ast_sip_sorcery_object_to_ami(const void *obj, struct ast_str **buf)
Converts a sorcery object to a string of object properties.
struct ast_str * ast_sip_create_ami_event(const char *event, struct ast_sip_ami *ami)
Creates a string to store AMI event data in.
#define PJSIP_EXPIRES_NOT_SPECIFIED
Definition: res_pjsip.h:68
int ast_sip_add_header(pjsip_tx_data *tdata, const char *name, const char *value)
Add a header to an outbound SIP message.
Definition: res_pjsip.c:2008
struct ast_sorcery * ast_sip_get_sorcery(void)
Get a pointer to the SIP sorcery structure.
int ast_sip_is_content_type(pjsip_media_type *content_type, char *type, char *subtype)
Checks if the given content type matches type/subtype.
Definition: res_pjsip.c:2248
void ast_sip_transport_monitor_unregister_key(const char *transport_key, ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches)
Unregister a reliable transport shutdown monitor.
int ast_sip_create_rdata_with_contact(pjsip_rx_data *rdata, char *packet, const char *src_name, int src_port, char *transport_type, const char *local_name, int local_port, const char *contact_uri)
General purpose method for creating an rdata structure using specific information.
Definition: res_pjsip.c:1214
static struct ast_sorcery * sorcery
void ast_sip_sanitize_xml(const char *input, char *output, size_t len)
Replace offensive XML characters with XML entities.
Definition: presence_xml.c:29
#define AST_PJSIP_XML_PROLOG_LEN
Length of the XML prolog when printing presence or other XML in PJSIP.
pj_xml_node * ast_sip_presence_xml_create_node(pj_pool_t *pool, pj_xml_node *parent, const char *name)
Create XML node.
Definition: presence_xml.c:152
pj_xml_attr * ast_sip_presence_xml_create_attr(pj_pool_t *pool, pj_xml_node *node, const char *name, const char *value)
Create XML attribute.
Definition: presence_xml.c:140
#define CLI_SHOW_SUB_FORMAT_ENTRY
static void subscription_tree_destructor(void *obj)
static int cli_show_subscriptions_detail(struct sip_subscription_tree *sub_tree, struct cli_sub_parms *cli)
struct ast_sip_endpoint * ast_sip_subscription_get_endpoint(struct ast_sip_subscription *sub)
Get the endpoint that is associated with this subscription.
int(* on_subscription_t)(struct sip_subscription_tree *sub, void *arg)
void ast_sip_subscription_get_remote_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
Retrive the remote URI for this subscription.
static int subscription_persistence_recreate(void *obj, void *arg, int flags)
Callback function to perform the actual recreation of a subscription.
static int subscription_persistence_load(void *data)
Function which loads and recreates persisted subscriptions upon startup when the system is fully boot...
static struct subscription_persistence * subscription_persistence_create(struct sip_subscription_tree *sub_tree)
Function which creates initial persistence information of a subscription in sorcery.
int ast_sip_register_publish_handler(struct ast_sip_publish_handler *handler)
Register a publish handler.
static struct sip_subscription_tree * create_subscription_tree(const struct ast_sip_subscription_handler *handler, struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *resource, struct ast_sip_pubsub_body_generator *generator, struct resource_tree *tree, pj_status_t *dlg_status, struct subscription_persistence *persistence)
Create a subscription tree based on a resource tree.
static int cli_show_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
static int cli_complete_subscription_common(struct sip_subscription_tree *sub_tree, struct cli_sub_complete_parms *cli)
void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler *handler)
Unregister a subscription handler.
static pjsip_generic_string_hdr * generate_content_id_hdr(pj_pool_t *pool, const struct ast_sip_subscription *sub)
Create a Content-ID header.
static struct ast_cli_entry cli_commands[]
const char * ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub)
Get the body type used for this subscription.
static int generate_initial_notify(struct ast_sip_subscription *sub)
static int allocate_tdata_buffer(pjsip_tx_data *tdata)
Pre-allocate a buffer for the transmission.
static int sip_publication_respond(struct ast_sip_publication *pub, int status_code, pjsip_rx_data *rdata)
static struct ast_sip_subscription_handler * find_sub_handler_for_event_name(const char *event_name)
static int ami_show_resource_lists(struct mansession *s, const struct message *m)
void ast_sip_pubsub_unregister_body_generator(struct ast_sip_pubsub_body_generator *generator)
Unregister a body generator with the pubsub core.
static unsigned int cli_subscription_expiry(struct sip_subscription_tree *sub_tree)
int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *handler)
Register a subscription handler.
static void pubsub_on_client_refresh(pjsip_evsub *sub)
static int publication_hash_fn(const void *obj, const int flags)
static int resource_list_apply_handler(const struct ast_sorcery *sorcery, void *obj)
void ast_sip_pubsub_unregister_body_supplement(struct ast_sip_pubsub_body_supplement *supplement)
Unregister a body generator with the pubsub core.
static int sub_tree_subscription_terminate_cb(void *data)
#define CLI_SHOW_SUB_FORMAT_HEADER
static void build_body_part(pj_pool_t *pool, struct ast_sip_subscription *sub, struct body_part_list *parts, unsigned int use_full_state)
Create a multipart body part for a subscribed resource.
static int build_resource_tree(struct ast_sip_endpoint *endpoint, const struct ast_sip_subscription_handler *handler, const char *resource, struct resource_tree *tree, int has_eventlist_support, pjsip_rx_data *rdata)
Build a resource tree.
static int schedule_notification(struct sip_subscription_tree *sub_tree)
static int pubsub_on_refresh_timeout(void *userdata)
static int publication_cmp_fn(void *obj, void *arg, int flags)
static struct ast_sched_context * sched
Scheduler used for automatically expiring publications.
sip_persistence_update_type
@ SUBSCRIPTION_PERSISTENCE_CREATED
@ SUBSCRIPTION_PERSISTENCE_SEND_REQUEST
@ SUBSCRIPTION_PERSISTENCE_REFRESHED
@ SUBSCRIPTION_PERSISTENCE_RECREATED
static int exceptional_accept(const pj_str_t *accept)
Is the Accept header from the SUBSCRIBE in the list of exceptions?
static void pubsub_on_server_timeout(pjsip_evsub *sub)
sip_subscription_tree_state
The state of the subscription tree.
@ SIP_SUB_TREE_TERMINATE_PENDING
@ SIP_SUB_TREE_TERMINATED
@ SIP_SUB_TREE_NORMAL
@ SIP_SUB_TREE_TERMINATE_IN_PROGRESS
static int resource_event_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
static int cli_complete_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
void ast_sip_subscription_destroy(struct ast_sip_subscription *sub)
Alert the pubsub core that the subscription is ready for destruction.
static int serialized_pubsub_on_refresh_timeout(void *userdata)
void ast_sip_subscription_remove_datastore(struct ast_sip_subscription *subscription, const char *name)
Remove a subscription datastore from the subscription.
static int serialized_send_notify(void *userdata)
static void clean_sub_tree(pjsip_evsub *evsub)
Callback sequence for subscription terminate:
sip_publish_type
The types of PUBLISH messages defined in RFC 3903.
@ SIP_PUBLISH_REMOVE
Remove.
@ SIP_PUBLISH_REFRESH
Refresh.
@ SIP_PUBLISH_UNKNOWN
Unknown.
@ SIP_PUBLISH_MODIFY
Modify.
@ SIP_PUBLISH_INITIAL
Initial.
static void * subscription_persistence_alloc(const char *name)
Allocator for subscription persistence.
static int serialized_pubsub_on_client_refresh(void *userdata)
static int sub_persistence_recreate(void *obj)
const struct ast_json * ast_sip_subscription_get_persistence_data(const struct ast_sip_subscription *subscription)
Retrieve persistence data for a subscription.
#define MAX_REGEX_ERROR_LEN
static int cli_list_subscriptions_detail(struct sip_subscription_tree *sub_tree, struct cli_sub_parms *cli)
static int esc_etag_counter
static char * cli_complete_subscription_callid(struct ast_cli_args *a)
static struct ast_sip_subscription * create_virtual_subscriptions(const struct ast_sip_subscription_handler *handler, const char *resource, struct ast_sip_pubsub_body_generator *generator, struct sip_subscription_tree *tree, struct tree_node *current)
Create a tree of virtual subscriptions based on a resource tree node.
struct ast_datastore * ast_sip_subscription_alloc_datastore(const struct ast_datastore_info *info, const char *uid)
Alternative for ast_datastore_alloc()
static const pj_str_t str_event_name
static int format_ami_resource_lists(void *obj, void *arg, int flags)
#define DEFAULT_PUBLISH_EXPIRES
Default expiration time for PUBLISH if one is not specified.
static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
static void shutdown_subscriptions(struct ast_sip_subscription *sub)
static void * rlmi_clone_data(pj_pool_t *pool, const void *data, unsigned len)
static int ami_show_subscriptions_outbound(struct mansession *s, const struct message *m)
static int cli_show_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
static void remove_subscription(struct sip_subscription_tree *obj)
static int persistence_endpoint_struct2str(const void *obj, const intptr_t *args, char **buf)
struct ast_taskprocessor * ast_sip_subscription_get_serializer(struct ast_sip_subscription *sub)
Get the serializer for the subscription.
static pjsip_require_hdr * create_require_eventlist(pj_pool_t *pool)
Shortcut method to create a Require: eventlist header.
int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip_body_data *notify_data, int terminate)
Notify a SIP subscription of a state change.
int ast_sip_pubsub_register_body_supplement(struct ast_sip_pubsub_body_supplement *supplement)
Register a body generator with the pubsub core.
void ast_sip_publication_remove_datastore(struct ast_sip_publication *publication, const char *name)
Remove a publication datastore from the publication.
static void * publication_resource_alloc(const char *name)
Allocator for publication resource.
static int resource_endpoint_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
void ast_sip_unregister_publish_handler(struct ast_sip_publish_handler *handler)
Unregister a publish handler.
static int cli_show_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
static int ami_show_subscriptions_inbound(struct mansession *s, const struct message *m)
static int cli_list_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
int ast_sip_subscription_add_datastore(struct ast_sip_subscription *subscription, struct ast_datastore *datastore)
Add a datastore to a SIP subscription.
static int parse_simple_message_summary(char *body, struct simple_message_summary *summary)
static struct ast_sip_pubsub_body_generator * subscription_get_generator_from_rdata(pjsip_rx_data *rdata, const struct ast_sip_subscription_handler *handler)
Retrieve a body generator using the Accept header of an rdata message.
static char * sub_tree_state_description[]
#define AMI_SHOW_SUBSCRIPTIONS_INBOUND
#define NEW_SUBSCRIBE(notifier, endpoint, resource, rdata)
const char * ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub)
Get the body subtype used for this subscription.
#define AMI_SHOW_SUBSCRIPTIONS_OUTBOUND
const char * ast_sip_subscription_get_resource_name(struct ast_sip_subscription *sub)
Get the name of the subscribed resource.
static int rlmi_print_body(struct pjsip_msg_body *msg_body, char *buf, pj_size_t size)
static struct ast_sip_subscription * allocate_subscription(const struct ast_sip_subscription_handler *handler, const char *resource, const char *display_name, struct sip_subscription_tree *tree)
#define PUBLICATIONS_BUCKETS
Number of buckets for publications (on a per handler)
static int have_visited(const char *resource, struct resources *visited)
Determine if this resource has been visited already.
static pjsip_evsub_user pubsub_cb
static int sched_cb(const void *data)
#define RESOURCE_LIST_INIT_SIZE
static struct pjsip_module pubsub_module
static void sub_add_handler(struct ast_sip_subscription_handler *handler)
pjsip_sip_uri * ast_sip_subscription_get_sip_uri(struct ast_sip_subscription *sub)
Retrieve the local sip uri for this subscription.
void ast_sip_subscription_set_persistence_data(struct ast_sip_subscription *subscription, struct ast_json *persistence_data)
Set persistence data for a subscription.
static char * cli_show_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
const char * ast_sip_publication_get_event_configuration(const struct ast_sip_publication *pub)
Given a publication, get the configuration name for the event type in use.
static pjsip_msg_body * generate_list_body(pj_pool_t *pool, struct ast_sip_subscription *sub, unsigned int force_full_state)
Create a resource list body for NOTIFY requests.
static int cli_show_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state)
Send a NOTIFY request to a subscriber.
static void destroy_subscriptions(struct ast_sip_subscription *root)
int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator *generator)
Register a body generator with the pubsub core.
static void build_node_children(struct ast_sip_endpoint *endpoint, const struct ast_sip_subscription_handler *handler, struct resource_list *list, struct tree_node *parent, struct resources *visited, pjsip_rx_data *rdata)
Build child nodes for a given parent.
static int persistence_generator_data_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)
Opaque structure representing an RFC 3265 SIP subscription.
static void add_subscription(struct sip_subscription_tree *obj)
static void pubsub_on_rx_refresh(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)
Called whenever an in-dialog SUBSCRIBE is received.
static void free_body_parts(struct body_part_list *parts)
Destroy a list of body parts.
static int subscription_unreference_dialog(void *obj)
static struct ast_sip_pubsub_body_generator * find_body_generator_type_subtype_nolock(const char *type, const char *subtype)
static int ami_subscription_detail_inbound(struct sip_subscription_tree *sub_tree, void *arg)
static int publish_expire(const void *data)
static int persistence_expires_struct2str(const void *obj, const intptr_t *args, char **buf)
static pjsip_msg_body * create_multipart_body(pj_pool_t *pool)
Create and initialize the PJSIP multipart body structure for a resource list subscription.
static int ami_subscription_detail(struct sip_subscription_tree *sub_tree, struct ast_sip_ami *ami, const char *event)
static pj_bool_t pubsub_on_rx_mwi_notify_request(pjsip_rx_data *rdata)
static int apply_list_configuration(struct ast_sorcery *sorcery)
static void pubsub_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
PJSIP callback when underlying SIP subscription changes state.
pjsip_dialog * ast_sip_subscription_get_dialog(struct ast_sip_subscription *sub)
Get the pjsip dialog that is associated with this subscription.
static int list_item_to_str(const void *obj, const intptr_t *args, char **buf)
static int list_item_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
static int persistence_endpoint_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
int ast_sip_pubsub_generate_body_content(const char *type, const char *subtype, struct ast_sip_body_data *data, struct ast_str **str)
Generate body content for a PUBLISH or NOTIFY.
static struct ast_sip_pubsub_body_generator * find_body_generator(char accept[AST_SIP_MAX_ACCEPT][64], size_t num_accept, const char *body_type)
static int ast_sip_pubsub_has_eventlist_support(pjsip_rx_data *rdata)
Check if the rdata has a Supported header containing 'eventlist'.
static struct ast_sip_publish_handler * find_pub_handler(const char *event)
static void tree_node_destroy(struct tree_node *node)
Destructor for a tree node.
static int persistence_tag_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
struct ast_datastore * ast_sip_publication_get_datastore(struct ast_sip_publication *publication, const char *name)
Retrieve a publication datastore.
static void subscription_persistence_remove(struct sip_subscription_tree *sub_tree)
Function which removes persistence of a subscription from sorcery.
static int persistence_expires_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
static int publish_expire_callback(void *data)
static void resource_list_destructor(void *obj)
static int cmp_strings(char *s1, char *s2)
Compare strings for equality checking for NULL.
static void subscription_persistence_update(struct sip_subscription_tree *sub_tree, pjsip_rx_data *rdata, enum sip_persistence_update_type type)
Function which updates persistence information of a subscription in sorcery.
struct ast_sip_endpoint * ast_sip_publication_get_endpoint(struct ast_sip_publication *pub)
Given a publication, get the associated endpoint.
#define MOD_DATA_MSG
static pj_bool_t pubsub_on_rx_notify_request(pjsip_rx_data *rdata)
static int item_in_vector(const struct resource_list *list, const char *item)
static void destroy_subscription(struct ast_sip_subscription *sub)
static pjsip_msg_body * generate_notify_body(pj_pool_t *pool, struct ast_sip_subscription *root, unsigned int force_full_state)
Create the body for a NOTIFY request.
#define CLI_LIST_SUB_FORMAT_HEADER
static void sub_tree_transport_cb(void *data)
int ast_sip_pubsub_is_body_generator_registered(const char *type, const char *subtype)
Is a body generator registered for the given type/subtype.
static int load_module(void)
static int persistence_tag_struct2str(const void *obj, const intptr_t *args, char **buf)
static void subscription_persistence_event_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message)
Event callback which fires subscription persistence recreation when the system is fully booted.
static int for_each_subscription(on_subscription_t on_subscription, void *arg)
static void add_rlmi_resource(pj_pool_t *pool, pj_xml_node *rlmi, const pjsip_generic_string_hdr *cid, const char *resource_name, const pjsip_sip_uri *resource_uri, pjsip_evsub_state state)
Add a resource XML element to an RLMI body.
static void pubsub_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 resource_tree_destroy(struct resource_tree *tree)
Destroy a resource tree.
static int initial_notify_task(void *obj)
static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg)
static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
static int sip_subscription_accept(struct sip_subscription_tree *sub_tree, pjsip_rx_data *rdata, int response)
static int cli_show_subscription_common(struct sip_subscription_tree *sub_tree, struct cli_sub_parms *cli)
static struct ast_sip_pubsub_body_generator * find_body_generator_accept(const char *accept)
static int unload_module(void)
static void * resource_list_alloc(const char *name)
static int cmp_subscription_childrens(struct ast_sip_subscription *s1, struct ast_sip_subscription *s2)
compares the childrens of two ast_sip_subscription s1 and s2
static int persistence_generator_data_struct2str(const void *obj, const intptr_t *args, char **buf)
static char * cli_list_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
static void publication_resource_destroy(void *obj)
Destructor for publication resource.
static struct ast_sip_pubsub_body_generator * find_body_generator_type_subtype(const char *type, const char *subtype)
static enum sip_publish_type determine_sip_publish_type(pjsip_rx_data *rdata, pjsip_generic_string_hdr *etag_hdr, unsigned int *expires, int *entity_id)
static char * cli_show_subscription_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
const pjsip_method pjsip_publish_method
Defined method for PUBLISH.
static void set_state_terminated(struct ast_sip_subscription *sub)
#define MOD_DATA_PERSISTENCE
const char * ast_sip_publication_get_resource(const struct ast_sip_publication *pub)
Given a publication, get the resource the publication is to.
struct ao2_container * ast_sip_publication_get_datastores(const struct ast_sip_publication *publication)
Get the datastores container for a publication.
static int cli_list_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
static void publish_add_handler(struct ast_sip_publish_handler *handler)
static struct ast_sip_publication * publish_request_initial(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, struct ast_sip_publish_handler *handler)
static int ami_subscription_detail_outbound(struct sip_subscription_tree *sub_tree, void *arg)
static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree, struct ast_str **buf)
struct ast_sip_subscription * ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler, struct ast_sip_endpoint *endpoint, const char *resource)
Create a new ast_sip_subscription structure.
static struct tree_node * tree_node_alloc(const char *resource, struct resources *visited, unsigned int full_state, const char *display_name)
Allocate a tree node.
#define CLI_LIST_SUB_FORMAT_ENTRY
static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree, pjsip_tx_data *tdata)
static pjsip_media_type rlmi_media_type
static void subscription_persistence_destroy(void *obj)
Destructor for subscription persistence.
int ast_sip_subscription_is_terminated(const struct ast_sip_subscription *sub)
Get whether the subscription has been terminated or not.
static pjsip_multipart_part * build_rlmi_body(pj_pool_t *pool, struct ast_sip_subscription *sub, struct body_part_list *body_parts, unsigned int full_state)
Create an RLMI body part for a multipart resource list body.
void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
Retrieve the local URI for this subscription.
static int cli_complete_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
void * ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, const char *header)
Get a header value for a subscription.
int ast_sip_publication_add_datastore(struct ast_sip_publication *publication, struct ast_datastore *datastore)
Add a datastore to a SIP publication.
static const char * sip_subscription_roles_map[]
static struct body_part * allocate_body_part(pj_pool_t *pool, const struct ast_sip_subscription *sub)
Allocate and initialize a body part structure.
static struct ast_sip_publication * sip_create_publication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *resource, const char *event_configuration_name)
static struct resource_list * retrieve_resource_list(const char *resource, const char *event)
Helper function for retrieving a resource list for a given event.
const char * accept_exceptions[]
Accept headers that are exceptions to the rule.
static void publication_destroy_fn(void *obj)
Internal destructor for publications.
static struct sip_subscription_tree * allocate_subscription_tree(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
static struct ast_sip_subscription_handler * subscription_get_handler_from_rdata(pjsip_rx_data *rdata, const char *endpoint)
Retrieve a handler using the Event header of an rdata message.
#define DEFAULT_EXPIRES
Default expiration for subscriptions.
static int destroy_subscriptions_task(void *obj)
struct ao2_container * ast_sip_subscription_get_datastores(const struct ast_sip_subscription *subscription)
Get the datastores container for a subscription.
struct ast_datastore * ast_sip_subscription_get_datastore(struct ast_sip_subscription *subscription, const char *name)
Retrieve a subscription datastore.
@ AST_SIP_PUBLISH_STATE_ACTIVE
@ AST_SIP_PUBLISH_STATE_TERMINATED
@ AST_SIP_PUBLISH_STATE_INITIALIZED
#define AST_SIP_MAX_ACCEPT
#define AST_SIP_DEVICE_FEATURE_SYNC_DATA
ast_sip_subscription_role
Role for the subscription that is being created.
@ AST_SIP_SUBSCRIBER
@ AST_SIP_NOTIFIER
#define NULL
Definition: resample.c:96
Scheduler Routines (derived from cheops)
#define AST_SCHED_DEL_UNREF(sched, id, refcall)
schedule task to get deleted and call unref function
Definition: sched.h:82
#define AST_SCHED_REPLACE_UNREF(id, sched, when, callback, data, unrefcall, addfailcall, refcall)
Definition: sched.h:152
int ast_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) attribute_warn_unused_result
Adds a scheduled event.
Definition: sched.c:567
void ast_sched_context_destroy(struct ast_sched_context *c)
destroys a schedule context
Definition: sched.c:271
int ast_sched_start_thread(struct ast_sched_context *con)
Start a thread for processing scheduler entries.
Definition: sched.c:197
struct ast_sched_context * ast_sched_context_create(void)
Create a scheduler context.
Definition: sched.c:238
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
int ast_sorcery_object_fields_register(struct ast_sorcery *sorcery, const char *type, const char *regex, aco_option_handler config_handler, sorcery_fields_handler sorcery_handler)
Register a regex for multiple fields within an object.
Definition: sorcery.c:1160
@ AST_RETRIEVE_FLAG_MULTIPLE
Return all matching objects.
Definition: sorcery.h:120
@ AST_RETRIEVE_FLAG_ALL
Perform no matching, return all objects.
Definition: sorcery.h:123
int ast_sorcery_create(const struct ast_sorcery *sorcery, void *object)
Create and potentially persist an object using an available wizard.
Definition: sorcery.c:2062
void * ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *type, const char *id)
Retrieve an object using its unique identifier.
Definition: sorcery.c:1853
#define ast_sorcery_object_register(sorcery, type, alloc, transform, apply)
Register an object type.
Definition: sorcery.h:837
#define ast_sorcery_object_field_register_custom(sorcery, type, name, default_val, config_handler, sorcery_handler, multiple_handler, flags,...)
Register a field within an object with custom handlers.
Definition: sorcery.h:1005
void * ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor)
Allocate a generic sorcery capable object.
Definition: sorcery.c:1728
#define ast_sorcery_apply_config(sorcery, name)
Definition: sorcery.h:455
#define ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, flags,...)
Register a field within an object.
Definition: sorcery.h:955
void * ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, const char *id)
Allocate an object.
Definition: sorcery.c:1744
int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object)
Update an object.
Definition: sorcery.c:2150
#define ast_sorcery_apply_default(sorcery, type, name, data)
Definition: sorcery.h:476
void ast_sorcery_reload_object(const struct ast_sorcery *sorcery, const char *type)
Inform any wizards of a specific object type to reload persistent objects.
Definition: sorcery.c:1442
void * ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const char *type, unsigned int flags, struct ast_variable *fields)
Retrieve an object or multiple objects using specific fields.
Definition: sorcery.c:1897
int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object)
Delete an object.
Definition: sorcery.c:2238
struct stasis_message_type * stasis_message_type(const struct stasis_message *msg)
Get the message type for a stasis_message.
@ STASIS_SUBSCRIPTION_FILTER_SELECTIVE
Definition: stasis.h:297
int stasis_subscription_accept_message_type(struct stasis_subscription *subscription, const struct stasis_message_type *type)
Indicate to a subscription that we are interested in a message type.
Definition: stasis.c:1050
int stasis_subscription_set_filter(struct stasis_subscription *subscription, enum stasis_subscription_message_filter filter)
Set the message type filtering level on a subscription.
Definition: stasis.c:1104
#define stasis_subscribe_pool(topic, callback, data)
Definition: stasis.h:680
void * stasis_message_data(const struct stasis_message *msg)
Get the data contained in a message.
struct stasis_subscription * stasis_unsubscribe(struct stasis_subscription *subscription)
Cancel a subscription.
Definition: stasis.c:998
char * ast_str_truncate(struct ast_str *buf, ssize_t len)
Truncates the enclosed string to the given length.
Definition: strings.h:786
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
static force_inline char * ast_str_to_lower(char *str)
Convert a string to all lower-case.
Definition: strings.h:1321
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
#define S_COR(a, b, c)
returns the equivalent of logic or for strings, with an additional boolean check: second one if not e...
Definition: strings.h:87
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
void ast_str_update(struct ast_str *buf)
Update the length of the buffer, after using ast_str merely as a buffer.
Definition: strings.h:703
char * ast_read_line_from_buffer(char **buffer)
Read lines from a string buffer.
Definition: strings.c:385
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
char * ast_generate_random_string(char *buf, size_t size)
Create a pseudo-random string of a fixed length.
Definition: strings.c:226
int ast_get_timeval(const char *src, struct timeval *tv, struct timeval _default, int *consumed)
Parse a time (float) string.
Definition: utils.c:2419
Generic container type.
descriptor for a cli entry.
Definition: cli.h:171
char * command
Definition: cli.h:186
const char * usage
Definition: cli.h:177
Structure for a data store type.
Definition: datastore.h:31
Structure for a data store object.
Definition: datastore.h:64
const char * uid
Definition: datastore.h:65
Structure used to handle boolean flags.
Definition: utils.h:199
unsigned int flags
Definition: utils.h:200
JSON parsing error information.
Definition: json.h:887
struct ast_json * json
Definition: json.h:1083
Abstract JSON element (object, array, string, int, ...).
struct ast_module * self
Definition: module.h:356
struct ast_party_name name
Subscriber name.
Definition: channel.h:342
struct ast_party_number number
Subscriber phone number.
Definition: channel.h:344
unsigned char valid
TRUE if the name information is valid/present.
Definition: channel.h:281
char * str
Subscriber name (Malloced)
Definition: channel.h:266
unsigned char valid
TRUE if the number information is valid/present.
Definition: channel.h:299
char * str
Subscriber phone number (Malloced)
Definition: channel.h:293
AMI variable container.
Definition: res_pjsip.h:3200
struct mansession * s
Definition: res_pjsip.h:3202
void * arg
Definition: res_pjsip.h:3208
const struct message * m
Definition: res_pjsip.h:3204
Data used to create bodies for NOTIFY/PUBLISH requests.
const char * body_type
Contact associated with an address of record.
Definition: res_pjsip.h:390
const ast_string_field uri
Definition: res_pjsip.h:412
Party identification options for endpoints.
Definition: res_pjsip.h:859
struct ast_party_id self
Definition: res_pjsip.h:860
An entity with which Asterisk communicates.
Definition: res_pjsip.h:1051
struct ast_sip_endpoint_id_configuration id
Definition: res_pjsip.h:1090
const ast_string_field aors
Definition: res_pjsip.h:1080
int(* new_subscribe)(struct ast_sip_endpoint *endpoint, const char *resource)
Called when a SUBSCRIBE arrives attempting to establish a new subscription.
int(* refresh_subscribe)(struct ast_sip_subscription *sub, pjsip_rx_data *rdata)
Called when a SUBSCRIBE arrives for an already active subscription.
int(* notify_created)(struct ast_sip_subscription *sub, pjsip_tx_data *tdata)
Optional callback to execute before sending outgoing NOTIFY requests. Because res_pjsip_pubsub create...
Structure representing a publication resource.
SORCERY_OBJECT(details)
Sorcery object details.
char * endpoint
Optional name of an endpoint that is only allowed to publish to this resource.
struct ast_variable * events
Mapping for event types to configuration.
Structure representing a SIP publication.
unsigned int expires
Expiration time of the publication.
struct ast_sip_publish_handler * handler
Handler for this publication.
struct ast_sip_endpoint * endpoint
The endpoint with which the subscription is communicating.
int sched_id
Scheduled item for expiration of publication.
char * event_configuration_name
The name of the event type configuration.
char data[0]
Data containing the above.
struct ao2_container * datastores
char * resource
The resource the publication is to.
int entity_tag
Entity tag for the publication.
Callbacks that publication handlers will define.
struct ast_sip_publish_handler * next
struct ao2_container * publications
Publications.
const char * event_name
The name of the event this handler deals with.
int(* publication_state_change)(struct ast_sip_publication *pub, pjsip_msg_body *body, enum ast_sip_publish_state state)
Published resource has changed states.
Pubsub body generator.
void(* to_string)(void *body, struct ast_str **str)
Convert the body to a string.
struct ast_sip_pubsub_body_generator::@263 list
void(* destroy_body)(void *body)
Deallocate resources created for the body.
const char * type
Content type In "plain/text", "plain" is the type.
const char * subtype
Content subtype In "plain/text", "text" is the subtype.
void *(* allocate_body)(void *data)
allocate body structure.
int(* generate_body_content)(void *body, void *data)
Add content to the body of a SIP request.
int(* supplement_body)(void *body, void *data)
Add additional content to a SIP request body.
const char * type
Content type In "plain/text", "plain" is the type.
struct ast_sip_pubsub_body_supplement::@264 list
const char * subtype
Content subtype In "plain/text", "text" is the subtype.
struct ast_sip_notifier * notifier
void(* to_ami)(struct ast_sip_subscription *sub, struct ast_str **buf)
Converts the subscriber to AMI.
struct ast_sip_subscription_handler * next
const char * accept[AST_SIP_MAX_ACCEPT]
Structure representing a "virtual" SIP subscription.
struct sip_subscription_tree * tree
struct ast_sip_pubsub_body_generator * body_generator
const struct ast_sip_subscription_handler * handler
struct ast_json * persistence_data
struct ast_str * body_text
struct ao2_container * datastores
pjsip_evsub_state subscription_state
struct ast_sip_subscription::@466 children
Full structure for sorcery.
Definition: sorcery.c:230
Support for dynamic strings.
Definition: strings.h:623
A ast_taskprocessor structure is a singleton by name.
Definition: taskprocessor.c:69
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
Type declaration for container of body part structures.
A multipart body part and meta-information.
pjsip_evsub_state state
const char * display_name
const char * resource
pjsip_generic_string_hdr * cid
pjsip_sip_uri * uri
pjsip_multipart_part * part
struct ast_cli_args * a
struct ast_str * buf
struct ast_cli_entry * e
struct ast_cli_args * a
Definition: astman.c:222
struct sip_subscription_tree * sub_tree
In case you didn't read that giant block of text above the mansession_session struct,...
Definition: manager.c:327
Definition: test_heap.c:38
struct subscription_persistence * persistence
Resource list configuration item.
SORCERY_OBJECT(details)
unsigned int full_state
struct resources items
unsigned int notification_batch_interval
unsigned int resource_display_name
A resource tree.
struct tree_node * root
unsigned int notification_batch_interval
A vector of strings commonly used throughout this module.
Definition: sched.c:76
struct sched_id * sched_id
Definition: sched.c:79
char message_account[PJSIP_MAX_URL_SIZE]
A tree of SIP subscriptions.
struct subscription_persistence * persistence
struct ast_sip_endpoint * endpoint
char transport_key[IP6ADDR_COLON_PORT_BUFLEN]
enum ast_sip_subscription_role role
struct sip_subscription_tree * next
unsigned int send_scheduled_notify
enum sip_subscription_tree_state state
unsigned int generate_initial_notify
struct ast_sip_subscription * root
struct ast_taskprocessor * serializer
unsigned int notification_batch_interval
struct ast_sip_sched_task * expiration_task
Structure used for persisting an inbound subscription.
struct ast_json * generator_data
char src_name[PJ_INET6_ADDRSTRLEN]
char contact_uri[PJSIP_MAX_URL_SIZE]
char packet[PJSIP_MAX_PKT_LEN]
char local_name[PJ_INET6_ADDRSTRLEN]
A node for a resource tree.
char * display_name
unsigned int full_state
struct tree_node::@467 children
char resource[0]
structure to hold users read from users.conf
int value
Definition: syslog.c:37
An API for managing task processing threads that can be shared across modules.
void * ast_taskprocessor_unreference(struct ast_taskprocessor *tps)
Unreference the specified taskprocessor and its reference count will decrement.
void ast_taskprocessor_build_name(char *buf, unsigned int size, const char *format,...)
Build a taskprocessor name with a sequence number on the end.
#define AST_TASKPROCESSOR_MAX_NAME
Suggested maximum taskprocessor name length (less null terminator).
Definition: taskprocessor.h:61
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
#define ast_test_suite_event_notify(s, f,...)
Definition: test.h:189
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_FAIL
Definition: test.h:196
@ AST_TEST_NOT_RUN
Definition: test.h:194
static void handler(const char *name, int response_code, struct ast_variable *get_params, struct ast_variable *path_vars, struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
Definition: test_ari.c:59
static struct aco_type item
Definition: test_config.c:1463
const char * args
static struct test_val a
static void test_handler(void *data, const char *app_name, struct ast_json *message)
int ast_time_t_to_string(time_t time, char *buf, size_t length)
Converts to a string representation of a time_t as decimal seconds since the epoch....
Definition: time.c:152
struct timeval ast_samp2tv(unsigned int _nsamp, unsigned int _rate)
Returns a timeval corresponding to the duration of n samples at rate r. Useful to convert samples to ...
Definition: time.h:282
struct timeval ast_tvadd(struct timeval a, struct timeval b)
Returns the sum of two timevals a + b.
Definition: extconf.c:2282
#define AST_TIME_T_LEN
Definition: time.h:45
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
struct timeval ast_tv(ast_time_t sec, ast_suseconds_t usec)
Returns a timeval from sec, usec.
Definition: time.h:235
int error(const char *format,...)
Definition: utils/frame.c:999
#define ast_test_flag(p, flag)
Definition: utils.h:63
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
#define ast_assert(a)
Definition: utils.h:739
#define MIN(a, b)
Definition: utils.h:231
#define ARRAY_LEN(a)
Definition: utils.h:666
Universally unique identifier support.
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
#define AST_VECTOR(name, type)
Define a vector structure.
Definition: vector.h:44
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680