<commit_before>/*
 * gibber-bytestream-ibb.c - Source for GibberBytestreamIBB
 * Copyright (C) 2007-2008 Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "gibber-bytestream-ibb.h"

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <glib.h>
#include <wocky/wocky-namespaces.h>

#include "gibber-xmpp-connection.h"
#include "gibber-muc-connection.h"
#include <wocky/wocky-stanza.h>
#include "gibber-namespaces.h"
#include "gibber-xmpp-error.h"

#define DEBUG_FLAG DEBUG_BYTESTREAM
#include "gibber-debug.h"

#include "gibber-signals-marshal.h"

/* IMPORTANT NOTE: This bytestream is not used anymore by Salut and so probably
 * a bit rotten */

/* FIXME: implement fragmentation using the "block-size" attribute */

static void
bytestream_iface_init (gpointer g_iface, gpointer iface_data);

G_DEFINE_TYPE_WITH_CODE (GibberBytestreamIBB, gibber_bytestream_ibb,
    G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (GIBBER_TYPE_BYTESTREAM_IFACE,
      bytestream_iface_init));

/* properties */
enum
{
  PROP_XMPP_CONNECTION = 1,
  PROP_SELF_ID,
  PROP_PEER_ID,
  PROP_STREAM_ID,
  PROP_STREAM_INIT_ID,
  PROP_STATE,
  PROP_PROTOCOL,
  LAST_PROPERTY
};

typedef struct _GibberBytestreamIBBPrivate GibberBytestreamIBBPrivate;
struct _GibberBytestreamIBBPrivate
{
  GibberXmppConnection *xmpp_connection;
  gchar *self_id;
  gchar *peer_id;
  gchar *stream_id;
  gchar *stream_init_id;
  GibberBytestreamState state;

  guint16 seq;
  guint16 last_seq_recv;

  gboolean dispose_has_run;
};

#define GIBBER_BYTESTREAM_IBB_GET_PRIVATE(obj) \
    ((GibberBytestreamIBBPrivate *) obj->priv)

static void
gibber_bytestream_ibb_init (GibberBytestreamIBB *self)
{
  GibberBytestreamIBBPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      GIBBER_TYPE_BYTESTREAM_IBB, GibberBytestreamIBBPrivate);

  self->priv = priv;
}

static void
xmpp_connection_received_stanza_cb (GibberXmppConnection *conn,
                                    WockyStanza *stanza,
                                    gpointer user_data)
{
  GibberBytestreamIBB *self = (GibberBytestreamIBB *) user_data;
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyNode *node = wocky_stanza_get_top_node (stanza);
  WockyNode *data;
  GString *str;
  guchar *decoded;
  gsize len;
  const gchar *from, *stream_id;

  data = wocky_node_get_child_ns (node, "data", WOCKY_XMPP_NS_IBB);
  if (data == NULL)
    {
      return;
    }

  stream_id = wocky_node_get_attribute (data, "sid");
  if (stream_id == NULL || strcmp (stream_id, priv->stream_id) != 0)
    {
      DEBUG ("bad stream id");
      return;
    }

  if (priv->state != GIBBER_BYTESTREAM_STATE_OPEN)
    {
      DEBUG ("can't receive data through a not open bytestream (state: %d)",
          priv->state);
      return;
    }

  from = wocky_node_get_attribute (node, "from");
  if (from == NULL)
    {
      DEBUG ("got a message without a from field, ignoring");
      return;
    }

  // XXX check sequence number ?

  decoded = g_base64_decode (data->content, &len);
  str = g_string_new_len ((const gchar *) decoded, len);
  g_signal_emit_by_name (G_OBJECT (self), "data-received", from, str);

  g_string_free (str, TRUE);
  g_free (decoded);
}

static void
gibber_bytestream_ibb_dispose (GObject *object)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  if (priv->dispose_has_run)
    return;
  priv->dispose_has_run = TRUE;

  if (priv->state != GIBBER_BYTESTREAM_STATE_CLOSED)
    {
      gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
    }

  G_OBJECT_CLASS (gibber_bytestream_ibb_parent_class)->dispose (object);
}

static void
gibber_bytestream_ibb_finalize (GObject *object)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  g_free (priv->stream_id);
  g_free (priv->stream_init_id);
  g_free (priv->self_id);
  g_free (priv->peer_id);

  G_OBJECT_CLASS (gibber_bytestream_ibb_parent_class)->finalize (object);
}

