[svn-commits] dlee: branch dlee/stasis-http r380809 - in /team/dlee/stasis-http: include/as...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Mon Feb 4 10:38:00 CST 2013


Author: dlee
Date: Mon Feb  4 10:37:56 2013
New Revision: 380809

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=380809
Log:
Merged kmoore's channel state event caching patch r4

Modified:
    team/dlee/stasis-http/include/asterisk/channel.h
    team/dlee/stasis-http/include/asterisk/event.h
    team/dlee/stasis-http/include/asterisk/event_defs.h
    team/dlee/stasis-http/include/asterisk/stringfields.h
    team/dlee/stasis-http/main/channel.c
    team/dlee/stasis-http/main/event.c
    team/dlee/stasis-http/main/manager.c
    team/dlee/stasis-http/res/res_security_log.c
    team/dlee/stasis-http/tests/test_stringfields.c

Modified: team/dlee/stasis-http/include/asterisk/channel.h
URL: http://svnview.digium.com/svn/asterisk/team/dlee/stasis-http/include/asterisk/channel.h?view=diff&rev=380809&r1=380808&r2=380809
==============================================================================
--- team/dlee/stasis-http/include/asterisk/channel.h (original)
+++ team/dlee/stasis-http/include/asterisk/channel.h Mon Feb  4 10:37:56 2013
@@ -151,6 +151,7 @@
 #include "asterisk/channelstate.h"
 #include "asterisk/ccss.h"
 #include "asterisk/framehook.h"
+#include "asterisk/indications.h"
 
 #define DATASTORE_INHERIT_FOREVER	INT_MAX
 
@@ -4102,4 +4103,83 @@
 void ast_channel_dialed_causes_clear(const struct ast_channel *chan);
 
 struct ast_flags *ast_channel_flags(struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Structure representing a snapshot of channel state.
+ */
+struct ast_channel_snapshot {
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(name);			/*!< ASCII unique channel name */
+		AST_STRING_FIELD(accountcode);		/*!< Account code for billing */
+		AST_STRING_FIELD(peeraccount);		/*!< Peer account code for billing */
+		AST_STRING_FIELD(userfield);		/*!< Userfield for CEL billing */
+		AST_STRING_FIELD(uniqueid);		/*!< Unique Channel Identifier */
+		AST_STRING_FIELD(linkedid);		/*!< Linked Channel Identifier -- gets propagated by linkage */
+		AST_STRING_FIELD(parkinglot);		/*! Default parking lot, if empty, default parking lot  */
+		AST_STRING_FIELD(hangupsource);		/*! Who is responsible for hanging up this channel */
+		AST_STRING_FIELD(appl);			/*!< Current application */
+		AST_STRING_FIELD(data);			/*!< Data passed to current application */
+		AST_STRING_FIELD(context);		/*!< Dialplan: Current extension context */
+		AST_STRING_FIELD(exten);		/*!< Dialplan: Current extension number */
+		AST_STRING_FIELD(caller_name);		/*!< Caller ID Name */
+		AST_STRING_FIELD(caller_number);	/*!< Caller ID Number */
+		AST_STRING_FIELD(connected_name);	/*!< Connected Line Name */
+		AST_STRING_FIELD(connected_number);	/*!< Connected Line Number */
+	);
+
+	struct timeval creationtime;	/*!< The time of channel creation */
+	enum ast_channel_state state;	/*!< State of line */
+	int priority;			/*!< Dialplan: Current extension priority */
+	int amaflags;			/*!< AMA flags for billing */
+	int hangupcause;		/*!< Why is the channel hanged up. See causes.h */
+	struct ast_flags flags;		/*!< channel flags of AST_FLAG_ type */
+};
+
+/*!
+ * \since 12
+ * \brief Generate a snapshot of the channel state
+ *
+ * \param chan The channel from which to generate a snapshot
+ *
+ * \retval pointer on success (must be ast_freed)
+ * \retval NULL on error
+ */
+struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Copy a snapshot of the channel state for use with the event system
+ *
+ * \param copy The snapshot to be copied into
+ * \param orig The snapshot to copy
+ * \param len The length of the snapshots to be copied
+ *
+ * \retval the value passed in via copy on success
+ * \retval NULL on error
+ */
+void *ast_channel_snapshot_copy(void *copy, const void *orig, size_t len);
+
+/*!
+ * \since 12
+ * \brief Compare two channel state snapshots
+ *
+ * \param one The first snapshot to compare
+ * \param one_len The length of the first snapshot to compare
+ * \param two The second snapshot to compare
+ * \param two_len The length of the second snapshot to compare
+ *
+ * \retval zero if equal
+ * \retval non-zero if not equal
+ */
+int ast_channel_snapshot_cmp(const void *one, size_t one_len, const void *two, size_t two_len);
+
+/*!
+ * \since 12
+ * \brief Destroy a snapshot of the channel state
+ *
+ * \param orig The snapshot to destroy
+ * \param orig_len The length of the snapshot to destroy
+ */
+void ast_channel_snapshot_destroy(void *orig, size_t orig_len);
 #endif /* _ASTERISK_CHANNEL_H */

