Better handle parking in CDRs
authorMatthew Jordan <mjordan@digium.com>
Fri, 28 Jun 2013 15:50:56 +0000 (15:50 +0000)
committerMatthew Jordan <mjordan@digium.com>
Fri, 28 Jun 2013 15:50:56 +0000 (15:50 +0000)
Parking typically occurs when a channel is transferred to a parking extension.
When this occurs, the channel never actually hits the dialplan if the extension
it was transferred to was a "parking extension", that is, the extension in
the first priority calls the Park application. Instead, the channel is
immediately sent into the holding bridge acting as the parking bridge.

This is problematic.

Because we never go out to the dialplan, the CDRs won't transition properly
and the application field will not be set to "Park". CDRs typically swallow
holding bridges, so the CDR itself won't even be generated.

This patch handles this by pulling out the holding bridge handling into its
own CDR state. CDRs now have an explicit parking state that accounts for this
specific subclass of the holding bridge. In addition, we handle the parking
stasis message to set application specific data on the CDR such that the
last known application for the CDR properly reflects "Park".

This is a bit sad since we're working around the odd internal implementation
of parking that exists in Asterisk (and that we had to maintain in order to
continue to meet some odd use cases of parking), but at least the code to
handle that is where it belongs: in CDRs as opposed to sprinkled liberally
throughout the codebase.

This patch also properly clears the OUTBOUND channel flag from a channel when
it leaves a bridge, and tweaks up dialing handling to properly compare the
correct CDR with the channel calling/being dialed.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393130 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/cdr.h
include/asterisk/parking.h
main/asterisk.c
main/bridging.c
main/cdr.c

index 548ca99..9d1f6d7 100644 (file)
@@ -99,6 +99,9 @@
  * has hung up, the state transitions to Finalized
  * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
  * state transitions to Bridge
+ * \li If a \ref ast_bridge_blob_type message indicating an entrance to a
+ * holding bridge with a subclass type of "parking" is received, the CDR is
+ * transitioned to the Parked state.
  *
  * \par Dial
  *
  * if we have a Party B. Otherwise, we transition to the Single state.
  * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
  * state transitions to Bridge (through the Dial state)
+ * \li If a \ref ast_bridge_blob_type message indicating an entrance to a
+ * holding bridge with a subclass type of "parking" is received, the CDR is
+ * transitioned to the Parked state.
  *
  * \par Bridge
  *
  * \li If a \ref ast_bridge_blob_type message indicating a leave is received,
  * the state transitions to the Pending state
  *
+ * \par Parked
+ *
+ * Parking is technically just another bridge in the Asterisk bridging
+ * framework. Unlike other bridges, however there are several key distinctions:
+ * \li With normal bridges, you want to show paths of communication between
+ * the participants. In parking, however, each participant is independent.
+ * From the perspective of a CDR, a call in parking should look like a dialplan
+ * application just executed.
+ * \li Holding bridges are typically items using in more complex applications,
+ * and so we usually don't want to show them. However, with Park, there is no
+ * application execution - often, a channel will be put directly into the
+ * holding bridge, bypassing the dialplan. This occurs when a call is blind
+ * transferred to a parking extension.
+ *
+ * As such, if a channel enters a bridge and that happens to be a holding bridge
+ * with a subclass type of "parking", we transition the CDR into the Parked
+ * state. The parking Stasis message updates the application name and data to
+ * reflect that the channel is in parking. When this occurs, a special flag is
+ * set on the CDR that prevents the application name from being updates by
+ * subsequent channel snapshot updates.
+ *
+ * The following transitions can occur while in the Parked state:
+ * \li If a \ref ast_bridge_blob_type message indicating a leave is received,
+ * the state transitions to the Pending state
+ *
  * \par Pending
  *
  * After a channel leaves a bridge, we often don't know what's going to happen
  * Context/Extension/Priority, a new CDR is created and transitioned to the
  * Single state. If the update indicates that the party has been hung up, the
  * CDR is transitioned to the Finalized state.