static void
gibber_bytestream_ibb_get_property (GObject *object,
                                    guint property_id,
                                    GValue *value,
                                    GParamSpec *pspec)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  switch (property_id)
    {
      case PROP_XMPP_CONNECTION:
        g_value_set_object (value, priv->xmpp_connection);
        break;
      case PROP_SELF_ID:
        g_value_set_string (value, priv->self_id);
        break;
      case PROP_PEER_ID:
        g_value_set_string (value, priv->peer_id);
        break;
      case PROP_STREAM_ID:
        g_value_set_string (value, priv->stream_id);
        break;
      case PROP_STREAM_INIT_ID:
        g_value_set_string (value, priv->stream_init_id);
        break;
      case PROP_STATE:
        g_value_set_uint (value, priv->state);
        break;
      case PROP_PROTOCOL:
        g_value_set_string (value, GIBBER_XMPP_NS_IBB);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
gibber_bytestream_ibb_set_property (GObject *object,
                                    guint property_id,
                                    const GValue *value,
                                    GParamSpec *pspec)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  switch (property_id)
    {
      case PROP_XMPP_CONNECTION:
        priv->xmpp_connection = g_value_get_object (value);
        if (priv->xmpp_connection != NULL)
          g_signal_connect (priv->xmpp_connection, "received-stanza",
              G_CALLBACK (xmpp_connection_received_stanza_cb), self);
        break;
      case PROP_SELF_ID:
        g_free (priv->self_id);
        priv->self_id = g_value_dup_string (value);
        break;
      case PROP_PEER_ID:
        g_free (priv->peer_id);
        priv->peer_id = g_value_dup_string (value);
        break;
      case PROP_STREAM_ID:
        g_free (priv->stream_id);
        priv->stream_id = g_value_dup_string (value);
        break;
      case PROP_STREAM_INIT_ID:
        g_free (priv->stream_init_id);
        priv->stream_init_id = g_value_dup_string (value);
        break;
      case PROP_STATE:
        if (priv->state != g_value_get_uint (value))
            {
              priv->state = g_value_get_uint (value);
              g_signal_emit_by_name (object, "state-changed", priv->state);
            }
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static GObject *
gibber_bytestream_ibb_constructor (GType type,
                                   guint n_props,
                                   GObjectConstructParam *props)
{
  GObject *obj;
  GibberBytestreamIBBPrivate *priv;

  obj = G_OBJECT_CLASS (gibber_bytestream_ibb_parent_class)->
           constructor (type, n_props, props);

  priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (GIBBER_BYTESTREAM_IBB (obj));

  g_assert (priv->xmpp_connection != NULL);
  g_assert (priv->stream_init_id != NULL);
  g_assert (priv->self_id != NULL);
  g_assert (priv->peer_id != NULL);

  return obj;
}

static void
gibber_bytestream_ibb_class_init (
    GibberBytestreamIBBClass *gibber_bytestream_ibb_class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (gibber_bytestream_ibb_class);
  GParamSpec *param_spec;

  g_type_class_add_private (gibber_bytestream_ibb_class,
      sizeof (GibberBytestreamIBBPrivate));

  object_class->dispose = gibber_bytestream_ibb_dispose;
  object_class->finalize = gibber_bytestream_ibb_finalize;

  object_class->get_property = gibber_bytestream_ibb_get_property;
  object_class->set_property = gibber_bytestream_ibb_set_property;
  object_class->constructor = gibber_bytestream_ibb_constructor;

  g_object_class_override_property (object_class, PROP_SELF_ID,
      "self-id");
  g_object_class_override_property (object_class, PROP_PEER_ID,
      "peer-id");
  g_object_class_override_property (object_class, PROP_STREAM_ID,
      "stream-id");
  g_object_class_override_property (object_class, PROP_STATE,
      "state");
  g_object_class_override_property (object_class, PROP_PROTOCOL,
      "protocol");

  param_spec = g_param_spec_object (
      "xmpp-connection",
      "GibberXmppConnection object",
      "Gibber XMPP connection object used for communication by this "
      "bytestream if it's a private one",
      GIBBER_TYPE_XMPP_CONNECTION,
      G_PARAM_CONSTRUCT_ONLY |
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_XMPP_CONNECTION,
      param_spec);

  param_spec = g_param_spec_string (
      "stream-init-id",
      "stream init ID",
      "the iq ID of the SI request, if any",
      "",
      G_PARAM_CONSTRUCT_ONLY |
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_STREAM_INIT_ID,
      param_spec);
}

/*
 * gibber_bytestream_ibb_send
 *
 * Implements gibber_bytestream_iface_send on GibberBytestreamIface
 */
static gboolean
gibber_bytestream_ibb_send (GibberBytestreamIface *bytestream,
                            guint len,
                            const gchar *str)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *stanza;
  gchar *seq, *encoded;
  gboolean ret;

  if (priv->state != GIBBER_BYTESTREAM_STATE_OPEN)
    {
      DEBUG ("can't send data through a not open bytestream (state: %d)",
          priv->state);
      return FALSE;
    }

  seq = g_strdup_printf ("%u", priv->seq++);

  encoded = g_base64_encode ((const guchar *) str, len);

  stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
      WOCKY_STANZA_SUB_TYPE_NONE,
      priv->self_id, priv->peer_id,
      WOCKY_NODE_START, "data",
        WOCKY_NODE_XMLNS, GIBBER_XMPP_NS_IBB,
        WOCKY_NODE_ATTRIBUTE, "sid", priv->stream_id,
        WOCKY_NODE_ATTRIBUTE, "seq", seq,
        WOCKY_NODE_TEXT, encoded,
      WOCKY_NODE_END,
      WOCKY_NODE_START, "amp",
        WOCKY_NODE_XMLNS, GIBBER_XMPP_NS_AMP,
        WOCKY_NODE_START, "rule",
          WOCKY_NODE_ATTRIBUTE, "condition", "deliver-at",
          WOCKY_NODE_ATTRIBUTE, "value", "stored",
          WOCKY_NODE_ATTRIBUTE, "action", "error",
        WOCKY_NODE_END,
        WOCKY_NODE_START, "rule",
          WOCKY_NODE_ATTRIBUTE, "condition", "match-resource",
          WOCKY_NODE_ATTRIBUTE, "value", "exact",
          WOCKY_NODE_ATTRIBUTE, "action", "error",
        WOCKY_NODE_END,
      WOCKY_NODE_END,
      NULL);

  DEBUG ("send %d bytes", len);
  ret = gibber_xmpp_connection_send (priv->xmpp_connection, stanza, NULL);

  g_object_unref (stanza);
  g_free (encoded);
  g_free (seq);

  return ret;
}