Modified: team/dlee/stasis-http/include/asterisk/event.h
URL: http://svnview.digium.com/svn/asterisk/team/dlee/stasis-http/include/asterisk/event.h?view=diff&rev=380809&r1=380808&r2=380809
==============================================================================
--- team/dlee/stasis-http/include/asterisk/event.h (original)
+++ team/dlee/stasis-http/include/asterisk/event.h Mon Feb  4 10:37:56 2013
@@ -203,6 +203,21 @@
 	enum ast_event_ie_type ie_type, void *data, size_t raw_datalen);
 
 /*!
+ * \brief Append a raw local parameter to a subscription
+ *
+ * \param sub the dynamic subscription allocated with ast_event_subscribe_new()
+ * \param ie_type the information element type for the parameter
+ * \param data the data that must be present in the event to match this subscription
+ * \param raw_datalen length of data
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ * \since 12
+ */
+int ast_event_sub_append_ie_raw_local(struct ast_event_sub *sub,
+	enum ast_event_ie_type ie_type, void *data, size_t raw_datalen);
+
+/*!
  * \brief Append an 'exists' parameter to a subscription
  *
  * \param sub the dynamic subscription allocated with ast_event_subscribe_new()
@@ -512,6 +527,25 @@
 	const void *data, size_t data_len);
 
 /*!
+ * \brief Append an information element that has a pointer-containing raw payload
+ * \since 12
+ *
+ * \param event the event that the IE will be appended to
+ * \param ie_type the type of IE to append
+ * \param data A pointer to the pointer-containing raw data for the payload of the IE
+ * \param data_len The amount of data to allocate for the payload
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * The pointer to the event will get updated with the new location for the event
+ * that now contains the appended information element.  If the re-allocation of
+ * the memory for this event fails, it will be set to NULL.
+ */
+int ast_event_append_ie_raw_local(struct ast_event **event, enum ast_event_ie_type ie_type,
+	const void *data, size_t data_len);
+
+/*!
  * \brief Append the global EID IE
  *
  * \param event the event to append IE to
@@ -594,6 +628,30 @@
 uint16_t ast_event_get_ie_raw_payload_len(const struct ast_event *event, enum ast_event_ie_type ie_type);
 
 /*!
+ * \brief Get the value of an information element that has a pointer-containing raw payload
+ * \since 12
+ *
+ * \param event The event to get the IE from
+ * \param ie_type the type of information element to retrieve
+ *
+ * \return This returns the payload of the information element with the given type.
+ *         If the information element isn't found, NULL will be returned.
+ */
+const void *ast_event_get_ie_raw_local(const struct ast_event *event, enum ast_event_ie_type ie_type);
+
+/*!
+ * \brief Get the length of the pointer-containing raw payload for a particular IE
+ * \since 12
+ *
+ * \param event The event to get the IE payload length from
+ * \param ie_type the type of information element to get the length of
+ *
+ * \return If an IE of type ie_type is found, its payload length is returned.
+ *         Otherwise, 0 is returned.
+ */
+uint16_t ast_event_get_ie_raw_local_payload_len(const struct ast_event *event, enum ast_event_ie_type ie_type);
+
+/*!
  * \brief Get the string representation of an information element type
  *
  * \param ie_type the information element type to get the string representation of
@@ -699,6 +757,16 @@
 enum ast_event_ie_type ast_event_iterator_get_ie_type(struct ast_event_iterator *iterator);
 
 /*!
+ * \brief Get the PL type of the current IE in the iterator instance
+ *
+ * \param iterator The iterator instance
+ *
+ * \return the ie PL type associated with the current IE
+ * \return AST_EVENT_IE_PLTYPE_UNKNOWN if the IE type is unknown
+ */
+enum ast_event_ie_pltype ast_event_iterator_get_ie_pltype(struct ast_event_iterator *iterator);
+
+/*!
  * \brief Get the value of the current IE in the iterator as an integer payload
  *
  * \param iterator The iterator instance
@@ -744,11 +812,40 @@
 uint16_t ast_event_iterator_get_ie_raw_payload_len(struct ast_event_iterator *iterator);
 
 /*!
+ * \brief Get the value of the current IE in the iterator instance that has a pointer-containing raw payload
+ * \since 12
+ *
+ * \param iterator The iterator instance
+ *
+ * \return This returns the payload of the information element as type raw local.
+ */
+void *ast_event_iterator_get_ie_raw_local(struct ast_event_iterator *iterator);
+
+/*!
+ * \brief Get the length of the pointer-containing raw payload for the current IE for an iterator
+ *
+ * \param iterator The IE iterator
+ *
+ * \return The payload length of the current IE
+ */
+uint16_t ast_event_iterator_get_ie_raw_local_payload_len(struct ast_event_iterator *iterator);
+
+/*!
  * \brief Get the minimum length of an ast_event.
  *
  * \return minimum amount of memory that will be consumed by any ast_event.
  */
 size_t ast_event_minimum_length(void);
+
+/*!
+ * \brief Send a channel state-related event.
+ *
+ * \param chan the channel about which to send information
+ *
+ * \return zero on success
+ * \return non-zero on failure
+ */
+int ast_event_queue_channel_state(struct ast_channel *chan);
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }

Modified: team/dlee/stasis-http/include/asterisk/event_defs.h
URL: http://svnview.digium.com/svn/asterisk/team/dlee/stasis-http/include/asterisk/event_defs.h?view=diff&rev=380809&r1=380808&r2=380809
==============================================================================
--- team/dlee/stasis-http/include/asterisk/event_defs.h (original)
+++ team/dlee/stasis-http/include/asterisk/event_defs.h Mon Feb  4 10:37:56 2013
@@ -60,8 +60,10 @@
 	AST_EVENT_ACL_CHANGE          = 0x0b,
 	/*! Send out a ping for debugging distributed events */
 	AST_EVENT_PING                = 0x0c,
+	/*! A channel state snapshot */
+	AST_EVENT_CHANNEL_STATE       = 0x0d,
 	/*! Number of event types.  This should be the last event type + 1 */
-	AST_EVENT_TOTAL               = 0x0d,
+	AST_EVENT_TOTAL               = 0x0e,
 };
 
 /*! \brief Event Information Element types */
@@ -304,8 +306,16 @@
 	 * Payload type: UINT
 	 */
 	AST_EVENT_IE_CACHABLE            = 0x003d,
+
+	/*!
+	 * \brief Channel state summary
+	 * Used by: AST_EVENT_CHANNEL_STATE
+	 * Payload type: RAW
+	 */
+	AST_EVENT_IE_CHANNEL_STATE       = 0x003e,
+
 	/*! \brief Must be the last IE value +1 */