+ * \li If a \ref ast_bridge_blob_type message indicating an entrance to a
+ * holding bridge with a subclass type of "parking" is received, the CDR is
+ * transitioned to the Parked state.
  *
  * \par Finalized
  *
 
 /*! \brief CDR engine settings */
 enum ast_cdr_settings {
-       CDR_ENABLED = 1 << 0,                           /*< Enable CDRs */
-       CDR_BATCHMODE = 1 << 1,                         /*< Whether or not we should dispatch CDRs in batches */
-       CDR_UNANSWERED = 1 << 2,                        /*< Log unanswered CDRs */
-       CDR_CONGESTION = 1 << 3,                        /*< Treat congestion as if it were a failed call */
-       CDR_END_BEFORE_H_EXTEN = 1 << 4,        /*< End the CDR before the 'h' extension runs */
-       CDR_INITIATED_SECONDS = 1 << 5,         /*< Include microseconds into the billing time */
-       CDR_DEBUG = 1 << 6,                                     /*< Enables extra debug statements */
+       CDR_ENABLED = 1 << 0,               /*< Enable CDRs */
+       CDR_BATCHMODE = 1 << 1,             /*< Whether or not we should dispatch CDRs in batches */
+       CDR_UNANSWERED = 1 << 2,            /*< Log unanswered CDRs */
+       CDR_CONGESTION = 1 << 3,            /*< Treat congestion as if it were a failed call */
+       CDR_END_BEFORE_H_EXTEN = 1 << 4,    /*< End the CDR before the 'h' extension runs */
+       CDR_INITIATED_SECONDS = 1 << 5,     /*< Include microseconds into the billing time */
+       CDR_DEBUG = 1 << 6,                 /*< Enables extra debug statements */
 };
 
 /*! \brief CDR Batch Mode settings */
 enum ast_cdr_batch_mode_settings {
-       BATCH_MODE_SCHEDULER_ONLY = 1 << 0,     /*< Don't spawn a thread to handle the batches - do it on the scheduler */
-       BATCH_MODE_SAFE_SHUTDOWN = 1 << 1,      /*< During safe shutdown, submit the batched CDRs */
+       BATCH_MODE_SCHEDULER_ONLY = 1 << 0, /*< Don't spawn a thread to handle the batches - do it on the scheduler */
+       BATCH_MODE_SAFE_SHUTDOWN = 1 << 1,  /*< During safe shutdown, submit the batched CDRs */
 };
 
 /*!
@@ -223,24 +257,25 @@ enum ast_cdr_batch_mode_settings {
  * state of a CDR object based on these flags.
  */
 enum ast_cdr_options {
-       AST_CDR_FLAG_KEEP_VARS = (1 << 0),                      /*< Copy variables during the operation */
-       AST_CDR_FLAG_DISABLE = (1 << 1),                        /*< Disable the current CDR */
-       AST_CDR_FLAG_DISABLE_ALL = (3 << 1),            /*< Disable the CDR and all future CDRs */
-       AST_CDR_FLAG_PARTY_A = (1 << 3),                        /*< Set the channel as party A */
-       AST_CDR_FLAG_FINALIZE = (1 << 4),                       /*< Finalize the current CDRs */
-       AST_CDR_FLAG_SET_ANSWER = (1 << 5),                     /*< If the channel is answered, set the answer time to now */
-       AST_CDR_FLAG_RESET = (1 << 6),                          /*< If set, set the start and answer time to now */
+       AST_CDR_FLAG_KEEP_VARS = (1 << 0),   /*< Copy variables during the operation */
+       AST_CDR_FLAG_DISABLE = (1 << 1),     /*< Disable the current CDR */
+       AST_CDR_FLAG_DISABLE_ALL = (3 << 1), /*< Disable the CDR and all future CDRs */
+       AST_CDR_FLAG_PARTY_A = (1 << 3),     /*< Set the channel as party A */
+       AST_CDR_FLAG_FINALIZE = (1 << 4),    /*< Finalize the current CDRs */
+       AST_CDR_FLAG_SET_ANSWER = (1 << 5),  /*< If the channel is answered, set the answer time to now */
+       AST_CDR_FLAG_RESET = (1 << 6),       /*< If set, set the start and answer time to now */
+       AST_CDR_LOCK_APP = (1 << 7),         /*< Prevent any further changes to the application field/data field for this CDR */
 };
 
 /*!
  * \brief CDR Flags - Disposition
  */
 enum ast_cdr_disposition {
-       AST_CDR_NOANSWER = 0,
-       AST_CDR_NULL     = (1 << 0),
-       AST_CDR_FAILED   = (1 << 1),
-       AST_CDR_BUSY     = (1 << 2),
-       AST_CDR_ANSWERED = (1 << 3),
+       AST_CDR_NOANSWER   = 0,
+       AST_CDR_NULL       = (1 << 0),
+       AST_CDR_FAILED     = (1 << 1),
+       AST_CDR_BUSY       = (1 << 2),
+       AST_CDR_ANSWERED   = (1 << 3),
        AST_CDR_CONGESTION = (1 << 4),
 };
 