static WockyStanza *
create_si_accept_iq (GibberBytestreamIBB *self)
{
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  return wocky_stanza_build (
      WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT,
      priv->self_id, priv->peer_id,
      WOCKY_NODE_ATTRIBUTE, "id", priv->stream_init_id,
      WOCKY_NODE_START, "si",
        WOCKY_NODE_XMLNS, GIBBER_XMPP_NS_SI,
        WOCKY_NODE_START, "feature",
          WOCKY_NODE_XMLNS, GIBBER_XMPP_NS_FEATURENEG,
          WOCKY_NODE_START, "x",
            WOCKY_NODE_XMLNS, GIBBER_XMPP_NS_DATA,
            WOCKY_NODE_ATTRIBUTE, "type", "submit",
            WOCKY_NODE_START, "field",
              WOCKY_NODE_ATTRIBUTE, "var", "stream-method",
              WOCKY_NODE_START, "value",
                WOCKY_NODE_TEXT, GIBBER_XMPP_NS_IBB,
              WOCKY_NODE_END,
            WOCKY_NODE_END,
          WOCKY_NODE_END,
        WOCKY_NODE_END,
      WOCKY_NODE_END, NULL);
}

/*
 * gibber_bytestream_ibb_accept
 *
 * Implements gibber_bytestream_iface_accept on GibberBytestreamIface
 */
static void
gibber_bytestream_ibb_accept (GibberBytestreamIface *bytestream,
                              GibberBytestreamAugmentSiAcceptReply func,
                              gpointer user_data)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *stanza;
  WockyNode *node;
  WockyNode *si;

  if (priv->state != GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
    {
      /* The stream was previoulsy or automatically accepted */
      DEBUG ("stream was already accepted");
      return;
    }

  stanza = create_si_accept_iq (self);
  node = wocky_stanza_get_top_node (stanza);
  si = wocky_node_get_child_ns (node, "si", GIBBER_XMPP_NS_SI);
  g_assert (si != NULL);

  if (func != NULL)
    {
      /* let the caller add his profile specific data */
      func (si, user_data);
    }

  gibber_xmpp_connection_send (priv->xmpp_connection, stanza, NULL);

  DEBUG ("stream is now accepted");
  g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_ACCEPTED, NULL);
}