-	AST_EVENT_IE_TOTAL               = 0x003e,
+	AST_EVENT_IE_TOTAL               = 0x003f,
 };
 
 /*!
@@ -323,6 +333,8 @@
 	AST_EVENT_IE_PLTYPE_RAW,
 	/*! Bit flags (unsigned integer, compared using boolean logic) */
 	AST_EVENT_IE_PLTYPE_BITFLAGS,
+	/*! Raw data, compared with custom function. May contain pointers. */
+	AST_EVENT_IE_PLTYPE_RAW_LOCAL,
 };
 
 /*!

Modified: team/dlee/stasis-http/include/asterisk/stringfields.h
URL: http://svnview.digium.com/svn/asterisk/team/dlee/stasis-http/include/asterisk/stringfields.h?view=diff&rev=380809&r1=380808&r2=380809
==============================================================================
--- team/dlee/stasis-http/include/asterisk/stringfields.h (original)
+++ team/dlee/stasis-http/include/asterisk/stringfields.h Mon Feb  4 10:37:56 2013
@@ -234,6 +234,27 @@
 	struct ast_string_field_pool *__field_mgr_pool;	\
 	field_list					\
 	struct ast_string_field_mgr __field_mgr
+
+/*!
+  \brief Compare the string fields in two instances of the same structure
+  \param instance1 The first instance of the structure to be compared
+  \param instance2 The second instance of the structure to be compared
+*/
+#define ast_string_field_cmp(instance1, instance2) \
+({ \
+	int cmp = 0; \
+	size_t ptr_size = sizeof(char *); \
+	int len = ((void *)&(instance1)->__field_mgr - (void *)&(instance1)->__field_mgr_pool)/ptr_size - 1; \
+	char **head1 = (void *)&(instance1)->__field_mgr_pool + ptr_size; \
+	char **head2 = (void *)&(instance2)->__field_mgr_pool + ptr_size; \
+	for (; len >= 0; len--) { \
+		cmp = strcmp(head1[len], head2[len]); \
+		if (cmp) { \
+			break; \
+		} \
+	} \
+	cmp; \
+})
 
 /*!
   \brief Initialize a field pool and fields

Modified: team/dlee/stasis-http/main/channel.c
URL: http://svnview.digium.com/svn/asterisk/team/dlee/stasis-http/main/channel.c?view=diff&rev=380809&r1=380808&r2=380809
==============================================================================
--- team/dlee/stasis-http/main/channel.c (original)
+++ team/dlee/stasis-http/main/channel.c Mon Feb  4 10:37:56 2013
@@ -1145,34 +1145,7 @@
 	 * a lot of data into this func to do it here!
 	 */
 	if (ast_get_channel_tech(tech) || (tech2 && ast_get_channel_tech(tech2))) {
-		/*** DOCUMENTATION
-			<managerEventInstance>
-				<synopsis>Raised when a new channel is created.</synopsis>
-				<syntax>
-					<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newstate']/managerEventInstance/syntax/parameter[@name='ChannelState'])" />
-					<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newstate']/managerEventInstance/syntax/parameter[@name='ChannelStateDesc'])" />
-				</syntax>
-			</managerEventInstance>
-		***/
-		ast_manager_event(tmp, EVENT_FLAG_CALL, "Newchannel",
-			"Channel: %s\r\n"
-			"ChannelState: %d\r\n"
-			"ChannelStateDesc: %s\r\n"
-			"CallerIDNum: %s\r\n"
-			"CallerIDName: %s\r\n"
-			"AccountCode: %s\r\n"
-			"Exten: %s\r\n"
-			"Context: %s\r\n"
-			"Uniqueid: %s\r\n",
-			ast_channel_name(tmp),
-			state,
-			ast_state2str(state),
-			S_OR(cid_num, ""),
-			S_OR(cid_name, ""),
-			ast_channel_accountcode(tmp),
-			S_OR(exten, ""),
-			S_OR(context, ""),
-			ast_channel_uniqueid(tmp));
+		ast_event_queue_channel_state(tmp);
 	}
 
 	ast_channel_internal_finalize(tmp);
@@ -2893,39 +2866,8 @@
 	ast_channel_unlock(chan);
 
 	ast_cc_offer(chan);
-	/*** DOCUMENTATION
-		<managerEventInstance>
-			<synopsis>Raised when a channel is hung up.</synopsis>
-				<syntax>
-					<parameter name="Cause">
-						<para>A numeric cause code for why the channel was hung up.</para>
-					</parameter>
-					<parameter name="Cause-txt">
-						<para>A description of why the channel was hung up.</para>
-					</parameter>
-				</syntax>
-		</managerEventInstance>
-	***/
-	ast_manager_event(chan, EVENT_FLAG_CALL, "Hangup",
-		"Channel: %s\r\n"
-		"Uniqueid: %s\r\n"
-		"CallerIDNum: %s\r\n"
-		"CallerIDName: %s\r\n"
-		"ConnectedLineNum: %s\r\n"
-		"ConnectedLineName: %s\r\n"
-		"AccountCode: %s\r\n"
-		"Cause: %d\r\n"
-		"Cause-txt: %s\r\n",
-		ast_channel_name(chan),
-		ast_channel_uniqueid(chan),
-		S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"),
-		S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>"),
-		S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, "<unknown>"),
-		S_COR(ast_channel_connected(chan)->id.name.valid, ast_channel_connected(chan)->id.name.str, "<unknown>"),
-		ast_channel_accountcode(chan),
-		ast_channel_hangupcause(chan),
-		ast_cause2str(ast_channel_hangupcause(chan))
-		);
+
+	ast_event_queue_channel_state(chan);
 
 	if (ast_channel_cdr(chan) && !ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_BRIDGED) &&
 		!ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_POST_DISABLED) &&
@@ -7437,47 +7379,7 @@
 	 * we override what they are saying the state is and things go amuck. */
 	ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, (ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE) ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), name);
 