index 9418916..c4019d9 100644 (file)
@@ -56,6 +56,8 @@ struct ast_parked_call_payload {
        );
 };
 
+struct ast_exten;
+
 /*!
  * \brief Constructor for parked_call_payload objects
  * \since 12
index bbe2585..c53652a 100644 (file)
@@ -4333,6 +4333,11 @@ int main(int argc, char *argv[])
                exit(1);
        }
 
+       if (ast_parking_stasis_init()) {
+               printf("%s", term_quit());
+               exit(1);
+       }
+
        if (ast_cdr_engine_init()) {
                printf("%s", term_quit());
                exit(1);
@@ -4366,11 +4371,6 @@ int main(int argc, char *argv[])
                exit(1);
        }
 
-       if (ast_parking_stasis_init()) {
-               printf("%s", term_quit());
-               exit(1);
-       }
-
        if (ast_cel_engine_init()) {
                printf("%s", term_quit());
                exit(1);
index 5e5721f..ee7f511 100644 (file)
@@ -654,7 +654,7 @@ static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel)
         * outgoing channel, clear the outgoing flag.
         */
        if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING)
-                       && (ast_channel_softhangup_internal_flag(bridge_channel->chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))) {
+                       && (!ast_check_hangup(bridge_channel->chan))) {
                ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING);
        }
 
index 64a2bb4..dbce8e5 100644 (file)
@@ -65,6 +65,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/data.h"
 #include "asterisk/config_options.h"
 #include "asterisk/json.h"
+#include "asterisk/parking.h"
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_channels.h"
 #include "asterisk/stasis_bridging.h"
@@ -329,6 +330,9 @@ static struct stasis_subscription *bridge_subscription;
 /*! \brief Our subscription for channels */
 static struct stasis_subscription *channel_subscription;
 
+/*! \brief Our subscription for parking */
+static struct stasis_subscription *parking_subscription;
+
 /*! \brief The parent topic for all topics we want to aggregate for CDRs */
 static struct stasis_topic *cdr_topic;
 
@@ -416,19 +420,38 @@ struct cdr_object_fn_table {
         * of this channel into the bridge is handled by the higher level message
         * handler.
         *
+        * Note that this handler is for when a channel enters into a "normal"
+        * bridge, where people actually talk to each other. Parking is its own
+        * thing.
+        *
         * \param cdr The \ref cdr_object
         * \param bridge The bridge that the Party A just entered into
         * \param channel The \ref ast_channel_snapshot for this CDR's Party A
         *
         * \retval 0 This CDR found a Party B for itself and updated it, or there
         * was no Party B to find (we're all alone)
-        * \retval 1 This CDR couldn't find a Party B, and there were options
+        * \retval 1 This CDR couldn't find a Party B and channels were in the bridge
         */
        int (* const process_bridge_enter)(struct cdr_object *cdr,
                        struct ast_bridge_snapshot *bridge,
                        struct ast_channel_snapshot *channel);
 
        /*!
+        * \brief Process entering into a parking bridge.
+        *
+        * \param cdr The \ref cdr_object
+        * \param bridge The parking bridge that Party A just entered into
+        * \param channel The \ref ast_channel_snapshot for this CDR's Party A
+        *
+        * \retval 0 This CDR successfully transitioned itself into the parked state
+        * \retval 1 This CDR couldn't handle the parking transition and we need a
+        *  new CDR.
+        */
+       int (* const process_parking_bridge_enter)(struct cdr_object *cdr,
+                       struct ast_bridge_snapshot *bridge,
+                       struct ast_channel_snapshot *channel);
+
+       /*!
         * \brief Process the leaving of a bridge by this CDR.
         *
         * \param cdr The \ref cdr_object
@@ -441,16 +464,30 @@ struct cdr_object_fn_table {
        int (* const process_bridge_leave)(struct cdr_object *cdr,
                        struct ast_bridge_snapshot *bridge,
                        struct ast_channel_snapshot *channel);
+
+       /*!
+        * \brief Process an update informing us that the channel got itself parked
+        *
+        * \param cdr The \ref cdr_object
+        * \param channel The parking information for this CDR's party A
+        *
+        * \retval 0 This CDR successfully parked itself
+        * \retval 1 This CDR couldn't handle the park
+        */
+       int (* const process_parked_channel)(struct cdr_object *cdr,
+                       struct ast_parked_call_payload *parking_info);
 };
 
 static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
+static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked_call_payload *parking_info);
 
 static void single_state_init_function(struct cdr_object *cdr);
 static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
 static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
  * \brief The virtual table for the Single state.