static void
gibber_bytestream_ibb_decline (GibberBytestreamIBB *self,
                               GError *error)
{
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *stanza;
  WockyNode *node;

  g_return_if_fail (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING);

  stanza = wocky_stanza_build (
      WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_ERROR,
      priv->self_id, priv->peer_id,
      WOCKY_NODE_ATTRIBUTE, "id", priv->stream_init_id,
      NULL);
  node = wocky_stanza_get_top_node (stanza);

  if (error != NULL && error->domain == GIBBER_XMPP_ERROR)
    {
      gibber_xmpp_error_to_node (error->code, node, error->message);
    }
  else
    {
      gibber_xmpp_error_to_node (XMPP_ERROR_FORBIDDEN, node,
          "Offer Declined");
    }

  gibber_xmpp_connection_send (priv->xmpp_connection, stanza, NULL);

  g_object_unref (stanza);
}

/*
 * gibber_bytestream_ibb_close
 *
 * Implements gibber_bytestream_iface_close on GibberBytestreamIface
 */
static void
gibber_bytestream_ibb_close (GibberBytestreamIface *bytestream,
                             GError *error)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED)
     /* bytestream already closed, do nothing */
     return;

  g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSING, NULL);

  if (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
    {
      /* Stream was created using SI so we decline the request */
      gibber_bytestream_ibb_decline (self, error);
    }

  else if (priv->xmpp_connection != NULL)
    {
      WockyStanza *stanza;

      DEBUG ("send IBB close stanza");

      stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ,
          WOCKY_STANZA_SUB_TYPE_SET,
          priv->self_id, priv->peer_id,
          WOCKY_NODE_START, "close",
            WOCKY_NODE_XMLNS, GIBBER_XMPP_NS_IBB,
            WOCKY_NODE_ATTRIBUTE, "sid", priv->stream_id,
          WOCKY_NODE_END, NULL);

      gibber_xmpp_connection_send (priv->xmpp_connection, stanza, NULL);

      g_object_unref (stanza);
    }

  g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL);
}

#if 0
static LmHandlerResult
ibb_init_reply_cb (GibberConnection *conn,
                   LmMessage *sent_msg,
                   LmMessage *reply_msg,
                   GObject *obj,
                   gpointer user_data)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (obj);

  if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_RESULT)
    {
      /* yeah, stream initiated */
      DEBUG ("IBB stream initiated");
      g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN, NULL);
    }
  else
    {
      DEBUG ("error during IBB initiation");
      g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL);
    }

  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
#endif

/*
 * gibber_bytestream_ibb_initiate
 *
 * Implements gibber_bytestream_iface_initiate on GibberBytestreamIface
 */
static gboolean
gibber_bytestream_ibb_initiate (GibberBytestreamIface *bytestream)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *msg;
  GError *error = NULL;

  if (priv->state != GIBBER_BYTESTREAM_STATE_INITIATING)
    {
      DEBUG ("bytestream is not is the initiating state (state %d",
          priv->state);
      return FALSE;
    }

  if (priv->xmpp_connection == NULL)
    {
      DEBUG ("Can only initiate a private bytestream");
      return FALSE;
    }

  if (priv->stream_id == NULL)
    {
      DEBUG ("stream doesn't have an ID");
      return FALSE;
    }

  msg = wocky_stanza_build (
      WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
      priv->self_id, priv->peer_id,
      WOCKY_NODE_START, "open",
        WOCKY_NODE_XMLNS, GIBBER_XMPP_NS_IBB,
        WOCKY_NODE_ATTRIBUTE, "sid", priv->stream_id,
        WOCKY_NODE_ATTRIBUTE, "block-size", "4096",
      WOCKY_NODE_END, NULL);

  /* XXX should send using _with_reply (ibb_init_reply_cb) */
  if (!gibber_xmpp_connection_send (priv->xmpp_connection, msg, &error))
    {
      DEBUG ("Error when sending IBB init stanza: %s", error->message);

      g_error_free (error);
      g_object_unref (msg);
      return FALSE;
    }

  g_object_unref (msg);<commit_msg>bytestream-ibb: stop using GibberXmppConnections<commit_after>/*
 * gibber-bytestream-ibb.c - Source for GibberBytestreamIBB
 * Copyright (C) 2007-2008 Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "gibber-bytestream-ibb.h"

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <glib.h>
#include <wocky/wocky-namespaces.h>
#include <wocky/wocky-stanza.h>
#include <wocky/wocky-porter.h>

#include "gibber-muc-connection.h"
#include "gibber-namespaces.h"
#include "gibber-xmpp-error.h"

#define DEBUG_FLAG DEBUG_BYTESTREAM
#include "gibber-debug.h"

#include "gibber-signals-marshal.h"

/* IMPORTANT NOTE: This bytestream is not used anymore by Salut and so probably
 * a bit rotten */