-	/* setstate used to conditionally report Newchannel; this is no more */
-	/*** DOCUMENTATION
-		<managerEventInstance>
-			<synopsis>Raised when a channel's state changes.</synopsis>
-			<syntax>
-				<parameter name="ChannelState">
-					<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
-				</parameter>
-				<parameter name="ChannelStateDesc">
-					<enumlist>
-						<enum name="Down"/>
-						<enum name="Rsrvd"/>
-						<enum name="OffHook"/>
-						<enum name="Dialing"/>
-						<enum name="Ring"/>
-						<enum name="Ringing"/>
-						<enum name="Up"/>
-						<enum name="Busy"/>
-						<enum name="Dialing Offhook"/>
-						<enum name="Pre-ring"/>
-						<enum name="Unknown"/>
-					</enumlist>
-				</parameter>
-			</syntax>
-		</managerEventInstance>
-	***/
-	ast_manager_event(chan, EVENT_FLAG_CALL, "Newstate",
-		"Channel: %s\r\n"
-		"ChannelState: %d\r\n"
-		"ChannelStateDesc: %s\r\n"
-		"CallerIDNum: %s\r\n"
-		"CallerIDName: %s\r\n"
-		"ConnectedLineNum: %s\r\n"
-		"ConnectedLineName: %s\r\n"
-		"Uniqueid: %s\r\n",
-		ast_channel_name(chan), ast_channel_state(chan), ast_state2str(ast_channel_state(chan)),
-		S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""),
-		S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""),
-		S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, ""),
-		S_COR(ast_channel_connected(chan)->id.name.valid, ast_channel_connected(chan)->id.name.str, ""),
-		ast_channel_uniqueid(chan));
+	ast_event_queue_channel_state(chan);
 
 	return 0;
 }
@@ -11281,3 +11183,108 @@
 {
 	ao2_unlink(channels, chan);
 }
+
+struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *chan)
+{
+	struct ast_channel_snapshot *snapshot;
+
+	snapshot = ast_calloc_with_stringfields(1, struct ast_channel_snapshot, 1024);
+	if (!snapshot) {
+		return NULL;
+	}
+
+	// fill out string fields here
+	ast_string_field_set(snapshot, name, ast_channel_name(chan));
+	ast_string_field_set(snapshot, accountcode, ast_channel_accountcode(chan));
+	ast_string_field_set(snapshot, peeraccount, ast_channel_peeraccount(chan));
+	ast_string_field_set(snapshot, userfield, ast_channel_userfield(chan));
+	ast_string_field_set(snapshot, uniqueid, ast_channel_uniqueid(chan));
+	ast_string_field_set(snapshot, linkedid, ast_channel_linkedid(chan));
+	ast_string_field_set(snapshot, parkinglot, ast_channel_parkinglot(chan));
+	ast_string_field_set(snapshot, hangupsource, ast_channel_hangupsource(chan));
+	if (ast_channel_appl(chan)) {
+		ast_string_field_set(snapshot, appl, ast_channel_appl(chan));
+	}
+	if (ast_channel_data(chan)) {
+		ast_string_field_set(snapshot, data, ast_channel_data(chan));
+	}
+	ast_string_field_set(snapshot, context, ast_channel_context(chan));
+	ast_string_field_set(snapshot, exten, ast_channel_exten(chan));
+
+	ast_string_field_set(snapshot, caller_name,
+		S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""));
+	ast_string_field_set(snapshot, caller_number,
+		S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""));
+
+	ast_string_field_set(snapshot, connected_name,
+		S_COR(ast_channel_connected(chan)->id.name.valid, ast_channel_connected(chan)->id.name.str, ""));
+	ast_string_field_set(snapshot, connected_number,
+		S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, ""));
+
+	// copy other information
+	snapshot->creationtime = ast_channel_creationtime(chan);
+	snapshot->state = ast_channel_state(chan);
+	snapshot->priority = ast_channel_priority(chan);
+	snapshot->amaflags = ast_channel_amaflags(chan);
+	snapshot->hangupcause = ast_channel_hangupcause(chan);
+	snapshot->flags = *ast_channel_flags(chan);
+
+	return snapshot;
+}
+
+void *ast_channel_snapshot_copy(void *copy_in, const void *orig_in, size_t len)
+{
+	struct ast_channel_snapshot *copy = copy_in;
+	const struct ast_channel_snapshot *orig = orig_in;
+	if ((ast_string_field_init(copy, 1024))) {
+		return NULL;
+	}
+	ast_string_field_set(copy, name, orig->name);
+	ast_string_field_set(copy, accountcode, orig->accountcode);
+	ast_string_field_set(copy, peeraccount, orig->peeraccount);
+	ast_string_field_set(copy, userfield, orig->userfield);
+	ast_string_field_set(copy, uniqueid, orig->uniqueid);
+	ast_string_field_set(copy, linkedid, orig->linkedid);
+	ast_string_field_set(copy, parkinglot, orig->parkinglot);
+	ast_string_field_set(copy, hangupsource, orig->hangupsource);
+	ast_string_field_set(copy, appl, orig->appl);
+	ast_string_field_set(copy, data, orig->data);
+	ast_string_field_set(copy, context, orig->context);
+	ast_string_field_set(copy, exten, orig->exten);
+	ast_string_field_set(copy, caller_name, orig->caller_name);
+	ast_string_field_set(copy, caller_number, orig->caller_number);
+	ast_string_field_set(copy, connected_name, orig->connected_name);
+	ast_string_field_set(copy, connected_number, orig->connected_number);
+
+	// copy other information
+	copy->creationtime = orig->creationtime;
+	copy->state = orig->state;
+	copy->priority = orig->priority;
+	copy->amaflags = orig->amaflags;
+	copy->hangupcause = orig->hangupcause;
+	copy->flags = orig->flags;
+	return copy_in;
+}
+
+int ast_channel_snapshot_cmp(const void *one_in, size_t one_len, const void *two_in, size_t two_len)
+{
+	/* Compare string fields */
+	const struct ast_channel_snapshot *one = one_in, *two = two_in;
+	int res = ast_string_field_cmp(one, two);
+	size_t non_str_len, offset;
+
+	if (res) {
+		return res;
+	}
+
+	/* Compare the rest of the structure */
+	offset = (const void *)&one->creationtime - (const void *)one;
+	non_str_len = one_len - offset;
+	return memcmp(one + offset, two + offset, non_str_len);
+}
+
+void ast_channel_snapshot_destroy(void *orig_in, size_t orig_len)
+{
+	struct ast_channel_snapshot *orig = orig_in;
+	ast_string_field_free_memory(orig);
+}