@@ -471,7 +508,9 @@ struct cdr_object_fn_table single_state_fn_table = {
        .process_dial_begin = single_state_process_dial_begin,
        .process_dial_end = base_process_dial_end,
        .process_bridge_enter = single_state_process_bridge_enter,
+       .process_parking_bridge_enter = single_state_process_parking_bridge_enter,
        .process_bridge_leave = base_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
 };
 
 static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
@@ -505,6 +544,7 @@ struct cdr_object_fn_table dial_state_fn_table = {
 static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
 static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static int dialed_pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
  * \brief The virtual table for the Dialed Pending state.
@@ -529,7 +569,9 @@ struct cdr_object_fn_table dialed_pending_state_fn_table = {
        .process_party_a = dialed_pending_state_process_party_a,
        .process_dial_begin = dialed_pending_state_process_dial_begin,
        .process_bridge_enter = dialed_pending_state_process_bridge_enter,
+       .process_parking_bridge_enter = dialed_pending_state_process_parking_bridge_enter,
        .process_bridge_leave = base_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
 };
 
 static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
@@ -550,12 +592,14 @@ struct cdr_object_fn_table bridge_state_fn_table = {
        .process_party_a = base_process_party_a,
        .process_party_b = bridge_state_process_party_b,
        .process_bridge_leave = bridge_state_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
 };
 
 static void pending_state_init_function(struct cdr_object *cdr);
 static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
 static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static int pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
  * \brief The virtual table for the Pending state
@@ -576,7 +620,27 @@ struct cdr_object_fn_table bridged_pending_state_fn_table = {
        .process_dial_begin = pending_state_process_dial_begin,
        .process_dial_end = base_process_dial_end,
        .process_bridge_enter = pending_state_process_bridge_enter,
+       .process_parking_bridge_enter = pending_state_process_parking_bridge_enter,
        .process_bridge_leave = base_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
+};
+
+static int parked_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+
+/*!
+ * \brief The virtual table for the Parked state
+ *
+ * Parking is weird. Unlike typical bridges, it has to be treated somewhat
+ * uniquely - a channel in a parking bridge (which is a subclass of a holding
+ * bridge) has to be handled as if the channel went into an application.
+ * However, when the channel comes out, we need a new CDR - unlike the Single
+ * state.
+ */
+struct cdr_object_fn_table parked_state_fn_table = {
+       .name = "Parked",
+       .process_party_a = base_process_party_a,
+       .process_bridge_leave = parked_state_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
 };
 
 static void finalized_state_init_function(struct cdr_object *cdr);
@@ -1239,7 +1303,9 @@ static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snaps
         * of "AppDialX". Prevent that, and any other application changes we might not want
         * here.
         */
-       if (!ast_strlen_zero(snapshot->appl) && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))) {
+       if (!ast_strlen_zero(snapshot->appl)
+                       && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))
+                       && !ast_test_flag(&cdr->flags, AST_CDR_LOCK_APP)) {
                ast_string_field_set(cdr, appl, snapshot->appl);
                ast_string_field_set(cdr, data, snapshot->data);
        }
@@ -1265,6 +1331,26 @@ static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snap
        return 0;
 }
 