/* FIXME: implement fragmentation using the "block-size" attribute */

static void
bytestream_iface_init (gpointer g_iface, gpointer iface_data);

G_DEFINE_TYPE_WITH_CODE (GibberBytestreamIBB, gibber_bytestream_ibb,
    G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (GIBBER_TYPE_BYTESTREAM_IFACE,
      bytestream_iface_init));

/* properties */
enum
{
  PROP_PORTER = 1,
  PROP_CONTACT,
  PROP_SELF_ID,
  PROP_PEER_ID,
  PROP_STREAM_ID,
  PROP_STREAM_INIT_ID,
  PROP_STATE,
  PROP_PROTOCOL,
  LAST_PROPERTY
};

typedef struct _GibberBytestreamIBBPrivate GibberBytestreamIBBPrivate;
struct _GibberBytestreamIBBPrivate
{
  WockyPorter *porter;
  WockyContact *contact;
  gchar *self_id;
  gchar *peer_id;
  gchar *stream_id;
  gchar *stream_init_id;
  guint stanza_received_id;
  GibberBytestreamState state;

  guint16 seq;
  guint16 last_seq_recv;

  gboolean dispose_has_run;
};

#define GIBBER_BYTESTREAM_IBB_GET_PRIVATE(obj) \
    ((GibberBytestreamIBBPrivate *) obj->priv)

static void
gibber_bytestream_ibb_init (GibberBytestreamIBB *self)
{
  GibberBytestreamIBBPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      GIBBER_TYPE_BYTESTREAM_IBB, GibberBytestreamIBBPrivate);

  self->priv = priv;
}

static gboolean
received_stanza_cb (WockyPorter *porter,
                    WockyStanza *stanza,
                    gpointer user_data)
{
  GibberBytestreamIBB *self = (GibberBytestreamIBB *) user_data;
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyNode *node = wocky_stanza_get_top_node (stanza);
  WockyNode *data;
  GString *str;
  guchar *decoded;
  gsize len;
  const gchar *from, *stream_id;

  data = wocky_node_get_child_ns (node, "data", WOCKY_XMPP_NS_IBB);
  if (data == NULL)
    {
      return FALSE;
    }

  stream_id = wocky_node_get_attribute (data, "sid");
  if (stream_id == NULL || strcmp (stream_id, priv->stream_id) != 0)
    {
      DEBUG ("bad stream id");
      return FALSE;
    }

  if (priv->state != GIBBER_BYTESTREAM_STATE_OPEN)
    {
      DEBUG ("can't receive data through a not open bytestream (state: %d)",
          priv->state);
      return FALSE;
    }

  from = wocky_node_get_attribute (node, "from");
  if (from == NULL)
    {
      DEBUG ("got a message without a from field, ignoring");
      return FALSE;
    }

  // XXX check sequence number ?

  decoded = g_base64_decode (data->content, &len);
  str = g_string_new_len ((const gchar *) decoded, len);
  g_signal_emit_by_name (G_OBJECT (self), "data-received", from, str);

  g_string_free (str, TRUE);
  g_free (decoded);

  return TRUE;
}

static void
gibber_bytestream_ibb_dispose (GObject *object)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  if (priv->dispose_has_run)
    return;
  priv->dispose_has_run = TRUE;

  if (priv->state != GIBBER_BYTESTREAM_STATE_CLOSED)
    {
      gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL);
    }

  G_OBJECT_CLASS (gibber_bytestream_ibb_parent_class)->dispose (object);
}

