Asterisk - The Open Source Telephony Project GIT-master-5467495
Loading...
Searching...
No Matches
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 */
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 */
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 */
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
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 if (sub->tree) {
1450 /* Clear tree before cleanup to avoid re-entrant destruction */
1451 struct sip_subscription_tree *tree = sub->tree;
1452 sub->tree=NULL;
1453 ao2_cleanup(tree);
1454 }
1455 ast_free(sub);
1456}
1457
1459{
1460 int i;
1461
1462 if (!root) {
1463 return;
1464 }
1465
1466 for (i = 0; i < AST_VECTOR_SIZE(&root->children); ++i) {
1467 struct ast_sip_subscription *child;
1468
1469 child = AST_VECTOR_GET(&root->children, i);
1470 destroy_subscriptions(child);
1471 }
1472
1474}
1475
1477 const char *resource, const char *display_name, struct sip_subscription_tree *tree)
1478{
1479 struct ast_sip_subscription *sub;
1480 pjsip_msg *msg;
1481 pjsip_sip_uri *request_uri;
1482
1484 if (!msg) {
1485 ast_log(LOG_ERROR, "No dialog message saved for SIP subscription. Cannot allocate subscription for resource %s\n", resource);
1486 return NULL;
1487 }
1488
1489 sub = ast_calloc(1, sizeof(*sub) + strlen(resource) + 1);
1490 if (!sub) {
1491 return NULL;
1492 }
1493 strcpy(sub->resource, resource); /* Safe */
1494
1495 sub->display_name = ast_strdup(display_name);
1496
1497 sub->datastores = ast_datastores_alloc();
1498 if (!sub->datastores) {
1500 return NULL;
1501 }
1502
1503 sub->body_text = ast_str_create(128);
1504 if (!sub->body_text) {
1506 return NULL;
1507 }
1508
1509 sub->uri = pjsip_sip_uri_create(tree->dlg->pool, PJ_FALSE);
1510 request_uri = pjsip_uri_get_uri(msg->line.req.uri);
1511 pjsip_sip_uri_assign(tree->dlg->pool, sub->uri, request_uri);
1512 pj_strdup2(tree->dlg->pool, &sub->uri->user, resource);
1513
1514 /* If there is any persistence information available for this subscription that was persisted
1515 * then make it available so that the NOTIFY has the correct state.
1516 */
1517
1520 }
1521
1522 sub->handler = handler;
1523 sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE;
1524 sub->tree = ao2_bump(tree);
1525
1526 return sub;
1527}
1528
1529/*!
1530 * \brief Create a tree of virtual subscriptions based on a resource tree node.
1531 *
1532 * \param handler The handler to supply to leaf subscriptions.
1533 * \param resource The requested resource for this subscription.
1534 * \param generator Body generator to use for leaf subscriptions.
1535 * \param tree The root of the subscription tree.
1536 * \param current The tree node that corresponds to the subscription being created.
1537 */
1539 const char *resource, struct ast_sip_pubsub_body_generator *generator,
1541{
1542 int i;
1543 struct ast_sip_subscription *sub;
1544
1546 if (!sub) {
1547 return NULL;
1548 }
1549
1550 sub->full_state = current->full_state;
1551 sub->body_generator = generator;
1552 AST_VECTOR_INIT(&sub->children, AST_VECTOR_SIZE(&current->children));
1553
1554 for (i = 0; i < AST_VECTOR_SIZE(&current->children); ++i) {
1555 struct ast_sip_subscription *child;
1556 struct tree_node *child_node = AST_VECTOR_GET(&current->children, i);
1557
1558 child = create_virtual_subscriptions(handler, child_node->resource, generator,
1559 tree, child_node);
1560
1561 if (!child) {
1562 ast_debug(1, "Child subscription to resource %s could not be created\n",
1563 child_node->resource);
1564 continue;
1565 }
1566
1567 if (AST_VECTOR_APPEND(&sub->children, child)) {
1568 ast_debug(1, "Child subscription to resource %s could not be appended\n",
1569 child_node->resource);
1570 destroy_subscriptions(child);
1571 }
1572 }
1573
1574 return sub;
1575}
1576
1578{
1579 int i;
1580
1581 if (!sub) {
1582 return;
1583 }
1584
1585 if (AST_VECTOR_SIZE(&sub->children) > 0) {
1586 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
1588 }
1589 return;
1590 }
1591
1592 /* We notify subscription shutdown only on the tree leaves. */
1593 if (sub->handler->subscription_shutdown) {
1594 sub->handler->subscription_shutdown(sub);
1595 }
1596}
1597
1599{
1600 struct sip_subscription_tree *sub_tree = obj;
1601
1602 /* This is why we keep the dialog on the subscription. When the subscription
1603 * is destroyed, there is no guarantee that the underlying dialog is ready
1604 * to be destroyed. Furthermore, there's no guarantee in the opposite direction
1605 * either. The dialog could be destroyed before our subscription is. We fix
1606 * this problem by keeping a reference to the dialog until it is time to
1607 * destroy the subscription. We need to have the dialog available when the
1608 * subscription is destroyed so that we can guarantee that our attempt to
1609 * remove the serializer will be successful.
1610 */
1611 pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module);
1612 sub_tree->dlg = NULL;
1613
1614 return 0;
1615}
1616
1617static void subscription_tree_destructor(void *obj)
1618{
1619 struct sip_subscription_tree *sub_tree = obj;
1620
1621 ast_debug(3, "Destroying subscription tree %p '%s->%s'\n",
1622 sub_tree,
1623 sub_tree->endpoint ? ast_sorcery_object_get_id(sub_tree->endpoint) : "Unknown",
1624 sub_tree->root ? sub_tree->root->resource : "Unknown");
1625
1626 destroy_subscriptions(sub_tree->root);
1627
1628 if (sub_tree->dlg) {
1631 }
1632
1633 ao2_cleanup(sub_tree->endpoint);
1634
1637}
1638
1640{
1641 ast_debug(3, "Removing subscription %p '%s->%s' reference to subscription tree %p\n",
1642 sub, ast_sorcery_object_get_id(sub->tree->endpoint), sub->resource, sub->tree);
1643 if (sub->tree) {
1644 /* Clear tree before cleanup to avoid re-entrant destruction */
1645 struct sip_subscription_tree *tree = sub->tree;
1646 sub->tree = NULL;
1647 ao2_cleanup(tree);
1648 }
1649}
1650
1651static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg)
1652{
1653 sub_tree->dlg = dlg;
1656 pjsip_evsub_set_mod_data(sub_tree->evsub, pubsub_module.id, sub_tree);
1657 pjsip_dlg_inc_session(dlg, &pubsub_module);
1658}
1659
1661{
1662 struct sip_subscription_tree *sub_tree;
1663
1664 sub_tree = ao2_alloc(sizeof *sub_tree, subscription_tree_destructor);
1665 if (!sub_tree) {
1666 return NULL;
1667 }
1668
1670
1671 if (rdata) {
1672 /*
1673 * We must continue using the serializer that the original
1674 * SUBSCRIBE came in on for the dialog. There may be
1675 * retransmissions already enqueued in the original
1676 * serializer that can result in reentrancy and message
1677 * sequencing problems.
1678 */
1680 } else {
1681 char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
1682
1683 /* Create name with seq number appended. */
1684 ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/pubsub/%s",
1686
1687 sub_tree->serializer = ast_sip_create_serializer(tps_name);
1688 }
1689 if (!sub_tree->serializer) {
1690 ao2_ref(sub_tree, -1);
1691 return NULL;
1692 }
1693
1694 sub_tree->endpoint = ao2_bump(endpoint);
1695 sub_tree->notify_sched_id = -1;
1696
1697 return sub_tree;
1698}
1699
1700/*!
1701 * \brief Create a subscription tree based on a resource tree.
1702 *
1703 * Using the previously-determined valid resources in the provided resource tree,
1704 * a corresponding tree of ast_sip_subscriptions are created. The root of the
1705 * subscription tree is a real subscription, and the rest in the tree are
1706 * virtual subscriptions.
1707 *
1708 * \param handler The handler to use for leaf subscriptions
1709 * \param endpoint The endpoint that sent the SUBSCRIBE request
1710 * \param rdata The SUBSCRIBE content
1711 * \param resource The requested resource in the SUBSCRIBE request
1712 * \param generator The body generator to use in leaf subscriptions
1713 * \param tree The resource tree on which the subscription tree is based
1714 * \param[out] dlg_status The result of attempting to create a dialog
1715 * \param persistence
1716 *
1717 * \retval NULL Could not create the subscription tree
1718 * \retval non-NULL The root of the created subscription tree
1719 */
1720
1722 struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *resource,
1723 struct ast_sip_pubsub_body_generator *generator, struct resource_tree *tree,
1724 pj_status_t *dlg_status, struct subscription_persistence *persistence)
1725{
1726 struct sip_subscription_tree *sub_tree;
1727 pjsip_dialog *dlg;
1728
1729 sub_tree = allocate_subscription_tree(endpoint, rdata);
1730 if (!sub_tree) {
1731 *dlg_status = PJ_ENOMEM;
1732 return NULL;
1733 }
1734 sub_tree->role = AST_SIP_NOTIFIER;
1735
1736 dlg = ast_sip_create_dialog_uas_locked(endpoint, rdata, dlg_status);
1737 if (!dlg) {
1738 if (*dlg_status != PJ_EEXISTS) {
1739 ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
1740 }
1741 ao2_ref(sub_tree, -1);
1742 return NULL;
1743 }
1744
1745 persistence = ast_sip_mod_data_get(rdata->endpt_info.mod_data,
1747 if (persistence) {
1748 /* Update the created dialog with the persisted information */
1749 pjsip_ua_unregister_dlg(pjsip_ua_instance(), dlg);
1750 pj_strdup2(dlg->pool, &dlg->local.info->tag, persistence->tag);
1751 dlg->local.tag_hval = pj_hash_calc_tolower(0, NULL, &dlg->local.info->tag);
1752 pjsip_ua_register_dlg(pjsip_ua_instance(), dlg);
1753 dlg->local.cseq = persistence->cseq;
1754 }
1755
1756 pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub_tree->evsub);
1757
1758 subscription_setup_dialog(sub_tree, dlg);
1759
1760 /*
1761 * The evsub and subscription setup both add dialog refs, so the dialog ref that
1762 * was added when the dialog was created (see ast_sip_create_dialog_uas_lock) can
1763 * now be removed. The lock should no longer be needed so can be removed too.
1764 */
1765 pjsip_dlg_dec_lock(dlg);
1766
1767#ifdef HAVE_PJSIP_EVSUB_GRP_LOCK
1768 pjsip_evsub_add_ref(sub_tree->evsub);
1769#endif
1770
1772 pjsip_msg_clone(dlg->pool, rdata->msg_info.msg));
1773
1775
1776 /* Persistence information needs to be available for all the subscriptions */
1777 sub_tree->persistence = ao2_bump(persistence);
1778
1779 sub_tree->root = create_virtual_subscriptions(handler, resource, generator, sub_tree, tree->root);
1780 if (AST_VECTOR_SIZE(&sub_tree->root->children) > 0) {
1781 sub_tree->is_list = 1;
1782 }
1783
1784 add_subscription(sub_tree);
1785
1786 return sub_tree;
1787}
1788
1789/*! Wrapper structure for initial_notify_task */
1794
1795static int initial_notify_task(void *obj);
1796static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state);
1797
1798/*! Persistent subscription recreation continuation under distributor serializer data */
1803
1804/*!
1805 * \internal
1806 * \brief subscription_persistence_recreate continuation under distributor serializer.
1807 * \since 13.10.0
1808 *
1809 * \retval 0 on success.
1810 * \retval -1 on error.
1811 */
1812static int sub_persistence_recreate(void *obj)
1813{
1814 struct persistence_recreate_data *recreate_data = obj;
1815 struct subscription_persistence *persistence = recreate_data->persistence;
1816 pjsip_rx_data *rdata = recreate_data->rdata;
1817 struct ast_sip_endpoint *endpoint;
1818 struct sip_subscription_tree *sub_tree;
1819 struct ast_sip_pubsub_body_generator *generator;
1821 char *resource;
1822 size_t resource_size;
1823 int resp;
1824 struct resource_tree tree;
1825 pjsip_expires_hdr *expires_header;
1826 int64_t expires;
1827 const pj_str_t *user;
1828
1829 user = ast_sip_pjsip_uri_get_username(rdata->msg_info.msg->line.req.uri);
1830 resource_size = pj_strlen(user) + 1;
1831 resource = ast_alloca(resource_size);
1832 ast_copy_pj_str(resource, user, resource_size);
1833
1834 /*
1835 * We may want to match without any user options getting
1836 * in the way.
1837 */
1839
1841 if (!handler || !handler->notifier) {
1842 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not get subscription handler.\n",
1843 persistence->endpoint);
1845 return 0;
1846 }
1847
1849 if (!generator) {
1850 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Body generator not available.\n",
1851 persistence->endpoint);
1853 return 0;
1854 }
1855
1856 ast_sip_mod_data_set(rdata->tp_info.pool, rdata->endpt_info.mod_data,
1857 pubsub_module.id, MOD_DATA_PERSISTENCE, persistence);
1858
1859 /* Getting the endpoint may take some time that can affect the expiration. */
1860 endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",
1861 persistence->endpoint);
1862 if (!endpoint) {
1863 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The endpoint was not found\n",
1864 persistence->endpoint);
1866 return 0;
1867 }
1868
1869 /* Update the expiration header with the new expiration */
1870 expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES,
1871 rdata->msg_info.msg->hdr.next);
1872 if (!expires_header) {
1873 expires_header = pjsip_expires_hdr_create(rdata->tp_info.pool, 0);
1874 if (!expires_header) {
1875 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not update expires header.\n",
1876 persistence->endpoint);
1878 ao2_ref(endpoint, -1);
1879 return 0;
1880 }
1881 pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr *) expires_header);
1882 }
1883
1884 expires = (ast_tvdiff_ms(persistence->expires, ast_tvnow()) / 1000);
1885 if (expires <= 0) {
1886 /* The subscription expired since we started recreating the subscription. */
1887 ast_debug(3, "Expired subscription retrived from persistent store '%s' %s\n",
1888 persistence->endpoint, persistence->tag);
1890 ao2_ref(endpoint, -1);
1891 return 0;
1892 }
1893 expires_header->ivalue = expires;
1894
1895 memset(&tree, 0, sizeof(tree));
1896 resp = build_resource_tree(endpoint, handler, resource, &tree,
1898 if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
1899 pj_status_t dlg_status;
1900
1901 sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator,
1902 &tree, &dlg_status, persistence);
1903 if (!sub_tree) {
1904 if (dlg_status != PJ_EEXISTS) {
1905 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not create subscription tree.\n",
1906 persistence->endpoint);
1908 }
1909 } else {
1910 struct initial_notify_data *ind = ast_malloc(sizeof(*ind));
1911
1912 if (!ind) {
1913 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
1914 goto error;
1915 }
1916
1917 ind->sub_tree = ao2_bump(sub_tree);
1918 ind->expires = expires_header->ivalue;
1919
1922 /* Could not send initial subscribe NOTIFY */
1923 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
1924 ao2_ref(sub_tree, -1);
1925 ast_free(ind);
1926 }
1927 }
1928 } else {
1930 }
1931
1932error:
1933 resource_tree_destroy(&tree);
1934 ao2_ref(endpoint, -1);
1935
1936 return 0;
1937}
1938
1939/*! \brief Callback function to perform the actual recreation of a subscription */
1940static int subscription_persistence_recreate(void *obj, void *arg, int flags)
1941{
1942 struct subscription_persistence *persistence = obj;
1943 pj_pool_t *pool = arg;
1945 pjsip_rx_data rdata;
1946 struct persistence_recreate_data recreate_data;
1947
1948 /* If this subscription used a reliable transport it can't be reestablished so remove it */
1950 ast_debug(3, "Deleting subscription marked as 'prune' from persistent store '%s' %s\n",
1953 return 0;
1954 }
1955
1956 /* If this subscription has already expired remove it */
1958 ast_debug(3, "Expired subscription retrived from persistent store '%s' %s\n",
1961 return 0;
1962 }
1963
1964 memset(&rdata, 0, sizeof(rdata));
1965 pj_pool_reset(pool);
1966 rdata.tp_info.pool = pool;
1967
1971 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The message could not be parsed\n",
1974 return 0;
1975 }
1976
1977 if (rdata.msg_info.msg->type != PJSIP_REQUEST_MSG) {
1978 ast_log(LOG_NOTICE, "Failed recreating '%s' subscription: Stored a SIP response instead of a request.\n",
1981 return 0;
1982 }
1983
1984 /* Continue the remainder in the distributor serializer */
1986 if (!serializer) {
1987 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not get distributor serializer.\n",
1990 return 0;
1991 }
1992 recreate_data.persistence = persistence;
1993 recreate_data.rdata = &rdata;
1995 &recreate_data)) {
1996 ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not continue under distributor serializer.\n",
1999 }
2001
2002 return 0;
2003}
2004
2005/*! \brief Function which loads and recreates persisted subscriptions upon startup when the system is fully booted */
2006static int subscription_persistence_load(void *data)
2007{
2008 struct ao2_container *persisted_subscriptions = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),
2009 "subscription_persistence", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
2010 pj_pool_t *pool;
2011
2012 pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "rtd%p", PJSIP_POOL_RDATA_LEN,
2013 PJSIP_POOL_RDATA_INC);
2014 if (!pool) {
2015 ast_log(LOG_WARNING, "Could not create a memory pool for recreating SIP subscriptions\n");
2016 return 0;
2017 }
2018
2019 ao2_callback(persisted_subscriptions, OBJ_NODATA, subscription_persistence_recreate, pool);
2020
2021 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
2022
2023 ao2_ref(persisted_subscriptions, -1);
2024 return 0;
2025}
2026
2027/*! \brief Event callback which fires subscription persistence recreation when the system is fully booted */
2029{
2030 struct ast_json_payload *payload;
2031 const char *type;
2032
2034 return;
2035 }
2036
2037 payload = stasis_message_data(message);
2038 type = ast_json_string_get(ast_json_object_get(payload->json, "type"));
2039
2040 /* This subscription only responds to the FullyBooted event so that all modules have been loaded when we
2041 * recreate SIP subscriptions.
2042 */
2043 if (strcmp(type, "FullyBooted")) {
2044 return;
2045 }
2046
2047 /* This has to be here so the subscription is recreated when the body generator is available */
2049
2050 /* Once the system is fully booted we don't care anymore */
2052}
2053
2054typedef int (*on_subscription_t)(struct sip_subscription_tree *sub, void *arg);
2055
2056static int for_each_subscription(on_subscription_t on_subscription, void *arg)
2057{
2058 int num = 0;
2059 struct sip_subscription_tree *i;
2060
2061 if (!on_subscription) {
2062 return num;
2063 }
2064
2067 if (on_subscription(i, arg)) {
2068 break;
2069 }
2070 ++num;
2071 }
2073 return num;
2074}
2075
2077 struct ast_str **buf)
2078{
2079 char str[256];
2080 struct ast_sip_endpoint_id_configuration *id = &sub_tree->endpoint->id;
2081
2082 ast_str_append(buf, 0, "Role: %s\r\n",
2083 sip_subscription_roles_map[sub_tree->role]);
2084 ast_str_append(buf, 0, "Endpoint: %s\r\n",
2086
2087 if (sub_tree->dlg) {
2088 ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str));
2089 } else {
2090 ast_copy_string(str, "<unknown>", sizeof(str));
2091 }
2092 ast_str_append(buf, 0, "Callid: %s\r\n", str);
2093
2094 ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(sub_tree->evsub));
2095
2096 ast_callerid_merge(str, sizeof(str),
2097 S_COR(id->self.name.valid, id->self.name.str, NULL),
2098 S_COR(id->self.number.valid, id->self.number.str, NULL),
2099 "Unknown");
2100
2101 ast_str_append(buf, 0, "Callerid: %s\r\n", str);
2102
2103 /* XXX This needs to be done recursively for lists */
2104 if (sub_tree->root->handler->to_ami) {
2105 sub_tree->root->handler->to_ami(sub_tree->root, buf);
2106 }
2107}
2108
2109
2111{
2112 pjsip_dialog *dlg;
2113 pjsip_msg *msg;
2114 pj_str_t name;
2115
2116 dlg = sub->tree->dlg;
2117 msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
2118 pj_cstr(&name, header);
2119
2120 return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
2121}
2122
2123/* XXX This function is not used. */
2125 struct ast_sip_endpoint *endpoint, const char *resource)
2126{
2127 struct ast_sip_subscription *sub;
2128 pjsip_dialog *dlg;
2129 struct ast_sip_contact *contact;
2130 pj_str_t event;
2131 pjsip_tx_data *tdata;
2132 pjsip_evsub *evsub;
2133 struct sip_subscription_tree *sub_tree = NULL;
2134
2136 if (!sub_tree) {
2137 return NULL;
2138 }
2139
2140 sub = allocate_subscription(handler, resource, NULL, sub_tree);
2141 if (!sub) {
2142 ao2_cleanup(sub_tree);
2143 return NULL;
2144 }
2145
2147 if (!contact || ast_strlen_zero(contact->uri)) {
2148 ast_log(LOG_WARNING, "No contacts configured for endpoint %s. Unable to create SIP subsription\n",
2150 ao2_ref(sub_tree, -1);
2151 ao2_cleanup(contact);
2152 return NULL;
2153 }
2154
2156 ao2_cleanup(contact);
2157 if (!dlg) {
2158 ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
2159 ao2_ref(sub_tree, -1);
2160 return NULL;
2161 }
2162
2163 pj_cstr(&event, handler->event_name);
2164 pjsip_evsub_create_uac(dlg, &pubsub_cb, &event, 0, &sub_tree->evsub);
2165 subscription_setup_dialog(sub_tree, dlg);
2166
2167 evsub = sub_tree->evsub;
2168
2169 if (pjsip_evsub_initiate(evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
2170 pjsip_evsub_send_request(sub_tree->evsub, tdata);
2171 } else {
2172 /* pjsip_evsub_terminate will result in pubsub_on_evsub_state,
2173 * being called and terminating the subscription. Therefore, we don't
2174 * need to decrease the reference count of sub here.
2175 */
2176 pjsip_evsub_terminate(evsub, PJ_TRUE);
2177 ao2_ref(sub_tree, -1);
2178 return NULL;
2179 }
2180
2181 add_subscription(sub_tree);
2182
2183 return sub;
2184}
2185
2187{
2188 ast_assert(sub->tree->dlg != NULL);
2189 return sub->tree->dlg;
2190}
2191
2193{
2194 ast_assert(sub->tree->endpoint != NULL);
2195 return ao2_bump(sub->tree->endpoint);
2196}
2197
2199{
2200 ast_assert(sub->tree->serializer != NULL);
2201 return sub->tree->serializer;
2202}
2203
2204/*!
2205 * \brief Pre-allocate a buffer for the transmission
2206 *
2207 * Typically, we let PJSIP do this step for us when we send a request. PJSIP's buffer
2208 * allocation algorithm is to allocate a buffer of PJSIP_MAX_PKT_LEN bytes and attempt
2209 * to write the packet to the allocated buffer. If the buffer is too small to hold the
2210 * packet, then we get told the message is too long to be sent.
2211 *
2212 * When dealing with SIP NOTIFY, especially with RLS, it is possible to exceed
2213 * PJSIP_MAX_PKT_LEN. Rather than accepting the limitation imposed on us by default,
2214 * we instead take the strategy of pre-allocating the buffer, testing for ourselves
2215 * if the message will fit, and resizing the buffer as required.
2216 *
2217 * The limit we impose is double that of the maximum packet length.
2218 *
2219 * \param tdata The tdata onto which to allocate a buffer
2220 * \retval 0 Success
2221 * \retval -1 The message is too large
2222 */
2223static int allocate_tdata_buffer(pjsip_tx_data *tdata)
2224{
2225 int buf_size;
2226 int size = -1;
2227 char *buf;
2228
2229 for (buf_size = PJSIP_MAX_PKT_LEN; size == -1 && buf_size < (PJSIP_MAX_PKT_LEN * 2); buf_size *= 2) {
2230 buf = pj_pool_alloc(tdata->pool, buf_size);
2231 size = pjsip_msg_print(tdata->msg, buf, buf_size);
2232 }
2233
2234 if (size == -1) {
2235 return -1;
2236 }
2237
2238 tdata->buf.start = buf;
2239 tdata->buf.cur = tdata->buf.start;
2240 tdata->buf.end = tdata->buf.start + buf_size;
2241
2242 return 0;
2243}
2244
2245static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree, pjsip_tx_data *tdata)
2246{
2247#ifdef TEST_FRAMEWORK
2248 struct ast_sip_endpoint *endpoint = sub_tree->endpoint;
2249 pjsip_evsub *evsub = sub_tree->evsub;
2250#endif
2251 int res;
2252
2253 if (allocate_tdata_buffer(tdata)) {
2254 ast_log(LOG_ERROR, "SIP request %s is too large to send.\n", tdata->info);
2255 pjsip_tx_data_dec_ref(tdata);
2256 return -1;
2257 }
2258
2259 res = pjsip_evsub_send_request(sub_tree->evsub, tdata);
2260
2262
2263 ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
2264 "StateText: %s\r\n"
2265 "Endpoint: %s\r\n",
2266 pjsip_evsub_get_state_name(evsub),
2267 ast_sorcery_object_get_id(endpoint));
2268
2269 return (res == PJ_SUCCESS ? 0 : -1);
2270}
2271
2272/*!
2273 * \brief Add a resource XML element to an RLMI body
2274 *
2275 * Each resource element represents a subscribed resource in the list. This function currently
2276 * will unconditionally add an instance element to each created resource element. Instance
2277 * elements refer to later parts in the multipart body.
2278 *
2279 * \param pool PJLIB allocation pool
2280 * \param rlmi
2281 * \param cid Content-ID header of the resource
2282 * \param resource_name Name of the resource
2283 * \param resource_uri URI of the resource
2284 * \param state State of the subscribed resource
2285 */
2286static void add_rlmi_resource(pj_pool_t *pool, pj_xml_node *rlmi, const pjsip_generic_string_hdr *cid,
2287 const char *resource_name, const pjsip_sip_uri *resource_uri, pjsip_evsub_state state)
2288{
2289 static pj_str_t cid_name = { "cid", 3 };
2290 pj_xml_node *resource;
2291 pj_xml_node *name;
2292 pj_xml_node *instance;
2293 pj_xml_attr *cid_attr;
2294 char id[6];
2295 char uri[PJSIP_MAX_URL_SIZE];
2296 char name_sanitized[PJSIP_MAX_URL_SIZE];
2297
2298 /* This creates a string representing the Content-ID without the enclosing < > */
2299 const pj_str_t cid_stripped = {
2300 .ptr = cid->hvalue.ptr + 1,
2301 .slen = cid->hvalue.slen - 2,
2302 };
2303
2304 resource = ast_sip_presence_xml_create_node(pool, rlmi, "resource");
2305 name = ast_sip_presence_xml_create_node(pool, resource, "name");
2306 instance = ast_sip_presence_xml_create_node(pool, resource, "instance");
2307
2308 pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, resource_uri, uri, sizeof(uri));
2309 ast_sip_presence_xml_create_attr(pool, resource, "uri", uri);
2310
2311 ast_sip_sanitize_xml(resource_name, name_sanitized, sizeof(name_sanitized));
2312 pj_strdup2(pool, &name->content, name_sanitized);
2313
2314 ast_generate_random_string(id, sizeof(id));
2315
2316 ast_sip_presence_xml_create_attr(pool, instance, "id", id);
2317 ast_sip_presence_xml_create_attr(pool, instance, "state",
2318 state == PJSIP_EVSUB_STATE_TERMINATED ? "terminated" : "active");
2319
2320 /* Use the PJLIB-util XML library directly here since we are using a
2321 * pj_str_t
2322 */
2323
2324 cid_attr = pj_xml_attr_new(pool, &cid_name, &cid_stripped);
2325 pj_xml_add_attr(instance, cid_attr);
2326}
2327
2328/*!
2329 * \brief A multipart body part and meta-information
2330 *
2331 * When creating a multipart body part, the end result (the
2332 * pjsip_multipart_part) is hard to inspect without undoing
2333 * a lot of what was done to create it. Therefore, we use this
2334 * structure to store meta-information about the body part.
2335 *
2336 * The main consumer of this is the creator of the RLMI body
2337 * part of a multipart resource list body.
2338 */
2340 /*! Content-ID header for the body part */
2341 pjsip_generic_string_hdr *cid;
2342 /*! Subscribed resource represented in the body part */
2343 const char *resource;
2344 /*! URI for the subscribed body part */
2345 pjsip_sip_uri *uri;
2346 /*! Subscription state of the resource represented in the body part */
2347 pjsip_evsub_state state;
2348 /*! The actual body part that will be present in the multipart body */
2349 pjsip_multipart_part *part;
2350 /*! Display name for the resource */
2351 const char *display_name;
2352};
2353
2354/*!
2355 * \brief Type declaration for container of body part structures
2356 */
2358
2359/*!
2360 * \brief Create a Content-ID header
2361 *
2362 * Content-ID headers are required by RFC2387 for multipart/related
2363 * bodies. They serve as identifiers for each part of the multipart body.
2364 *
2365 * \param pool PJLIB allocation pool
2366 * \param sub Subscription to a resource
2367 */
2368static pjsip_generic_string_hdr *generate_content_id_hdr(pj_pool_t *pool,
2369 const struct ast_sip_subscription *sub)
2370{
2371 static const pj_str_t cid_name = { "Content-ID", 10 };
2372 pjsip_generic_string_hdr *cid;
2373 char id[6];
2374 size_t alloc_size;
2375 pj_str_t cid_value;
2376
2377 /* '<' + '@' + '>' = 3. pj_str_t does not require a null-terminator */
2378 alloc_size = sizeof(id) + pj_strlen(&sub->uri->host) + 3;
2379 cid_value.ptr = pj_pool_alloc(pool, alloc_size);
2380 cid_value.slen = sprintf(cid_value.ptr, "<%s@%.*s>",
2381 ast_generate_random_string(id, sizeof(id)),
2382 (int) pj_strlen(&sub->uri->host), pj_strbuf(&sub->uri->host));
2383 cid = pjsip_generic_string_hdr_create(pool, &cid_name, &cid_value);
2384
2385 return cid;
2386}
2387
2388static int rlmi_print_body(struct pjsip_msg_body *msg_body, char *buf, pj_size_t size)
2389{
2390 int num_printed;
2391 pj_xml_node *rlmi = msg_body->data;
2392
2393 num_printed = pj_xml_print(rlmi, buf, size, PJ_TRUE);
2394 if (num_printed <= AST_PJSIP_XML_PROLOG_LEN) {
2395 return -1;
2396 }
2397
2398 return num_printed;
2399}
2400
2401static void *rlmi_clone_data(pj_pool_t *pool, const void *data, unsigned len)
2402{
2403 const pj_xml_node *rlmi = data;
2404
2405 return pj_xml_clone(pool, rlmi);
2406}
2407
2408/*!
2409 * \brief Create an RLMI body part for a multipart resource list body
2410 *
2411 * RLMI (Resource list meta information) is a special body type that lists
2412 * the subscribed resources and tells subscribers the number of subscribed
2413 * resources and what other body parts are in the multipart body. The
2414 * RLMI body also has a version number that a subscriber can use to ensure
2415 * that the locally-stored state corresponds to server state.
2416 *
2417 * \param pool The allocation pool
2418 * \param sub The subscription representing the subscribed resource list
2419 * \param body_parts A container of body parts that RLMI will refer to
2420 * \param full_state Indicates whether this is a full or partial state notification
2421 * \return The multipart part representing the RLMI body
2422 */
2423static pjsip_multipart_part *build_rlmi_body(pj_pool_t *pool, struct ast_sip_subscription *sub,
2424 struct body_part_list *body_parts, unsigned int full_state)
2425{
2426 pj_xml_node *rlmi;
2427 pj_xml_node *name;
2428 pjsip_multipart_part *rlmi_part;
2429 char version_str[32];
2430 char uri[PJSIP_MAX_URL_SIZE];
2431 pjsip_generic_string_hdr *cid;
2432 int i;
2433
2434 rlmi = ast_sip_presence_xml_create_node(pool, NULL, "list");
2435 ast_sip_presence_xml_create_attr(pool, rlmi, "xmlns", "urn:ietf:params:xml:ns:rlmi");
2436
2437 ast_sip_subscription_get_local_uri(sub, uri, sizeof(uri));
2438 ast_sip_presence_xml_create_attr(pool, rlmi, "uri", uri);
2439
2440 snprintf(version_str, sizeof(version_str), "%u", sub->version++);
2441 ast_sip_presence_xml_create_attr(pool, rlmi, "version", version_str);
2442 ast_sip_presence_xml_create_attr(pool, rlmi, "fullState", full_state ? "true" : "false");
2443
2444 name = ast_sip_presence_xml_create_node(pool, rlmi, "name");
2445 pj_strdup2(pool, &name->content, ast_sip_subscription_get_resource_name(sub));
2446
2447 for (i = 0; i < AST_VECTOR_SIZE(body_parts); ++i) {
2448 const struct body_part *part = AST_VECTOR_GET(body_parts, i);
2449
2450 add_rlmi_resource(pool, rlmi, part->cid, S_OR(part->display_name, part->resource), part->uri, part->state);
2451 }
2452
2453 rlmi_part = pjsip_multipart_create_part(pool);
2454
2455 rlmi_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
2456 pjsip_media_type_cp(pool, &rlmi_part->body->content_type, &rlmi_media_type);
2457
2458 rlmi_part->body->data = pj_xml_clone(pool, rlmi);
2459 rlmi_part->body->clone_data = rlmi_clone_data;
2460 rlmi_part->body->print_body = rlmi_print_body;
2461
2463 pj_list_insert_before(&rlmi_part->hdr, cid);
2464
2465 return rlmi_part;
2466}
2467
2468static pjsip_msg_body *generate_notify_body(pj_pool_t *pool, struct ast_sip_subscription *root,
2469 unsigned int force_full_state);
2470
2471/*!
2472 * \brief Destroy a list of body parts
2473 *
2474 * \param parts The container of parts to destroy
2475 */
2476static void free_body_parts(struct body_part_list *parts)
2477{
2478 int i;
2479
2480 for (i = 0; i < AST_VECTOR_SIZE(parts); ++i) {
2481 struct body_part *part = AST_VECTOR_GET(parts, i);
2482 ast_free(part);
2483 }
2484
2485 AST_VECTOR_FREE(parts);
2486}
2487
2488/*!
2489 * \brief Allocate and initialize a body part structure
2490 *
2491 * \param pool PJLIB allocation pool
2492 * \param sub Subscription representing a subscribed resource
2493 */
2494static struct body_part *allocate_body_part(pj_pool_t *pool, const struct ast_sip_subscription *sub)
2495{
2496 struct body_part *bp;
2497
2498 bp = ast_calloc(1, sizeof(*bp));
2499 if (!bp) {
2500 return NULL;
2501 }
2502
2503 bp->cid = generate_content_id_hdr(pool, sub);
2504 bp->resource = sub->resource;
2505 bp->state = sub->subscription_state;
2506 bp->uri = sub->uri;
2507 bp->display_name = sub->display_name;
2508
2509 return bp;
2510}
2511
2512/*!
2513 * \brief Create a multipart body part for a subscribed resource
2514 *
2515 * \param pool PJLIB allocation pool
2516 * \param sub The subscription representing a subscribed resource
2517 * \param parts A vector of parts to append the created part to.
2518 * \param use_full_state Unused locally, but may be passed to other functions
2519 */
2520static void build_body_part(pj_pool_t *pool, struct ast_sip_subscription *sub,
2521 struct body_part_list *parts, unsigned int use_full_state)
2522{
2523 struct body_part *bp;
2524 pjsip_msg_body *body;
2525
2526 bp = allocate_body_part(pool, sub);
2527 if (!bp) {
2528 return;
2529 }
2530
2531 body = generate_notify_body(pool, sub, use_full_state);
2532 if (!body) {
2533 /* Partial state was requested and the resource has not changed state */
2534 ast_free(bp);
2535 return;
2536 }
2537
2538 bp->part = pjsip_multipart_create_part(pool);
2539 bp->part->body = body;
2540 pj_list_insert_before(&bp->part->hdr, bp->cid);
2541
2542 if (AST_VECTOR_APPEND(parts, bp)) {
2543 ast_free(bp);
2544 }
2545}
2546
2547/*!
2548 * \brief Create and initialize the PJSIP multipart body structure for a resource list subscription
2549 *
2550 * \param pool
2551 * \return The multipart message body
2552 */
2553static pjsip_msg_body *create_multipart_body(pj_pool_t *pool)
2554{
2555 pjsip_media_type media_type;
2556 pjsip_param *media_type_param;
2557 char boundary[6];
2558 pj_str_t pj_boundary;
2559
2560 pjsip_media_type_init2(&media_type, "multipart", "related");
2561
2562 media_type_param = pj_pool_alloc(pool, sizeof(*media_type_param));
2563 pj_list_init(media_type_param);
2564
2565 pj_strdup2(pool, &media_type_param->name, "type");
2566 pj_strdup2(pool, &media_type_param->value, "\"application/rlmi+xml\"");
2567
2568 pj_list_insert_before(&media_type.param, media_type_param);
2569
2570 pj_cstr(&pj_boundary, ast_generate_random_string(boundary, sizeof(boundary)));
2571 return pjsip_multipart_create(pool, &media_type, &pj_boundary);
2572}
2573
2574/*!
2575 * \brief Create a resource list body for NOTIFY requests
2576 *
2577 * Resource list bodies are multipart/related bodies. The first part of the multipart body
2578 * is an RLMI body that describes the rest of the parts to come. The other parts of the body
2579 * convey state of individual subscribed resources.
2580 *
2581 * \param pool PJLIB allocation pool
2582 * \param sub Subscription details from which to generate body
2583 * \param force_full_state If true, ignore resource list settings and send a full state notification
2584 * \return The generated multipart/related body
2585 */
2586static pjsip_msg_body *generate_list_body(pj_pool_t *pool, struct ast_sip_subscription *sub,
2587 unsigned int force_full_state)
2588{
2589 int i;
2590 pjsip_multipart_part *rlmi_part;
2591 pjsip_msg_body *multipart;
2592 struct body_part_list body_parts;
2593 unsigned int use_full_state = force_full_state ? 1 : sub->full_state;
2594
2595 if (AST_VECTOR_INIT(&body_parts, AST_VECTOR_SIZE(&sub->children))) {
2596 return NULL;
2597 }
2598
2599 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
2600 build_body_part(pool, AST_VECTOR_GET(&sub->children, i), &body_parts, use_full_state);
2601 }
2602
2603 /* This can happen if issuing partial state and no children of the list have changed state */
2604 if (AST_VECTOR_SIZE(&body_parts) == 0) {
2605 free_body_parts(&body_parts);
2606 return NULL;
2607 }
2608
2609 multipart = create_multipart_body(pool);
2610
2611 rlmi_part = build_rlmi_body(pool, sub, &body_parts, use_full_state);
2612 if (!rlmi_part) {
2613 free_body_parts(&body_parts);
2614 return NULL;
2615 }
2616 pjsip_multipart_add_part(pool, multipart, rlmi_part);
2617
2618 for (i = 0; i < AST_VECTOR_SIZE(&body_parts); ++i) {
2619 pjsip_multipart_add_part(pool, multipart, AST_VECTOR_GET(&body_parts, i)->part);
2620 }
2621
2622 free_body_parts(&body_parts);
2623 return multipart;
2624}
2625
2626/*!
2627 * \brief Create the body for a NOTIFY request.
2628 *
2629 * \param pool The pool used for allocations
2630 * \param root The root of the subscription tree
2631 * \param force_full_state If true, ignore resource list settings and send a full state notification
2632 */
2633static pjsip_msg_body *generate_notify_body(pj_pool_t *pool, struct ast_sip_subscription *root,
2634 unsigned int force_full_state)
2635{
2636 pjsip_msg_body *body;
2637
2638 if (AST_VECTOR_SIZE(&root->children) == 0) {
2639 if (force_full_state || root->body_changed) {
2640 /* Not a list. We've already generated the body and saved it on the subscription.
2641 * Use that directly.
2642 */
2643 pj_str_t type;
2644 pj_str_t subtype;
2645 pj_str_t text;
2646
2648 pj_cstr(&subtype, ast_sip_subscription_get_body_subtype(root));
2649 pj_cstr(&text, ast_str_buffer(root->body_text));
2650
2651 body = pjsip_msg_body_create(pool, &type, &subtype, &text);
2652 root->body_changed = 0;
2653 } else {
2654 body = NULL;
2655 }
2656 } else {
2657 body = generate_list_body(pool, root, force_full_state);
2658 }
2659
2660 return body;
2661}
2662
2663/*!
2664 * \brief Shortcut method to create a Require: eventlist header
2665 */
2666static pjsip_require_hdr *create_require_eventlist(pj_pool_t *pool)
2667{
2668 pjsip_require_hdr *require;
2669
2670 require = pjsip_require_hdr_create(pool);
2671 pj_strdup2(pool, &require->values[0], "eventlist");
2672 require->count = 1;
2673
2674 return require;
2675}
2676
2678{
2679 int i;
2680
2681 sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
2682 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
2683 set_state_terminated(AST_VECTOR_GET(&sub->children, i));
2684 }
2685}
2686
2687/*!
2688 * \brief Send a NOTIFY request to a subscriber
2689 *
2690 * \pre sub_tree->dlg is locked
2691 *
2692 * \param sub_tree The subscription tree representing the subscription
2693 * \param force_full_state If true, ignore resource list settings and send full resource list state.
2694 * \retval 0 Success
2695 * \retval non-zero Failure
2696 */
2697static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state)
2698{
2699 pjsip_evsub *evsub = sub_tree->evsub;
2700 pjsip_tx_data *tdata;
2701
2702 if (ast_shutdown_final()
2703 && sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED
2704 && sub_tree->persistence) {
2705 return 0;
2706 }
2707
2708 if (pjsip_evsub_notify(evsub, sub_tree->root->subscription_state,
2709 NULL, NULL, &tdata) != PJ_SUCCESS) {
2710 return -1;
2711 }
2712
2713 tdata->msg->body = generate_notify_body(tdata->pool, sub_tree->root, force_full_state);
2714 if (!tdata->msg->body) {
2715 pjsip_tx_data_dec_ref(tdata);
2716 return -1;
2717 }
2718
2719 if (sub_tree->is_list) {
2720 pjsip_require_hdr *require = create_require_eventlist(tdata->pool);
2721 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) require);
2722 }
2723
2724 if (sub_tree->root->handler->notifier->notify_created) {
2725 /* The module for this event wants a callback to the pjsip_tx_data,
2726 * e.g. so it can add custom headers or do something custom to the response. */
2727 sub_tree->root->handler->notifier->notify_created(sub_tree->root, tdata);
2728 }
2729
2730 if (sip_subscription_send_request(sub_tree, tdata)) {
2731 /* do not call pjsip_tx_data_dec_ref(tdata). The pjsip_dlg_send_request deletes the message on error */
2732 return -1;
2733 }
2734
2735 sub_tree->send_scheduled_notify = 0;
2736
2737 return 0;
2738}
2739
2740static int serialized_send_notify(void *userdata)
2741{
2742 struct sip_subscription_tree *sub_tree = userdata;
2743 pjsip_dialog *dlg = sub_tree->dlg;
2744
2745 pjsip_dlg_inc_lock(dlg);
2746
2747 sub_tree->notify_sched_id = -1;
2748
2749 /* It's possible that between when the notification was scheduled
2750 * and now a new SUBSCRIBE arrived requiring full state to be
2751 * sent out in an immediate NOTIFY. It's also possible that we're
2752 * already processing a terminate. If that has happened, we need to
2753 * bail out here instead of sending the batched NOTIFY.
2754 */
2755
2757 || !sub_tree->send_scheduled_notify) {
2758 pjsip_dlg_dec_lock(dlg);
2759 ao2_cleanup(sub_tree);
2760 return 0;
2761 }
2762
2763 if (sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED) {
2765 }
2766
2767 send_notify(sub_tree, 0);
2768
2770 sub_tree->state == SIP_SUB_TREE_TERMINATED
2771 ? "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_STATE_CHANGED",
2772 "Resource: %s", sub_tree->root->resource);
2773
2774 pjsip_dlg_dec_lock(dlg);
2775 ao2_cleanup(sub_tree);
2776 return 0;
2777}
2778
2779static int sched_cb(const void *data)
2780{
2781 struct sip_subscription_tree *sub_tree = (struct sip_subscription_tree *) data;
2782
2783 /* We don't need to bump the refcount of sub_tree since we bumped it when scheduling this task */
2784 if (ast_sip_push_task(sub_tree->serializer, serialized_send_notify, sub_tree)) {
2785 ao2_cleanup(sub_tree);
2786 }
2787
2788 return 0;
2789}
2790
2792{
2793 /* There's already a notification scheduled */
2794 if (sub_tree->notify_sched_id > -1) {
2795 return 0;
2796 }
2797
2798 sub_tree->send_scheduled_notify = 1;
2800 if (sub_tree->notify_sched_id < 0) {
2801 ao2_cleanup(sub_tree);
2802 return -1;
2803 }
2804
2805 return 0;
2806}
2807
2809 int terminate)
2810{
2811 int res;
2812 pjsip_dialog *dlg = sub->tree->dlg;
2813
2814 pjsip_dlg_inc_lock(dlg);
2815
2816 if (sub->tree->state != SIP_SUB_TREE_NORMAL) {
2817 pjsip_dlg_dec_lock(dlg);
2818 return 0;
2819 }
2820
2823 pjsip_dlg_dec_lock(dlg);
2824 return -1;
2825 }
2826
2827 sub->body_changed = 1;
2828 if (terminate) {
2829 sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
2830 sub->tree->state = SIP_SUB_TREE_TERMINATE_PENDING;
2831 }
2832
2833 if (sub->tree->notification_batch_interval) {
2834 res = schedule_notification(sub->tree);
2835 } else {
2836 /* See the note in pubsub_on_rx_refresh() for why sub->tree is refbumped here */
2837 ao2_ref(sub->tree, +1);
2838 if (terminate) {
2840 }
2841 res = send_notify(sub->tree, 0);
2842 ast_test_suite_event_notify(terminate ? "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_STATE_CHANGED",
2843 "Resource: %s",
2844 sub->tree->root->resource);
2845 ao2_ref(sub->tree, -1);
2846 }
2847
2848 pjsip_dlg_dec_lock(dlg);
2849 return res;
2850}
2851
2853{
2854 return sub->uri;
2855}
2856
2858{
2859 pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, sub->uri, buf, size);
2860}
2861
2863{
2864 pjsip_dialog *dlg;
2865 pjsip_sip_uri *uri;
2866
2867 dlg = sub->tree->dlg;
2868 uri = pjsip_uri_get_uri(dlg->remote.info->uri);
2869
2870 if (pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, uri, buf, size) < 0) {
2871 *buf = '\0';
2872 }
2873}
2874
2876{
2877 return sub->resource;
2878}
2879
2881{
2882 return sub->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ? 1 : 0;
2883}
2884
2885static int sip_subscription_accept(struct sip_subscription_tree *sub_tree, pjsip_rx_data *rdata, int response)
2886{
2887 pjsip_hdr res_hdr;
2888
2889 /* If this is a persistence recreation the subscription has already been accepted */
2890 if (ast_sip_mod_data_get(rdata->endpt_info.mod_data, pubsub_module.id, MOD_DATA_PERSISTENCE)) {
2891 return 0;
2892 }
2893
2894 pj_list_init(&res_hdr);
2895 if (sub_tree->is_list) {
2896 /* If subscribing to a list, our response has to have a Require: eventlist header in it */
2897 pj_list_insert_before(&res_hdr, create_require_eventlist(rdata->tp_info.pool));
2898 }
2899
2900 return pjsip_evsub_accept(sub_tree->evsub, rdata, response, &res_hdr) == PJ_SUCCESS ? 0 : -1;
2901}
2902
2907
2909{
2910 return ast_datastores_add(subscription->datastores, datastore);
2911}
2912
2914{
2915 return ast_datastores_find(subscription->datastores, name);
2916}
2917
2919{
2920 ast_datastores_remove(subscription->datastores, name);
2921}
2922
2924{
2925 return subscription->datastores;
2926}
2927
2929{
2930 return ast_datastores_add(publication->datastores, datastore);
2931}
2932
2934{
2935 return ast_datastores_find(publication->datastores, name);
2936}
2937
2939{
2940 ast_datastores_remove(publication->datastores, name);
2941}
2942
2944{
2945 return publication->datastores;
2946}
2947
2948void ast_sip_subscription_set_persistence_data(struct ast_sip_subscription *subscription, struct ast_json *persistence_data)
2949{
2950 ast_json_unref(subscription->persistence_data);
2951 subscription->persistence_data = persistence_data;
2952
2953 if (subscription->tree->persistence) {
2954 if (!subscription->tree->persistence->generator_data) {
2956 if (!subscription->tree->persistence->generator_data) {
2957 return;
2958 }
2959 }
2960 ast_json_object_set(subscription->tree->persistence->generator_data, subscription->resource,
2961 ast_json_ref(persistence_data));
2962 }
2963}
2964
2966{
2967 return subscription->persistence_data;
2968}
2969
2971
2972static int publication_hash_fn(const void *obj, const int flags)
2973{
2974 const struct ast_sip_publication *publication = obj;
2975 const int *entity_tag = obj;
2976
2977 return flags & OBJ_KEY ? *entity_tag : publication->entity_tag;
2978}
2979
2980static int publication_cmp_fn(void *obj, void *arg, int flags)
2981{
2982 const struct ast_sip_publication *publication1 = obj;
2983 const struct ast_sip_publication *publication2 = arg;
2984 const int *entity_tag = arg;
2985
2986 return (publication1->entity_tag == (flags & OBJ_KEY ? *entity_tag : publication2->entity_tag) ?
2987 CMP_MATCH | CMP_STOP : 0);
2988}
2989
2996
2998{
2999 if (ast_strlen_zero(handler->event_name)) {
3000 ast_log(LOG_ERROR, "No event package specified for publish handler. Cannot register\n");
3001 return -1;
3002 }
3003
3006 if (!handler->publications) {
3007 ast_log(LOG_ERROR, "Could not allocate publications container for event '%s'\n",
3008 handler->event_name);
3009 return -1;
3010 }
3011
3013
3014 return 0;
3015}
3016
3032
3034
3041
3043{
3044 struct ast_sip_subscription_handler *iter;
3045
3048 if (!strcmp(iter->event_name, event_name)) {
3049 break;
3050 }
3051 }
3053 return iter;
3054}
3055
3057{
3058 pj_str_t event;
3059 pj_str_t accept[AST_SIP_MAX_ACCEPT] = { {0, }, };
3060 struct ast_sip_subscription_handler *existing;
3061 int i = 0;
3062
3063 if (ast_strlen_zero(handler->event_name)) {
3064 ast_log(LOG_ERROR, "No event package specified for subscription handler. Cannot register\n");
3065 return -1;
3066 }
3067
3068 existing = find_sub_handler_for_event_name(handler->event_name);
3069 if (existing) {
3071 "Unable to register subscription handler for event %s. A handler is already registered\n",
3072 handler->event_name);
3073 return -1;
3074 }
3075
3076 for (i = 0; i < AST_SIP_MAX_ACCEPT && !ast_strlen_zero(handler->accept[i]); ++i) {
3077 pj_cstr(&accept[i], handler->accept[i]);
3078 }
3079
3080 pj_cstr(&event, handler->event_name);
3081
3082 pjsip_evsub_register_pkg(&pubsub_module, &event, DEFAULT_EXPIRES, i, accept);
3083
3085
3086 return 0;
3087}
3088
3103
3105{
3107
3109 if (!strcmp(gen->type, type)
3110 && !strcmp(gen->subtype, subtype)) {
3111 break;
3112 }
3113 }
3114
3115 return gen;
3116}
3117
3127
3129{
3130 char *accept_copy = ast_strdupa(accept);
3131 char *subtype = accept_copy;
3132 char *type = strsep(&subtype, "/");
3133
3135 return NULL;
3136 }
3137
3139}
3140
3142 size_t num_accept, const char *body_type)
3143{
3144 int i;
3145 struct ast_sip_pubsub_body_generator *generator = NULL;
3146
3147 for (i = 0; i < num_accept; ++i) {
3148 generator = find_body_generator_accept(accept[i]);
3149 if (generator) {
3150 ast_debug(3, "Body generator %p found for accept type %s\n", generator, accept[i]);
3151 if (strcmp(generator->body_type, body_type)) {
3152 ast_log(LOG_WARNING, "Body generator '%s/%s'(%p) does not accept the type of data this event generates\n",
3153 generator->type, generator->subtype, generator);
3154 generator = NULL;
3155 continue;
3156 }
3157 break;
3158 } else {
3159 ast_debug(3, "No body generator found for accept type %s\n", accept[i]);
3160 }
3161 }
3162
3163 return generator;
3164}
3165
3167{
3168 void *notify_data;
3169 int res;
3170 struct ast_sip_body_data data = {
3171 .body_type = sub->handler->body_type,
3172 };
3173
3174 if (AST_VECTOR_SIZE(&sub->children) > 0) {
3175 int i;
3176
3177 for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
3178 if (generate_initial_notify(AST_VECTOR_GET(&sub->children, i))) {
3179 return -1;
3180 }
3181 }
3182
3183 return 0;
3184 }
3185
3186 /* We notify subscription establishment only on the tree leaves. */
3187 if (sub->handler->notifier->subscription_established(sub)) {
3188 return -1;
3189 }
3190
3191 notify_data = sub->handler->notifier->get_notify_data(sub);
3192 if (!notify_data) {
3193 ast_debug(3, "No notify data, not generating any body content\n");
3194 return -1;
3195 }
3196
3197 data.body_data = notify_data;
3198
3200 ast_sip_subscription_get_body_subtype(sub), &data, &sub->body_text);
3201
3203
3204 return res;
3205}
3206
3207static int pubsub_on_refresh_timeout(void *userdata);
3208
3209static int initial_notify_task(void * obj)
3210{
3211 struct initial_notify_data *ind = obj;
3212
3214 pjsip_evsub_terminate(ind->sub_tree->evsub, PJ_TRUE);
3215 } else {
3216 send_notify(ind->sub_tree, 1);
3217 ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
3218 "Resource: %s",
3219 ind->sub_tree->root->resource);
3220 }
3221
3223 char *name = ast_alloca(strlen("->/ ") +
3224 strlen(ind->sub_tree->persistence->endpoint) +
3225 strlen(ind->sub_tree->root->resource) +
3226 strlen(ind->sub_tree->root->handler->event_name) +
3227 ind->sub_tree->dlg->call_id->id.slen + 1);
3228
3229 sprintf(name, "%s->%s/%s %.*s", ind->sub_tree->persistence->endpoint,
3231 (int)ind->sub_tree->dlg->call_id->id.slen, ind->sub_tree->dlg->call_id->id.ptr);
3232
3233 ast_debug(3, "Scheduling timer: %s\n", name);
3237 if (!ind->sub_tree->expiration_task) {
3238 ast_log(LOG_ERROR, "Unable to create expiration timer of %d seconds for %s\n",
3239 ind->expires, name);
3240 }
3241 }
3242
3243 ao2_ref(ind->sub_tree, -1);
3244 ast_free(ind);
3245
3246 return 0;
3247}
3248
3249static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
3250{
3251 pjsip_expires_hdr *expires_header;
3253 RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
3254 struct sip_subscription_tree *sub_tree;
3255 struct ast_sip_pubsub_body_generator *generator;
3256 char *resource;
3257 pjsip_uri *request_uri;
3258 size_t resource_size;
3259 int resp;
3260 struct resource_tree tree;
3261 pj_status_t dlg_status;
3262 const pj_str_t *user;
3263
3264 endpoint = ast_pjsip_rdata_get_endpoint(rdata);
3265 ast_assert(endpoint != NULL);
3266
3267 if (!endpoint->subscription.allow) {
3268 ast_log(LOG_WARNING, "Subscriptions not permitted for endpoint %s.\n", ast_sorcery_object_get_id(endpoint));
3269 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 603, NULL, NULL, NULL);
3270 return PJ_TRUE;
3271 }
3272
3273 request_uri = rdata->msg_info.msg->line.req.uri;
3274
3275 if (!ast_sip_is_uri_sip_sips(request_uri)) {
3276 char uri_str[PJSIP_MAX_URL_SIZE];
3277
3278 pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, uri_str, sizeof(uri_str));
3279 ast_log(LOG_WARNING, "Request URI '%s' is not a sip: or sips: URI.\n", uri_str);
3280 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
3281 return PJ_TRUE;
3282 }
3283
3284 user = ast_sip_pjsip_uri_get_username(request_uri);
3285 resource_size = pj_strlen(user) + 1;
3286 resource = ast_alloca(resource_size);
3287 ast_copy_pj_str(resource, user, resource_size);
3288
3289 /*
3290 * We may want to match without any user options getting
3291 * in the way.
3292 */
3294
3295 expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, rdata->msg_info.msg->hdr.next);
3296 if (expires_header) {
3297 if (expires_header->ivalue == 0) {
3298 ast_debug(1, "Subscription request from endpoint %s rejected. Expiration of 0 is invalid\n",
3299 ast_sorcery_object_get_id(endpoint));
3300 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
3301 return PJ_TRUE;
3302 }
3303 if (expires_header->ivalue < endpoint->subscription.minexpiry) {
3304 ast_log(LOG_WARNING, "Subscription expiration %d is too brief for endpoint %s. Minimum is %u\n",
3305 expires_header->ivalue, ast_sorcery_object_get_id(endpoint), endpoint->subscription.minexpiry);
3306 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 423, NULL, NULL, NULL);
3307 return PJ_TRUE;
3308 }
3309 }
3310
3312 if (!handler) {
3313 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3314 return PJ_TRUE;
3315 }
3316
3318 if (!generator) {
3319 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3320 return PJ_TRUE;
3321 }
3322
3323 memset(&tree, 0, sizeof(tree));
3324 resp = build_resource_tree(endpoint, handler, resource, &tree,
3326 if (!PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
3327 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, resp, NULL, NULL, NULL);
3328 resource_tree_destroy(&tree);
3329 return PJ_TRUE;
3330 }
3331
3332 sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, &tree, &dlg_status, NULL);
3333 if (!sub_tree) {
3334 if (dlg_status != PJ_EEXISTS) {
3335 ast_debug(3, "No dialog exists, rejecting\n");
3336 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
3337 }
3338 } else {
3339 struct initial_notify_data *ind = ast_malloc(sizeof(*ind));
3340
3341 if (!ind) {
3342 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
3343 resource_tree_destroy(&tree);
3344 return PJ_TRUE;
3345 }
3346
3347 ind->sub_tree = ao2_bump(sub_tree);
3348 /* Since this is a normal subscribe, pjproject takes care of the timer */
3350
3353 sip_subscription_accept(sub_tree, rdata, resp);
3355 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
3356 ao2_ref(sub_tree, -1);
3357 ast_free(ind);
3358 }
3359 }
3360
3361 resource_tree_destroy(&tree);
3362 return PJ_TRUE;
3363}
3364
3366{
3367 struct ast_sip_publish_handler *iter = NULL;
3368
3371 if (strcmp(event, iter->event_name)) {
3372 ast_debug(3, "Event %s does not match %s\n", event, iter->event_name);
3373 continue;
3374 }
3375 ast_debug(3, "Event name match: %s = %s\n", event, iter->event_name);
3376 break;
3377 }
3379
3380 return iter;
3381}
3382
3383static enum sip_publish_type determine_sip_publish_type(pjsip_rx_data *rdata,
3384 pjsip_generic_string_hdr *etag_hdr, unsigned int *expires, int *entity_id)
3385{
3386 pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
3387
3388 if (etag_hdr) {
3389 char etag[pj_strlen(&etag_hdr->hvalue) + 1];
3390
3391 ast_copy_pj_str(etag, &etag_hdr->hvalue, sizeof(etag));
3392
3393 if (sscanf(etag, "%30d", entity_id) != 1) {
3394 return SIP_PUBLISH_UNKNOWN;
3395 }
3396 }
3397
3398 *expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
3399
3400 if (!(*expires)) {
3401 return SIP_PUBLISH_REMOVE;
3402 } else if (!etag_hdr && rdata->msg_info.msg->body) {
3403 return SIP_PUBLISH_INITIAL;
3404 } else if (etag_hdr && !rdata->msg_info.msg->body) {
3405 return SIP_PUBLISH_REFRESH;
3406 } else if (etag_hdr && rdata->msg_info.msg->body) {
3407 return SIP_PUBLISH_MODIFY;
3408 }
3409
3410 return SIP_PUBLISH_UNKNOWN;
3411}
3412
3413/*! \brief Internal destructor for publications */
3414static void publication_destroy_fn(void *obj)
3415{
3416 struct ast_sip_publication *publication = obj;
3417
3418 ast_debug(3, "Destroying SIP publication\n");
3419
3420 ao2_cleanup(publication->datastores);
3421 ao2_cleanup(publication->endpoint);
3422
3424}
3425
3426static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
3427 const char *resource, const char *event_configuration_name)
3428{
3429 struct ast_sip_publication *publication;
3430 pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
3431 size_t resource_len = strlen(resource) + 1, event_configuration_name_len = strlen(event_configuration_name) + 1;
3432 char *dst;
3433
3435
3436 if (!(publication = ao2_alloc(sizeof(*publication) + resource_len + event_configuration_name_len, publication_destroy_fn))) {
3437 return NULL;
3438 }
3439
3441
3442 if (!(publication->datastores = ast_datastores_alloc())) {
3443 ao2_ref(publication, -1);
3444 return NULL;
3445 }
3446
3448 ao2_ref(endpoint, +1);
3449 publication->endpoint = endpoint;
3450 publication->expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
3451 publication->sched_id = -1;
3452 dst = publication->data;
3453 publication->resource = strcpy(dst, resource);
3454 dst += resource_len;
3455 publication->event_configuration_name = strcpy(dst, event_configuration_name);
3456
3457 return publication;
3458}
3459
3460static int sip_publication_respond(struct ast_sip_publication *pub, int status_code,
3461 pjsip_rx_data *rdata)
3462{
3463 pjsip_tx_data *tdata;
3464 pjsip_transaction *tsx;
3465
3466 if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, status_code, NULL, &tdata) != PJ_SUCCESS) {
3467 return -1;
3468 }
3469
3470 if (PJSIP_IS_STATUS_IN_CLASS(status_code, 200)) {
3471 char buf[30];
3472
3473 snprintf(buf, sizeof(buf), "%d", pub->entity_tag);
3474 ast_sip_add_header(tdata, "SIP-ETag", buf);
3475
3476 snprintf(buf, sizeof(buf), "%d", pub->expires);
3477 ast_sip_add_header(tdata, "Expires", buf);
3478 }
3479
3480 if (pjsip_tsx_create_uas(&pubsub_module, rdata, &tsx) != PJ_SUCCESS) {
3481 pjsip_tx_data_dec_ref(tdata);
3482 return -1;
3483 }
3484
3485 pjsip_tsx_recv_msg(tsx, rdata);
3486
3487 if (pjsip_tsx_send_msg(tsx, tdata) != PJ_SUCCESS) {
3488 pjsip_tx_data_dec_ref(tdata);
3489 return -1;
3490 }
3491
3492 return 0;
3493}
3494
3495static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
3497{
3498 struct ast_sip_publication *publication;
3499 char *resource_name;
3500 size_t resource_size;
3502 struct ast_variable *event_configuration_name = NULL;
3503 pjsip_uri *request_uri;
3504 int resp;
3505 const pj_str_t *user;
3506
3507 request_uri = rdata->msg_info.msg->line.req.uri;
3508
3509 if (!ast_sip_is_uri_sip_sips(request_uri)) {
3510 char uri_str[PJSIP_MAX_URL_SIZE];
3511
3512 pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, uri_str, sizeof(uri_str));
3513 ast_log(LOG_WARNING, "Request URI '%s' is not a sip: or sips: URI.\n", uri_str);
3514 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
3515 return NULL;
3516 }
3517
3518 user = ast_sip_pjsip_uri_get_username(request_uri);
3519 resource_size = pj_strlen(user) + 1;
3520 resource_name = ast_alloca(resource_size);
3521 ast_copy_pj_str(resource_name, user, resource_size);
3522
3523 /*
3524 * We may want to match without any user options getting
3525 * in the way.
3526 */
3528
3529 resource = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "inbound-publication", resource_name);
3530 if (!resource) {
3531 ast_debug(1, "No 'inbound-publication' defined for resource '%s'\n", resource_name);
3532 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
3533 return NULL;
3534 }
3535
3536 if (!ast_strlen_zero(resource->endpoint) && strcmp(resource->endpoint, ast_sorcery_object_get_id(endpoint))) {
3537 ast_debug(1, "Resource %s has a defined endpoint '%s', but does not match endpoint '%s' that received the request\n",
3538 resource_name, resource->endpoint, ast_sorcery_object_get_id(endpoint));
3539 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
3540 return NULL;
3541 }
3542
3543 for (event_configuration_name = resource->events; event_configuration_name; event_configuration_name = event_configuration_name->next) {
3544 if (!strcmp(event_configuration_name->name, handler->event_name)) {
3545 break;
3546 }
3547 }
3548
3549 if (!event_configuration_name) {
3550 ast_debug(1, "Event '%s' is not configured for '%s'\n", handler->event_name, resource_name);
3551 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
3552 return NULL;
3553 }
3554
3555 resp = handler->new_publication(endpoint, resource_name, event_configuration_name->value);
3556
3557 if (!PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
3558 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, resp, NULL, NULL, NULL);
3559 return NULL;
3560 }
3561
3562 publication = sip_create_publication(endpoint, rdata, S_OR(resource_name, ""), event_configuration_name->value);
3563
3564 if (!publication) {
3565 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 503, NULL, NULL, NULL);
3566 return NULL;
3567 }
3568
3569 publication->handler = handler;
3570 if (publication->handler->publication_state_change(publication, rdata->msg_info.msg->body,
3572 ast_debug(3, "Publication state change failed\n");
3573 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
3574 ao2_cleanup(publication);
3575 return NULL;
3576 }
3577
3578 sip_publication_respond(publication, resp, rdata);
3579
3580 return publication;
3581}
3582
3583static int publish_expire_callback(void *data)
3584{
3585 RAII_VAR(struct ast_sip_publication *, publication, data, ao2_cleanup);
3586
3587 if (publication->handler->publish_expire) {
3588 publication->handler->publish_expire(publication);
3589 }
3590
3591 return 0;
3592}
3593
3594static int publish_expire(const void *data)
3595{
3596 struct ast_sip_publication *publication = (struct ast_sip_publication*)data;
3597
3598 ao2_unlink(publication->handler->publications, publication);
3599 publication->sched_id = -1;
3600
3601 if (ast_sip_push_task(NULL, publish_expire_callback, publication)) {
3602 ao2_cleanup(publication);
3603 }
3604
3605 return 0;
3606}
3607
3608static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
3609{
3610 pjsip_event_hdr *event_header;
3612 RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
3613 char event[32];
3614 static const pj_str_t str_sip_if_match = { "SIP-If-Match", 12 };
3615 pjsip_generic_string_hdr *etag_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_sip_if_match, NULL);
3616 enum sip_publish_type publish_type;
3617 RAII_VAR(struct ast_sip_publication *, publication, NULL, ao2_cleanup);
3618 unsigned int expires = 0;
3619 int entity_id, response = 0;
3620
3621 endpoint = ast_pjsip_rdata_get_endpoint(rdata);
3622 ast_assert(endpoint != NULL);
3623
3624 event_header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_event_name, rdata->msg_info.msg->hdr.next);
3625 if (!event_header) {
3626 ast_log(LOG_WARNING, "Incoming PUBLISH request from %s with no Event header\n",
3627 ast_sorcery_object_get_id(endpoint));
3628 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3629 return PJ_TRUE;
3630 }
3631 ast_copy_pj_str(event, &event_header->event_type, sizeof(event));
3632
3634 if (!handler) {
3635 ast_log(LOG_WARNING, "No registered publish handler for event %s from %s\n", event,
3636 ast_sorcery_object_get_id(endpoint));
3637 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
3638 return PJ_TRUE;
3639 }
3640
3641 publish_type = determine_sip_publish_type(rdata, etag_hdr, &expires, &entity_id);
3642
3643 /* If this is not an initial publish ensure that a publication is present */
3644 if ((publish_type != SIP_PUBLISH_INITIAL) && (publish_type != SIP_PUBLISH_UNKNOWN)) {
3645 if (!(publication = ao2_find(handler->publications, &entity_id, OBJ_KEY | OBJ_UNLINK))) {
3646 static const pj_str_t str_conditional_request_failed = { "Conditional Request Failed", 26 };
3647
3648 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 412, &str_conditional_request_failed,
3649 NULL, NULL);
3650 return PJ_TRUE;
3651 }
3652
3653 /* Per the RFC every response has to have a new entity tag */
3654 publication->entity_tag = ast_atomic_fetchadd_int(&esc_etag_counter, +1);
3655
3656 /* Update the expires here so that the created responses will contain the correct value */
3657 publication->expires = expires;
3658 }
3659
3660 switch (publish_type) {
3662 publication = publish_request_initial(endpoint, rdata, handler);
3663 break;
3665 case SIP_PUBLISH_MODIFY:
3666 if (handler->publication_state_change(publication, rdata->msg_info.msg->body,
3668 /* If an error occurs we want to terminate the publication */
3669 expires = 0;
3670 }
3671 response = 200;
3672 break;
3673 case SIP_PUBLISH_REMOVE:
3674 handler->publication_state_change(publication, rdata->msg_info.msg->body,
3676 response = 200;
3677 break;
3679 default:
3680 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
3681 break;
3682 }
3683
3684 if (publication) {
3685 if (expires) {
3686 ao2_link(handler->publications, publication);
3687
3688 AST_SCHED_REPLACE_UNREF(publication->sched_id, sched, expires * 1000, publish_expire, publication,
3689 ao2_ref(_data, -1), ao2_ref(publication, -1), ao2_ref(publication, +1));
3690 } else {
3691 AST_SCHED_DEL_UNREF(sched, publication->sched_id, ao2_ref(publication, -1));
3692 }
3693 }
3694
3695 if (response) {
3696 sip_publication_respond(publication, response, rdata);
3697 }
3698
3699 return PJ_TRUE;
3700}
3701
3703{
3704 return pub->endpoint;
3705}
3706
3708{
3709 return pub->resource;
3710}
3711
3713{
3714 return pub->event_configuration_name;
3715}
3716
3717int ast_sip_pubsub_is_body_generator_registered(const char *type, const char *subtype)
3718{
3719 return !!find_body_generator_type_subtype(type, subtype);
3720}
3721
3723{
3724 struct ast_sip_pubsub_body_generator *existing;
3725 pj_str_t accept;
3726 pj_size_t accept_len;
3727
3729 existing = find_body_generator_type_subtype_nolock(generator->type, generator->subtype);
3730 if (existing) {
3732 ast_log(LOG_WARNING, "A body generator for %s/%s is already registered.\n",
3733 generator->type, generator->subtype);
3734 return -1;
3735 }
3738
3739 /* Lengths of type and subtype plus a slash. */
3740 accept_len = strlen(generator->type) + strlen(generator->subtype) + 1;
3741
3742 /* Add room for null terminator that sprintf() will set. */
3743 pj_strset(&accept, ast_alloca(accept_len + 1), accept_len);
3744 sprintf((char *) pj_strbuf(&accept), "%s/%s", generator->type, generator->subtype);/* Safe */
3745
3746 pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module,
3747 PJSIP_H_ACCEPT, NULL, 1, &accept);
3748
3749 return 0;
3750}
3751
3766
3775
3790
3792{
3793 return sub->body_generator->type;
3794}
3795
3797{
3798 return sub->body_generator->subtype;
3799}
3800
3802 struct ast_sip_body_data *data, struct ast_str **str)
3803{
3804 struct ast_sip_pubsub_body_supplement *supplement;
3805 struct ast_sip_pubsub_body_generator *generator;
3806 int res = 0;
3807 void *body;
3808
3810 if (!generator) {
3811 ast_log(LOG_WARNING, "Unable to find a body generator for %s/%s\n",
3812 type, subtype);
3813 return -1;
3814 }
3815
3816 if (strcmp(data->body_type, generator->body_type)) {
3817 ast_log(LOG_WARNING, "%s/%s body generator does not accept the type of data provided\n",
3818 type, subtype);
3819 return -1;
3820 }
3821
3822 body = generator->allocate_body(data->body_data);
3823 if (!body) {
3824 ast_log(LOG_WARNING, "%s/%s body generator could not to allocate a body\n",
3825 type, subtype);
3826 return -1;
3827 }
3828
3829 if (generator->generate_body_content(body, data->body_data)) {
3830 res = -1;
3831 goto end;
3832 }
3833
3836 if (!strcmp(generator->type, supplement->type) &&
3837 !strcmp(generator->subtype, supplement->subtype)) {
3838 res = supplement->supplement_body(body, data->body_data);
3839 if (res) {
3840 break;
3841 }
3842 }
3843 }
3845
3846 if (!res) {
3847 generator->to_string(body, str);
3848 }
3849
3850end:
3851 if (generator->destroy_body) {
3852 generator->destroy_body(body);
3853 }
3854
3855 return res;
3856}
3857
3866
3867static int parse_simple_message_summary(char *body,
3868 struct simple_message_summary *summary)
3869{
3870 char *line;
3871 char *buffer;
3872 int found_counts = 0;
3873
3874 if (ast_strlen_zero(body) || !summary) {
3875 return -1;
3876 }
3877
3878 buffer = ast_strdupa(body);
3879 memset(summary, 0, sizeof(*summary));
3880
3881 while ((line = ast_read_line_from_buffer(&buffer))) {
3882 line = ast_str_to_lower(line);
3883
3884 if (sscanf(line, "voice-message: %d/%d (%d/%d)",
3885 &summary->voice_messages_new, &summary->voice_messages_old,
3887 found_counts = 1;
3888 } else {
3889 sscanf(line, "message-account: %511s", summary->message_account);
3890 }
3891 }
3892
3893 return !found_counts;
3894}
3895
3896static pj_bool_t pubsub_on_rx_mwi_notify_request(pjsip_rx_data *rdata)
3897{
3898 RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
3899 struct simple_message_summary summary;
3900 const char *endpoint_name;
3901 char *atsign;
3902 char *context;
3903 char *body;
3904 char *mailbox;
3905 int body_len;
3906 int rc;
3907
3908 endpoint = ast_pjsip_rdata_get_endpoint(rdata);
3909 if (!endpoint) {
3910 ast_debug(1, "Incoming MWI: Endpoint not found in rdata (%p)\n", rdata);
3911 rc = 404;
3912 goto error;
3913 }
3914
3915 endpoint_name = ast_sorcery_object_get_id(endpoint);
3916 ast_debug(1, "Incoming MWI: Found endpoint: %s\n", endpoint_name);
3917 if (ast_strlen_zero(endpoint->incoming_mwi_mailbox)) {
3918 ast_debug(1, "Incoming MWI: No incoming mailbox specified for endpoint '%s'\n", endpoint_name);
3919 ast_test_suite_event_notify("PUBSUB_NO_INCOMING_MWI_MAILBOX",
3920 "Endpoint: %s", endpoint_name);
3921 rc = 404;
3922 goto error;
3923 }
3924
3925 mailbox = ast_strdupa(endpoint->incoming_mwi_mailbox);
3926 atsign = strchr(mailbox, '@');
3927 if (!atsign) {
3928 ast_debug(1, "Incoming MWI: No '@' found in endpoint %s's incoming mailbox '%s'. Can't parse context\n",
3929 endpoint_name, endpoint->incoming_mwi_mailbox);
3930 rc = 404;
3931 goto error;
3932 }
3933
3934 *atsign = '\0';
3935 context = atsign + 1;
3936
3937 body = ast_alloca(rdata->msg_info.msg->body->len + 1);
3938 body_len = rdata->msg_info.msg->body->print_body(rdata->msg_info.msg->body, body,
3939 rdata->msg_info.msg->body->len + 1);
3940
3941 if (body_len < 0 || body_len > rdata->msg_info.msg->body->len) {
3942 ast_debug(1, "Incoming MWI: Endpoint: '%s' Unable to print request body\n", endpoint_name);
3943 rc = 404;
3944 goto error;
3945 }
3946 body[body_len] = '\0';
3947
3948 if (parse_simple_message_summary(body, &summary) != 0) {
3949 ast_debug(1, "Incoming MWI: Endpoint: '%s' There was an issue getting message info from body '%s'\n",
3950 ast_sorcery_object_get_id(endpoint), body);
3951 rc = 404;
3952 goto error;
3953 }
3954
3955 if (ast_publish_mwi_state(mailbox, context,
3956 summary.voice_messages_new, summary.voice_messages_old)) {
3957 ast_log(LOG_ERROR, "Incoming MWI: Endpoint: '%s' Could not publish MWI to stasis. "
3958 "Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n",
3959 endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,
3960 summary.voice_messages_new, summary.voice_messages_old,
3962 rc = 404;
3963 } else {
3964 ast_debug(1, "Incoming MWI: Endpoint: '%s' Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n",
3965 endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,
3966 summary.voice_messages_new, summary.voice_messages_old,
3968 ast_test_suite_event_notify("PUBSUB_INCOMING_MWI_PUBLISH",
3969 "Endpoint: %s\r\n"
3970 "Mailbox: %s\r\n"
3971 "MessageAccount: %s\r\n"
3972 "VoiceMessagesNew: %d\r\n"
3973 "VoiceMessagesOld: %d\r\n"
3974 "VoiceMessagesUrgentNew: %d\r\n"
3975 "VoiceMessagesUrgentOld: %d",
3976 endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,
3977 summary.voice_messages_new, summary.voice_messages_old,
3979 rc = 200;
3980 }
3981
3982error:
3983 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, rc, NULL, NULL, NULL);
3984 return PJ_TRUE;
3985}
3986
3987static pj_bool_t pubsub_on_rx_notify_request(pjsip_rx_data *rdata)
3988{
3989 if (rdata->msg_info.msg->body &&
3990 ast_sip_is_content_type(&rdata->msg_info.msg->body->content_type,
3991 "application", "simple-message-summary")) {
3992 return pubsub_on_rx_mwi_notify_request(rdata);
3993 }
3994 return PJ_FALSE;
3995}
3996
3997static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)
3998{
3999 if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method())) {
4000 return pubsub_on_rx_subscribe_request(rdata);
4001 } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_publish_method)) {
4002 return pubsub_on_rx_publish_request(rdata);
4003 } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_notify_method)) {
4004 return pubsub_on_rx_notify_request(rdata);
4005 }
4006
4007 return PJ_FALSE;
4008}
4009
4010/*!
4011 * \brief Callback sequence for subscription terminate:
4012 *
4013 * * Please note that the descriptions below represent pjproject behavior on versions
4014 * >= 2.13.
4015 * * Client initiated:
4016 * pjproject receives SUBSCRIBE on the subscription's serializer thread
4017 * calls pubsub_evsub_set_state with state = TERMINATED
4018 * pubsub_on_evsub_state checks the event and finds it is due to a received
4019 * SUBSCRIBE with an expires of 0 and so does nothing.
4020 * calls pubsub_on_rx_refresh with dialog locked
4021 * pubsub_on_rx_refresh sets TERMINATE_PENDING
4022 * calls pubsub_on_refresh_timeout to push final NOTIFY to pjproject
4023 * checks state == TERMINATE_PENDING
4024 * sets TERMINATE_IN_PROGRESS
4025 * calls send_notify (2)
4026 * send_notify ultimately calls pjsip_evsub_send_request
4027 * pjsip_evsub_send_request calls evsub's set_state
4028 * set_state calls pubsub_evsub_set_state
4029 * pubsub_on_evsub_state checks state == TERMINATE_IN_PROGRESS
4030 * removes the subscriptions
4031 * cleans up references to evsub
4032 * sets state = TERMINATED
4033 * pubsub_on_refresh_timeout unlocks dialog
4034 * returns to pjproject
4035 * pjproject unlocks dialog
4036 *
4037 * * Subscription timer expires:
4038 * pjproject timer expires
4039 * locks dialog
4040 * calls pubsub_on_server_timeout
4041 * pubsub_on_server_timeout checks state == NORMAL
4042 * sets TERMINATE_PENDING
4043 * pushes serialized_pubsub_on_refresh_timeout
4044 * returns to pjproject
4045 * pjproject unlocks dialog
4046 * serialized_pubsub_on_refresh_timeout starts (1)
4047 * locks dialog
4048 * checks state == TERMINATE_PENDING
4049 * sets TERMINATE_IN_PROGRESS
4050 * calls send_notify (2)
4051 * send_notify ultimately calls pjsip_evsub_send_request
4052 * pjsip_evsub_send_request calls evsub's set_state
4053 * set_state calls pubsub_evsub_set_state
4054 * pubsub_on_evsub_state checks state == TERMINATE_IN_PROGRESS
4055 * checks that the event is not due to un-SUBSCRIBE
4056 * removes the subscriptions
4057 * cleans up references to evsub
4058 * sets state = TERMINATED
4059 * serialized_pubsub_on_refresh_timeout unlocks dialog
4060 *
4061 * * Transmission failure sending NOTIFY or error response from client
4062 * pjproject transaction timer expires or non OK response
4063 * pjproject locks dialog
4064 * calls pubsub_on_evsub_state with event TSX_STATE
4065 * pubsub_on_evsub_state checks event == TSX_STATE
4066 * removes the subscriptions
4067 * cleans up references to evsub
4068 * sets state = TERMINATED
4069 * pjproject unlocks dialog
4070 *
4071 * * ast_sip_subscription_notify is called
4072 * checks state == NORMAL
4073 * if not batched...
4074 * sets TERMINATE_IN_PROGRESS (if terminate is requested)
4075 * calls send_notify
4076 * See (2) Above
4077 * if batched...
4078 * sets TERMINATE_PENDING
4079 * schedules task
4080 * scheduler runs sched_task
4081 * sched_task pushes serialized_send_notify
4082 * serialized_send_notify starts
4083 * checks state <= TERMINATE_PENDING
4084 * if state == TERMINATE_PENDING set state = TERMINATE_IN_PROGRESS
4085 * call send_notify
4086 * See (2) Above
4087 *
4088 */
4089
4090
4091/* The code in this function was previously in pubsub_on_evsub_state. */
4092static void clean_sub_tree(pjsip_evsub *evsub)
4093{
4094
4095 struct sip_subscription_tree *sub_tree;
4096 sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4097
4098 ast_debug(3, "Cleaning subscription %p\n", evsub);
4099
4100 if (sub_tree->expiration_task) {
4101 char task_name[256];
4102
4103 ast_sip_sched_task_get_name(sub_tree->expiration_task, task_name, sizeof(task_name));
4104 ast_debug(3, "Cancelling timer: %s\n", task_name);
4106 ao2_cleanup(sub_tree->expiration_task);
4107 sub_tree->expiration_task = NULL;
4108 }
4109
4110 remove_subscription(sub_tree);
4111
4112 pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL);
4113
4114#ifdef HAVE_PJSIP_EVSUB_GRP_LOCK
4115 pjsip_evsub_dec_ref(sub_tree->evsub);
4116#endif
4117
4118 sub_tree->evsub = NULL;
4119
4122
4124 shutdown_subscriptions(sub_tree->root);
4125
4126 sub_tree->state = SIP_SUB_TREE_TERMINATED;
4127 /* Remove evsub's reference to the sub_tree */
4128 ao2_ref(sub_tree, -1);
4129}
4130
4131/* This functionality appeared in pjsip 2.13 */
4132#if PJ_VERSION_NUM >= 0x020D0000
4133# define HAVE_PJSIP_EVSUB_PENDING_NOTIFY 1
4134#endif
4135
4136/*!
4137 * \brief PJSIP callback when underlying SIP subscription changes state
4138 *
4139 * Although this function is called for every state change, we only care
4140 * about the TERMINATED state, and only when we're actually processing the final
4141 * notify (SIP_SUB_TREE_TERMINATE_IN_PROGRESS) OR when a transmission failure
4142 * occurs (PJSIP_EVENT_TSX_STATE). In this case, we do all the subscription tree
4143 * cleanup tasks and decrement the evsub reference.
4144 */
4145static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
4146{
4147 struct sip_subscription_tree *sub_tree =
4148 pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4149
4150 ast_debug(3, "evsub %p state %s event %s sub_tree %p sub_tree state %s\n", evsub,
4151 pjsip_evsub_get_state_name(evsub), pjsip_event_str(event->type), sub_tree,
4152 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4153
4154 if (!sub_tree || pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
4155 return;
4156 }
4157
4158 /* It's easier to write this as what we WANT to process, then negate it. */
4159 if (!(sub_tree->state == SIP_SUB_TREE_TERMINATE_IN_PROGRESS
4160 || (event->type == PJSIP_EVENT_TSX_STATE && sub_tree->state == SIP_SUB_TREE_NORMAL)
4161 )) {
4162 ast_debug(3, "Do nothing.\n");
4163 return;
4164 }
4165
4166#ifdef HAVE_PJSIP_EVSUB_PENDING_NOTIFY
4167 /* This check looks for re-subscribes with an expires of 0. If we receive one of those,
4168 we don't want to clean the evsub because we still need it to send the final NOTIFY.
4169 This was previously handled by pubsub_on_rx_refresh setting:
4170 'sub_tree->state = SIP_SUB_TREE_TERMINATE_PENDING' */
4171 if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
4172 !pjsip_method_cmp(&event->body.tsx_state.tsx->method, &pjsip_subscribe_method) &&
4173 pjsip_evsub_get_expires(evsub) == 0) {
4174 ast_debug(3, "Subscription ending, do nothing.\n");
4175 return;
4176 }
4177#endif
4178 /* If we made it this far, we want to clean the sub tree. For pjproject <2.13, the sub_tree
4179 state check makes sure the evsub is not cleaned at the wrong time */
4181}
4182
4183static int pubsub_on_refresh_timeout(void *userdata)
4184{
4185 struct sip_subscription_tree *sub_tree = userdata;
4186 pjsip_dialog *dlg = sub_tree->dlg;
4187
4188 ast_debug(3, "sub_tree %p sub_tree state %s\n", sub_tree,
4189 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4190
4191 pjsip_dlg_inc_lock(dlg);
4192 if (sub_tree->state >= SIP_SUB_TREE_TERMINATE_IN_PROGRESS) {
4193 pjsip_dlg_dec_lock(dlg);
4194 return 0;
4195 }
4196
4197 if (sub_tree->state == SIP_SUB_TREE_TERMINATE_PENDING) {
4199 set_state_terminated(sub_tree->root);
4200 }
4201
4202 if (sub_tree->generate_initial_notify) {
4203 sub_tree->generate_initial_notify = 0;
4204 if (generate_initial_notify(sub_tree->root)) {
4205 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
4206 pjsip_dlg_dec_lock(dlg);
4207 return 0;
4208 }
4209 }
4210
4211 send_notify(sub_tree, 1);
4212
4213 ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ?
4214 "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED",
4215 "Resource: %s", sub_tree->root->resource);
4216
4217 pjsip_dlg_dec_lock(dlg);
4218
4219 return 0;
4220}
4221
4223{
4224 struct sip_subscription_tree *sub_tree = userdata;
4225
4226 ast_debug(3, "sub_tree %p sub_tree state %s\n", sub_tree,
4227 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4228
4229 pubsub_on_refresh_timeout(userdata);
4230 ao2_cleanup(sub_tree);
4231
4232 return 0;
4233}
4234
4235/*!
4236 * \brief Compare strings for equality checking for NULL.
4237 *
4238 * This function considers NULL values as empty strings.
4239 * This means NULL or empty strings are equal.
4240 *
4241 * \retval 0 The strings are equal
4242 * \retval 1 The strings are not equal
4243 */
4244static int cmp_strings(char *s1, char *s2)
4245{
4246 if (!ast_strlen_zero(s1) && !ast_strlen_zero(s2)) {
4247 return strcmp(s1, s2);
4248 }
4249
4250 return ast_strlen_zero(s1) == ast_strlen_zero(s2) ? 0 : 1;
4251}
4252
4253/*!
4254 * \brief compares the childrens of two ast_sip_subscription s1 and s2
4255 *
4256 * \retval 0 The s1 childrens match the s2 childrens
4257 * \retval 1 The s1 childrens do not match the s2 childrens
4258 */
4260{
4261 int i;
4262
4263 if (AST_VECTOR_SIZE(&s1->children) != AST_VECTOR_SIZE(&s2->children)) {
4264 return 1;
4265 }
4266
4267 for (i = 0; i < AST_VECTOR_SIZE(&s1->children); ++i) {
4268 struct ast_sip_subscription *c1 = AST_VECTOR_GET(&s1->children, i);
4269 struct ast_sip_subscription *c2 = AST_VECTOR_GET(&s2->children, i);
4270
4271 if (cmp_strings(c1->resource, c2->resource)
4272 || cmp_strings(c1->display_name, c2->display_name)) {
4273
4274 return 1;
4275 }
4276 }
4277
4278 return 0;
4279}
4280
4281static int destroy_subscriptions_task(void *obj)
4282{
4283 struct ast_sip_subscription *sub = (struct ast_sip_subscription *) obj;
4284
4286
4287 return 0;
4288}
4289
4290/*!
4291 * \brief Called whenever an in-dialog SUBSCRIBE is received
4292 *
4293 * This includes both SUBSCRIBE requests that actually refresh the subscription
4294 * as well as SUBSCRIBE requests that end the subscription.
4295 *
4296 * In either case we push an appropriate NOTIFY via pubsub_on_refresh_timeout.
4297 */
4298static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
4299 int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
4300{
4301 struct sip_subscription_tree *sub_tree;
4303
4304 sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4305 ast_debug(3, "evsub %p sub_tree %p sub_tree state %s\n", evsub, sub_tree,
4306 (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
4307
4308 if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) {
4309 return;
4310 }
4311
4312 if (sub_tree->expiration_task) {
4313 char task_name[256];
4314
4315 ast_sip_sched_task_get_name(sub_tree->expiration_task, task_name, sizeof(task_name));
4316 ast_debug(3, "Cancelling timer: %s\n", task_name);
4318 ao2_cleanup(sub_tree->expiration_task);
4319 sub_tree->expiration_task = NULL;
4320 }
4321
4322 /* PJSIP will set the evsub's state to terminated before calling into this function
4323 * if the Expires value of the incoming SUBSCRIBE is 0.
4324 */
4325
4326 if (pjsip_evsub_get_state(sub_tree->evsub) == PJSIP_EVSUB_STATE_TERMINATED) {
4328 }
4329
4331
4332 /* If the handler wants a callback on refresh, then do it (some protocols require this). */
4333 if (sub_tree->state == SIP_SUB_TREE_NORMAL && sub_tree->root->handler->notifier->refresh_subscribe) {
4334 if (!sub_tree->root->handler->notifier->refresh_subscribe(sub_tree->root, rdata)) {
4335 return; /* If the callback handled it, we're done. */
4336 }
4337 }
4338
4339 if (sub_tree->state == SIP_SUB_TREE_NORMAL && sub_tree->is_list) {
4340 /* update RLS */
4341 const char *resource = sub_tree->root->resource;
4342 struct ast_sip_subscription *old_root = sub_tree->root;
4343 struct ast_sip_subscription *new_root = NULL;
4344
4345 struct ast_sip_pubsub_body_generator *generator = NULL;
4346
4347 if (endpoint && (generator = subscription_get_generator_from_rdata(rdata, sub_tree->root->handler))) {
4348
4349 struct resource_tree tree;
4350 int resp;
4351
4352 memset(&tree, 0, sizeof(tree));
4353 resp = build_resource_tree(endpoint, sub_tree->root->handler, resource, &tree,
4355 if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
4356 new_root = create_virtual_subscriptions(sub_tree->root->handler, resource, generator, sub_tree, tree.root);
4357 if (new_root) {
4358 if (cmp_subscription_childrens(old_root, new_root)) {
4359 ast_debug(1, "RLS '%s->%s' was modified, regenerate it\n", ast_sorcery_object_get_id(endpoint), old_root->resource);
4360 new_root->version = old_root->version;
4361 sub_tree->root = new_root;
4362 sub_tree->generate_initial_notify = 1;
4363
4364 /* If there is scheduled notification need to delete it to avoid use old subscriptions */
4365 if (sub_tree->notify_sched_id > -1) {
4366 AST_SCHED_DEL_UNREF(sched, sub_tree->notify_sched_id, ao2_ref(sub_tree, -1));
4367 sub_tree->send_scheduled_notify = 0;
4368 }
4369
4370 /* Terminate old subscriptions to stop sending NOTIFY messages on exten/device state changes */
4371 set_state_terminated(old_root);
4372
4373 /* Shutdown old subscriptions to remove exten/device state change callbacks
4374 that can queue tasks for old subscriptions */
4375 shutdown_subscriptions(old_root);
4376
4377 /* Postpone destruction until all already queued tasks that may be using old subscriptions have completed */
4378 if (ast_sip_push_task(sub_tree->serializer, destroy_subscriptions_task, old_root)) {
4379 ast_log(LOG_ERROR, "Failed to push task to destroy old subscriptions for RLS '%s->%s'.\n",
4380 ast_sorcery_object_get_id(endpoint), old_root->resource);
4381 }
4382 } else {
4383 destroy_subscriptions(new_root);
4384 }
4385 }
4386 } else {
4388 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
4389 }
4390
4391 resource_tree_destroy(&tree);
4392 }
4393 }
4394
4396
4397#ifdef HAVE_PJSIP_EVSUB_PENDING_NOTIFY
4398 /* As of pjsip 2.13, the NOTIFY has to be sent within this function as pjproject now
4399 requires it. Previously this would have caused an early NOTIFY to go out before the
4400 SUBSCRIBE's 200 OK. The previous solution was to push the NOTIFY, but now pjproject
4401 looks for the NOTIFY to be sent from this function and caches it to send after it
4402 auto-replies to the SUBSCRIBE. */
4403 pubsub_on_refresh_timeout(sub_tree);
4404#else
4406 /* If we can't push the NOTIFY refreshing task...we'll just go with it. */
4407 ast_log(LOG_ERROR, "Failed to push task to send NOTIFY.\n");
4408 sub_tree->state = SIP_SUB_TREE_NORMAL;
4409 ao2_ref(sub_tree, -1);
4410 }
4411#endif
4412
4413 if (sub_tree->is_list) {
4414 pj_list_insert_before(res_hdr, create_require_eventlist(rdata->tp_info.pool));
4415 }
4416}
4417
4418static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code,
4419 pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
4420{
4421 struct ast_sip_subscription *sub;
4422
4423 if (!(sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id))) {
4424 return;
4425 }
4426
4427 sub->handler->subscriber->state_change(sub, rdata->msg_info.msg->body,
4428 pjsip_evsub_get_state(evsub));
4429}
4430
4431static int serialized_pubsub_on_client_refresh(void *userdata)
4432{
4433 struct sip_subscription_tree *sub_tree = userdata;
4434 pjsip_tx_data *tdata;
4435
4436 if (!sub_tree->evsub) {
4437 ao2_cleanup(sub_tree);
4438 return 0;
4439 }
4440
4441 if (pjsip_evsub_initiate(sub_tree->evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
4442 pjsip_evsub_send_request(sub_tree->evsub, tdata);
4443 } else {
4444 pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
4445 }
4446
4447 ao2_cleanup(sub_tree);
4448 return 0;
4449}
4450
4451static void pubsub_on_client_refresh(pjsip_evsub *evsub)
4452{
4453 struct sip_subscription_tree *sub_tree;
4454
4455 if (!(sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id))) {
4456 return;
4457 }
4458
4460 ao2_cleanup(sub_tree);
4461 }
4462}
4463
4464static void pubsub_on_server_timeout(pjsip_evsub *evsub)
4465{
4466 struct sip_subscription_tree *sub_tree;
4467
4468 /* PJSIP does not terminate the server timeout timer when a SUBSCRIBE
4469 * with Expires: 0 arrives to end a subscription, nor does it terminate
4470 * this timer when we send a NOTIFY request in response to receiving such
4471 * a SUBSCRIBE. PJSIP does not stop the server timeout timer until the
4472 * NOTIFY transaction has finished (either through receiving a response
4473 * or through a transaction timeout).
4474 *
4475 * Therefore, it is possible that we can be told that a server timeout
4476 * occurred after we already thought that the subscription had been
4477 * terminated. In such a case, we will have already removed the sub_tree
4478 * from the evsub's mod_data array.
4479 */
4480
4481 sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
4482 if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) {
4483 return;
4484 }
4485
4488 sub_tree->state = SIP_SUB_TREE_NORMAL;
4489 ao2_cleanup(sub_tree);
4490 }
4491}
4492
4494 struct ast_sip_ami *ami,
4495 const char *event)
4496{
4497 struct ast_str *buf;
4498
4500 if (!buf) {
4501 return -1;
4502 }
4503
4504 sip_subscription_to_ami(sub_tree, &buf);
4505 astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
4506 ast_free(buf);
4507
4508 ++ami->count;
4509 return 0;
4510}
4511
4512static int ami_subscription_detail_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4513{
4514 return sub_tree->role == AST_SIP_NOTIFIER ? ami_subscription_detail(
4515 sub_tree, arg, "InboundSubscriptionDetail") : 0;
4516}
4517
4518static int ami_subscription_detail_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4519{
4520 return sub_tree->role == AST_SIP_SUBSCRIBER ? ami_subscription_detail(
4521 sub_tree, arg, "OutboundSubscriptionDetail") : 0;
4522}
4523
4524static int ami_show_subscriptions_inbound(struct mansession *s, const struct message *m)
4525{
4526 struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
4527
4528 astman_send_listack(s, m, "Following are Events for each inbound Subscription",
4529 "start");
4530
4532
4533 astman_send_list_complete_start(s, m, "InboundSubscriptionDetailComplete", ami.count);
4535 return 0;
4536}
4537
4538static int ami_show_subscriptions_outbound(struct mansession *s, const struct message *m)
4539{
4540 struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
4541
4542 astman_send_listack(s, m, "Following are Events for each outbound Subscription",
4543 "start");
4544
4546
4547 astman_send_list_complete_start(s, m, "OutboundSubscriptionDetailComplete", ami.count);
4549 return 0;
4550}
4551
4552static int format_ami_resource_lists(void *obj, void *arg, int flags)
4553{
4554 struct resource_list *list = obj;
4555 struct ast_sip_ami *ami = arg;
4556 struct ast_str *buf;
4557
4558 buf = ast_sip_create_ami_event("ResourceListDetail", ami);
4559 if (!buf) {
4560 return CMP_STOP;
4561 }
4562
4563 if (ast_sip_sorcery_object_to_ami(list, &buf)) {
4564 ast_free(buf);
4565 return CMP_STOP;
4566 }
4567 astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
4568 ast_free(buf);
4569
4570 ++ami->count;
4571 return 0;
4572}
4573
4574static int ami_show_resource_lists(struct mansession *s, const struct message *m)
4575{
4576 struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
4577 struct ao2_container *lists;
4578
4579 lists = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "resource_list",
4581
4582 if (!lists || !ao2_container_count(lists)) {
4583 astman_send_error(s, m, "No resource lists found\n");
4584 return 0;
4585 }
4586
4587 astman_send_listack(s, m, "A listing of resource lists follows, presented as ResourceListDetail events",
4588 "start");
4589
4591
4592 astman_send_list_complete_start(s, m, "ResourceListDetailComplete", ami.count);
4594 return 0;
4595}
4596
4597#define AMI_SHOW_SUBSCRIPTIONS_INBOUND "PJSIPShowSubscriptionsInbound"
4598#define AMI_SHOW_SUBSCRIPTIONS_OUTBOUND "PJSIPShowSubscriptionsOutbound"
4599
4600#define MAX_REGEX_ERROR_LEN 128
4601
4603 /*! CLI handler entry e parameter */
4605 /*! CLI handler entry a parameter */
4607 /*! CLI subscription entry output line(s) */
4608 struct ast_str *buf;
4609 /*! Compiled regular expression to select if buf is written to CLI when not NULL. */
4610 regex_t *like;
4612};
4613
4616 /*! Found callid for search position */
4617 char *callid;
4620};
4621
4623{
4624 pj_str_t *callid;
4625
4626 if (!sub_tree->dlg) {
4627 return 0;
4628 }
4629
4630 callid = &sub_tree->dlg->call_id->id;
4631 if (cli->wordlen <= pj_strlen(callid)
4632 && !strncasecmp(cli->a->word, pj_strbuf(callid), cli->wordlen)
4633 && (++cli->which > cli->a->n)) {
4634 cli->callid = ast_malloc(pj_strlen(callid) + 1);
4635 if (cli->callid) {
4636 ast_copy_pj_str(cli->callid, callid, pj_strlen(callid) + 1);
4637 }
4638 return -1;
4639 }
4640 return 0;
4641}
4642
4643static int cli_complete_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4644{
4645 return sub_tree->role == AST_SIP_NOTIFIER
4646 ? cli_complete_subscription_common(sub_tree, arg) : 0;
4647}
4648
4649static int cli_complete_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4650{
4651 return sub_tree->role == AST_SIP_SUBSCRIBER
4652 ? cli_complete_subscription_common(sub_tree, arg) : 0;
4653}
4654
4656{
4658 on_subscription_t on_subscription;
4659
4660 if (a->pos != 4) {
4661 return NULL;
4662 }
4663
4664 if (!strcasecmp(a->argv[3], "inbound")) {
4665 on_subscription = cli_complete_subscription_inbound;
4666 } else if (!strcasecmp(a->argv[3], "outbound")) {
4667 on_subscription = cli_complete_subscription_outbound;
4668 } else {
4669 /* Should never get here */
4670 ast_assert(0);
4671 return NULL;
4672 }
4673
4674 cli.a = a;
4675 cli.callid = NULL;
4676 cli.wordlen = strlen(a->word);
4677 cli.which = 0;
4678 for_each_subscription(on_subscription, &cli);
4679
4680 return cli.callid;
4681}
4682
4683static unsigned int cli_subscription_expiry(struct sip_subscription_tree *sub_tree)
4684{
4685 int expiry;
4686
4687 expiry = sub_tree->persistence
4688 ? ast_tvdiff_ms(sub_tree->persistence->expires, ast_tvnow()) / 1000
4689 : 0;
4690 if (expiry < 0) {
4691 /* Subscription expired */
4692 expiry = 0;
4693 }
4694 return expiry;
4695}
4696
4698{
4699 const char *callid = (const char *) cli->buf;/* Member repurposed to pass in callid */
4700 pj_str_t *sub_callid;
4701 struct ast_str *buf;
4702 char *src;
4703 char *dest;
4704 char *key;
4705 char *value;
4706 char *value_end;
4707 int key_len;
4708 int key_filler_width;
4709 int value_len;
4710
4711 if (!sub_tree->dlg) {
4712 return 0;
4713 }
4714 sub_callid = &sub_tree->dlg->call_id->id;
4715 if (pj_strcmp2(sub_callid, callid)) {
4716 return 0;
4717 }
4718
4719 buf = ast_str_create(512);
4720 if (!buf) {
4721 return -1;
4722 }
4723
4724 ast_cli(cli->a->fd,
4725 "%-20s: %s\n"
4726 "===========================================================================\n",
4727 "ParameterName", "ParameterValue");
4728
4729 ast_str_append(&buf, 0, "Resource: %s\n", sub_tree->root->resource);
4730 ast_str_append(&buf, 0, "Event: %s\n", sub_tree->root->handler->event_name);
4731 ast_str_append(&buf, 0, "Expiry: %u\n", cli_subscription_expiry(sub_tree));
4732
4733 sip_subscription_to_ami(sub_tree, &buf);
4734
4735 /* Convert AMI \r\n to \n line terminators. */
4736 src = strchr(ast_str_buffer(buf), '\r');
4737 if (src) {
4738 dest = src;
4739 ++src;
4740 while (*src) {
4741 if (*src == '\r') {
4742 ++src;
4743 continue;
4744 }
4745 *dest++ = *src++;
4746 }
4747 *dest = '\0';
4749 }
4750
4751 /* Reformat AMI key value pairs to pretty columns */
4752 key = ast_str_buffer(buf);
4753 do {
4754 value = strchr(key, ':');
4755 if (!value) {
4756 break;
4757 }
4758 value_end = strchr(value, '\n');
4759 if (!value_end) {
4760 break;
4761 }
4762
4763 /* Calculate field lengths */
4764 key_len = value - key;
4765 key_filler_width = 20 - key_len;
4766 if (key_filler_width < 0) {
4767 key_filler_width = 0;
4768 }
4769 value_len = value_end - value;
4770
4771 ast_cli(cli->a->fd, "%.*s%*s%.*s\n",
4772 key_len, key, key_filler_width, "",
4773 value_len, value);
4774
4775 key = value_end + 1;
4776 } while (*key);
4777 ast_cli(cli->a->fd, "\n");
4778
4779 ast_free(buf);
4780
4781 return -1;
4782}
4783
4784static int cli_show_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4785{
4786 return sub_tree->role == AST_SIP_NOTIFIER
4787 ? cli_show_subscription_common(sub_tree, arg) : 0;
4788}
4789
4790static int cli_show_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4791{
4792 return sub_tree->role == AST_SIP_SUBSCRIBER
4793 ? cli_show_subscription_common(sub_tree, arg) : 0;
4794}
4795
4796static char *cli_show_subscription_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
4797{
4798 on_subscription_t on_subscription;
4799 struct cli_sub_parms cli;
4800
4801 switch (cmd) {
4802 case CLI_INIT:
4803 e->command = "pjsip show subscription {inbound|outbound}";
4804 e->usage = "Usage:\n"
4805 " pjsip show subscription inbound <call-id>\n"
4806 " pjsip show subscription outbound <call-id>\n"
4807 " Show active subscription with the dialog call-id\n";
4808 return NULL;
4809 case CLI_GENERATE:
4811 }
4812
4813 if (a->argc != 5) {
4814 return CLI_SHOWUSAGE;
4815 }
4816
4817 if (!strcasecmp(a->argv[3], "inbound")) {
4818 on_subscription = cli_show_subscription_inbound;
4819 } else if (!strcasecmp(a->argv[3], "outbound")) {
4820 on_subscription = cli_show_subscription_outbound;
4821 } else {
4822 /* Should never get here */
4823 ast_assert(0);
4824 return NULL;
4825 }
4826
4827 /* Find the subscription with the specified call-id */
4828 cli.a = a;
4829 cli.e = e;
4830 cli.buf = (void *) a->argv[4];/* Repurpose the buf member to pass in callid */
4831 for_each_subscription(on_subscription, &cli);
4832
4833 return CLI_SUCCESS;
4834}
4835
4836#define CLI_SHOW_SUB_FORMAT_HEADER \
4837 "Endpoint: <Endpoint/Caller-ID.............................................>\n" \
4838 "Resource: <Resource/Event.................................................>\n" \
4839 " Expiry: <Expiry> <Call-id..............................................>\n" \
4840 "===========================================================================\n\n"
4841#define CLI_SHOW_SUB_FORMAT_ENTRY \
4842 "Endpoint: %s/%s\n" \
4843 "Resource: %s/%s\n" \
4844 " Expiry: %8d %s\n\n"
4845
4847{
4848 char caller_id[256];
4849 char callid[256];
4850
4851 ast_callerid_merge(caller_id, sizeof(caller_id),
4852 S_COR(sub_tree->endpoint->id.self.name.valid,
4853 sub_tree->endpoint->id.self.name.str, NULL),
4854 S_COR(sub_tree->endpoint->id.self.number.valid,
4855 sub_tree->endpoint->id.self.number.str, NULL),
4856 "<none>");
4857
4858 /* Call-id */
4859 if (sub_tree->dlg) {
4860 ast_copy_pj_str(callid, &sub_tree->dlg->call_id->id, sizeof(callid));
4861 } else {
4862 ast_copy_string(callid, "<unknown>", sizeof(callid));
4863 }
4864
4866 ast_sorcery_object_get_id(sub_tree->endpoint), caller_id,
4867 sub_tree->root->resource, sub_tree->root->handler->event_name,
4868 cli_subscription_expiry(sub_tree), callid);
4869
4870 if (cli->like) {
4871 if (regexec(cli->like, ast_str_buffer(cli->buf), 0, NULL, 0)) {
4872 /* Output line did not match the regex */
4873 return 0;
4874 }
4875 }
4876
4877 ast_cli(cli->a->fd, "%s", ast_str_buffer(cli->buf));
4878 ++cli->count;
4879
4880 return 0;
4881}
4882
4883static int cli_show_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
4884{
4885 return sub_tree->role == AST_SIP_NOTIFIER
4886 ? cli_show_subscriptions_detail(sub_tree, arg) : 0;
4887}
4888
4889static int cli_show_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
4890{
4891 return sub_tree->role == AST_SIP_SUBSCRIBER
4892 ? cli_show_subscriptions_detail(sub_tree, arg) : 0;
4893}
4894
4895static char *cli_show_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
4896{
4897 on_subscription_t on_subscription;
4898 struct cli_sub_parms cli;
4899 regex_t like;
4900 const char *regex;
4901
4902 switch (cmd) {
4903 case CLI_INIT:
4904 e->command = "pjsip show subscriptions {inbound|outbound} [like]";
4905 e->usage = "Usage:\n"
4906 " pjsip show subscriptions inbound [like <regex>]\n"
4907 " Show active inbound subscriptions\n"
4908 " pjsip show subscriptions outbound [like <regex>]\n"
4909 " Show active outbound subscriptions\n"
4910 "\n"
4911 " The regex selects a subscriptions output that matches.\n"
4912 " i.e., All output lines for a subscription are checked\n"
4913 " as a block by the regex.\n";
4914 return NULL;
4915 case CLI_GENERATE:
4916 return NULL;
4917 }
4918
4919 if (a->argc != 4 && a->argc != 6) {
4920 return CLI_SHOWUSAGE;
4921 }
4922 if (!strcasecmp(a->argv[3], "inbound")) {
4923 on_subscription = cli_show_subscriptions_inbound;
4924 } else if (!strcasecmp(a->argv[3], "outbound")) {
4925 on_subscription = cli_show_subscriptions_outbound;
4926 } else {
4927 /* Should never get here */
4928 ast_assert(0);
4929 return CLI_SHOWUSAGE;
4930 }
4931 if (a->argc == 6) {
4932 int rc;
4933
4934 if (strcasecmp(a->argv[4], "like")) {
4935 return CLI_SHOWUSAGE;
4936 }
4937
4938 /* Setup regular expression */
4939 memset(&like, 0, sizeof(like));
4940 cli.like = &like;
4941 regex = a->argv[5];
4942 rc = regcomp(cli.like, regex, REG_EXTENDED | REG_NOSUB);
4943 if (rc) {
4944 char *regerr = ast_alloca(MAX_REGEX_ERROR_LEN);
4945
4946 regerror(rc, cli.like, regerr, MAX_REGEX_ERROR_LEN);
4947 ast_cli(a->fd, "Regular expression '%s' failed to compile: %s\n",
4948 regex, regerr);
4949 return CLI_FAILURE;
4950 }
4951 } else {
4952 cli.like = NULL;
4953 regex = NULL;
4954 }
4955
4956 cli.a = a;
4957 cli.e = e;
4958 cli.count = 0;
4959 cli.buf = ast_str_create(256);
4960 if (!cli.buf) {
4961 if (cli.like) {
4962 regfree(cli.like);
4963 }
4964 return CLI_FAILURE;
4965 }
4966
4968 for_each_subscription(on_subscription, &cli);
4969 ast_cli(a->fd, "%d active subscriptions%s%s%s\n",
4970 cli.count,
4971 regex ? " matched \"" : "",
4972 regex ?: "",
4973 regex ? "\"" : "");
4974
4975 ast_free(cli.buf);
4976 if (cli.like) {
4977 regfree(cli.like);
4978 }
4979
4980 return CLI_SUCCESS;
4981}
4982
4983#define CLI_LIST_SUB_FORMAT_HEADER "%-30.30s %-30.30s %6.6s %s\n"
4984#define CLI_LIST_SUB_FORMAT_ENTRY "%-30.30s %-30.30s %6d %s\n"
4985
4987{
4988 char ep_cid_buf[50];
4989 char res_evt_buf[50];
4990 char callid[256];
4991
4992 /* Endpoint/CID column */
4993 snprintf(ep_cid_buf, sizeof(ep_cid_buf), "%s/%s",
4995 S_COR(sub_tree->endpoint->id.self.name.valid, sub_tree->endpoint->id.self.name.str,
4996 S_COR(sub_tree->endpoint->id.self.number.valid,
4997 sub_tree->endpoint->id.self.number.str, "<none>")));
4998
4999 /* Resource/Event column */
5000 snprintf(res_evt_buf, sizeof(res_evt_buf), "%s/%s",
5001 sub_tree->root->resource,
5002 sub_tree->root->handler->event_name);
5003
5004 /* Call-id column */
5005 if (sub_tree->dlg) {
5006 ast_copy_pj_str(callid, &sub_tree->dlg->call_id->id, sizeof(callid));
5007 } else {
5008 ast_copy_string(callid, "<unknown>", sizeof(callid));
5009 }
5010
5012 ep_cid_buf,
5013 res_evt_buf,
5014 cli_subscription_expiry(sub_tree),
5015 callid);
5016
5017 if (cli->like) {
5018 if (regexec(cli->like, ast_str_buffer(cli->buf), 0, NULL, 0)) {
5019 /* Output line did not match the regex */
5020 return 0;
5021 }
5022 }
5023
5024 ast_cli(cli->a->fd, "%s", ast_str_buffer(cli->buf));
5025 ++cli->count;
5026
5027 return 0;
5028}
5029
5030static int cli_list_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
5031{
5032 return sub_tree->role == AST_SIP_NOTIFIER
5033 ? cli_list_subscriptions_detail(sub_tree, arg) : 0;
5034}
5035
5036static int cli_list_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
5037{
5038 return sub_tree->role == AST_SIP_SUBSCRIBER
5039 ? cli_list_subscriptions_detail(sub_tree, arg) : 0;
5040}
5041
5042static char *cli_list_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
5043{
5044 on_subscription_t on_subscription;
5045 struct cli_sub_parms cli;
5046 regex_t like;
5047 const char *regex;
5048
5049 switch (cmd) {
5050 case CLI_INIT:
5051 e->command = "pjsip list subscriptions {inbound|outbound} [like]";
5052 e->usage = "Usage:\n"
5053 " pjsip list subscriptions inbound [like <regex>]\n"
5054 " List active inbound subscriptions\n"
5055 " pjsip list subscriptions outbound [like <regex>]\n"
5056 " List active outbound subscriptions\n"
5057 "\n"
5058 " The regex selects output lines that match.\n";
5059 return NULL;
5060 case CLI_GENERATE:
5061 return NULL;
5062 }
5063
5064 if (a->argc != 4 && a->argc != 6) {
5065 return CLI_SHOWUSAGE;
5066 }
5067 if (!strcasecmp(a->argv[3], "inbound")) {
5068 on_subscription = cli_list_subscriptions_inbound;
5069 } else if (!strcasecmp(a->argv[3], "outbound")) {
5070 on_subscription = cli_list_subscriptions_outbound;
5071 } else {
5072 /* Should never get here */
5073 ast_assert(0);
5074 return CLI_SHOWUSAGE;
5075 }
5076 if (a->argc == 6) {
5077 int rc;
5078
5079 if (strcasecmp(a->argv[4], "like")) {
5080 return CLI_SHOWUSAGE;
5081 }
5082
5083 /* Setup regular expression */
5084 memset(&like, 0, sizeof(like));
5085 cli.like = &like;
5086 regex = a->argv[5];
5087 rc = regcomp(cli.like, regex, REG_EXTENDED | REG_NOSUB);
5088 if (rc) {
5089 char *regerr = ast_alloca(MAX_REGEX_ERROR_LEN);
5090
5091 regerror(rc, cli.like, regerr, MAX_REGEX_ERROR_LEN);
5092 ast_cli(a->fd, "Regular expression '%s' failed to compile: %s\n",
5093 regex, regerr);
5094 return CLI_FAILURE;
5095 }
5096 } else {
5097 cli.like = NULL;
5098 regex = NULL;
5099 }
5100
5101 cli.a = a;
5102 cli.e = e;
5103 cli.count = 0;
5104 cli.buf = ast_str_create(256);
5105 if (!cli.buf) {
5106 if (cli.like) {
5107 regfree(cli.like);
5108 }
5109 return CLI_FAILURE;
5110 }
5111
5113 "Endpoint/CLI", "Resource/Event", "Expiry", "Call-id");
5114 for_each_subscription(on_subscription, &cli);
5115 ast_cli(a->fd, "\n%d active subscriptions%s%s%s\n",
5116 cli.count,
5117 regex ? " matched \"" : "",
5118 regex ?: "",
5119 regex ? "\"" : "");
5120
5121 ast_free(cli.buf);
5122 if (cli.like) {
5123 regfree(cli.like);
5124 }
5125
5126 return CLI_SUCCESS;
5127}
5128
5129static struct ast_cli_entry cli_commands[] = {
5130 AST_CLI_DEFINE(cli_list_subscriptions_inout, "List active inbound/outbound subscriptions"),
5131 AST_CLI_DEFINE(cli_show_subscription_inout, "Show active subscription details"),
5132 AST_CLI_DEFINE(cli_show_subscriptions_inout, "Show active inbound/outbound subscriptions"),
5133};
5134
5135static int persistence_endpoint_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5136{
5137 struct subscription_persistence *persistence = obj;
5138
5139 persistence->endpoint = ast_strdup(var->value);
5140 return 0;
5141}
5142
5143static int persistence_endpoint_struct2str(const void *obj, const intptr_t *args, char **buf)
5144{
5145 const struct subscription_persistence *persistence = obj;
5146
5147 *buf = ast_strdup(persistence->endpoint);
5148 return 0;
5149}
5150
5151static int persistence_tag_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5152{
5153 struct subscription_persistence *persistence = obj;
5154
5155 persistence->tag = ast_strdup(var->value);
5156 return 0;
5157}
5158
5159static int persistence_tag_struct2str(const void *obj, const intptr_t *args, char **buf)
5160{
5161 const struct subscription_persistence *persistence = obj;
5162
5163 *buf = ast_strdup(persistence->tag);
5164 return 0;
5165}
5166
5167static int persistence_generator_data_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5168{
5169 struct subscription_persistence *persistence = obj;
5170 struct ast_json_error error;
5171
5172 /* We tolerate a failure of the JSON to load and instead start fresh, since this field
5173 * originates from the persistence code and not a user.
5174 */
5175 persistence->generator_data = ast_json_load_string(var->value, &error);
5176
5177 return 0;
5178}
5179
5180static int persistence_generator_data_struct2str(const void *obj, const intptr_t *args, char **buf)
5181{
5182 const struct subscription_persistence *persistence = obj;
5183 char *value;
5184
5185 if (!persistence->generator_data) {
5186 return 0;
5187 }
5188
5190 if (!value) {
5191 return -1;
5192 }
5193
5194 *buf = ast_strdup(value);
5196
5197 return 0;
5198}
5199
5200static int persistence_expires_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
5201{
5202 struct subscription_persistence *persistence = obj;
5203 return ast_get_timeval(var->value, &persistence->expires, ast_tv(0, 0), NULL);
5204}
5205
5206static int persistence_expires_struct2str(const void *obj, const intptr_t *args, char **buf)
5207{
5208 const struct subscription_persistence *persistence = obj;
5209 char secs[AST_TIME_T_LEN];
5210
5211 ast_time_t_to_string(persistence->expires.tv_sec, secs, sizeof(secs));
5212
5213 return (ast_asprintf(buf, "%s", secs) < 0) ? -1 : 0;
5214}
5215
5216#define RESOURCE_LIST_INIT_SIZE 4
5217
5218static void resource_list_destructor(void *obj)
5219{
5220 struct resource_list *list = obj;
5221 int i;
5222
5223 for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
5224 ast_free((char *) AST_VECTOR_GET(&list->items, i));
5225 }
5226
5227 AST_VECTOR_FREE(&list->items);
5228}
5229
5230static void *resource_list_alloc(const char *name)
5231{
5232 struct resource_list *list;
5233
5235 if (!list) {
5236 return NULL;
5237 }
5238
5240 ao2_cleanup(list);
5241 return NULL;
5242 }
5243
5244 return list;
5245}
5246
5247static int item_in_vector(const struct resource_list *list, const char *item)
5248{
5249 int i;
5250
5251 for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
5252 if (!strcmp(item, AST_VECTOR_GET(&list->items, i))) {
5253 return 1;
5254 }
5255 }
5256
5257 return 0;
5258}
5259
5260static int list_item_handler(const struct aco_option *opt,
5261 struct ast_variable *var, void *obj)
5262{
5263 struct resource_list *list = obj;
5264 char *items = ast_strdupa(var->value);
5265 char *item;
5266
5267 while ((item = ast_strip(strsep(&items, ",")))) {
5268 if (ast_strlen_zero(item)) {
5269 continue;
5270 }
5271
5272 if (item_in_vector(list, item)) {
5273 ast_log(LOG_WARNING, "Ignoring duplicated list item '%s'\n", item);
5274 continue;
5275 }
5276
5277 item = ast_strdup(item);
5278 if (!item || AST_VECTOR_APPEND(&list->items, item)) {
5279 ast_free(item);
5280 return -1;
5281 }
5282 }
5283
5284 return 0;
5285}
5286
5287static int list_item_to_str(const void *obj, const intptr_t *args, char **buf)
5288{
5289 const struct resource_list *list = obj;
5290 int i;
5291 struct ast_str *str = ast_str_create(32);
5292
5293 for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
5294 ast_str_append(&str, 0, "%s,", AST_VECTOR_GET(&list->items, i));
5295 }
5296
5297 /* Chop off trailing comma */
5298 ast_str_truncate(str, -1);
5300 ast_free(str);
5301 return 0;
5302}
5303
5304static int resource_list_apply_handler(const struct ast_sorcery *sorcery, void *obj)
5305{
5306 struct resource_list *list = obj;
5307
5308 if (ast_strlen_zero(list->event)) {
5309 ast_log(LOG_WARNING, "Resource list '%s' has no event set\n",
5311 return -1;
5312 }
5313
5314 if (AST_VECTOR_SIZE(&list->items) == 0) {
5315 ast_log(LOG_WARNING, "Resource list '%s' has no list items\n",
5317 return -1;
5318 }
5319
5320 return 0;
5321}
5322
5324{
5325 ast_sorcery_apply_default(sorcery, "resource_list", "config",
5326 "pjsip.conf,criteria=type=resource_list");
5329 return -1;
5330 }
5331
5332 ast_sorcery_object_field_register(sorcery, "resource_list", "type", "",
5333 OPT_NOOP_T, 0, 0);
5334 ast_sorcery_object_field_register(sorcery, "resource_list", "event", "",
5336 ast_sorcery_object_field_register(sorcery, "resource_list", "full_state", "no",
5338 ast_sorcery_object_field_register(sorcery, "resource_list", "notification_batch_interval",
5340 ast_sorcery_object_field_register_custom(sorcery, "resource_list", "list_item",
5342 ast_sorcery_object_field_register(sorcery, "resource_list", "resource_display_name", "no",
5344
5345 ast_sorcery_reload_object(sorcery, "resource_list");
5346
5347 return 0;
5348}
5349
5350#ifdef TEST_FRAMEWORK
5351
5352/*!
5353 * \brief "bad" resources
5354 *
5355 * These are resources that the test handler will reject subscriptions to.
5356 */
5357const char *bad_resources[] = {
5358 "coconut",
5359 "cilantro",
5360 "olive",
5361 "cheese",
5362};
5363
5364/*!
5365 * \brief new_subscribe callback for unit tests
5366 *
5367 * Will give a 200 OK response to any resource except the "bad" ones.
5368 */
5369static int test_new_subscribe(struct ast_sip_endpoint *endpoint, const char *resource)
5370{
5371 int i;
5372
5373 for (i = 0; i < ARRAY_LEN(bad_resources); ++i) {
5374 if (!strcmp(resource, bad_resources[i])) {
5375 return 400;
5376 }
5377 }
5378
5379 return 200;
5380}
5381
5382/*!
5383 * \brief Subscription notifier for unit tests.
5384 *
5385 * Since unit tests are only concerned with building a resource tree,
5386 * only the new_subscribe callback needs to be defined.
5387 */
5388struct ast_sip_notifier test_notifier = {
5389 .new_subscribe = test_new_subscribe,
5390};
5391
5392/*!
5393 * \brief Subscription handler for unit tests.
5394 */
5396 .event_name = "test",
5397 .body_type = "",
5398 .notifier = &test_notifier,
5399};
5400
5401/*!
5402 * \brief Set properties on an allocated resource list
5403 *
5404 * \param list The list to set details on.
5405 * \param event The list's event.
5406 * \param resources Array of resources to add to the list.
5407 * \param num_resources Number of resources in the array.
5408 * \retval 0 Success
5409 * \retval non-zero Failure
5410 */
5411static int populate_list(struct resource_list *list, const char *event, const char **resources, size_t num_resources)
5412{
5413 int i;
5414
5415 ast_copy_string(list->event, event, sizeof(list->event));
5416
5417 for (i = 0; i < num_resources; ++i) {
5418 char *resource = ast_strdup(resources[i]);
5419
5420 if (!resource || AST_VECTOR_APPEND(&list->items, resource)) {
5421 ast_free(resource);
5422 return -1;
5423 }
5424 }
5425 return 0;
5426}
5427
5428/*!
5429 * \brief RAII callback to destroy a resource list
5430 */
5431static void cleanup_resource_list(struct resource_list *list)
5432{
5433 if (!list) {
5434 return;
5435 }
5436
5438 ao2_cleanup(list);
5439}
5440
5441/*!
5442 * \brief allocate a resource list, store it in sorcery, and set its details
5443 *
5444 * \param test The unit test. Used for logging status messages.
5445 * \param list_name The name of the list to create.
5446 * \param event The event the list services
5447 * \param resources Array of resources to apply to the list
5448 * \param num_resources The number of resources in the array
5449 * \retval NULL Failed to allocate or populate list
5450 * \retval non-NULL The created list
5451 */
5452static struct resource_list *create_resource_list(struct ast_test *test,
5453 const char *list_name, const char *event, const char **resources, size_t num_resources)
5454{
5455 struct resource_list *list;
5456
5457 list = ast_sorcery_alloc(ast_sip_get_sorcery(), "resource_list", list_name);
5458 if (!list) {
5459 ast_test_status_update(test, "Could not allocate resource list in sorcery\n");
5460 return NULL;
5461 }
5462
5464 ast_test_status_update(test, "Could not store the resource list in sorcery\n");
5465 ao2_cleanup(list);
5466 return NULL;
5467 }
5468
5469 if (populate_list(list, event, resources, num_resources)) {
5470 ast_test_status_update(test, "Could not add resources to the resource list\n");
5471 cleanup_resource_list(list);
5472 return NULL;
5473 }
5474
5475 return list;
5476}
5477
5478/*!
5479 * \brief Check the integrity of a tree node against a set of resources.
5480 *
5481 * The tree node's resources must be in the same order as the resources in
5482 * the supplied resources array. Because of this constraint, tests can misrepresent
5483 * the size of the resources array as being smaller than it really is if resources
5484 * at the end of the array should not be present in the tree node.
5485 *
5486 * \param test The unit test. Used for printing status messages.
5487 * \param node The constructed tree node whose integrity is under question.
5488 * \param resources Array of expected resource values
5489 * \param num_resources The number of resources to check in the array.
5490 */
5491static int check_node(struct ast_test *test, struct tree_node *node,
5492 const char **resources, size_t num_resources)
5493{
5494 int i;
5495
5496 if (AST_VECTOR_SIZE(&node->children) != num_resources) {
5497 ast_test_status_update(test, "Unexpected number of resources in tree. Expected %zu, got %zu\n",
5498 num_resources, AST_VECTOR_SIZE(&node->children));
5499 return -1;
5500 }
5501
5502 for (i = 0; i < num_resources; ++i) {
5503 if (strcmp(resources[i], AST_VECTOR_GET(&node->children, i)->resource)) {
5504 ast_test_status_update(test, "Mismatched resources. Expected '%s' but got '%s'\n",
5505 resources[i], AST_VECTOR_GET(&node->children, i)->resource);
5506 return -1;
5507 }
5508 }
5509
5510 return 0;
5511}
5512
5513/*!
5514 * \brief RAII_VAR callback to destroy an allocated resource tree
5515 */
5516static void test_resource_tree_destroy(struct resource_tree *tree)
5517{
5519 ast_free(tree);
5520}
5521
5522static int ineligible_configuration(void)
5523{
5524 struct ast_config *config;
5525 struct ast_flags flags = {0,};
5526 const char *value;
5527
5528 config = ast_config_load("sorcery.conf", flags);
5529 if (!config) {
5530 return 1;
5531 }
5532
5533 value = ast_variable_retrieve(config, "res_pjsip_pubsub", "resource_list");
5534 if (ast_strlen_zero(value)) {
5536 return 1;
5537 }
5538
5539 if (strcasecmp(value, "memory") && strcasecmp(value, "astdb")) {
5541 return 1;
5542 }
5543
5544 return 0;
5545}
5546
5548{
5549 RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
5550 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5551 const char *resources[] = {
5552 "huey",
5553 "dewey",
5554 "louie",
5555 };
5556 int resp;
5557
5558 switch (cmd) {
5559 case TEST_INIT:
5560 info->name = "resource_tree";
5561 info->category = "/res/res_pjsip_pubsub/";
5562 info->summary = "Basic resource tree integrity check";
5563 info->description =
5564 "Create a resource list and ensure that our attempt to build a tree works as expected.";
5565 return AST_TEST_NOT_RUN;
5566 case TEST_EXECUTE:
5567 break;
5568 }
5569
5570 if (ineligible_configuration()) {
5571 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5572 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5573 return AST_TEST_NOT_RUN;
5574 }
5575
5576 list = create_resource_list(test, "foo", "test", resources, ARRAY_LEN(resources));
5577 if (!list) {
5578 return AST_TEST_FAIL;
5579 }
5580
5581 tree = ast_calloc(1, sizeof(*tree));
5582 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5583 if (resp != 200) {
5584 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5585 return AST_TEST_FAIL;
5586 }
5587
5588 if (!tree->root) {
5589 ast_test_status_update(test, "Resource tree has no root\n");
5590 return AST_TEST_FAIL;
5591 }
5592
5593 if (check_node(test, tree->root, resources, ARRAY_LEN(resources))) {
5594 return AST_TEST_FAIL;
5595 }
5596
5597 return AST_TEST_PASS;
5598}
5599
5600AST_TEST_DEFINE(complex_resource_tree)
5601{
5602 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5603 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5604 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5605 const char *resources_1[] = {
5606 "huey",
5607 "dewey",
5608 "louie",
5609 "dwarves",
5610 };
5611 const char *resources_2[] = {
5612 "happy",
5613 "grumpy",
5614 "doc",
5615 "bashful",
5616 "dopey",
5617 "sneezy",
5618 "sleepy",
5619 };
5620 int resp;
5621 struct tree_node *node;
5622
5623 switch (cmd) {
5624 case TEST_INIT:
5625 info->name = "complex_resource_tree";
5626 info->category = "/res/res_pjsip_pubsub/";
5627 info->summary = "Complex resource tree integrity check";
5628 info->description =
5629 "Create a complex resource list and ensure that our attempt to build a tree works as expected.";
5630 return AST_TEST_NOT_RUN;
5631 case TEST_EXECUTE:
5632 break;
5633 }
5634
5635 if (ineligible_configuration()) {
5636 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5637 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5638 return AST_TEST_NOT_RUN;
5639 }
5640
5641 list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
5642 if (!list_1) {
5643 return AST_TEST_FAIL;
5644 }
5645
5646 list_2 = create_resource_list(test, "dwarves", "test", resources_2, ARRAY_LEN(resources_2));
5647 if (!list_2) {
5648 return AST_TEST_FAIL;
5649 }
5650
5651 tree = ast_calloc(1, sizeof(*tree));
5652 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5653 if (resp != 200) {
5654 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5655 return AST_TEST_FAIL;
5656 }
5657
5658 if (!tree->root) {
5659 ast_test_status_update(test, "Resource tree has no root\n");
5660 return AST_TEST_FAIL;
5661 }
5662
5663 node = tree->root;
5664 if (check_node(test, node, resources_1, ARRAY_LEN(resources_1))) {
5665 return AST_TEST_FAIL;
5666 }
5667
5668 /* The embedded list is at index 3 in the root node's children */
5669 node = AST_VECTOR_GET(&node->children, 3);
5670 if (check_node(test, node, resources_2, ARRAY_LEN(resources_2))) {
5671 return AST_TEST_FAIL;
5672 }
5673
5674 return AST_TEST_PASS;
5675}
5676
5677AST_TEST_DEFINE(bad_resource)
5678{
5679 RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
5680 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5681 const char *resources[] = {
5682 "huey",
5683 "dewey",
5684 "louie",
5685 "coconut", /* A "bad" resource */
5686 };
5687 int resp;
5688
5689 switch (cmd) {
5690 case TEST_INIT:
5691 info->name = "bad_resource";
5692 info->category = "/res/res_pjsip_pubsub/";
5693 info->summary = "Ensure bad resources do not end up in the tree";
5694 info->description =
5695 "Create a resource list with a single bad resource. Ensure the bad resource does not end up in the tree.";
5696 return AST_TEST_NOT_RUN;
5697 case TEST_EXECUTE:
5698 break;
5699 }
5700
5701 if (ineligible_configuration()) {
5702 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5703 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5704 return AST_TEST_NOT_RUN;
5705 }
5706
5707 list = create_resource_list(test, "foo", "test", resources, ARRAY_LEN(resources));
5708 if (!list) {
5709 return AST_TEST_FAIL;
5710 }
5711
5712 tree = ast_calloc(1, sizeof(*tree));
5713 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5714 if (resp != 200) {
5715 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5716 return AST_TEST_FAIL;
5717 }
5718
5719 if (!tree->root) {
5720 ast_test_status_update(test, "Resource tree has no root\n");
5721 return AST_TEST_FAIL;
5722 }
5723
5724 /* We check against all but the final resource since we expect it not to be in the tree */
5725 if (check_node(test, tree->root, resources, ARRAY_LEN(resources) - 1)) {
5726 return AST_TEST_FAIL;
5727 }
5728
5729 return AST_TEST_PASS;
5730
5731}
5732
5733AST_TEST_DEFINE(bad_branch)
5734{
5735 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5736 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5737 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5738 const char *resources_1[] = {
5739 "huey",
5740 "dewey",
5741 "louie",
5742 "gross",
5743 };
5744 /* This list has nothing but bad resources */
5745 const char *resources_2[] = {
5746 "coconut",
5747 "cilantro",
5748 "olive",
5749 "cheese",
5750 };
5751 int resp;
5752
5753 switch (cmd) {
5754 case TEST_INIT:
5755 info->name = "bad_branch";
5756 info->category = "/res/res_pjsip_pubsub/";
5757 info->summary = "Ensure bad branches are pruned from the tree";
5758 info->description =
5759 "Create a resource list that makes a tree with an entire branch of bad resources.\n"
5760 "Ensure the bad branch is pruned from the tree.";
5761 return AST_TEST_NOT_RUN;
5762 case TEST_EXECUTE:
5763 break;
5764 }
5765
5766 if (ineligible_configuration()) {
5767 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5768 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5769 return AST_TEST_NOT_RUN;
5770 }
5771
5772 list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
5773 if (!list_1) {
5774 return AST_TEST_FAIL;
5775 }
5776 list_2 = create_resource_list(test, "gross", "test", resources_2, ARRAY_LEN(resources_2));
5777 if (!list_2) {
5778 return AST_TEST_FAIL;
5779 }
5780
5781 tree = ast_calloc(1, sizeof(*tree));
5782 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5783 if (resp != 200) {
5784 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5785 return AST_TEST_FAIL;
5786 }
5787
5788 if (!tree->root) {
5789 ast_test_status_update(test, "Resource tree has no root\n");
5790 return AST_TEST_FAIL;
5791 }
5792
5793 /* We check against all but the final resource of the list since the entire branch should
5794 * be pruned from the tree
5795 */
5796 if (check_node(test, tree->root, resources_1, ARRAY_LEN(resources_1) - 1)) {
5797 return AST_TEST_FAIL;
5798 }
5799
5800 return AST_TEST_PASS;
5801
5802}
5803
5804AST_TEST_DEFINE(duplicate_resource)
5805{
5806 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5807 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5808 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5809 const char *resources_1[] = {
5810 "huey",
5811 "ducks",
5812 "dewey",
5813 "louie",
5814 };
5815 const char *resources_2[] = {
5816 "donald",
5817 "daisy",
5818 "scrooge",
5819 "dewey",
5820 "louie",
5821 "huey",
5822 };
5823 int resp;
5824 struct tree_node *node;
5825
5826 switch (cmd) {
5827 case TEST_INIT:
5828 info->name = "duplicate_resource";
5829 info->category = "/res/res_pjsip_pubsub/";
5830 info->summary = "Ensure duplicated resources do not end up in the tree";
5831 info->description =
5832 "Create a resource list with a single duplicated resource. Ensure the duplicated resource does not end up in the tree.";
5833 return AST_TEST_NOT_RUN;
5834 case TEST_EXECUTE:
5835 break;
5836 }
5837
5838 if (ineligible_configuration()) {
5839 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5840 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5841 return AST_TEST_NOT_RUN;
5842 }
5843
5844 list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
5845 if (!list_1) {
5846 return AST_TEST_FAIL;
5847 }
5848
5849 list_2 = create_resource_list(test, "ducks", "test", resources_2, ARRAY_LEN(resources_2));
5850 if (!list_2) {
5851 return AST_TEST_FAIL;
5852 }
5853
5854 tree = ast_calloc(1, sizeof(*tree));
5855 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5856 if (resp != 200) {
5857 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5858 return AST_TEST_FAIL;
5859 }
5860
5861 if (!tree->root) {
5862 ast_test_status_update(test, "Resource tree has no root\n");
5863 return AST_TEST_FAIL;
5864 }
5865
5866 node = tree->root;
5867 /* This node should have "huey" and "ducks". "dewey" and "louie" should not
5868 * be present since they were found in the "ducks" list.
5869 */
5870 if (check_node(test, node, resources_1, ARRAY_LEN(resources_1) - 2)) {
5871 return AST_TEST_FAIL;
5872 }
5873
5874 /* This node should have "donald", "daisy", "scrooge", "dewey", and "louie".
5875 * "huey" is not here since that was already encountered in the parent list
5876 */
5877 node = AST_VECTOR_GET(&node->children, 1);
5878 if (check_node(test, node, resources_2, ARRAY_LEN(resources_2) - 1)) {
5879 return AST_TEST_FAIL;
5880 }
5881
5882 return AST_TEST_PASS;
5883}
5884
5885AST_TEST_DEFINE(loop)
5886{
5887 RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
5888 RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
5889 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5890 const char *resources_1[] = {
5891 "derp",
5892 };
5893 const char *resources_2[] = {
5894 "herp",
5895 };
5896 int resp;
5897
5898 switch (cmd) {
5899 case TEST_INIT:
5900 info->name = "loop";
5901 info->category = "/res/res_pjsip_pubsub/";
5902 info->summary = "Test that loops are properly detected.";
5903 info->description =
5904 "Create two resource lists that refer to each other. Ensure that attempting to build a tree\n"
5905 "results in an empty tree.";
5906 return AST_TEST_NOT_RUN;
5907 case TEST_EXECUTE:
5908 break;
5909 }
5910
5911 if (ineligible_configuration()) {
5912 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5913 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5914 return AST_TEST_NOT_RUN;
5915 }
5916
5917 list_1 = create_resource_list(test, "herp", "test", resources_1, ARRAY_LEN(resources_1));
5918 if (!list_1) {
5919 return AST_TEST_FAIL;
5920 }
5921 list_2 = create_resource_list(test, "derp", "test", resources_2, ARRAY_LEN(resources_2));
5922 if (!list_2) {
5923 return AST_TEST_FAIL;
5924 }
5925
5926 tree = ast_calloc(1, sizeof(*tree));
5927 resp = build_resource_tree(NULL, &test_handler, "herp", tree, 1, NULL);
5928 if (resp == 200) {
5929 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5930 return AST_TEST_FAIL;
5931 }
5932
5933 return AST_TEST_PASS;
5934}
5935
5936AST_TEST_DEFINE(bad_event)
5937{
5938 RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
5939 RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
5940 const char *resources[] = {
5941 "huey",
5942 "dewey",
5943 "louie",
5944 };
5945 int resp;
5946
5947 switch (cmd) {
5948 case TEST_INIT:
5949 info->name = "bad_event";
5950 info->category = "/res/res_pjsip_pubsub/";
5951 info->summary = "Ensure that list with wrong event specified is not retrieved";
5952 info->description =
5953 "Create a simple resource list for event 'tsetse'. Ensure that trying to retrieve the list for event 'test' fails.";
5954 return AST_TEST_NOT_RUN;
5955 case TEST_EXECUTE:
5956 break;
5957 }
5958
5959 if (ineligible_configuration()) {
5960 ast_test_status_update(test, "Ineligible configuration for this test. Please add a "
5961 "'res_pjsip_pubsub' section to sorcery.conf, and set 'resource_list=memory'\n");
5962 return AST_TEST_NOT_RUN;
5963 }
5964
5965 list = create_resource_list(test, "foo", "tsetse", resources, ARRAY_LEN(resources));
5966 if (!list) {
5967 return AST_TEST_FAIL;
5968 }
5969
5970 tree = ast_calloc(1, sizeof(*tree));
5971 /* Since the test_handler is for event "test", this should not build a list, but
5972 * instead result in a single resource being created, called "foo"
5973 */
5974 resp = build_resource_tree(NULL, &test_handler, "foo", tree, 1, NULL);
5975 if (resp != 200) {
5976 ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
5977 return AST_TEST_FAIL;
5978 }
5979
5980 if (!tree->root) {
5981 ast_test_status_update(test, "Resource tree has no root\n");
5982 return AST_TEST_FAIL;
5983 }
5984
5985 if (strcmp(tree->root->resource, "foo")) {
5986 ast_test_status_update(test, "Unexpected resource %s found in tree\n", tree->root->resource);
5987 return AST_TEST_FAIL;
5988 }
5989
5990 return AST_TEST_PASS;
5991}
5992
5993#endif
5994
5995static int resource_endpoint_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
5996{
5997 struct ast_sip_publication_resource *resource = obj;
5998
5999 ast_free(resource->endpoint);
6000 resource->endpoint = ast_strdup(var->value);
6001
6002 return 0;
6003}
6004
6005static int resource_event_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
6006{
6007 struct ast_sip_publication_resource *resource = obj;
6008 /* The event configuration name starts with 'event_' so skip past it to get the real name */
6009 const char *event = var->name + 6;
6010 struct ast_variable *item;
6011
6012 if (ast_strlen_zero(event) || ast_strlen_zero(var->value)) {
6013 return -1;
6014 }
6015
6016 item = ast_variable_new(event, var->value, "");
6017 if (!item) {
6018 return -1;
6019 }
6020
6021 if (resource->events) {
6022 item->next = resource->events;
6023 }
6024 resource->events = item;
6025
6026 return 0;
6027}
6028
6029static int load_module(void)
6030{
6031 static const pj_str_t str_PUBLISH = { "PUBLISH", 7 };
6032 struct ast_sorcery *sorcery;
6033
6035
6036 if (!(sched = ast_sched_context_create())) {
6037 ast_log(LOG_ERROR, "Could not create scheduler for publication expiration\n");
6039 }
6040
6042 ast_log(LOG_ERROR, "Could not start scheduler thread for publication expiration\n");
6045 }
6046
6047 ast_sorcery_apply_config(sorcery, "res_pjsip_pubsub");
6048 ast_sorcery_apply_default(sorcery, "subscription_persistence", "astdb", "subscription_persistence");
6050 NULL, NULL)) {
6051 ast_log(LOG_ERROR, "Could not register subscription persistence object support\n");
6054 }
6055 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "packet", "", OPT_CHAR_ARRAY_T, 0,
6056 CHARFLDSET(struct subscription_persistence, packet));
6057 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "src_name", "", OPT_CHAR_ARRAY_T, 0,
6058 CHARFLDSET(struct subscription_persistence, src_name));
6059 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "src_port", "0", OPT_UINT_T, 0,
6060 FLDSET(struct subscription_persistence, src_port));
6061 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "transport_key", "0", OPT_CHAR_ARRAY_T, 0,
6062 CHARFLDSET(struct subscription_persistence, transport_type));
6063 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "local_name", "", OPT_CHAR_ARRAY_T, 0,
6064 CHARFLDSET(struct subscription_persistence, local_name));
6065 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "local_port", "0", OPT_UINT_T, 0,
6066 FLDSET(struct subscription_persistence, local_port));
6067 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "cseq", "0", OPT_UINT_T, 0,
6068 FLDSET(struct subscription_persistence, cseq));
6069 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "endpoint", "",
6071 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "tag", "",
6073 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "expires", "",
6075 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "contact_uri", "", OPT_CHAR_ARRAY_T, 0,
6076 CHARFLDSET(struct subscription_persistence, contact_uri));
6077 ast_sorcery_object_field_register(sorcery, "subscription_persistence", "prune_on_boot", "no", OPT_YESNO_T, 1,
6078 FLDSET(struct subscription_persistence, prune_on_boot));
6079 ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "generator_data", "",
6081
6085 }
6086
6087 ast_sorcery_apply_default(sorcery, "inbound-publication", "config", "pjsip.conf,criteria=type=inbound-publication");
6089 NULL, NULL)) {
6090 ast_log(LOG_ERROR, "Could not register subscription persistence object support\n");
6093 }
6094 ast_sorcery_object_field_register(sorcery, "inbound-publication", "type", "", OPT_NOOP_T, 0, 0);
6095 ast_sorcery_object_field_register_custom(sorcery, "inbound-publication", "endpoint", "",
6097 ast_sorcery_object_fields_register(sorcery, "inbound-publication", "^event_", resource_event_handler, NULL);
6098 ast_sorcery_reload_object(sorcery, "inbound-publication");
6099
6101 ast_log(LOG_ERROR, "Could not register pubsub service\n");
6104 }
6105
6106 if (pjsip_evsub_init_module(ast_sip_get_pjsip_endpoint()) != PJ_SUCCESS) {
6107 ast_log(LOG_ERROR, "Could not initialize pjsip evsub module.\n");
6111 }
6112
6113 /* Once pjsip_evsub_init_module succeeds we cannot unload.
6114 * Keep all module_load errors above this point. */
6116
6117 pjsip_media_type_init2(&rlmi_media_type, "application", "rlmi+xml");
6118
6119 pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &str_PUBLISH);
6120
6121 if (ast_fully_booted) {
6123 } else {
6124 struct stasis_subscription *sub;
6125
6129 }
6130
6135 ast_manager_register_xml("PJSIPShowResourceLists", EVENT_FLAG_SYSTEM,
6137
6139
6141 AST_TEST_REGISTER(complex_resource_tree);
6142 AST_TEST_REGISTER(bad_resource);
6143 AST_TEST_REGISTER(bad_branch);
6144 AST_TEST_REGISTER(duplicate_resource);
6145 AST_TEST_REGISTER(loop);
6146 AST_TEST_REGISTER(bad_event);
6147
6149}
6150
6176
6178 .support_level = AST_MODULE_SUPPORT_CORE,
6179 .load = load_module,
6180 .unload = unload_module,
6181 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
6182 .requires = "res_pjsip",
void ast_cli_unregister_multiple(void)
Definition ael_main.c:408
static struct ast_generator gen
const char * str
Definition app_jack.c:150
char * text
Definition app_queue.c:1791
enum queue_result id
Definition app_queue.c:1790
#define var
Definition ast_expr2f.c:605
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:1884
#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 void destroy_subscriptions(void)
Destroy the active Stasis subscriptions.
Definition cdr.c:4456
static const char type[]
static const char config[]
static struct ast_sorcery * sorcery
#define AST_MAX_EXTENSION
Definition channel.h:134
size_t current
Standard Command Line Interface.
#define CLI_SHOWUSAGE
Definition cli.h:45
#define CLI_SUCCESS
Definition cli.h:44
#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[]
#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:1963
void astman_send_error(struct mansession *s, const struct message *m, char *error)
Send error in manager transaction.
Definition manager.c:1921
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:1999
struct stasis_topic * ast_manager_get_topic(void)
Get the Stasis Message Bus API topic for AMI.
Definition manager.c:441
const char * astman_get_header(const struct message *m, char *var)
Get header from manager transaction.
Definition manager.c:1582
void astman_send_list_complete_end(struct mansession *s)
End the list complete event.
Definition manager.c:2007
void astman_append(struct mansession *s, const char *fmt,...)
Definition manager.c:1842
int ast_manager_unregister(const char *action)
Unregister a registered manager command.
Definition manager.c:7782
struct ast_taskprocessor * ast_sip_get_distributor_serializer(pjsip_rx_data *rdata)
Determine the distributor serializer for the SIP message.
#define ast_sip_push_task(serializer, sip_task, task_data)
Definition res_pjsip.h:2100
struct ast_taskprocessor * ast_sip_create_serializer(const char *name)
Create a new serializer for SIP tasks.
Definition res_pjsip.c:2092
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 taskpool.
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.
#define ast_sip_push_task_wait_serializer(serializer, sip_task, task_data)
Definition res_pjsip.h:2195
int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen)
Gets the task name.
#define ast_sip_push_task_wait_servant(serializer, sip_task, task_data)
Definition res_pjsip.h:2139
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.
@ AST_SIP_SCHED_TASK_FIXED
Definition res_pjsip.h:2225
@ AST_SIP_SCHED_TASK_DATA_AO2
Definition res_pjsip.h:2248
#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:1287
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition extconf.c:1260
#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
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition linkedlists.h:78
#define AST_RWLIST_TRAVERSE_SAFE_BEGIN
#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.
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define AST_RWLIST_TRAVERSE_SAFE_END
#define AST_RWLIST_TRAVERSE
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
#define AST_RWLIST_INSERT_TAIL
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:764
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:193
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
#define ast_fully_booted
Definition options.h:127
static struct stasis_subscription * sub
Statsd channel stats. Exmaple of how to subscribe to Stasis events.
int ast_sip_will_uri_survive_restart(pjsip_sip_uri *uri, struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
Definition res_pjsip.c:523
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:3452
void ast_sip_unregister_service(pjsip_module *module)
Definition res_pjsip.c:127
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:111
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:962
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:518
#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:3535
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:3442
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:2176
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:1190
#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:3130
#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:3098
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:2006
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:2223
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:1212
void ast_sip_sanitize_xml(const char *input, char *output, size_t len)
Replace offensive XML characters with XML entities.
#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.
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.
#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.
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
static struct @522 args
#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:2381
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:1224
@ 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:2126
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:1917
#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:1792
#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:1808
int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object)
Update an object.
Definition sorcery.c:2214
#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:1506
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:1961
int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object)
Delete an object.
Definition sorcery.c:2302
@ 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:1101
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:1155
#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:1049
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
#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
char *attribute_pure ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition strings.h:761
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:2453
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 struct ast_datastore_info * info
Definition datastore.h:67
const char * uid
Definition datastore.h:65
Structure used to handle boolean flags.
Definition utils.h:220
unsigned int flags
Definition utils.h:221
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:3231
struct mansession * s
Definition res_pjsip.h:3233
const struct message * m
Definition res_pjsip.h:3235
Data used to create bodies for NOTIFY/PUBLISH requests.
Contact associated with an address of record.
Definition res_pjsip.h:392
const ast_string_field uri
Definition res_pjsip.h:414
Party identification options for endpoints.
Definition res_pjsip.h:871
An entity with which Asterisk communicates.
Definition res_pjsip.h:1067
struct ast_sip_endpoint_id_configuration id
Definition res_pjsip.h:1106
const ast_string_field aors
Definition res_pjsip.h:1096
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.
void(* to_string)(void *body, struct ast_str **str)
Convert the body to a string.
struct ast_sip_pubsub_body_generator::@278 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.
struct ast_sip_pubsub_body_supplement::@279 list
const char * type
Content type In "plain/text", "plain" is the type.
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_sip_subscription::@504 children
struct ast_json * persistence_data
struct ast_str * body_text
struct ao2_container * datastores
pjsip_evsub_state subscription_state
Full structure for sorcery.
Definition sorcery.c:231
Support for dynamic strings.
Definition strings.h:623
A ast_taskprocessor structure is a singleton by name.
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
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:314
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.
struct tree_node::@505 children
unsigned int full_state
structure to hold users read from phoneprov_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).
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
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:2280
#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,...)
#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:981
#define ast_assert(a)
Definition utils.h:779
#define MIN(a, b)
Definition utils.h:252
#define ARRAY_LEN(a)
Definition utils.h:706
Universally unique identifier support.
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition vector.h:637
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition vector.h:185
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition vector.h:124
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition vector.h:267
#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:708