+static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked_call_payload *parking_info)
+{
+       char park_info[128];
+
+       ast_assert(!strcmp(parking_info->parkee->name, cdr->party_a.snapshot->name));
+
+       /* Update Party A information regardless */
+       cdr->fn_table->process_party_a(cdr, parking_info->parkee);
+
+       /* Fake out where we're parked */
+       ast_string_field_set(cdr, appl, "Park");
+       snprintf(park_info, sizeof(park_info), "%s:%u", parking_info->parkinglot, parking_info->parkingspace);
+       ast_string_field_set(cdr, data, park_info);
+
+       /* Prevent any further changes to the App/Data fields for this record */
+       ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
+
+       return 0;
+}
+
 /* SINGLE STATE */
 
 static void single_state_init_function(struct cdr_object *cdr) {
@@ -1395,6 +1481,13 @@ static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_
        return success;
 }
 
+static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+       cdr_object_transition_state(cdr, &parked_state_fn_table);
+       return 0;
+}
+
+
 /* DIAL STATE */
 
 static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
@@ -1567,14 +1660,18 @@ static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, str
        return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
 }
 
-static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+static int dialed_pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
-       struct cdr_object *new_cdr;
+       /* We can't handle this as we have a Party B - ask for a new one */
+       return 1;
+}
 
+static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
        cdr_object_transition_state(cdr, &finalized_state_fn_table);
-       new_cdr = cdr_object_create_and_append(cdr);
-       cdr_object_transition_state(cdr, &single_state_fn_table);
-       return new_cdr->fn_table->process_dial_begin(cdr, caller, peer);
+
+       /* Ask for a new CDR */
+       return 1;
 }
 
 /* BRIDGE STATE */
@@ -1647,6 +1744,25 @@ static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast
        return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
 }
 
+static int pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+       cdr_object_transition_state(cdr, &single_state_fn_table);
+       ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
+       return cdr->fn_table->process_parking_bridge_enter(cdr, bridge, channel);
+}
+
+/* PARKED STATE */
+
+static int parked_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+       if (strcmp(cdr->party_a.snapshot->name, channel->name)) {
+               return 1;
+       }
+       cdr_object_transition_state(cdr, &finalized_state_fn_table);
+
+       return 0;
+}
+
 /* FINALIZED STATE */
 
 static void finalized_state_init_function(struct cdr_object *cdr)
@@ -1688,16 +1804,14 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
        struct ast_multi_channel_blob *payload = stasis_message_data(message);
        struct ast_channel_snapshot *caller;
        struct ast_channel_snapshot *peer;
+       struct ast_channel_snapshot *party_a_snapshot;
+       struct ast_channel_snapshot *party_b_snapshot;
        struct cdr_object_snapshot *party_a;
-       struct cdr_object_snapshot *party_b;
        struct cdr_object *it_cdr;
        struct ast_json *dial_status_blob;
        const char *dial_status = NULL;
        int res = 1;
 