static void
gibber_bytestream_ibb_finalize (GObject *object)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  g_free (priv->stream_id);
  g_free (priv->stream_init_id);
  g_free (priv->self_id);
  g_free (priv->peer_id);

  G_OBJECT_CLASS (gibber_bytestream_ibb_parent_class)->finalize (object);
}

static void
gibber_bytestream_ibb_get_property (GObject *object,
                                    guint property_id,
                                    GValue *value,
                                    GParamSpec *pspec)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  switch (property_id)
    {
      case PROP_PORTER:
        g_value_set_object (value, priv->porter);
        break;
      case PROP_CONTACT:
        g_value_set_object (value, priv->contact);
        break;
      case PROP_SELF_ID:
        g_value_set_string (value, priv->self_id);
        break;
      case PROP_PEER_ID:
        g_value_set_string (value, priv->peer_id);
        break;
      case PROP_STREAM_ID:
        g_value_set_string (value, priv->stream_id);
        break;
      case PROP_STREAM_INIT_ID:
        g_value_set_string (value, priv->stream_init_id);
        break;
      case PROP_STATE:
        g_value_set_uint (value, priv->state);
        break;
      case PROP_PROTOCOL:
        g_value_set_string (value, GIBBER_XMPP_NS_IBB);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
make_porter_connections (GibberBytestreamIBB *self)
{
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  static gboolean done = FALSE;
  gchar *jid;

  if (done)
    return;

  jid = wocky_contact_dup_jid (priv->contact);

  priv->stanza_received_id = wocky_porter_register_handler_from (priv->porter,
      WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_TYPE_NONE, jid,
      WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, received_stanza_cb, self, NULL);

  g_free (jid);

  done = TRUE;
}

static void
gibber_bytestream_ibb_set_property (GObject *object,
                                    guint property_id,
                                    const GValue *value,
                                    GParamSpec *pspec)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (object);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  switch (property_id)
    {
      case PROP_PORTER:
        priv->porter = g_value_dup_object (value);

        if (priv->porter != NULL && priv->contact != NULL)
          make_porter_connections (self);
        break;
      case PROP_CONTACT:
        priv->contact = g_value_dup_object (value);

        if (priv->porter != NULL && priv->contact != NULL)
          make_porter_connections (self);
        break;
      case PROP_SELF_ID:
        g_free (priv->self_id);
        priv->self_id = g_value_dup_string (value);
        break;
      case PROP_PEER_ID:
        g_free (priv->peer_id);
        priv->peer_id = g_value_dup_string (value);
        break;
      case PROP_STREAM_ID:
        g_free (priv->stream_id);
        priv->stream_id = g_value_dup_string (value);
        break;
      case PROP_STREAM_INIT_ID:
        g_free (priv->stream_init_id);
        priv->stream_init_id = g_value_dup_string (value);
        break;
      case PROP_STATE:
        if (priv->state != g_value_get_uint (value))
            {
              priv->state = g_value_get_uint (value);
              g_signal_emit_by_name (object, "state-changed", priv->state);
            }
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static GObject *
gibber_bytestream_ibb_constructor (GType type,
                                   guint n_props,
                                   GObjectConstructParam *props)
{
  GObject *obj;
  GibberBytestreamIBBPrivate *priv;

  obj = G_OBJECT_CLASS (gibber_bytestream_ibb_parent_class)->
           constructor (type, n_props, props);

  priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (GIBBER_BYTESTREAM_IBB (obj));

  g_assert (priv->porter != NULL);
  g_assert (priv->contact != NULL);
  g_assert (priv->stream_init_id != NULL);
  g_assert (priv->self_id != NULL);
  g_assert (priv->peer_id != NULL);

  return obj;
}

static void
gibber_bytestream_ibb_class_init (
    GibberBytestreamIBBClass *gibber_bytestream_ibb_class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (gibber_bytestream_ibb_class);
  GParamSpec *param_spec;

  g_type_class_add_private (gibber_bytestream_ibb_class,
      sizeof (GibberBytestreamIBBPrivate));

  object_class->dispose = gibber_bytestream_ibb_dispose;
  object_class->finalize = gibber_bytestream_ibb_finalize;

  object_class->get_property = gibber_bytestream_ibb_get_property;
  object_class->set_property = gibber_bytestream_ibb_set_property;
  object_class->constructor = gibber_bytestream_ibb_constructor;

  g_object_class_override_property (object_class, PROP_SELF_ID,
      "self-id");
  g_object_class_override_property (object_class, PROP_PEER_ID,
      "peer-id");
  g_object_class_override_property (object_class, PROP_STREAM_ID,
      "stream-id");
  g_object_class_override_property (object_class, PROP_STATE,
      "state");
  g_object_class_override_property (object_class, PROP_PROTOCOL,
      "protocol");

  param_spec = g_param_spec_object (
      "porter",
      "WockyPorter object",
      "Wocky porter object used for communication by this "
      "bytestream if it's a private one",
      WOCKY_TYPE_PORTER,
      G_PARAM_CONSTRUCT_ONLY |
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_PORTER,
      param_spec);

  param_spec = g_param_spec_object (
      "contact",
      "WockyContact object",
      "Contact object used for communication by this "
      "bytestream if it's a private one",
      WOCKY_TYPE_CONTACT,
      G_PARAM_CONSTRUCT_ONLY |
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_CONTACT,
      param_spec);

  param_spec = g_param_spec_string (
      "stream-init-id",
      "stream init ID",
      "the iq ID of the SI request, if any",
      "",
      G_PARAM_CONSTRUCT_ONLY |
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_STREAM_INIT_ID,
      param_spec);
}

/*
 * gibber_bytestream_ibb_send
 *
 * Implements gibber_bytestream_iface_send on GibberBytestreamIface
 */
static gboolean
gibber_bytestream_ibb_send (GibberBytestreamIface *bytestream,
                            guint len,
                            const gchar *str)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *stanza;
  gchar *seq, *encoded;

  if (priv->state != GIBBER_BYTESTREAM_STATE_OPEN)
    {
      DEBUG ("can't send data through a not open bytestream (state: %d)",
          priv->state);
      return FALSE;
    }

  seq = g_strdup_printf ("%u", priv->seq++);

  encoded = g_base64_encode ((const guchar *) str, len);

  stanza = wocky_stanza_build_to_contact (WOCKY_STANZA_TYPE_MESSAGE,
      WOCKY_STANZA_SUB_TYPE_NONE,
      priv->self_id, priv->contact,
      '(', "data",
        ':', GIBBER_XMPP_NS_IBB,
        '@', "sid", priv->stream_id,
        '@', "seq", seq,
        '$', encoded,
      ')',
      '(', "amp",
        ':', GIBBER_XMPP_NS_AMP,
        '(', "rule",
          '@', "condition", "deliver-at",
          '@', "value", "stored",
          '@', "action", "error",
        ')',
        '(', "rule",
          '@', "condition", "match-resource",
          '@', "value", "exact",
          '@', "action", "error",
        ')',
      ')',
      NULL);

  DEBUG ("send %d bytes", len);
  wocky_porter_send (priv->porter, stanza);

  g_object_unref (stanza);
  g_free (encoded);
  g_free (seq);

  return TRUE;
}

static WockyStanza *
create_si_accept_iq (GibberBytestreamIBB *self)
{
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  return wocky_stanza_build_to_contact (
      WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT,
      priv->self_id, priv->contact,
      '@', "id", priv->stream_init_id,
      '(', "si",
        ':', GIBBER_XMPP_NS_SI,
        '(', "feature",
          ':', GIBBER_XMPP_NS_FEATURENEG,
          '(', "x",
            ':', GIBBER_XMPP_NS_DATA,
            '@', "type", "submit",
            '(', "field",
              '@', "var", "stream-method",
              '(', "value",
                '$', GIBBER_XMPP_NS_IBB,
              ')',
            ')',
          ')',
        ')',
      ')', NULL);
}

/*
 * gibber_bytestream_ibb_accept
 *
 * Implements gibber_bytestream_iface_accept on GibberBytestreamIface
 */
static void
gibber_bytestream_ibb_accept (GibberBytestreamIface *bytestream,
                              GibberBytestreamAugmentSiAcceptReply func,
                              gpointer user_data)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *stanza;
  WockyNode *node;
  WockyNode *si;

  if (priv->state != GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
    {
      /* The stream was previoulsy or automatically accepted */
      DEBUG ("stream was already accepted");
      return;
    }

  stanza = create_si_accept_iq (self);
  node = wocky_stanza_get_top_node (stanza);
  si = wocky_node_get_child_ns (node, "si", GIBBER_XMPP_NS_SI);
  g_assert (si != NULL);

  if (func != NULL)
    {
      /* let the caller add his profile specific data */
      func (si, user_data);
    }

  wocky_porter_send (priv->porter, stanza);

  g_object_unref (stanza);

  DEBUG ("stream is now accepted");
  g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_ACCEPTED, NULL);
}