Modified: team/dlee/stasis-http/main/event.c
URL: http://svnview.digium.com/svn/asterisk/team/dlee/stasis-http/main/event.c?view=diff&rev=380809&r1=380808&r2=380809
==============================================================================
--- team/dlee/stasis-http/main/event.c (original)
+++ team/dlee/stasis-http/main/event.c Mon Feb  4 10:37:56 2013
@@ -43,6 +43,7 @@
 #include "asterisk/taskprocessor.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/cli.h"
+#include "asterisk/channel.h"
 
 static struct ast_taskprocessor *event_dispatcher;
 
@@ -51,8 +52,7 @@
  *
  * \note The format of this structure is important.  Since these events may
  *       be sent directly over a network, changing this structure will break
- *       compatibility with older versions.  However, at this point, this code
- *       has not made it into a release, so it is still fair game for change.
+ *       compatibility with older versions.
  */
 struct ast_event_ie {
 	enum ast_event_ie_type ie_type:16;
@@ -143,6 +143,7 @@
 static int ast_event_hash_devstate(const void *obj, const int flags);
 static int ast_event_hash_devstate_change(const void *obj, const int flags);
 static int ast_event_hash_presence_state_change(const void *obj, const int flags);
+static int ast_event_hash_channel_state(const void *obj, const int flags);
 
 #ifdef LOW_MEMORY
 #define NUM_CACHE_BUCKETS 17
@@ -191,6 +192,10 @@
 		.hash_fn = ast_event_hash_presence_state_change,
 		.cache_args = { AST_EVENT_IE_PRESENCE_STATE, },
 	},
+	[AST_EVENT_CHANNEL_STATE] = {
+		.hash_fn = ast_event_hash_channel_state,
+		.cache_args = { AST_EVENT_IE_CEL_CHANNAME},
+	},
 
 };
 
@@ -215,6 +220,7 @@
 	[AST_EVENT_CEL]                 = "CEL",
 	[AST_EVENT_SECURITY]            = "Security",
 	[AST_EVENT_NETWORK_CHANGE]      = "NetworkChange",
+	[AST_EVENT_CHANNEL_STATE]       = "ChannelState",
 };
 
 /*!
@@ -222,7 +228,10 @@
  */
 static const struct ie_map {
 	enum ast_event_ie_pltype ie_pltype;
-	const char *name;
+	const char *name;						/*!< Human-readable name for the IE type */
+	void *(*copy)(void *copy, const void *orig, size_t len);	/*!< Copy constructor used for RAW_LOCAL PLTYPEs */
+	int (*cmp)(const void *one, size_t one_len, const void *two, size_t two_len);	/*!< Comparator used for RAW_LOCAL PLTYPEs */
+	void (*destroy)(void *orig, size_t orig_len);			/*!< Destructor used for RAW_LOCAL PLTYPEs */
 } ie_maps[AST_EVENT_IE_TOTAL] = {
 	[AST_EVENT_IE_NEWMSGS]             = { AST_EVENT_IE_PLTYPE_UINT, "NewMessages" },
 	[AST_EVENT_IE_OLDMSGS]             = { AST_EVENT_IE_PLTYPE_UINT, "OldMessages" },
@@ -279,7 +288,8 @@
 	[AST_EVENT_IE_RECEIVED_HASH]       = { AST_EVENT_IE_PLTYPE_STR,  "ReceivedHash" },
 	[AST_EVENT_IE_USING_PASSWORD]      = { AST_EVENT_IE_PLTYPE_UINT, "UsingPassword" },
 	[AST_EVENT_IE_ATTEMPTED_TRANSPORT] = { AST_EVENT_IE_PLTYPE_STR,  "AttemptedTransport" },
-	[AST_EVENT_IE_CACHABLE]            = { AST_EVENT_IE_PLTYPE_UINT,  "Cachable" },
+	[AST_EVENT_IE_CACHABLE]            = { AST_EVENT_IE_PLTYPE_UINT, "Cachable" },
+	[AST_EVENT_IE_CHANNEL_STATE]       = { AST_EVENT_IE_PLTYPE_RAW_LOCAL,  "ChannelState", ast_channel_snapshot_copy, ast_channel_snapshot_cmp, ast_channel_snapshot_destroy },
 };
 
 const char *ast_event_get_type_name(const struct ast_event *event)
@@ -364,6 +374,10 @@
 		ast_free((char *) ie_val->payload.str);
 		break;
 	case AST_EVENT_IE_PLTYPE_RAW:
+		ast_free(ie_val->payload.raw);
+		break;
+	case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+		ie_maps[ie_val->ie_type].destroy(ie_val->payload.raw, ie_val->raw_datalen);
 		ast_free(ie_val->payload.raw);
 		break;
 	case AST_EVENT_IE_PLTYPE_UINT:
@@ -442,6 +456,13 @@
 			&& !memcmp(sub_ie_val->payload.raw, event_ie_val->payload.raw,
 				sub_ie_val->raw_datalen));
 		break;
+	case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+		res = (sub_ie_val->raw_datalen == event_ie_val->raw_datalen
+			&& !ie_maps[sub_ie_val->ie_type].cmp(sub_ie_val->payload.raw,
+							sub_ie_val->raw_datalen,
+							event_ie_val->payload.raw,
+							event_ie_val->raw_datalen));
+		break;
 	case AST_EVENT_IE_PLTYPE_EXISTS:
 		/* Should never get here since check_ie_vals cannot have this type. */
 		break;