-       CDR_DEBUG(mod_cfg, "Dial message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
-       ast_assert(payload != NULL);
-
        caller = ast_multi_channel_blob_get_channel(payload, "caller");
        peer = ast_multi_channel_blob_get_channel(payload, "peer");
        if (!peer && !caller) {
@@ -1708,6 +1822,13 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                dial_status = ast_json_string_get(dial_status_blob);
        }
 
+       CDR_DEBUG(mod_cfg, "Dial %s message for %s, %s: %u.%08u\n",
+                       ast_strlen_zero(dial_status) ? "Begin" : "End",
+                       caller ? caller->name : "(none)",
+                       peer ? peer->name : "(none)",
+                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
+                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
        /* Figure out who is running this show */
        if (caller) {
                cdr_caller = ao2_find(active_cdrs_by_channel, caller->name, OBJ_KEY);
@@ -1716,22 +1837,24 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                cdr_peer = ao2_find(active_cdrs_by_channel, peer->name, OBJ_KEY);
        }
        if (cdr_caller && cdr_peer) {
-               party_a = cdr_object_pick_party_a(&cdr_caller->party_a, &cdr_peer->party_a);
-               if (!strcmp(party_a->snapshot->name, cdr_caller->party_a.snapshot->name)) {
+               party_a = cdr_object_pick_party_a(&cdr_caller->last->party_a, &cdr_peer->last->party_a);
+               if (!strcmp(party_a->snapshot->name, cdr_caller->last->party_a.snapshot->name)) {
                        cdr = cdr_caller;
-                       party_b = &cdr_peer->party_a;
+                       party_a_snapshot = caller;
+                       party_b_snapshot = peer;
                } else {
                        cdr = cdr_peer;
-                       party_b = &cdr_caller->party_a;
+                       party_a_snapshot = peer;
+                       party_b_snapshot = caller;
                }
        } else if (cdr_caller) {
                cdr = cdr_caller;
-               party_a = &cdr_caller->party_a;
-               party_b = NULL;
+               party_a_snapshot = caller;
+               party_b_snapshot = NULL;
        } else if (cdr_peer) {
                cdr = cdr_peer;
-               party_a = NULL;
-               party_b = &cdr_peer->party_a;
+               party_a_snapshot = NULL;
+               party_b_snapshot = peer;
        } else {
                return;
        }
@@ -1744,22 +1867,22 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                        }
                        CDR_DEBUG(mod_cfg, "%p - Processing Dial Begin message for channel %s, peer %s\n",
                                        cdr,
-                                       party_a ? party_a->snapshot->name : "(none)",
-                                       party_b ? party_b->snapshot->name : "(none)");
+                                       party_a_snapshot ? party_a_snapshot->name : "(none)",
+                                       party_b_snapshot ? party_b_snapshot->name : "(none)");
                        res &= it_cdr->fn_table->process_dial_begin(it_cdr,
-                                       party_a ? party_a->snapshot : NULL,
-                                       party_b ? party_b->snapshot : NULL);
+                                       party_a_snapshot,
+                                       party_b_snapshot);
                } else {
                        if (!it_cdr->fn_table->process_dial_end) {
                                continue;
                        }
                        CDR_DEBUG(mod_cfg, "%p - Processing Dial End message for channel %s, peer %s\n",
                                        cdr,
-                                       party_a ? party_a->snapshot->name : "(none)",
-                                       party_b ? party_b->snapshot->name : "(none)");
+                                       party_a_snapshot ? party_a_snapshot->name : "(none)",
+                                       party_b_snapshot ? party_b_snapshot->name : "(none)");
                        it_cdr->fn_table->process_dial_end(it_cdr,
-                                       party_a ? party_a->snapshot : NULL,
-                                       party_b ? party_b->snapshot : NULL,
+                                       party_a_snapshot,
+                                       party_b_snapshot,
                                        dial_status);
                }
        }
@@ -1773,8 +1896,8 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                        return;
                }
                new_cdr->fn_table->process_dial_begin(new_cdr,
-                               party_a ? party_a->snapshot : NULL,
-                               party_b ? party_b->snapshot : NULL);
+                               party_a_snapshot,
+                               party_b_snapshot);
        }
        ao2_unlock(cdr);
 }
@@ -1932,7 +2055,9 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription
                                /* We're not hung up and we have a new snapshot - we need a new CDR */
                                struct cdr_object *new_cdr;
                                new_cdr = cdr_object_create_and_append(cdr);
