diff options
Diffstat (limited to 'agent')
-rw-r--r-- | agent/Makefile.am | 15 | ||||
-rw-r--r-- | agent/agent.h | 15 | ||||
-rw-r--r-- | agent/inputstream.c | 314 | ||||
-rw-r--r-- | agent/inputstream.h | 87 | ||||
-rw-r--r-- | agent/iostream.c | 355 | ||||
-rw-r--r-- | agent/iostream.h | 92 | ||||
-rw-r--r-- | agent/outputstream.c | 417 | ||||
-rw-r--r-- | agent/outputstream.h | 87 |
8 files changed, 1381 insertions, 1 deletions
diff --git a/agent/Makefile.am b/agent/Makefile.am index 7975906..0506551 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -61,6 +61,12 @@ libagent_la_SOURCES = \ interfaces.h \ pseudotcp.h \ pseudotcp.c \ + iostream.h \ + iostream.c \ + inputstream.h \ + inputstream.c \ + outputstream.h \ + outputstream.c \ $(BUILT_SOURCES) libagent_la_LIBADD = \ @@ -72,7 +78,14 @@ libagent_la_DEPENDENCIES = \ $(top_builddir)/socket/libsocket.la \ $(top_builddir)/stun/libstun.la -pkginclude_HEADERS = agent.h candidate.h debug.h address.h interfaces.h pseudotcp.h +pkginclude_HEADERS = \ + agent.h \ + candidate.h \ + debug.h \ + address.h \ + interfaces.h \ + pseudotcp.h \ + $(NULL) if WINDOWS libagent_la_LIBADD += -liphlpapi -lws2_32 diff --git a/agent/agent.h b/agent/agent.h index 9d7bb25..d888148 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -57,6 +57,18 @@ * given #NiceAgent). These IDs are guaranteed to be positive (i.e. non-zero) * for valid streams/components. * + * Each stream can receive data in one of two ways: using + * nice_agent_attach_recv() or nice_agent_recv() (and the derived + * #NiceInputStream and #NiceIOStream classes accessible using + * nice_agent_build_io_stream()). nice_agent_attach_recv() is non-blocking: it + * takes a user-provided callback function and attaches the stream’s socket to + * the provided #GMainContext, invoking the callback in that context for every + * packet received. nice_agent_recv() instead blocks on receiving a packet, and + * writes it directly into a user-provided buffer. This reduces the number of + * callback invokations and (potentially) buffer copies required to receive + * packets. nice_agent_recv() (or #NiceInputStream) is designed to be used in a + * blocking loop in a separate thread. + * <example> <title>Simple example on how to use libnice</title> <programlisting> @@ -675,6 +687,9 @@ nice_agent_restart ( * Attaches the stream's component's sockets to the Glib Mainloop Context in * order to be notified whenever data becomes available for a component. * + * This must not be used in combination with nice_agent_recv() (or + * #NiceIOStream or #NiceInputStream) on the same stream/component pair. + * * Returns: %TRUE on success, %FALSE if the stream or component IDs are invalid. */ gboolean diff --git a/agent/inputstream.c b/agent/inputstream.c new file mode 100644 index 0000000..de65f66 --- /dev/null +++ b/agent/inputstream.c @@ -0,0 +1,314 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2010, 2013 Collabora Ltd. + * Contact: Youness Alaoui + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Youness Alaoui, Collabora Ltd. + * Philip Withnall, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +/** + * SECTION:nice_input_stream + * @short_description: #GInputStream implementation for libnice + * @see_also: #NiceAgent + * @include: inputstream.h + * @stability: Stable + * + * #NiceInputStream is a #GInputStream wrapper for a single reliable stream and + * component of a #NiceAgent. Given an existing reliable #NiceAgent, plus the + * IDs of an existing stream and component in the agent, it will provide a + * streaming input interface for reading from the given component. + * + * A single #NiceInputStream can only be used with a single agent, stream and + * component triple, and will be closed as soon as that stream is removed from + * the agent (e.g. if nice_agent_remove_stream() is called from another thread). + * If g_input_stream_close() is called on a #NiceInputStream, the input stream + * will be marked as closed, but the underlying #NiceAgent stream will not be + * removed. Use nice_agent_remove_stream() to do that. + * + * Since: 0.1.5 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "inputstream.h" +#include "agent-priv.h" + +static void streams_removed_cb (NiceAgent *agent, guint *stream_ids, + gpointer user_data); + +G_DEFINE_TYPE (NiceInputStream, nice_input_stream, G_TYPE_INPUT_STREAM); + +enum +{ + PROP_AGENT = 1, + PROP_STREAM_ID, + PROP_COMPONENT_ID, +}; + +struct _NiceInputStreamPrivate +{ + GWeakRef/*<NiceAgent>*/ agent_ref; + guint stream_id; + guint component_id; +}; + +static void nice_input_stream_dispose (GObject *object); +static void nice_input_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); +static void nice_input_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static gssize nice_input_stream_read (GInputStream *stream, void *buffer, + gsize count, GCancellable *cancellable, GError **error); + +static void +nice_input_stream_class_init (NiceInputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NiceInputStreamPrivate)); + + gobject_class->set_property = nice_input_stream_set_property; + gobject_class->get_property = nice_input_stream_get_property; + gobject_class->dispose = nice_input_stream_dispose; + + stream_class->read_fn = nice_input_stream_read; + + /** + * NiceInputStream:agent: + * + * The #NiceAgent to wrap with an input stream. This must be an existing + * reliable agent. + * + * A reference is not held on the #NiceAgent. If the agent is destroyed before + * the #NiceInputStream, %G_IO_ERROR_CLOSED will be returned for all + * subsequent operations on the stream. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_AGENT, + g_param_spec_object ("agent", + "NiceAgent", + "The underlying NiceAgent", + NICE_TYPE_AGENT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * NiceInputStream:stream-id: + * + * ID of the stream to use in the #NiceInputStream:agent. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_STREAM_ID, + g_param_spec_uint ( + "stream-id", + "Agent’s stream ID", + "The ID of the agent’s stream to wrap.", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * NiceInputStream:component-id: + * + * ID of the component to use in the #NiceInputStream:agent. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_COMPONENT_ID, + g_param_spec_uint ( + "component-id", + "Agent’s component ID", + "The ID of the agent’s component to wrap.", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +nice_input_stream_dispose (GObject *object) +{ + NiceInputStream *self = NICE_INPUT_STREAM (object); + NiceAgent *agent; + + agent = g_weak_ref_get (&self->priv->agent_ref); + if (agent != NULL) { + g_signal_handlers_disconnect_by_func (agent, streams_removed_cb, self); + g_object_unref (agent); + } + + g_weak_ref_clear (&self->priv->agent_ref); + + G_OBJECT_CLASS (nice_input_stream_parent_class)->dispose (object); +} + +static void +nice_input_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NiceInputStream *self = NICE_INPUT_STREAM (object); + + switch (prop_id) { + case PROP_AGENT: + g_value_take_object (value, g_weak_ref_get (&self->priv->agent_ref)); + break; + case PROP_STREAM_ID: + g_value_set_uint (value, self->priv->stream_id); + break; + case PROP_COMPONENT_ID: + g_value_set_uint (value, self->priv->component_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nice_input_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NiceInputStream *self = NICE_INPUT_STREAM (object); + + switch (prop_id) { + case PROP_AGENT: { + /* Construct only. */ + NiceAgent *agent = g_value_dup_object (value); + g_weak_ref_set (&self->priv->agent_ref, agent); + + /* agent may be NULL if the stream is being constructed by + * nice_io_stream_get_input_stream() after the NiceIOStream’s agent has + * already been finalised. */ + if (agent != NULL) { + g_signal_connect (agent, "streams-removed", + (GCallback) streams_removed_cb, self); + g_object_unref (agent); + } + + break; + } + case PROP_STREAM_ID: + /* Construct only. */ + self->priv->stream_id = g_value_get_uint (value); + break; + case PROP_COMPONENT_ID: + /* Construct only. */ + self->priv->component_id = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nice_input_stream_init (NiceInputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, NICE_TYPE_INPUT_STREAM, + NiceInputStreamPrivate); + + g_weak_ref_init (&stream->priv->agent_ref, NULL); +} + +/** + * nice_input_stream_new: + * @agent: A #NiceAgent + * @stream_id: The ID of the agent’s stream to wrap + * @component_id: The ID of the agent’s component to wrap + * + * Create a new #NiceInputStream wrapping the given stream/component from + * @agent, which must be a reliable #NiceAgent. + * + * The constructed #NiceInputStream will not hold a reference to @agent. If + * @agent is destroyed before the input stream, %G_IO_ERROR_CLOSED will be + * returned for all subsequent operations on the stream. + * + * Returns: The new #NiceInputStream object + * + * Since: 0.1.5 + */ +NiceInputStream * +nice_input_stream_new (NiceAgent *agent, guint stream_id, guint component_id) +{ + g_return_val_if_fail (NICE_IS_AGENT (agent), NULL); + g_return_val_if_fail (stream_id >= 1, NULL); + g_return_val_if_fail (component_id >= 1, NULL); + + return g_object_new (NICE_TYPE_INPUT_STREAM, + "agent", agent, + "stream-id", stream_id, + "component-id", component_id, + NULL); +} + +static gssize +nice_input_stream_read (GInputStream *stream, void *buffer, gsize count, + GCancellable *cancellable, GError **error) +{ + NiceInputStreamPrivate *priv = NICE_INPUT_STREAM (stream)->priv; + NiceAgent *agent; /* owned */ + gssize len; + + /* Closed streams are not readable. */ + if (g_input_stream_is_closed (stream)) + return 0; + + /* Has the agent disappeared? */ + agent = g_weak_ref_get (&priv->agent_ref); + if (agent == NULL) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + "Stream is closed due to the NiceAgent being finalised."); + return -1; + } + + len = nice_agent_recv (agent, priv->stream_id, priv->component_id, + buffer, count, cancellable, error); + + g_object_unref (agent); + + return len; +} + +static void +streams_removed_cb (NiceAgent *agent, guint *stream_ids, gpointer user_data) +{ + NiceInputStream *self = NICE_INPUT_STREAM (user_data); + guint i; + + for (i = 0; stream_ids[i] != 0; i++) { + if (stream_ids[i] == self->priv->stream_id) { + /* The socket has been closed. */ + g_input_stream_close (G_INPUT_STREAM (self), NULL, NULL); + break; + } + } +} diff --git a/agent/inputstream.h b/agent/inputstream.h new file mode 100644 index 0000000..e59dddf --- /dev/null +++ b/agent/inputstream.h @@ -0,0 +1,87 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2010, 2013 Collabora Ltd. + * Contact: Youness Alaoui + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Youness Alaoui, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +#ifndef __NICE_INPUT_STREAM_H__ +#define __NICE_INPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gio.h> +#include "agent.h" + +G_BEGIN_DECLS + +/* TYPE MACROS */ +#define NICE_TYPE_INPUT_STREAM \ + (nice_input_stream_get_type ()) +#define NICE_INPUT_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NICE_TYPE_INPUT_STREAM, \ + NiceInputStream)) +#define NICE_INPUT_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NICE_TYPE_INPUT_STREAM, \ + NiceInputStreamClass)) +#define NICE_IS_INPUT_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NICE_TYPE_INPUT_STREAM)) +#define NICE_IS_INPUT_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NICE_TYPE_INPUT_STREAM)) +#define NICE_INPUT_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NICE_TYPE_INPUT_STREAM, \ + NiceInputStreamClass)) + + +typedef struct _NiceInputStreamPrivate NiceInputStreamPrivate; +typedef struct _NiceInputStreamClass NiceInputStreamClass; +typedef struct _NiceInputStream NiceInputStream; + +GType nice_input_stream_get_type (void); + +struct _NiceInputStreamClass +{ + GInputStreamClass parent_class; +}; + +struct _NiceInputStream +{ + GInputStream parent_instance; + NiceInputStreamPrivate *priv; +}; + + +NiceInputStream *nice_input_stream_new (NiceAgent *agent, + guint stream_id, guint component_id); + + +G_END_DECLS + +#endif /* __NICE_INPUT_STREAM_H__ */ diff --git a/agent/iostream.c b/agent/iostream.c new file mode 100644 index 0000000..f1b6e94 --- /dev/null +++ b/agent/iostream.c @@ -0,0 +1,355 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2010, 2013 Collabora Ltd. + * Contact: Youness Alaoui + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Youness Alaoui, Collabora Ltd. + * Philip Withnall, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +/** + * SECTION:nice_io_stream + * @short_description: #GIOStream implementation for libnice + * @see_also: #NiceAgent + * @include: iostream.h + * @stability: Stable + * + * #NiceIOStream is a #GIOStream wrapper for a single reliable stream and + * component of a #NiceAgent. Given an existing reliable #NiceAgent, plus the + * IDs of an existing stream and component in the agent, it will provide a + * streaming input and output interface for communication over the given + * component. + * + * A single #NiceIOStream can only be used with a single agent, stream and + * component triple, and will be closed as soon as that stream is removed from + * the agent (e.g. if nice_agent_remove_stream() is called from another thread). + * If g_io_stream_close() is called on a #NiceIOStream, the I/O stream will be + * marked as closed in both directions, but the underlying #NiceAgent stream + * will not be removed. Use nice_agent_remove_stream() to do that. + * + * Since: 0.1.5 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "iostream.h" +#include "inputstream.h" +#include "outputstream.h" + +G_DEFINE_TYPE (NiceIOStream, nice_io_stream, G_TYPE_IO_STREAM); + +enum +{ + PROP_AGENT = 1, + PROP_STREAM_ID, + PROP_COMPONENT_ID, +}; + +struct _NiceIOStreamPrivate +{ + GWeakRef/*<NiceAgent>*/ agent_ref; + guint stream_id; + guint component_id; + + GInputStream *input_stream; /* owned */ + GOutputStream *output_stream; /* owned */ +}; + +static void nice_io_stream_dispose (GObject *object); +static void nice_io_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); +static void nice_io_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static GInputStream *nice_io_stream_get_input_stream (GIOStream *stream); +static GOutputStream *nice_io_stream_get_output_stream (GIOStream *stream); + +static void streams_removed_cb (NiceAgent *agent, guint *stream_ids, + gpointer user_data); + +static void +nice_io_stream_class_init (NiceIOStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GIOStreamClass *stream_class = G_IO_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NiceIOStreamPrivate)); + + gobject_class->set_property = nice_io_stream_set_property; + gobject_class->get_property = nice_io_stream_get_property; + gobject_class->dispose = nice_io_stream_dispose; + + stream_class->get_input_stream = nice_io_stream_get_input_stream; + stream_class->get_output_stream = nice_io_stream_get_output_stream; + + /** + * NiceIOStream:agent: + * + * The #NiceAgent to wrap with an I/O stream. This must be an existing + * reliable agent. + * + * A reference is not held on the #NiceAgent. If the agent is destroyed before + * the #NiceIOStream, %G_IO_ERROR_CLOSED will be returned for all subsequent + * operations on the stream. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_AGENT, + g_param_spec_object ("agent", + "NiceAgent", + "The underlying NiceAgent", + NICE_TYPE_AGENT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * NiceIOStream:stream-id: + * + * ID of the stream to use in the #NiceIOStream:agent. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_STREAM_ID, + g_param_spec_uint ( + "stream-id", + "Agent’s stream ID", + "The ID of the agent’s stream to wrap.", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * NiceIOStream:component-id: + * + * ID of the component to use in the #NiceIOStream:agent. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_COMPONENT_ID, + g_param_spec_uint ( + "component-id", + "Agent’s component ID", + "The ID of the agent’s component to wrap.", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +nice_io_stream_init (NiceIOStream *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NICE_TYPE_IO_STREAM, + NiceIOStreamPrivate); + + g_weak_ref_init (&self->priv->agent_ref, NULL); + + /* Invalidate the stream/component IDs to begin with. */ + self->priv->stream_id = 0; + self->priv->component_id = 0; +} + +static void +nice_io_stream_dispose (GObject *object) +{ + NiceIOStream *self = NICE_IO_STREAM (object); + NiceAgent *agent; + + /* Ensure the stream is closed before continuing. Otherwise, if the input or + * output streams haven’t yet been lazily created, closing the stream in + * g_io_stream_dispose() will lazily create them, but NiceAgent will be NULL + * by that point and things will explode. */ + if (!g_io_stream_is_closed (G_IO_STREAM (object))) + g_io_stream_close (G_IO_STREAM (object), NULL, NULL); + + /* Clear everything away. */ + if (self->priv->input_stream != NULL) + g_object_unref (self->priv->input_stream); + self->priv->input_stream = NULL; + + if (self->priv->output_stream != NULL) + g_object_unref (self->priv->output_stream); + self->priv->output_stream = NULL; + + agent = g_weak_ref_get (&self->priv->agent_ref); + if (agent != NULL) { + g_signal_handlers_disconnect_by_func (agent, streams_removed_cb, self); + g_object_unref (agent); + } + + g_weak_ref_clear (&self->priv->agent_ref); + + G_OBJECT_CLASS (nice_io_stream_parent_class)->dispose (object); +} + +static void +nice_io_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NiceIOStream *self = NICE_IO_STREAM (object); + + switch (prop_id) { + case PROP_AGENT: + g_value_take_object (value, g_weak_ref_get (&self->priv->agent_ref)); + break; + case PROP_STREAM_ID: + g_value_set_uint (value, self->priv->stream_id); + break; + case PROP_COMPONENT_ID: + g_value_set_uint (value, self->priv->component_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nice_io_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NiceIOStream *self = NICE_IO_STREAM (object); + + switch (prop_id) { + case PROP_AGENT: { + /* Construct only. */ + NiceAgent *agent = g_value_dup_object (value); + g_weak_ref_set (&self->priv->agent_ref, agent); + g_signal_connect (agent, "streams-removed", + (GCallback) streams_removed_cb, self); + g_object_unref (agent); + + break; + } + case PROP_STREAM_ID: + /* Construct only. */ + self->priv->stream_id = g_value_get_uint (value); + break; + case PROP_COMPONENT_ID: + /* Construct only. */ + self->priv->component_id = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +/** + * nice_io_stream_new: + * @agent: A #NiceAgent + * @stream_id: The ID of the agent’s stream to wrap + * @component_id: The ID of the agent’s component to wrap + * + * Create a new #NiceIOStream wrapping the given stream/component from @agent, + * which must be a reliable #NiceAgent. + * + * The constructed #NiceIOStream will not hold a reference to @agent. If @agent + * is destroyed before the I/O stream, %G_IO_ERROR_CLOSED will be returned for + * all subsequent operations on the stream. + * + * Returns: The new #NiceIOStream object + * + * Since: 0.1.5 + */ +GIOStream * +nice_io_stream_new (NiceAgent *agent, guint stream_id, guint component_id) +{ + gboolean reliable_agent; + + g_return_val_if_fail (NICE_IS_AGENT (agent), NULL); + g_return_val_if_fail (stream_id > 0, NULL); + g_return_val_if_fail (component_id > 0, NULL); + + g_object_get (agent, "reliable", &reliable_agent, NULL); + g_return_val_if_fail (reliable_agent, NULL); + + return g_object_new (NICE_TYPE_IO_STREAM, + "agent", agent, + "stream-id", stream_id, + "component-id", component_id, + NULL); +} + +static GInputStream * +nice_io_stream_get_input_stream (GIOStream *stream) +{ + NiceIOStream *self = NICE_IO_STREAM (stream); + + if (G_UNLIKELY (self->priv->input_stream == NULL)) { + NiceAgent *agent; + + /* Note that agent may be NULL here. NiceInputStream must support + * construction with a NULL agent. */ + agent = g_weak_ref_get (&self->priv->agent_ref); + self->priv->input_stream = G_INPUT_STREAM (nice_input_stream_new ( + agent, self->priv->stream_id, self->priv->component_id)); + if (agent != NULL) + g_object_unref (agent); + } + + return self->priv->input_stream; +} + +static GOutputStream * +nice_io_stream_get_output_stream (GIOStream *stream) +{ + NiceIOStream *self = NICE_IO_STREAM (stream); + + if (G_UNLIKELY (self->priv->output_stream == NULL)) { + NiceAgent *agent; + + /* Note that agent may be NULL here. NiceOutputStream must support + * construction with a NULL agent. */ + agent = g_weak_ref_get (&self->priv->agent_ref); + self->priv->output_stream = g_object_new (NICE_TYPE_OUTPUT_STREAM, + "agent", agent, + "stream-id", self->priv->stream_id, + "component-id", self->priv->component_id, + NULL); + + if (agent != NULL) + g_object_unref (agent); + } + + return self->priv->output_stream; +} + +static void +streams_removed_cb (NiceAgent *agent, guint *stream_ids, gpointer user_data) +{ + NiceIOStream *self = NICE_IO_STREAM (user_data); + guint i; + + for (i = 0; stream_ids[i] != 0; i++) { + if (stream_ids[i] == self->priv->stream_id) { + /* The socket has been closed. */ + g_io_stream_close (G_IO_STREAM (self), NULL, NULL); + break; + } + } +} diff --git a/agent/iostream.h b/agent/iostream.h new file mode 100644 index 0000000..88f42c0 --- /dev/null +++ b/agent/iostream.h @@ -0,0 +1,92 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2010, 2013 Collabora Ltd. + * Contact: Youness Alaoui + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Youness Alaoui, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +#ifndef __NICE_IO_STREAM_H__ +#define __NICE_IO_STREAM_H__ + +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +/* TYPE MACROS */ + +/* IO Stream */ +#define NICE_TYPE_IO_STREAM \ + (nice_io_stream_get_type ()) +#define NICE_IO_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NICE_TYPE_IO_STREAM, \ + NiceIOStream)) +#define NICE_IO_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NICE_TYPE_IO_STREAM, \ + NiceIOStreamClass)) +#define NICE_IS_IO_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NICE_TYPE_IO_STREAM)) +#define NICE_IS_IO_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NICE_TYPE_IO_STREAM)) +#define NICE_IO_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NICE_TYPE_IO_STREAM, \ + NiceIOStreamClass)) + +/* IO Stream */ +typedef struct _NiceIOStreamPrivate NiceIOStreamPrivate; +typedef struct _NiceIOStreamClass NiceIOStreamClass; +typedef struct _NiceIOStream NiceIOStream; + +#include "agent.h" +#include "inputstream.h" +#include "outputstream.h" + +/* IO Stream */ +GType nice_io_stream_get_type (void); + +struct _NiceIOStreamClass +{ + GIOStreamClass parent_class; + +}; + +struct _NiceIOStream +{ + GIOStream parent_instance; + NiceIOStreamPrivate *priv; +}; + +GIOStream *nice_io_stream_new (NiceAgent *agent, + guint stream_id, guint component_id); + +G_END_DECLS + +#endif /* __NICE_IO_STREAM_H__ */ diff --git a/agent/outputstream.c b/agent/outputstream.c new file mode 100644 index 0000000..85b29e2 --- /dev/null +++ b/agent/outputstream.c @@ -0,0 +1,417 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2010, 2013 Collabora Ltd. + * Contact: Youness Alaoui + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Youness Alaoui, Collabora Ltd. + * Philip Withnall, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +/** + * SECTION:nice_output_stream + * @short_description: #GOutputStream implementation for libnice + * @see_also: #NiceAgent + * @include: outputstream.h + * @stability: Stable + * + * #NiceOutputStream is a #GOutputStream wrapper for a single reliable stream + * and component of a #NiceAgent. Given an existing reliable #NiceAgent, plus + * the IDs of an existing stream and component in the agent, it will provide a + * streaming output interface for writing to the given component. + * + * A single #NiceOutputStream can only be used with a single agent, stream and + * component triple, and will be closed as soon as that stream is removed from + * the agent (e.g. if nice_agent_remove_stream() is called from another thread). + * If g_output_stream_close() is called on a #NiceOutputStream, the output + * stream will be marked as closed, but the underlying #NiceAgent stream will + * not be removed. Use nice_agent_remove_stream() to do that. + * + * The output stream can only be used once the + * #NiceAgent::reliable-transport-writable signal has been received for the + * stream/component pair. Any calls to g_output_stream_write() before then will + * return %G_IO_ERROR_BROKEN_PIPE. + * + * Since: 0.1.5 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "outputstream.h" + +static void streams_removed_cb (NiceAgent *agent, guint *stream_ids, + gpointer user_data); + +G_DEFINE_TYPE (NiceOutputStream, + nice_output_stream, G_TYPE_OUTPUT_STREAM); + +enum +{ + PROP_AGENT = 1, + PROP_STREAM_ID, + PROP_COMPONENT_ID, +}; + +struct _NiceOutputStreamPrivate +{ + GWeakRef/*<NiceAgent>*/ agent_ref; + guint stream_id; + guint component_id; +}; + +static void nice_output_stream_dispose (GObject *object); +static void nice_output_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); +static void nice_output_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); + +static gssize nice_output_stream_write (GOutputStream *stream, + const void *buffer, gsize count, GCancellable *cancellable, GError **error); + + + +/* Output Stream */ +static void +nice_output_stream_class_init (NiceOutputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NiceOutputStreamPrivate)); + + stream_class->write_fn = nice_output_stream_write; + + gobject_class->set_property = nice_output_stream_set_property; + gobject_class->get_property = nice_output_stream_get_property; + gobject_class->dispose = nice_output_stream_dispose; + + /** + * NiceOutputStream:agent: + * + * The #NiceAgent to wrap with an output stream. This must be an existing + * reliable agent. + * + * A reference is not held on the #NiceAgent. If the agent is destroyed before + * the #NiceOutputStream, %G_IO_ERROR_CLOSED will be returned for all + * subsequent operations on the stream. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_AGENT, + g_param_spec_object ("agent", + "NiceAgent", + "The underlying NiceAgent", + NICE_TYPE_AGENT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * NiceOutputStream:stream-id: + * + * ID of the stream to use in the #NiceOutputStream:agent. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_STREAM_ID, + g_param_spec_uint ( + "stream-id", + "Agent’s stream ID", + "The ID of the agent’s stream to wrap.", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * NiceOutputStream:component-id: + * + * ID of the component to use in the #NiceOutputStream:agent. + * + * Since: 0.1.5 + */ + g_object_class_install_property (gobject_class, PROP_COMPONENT_ID, + g_param_spec_uint ( + "component-id", + "Agent’s component ID", + "The ID of the agent’s component to wrap.", + 0, G_MAXUINT, + 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +nice_output_stream_dispose (GObject *object) +{ + NiceOutputStream *self = NICE_OUTPUT_STREAM (object); + NiceAgent *agent; + + agent = g_weak_ref_get (&self->priv->agent_ref); + if (agent != NULL) { + g_signal_handlers_disconnect_by_func (agent, streams_removed_cb, self); + g_object_unref (agent); + } + + g_weak_ref_clear (&self->priv->agent_ref); + + G_OBJECT_CLASS (nice_output_stream_parent_class)->dispose (object); +} + +static void +nice_output_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NiceOutputStream *self = NICE_OUTPUT_STREAM (object); + + switch (prop_id) { + case PROP_AGENT: + g_value_take_object (value, g_weak_ref_get (&self->priv->agent_ref)); + break; + case PROP_STREAM_ID: + g_value_set_uint (value, self->priv->stream_id); + break; + case PROP_COMPONENT_ID: + g_value_set_uint (value, self->priv->component_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nice_output_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NiceOutputStream *self = NICE_OUTPUT_STREAM (object); + + switch (prop_id) { + case PROP_AGENT: { + /* Construct only. */ + NiceAgent *agent = g_value_dup_object (value); + g_weak_ref_set (&self->priv->agent_ref, agent); + + /* agent may be NULL if the stream is being constructed by + * nice_io_stream_get_output_stream() after the NiceIOStream’s agent has + * already been finalised. */ + if (agent != NULL) { + g_signal_connect (agent, "streams-removed", + (GCallback) streams_removed_cb, self); + g_object_unref (agent); + } + + break; + } + case PROP_STREAM_ID: + /* Construct only. */ + self->priv->stream_id = g_value_get_uint (value); + break; + case PROP_COMPONENT_ID: + /* Construct only. */ + self->priv->component_id = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nice_output_stream_init (NiceOutputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, NICE_TYPE_OUTPUT_STREAM, + NiceOutputStreamPrivate); + + g_weak_ref_init (&stream->priv->agent_ref, NULL); +} + +/** + * nice_output_stream_new: + * @agent: A #NiceAgent + * @stream_id: The ID of the agent’s stream to wrap + * @component_id: The ID of the agent’s component to wrap + * + * Create a new #NiceOutputStream wrapping the given stream/component from + * @agent, which must be a reliable #NiceAgent. + * + * The constructed #NiceOutputStream will not hold a reference to @agent. If + * @agent is destroyed before the output stream, %G_IO_ERROR_CLOSED will be + * returned for all subsequent operations on the stream. + * + * Returns: The new #NiceOutputStream object + * + * Since: 0.1.5 + */ +NiceOutputStream * +nice_output_stream_new (NiceAgent *agent, guint stream_id, guint component_id) +{ + g_return_val_if_fail (NICE_IS_AGENT (agent), NULL); + g_return_val_if_fail (stream_id >= 1, NULL); + g_return_val_if_fail (component_id >= 1, NULL); + + return g_object_new (NICE_TYPE_OUTPUT_STREAM, + "agent", agent, + "stream-id", stream_id, + "component-id", component_id, + NULL); +} + +typedef struct { + GCond cond; + GMutex mutex; + GError **error; +} WriteData; + +static void +write_cancelled_cb (GCancellable *cancellable, gpointer user_data) +{ + WriteData *write_data = user_data; + + g_mutex_lock (&write_data->mutex); + g_cond_broadcast (&write_data->cond); + g_mutex_unlock (&write_data->mutex); + + g_cancellable_set_error_if_cancelled (cancellable, write_data->error); +} + +static void +reliable_transport_writeable_cb (NiceAgent *agent, guint stream_id, + guint component_id, gpointer user_data) +{ + WriteData *write_data = user_data; + + g_mutex_lock (&write_data->mutex); + g_cond_broadcast (&write_data->cond); + g_mutex_unlock (&write_data->mutex); +} + +static gssize +nice_output_stream_write (GOutputStream *stream, const void *buffer, gsize count, + GCancellable *cancellable, GError **error) +{ + NiceOutputStream *self = NICE_OUTPUT_STREAM (stream); + gssize len = -1, _len; + GError *child_error = NULL; + NiceAgent *agent = NULL; /* owned */ + gulong cancel_id = 0, writeable_id; + WriteData write_data; + + /* Closed streams are not writeable. */ + if (g_output_stream_is_closed (stream)) { + g_set_error_literal (&child_error, G_IO_ERROR, G_IO_ERROR_CLOSED, + "Stream is closed."); + goto done; + } + + /* Has the agent disappeared? */ + agent = g_weak_ref_get (&self->priv->agent_ref); + if (agent == NULL) { + g_set_error_literal (&child_error, G_IO_ERROR, G_IO_ERROR_CLOSED, + "Stream is closed due to the NiceAgent being finalised."); + goto done; + } + + if (count == 0) + return 0; + + /* FIXME: nice_agent_send_full() is non-blocking, which is a bit unexpected + * since nice_agent_recv() is blocking. Currently this uses a fairly dodgy + * GCond solution; would be much better for nice_agent_send() to block + * properly in the main loop. */ + len = 0; + write_data.error = &child_error; + + g_mutex_init (&write_data.mutex); + g_cond_init (&write_data.cond); + + if (cancellable != NULL) { + cancel_id = g_cancellable_connect (cancellable, + (GCallback) write_cancelled_cb, &write_data, NULL); + } + + writeable_id = g_signal_connect (G_OBJECT (agent), + "reliable-transport-writable", + (GCallback) reliable_transport_writeable_cb, &write_data); + + g_mutex_lock (&write_data.mutex); + + do { + _len = nice_agent_send_full (agent, self->priv->stream_id, + self->priv->component_id, (guint8 *) buffer + len, count - len, + cancellable, &child_error); + + if (_len == -1 && + g_error_matches (child_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + /* EWOULDBLOCK. */ + g_clear_error (&child_error); + g_cond_wait (&write_data.cond, &write_data.mutex); + } else if (_len > 0) { + /* Success. */ + len += _len; + } else { + /* Other error. */ + len = _len; + break; + } + } while ((gsize) len < count); + + g_mutex_unlock (&write_data.mutex); + + g_signal_handler_disconnect (G_OBJECT (agent), writeable_id); + + if (cancellable != NULL) + g_cancellable_disconnect (cancellable, cancel_id); + + g_cond_clear (&write_data.cond); + g_mutex_clear (&write_data.mutex); + +done: + if (agent != NULL) + g_object_unref (agent); + + g_assert ((child_error != NULL) == (len == -1)); + g_assert (len != 0); + + if (child_error != NULL) + g_propagate_error (error, child_error); + + return len; +} + +static void +streams_removed_cb (NiceAgent *agent, guint *stream_ids, gpointer user_data) +{ + NiceOutputStream *self = NICE_OUTPUT_STREAM (user_data); + guint i; + + for (i = 0; stream_ids[i] != 0; i++) { + if (stream_ids[i] == self->priv->stream_id) { + /* The socket has been closed. */ + g_output_stream_close (G_OUTPUT_STREAM (self), NULL, NULL); + break; + } + } +} diff --git a/agent/outputstream.h b/agent/outputstream.h new file mode 100644 index 0000000..eefdb3c --- /dev/null +++ b/agent/outputstream.h @@ -0,0 +1,87 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2010, 2013 Collabora Ltd. + * Contact: Youness Alaoui + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Youness Alaoui, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +#ifndef __NICE_OUTPUT_STREAM_H__ +#define __NICE_OUTPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gio.h> +#include "agent.h" + +G_BEGIN_DECLS + +/* TYPE MACROS */ +#define NICE_TYPE_OUTPUT_STREAM \ + (nice_output_stream_get_type ()) +#define NICE_OUTPUT_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NICE_TYPE_OUTPUT_STREAM, \ + NiceOutputStream)) +#define NICE_OUTPUT_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NICE_TYPE_OUTPUT_STREAM, \ + NiceOutputStreamClass)) +#define NICE_IS_OUTPUT_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NICE_TYPE_OUTPUT_STREAM)) +#define NICE_IS_OUTPUT_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NICE_TYPE_OUTPUT_STREAM)) +#define NICE_OUTPUT_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NICE_TYPE_OUTPUT_STREAM, \ + NiceOutputStreamClass)) + + +typedef struct _NiceOutputStreamPrivate NiceOutputStreamPrivate; +typedef struct _NiceOutputStreamClass NiceOutputStreamClass; +typedef struct _NiceOutputStream NiceOutputStream; + + +GType nice_output_stream_get_type (void); + +struct _NiceOutputStreamClass +{ + GOutputStreamClass parent_class; +}; + +struct _NiceOutputStream +{ + GOutputStream parent_instance; + NiceOutputStreamPrivate *priv; +}; + + +NiceOutputStream *nice_output_stream_new (NiceAgent *agent, + guint stream_id, guint component_id); + +G_END_DECLS + +#endif /* __NICE_OUTPUT_STREAM_H__ */ |