@@ -507,6 +528,20 @@
 
 			ie_value->payload.raw = ast_alloca(datalen);
 			memcpy(ie_value->payload.raw, data, datalen);
+			ie_value->raw_datalen = datalen;
+			insert = 1;
+			break;
+		}
+		case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+		{
+			void *data = va_arg(ap, void *);
+			size_t datalen = va_arg(ap, size_t);
+
+			ie_value->payload.raw = ast_alloca(datalen);
+			ie_maps[ie_type].copy(
+				ie_value->payload.raw,
+				data,
+				datalen);
 			ie_value->raw_datalen = datalen;
 			insert = 1;
 			break;
@@ -628,6 +663,29 @@
 			&& !memcmp(buf, ast_event_get_ie_raw(event, ie_val->ie_type), ie_payload_len)) ? 1 : 0;
 	}
 
+	case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+	{
+		const void *buf = event2 ? ast_event_get_ie_raw_local(event2, ie_val->ie_type) : ie_val->payload.raw;
+		uint16_t ie_payload_len = event2 ? ast_event_get_ie_raw_local_payload_len(event2, ie_val->ie_type) : ie_val->raw_datalen;
+		uint16_t event1_payload_len = ast_event_get_ie_raw_local_payload_len(event, ie_val->ie_type);
+		int event_cmp;
+
+		if (!buf) {
+			return 0;
+		}
+		if (ie_payload_len != event1_payload_len) {
+			return 0;
+		}
+		event_cmp = ie_maps[ie_val->ie_type].cmp(buf,
+							ie_payload_len,
+							ast_event_get_ie_raw_local(event, ie_val->ie_type),
+							event1_payload_len);
+		if (event_cmp) {
+			return 0;
+		}
+		return 1;
+	}
+
 	case AST_EVENT_IE_PLTYPE_EXISTS:
 	{
 		return ast_event_get_ie_raw(event, ie_val->ie_type) ? 1 : 0;
@@ -704,6 +762,9 @@
 		case AST_EVENT_IE_PLTYPE_RAW:
 			ast_event_append_ie_raw(&event, ie_val->ie_type, ie_val->payload.raw, ie_val->raw_datalen);
 			break;
+		case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+			ast_event_append_ie_raw_local(&event, ie_val->ie_type, ie_val->payload.raw, ie_val->raw_datalen);
+			break;
 		}
 		if (!event)
 			break;
@@ -901,6 +962,38 @@
 	return 0;
 }
 
+int ast_event_sub_append_ie_raw_local(struct ast_event_sub *sub,
+	enum ast_event_ie_type ie_type, void *data, size_t raw_local_datalen)
+{
+	struct ast_event_ie_val *ie_val;
+
+	if (ie_type <= 0 || ie_type >= AST_EVENT_IE_TOTAL) {
+		return -1;
+	}
+
+	if (!(ie_val = ast_calloc(1, sizeof(*ie_val)))) {
+		return -1;
+	}
+
+	ie_val->ie_type = ie_type;
+	ie_val->ie_pltype = AST_EVENT_IE_PLTYPE_RAW_LOCAL;
+	ie_val->raw_datalen = raw_local_datalen;
+
+	if (!(ie_val->payload.raw = ast_malloc(raw_local_datalen))) {
+		ast_free(ie_val);
+		return -1;
+	}
+
+	ie_maps[ie_type].copy(
+		ie_val->payload.raw,
+		data,
+		raw_local_datalen);
+
+	AST_LIST_INSERT_TAIL(&sub->ie_vals, ie_val, entry);
+
+	return 0;
+}
+
 int ast_event_sub_activate(struct ast_event_sub *sub)
 {
 	if (ast_event_check_subscriber(AST_EVENT_SUB,
@@ -969,6 +1062,13 @@
 			void *data = va_arg(ap, void *);
 			size_t data_len = va_arg(ap, size_t);
 			ast_event_sub_append_ie_raw(sub, ie_type, data, data_len);
+			break;
+		}
+		case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+		{
+			void *data = va_arg(ap, void *);
+			size_t data_len = va_arg(ap, size_t);
+			ast_event_sub_append_ie_raw_local(sub, ie_type, data, data_len);
 			break;
 		}
 		case AST_EVENT_IE_PLTYPE_EXISTS:
@@ -1053,6 +1153,15 @@
 	return ntohs(iterator->ie->ie_type);
 }
 
+enum ast_event_ie_pltype ast_event_iterator_get_ie_pltype(struct ast_event_iterator *iterator)
+{
+	enum ast_event_ie_type ie_type = ast_event_iterator_get_ie_type(iterator);
+	if (ie_type < AST_EVENT_IE_TOTAL) {
+		return ie_maps[ie_type].ie_pltype;
+	}
+	return AST_EVENT_IE_PLTYPE_UNKNOWN;
+}
+
 uint32_t ast_event_iterator_get_ie_uint(struct ast_event_iterator *iterator)
 {
 	return ntohl(get_unaligned_uint32(iterator->ie->ie_payload));
@@ -1080,6 +1189,16 @@
 uint16_t ast_event_iterator_get_ie_raw_payload_len(struct ast_event_iterator *iterator)
 {
 	return ntohs(iterator->ie->ie_payload_len);
+}
+
+void *ast_event_iterator_get_ie_raw_local(struct ast_event_iterator *iterator)
+{
+	return ast_event_iterator_get_ie_raw(iterator);
+}
+
+uint16_t ast_event_iterator_get_ie_raw_local_payload_len(struct ast_event_iterator *iterator)
+{
+	return ast_event_iterator_get_ie_raw_payload_len(iterator);
 }
 
 enum ast_event_type ast_event_get_type(const struct ast_event *event)
@@ -1149,6 +1268,16 @@
 	}
 
 	return 0;
+}
+
+const void *ast_event_get_ie_raw_local(const struct ast_event *event, enum ast_event_ie_type ie_type)
+{
+	return ast_event_get_ie_raw(event, ie_type);
+}
+
+uint16_t ast_event_get_ie_raw_local_payload_len(const struct ast_event *event, enum ast_event_ie_type ie_type)
+{
+	return ast_event_get_ie_raw_payload_len(event, ie_type);
 }
 
 int ast_event_append_ie_str(struct ast_event **event, enum ast_event_ie_type ie_type,
@@ -1204,6 +1333,33 @@
 	ie->ie_type = htons(ie_type);
 	ie->ie_payload_len = htons(data_len);
 	memcpy(ie->ie_payload, data, data_len);
+
+	(*event)->event_len = htons(event_len + extra_len);
+
+	return 0;
+}
+
+int ast_event_append_ie_raw_local(struct ast_event **event, enum ast_event_ie_type ie_type,
+	const void *data, size_t data_len)
+{
+	struct ast_event_ie *ie;
+	unsigned int extra_len;
+	uint16_t event_len;
+
+	event_len = ntohs((*event)->event_len);
+	extra_len = sizeof(*ie) + data_len;
+
+	if (!(*event = ast_realloc(*event, event_len + extra_len))) {
+		return -1;
+	}
+
+	ie = (struct ast_event_ie *) ( ((char *) *event) + event_len );
+	ie->ie_type = htons(ie_type);
+	ie->ie_payload_len = htons(data_len);
+	ie_maps[ie_type].copy(
+		ie->ie_payload,
+		data,
+		data_len);
 
 	(*event)->event_len = htons(event_len + extra_len);
 
@@ -1259,6 +1415,19 @@
 			insert = 1;
 			break;
 		}