-                               new_cdr->fn_table->process_party_a(new_cdr, new_snapshot);
+                               if (new_cdr) {
+                                       new_cdr->fn_table->process_party_a(new_cdr, new_snapshot);
+                               }
                        }
                } else {
                        CDR_DEBUG(mod_cfg, "%p - Beginning finalize/dispatch for %s\n", cdr, old_snapshot->name);
@@ -1998,7 +2123,7 @@ static int filter_bridge_messages(struct ast_bridge_snapshot *bridge)
        /* Ignore holding bridge technology messages. We treat this simply as an application
         * that a channel enters into.
         */
-       if (!strcmp(bridge->technology, "holding_bridge")) {
+       if (!strcmp(bridge->technology, "holding_bridge") && strcmp(bridge->subclass, "parking")) {
                return 1;
        }
        return 0;
@@ -2006,8 +2131,10 @@ static int filter_bridge_messages(struct ast_bridge_snapshot *bridge)
 
 /*!
  * \brief Handler for when a channel leaves a bridge
- * \param bridge The \ref ast_bridge_snapshot representing the bridge
- * \param channel The \ref ast_channel_snapshot representing the channel
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message - hopefully a bridge one!
  */
 static void handle_bridge_leave_message(void *data, struct stasis_subscription *sub,
                struct stasis_topic *topic, struct stasis_message *message)
@@ -2032,7 +2159,10 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                return;
        }
 
-       CDR_DEBUG(mod_cfg, "Bridge Leave message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+       CDR_DEBUG(mod_cfg, "Bridge Leave message for %s: %u.%08u\n",
+                       channel->name,
+                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
+                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
 
        if (!cdr) {
                ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
@@ -2057,19 +2187,25 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                return;
        }
 
-       ao2_unlink(active_cdrs_by_bridge, cdr);
+       if (strcmp(bridge->subclass, "parking")) {
+               ao2_unlink(active_cdrs_by_bridge, cdr);
+       }
 
        /* Create a new pending record. If the channel decides to do something else,
         * the pending record will handle it - otherwise, if gets dropped.
         */
        pending_cdr = cdr_object_create_and_append(cdr);
-       cdr_object_transition_state(pending_cdr, &bridged_pending_state_fn_table);
+       if (pending_cdr) {
+               cdr_object_transition_state(pending_cdr, &bridged_pending_state_fn_table);
+       }
        ao2_unlock(cdr);
 
-       /* Party B */
-       ao2_callback(active_cdrs_by_bridge, OBJ_NODATA,
-                       cdr_object_party_b_left_bridge_cb,
-                       &leave_data);
+       if (strcmp(bridge->subclass, "parking")) {
+               /* Party B */
+               ao2_callback(active_cdrs_by_bridge, OBJ_NODATA,
+                               cdr_object_party_b_left_bridge_cb,
+                               &leave_data);
+       }
 }
 
 struct bridge_candidate {
@@ -2239,6 +2375,9 @@ static void bridge_candidate_add_to_cdr(struct cdr_object *cdr,
        struct cdr_object *new_cdr;
 
        new_cdr = cdr_object_create_and_append(cdr);
+       if (!new_cdr) {
+               return;
+       }
        cdr_object_snapshot_copy(&new_cdr->party_b, party_b);
        cdr_object_check_party_a_answer(new_cdr);
        ast_string_field_set(new_cdr, bridge, cdr->bridge);
@@ -2340,38 +2479,60 @@ static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_sna
        return;
 }
 
-/*!
- * \brief Handler for Stasis-Core bridge enter messages
- * \param data Passed on
- * \param sub The stasis subscription for this message callback
- * \param topic The topic this message was published for
- * \param message The message - hopefully a bridge one!
+/*! \brief Handle entering into a parking bridge
+ * \param cdr The CDR to operate on
+ * \param bridge The bridge the channel just entered
+ * \param channel The channel snapshot
  */
-static void handle_bridge_enter_message(void *data, struct stasis_subscription *sub,
-               struct stasis_topic *topic, struct stasis_message *message)
+static void handle_parking_bridge_enter_message(struct cdr_object *cdr,
+               struct ast_bridge_snapshot *bridge,
+               struct ast_channel_snapshot *channel)
 {
-       struct ast_bridge_blob *update = stasis_message_data(message);
-       struct ast_bridge_snapshot *bridge = update->bridge;
-       struct ast_channel_snapshot *channel = update->channel;
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
-                       ao2_cleanup);
        RAII_VAR(struct module_config *, mod_cfg,
                        ao2_global_obj_ref(module_configs), ao2_cleanup);
        int res = 1;
        struct cdr_object *it_cdr;
-       struct cdr_object *handled_cdr = NULL;
+       struct cdr_object *new_cdr;
 
-       if (filter_bridge_messages(bridge)) {
-               return;
-       }
+       ao2_lock(cdr);
 
-       CDR_DEBUG(mod_cfg, "Bridge Enter message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+               if (it_cdr->fn_table->process_parking_bridge_enter) {
+                       res &= it_cdr->fn_table->process_parking_bridge_enter(it_cdr, bridge, channel);
+               }
+               if (it_cdr->fn_table->process_party_a) {
+                       CDR_DEBUG(mod_cfg, "%p - Updating Party A %s snapshot\n", it_cdr,
+                                       channel->name);
+                       it_cdr->fn_table->process_party_a(it_cdr, channel);
+               }
+       }
 
-       if (!cdr) {
-               ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
-               return;
+       if (res) {
+               /* No one handled it - we need a new one! */
+               new_cdr = cdr_object_create_and_append(cdr);
+               if (new_cdr) {
+                       /* Let the single state transition us to Parked */
+                       cdr_object_transition_state(new_cdr, &single_state_fn_table);
+                       new_cdr->fn_table->process_parking_bridge_enter(new_cdr, bridge, channel);
+               }
        }
+       ao2_unlock(cdr);
+}
+
+/*! \brief Handle a bridge enter message for a 'normal' bridge
+ * \param cdr The CDR to operate on
+ * \param bridge The bridge the channel just entered
+ * \param channel The channel snapshot
+ */
+static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
+               struct ast_bridge_snapshot *bridge,
+               struct ast_channel_snapshot *channel)
+{
+       RAII_VAR(struct module_config *, mod_cfg,
+                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+       int res = 1;
+       struct cdr_object *it_cdr;
+       struct cdr_object *handled_cdr = NULL;
 
        ao2_lock(cdr);
 
@@ -2416,6 +2577,96 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription *
        ao2_unlock(cdr);
 }
 
+/*!
+ * \brief Handler for Stasis-Core bridge enter messages
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message - hopefully a bridge one!
+ */
+static void handle_bridge_enter_message(void *data, struct stasis_subscription *sub,
+               struct stasis_topic *topic, struct stasis_message *message)
+{
+       struct ast_bridge_blob *update = stasis_message_data(message);
+       struct ast_bridge_snapshot *bridge = update->bridge;
+       struct ast_channel_snapshot *channel = update->channel;
+       RAII_VAR(struct cdr_object *, cdr,
+                       ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+                       ao2_cleanup);
+       RAII_VAR(struct module_config *, mod_cfg,
+                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+       if (filter_bridge_messages(bridge)) {
+               return;
+       }
+
+       CDR_DEBUG(mod_cfg, "Bridge Enter message for channel %s: %u.%08u\n",
+                       channel->name,
+                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
+                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+       if (!cdr) {
+               ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+               return;
+       }
+
+       if (!strcmp(bridge->subclass, "parking")) {
+               handle_parking_bridge_enter_message(cdr, bridge, channel);
+       } else {
+               handle_standard_bridge_enter_message(cdr, bridge, channel);
+       }
+}
+
+/*!
+ * \brief Handler for when a channel is parked
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message about who got parked
+ * */
+static void handle_parked_call_message(void *data, struct stasis_subscription *sub,
+               struct stasis_topic *topic, struct stasis_message *message)
+{
+       struct ast_parked_call_payload *payload = stasis_message_data(message);
+       struct ast_channel_snapshot *channel = payload->parkee;
+       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+       RAII_VAR(struct module_config *, mod_cfg,
+                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct cdr_object *it_cdr;
+
+       /* Anything other than getting parked will be handled by other updates */
+       if (payload->event_type != PARKED_CALL) {
+               return;
+       }
+
+       /* No one got parked? */
+       if (!channel) {
+               return;
+       }
+
+       CDR_DEBUG(mod_cfg, "Parked Call message for channel %s: %u.%08u\n",
+                       channel->name,
+                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
+                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+       cdr = ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY);
+       if (!cdr) {
+               ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+               return;
+       }
+
+       ao2_lock(cdr);
+
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+               if (it_cdr->fn_table->process_parked_channel) {
+                       it_cdr->fn_table->process_parked_channel(it_cdr, payload);
+               }
+       }
+
+       ao2_unlock(cdr);
+
+}
+
 struct ast_cdr_config *ast_cdr_get_config(void)
 {
        RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
@@ -2953,6 +3204,7 @@ static void post_cdr(struct ast_cdr *cdr)
                if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) &&
                                cdr->disposition < AST_CDR_ANSWERED &&
                                (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
+                       ast_log(AST_LOG_WARNING, "Skipping CDR since we weren't answered\n");
                        continue;
                }
 
@@ -3584,6 +3836,11 @@ int ast_cdr_engine_init(void)
        if (!bridge_subscription) {
                return -1;
        }
+       parking_subscription = stasis_forward_all(ast_parking_topic(), cdr_topic);
+       if (!parking_subscription) {
+               return -1;
+       }
+
        stasis_router = stasis_message_router_create(cdr_topic);
        if (!stasis_router) {
                return -1;
@@ -3592,6 +3849,7 @@ int ast_cdr_engine_init(void)
        stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL);
        stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL);
        stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL);
+       stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL);
 
        sched = ast_sched_context_create();
        if (!sched) {