static void
gibber_bytestream_ibb_decline (GibberBytestreamIBB *self,
                               GError *error)
{
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *stanza;
  WockyNode *node;

  g_return_if_fail (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING);

  stanza = wocky_stanza_build_to_contact (
      WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_ERROR,
      priv->self_id, priv->contact,
      '@', "id", priv->stream_init_id,
      NULL);
  node = wocky_stanza_get_top_node (stanza);

  if (error != NULL && error->domain == GIBBER_XMPP_ERROR)
    {
      gibber_xmpp_error_to_node (error->code, node, error->message);
    }
  else
    {
      gibber_xmpp_error_to_node (XMPP_ERROR_FORBIDDEN, node,
          "Offer Declined");
    }

  wocky_porter_send (priv->porter, stanza);

  g_object_unref (stanza);
}

/*
 * gibber_bytestream_ibb_close
 *
 * Implements gibber_bytestream_iface_close on GibberBytestreamIface
 */
static void
gibber_bytestream_ibb_close (GibberBytestreamIface *bytestream,
                             GError *error)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);

  if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED)
     /* bytestream already closed, do nothing */
     return;

  g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSING, NULL);

  if (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING)
    {
      /* Stream was created using SI so we decline the request */
      gibber_bytestream_ibb_decline (self, error);
    }
  else
    {
      WockyStanza *stanza;

      DEBUG ("send IBB close stanza");

      stanza = wocky_stanza_build_to_contact (WOCKY_STANZA_TYPE_IQ,
          WOCKY_STANZA_SUB_TYPE_SET,
          priv->self_id, priv->contact,
          '(', "close",
            ':', GIBBER_XMPP_NS_IBB,
            '@', "sid", priv->stream_id,
          ')', NULL);

      wocky_porter_send (priv->porter, stanza);

      g_object_unref (stanza);
    }

  g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL);
}