+		case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+		{
+			void *data = va_arg(ap, void *);
+			size_t datalen = va_arg(ap, size_t);
+			ie_value->payload.raw = ast_alloca(datalen);
+			ie_maps[ie_type].copy(
+				ie_value->payload.raw,
+				data,
+				datalen);
+			ie_value->raw_datalen = datalen;
+			insert = 1;
+			break;
+		}
 		case AST_EVENT_IE_PLTYPE_UNKNOWN:
 		case AST_EVENT_IE_PLTYPE_EXISTS:
 			break;
@@ -1294,6 +1463,10 @@
 			ast_event_append_ie_raw(&event, ie_val->ie_type,
 					ie_val->payload.raw, ie_val->raw_datalen);
 			break;
+		case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+			ast_event_append_ie_raw_local(&event, ie_val->ie_type,
+					ie_val->payload.raw, ie_val->raw_datalen);
+			break;
 		case AST_EVENT_IE_PLTYPE_EXISTS:
 		case AST_EVENT_IE_PLTYPE_UNKNOWN:
 			break;
@@ -1322,6 +1495,21 @@
 
 void ast_event_destroy(struct ast_event *event)
 {
+	struct ast_event_iterator iterator;
+	int res;
+
+	/* take care of IE types that need the use of destructors */
+	for (res = ast_event_iterator_init(&iterator, event); !res; res = ast_event_iterator_next(&iterator)) {
+		if (ast_event_iterator_get_ie_pltype(&iterator) == AST_EVENT_IE_PLTYPE_RAW_LOCAL) {
+			enum ast_event_ie_type ie_type = ast_event_iterator_get_ie_type(&iterator);
+			void *orig = ast_event_iterator_get_ie_raw_local(&iterator);
+			size_t len = ast_event_iterator_get_ie_raw_local_payload_len(&iterator);
+
+			/* run the destructor */
+			ie_maps[ie_type].destroy(orig, len);
+		}
+	}
+
 	ast_free(event);
 }
 
@@ -1336,6 +1524,8 @@
 {
 	struct ast_event *dup_event;
 	uint16_t event_len;
+	struct ast_event_iterator iterator;
+	int res;
 
 	event_len = ast_event_get_size(event);
 
@@ -1344,6 +1534,21 @@
 	}
 
 	memcpy(dup_event, event, event_len);
+
+	/* take care of IE types that need the use of copy constructors */
+	for (res = ast_event_iterator_init(&iterator, event); !res; res = ast_event_iterator_next(&iterator)) {
+		if (ast_event_iterator_get_ie_pltype(&iterator) == AST_EVENT_IE_PLTYPE_RAW_LOCAL) {
+			enum ast_event_ie_type ie_type = ast_event_iterator_get_ie_type(&iterator);
+			void *orig = ast_event_iterator_get_ie_raw_local(&iterator);
+			size_t len = ast_event_iterator_get_ie_raw_local_payload_len(&iterator);
+			void *copy = (void *)ast_event_get_ie_raw_local(dup_event, ie_type);
+
+			/* clear this out as if it was never touched */
+			memset(copy, 0, len);
+			/* run the copy constructor */
+			ie_maps[ie_type].copy(copy, orig, len);
+		}
+	}
 
 	return dup_event;
 }
@@ -1398,6 +1603,13 @@
 			void *data = va_arg(ap, void *);
 			size_t datalen = va_arg(ap, size_t);
 			ast_event_append_ie_raw(&cache_arg_event, ie_type, data, datalen);
+			break;
+		}
+		case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+		{
+			void *data = va_arg(ap, void *);
+			size_t datalen = va_arg(ap, size_t);
+			ast_event_append_ie_raw_local(&cache_arg_event, ie_type, data, datalen);
 			break;
 		}
 		case AST_EVENT_IE_PLTYPE_EXISTS:
@@ -1638,6 +1850,22 @@
 
 /*!
  * \internal
+ * \brief Hash function for AST_EVENT_CHANNEL_STATE
+ *
+ * \param[in] obj an ast_event
+ * \param[in] flags unused
+ *
+ * \return hash value
+ */
+static int ast_event_hash_channel_state(const void *obj, const int flags)
+{
+	const struct ast_event *event = obj;
+
+	return ast_str_hash(ast_event_get_ie_str(event, AST_EVENT_IE_CEL_CHANNAME));
+}
+
+/*!
+ * \internal
  * \brief Compare two events
  *
  * \param[in] obj the first event, as an ast_event_ref
@@ -1705,6 +1933,17 @@
 	}
 }
 
+static void dump_raw_local_ie(struct ast_event_iterator *i, struct ast_cli_args *a)
+{
+	enum ast_event_ie_type ie_type;
+	const char *ie_type_name;
+
+	ie_type = ast_event_iterator_get_ie_type(i);
+	ie_type_name = ast_event_get_ie_type_name(ie_type);
+
+	ast_cli(a->fd, "%s\n", ie_type_name);
+}
+
 static int event_dump_cli(void *obj, void *arg, int flags)
 {
 	const struct ast_event_ref *event_ref = obj;
@@ -1747,6 +1986,9 @@
 			break;
 		case AST_EVENT_IE_PLTYPE_RAW:
 			dump_raw_ie(&i, a);
+			break;
+		case AST_EVENT_IE_PLTYPE_RAW_LOCAL:
+			dump_raw_local_ie(&i, a);
 			break;
 		}
 	} while (!ast_event_iterator_next(&i));
@@ -1884,3 +2126,28 @@
 {
 	return sizeof(struct ast_event);
 }
+
+int ast_event_queue_channel_state(struct ast_channel *chan)
+{
+	struct ast_channel_snapshot *snapshot = ast_channel_snapshot_create(chan);
+	struct ast_event *newchannel_event;
+	if (!snapshot) {
+		return -1;
+	}
+	newchannel_event = ast_event_new(AST_EVENT_CHANNEL_STATE,
+		AST_EVENT_IE_CEL_CHANNAME,          AST_EVENT_IE_PLTYPE_STR, ast_channel_name(chan),
+		AST_EVENT_IE_CHANNEL_STATE,         AST_EVENT_IE_PLTYPE_RAW_LOCAL, snapshot, sizeof(*snapshot),
+		AST_EVENT_IE_END);
+
+	ast_free(snapshot);
+	if (!newchannel_event) {
+		return -1;
+	}
+
+	if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE)) {
+		ast_event_queue(newchannel_event);
+	} else {
+		ast_event_queue_and_cache(newchannel_event);
+	}
+	return 0;
+}

Modified: team/dlee/stasis-http/main/manager.c
URL: http://svnview.digium.com/svn/asterisk/team/dlee/stasis-http/main/manager.c?view=diff&rev=380809&r1=380808&r2=380809
==============================================================================
--- team/dlee/stasis-http/main/manager.c (original)
+++ team/dlee/stasis-http/main/manager.c Mon Feb  4 10:37:56 2013
@@ -1031,6 +1031,8 @@
 static int authtimeout;
 static int authlimit;
 static char *manager_channelvars;
+static struct ast_event_sub *channel_state_sub; /*!< Subscription for channel state changes */
+struct ao2_container *channel_state_cache;	/*!< Holds channel state for diffs */
 
 #define DEFAULT_REALM		"asterisk"
 static char global_realm[MAXHOSTNAMELEN];	/*!< Default realm */
@@ -7343,6 +7345,192 @@
 	AST_RWLIST_UNLOCK(&channelvars);
 }
 
+static void manager_newchannel(const struct ast_channel_snapshot *snapshot)
+{
+	/*** DOCUMENTATION
+		<managerEventInstance>
+			<synopsis>Raised when a new channel is created.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newstate']/managerEventInstance/syntax/parameter[@name='ChannelState'])" />
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newstate']/managerEventInstance/syntax/parameter[@name='ChannelStateDesc'])" />
+			</syntax>
+		</managerEventInstance>
+	***/
+	manager_event(EVENT_FLAG_CALL, "Newchannel",
+		"Channel: %s\r\n"
+		"ChannelState: %d\r\n"
+		"ChannelStateDesc: %s\r\n"
+		"CallerIDNum: %s\r\n"
+		"CallerIDName: %s\r\n"
+		"AccountCode: %s\r\n"
+		"Exten: %s\r\n"
+		"Context: %s\r\n"
+		"Uniqueid: %s\r\n",
+		snapshot->name,
+		snapshot->state,
+		ast_state2str(snapshot->state),
+		snapshot->caller_number,
+		snapshot->caller_name,
+		snapshot->accountcode,
+		snapshot->exten,
+		snapshot->context,
+		snapshot->uniqueid);
+}
+
+static void manager_newstate(const struct ast_channel_snapshot *snapshot)
+{
+	/*** DOCUMENTATION
+		<managerEventInstance>
+			<synopsis>Raised when a channel's state changes.</synopsis>
+			<syntax>
+				<parameter name="ChannelState">
+					<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+				</parameter>
+				<parameter name="ChannelStateDesc">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	***/
+	manager_event(EVENT_FLAG_CALL, "Newstate",
+		"Channel: %s\r\n"
+		"ChannelState: %d\r\n"
+		"ChannelStateDesc: %s\r\n"
+		"CallerIDNum: %s\r\n"
+		"CallerIDName: %s\r\n"
+		"ConnectedLineNum: %s\r\n"
+		"ConnectedLineName: %s\r\n"
+		"Uniqueid: %s\r\n",
+		snapshot->name,
+		snapshot->state,
+		ast_state2str(snapshot->state),
+		snapshot->caller_number,
+		snapshot->caller_name,
+		snapshot->connected_number,
+		snapshot->connected_name,
+		snapshot->uniqueid);
+}
+
+static void manager_hangup(const struct ast_channel_snapshot *snapshot)
+{
+	/*** DOCUMENTATION
+		<managerEventInstance>
+			<synopsis>Raised when a channel is hung up.</synopsis>

[... 280 lines stripped ...]



More information about the svn-commits mailing list