#if 0
static LmHandlerResult
ibb_init_reply_cb (GibberConnection *conn,
                   LmMessage *sent_msg,
                   LmMessage *reply_msg,
                   GObject *obj,
                   gpointer user_data)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (obj);

  if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_RESULT)
    {
      /* yeah, stream initiated */
      DEBUG ("IBB stream initiated");
      g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN, NULL);
    }
  else
    {
      DEBUG ("error during IBB initiation");
      g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL);
    }

  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
#endif

/*
 * gibber_bytestream_ibb_initiate
 *
 * Implements gibber_bytestream_iface_initiate on GibberBytestreamIface
 */
static gboolean
gibber_bytestream_ibb_initiate (GibberBytestreamIface *bytestream)
{
  GibberBytestreamIBB *self = GIBBER_BYTESTREAM_IBB (bytestream);
  GibberBytestreamIBBPrivate *priv = GIBBER_BYTESTREAM_IBB_GET_PRIVATE (self);
  WockyStanza *msg;

  if (priv->state != GIBBER_BYTESTREAM_STATE_INITIATING)
    {
      DEBUG ("bytestream is not is the initiating state (state %d",
          priv->state);
      return FALSE;
    }

  if (priv->stream_id == NULL)
    {
      DEBUG ("stream doesn't have an ID");
      return FALSE;
    }

  msg = wocky_stanza_build_to_contact (
      WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
      priv->self_id, priv->contact,
      '(', "open",
        ':', GIBBER_XMPP_NS_IBB,
        '@', "sid", priv->stream_id,
        '@', "block-size", "4096",
      ')', NULL);

  /* XXX should send using _with_reply (ibb_init_reply_cb) */
  wocky_porter_send (priv->porter, msg);

  g_object_unref (msg);
{'fraction_non_alphanumeric': 0.09435086388994204, 'fraction_numerical': 0.0019498456372203867, 'mean_word_length': 3.2709923664122136, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 183, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5144848', 'n_tokens_mistral': 13668, 'n_tokens_neox': 13337, 'n_words': 2521}