/*
 *
 *  Multimedia Messaging Service Daemon - The Next Generation
 *
 *  Copyright (C) 2010-2011, Intel Corporation
 *                2021, Chris Talbot <chris@talbothome.com>
 *                2021, Clayton Craft <clayton@craftyguy.net>
 *                2020, Anteater <nt8r@protonmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#define _XOPEN_SOURCE 700

/* See https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/332#note_1107764 */
#define EDS_DISABLE_DEPRECATED

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <unistd.h>
#include <net/if.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <glib.h>
#include <glib/gstdio.h>
#include <time.h>
#include <stdio.h>
#include <gio/gio.h>
#include <stdlib.h>
#include <asm/socket.h>

#include <libsoup/soup.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <netdb.h>
#include <ares.h>

#include "mmsutil.h"
#include "mms.h"
#include "dbus.h"
#include "wsputil.h"
#include "phone-utils.h"

#define BEARER_SETUP_TIMEOUT    20      /* 20 seconds */
#define BEARER_IDLE_TIMEOUT     10      /* 10 seconds */
#define CHUNK_SIZE 2048                  /* 2 Kib */
#define DEFAULT_CONTENT_TYPE "application/vnd.wap.mms-message"

#define CT_MUTLIPART "Content-Type: \"application/vnd.wap.multipart."
#define CT_TYPE ";type=\"application/smil\""
#define CT_START ";start=\"<SMIL>\""
#define CT_MULTIPART_RELATED CT_MUTLIPART "related\"" CT_TYPE CT_START
#define CT_MULTIPART_MIXED CT_MUTLIPART "mixed\""
#define CONTENT_ID_SMIL "SMIL"
#define CONTENT_TYPE_APP_SMIL "Content-Type: \"application/smil\";charset=utf-8"

#define DEFAULT_MAX_ATTACHMENTS_NUMBER 25
#define MAX_ATTEMPTS 3
#define DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE 1100000
#define MMS_CONTENT_TYPE "application/vnd.wap.mms-message"

#define SETTINGS_STORE "mms"
#define SETTINGS_GROUP "Settings"

#define RESOLVED_SERVICE                "org.freedesktop.resolve1"
#define RESOLVED_PATH                   "/org/freedesktop/resolve1"
#define RESOLVED_MANAGER_INTERFACE      RESOLVED_SERVICE ".Manager"

static const char *ctl_chars = "\x01\x02\x03\x04\x05\x06\x07\x08\x0A"
                               "\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14"
                               "\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E"
                               "\x1F\x7F";

static const char *sep_chars = "()<>@,;:\\\"/[]?={} \t";

static const char *const empty_subjects[] = {
  "NoSubject",
  "New MMS",
  "",
  NULL,
};


struct mms_request;

typedef gboolean (*mms_request_result_cb_t) (struct mms_request *request);

struct mms_service
{
  gint refcount;
  char *identity;
  char *path;
  gchar *mmsc;
  gchar *resolvers_ipv4_csv;
  gchar *resolvers_ipv6_csv;
  gchar *interface;
  char *request_path;
  gboolean proxy_active;
  char *country_code;
  char *own_number;
  char *apn;
  mms_service_bearer_handler_func_t bearer_handler;
  void *bearer_data;
  guint bearer_timeout;
  gboolean bearer_setup;
  gboolean bearer_active;
  GQueue *request_queue;
  SoupMessage *current_request_msg;
  SoupSession *web;
  GHashTable *messages;
  GKeyFile *settings;
  gboolean use_delivery_reports;
  int max_attach_total_size;
  int max_attachments;
  int auto_create_smil;
  int force_c_ares;
  GCancellable *cancel_current_msg;
  int notification_ind;
};

enum mms_request_type {
  MMS_REQUEST_TYPE_GET,
  MMS_REQUEST_TYPE_POST,
  MMS_REQUEST_TYPE_POST_TMP
};

struct mms_request
{
  enum mms_request_type type;
  char *data_path;
  char *data_to_post_path;
  gchar *location;
  int fd;
  guint16 status;
  guint16 attempt;
  struct mms_service *service;
  gulong soupmessage_network_event_signal_id;
  mms_request_result_cb_t result_cb;
  struct mms_message *msg;
};

enum mms_tx_rx_error {
  MMS_TX_RX_ERROR_UNKNOWN,
  MMS_TX_RX_ERROR_DNS,
  MMS_TX_RX_ERROR_HTTP
};

static GList *service_list;

static guint32 transaction_id_start = 0;

gboolean global_debug = FALSE;

guint service_registration_id;
guint manager_registration_id;
guint systemd_resolved_watcher_id;
GDBusProxy *systemd_resolved_proxy;

static const char *time_to_str (const time_t *t);
void debug_print (const char *s,
                  void       *data);
static void process_request_queue (struct mms_service *service);
static void emit_msg_status_changed (const char *path,
                                     const char *new_status);
static void append_message (const char               *path,
                            const struct mms_service *service,
                            struct mms_message       *msg,
                            GVariantBuilder          *message_builder);
static void append_message_entry (char                     *path,
                                  const struct mms_service *service,
                                  struct mms_message       *msg,
                                  GVariantBuilder          *message_builder);
static void append_properties (GVariantBuilder    *service_builder,
                               struct mms_service *service);
static gboolean send_message_get_args (GVariant           *parameters,
                                       struct mms_message *msg,
                                       struct mms_service *service);
static void release_attachement_data (GSList *attach);
static inline char *create_transaction_id (void);
static struct mms_request *create_request (enum                          mms_request_type type,
                                           mms_request_result_cb_t                        result_cb,
                                           char                                          *location,
                                           struct mms_service                            *service,
                                           struct mms_message                            *msg);
static gboolean result_request_send_conf (struct mms_request *request);
static const char *mms_address_to_string (char *mms_address);
static void emit_message_added (const struct mms_service *service,
                                struct mms_message       *msg);
static void mms_request_destroy (struct mms_request *request);
static gboolean valid_content_type (char *ct);
static gboolean result_request_retrieve_conf (struct mms_request *request);
static gboolean result_request_notify_resp (struct mms_request *request);

static void
handle_method_call_service (GDBusConnection       *connection,
                            const gchar           *sender,
                            const gchar           *object_path,
                            const gchar           *interface_name,
                            const gchar           *method_name,
                            GVariant              *parameters,
                            GDBusMethodInvocation *invocation,
                            gpointer               user_data)
{
  if (g_strcmp0 (method_name, "GetMessages") == 0)
    {
      struct mms_service      *service = user_data;
      GVariantBuilder          messages_builder;
      GVariant                *messages, *all_messages;
      GHashTableIter           table_iter;
      gpointer                 key, value;
      guint                    i = 0;

      DBG ("Retrieving all Messages...");

      if (g_hash_table_size (service->messages) == 0)
        {
          all_messages = g_variant_new ("(a(oa{sv}))", NULL);
          DBG ("No Messages!");
        }
      else {
          g_variant_builder_init (&messages_builder, G_VARIANT_TYPE ("a(oa{sv})"));

          g_hash_table_iter_init (&table_iter, service->messages);
          while (g_hash_table_iter_next (&table_iter, &key, &value))
            {
              i = i + 1;
              DBG ("On message %d!", i);
              g_variant_builder_open (&messages_builder, G_VARIANT_TYPE ("(oa{sv})"));
              append_message_entry (key, service, value, &messages_builder);
              g_variant_builder_close (&messages_builder);
            }
          DBG ("Messages total: %d", i);
          messages = g_variant_builder_end (&messages_builder);

          all_messages = g_variant_new ("(*)", messages);
        }

      g_dbus_method_invocation_return_value (invocation, all_messages);
    }
  else if (g_strcmp0 (method_name, "GetProperties") == 0)
    {
      struct mms_service      *service = user_data;
      GVariantBuilder          properties_builder;
      GVariant                *properties, *all_properties;

      g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}"));

      g_variant_builder_add_parsed (&properties_builder,
                                    "{'UseDeliveryReports', <%b>}",
                                    service->use_delivery_reports);

      g_variant_builder_add_parsed (&properties_builder,
                                    "{'AutoCreateSMIL', <%b>}",
                                    service->auto_create_smil);

      g_variant_builder_add_parsed (&properties_builder,
                                    "{'TotalMaxAttachmentSize', <%i>}",
                                    service->max_attach_total_size);

      g_variant_builder_add_parsed (&properties_builder,
                                    "{'MaxAttachments', <%i>}",
                                    service->max_attachments);

      g_variant_builder_add_parsed (&properties_builder,
                                    "{'NotificationInds', <%i>}",
                                    service->notification_ind);

      properties = g_variant_builder_end (&properties_builder);


      all_properties = g_variant_new ("(*)", properties);

      g_dbus_method_invocation_return_value (invocation, all_properties);
    }
  else if (g_strcmp0 (method_name, "SendMessage") == 0)
    {
      struct mms_message              *msg;
      struct mms_service              *service = user_data;
      struct mms_request              *request;
      GVariant                        *messagepathvariant;
      GKeyFile                        *meta;
      const char                      *datestr;

      msg = g_new0 (struct mms_message, 1);
      if (msg == NULL)
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_FAILED,
                                                 "Could not allocate memory for MMS!");
          return;
        }

      msg->type = MMS_MESSAGE_TYPE_SEND_REQ;
      msg->version = MMS_MESSAGE_VERSION_1_3;

      msg->sr.status = MMS_MESSAGE_STATUS_DRAFT;

      msg->sr.dr = service->use_delivery_reports;

      time (&msg->sr.date);

      datestr = time_to_str (&msg->sr.date);

      g_free (msg->sr.datestamp);
      msg->sr.datestamp = g_strdup (datestr);

      if (send_message_get_args (parameters, msg, service) == FALSE)
        {
          DBG ("Invalid arguments");

          release_attachement_data (msg->attachments);

          mms_message_free (msg);

          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Invalid Arguments!");
          return;
        }

      msg->transaction_id = create_transaction_id ();
      if (msg->transaction_id == NULL)
        {
          g_critical ("Error in create_transaction_id()");
          release_attachement_data (msg->attachments);
          mms_message_free (msg);

          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_FAILED,
                                                 "There was an error sending the MMS!");
          return;
        }

      request = create_request (MMS_REQUEST_TYPE_POST,
                                result_request_send_conf, NULL, service, msg);
      if (request == NULL)
        {
          g_critical ("Error in create_request()");
          release_attachement_data (msg->attachments);
          mms_message_free (msg);

          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_FAILED,
                                                 "There was an error sending the MMS!");
          return;
        }

      /* Encode the MMS on the temp file */
      if (mms_message_encode (msg, request->fd) == FALSE)
        {
          g_critical ("Error in mms_message_encode()");
          unlink (request->data_path);
          mms_request_destroy (request);

          release_attachement_data (msg->attachments);
          mms_message_free (msg);

          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_FAILED,
                                                 "There was an error sending the MMS!");
          return;
        }

      close (request->fd);

      request->fd = -1;

      /* Rename the temp file to the stored MMS file */
      msg->uuid = mms_store_file (service->identity,
                                  request->data_path);
      if (msg->uuid == NULL)
        {
          g_critical ("Error in mms_store_file()");
          unlink (request->data_path);
          mms_request_destroy (request);

          release_attachement_data (msg->attachments);
          mms_message_free (msg);

          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_FAILED,
                                                 "There was an error sending the MMS!");
          return;
        }

      /* Remap the data_path file to the renamed file */
      g_free (request->data_path);
      request->data_path = g_strdup_printf ("%s/.mms/%s/%s", g_get_home_dir (),
                                            service->identity, msg->uuid);

      meta = mms_store_meta_open (service->identity, msg->uuid);
      if (meta == NULL)
        {
          g_critical ("Error in mms_store_meta_open()");
          unlink (request->data_path);
          mms_request_destroy (request);

          release_attachement_data (msg->attachments);
          mms_message_free (msg);

          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_FAILED,
                                                 "There was an error sending the MMS!");
          return;
        }

      g_key_file_set_string (meta, "info", "date", msg->sr.datestamp);
      g_key_file_set_string (meta, "info", "state", "draft");

      if (msg->sr.dr)
        {
          char **tos;
          int i;
          GString *to_concat = g_string_new (NULL);

          g_key_file_set_boolean (meta, "info", "delivery_report", TRUE);

          tos = g_strsplit (msg->sr.to, ",", 0);

          for (i = 0; tos[i] != NULL; i++)
            {
              g_autofree char *to = g_strdup (tos[i]);
              g_autofree char *formatted_to = NULL;

              mms_address_to_string (to);
              formatted_to = phone_utils_format_number_e164 (to,
                                                             service->country_code,
                                                             TRUE);
              to_concat = g_string_append (to_concat, formatted_to);
              to_concat = g_string_append (to_concat, ",");

              g_key_file_set_string (meta, "delivery_status", formatted_to,
                                     "none");
            }
          to_concat = g_string_truncate (to_concat, (strlen (to_concat->str) - 1));
          g_key_file_set_string (meta, "info", "delivery_recipients", to_concat->str);
          g_key_file_set_integer (meta, "info", "delivery_recipients_number", i);
          g_key_file_set_integer (meta, "delivery_status", "delivery_number_complete", 0);

          g_strfreev (tos);
          msg->sr.delivery_recipients = g_string_free (to_concat, FALSE);
        }
      else
        g_key_file_set_boolean (meta, "info", "delivery_report", FALSE);

      mms_store_meta_close (service->identity, msg->uuid, meta, TRUE);

      if (mms_message_register (service, msg) < 0)
        {
          g_critical ("Error in mms_message_register()");
          unlink (request->data_path);
          mms_request_destroy (request);

          release_attachement_data (msg->attachments);
          mms_message_free (msg);

          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_FAILED,
                                                 "There was an error sending the MMS!");
          return;
        }

      emit_message_added (service, msg);

      release_attachement_data (msg->attachments);

      g_queue_push_tail (service->request_queue, request);

      activate_bearer (service);

      messagepathvariant = g_variant_new ("(o)", msg->path);

      g_dbus_method_invocation_return_value (invocation, messagepathvariant);
    }
  else if (g_strcmp0 (method_name, "SetProperty") == 0)
    {
      GVariant *variantstatus;
      gchar *dict;
      gboolean deliveryreports, autocreatesmil;
      gint max_size;
      struct mms_service *service = user_data;

      g_variant_get (parameters, "(sv)", &dict, &variantstatus);

      if (g_strcmp0 (dict, "UseDeliveryReports") == 0)
        {
          g_variant_get (variantstatus, "b", &deliveryreports);

          service->use_delivery_reports = deliveryreports;
          DBG ("Delivery Reports set to %d", deliveryreports);

          g_key_file_set_boolean (service->settings,
                                  SETTINGS_GROUP,
                                  "UseDeliveryReports",
                                  service->use_delivery_reports);

          g_dbus_method_invocation_return_value (invocation, NULL);
        }
      else if (g_strcmp0 (dict, "TotalMaxAttachmentSize") == 0)
        {
          g_variant_get (variantstatus, "i", &max_size);
          service->max_attach_total_size = max_size;
          DBG ("TotalMaxAttachmentSize is now set to %d!",
               service->max_attach_total_size);

          g_key_file_set_integer (service->settings,
                                  SETTINGS_GROUP,
                                  "TotalMaxAttachmentSize",
                                  service->max_attach_total_size);

          g_dbus_method_invocation_return_value (invocation, NULL);
        }
      else if (g_strcmp0 (dict, "MaxAttachments") == 0)
        {
          g_variant_get (variantstatus, "i", &max_size);
          service->max_attachments = max_size;
          DBG ("MaxAttachments is now set to %d!",
               service->max_attachments);

          g_key_file_set_integer (service->settings,
                                  SETTINGS_GROUP,
                                  "MaxAttachments",
                                  service->max_attachments);

          g_dbus_method_invocation_return_value (invocation, NULL);
        }
      else if (g_strcmp0 (dict, "AutoCreateSMIL") == 0)
        {
          g_variant_get (variantstatus, "b", &autocreatesmil);

          service->auto_create_smil = autocreatesmil;
          DBG ("AutoCreateSMIL set to %d", autocreatesmil);

          g_key_file_set_boolean (service->settings,
                                  SETTINGS_GROUP,
                                  "AutoCreateSMIL",
                                  service->auto_create_smil);

          g_dbus_method_invocation_return_value (invocation, NULL);
        }
      else {
          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Cannot find the Property requested!");
          return;
        }
      mms_settings_sync (service->identity, SETTINGS_STORE, service->settings);
    }
  else {
      g_dbus_method_invocation_return_error (invocation,
                                             G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "Cannot find the method requested!");
      return;
    }
}

static const GDBusInterfaceVTable interface_vtable_service = {
  handle_method_call_service
};

static void
handle_method_call_manager (GDBusConnection       *connection,
                            const gchar           *sender,
                            const gchar           *object_path,
                            const gchar           *interface_name,
                            const gchar           *method_name,
                            GVariant              *parameters,
                            GDBusMethodInvocation *invocation,
                            gpointer               user_data)
{
  if (g_strcmp0 (method_name, "GetServices") == 0)
    {
      struct mms_service      *service;
      GVariant                *all_services;
      GList                   *l;

      DBG ("At Get Services Method Call");

      if (service_list)
        {
          GVariantBuilder  service_builder;
          GVariant        *get_services;

          g_variant_builder_init (&service_builder, G_VARIANT_TYPE ("a(oa{sv})"));
          for (l = service_list; l != NULL; l = l->next)
            {
              service = l->data;
              g_variant_builder_open (&service_builder, G_VARIANT_TYPE ("(oa{sv})"));
              append_properties (&service_builder, service);
              g_variant_builder_close (&service_builder);
            }
          get_services = g_variant_builder_end (&service_builder);
          all_services = g_variant_new ("(*)", get_services);
        }
      else
        all_services = g_variant_new ("(a(oa{sv}))", NULL);
      g_dbus_method_invocation_return_value (invocation, all_services);
    }
  else {
      g_dbus_method_invocation_return_error (invocation,
                                             G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "Cannot find the method requested!");
      return;
    }
}

static const GDBusInterfaceVTable interface_vtable_manager = {
  handle_method_call_manager
};

static void
handle_method_call_message (GDBusConnection       *connection,
                            const gchar           *sender,
                            const gchar           *object_path,
                            const gchar           *interface_name,
                            const gchar           *method_name,
                            GVariant              *parameters,
                            GDBusMethodInvocation *invocation,
                            gpointer               user_data)
{
  if (g_strcmp0 (method_name, "Delete") == 0)
    {
      struct mms_service *service = user_data;
      struct mms_message *mms;
      const char *path = object_path;
      g_autofree char *uuid = NULL;

      DBG ("Deleting Message path %s", path);

      mms = g_hash_table_lookup (service->messages, path);
      if (mms == NULL)
        g_dbus_method_invocation_return_dbus_error (invocation,
                                                    MMS_MESSAGE_INTERFACE,
                                                    "Cannot find the MMS to delete!");

      uuid = g_strdup (mms->uuid);
      if (mms_message_unregister (service, path, mms->message_registration_id) < 0)
        g_dbus_method_invocation_return_dbus_error (invocation,
                                                    MMS_MESSAGE_INTERFACE,
                                                    "There was an error deleting the MMS!");
      mms_store_remove (service->identity, uuid);

      DBG ("Successfully Deleted Message!");
      g_dbus_method_invocation_return_value (invocation, NULL);
    }
  else if (g_strcmp0 (method_name, "MarkRead") == 0)
    {
      struct mms_service *service = user_data;
      struct mms_message *mms;
      const char *path = object_path;
      g_autofree char *state = NULL;
      GKeyFile *meta;

      DBG ("message path %s", path);

      mms = g_hash_table_lookup (service->messages, path);
      if (mms == NULL)
        g_dbus_method_invocation_return_dbus_error (invocation,
                                                    MMS_MESSAGE_INTERFACE,
                                                    "Cannot find this MMS!");

      meta = mms_store_meta_open (service->identity, mms->uuid);

      state = g_key_file_get_string (meta, "info", "state", NULL);
      if (state == NULL)
        {
          mms_store_meta_close (service->identity, mms->uuid, meta, FALSE);
          g_dbus_method_invocation_return_dbus_error (invocation,
                                                      MMS_MESSAGE_INTERFACE,
                                                      "Cannot find this MMS. Was it Deleted?");
        }

      if (strcmp (state, "received") != 0 && strcmp (state, "sent") != 0 && strcmp (state, "delivered") != 0)
        {
          mms_store_meta_close (service->identity, mms->uuid, meta, FALSE);
          g_dbus_method_invocation_return_dbus_error (invocation,
                                                      MMS_MESSAGE_INTERFACE,
                                                      "This MMS cannot be marked read!");
        }

      g_key_file_set_boolean (meta, "info", "read", TRUE);
      mms->rc.status = MMS_MESSAGE_STATUS_READ;

      mms_store_meta_close (service->identity, mms->uuid, meta, TRUE);

      emit_msg_status_changed (path, "read");

      g_dbus_method_invocation_return_value (invocation, NULL);
    }
  else {
      g_dbus_method_invocation_return_error (invocation,
                                             G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "Cannot find the method requested!");
      return;
    }
}

static const GDBusInterfaceVTable interface_vtable_message = {
  handle_method_call_message
};


static void
mms_load_settings (struct mms_service *service)
{
  GError *error;

  service->settings = mms_settings_open (service->identity,
                                         SETTINGS_STORE);
  if (service->settings == NULL)
    return;

  error = NULL;
  service->use_delivery_reports =
    g_key_file_get_boolean (service->settings, SETTINGS_GROUP,
                            "UseDeliveryReports", &error);

  if (error)
    {
      g_error_free (error);
      service->use_delivery_reports = FALSE;
      g_key_file_set_boolean (service->settings, SETTINGS_GROUP,
                              "UseDeliveryReports",
                              service->use_delivery_reports);
      error = NULL;
    }

  service->max_attach_total_size =
    g_key_file_get_integer (service->settings, SETTINGS_GROUP,
                            "TotalMaxAttachmentSize", &error);

  if (error)
    {
      g_error_free (error);
      service->max_attach_total_size = DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE;
      g_key_file_set_integer (service->settings, SETTINGS_GROUP,
                              "TotalMaxAttachmentSize",
                              service->max_attach_total_size);
      error = NULL;
    }
  DBG ("Maximum Attachment Total Size (in bytes): %d", service->max_attach_total_size);

  service->max_attachments =
    g_key_file_get_integer (service->settings, SETTINGS_GROUP,
                            "MaxAttachments", &error);

  if (error)
    {
      g_error_free (error);
      service->max_attachments = DEFAULT_MAX_ATTACHMENTS_NUMBER;
      g_key_file_set_integer (service->settings, SETTINGS_GROUP,
                              "MaxAttachments",
                              service->max_attachments);
      error = NULL;
    }
  DBG ("Maximum Number of Attachments: %d", service->max_attachments);

  service->auto_create_smil =
    g_key_file_get_boolean (service->settings, SETTINGS_GROUP,
                            "AutoCreateSMIL", &error);

  if (error)
    {
      g_error_free (error);
      service->auto_create_smil = FALSE;
      g_key_file_set_boolean (service->settings, SETTINGS_GROUP,
                              "AutoCreateSMIL",
                              service->auto_create_smil);
      error = NULL;
    }
  DBG ("AutoCreateSMIL is set to: %d", service->auto_create_smil);

  service->force_c_ares =
    g_key_file_get_boolean (service->settings, SETTINGS_GROUP,
                            "ForceCAres", &error);

  if (error)
    {
      g_error_free (error);
      service->force_c_ares = TRUE;
      g_key_file_set_boolean (service->settings, SETTINGS_GROUP,
                              "ForceCAres",
                              service->force_c_ares);
      error = NULL;
    }
  DBG ("Force c-ares is set to: %d", service->force_c_ares);
}


static void
mms_request_destroy (struct mms_request *request)
{
  g_free (request->data_path);
  g_free (request->data_to_post_path);
  g_free (request->location);
  g_free (request);
}

static struct mms_message *
mms_request_steal_message (struct mms_request *request)
{
  struct mms_message *msg = request->msg;

  request->msg = NULL;

  return msg;
}


static void
emit_msg_status_changed (const char *path,
                         const char *new_status)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  GVariant *changedproperty;
  g_autoptr(GError) error = NULL;

  DBG ("Emitting status of %s changed to %s", path, new_status);

  changedproperty = g_variant_new_parsed ("('status', <%s>)", new_status);
  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 path,
                                 MMS_MESSAGE_INTERFACE,
                                 "PropertyChanged",
                                 changedproperty,
                                 &error);

  if (error != NULL)
    {
      g_warning ("Error in Proxy call: %s\n", error->message);
      error = NULL;
    }
}

static gboolean
valid_content_type (char *ct)
{
  if (strlen (ct) == 0)
    return FALSE;

  if (strpbrk (ct, ctl_chars) != NULL)
    return FALSE;

  if (isspace (*ct) == TRUE)
    return FALSE;

  ct = strpbrk (ct, sep_chars);
  if (ct == NULL)
    return FALSE;

  if (ct[0] != '/')
    return FALSE;

  ct += 1;

  ct = strpbrk (ct, sep_chars);
  if (ct == NULL)
    return TRUE;

  return FALSE;
}

static gboolean
mmap_file (const char *path,
           void      **out_pdu,
           size_t     *out_len)
{
  struct stat st;
  int fd;

  fd = open (path, O_RDONLY);
  if (fd < 0)
    {
      g_critical ("Failed to open %s", path);
      return FALSE;
    }

  if (fstat (fd, &st) < 0)
    {
      g_critical ("Failed to stat %s", path);
      close (fd);
      return FALSE;
    }

  *out_pdu = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);

  close (fd);

  if (*out_pdu == MAP_FAILED)
    {
      g_critical ("Failed to mmap %s", path);
      return FALSE;
    }

  *out_len = st.st_size;

  return TRUE;
}

static const char *
mms_address_to_string (char *mms_address)
{
  unsigned int prefix_len;

  if (g_str_has_suffix (mms_address, "/TYPE=PLMN") == TRUE)
    {
      prefix_len = strlen (mms_address) - 10;

      mms_address[prefix_len] = '\0';
    }

  return (const char *) mms_address;
}

static gboolean
send_message_get_recipients (GVariant           *recipients,
                             struct mms_message *msg,
                             struct mms_service *service)
{
  GVariantIter iter;
  g_autoptr(GVariant) single_recipient = NULL;

  g_variant_iter_init (&iter, recipients);

  while ((single_recipient = g_variant_iter_next_value (&iter)))
    {
      const char *rec;
      g_autofree char *formatted_rec = NULL;
      char *tmp;
      rec = g_variant_get_string (single_recipient, NULL);

      formatted_rec = phone_utils_format_number_e164 (rec,
                                                      service->country_code,
                                                      FALSE);

      if (formatted_rec == NULL)
        return FALSE;

      if (msg->sr.to != NULL)
        {
          tmp = g_strconcat (msg->sr.to, ",",
                             formatted_rec, "/TYPE=PLMN", NULL);

          if (tmp == NULL)
            return FALSE;

          g_free (msg->sr.to);

          msg->sr.to = tmp;
        }
      else
        msg->sr.to = g_strdup_printf ("%s/TYPE=PLMN", formatted_rec);
    }
  return TRUE;
}


static gboolean
send_message_get_attachments (GVariant           *attachments,
                              struct mms_message *msg,
                              struct mms_service *service)
{
  gsize number_of_attachments;
  GVariantIter iter;
  g_autoptr(GVariant) single_attachment = NULL;
  int attach_total_size = 0;

  number_of_attachments = g_variant_iter_init (&iter, attachments);
  DBG ("number_of_attachments %ld", number_of_attachments);

  if (number_of_attachments > service->max_attachments)
    {
      DBG ("Error: Too many attachments!");
      return FALSE;
    }

  while ((single_attachment = g_variant_iter_next_value (&iter)))
    {
      g_autofree char *content_id = NULL;
      g_autofree char *mime_type = NULL;
      g_autofree char *mime_type_test = NULL;
      g_autofree char *file_path = NULL;
      struct mms_attachment *attach;
      void *ptr;

      g_variant_get (single_attachment, "(sss)", &content_id, &mime_type, &file_path);
      DBG ("Content ID: %s, MIME Type: %s, File Path: %s", content_id, mime_type, file_path);

      if (valid_content_type (mime_type) == FALSE)
        return FALSE;

      /*
       * Android does not recognise the text/vcard nor the
       * text/vcalendar MIME type, but does recognize the
       * test/x-vCard and text/x-vCalendar MIME types.
       * this is a fix to maintain compatibility with that.
       */

      mime_type_test = g_utf8_strdown (mime_type, -1);

      if (g_strcmp0 (mime_type_test, "text/vcard") == 0)
        {
          g_free (mime_type);
          mime_type = g_strdup ("text/x-vCard");
        }

      if (g_strcmp0 (mime_type_test, "text/calendar") == 0)
        {
          g_free (mime_type);
          mime_type = g_strdup ("text/x-vCalendar");
        }

      attach = g_try_new0 (struct mms_attachment, 1);
      if (attach == NULL)
        return FALSE;

      if (mmap_file (file_path, &ptr, &attach->length) == FALSE)
        return FALSE;

      attach_total_size = attach_total_size + attach->length;

      DBG ("Total attachment size: %d", attach_total_size);
      DBG ("Maximum Attachment Total Size (in bytes): %d", service->max_attach_total_size);

      if (attach_total_size > service->max_attach_total_size)
        {
          DBG ("Error: Total Attachment size too large!");
          return FALSE;
        }

      attach->data = ptr;

      attach->content_id = g_strdup (content_id);

      if (g_str_has_prefix (mime_type, "text/") == TRUE)
        attach->content_type = g_strconcat ("Content-Type: \"",
                                            mime_type,
                                            "\";charset=utf-8",
                                            NULL);
      else
        attach->content_type = g_strconcat ("Content-Type: \"",
                                            mime_type,
                                            "\"",
                                            NULL);

      msg->attachments = g_slist_append (msg->attachments, attach);
    }

  return TRUE;
}


static gboolean
send_message_get_args (GVariant           *parameters,
                       struct mms_message *msg,
                       struct mms_service *service)
{
  const char *smil_copy = NULL;
  g_autofree char *smil = NULL;
  g_autofree char *created_smil = NULL;
  g_autoptr(GVariant) recipients = NULL;
  g_autoptr(GVariant) options_container = NULL;
  g_autoptr(GVariant) options = NULL;
  g_autoptr(GVariant) attachments = NULL;

  recipients = g_variant_get_child_value (parameters, 0);

  if (send_message_get_recipients (recipients, msg, service) == FALSE)
    return FALSE;

  options_container = g_variant_get_child_value (parameters, 1);
  options = g_variant_get_child_value (options_container, 0);

  /* Only SMIL was sent as an option */
  if (g_variant_check_format_string (options, "s", FALSE))
    {
      smil_copy = g_variant_get_string (options, NULL);
      smil = g_strdup (smil_copy);
      msg->sr.subject = g_strdup ("");
    }
  else if (g_variant_check_format_string (options, "a{sv}", FALSE))
    {
      GVariantDict  dict;
      char *subject;
      gboolean delivery_report;
      g_variant_dict_init (&dict, options);
      if (g_variant_dict_lookup (&dict, "smil", "s", &smil_copy))
        smil = g_strdup (smil_copy);
      else
        smil = g_strdup ("");

      if (g_variant_dict_lookup (&dict, "DeliveryReport", "b", &delivery_report))
        msg->sr.dr = delivery_report;

      if (g_variant_dict_lookup (&dict, "Subject", "s", &subject))
        msg->sr.subject = subject;
      else
        msg->sr.subject = g_strdup ("");

      g_variant_dict_clear (&dict);
    }
  else {
      g_critical ("This is an invalid g_variant format!");
      return FALSE;
    }

  attachments = g_variant_get_child_value (parameters, 2);

  if (strlen (smil) > 0)
    created_smil = g_strdup (smil);
  else if (service->auto_create_smil)
    created_smil = mms_message_create_smil (attachments);
  else
    created_smil = g_strdup ("");

  if (strlen (created_smil) > 0)
    {
      struct mms_attachment *attach;
      DBG ("Attaching SMIL");
      attach = g_try_new0 (struct mms_attachment, 1);
      if (attach == NULL)
        return FALSE;

      attach->content_id = g_strdup (CONTENT_ID_SMIL);
      attach->content_type = g_strdup (CONTENT_TYPE_APP_SMIL);
      attach->length = strlen (created_smil) + 1;

      // Use safer g_memdup2() if glib is 2.68.1 or higher

                #if GLIB_CHECK_VERSION (2, 68, 1)
      attach->data = g_memdup2 (created_smil, attach->length);
                #else
      // https://discourse.gnome.org/t/port-your-module-from-g-memdup-to-g-memdup2-now/5538
      // g_memdup() has a flaw that will cause an overflow in over 32-bit numbers.
      // In the future, g_memdup2 will be needed, but it is not present
      // in glib 2.66. However, since SMIL should never be over 4294967295
      // (max 32 bit unsigned value) chars long, I can just add a check.

      if (attach->length > G_MAXUINT)
        {
          g_critical ("Possible integer overflow! Aborting");
          return FALSE;
        }
      attach->data = g_memdup (created_smil, attach->length);

                #endif

      msg->attachments = g_slist_append (msg->attachments, attach);

      msg->sr.content_type = g_strdup (CT_MULTIPART_RELATED);
    }
  else
    msg->sr.content_type = g_strdup (CT_MULTIPART_MIXED);

  if (send_message_get_attachments (attachments, msg, service) == FALSE)
    return FALSE;

  return TRUE;
}

static gchar *
resolve_host_systemd (const char         *host,
                      struct mms_service *service)
{
  DBG ("%s", __func__);

  gchar *host_ip = NULL;
  g_autoptr(GError) error = NULL;
  g_autoptr(GVariant) result = NULL;
  g_autoptr(GVariant) addresses = NULL;
  g_autoptr(GVariant) first_address = NULL;
  g_autoptr(GVariant) first_address_bytes = NULL;
  GVariantIter iter;
  guchar addr_byte;
  unsigned char buf[sizeof(struct in6_addr)];
  size_t i = 0;
  char str[INET6_ADDRSTRLEN] = { '\0' };

  result = g_dbus_proxy_call_sync (
    systemd_resolved_proxy,
    "ResolveHostname",
    g_variant_new ("(isit)", if_nametoindex (service->interface), host, AF_UNSPEC, 0),
    G_DBUS_CALL_FLAGS_NONE,
    -1,
    NULL,
    &error
    );

  if (result == NULL)
    {
      g_warning ("Error while resolving hostname with systemd-resolved: %s\n", error->message);
      return NULL;
    }

  addresses = g_variant_get_child_value (result, 0);
  first_address = g_variant_get_child_value (addresses, 0);

  int first_address_family;
  g_variant_get_child (first_address, 1, "i", &first_address_family);

  first_address_bytes = g_variant_get_child_value (first_address, 2);

  // iterate over GVariant byte array to convert to char array
  g_variant_iter_init (&iter, first_address_bytes);
  while (g_variant_iter_next (&iter, "y", &addr_byte) && (i < sizeof(struct in6_addr)))
    {
      buf[i] = addr_byte;
      i++;
    }

  inet_ntop (first_address_family, buf, str, INET6_ADDRSTRLEN);

  host_ip = g_strdup ((const gchar *) &str);

  DBG ("systemd-resolved host ip: %s", host_ip);

  return host_ip;
}

static void
resolve_callback (void    *arg,
                  int      status,
                  int      timeouts,
                  struct
                  hostent *host)
{
  char **host_ip = (char **)arg;
  gchar ip[64];

  if (!host || status != ARES_SUCCESS)
    {
      DBG ("Failed to resolve host: %s\n", ares_strerror (status));
      return;
    }

  inet_ntop (host->h_addrtype, host->h_addr_list[0], ip, sizeof(ip));

  *host_ip = g_strdup (ip);

  DBG ("Found IP for '%s': %s\n", host->h_name, *host_ip);
}

static void
resolve_wait (ares_channel channel)
{
  int nfds, count;
  fd_set readers, writers;
  struct timeval tv, *tvp;

  while (1)
    {
      FD_ZERO (&readers);
      FD_ZERO (&writers);
      nfds = ares_fds (channel, &readers, &writers);
      if (nfds == 0)
        break;
      tvp = ares_timeout (channel, NULL, &tv);
      count = select (nfds, &readers, &writers, NULL, tvp);
      if (count == -1)
        {
          g_critical ("Error waiting for c-ares read/write descriptors");
          break;
        }
      ares_process (channel, &readers, &writers);
    }
}

static gchar *
resolve_host_ares (const char         *host,
                   struct mms_service *service)
{
  DBG ("%s", __func__);

  ares_channel chan;
  int ares_return;
  gchar *host_ip = NULL;
  g_autofree gchar *nsall_csv = NULL;

  ares_return = ares_init (&chan);
  if (ares_return != ARES_SUCCESS)
    {
      g_warning ("Ares init failed: %s\n", ares_strerror (ares_return));
      goto ares_out;
    }

  /*
   * ares_set_local_dev () works without root
   * https://github.com/c-ares/c-ares/issues/405
   */
  DBG ("Binding resolver queries to interface %s", service->interface);
  ares_set_local_dev (chan, service->interface);

  if (service->resolvers_ipv6_csv && *service->resolvers_ipv6_csv &&
      service->resolvers_ipv4_csv && *service->resolvers_ipv4_csv)
    nsall_csv = g_strjoin (",", service->resolvers_ipv6_csv, service->resolvers_ipv4_csv, NULL);
  else if (service->resolvers_ipv6_csv && *service->resolvers_ipv6_csv)
    nsall_csv = g_strdup (service->resolvers_ipv6_csv);
  else if (service->resolvers_ipv4_csv && *service->resolvers_ipv4_csv)
    nsall_csv = g_strdup (service->resolvers_ipv4_csv);
  else {
      g_warning ("No active DNS Servers\n");
      goto ares_out;
    }

  DBG ("All Nameservers: %s", nsall_csv);

  ares_return = ares_set_servers_csv (chan, nsall_csv);
  if (ares_return != ARES_SUCCESS)
    g_warning ("Ares failed to set list of nameservers ('%s'), %s\n",
               nsall_csv, ares_strerror (ares_return));
  ares_gethostbyname (chan, host, AF_UNSPEC, resolve_callback,
                      (void *)&host_ip);
  resolve_wait (chan);
  if (host_ip == NULL)
    {
      g_warning ("Failed to resolve '%s'\n", host);
      goto ares_out;
    }

 ares_out:
  ares_destroy (chan);
  return host_ip;
}

static gchar *
resolve_host (struct mms_service *service,
              const gchar        *request_uri)
{
  g_autoptr(GUri) uri = NULL;
  g_autoptr(GUri) resolved_uri = NULL;
  g_autofree gchar *host_ip = NULL;
  gchar *new_uri = NULL;
  const gchar *host = NULL;

  if (service->proxy_active == TRUE)
    {
      DBG ("There is an active proxy! Not attempting to resolve host");
      new_uri = g_strdup (request_uri);
      DBG ("Using URI for request: %s", new_uri);
      return new_uri;
    }
  else
    DBG ("No active proxy");

  // Parse host from uri
  uri = g_uri_parse (request_uri, SOUP_HTTP_URI_FLAGS, NULL);
  if (uri == NULL)
    {
      g_warning ("Unable to init new uri: %s\n", request_uri);
      return NULL;
    }
  host = g_uri_get_host (uri);

  if (systemd_resolved_proxy != NULL && !service->force_c_ares)
    host_ip = resolve_host_systemd (host, service);
  else
    host_ip = resolve_host_ares (host, service);

  if (host_ip != NULL)
    {
      resolved_uri = soup_uri_copy (uri, SOUP_URI_HOST, host_ip, SOUP_URI_NONE);
      new_uri = g_uri_to_string (resolved_uri);
    }

  DBG ("Using URI for request: %s", new_uri);
  return new_uri;
}

static struct mms_request *
create_request (enum mms_request_type   type,
                mms_request_result_cb_t result_cb,
                gchar                  *location,
                struct mms_service     *service,
                struct mms_message     *msg)
{
  struct mms_request *request;

  request = g_try_new0 (struct mms_request, 1);
  if (request == NULL)
    return NULL;

  request->type = type;

  switch (request->type)
    {
    case MMS_REQUEST_TYPE_GET:
      request->data_path = g_strdup_printf ("%s%s", service->request_path,
                                            "receive.XXXXXX.mms");

      break;
    case MMS_REQUEST_TYPE_POST:
    case MMS_REQUEST_TYPE_POST_TMP:
      request->data_path = g_strdup_printf ("%s%s", service->request_path,
                                            "send.XXXXXX.mms");

      break;
    }

  request->fd = g_mkstemp_full (request->data_path,
                                O_WRONLY | O_CREAT | O_TRUNC,
                                S_IWUSR | S_IRUSR);
  if (request->fd < 0)
    {
      mms_request_destroy (request);

      return NULL;
    }

  request->result_cb = result_cb;

  request->location = g_strdup (location);

  request->service = service;

  request->msg = msg;

  request->status = 0;

  request->attempt = 0;

  request->data_to_post_path = NULL;

  return request;
}

static gboolean
bearer_setup_timeout (gpointer user_data)
{
  struct mms_service *service = user_data;

  DBG ("service %p", service);

  service->bearer_timeout = 0;

  service->bearer_setup = FALSE;

  return FALSE;
}

void
activate_bearer (struct mms_service *service)
{
  DBG ("service %p setup %d active %d", service, service->bearer_setup, service->bearer_active);

  /* Bearer is in the process of setting up already, do nothing */
  if (service->bearer_setup == TRUE)
    return;

  /* Bearer is active already, process request queue */
  if (service->bearer_active == TRUE)
    {
      process_request_queue (service);
      return;
    }

  /* Bearer is non-existant, do nothing */
  if (service->bearer_handler == NULL)
    return;

  DBG ("service %p waiting for %d seconds", service, BEARER_SETUP_TIMEOUT);

  service->bearer_setup = TRUE;

  /* If bearer takes too long to set up, have a timeout to let mmsd-tng try again*/
  service->bearer_timeout = g_timeout_add_seconds (BEARER_SETUP_TIMEOUT,
                                                   bearer_setup_timeout, service);

  service->bearer_handler (TRUE, service->bearer_data);
}

static inline char *
create_transaction_id (void)
{
  return g_strdup_printf ("%08X%s", transaction_id_start++,
                          "0123456789ABCDEF0123456789ABCDEF");
}

static gboolean
result_request_send_conf (struct mms_request *request)
{
  struct mms_message *msg;
  struct mms_service *service = request->service;
  const char *uuid;
  GKeyFile *meta;
  void *pdu;
  size_t len;
  g_autofree char *path = NULL;

  if (request->msg == NULL)
    return FALSE;

  uuid = request->msg->uuid;

  path = g_strdup_printf ("%s/%s/%s", MMS_PATH, service->identity, uuid);

  if (request->status != 200)
    return FALSE;

  msg = g_try_new0 (struct mms_message, 1);
  if (msg == NULL)
    return FALSE;

  if (mmap_file (request->data_path, &pdu, &len) == FALSE)
    {
      mms_message_free (msg);
      return FALSE;
    }

  if (mms_message_decode (pdu, len, msg) == FALSE)
    {
      g_critical ("Failed to decode pdu %s", request->data_path);

      munmap (pdu, len);

      mms_message_free (msg);

      return FALSE;
    }

  DBG ("response status : %d", msg->sc.rsp_status);

  munmap (pdu, len);

  unlink (request->data_path);

  meta = mms_store_meta_open (service->identity, uuid);
  if (meta == NULL)
    {
      mms_message_free (msg);

      return FALSE;
    }

  g_key_file_set_string (meta, "info", "state", "sent");
  g_key_file_set_string (meta, "info", "id", msg->sc.msgid);
  request->msg->sr.status = MMS_MESSAGE_STATUS_SENT;

  mms_message_free (msg);

  mms_store_meta_close (service->identity, uuid, meta, TRUE);

  emit_msg_status_changed (path, "sent");

  return TRUE;
}

static void
append_message_entry (char                     *path,
                      const struct mms_service *service,
                      struct mms_message       *msg,
                      GVariantBuilder          *message_builder)
{
  g_autoptr(GError) error = NULL;

  DBG ("Message Added %p", msg);

  append_message (msg->path, service, msg, message_builder);
}

static gboolean
mms_attachment_is_smil (const struct mms_attachment *part)
{
  if (g_str_match_string ("application/smil", part->content_type, TRUE))
    return TRUE;

  return FALSE;
}

static void
release_data (gpointer data,
              gpointer user_data)
{
  struct mms_attachment *attach = data;

  if (mms_attachment_is_smil (attach))
    g_free (attach->data);
  else
    munmap (attach->data, attach->length);
}

static void
release_attachement_data (GSList *attach)
{
  if (attach != NULL)
    g_slist_foreach (attach, release_data, NULL);
}


static void
destroy_message (gpointer data)
{
  struct mms_message *mms = data;

  mms_message_free (mms);
}

struct mms_service *
mms_service_create (void)
{
  struct mms_service *service;

  service = g_try_new0 (struct mms_service, 1);
  if (service == NULL)
    return NULL;

  service->refcount = 1;

  service->request_queue = g_queue_new ();
  if (service->request_queue == NULL)
    {
      g_free (service);
      return NULL;
    }

  service->web = NULL;
  service->current_request_msg = NULL;
  service->cancel_current_msg = NULL;

  /*
   * The key in the hash table is the MMS path, which is destroyed
   * in destroy_message(). Thus, no need to have a key_destroy_func
   */
  service->messages = g_hash_table_new_full (g_str_hash, g_str_equal,
                                             NULL, destroy_message);
  if (service->messages == NULL)
    {
      g_queue_free (service->request_queue);
      g_free (service);
      return NULL;
    }

  DBG ("service %p", service);

  return service;
}

struct mms_service *
mms_service_ref (struct mms_service *service)
{
  if (service == NULL)
    return NULL;

  g_atomic_int_inc (&service->refcount);

  return service;
}

static void
mms_message_dbus_unregister (unsigned int message_registration_id)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  g_dbus_connection_unregister_object (connection,
                                       message_registration_id);
}

static void
dbus_unregister_message (gpointer key,
                         gpointer value,
                         gpointer user_data)
{
  //struct mms_service *service = user_data;
  struct mms_message *msg = value;

  mms_message_dbus_unregister (msg->message_registration_id);
}

static void
destroy_message_table (struct mms_service *service)
{
  if (service->messages == NULL)
    return;

  g_hash_table_foreach (service->messages, dbus_unregister_message, service);

  g_hash_table_destroy (service->messages);
  service->messages = NULL;
}

void
mms_service_unref (struct mms_service *service)
{
  struct mms_request *request;

  if (service == NULL)
    return;

  if (g_atomic_int_dec_and_test (&service->refcount) == FALSE)
    return;

  g_clear_object (&service->web);
  g_clear_pointer (&service->resolvers_ipv4_csv, g_free);
  g_clear_pointer (&service->resolvers_ipv6_csv, g_free);
  g_clear_object (&service->cancel_current_msg);

  while ((request = g_queue_pop_head (service->request_queue)))
    mms_request_destroy (request);

  g_queue_free (service->request_queue);

  destroy_message_table (service);

  g_free (service->mmsc);

  g_free (service->identity);
  g_free (service->path);
  g_free (service);
}

static void
append_properties (GVariantBuilder    *service_builder,
                   struct mms_service *service)
{
  g_variant_builder_add (service_builder, "o", service->path);

  g_variant_builder_open (service_builder, G_VARIANT_TYPE ("a{sv}"));

  g_variant_builder_add_parsed (service_builder,
                                "{'Identity', <%s>}",
                                service->identity);

  g_variant_builder_close (service_builder);
}


static void
emit_service_added (struct mms_service *service)
{
  GDBusConnection         *connection = mms_dbus_get_connection ();
  GVariantBuilder          service_builder;
  GVariant                *service_added;
  g_autoptr(GError) error = NULL;

  g_variant_builder_init (&service_builder, G_VARIANT_TYPE ("(oa{sv})"));

  DBG ("Service Added %p", service);

  append_properties (&service_builder, service);

  service_added = g_variant_builder_end (&service_builder);

  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 MMS_PATH,
                                 MMS_MANAGER_INTERFACE,
                                 "ServiceAdded",
                                 service_added,
                                 &error);

  if (error != NULL)
    {
      g_warning ("Error in Proxy call: %s\n", error->message);
      error = NULL;
    }
}


static void
emit_service_removed (struct mms_service *service)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  GVariant *servicepathvariant;
  g_autoptr(GError) error = NULL;

  servicepathvariant = g_variant_new ("(o)", service->path);

  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 MMS_PATH,
                                 MMS_MANAGER_INTERFACE,
                                 "ServiceRemoved",
                                 servicepathvariant,
                                 &error);

  if (error != NULL)
    g_warning ("Error in Proxy call: %s\n", error->message);
}


static gboolean
load_message_from_store (const char         *service_id,
                         const char         *uuid,
                         struct mms_message *msg)
{
  GKeyFile *meta;
  g_autofree char *state = NULL;
  gboolean read_status;
  g_autofree char *data_path = NULL;
  g_autofree char *datestr = NULL;
  gboolean success = FALSE;
  gboolean tainted = FALSE;
  void *pdu;
  size_t len;
  struct tm tm;

  meta = mms_store_meta_open (service_id, uuid);
  if (meta == NULL)
    return FALSE;

  state = g_key_file_get_string (meta, "info", "state", NULL);
  if (state == NULL)
    goto out;

  read_status = g_key_file_get_boolean (meta, "info", "read", NULL);

  datestr = g_key_file_get_string (meta, "info", "date", NULL);
  if (datestr != NULL)
    strptime (datestr, "%Y-%m-%dT%H:%M:%S%z", &tm);
  else {
      time_t date;
      const char *datestrconst = NULL;
      DBG ("src/service.c:load_message_from_store() There is no date stamp!");
      DBG ("src/service.c:load_message_from_store() Setting time to now.");
      time (&date);
      datestrconst = time_to_str (&date);
      strptime (datestrconst, "%Y-%m-%dT%H:%M:%S%z", &tm);
      g_free (datestr);
      datestr = g_strdup (datestrconst);
      DBG ("src/service.c:load_message_from_store() Time is %s.", datestr);
      g_key_file_set_string (meta, "info", "date", datestr);
    }

  data_path = mms_store_get_path (service_id, uuid);
  if (data_path == NULL)
    goto out;

  if (mmap_file (data_path, &pdu, &len) == FALSE)
    goto out;

  if (mms_message_decode (pdu, len, msg) == FALSE)
    {
      g_critical ("Failed to decode %s", data_path);
      munmap (pdu, len);
      tainted = TRUE;
      goto out;
    }

  munmap (pdu, len);

  msg->uuid = g_strdup (uuid);

  if (strcmp (state, "received") == 0 && msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF)
    {
      msg->rc.datestamp = g_strdup (datestr);
      msg->rc.date = mktime (&tm);
      if (read_status == TRUE)
        msg->rc.status = MMS_MESSAGE_STATUS_READ;
      else
        msg->rc.status = MMS_MESSAGE_STATUS_RECEIVED;
    }
  else if (strcmp (state, "downloaded") == 0 && msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF)
    {
      msg->rc.status = MMS_MESSAGE_STATUS_DOWNLOADED;
      if (msg->transaction_id == NULL)
        {
          g_critical ("Downloaded Message has no transaction ID!");
          msg->transaction_id = g_strdup ("");
        }
    }
  else if (strcmp (state, "sent") == 0 && msg->type == MMS_MESSAGE_TYPE_SEND_REQ)
    {
      msg->sr.datestamp = g_strdup (datestr);
      msg->sr.date = mktime (&tm);
      msg->sr.status = MMS_MESSAGE_STATUS_SENT;
      msg->sr.dr = g_key_file_get_boolean (meta, "info", "delivery_report", NULL);
      if (msg->sr.dr)
        msg->sr.delivery_recipients = g_key_file_get_string (meta, "info", "delivery_recipients", NULL);
      else
        msg->sr.delivery_recipients = NULL;
    }
  else if (strcmp (state, "draft") == 0 && msg->type == MMS_MESSAGE_TYPE_SEND_REQ)
    {
      msg->sr.datestamp = g_strdup (datestr);
      msg->sr.date = mktime (&tm);
      msg->sr.status = MMS_MESSAGE_STATUS_DRAFT;
      msg->sr.dr = g_key_file_get_boolean (meta, "info", "delivery_report", NULL);
      if (msg->sr.dr)
        msg->sr.delivery_recipients = g_key_file_get_string (meta, "info", "delivery_recipients", NULL);
      else
        msg->sr.delivery_recipients = NULL;
    }
  else if (strcmp (state, "delivered") == 0 && msg->type == MMS_MESSAGE_TYPE_SEND_REQ)
    {
      msg->sr.datestamp = g_strdup (datestr);
      msg->sr.date = mktime (&tm);
      msg->sr.status = MMS_MESSAGE_STATUS_DELIVERED;
      msg->sr.dr = g_key_file_get_boolean (meta, "info", "delivery_report", NULL);
      if (msg->sr.dr)
        msg->sr.delivery_recipients = g_key_file_get_string (meta, "info", "delivery_recipients", NULL);
      else
        msg->sr.delivery_recipients = NULL;
    }
  else if (msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND)
    {
      g_autofree char *expirystr = NULL;
      const char *expirystr_new = NULL;
      expirystr = g_key_file_get_string (meta, "info", "expiration", NULL);
      if (expirystr)
        {
          struct tm tm2;
          strptime (expirystr, "%Y-%m-%dT%H:%M:%S%z", &tm2);
          msg->ni.expiry = mktime (&tm2);
        }
      else {
          expirystr_new = time_to_str (&msg->ni.expiry);
          g_key_file_set_string (meta, "info", "expiration", expirystr_new);
        }
    }
  else if (msg->type != MMS_MESSAGE_TYPE_DELIVERY_IND)
    goto out;

  success = TRUE;

 out:
  mms_store_meta_close (service_id, uuid, meta, TRUE);

  if (tainted == TRUE)
    mms_store_remove (service_id, uuid);

  return success;
}


static struct mms_request *
build_notify_resp_ind (struct mms_service            *service,
                       enum mms_message_notify_status status,
                       struct mms_message            *rc_msg)
{
  struct mms_message *ni_msg;
  struct mms_request *notify_request;
  gboolean result;

  ni_msg = g_try_new0 (struct mms_message, 1);
  if (ni_msg == NULL)
    return NULL;

  ni_msg->type = MMS_MESSAGE_TYPE_NOTIFYRESP_IND;
  ni_msg->version = MMS_MESSAGE_VERSION_1_3;
  ni_msg->transaction_id = g_strdup (rc_msg->transaction_id);
  ni_msg->nri.notify_status = status;

  notify_request = create_request (MMS_REQUEST_TYPE_POST_TMP,
                                   result_request_notify_resp,
                                   NULL, service, rc_msg);

  if (notify_request == NULL)
    {
      mms_message_free (ni_msg);
      return NULL;
    }

  if (status == MMS_MESSAGE_NOTIFY_STATUS_UNRECOGNISED)
    notify_request->msg = NULL;

  /*
   * We are building an MMS awknowledging that we just downloaded the one
   * from transation ID.
   */
  result = mms_message_encode (ni_msg, notify_request->fd);

  close (notify_request->fd);

  notify_request->fd = -1;

  mms_message_free (ni_msg);

  if (result == FALSE)
    {
      unlink (notify_request->data_path);
      mms_request_destroy (notify_request);

      return NULL;
    }

  return notify_request;
}

static gboolean
process_delivery_ind (struct mms_service *service,
                      struct mms_message *msg)
{
  g_autofree char *formatted_to = NULL;
  const char *delivery_status;
  GHashTableIter           table_iter;
  gpointer                 key, value;
  gboolean                 update_successful = FALSE;

  DBG ("At process_delivery_ind");

  mms_address_to_string (msg->di.to);
  formatted_to = phone_utils_format_number_e164 (msg->di.to,
                                                 service->country_code,
                                                 TRUE);
  g_message ("Msg ID: %s\n", msg->di.msgid);
  //g_message("To: %s\n", formatted_to);

  switch (msg->di.dr_status)
    {
    case MMS_MESSAGE_DELIVERY_STATUS_EXPIRED:
      delivery_status = "expired";
      break;
    case MMS_MESSAGE_DELIVERY_STATUS_RETRIEVED:
      delivery_status = "retrieved";
      break;
    case MMS_MESSAGE_DELIVERY_STATUS_REJECTED:
      delivery_status = "rejected";
      break;
    case MMS_MESSAGE_DELIVERY_STATUS_DEFERRED:
      delivery_status = "deferred";
      break;
    case MMS_MESSAGE_DELIVERY_STATUS_UNRECOGNISED:
      delivery_status = "unrecognized";
      break;
    case MMS_MESSAGE_DELIVERY_STATUS_INDETERMINATE:
      delivery_status = "indeterminate";
      break;
    case MMS_MESSAGE_DELIVERY_STATUS_FORWARDED:
      delivery_status = "forwarded";
      break;
    case MMS_MESSAGE_DELIVERY_STATUS_UNREACHABLE:
      delivery_status = "unreachable";
      break;
    default:
      delivery_status = "error";
      break;
    }
  g_message ("Delivery Report status: %d, %s \n", msg->di.dr_status, delivery_status);

  g_hash_table_iter_init (&table_iter, service->messages);
  while (g_hash_table_iter_next (&table_iter, &key, &value))
    {
      GKeyFile *meta;
      struct mms_message *delivery_msg = value;
      g_autofree char *msgid = NULL;

      meta = mms_store_meta_open (service->identity, delivery_msg->uuid);
      msgid = g_key_file_get_string (meta, "info", "id", NULL);
      if (msgid != NULL)
        //DBG("On message %s: %s", delivery_msg->uuid, msgid);
        if (strcmp (msg->di.msgid, msgid) == 0)
          {
            g_autofree char *path = g_strdup_printf ("%s/%s/%s", MMS_PATH, service->identity, delivery_msg->uuid);
            g_autofree char *delivery_update = g_strdup_printf ("delivery_update,%s=%s", formatted_to, delivery_status);
            int delivery_number, total_delivery_number;

            g_key_file_set_string (meta,
                                   "delivery_status",
                                   formatted_to,
                                   delivery_status);
            delivery_number = g_key_file_get_integer (meta,
                                                      "delivery_status",
                                                      "delivery_number_complete",
                                                      NULL);
            delivery_number = delivery_number + 1;
            g_key_file_set_integer (meta,
                                    "delivery_status",
                                    "delivery_number_complete",
                                    delivery_number);
            total_delivery_number = g_key_file_get_integer (meta,
                                                            "info",
                                                            "delivery_recipients_number",
                                                            NULL);
            //DBG("delivery_update: %s", delivery_update);
            emit_msg_status_changed (path, delivery_update);
            if (delivery_number == total_delivery_number)
              {
                DBG ("All Recipients delivered (or had error)");

                delivery_msg->sr.status = MMS_MESSAGE_STATUS_DELIVERED;
                g_key_file_set_string (meta, "info", "state", "delivered");
                emit_msg_status_changed (path, "delivered");
              }
            mms_store_meta_close (service->identity, delivery_msg->uuid, meta, TRUE);
            update_successful = TRUE;
            break;
          }
      mms_store_meta_close (service->identity, delivery_msg->uuid, meta, FALSE);
    }
  return update_successful;
}

gboolean
check_message_expiration (struct mms_service *service,
                          struct mms_message *msg)
{
  time_t now;
  if (msg->type != MMS_MESSAGE_TYPE_NOTIFICATION_IND)
    {
      g_critical ("This is not a notification! This function won't do anything");
      return TRUE;
    }

  now = time (NULL);

  if (now >= msg->ni.expiry)
    {
      DBG ("Message is expired!");
      mms_message_register (service, msg);
      emit_message_added (service, msg);

      service->notification_ind = service->notification_ind - 1;
      DBG ("Number of notifications: %i", service->notification_ind);

      return FALSE;
    }
  return TRUE;
}

static void
process_message_on_start (struct mms_service *service,
                          const char         *uuid)
{
  struct mms_message *msg;
  struct mms_request *request;
  const char *service_id = service->identity;

  msg = g_try_new0 (struct mms_message, 1);
  if (msg == NULL)
    return;

  if (load_message_from_store (service_id, uuid, msg) == FALSE)
    {
      DBG ("Failed to load_message_from_store() from MMS with uuid %s", uuid);
      mms_message_free (msg);
      return;
    }

  if (msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND)
    {
      request = create_request (MMS_REQUEST_TYPE_GET,
                                result_request_retrieve_conf,
                                msg->ni.location, service, msg);
      if (request == NULL)
        {
          mms_message_free (msg);
          return;
        }

      service->notification_ind = service->notification_ind + 1;
      DBG ("Number of notifications: %i", service->notification_ind);
    }
  else if (msg->type == MMS_MESSAGE_TYPE_SEND_REQ)
    {
      if (msg->sr.status == MMS_MESSAGE_STATUS_DRAFT)
        {
          request = create_request (MMS_REQUEST_TYPE_POST,
                                    result_request_send_conf, NULL, service, NULL);
          if (request == NULL)
            goto register_sr;

          close (request->fd);
          request->fd = -1;

          /*
           * The data_path is for encoding the message to send,
           * but since we already built it, just delete it.
           */
          unlink (request->data_path);
          g_free (request->data_path);
          request->data_path = mms_store_get_path (service_id,
                                                   uuid);

          request->msg = msg;
        }
      else
        request = NULL;
 register_sr:
      mms_message_register (service, msg);
    }
  else if (msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF)
    {
      if (msg->rc.status == MMS_MESSAGE_STATUS_DOWNLOADED)
        {
          request = build_notify_resp_ind (service,
                                           MMS_MESSAGE_NOTIFY_STATUS_RETRIEVED,
                                           msg);
          if (request == NULL)
            mms_message_free (msg);
        }
      else {
          request = NULL;
          mms_message_register (service, msg);
        }
    }
  else if (msg->type == MMS_MESSAGE_TYPE_DELIVERY_IND)
    {
      if (process_delivery_ind (service, msg) == FALSE)
        DBG ("There was an issue finding the message to go with the delivery Notification. Was it deleted?");
      DBG ("Deleting Delivery Notification");
      mms_store_remove (service->identity, msg->uuid);
      mms_message_free (msg);
      request = NULL;
    }
  else
    request = NULL;

  if (request != NULL)
    {
      g_queue_push_tail (service->request_queue, request);
      activate_bearer (service);
    }
}

static void
remove_stale_requests (struct mms_service *service)
{
  GDir *dir;
  const char *file;
  g_autofree char *service_path = NULL;

  dir = g_dir_open (service->request_path, 0, NULL);
  if (dir == NULL)
    return;

  while ((file = g_dir_read_name (dir)) != NULL)
    {
      const size_t suffix_len = 4;
      g_autofree char *filepath = g_strdup_printf ("%s%s", service->request_path, file);

      if (g_str_has_suffix (file, ".mms") == FALSE)
        continue;

      if (strlen (file) - suffix_len == 0)
        continue;

      unlink (filepath);
    }

  g_dir_close (dir);
}

static void
load_messages (struct mms_service *service)
{
  GDir *dir;
  const char *file;
  const char *homedir;
  g_autofree char *service_path = NULL;

  homedir = g_get_home_dir ();
  if (homedir == NULL)
    return;

  service_path = g_strdup_printf ("%s/.mms/%s/", homedir,
                                  service->identity);

  dir = g_dir_open (service_path, 0, NULL);
  if (dir == NULL)
    return;

  while ((file = g_dir_read_name (dir)) != NULL)
    {
      const size_t suffix_len = 7;
      g_autofree char *uuid = NULL;

      if (g_str_has_suffix (file, ".status") == FALSE)
        continue;

      if (strlen (file) - suffix_len == 0)
        continue;

      uuid = g_strndup (file, strlen (file) - suffix_len);

      process_message_on_start (service, uuid);
    }

  g_dir_close (dir);
}

GKeyFile *
mms_service_get_keyfile (struct mms_service *service)
{
  return service->settings;
}

int
mms_service_register (struct mms_service *service)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  GDBusNodeInfo   *introspection_data = mms_dbus_get_introspection_data ();
  g_autoptr(GError)  error = NULL;

  DBG ("service %p", service);

  if (service == NULL)
    return -EINVAL;

  if (service->identity == NULL)
    return -EINVAL;

  if (service->path != NULL)
    return -EBUSY;

  service->path = g_strdup_printf ("%s/%s", MMS_PATH, service->identity);
  if (service->path == NULL)
    return -ENOMEM;

  service_registration_id = g_dbus_connection_register_object (connection,
                                                               service->path,
                                                               introspection_data->interfaces[0],
                                                               &interface_vtable_service,
                                                               service,         // user_data
                                                               NULL,    // user_data_free_func
                                                               &error);   // GError**

  if (error)
    g_critical ("Error Registering Service: %s", error->message);

  g_assert (service_registration_id > 0);

  service_list = g_list_append (service_list, service);

  emit_service_added (service);

  mms_load_settings (service);

  service->request_path = g_strdup_printf ("%s/mmstng/%s/",
                                           g_get_user_cache_dir (),
                                           service->identity);

  if (g_mkdir_with_parents (service->request_path, 0777) == -1)
    g_critical ("Could not make mms request path!");

  remove_stale_requests (service);

  load_messages (service);

  ares_library_init (ARES_LIB_INIT_ALL);

  return 0;
}

int
mms_service_unregister (struct mms_service *service)
{
  GDBusConnection *connection = mms_dbus_get_connection ();

  DBG ("service %p", service);

  if (service == NULL)
    return -EINVAL;

  if (service->path == NULL)
    return -EINVAL;

  if (service->messages != NULL)
    destroy_message_table (service);

  if (service->settings != NULL)
    {
      g_key_file_set_boolean (service->settings, SETTINGS_GROUP,
                              "UseDeliveryReports",
                              service->use_delivery_reports);

      mms_settings_close (service->identity, SETTINGS_STORE,
                          service->settings, TRUE);

      service->settings = NULL;
    }

  //Disconnect the service dbus interface
  g_dbus_connection_unregister_object (connection,
                                       service_registration_id);

  service_list = g_list_remove (service_list, service);

  emit_service_removed (service);

  g_clear_pointer (&service->country_code, g_free);
  g_clear_pointer (&service->path, g_free);
  g_clear_pointer (&service->interface, g_free);
  g_clear_pointer (&service->own_number, g_free);
  g_clear_pointer (&service->apn, g_free);
  g_clear_pointer (&service->request_path, g_free);

  ares_library_cleanup ();

  return 0;
}

static const char *
time_to_str (const time_t *t)
{
  static char buf[128];
  struct tm tm;

  strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime_r (t, &tm));
  buf[127] = '\0';
  DBG ("Time %ld, Human Format %s", *t, buf);
  return buf;
}


static void
append_attachment_properties (struct mms_attachment *part,
                              GVariantBuilder       *attachment_builder,
                              const char            *path)
{
  g_autofree char *temp_content_id = NULL;
  g_autofree char *temp_content_type = NULL;
  if (strlen (part->content_id) == 0)
    {
      // Some MMSes do not encode a filename for the attachment. If this happens,
      // the value of part->content_id will be empty. Rather than make
      // this a chat applicaton's problem, I am adding a random filename here.
      DBG ("Content ID is empty, manually adding random id...");
      temp_content_id = g_uuid_string_random ();
      DBG ("fixed content-id: %s\n", temp_content_id);
    }
  else
    temp_content_id = g_strdup (part->content_id);
  // send_message_get_attachments () adds stuff to content type.
  // I am filtering them out
  if (strstr (part->content_type, "Content-Type: "))
    {
      DBG ("Need to fix content type %s", part->content_type);
      g_auto(GStrv) tokens = NULL;
      tokens = g_strsplit (part->content_type, "\"", -1);
      temp_content_type = g_strdup (tokens[1]);
      DBG ("fixed content-type: %s\n", temp_content_type);
    }
  else
    temp_content_type = g_strdup (part->content_type);


  g_variant_builder_add (attachment_builder, "(ssstt)",
                         temp_content_id,
                         temp_content_type,
                         path,
                         part->offset,
                         part->length);
}


static void
append_smil (GVariantBuilder             *message_builder,
             const char                  *path,
             const struct mms_attachment *part)
{
  const char *to_codeset = "utf-8";
  char *from_codeset;
  void *data;
  size_t len;
  g_autofree char *smil = NULL;

  if (mmap_file (path, &data, &len) == FALSE)
    return;

  from_codeset = mms_content_type_get_param_value (part->content_type,
                                                   "charset");
  if (from_codeset != NULL)
    {
      smil = g_convert ((const char *) data + part->offset,
                        part->length, to_codeset, from_codeset,
                        NULL, NULL, NULL);

      g_free (from_codeset);
    }
  else
    smil = g_convert ((const char *) data + part->offset,
                      part->length, to_codeset, "us-ascii",
                      NULL, NULL, NULL);

  munmap (data, len);

  if (smil == NULL)
    {
      g_critical ("Failed to convert smil attachment\n");
      return;
    }

  g_variant_builder_add_parsed (message_builder,
                                "{'Smil', <%s>}",
                                smil);
}


static inline void
check_null_content_id (struct mms_attachment *attachment)
{
  if (attachment->content_id == NULL)
    attachment->content_id = g_strdup ("");
}


static gboolean
check_empty_subject (char *subject)
{
  for (guint i = 0; i < G_N_ELEMENTS (empty_subjects); i++)
    if (g_strcmp0 (empty_subjects[i], subject) == 0)
      return TRUE;
  return FALSE;
}


static void
append_msg_subject (GVariantBuilder *message_builder,
                    char            *subject)
{
  char *subj = check_empty_subject (subject) ? "" : subject;
  g_variant_builder_add_parsed (message_builder,
                                "{'Subject', <%s>}",
                                subj);
}


static void
append_msg_attachments (GVariantBuilder    *message_builder,
                        const char         *path,
                        struct mms_message *msg)
{
  GSList *part;
  struct mms_attachment *smil;
  GVariantBuilder attachment_builder;
  GVariant *attachments;

  g_variant_builder_init (&attachment_builder, G_VARIANT_TYPE_ARRAY);

  smil = NULL;
  for (part = msg->attachments; part != NULL;
       part = g_slist_next (part))
    {
      check_null_content_id (part->data);

      if (mms_attachment_is_smil (part->data))
        smil = part->data;
      else
        append_attachment_properties (part->data,
                                      &attachment_builder,
                                      path);
    }

  attachments = g_variant_builder_end (&attachment_builder);

  g_variant_builder_add (message_builder, "{sv}", "Attachments", attachments);

  if (smil == NULL)
    {
      DBG ("No Smil!");
      return;
    }

  DBG ("Attaching Smil!");
  switch (msg->type)
    {
    case MMS_MESSAGE_TYPE_SEND_REQ:
      append_smil (message_builder, path, smil);
      return;
    case MMS_MESSAGE_TYPE_SEND_CONF:
      return;
    case MMS_MESSAGE_TYPE_NOTIFICATION_IND:
      return;
    case MMS_MESSAGE_TYPE_NOTIFYRESP_IND:
      return;
    case MMS_MESSAGE_TYPE_RETRIEVE_CONF:
      append_smil (message_builder, path, smil);
      break;
    case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND:
      return;
    case MMS_MESSAGE_TYPE_DELIVERY_IND:
      return;
    }
}


static void
append_msg_recipients (GVariantBuilder          *message_builder,
                       struct mms_message       *msg,
                       const struct mms_service *service)
{
  g_auto(GStrv) tokens = NULL;
  unsigned int i;
  const char *rcpt;
  GVariantBuilder recipient_builder;
  GVariant *recipients;

  g_variant_builder_init (&recipient_builder, G_VARIANT_TYPE_ARRAY);

  switch (msg->type)
    {
    case MMS_MESSAGE_TYPE_SEND_REQ:
      tokens = g_strsplit (msg->sr.to, ",", -1);
      break;
    case MMS_MESSAGE_TYPE_SEND_CONF:
      return;
    case MMS_MESSAGE_TYPE_NOTIFICATION_IND:
      return;
    case MMS_MESSAGE_TYPE_NOTIFYRESP_IND:
      return;
    case MMS_MESSAGE_TYPE_RETRIEVE_CONF:
      tokens = g_strsplit (msg->rc.to, ",", -1);
      break;
    case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND:
      return;
    case MMS_MESSAGE_TYPE_DELIVERY_IND:
      return;
    }

  for (i = 0; tokens[i] != NULL; i++)
    {
      g_autofree char *formatted_rcpt = NULL;

      rcpt = mms_address_to_string (tokens[i]);
      //DBG("rcpt=%s", rcpt);
      formatted_rcpt = phone_utils_format_number_e164 (rcpt,
                                                       service->country_code,
                                                       TRUE);
      //DBG("Formatted rcpt=%s", formatted_rcpt);
      g_variant_builder_add (&recipient_builder, "s", formatted_rcpt);
    }

  recipients = g_variant_builder_end (&recipient_builder);

  g_variant_builder_add (message_builder, "{sv}", "Recipients", recipients);
}

static void
append_ni_msg_properties (GVariantBuilder          *message_builder,
                          struct mms_message       *msg,
                          const struct mms_service *service)
{
  const char *expire_time = time_to_str (&msg->ni.expiry);
  const char *status = "expired";
  const char *from_prefix;
  g_autofree char *from = NULL;
  time_t now;

  if (msg->type != MMS_MESSAGE_TYPE_NOTIFICATION_IND)
    {
      g_critical ("This is not a notification! This function won't do anything");
      return;
    }

  now = time (NULL);

  if (now < msg->ni.expiry)
    {
      DBG ("This notification is not expired!");
      return;
    }

  g_variant_builder_add_parsed (message_builder,
                                "{'Status', <%s>}",
                                status);

  g_variant_builder_add_parsed (message_builder,
                                "{'Expire', <%s>}",
                                expire_time);

  append_msg_subject (message_builder, msg->ni.subject);

  from = g_strdup (msg->ni.from);

  if (from != NULL)
    {
      g_autofree char *formatted_from = NULL;

      from_prefix = mms_address_to_string (from);
      formatted_from = phone_utils_format_number_e164 (from_prefix,
                                                       service->country_code,
                                                       TRUE);
      g_variant_builder_add_parsed (message_builder,
                                    "{'Sender', <%s>}",
                                    formatted_from);
    }

  if (service->own_number != NULL)
    g_variant_builder_add_parsed (message_builder,
                                  "{'Modem Number', <%s>}",
                                  service->own_number);
  else
    g_variant_builder_add_parsed (message_builder,
                                  "{'Modem Number', <''>}");
}

static void
append_rc_msg_properties (GVariantBuilder          *message_builder,
                          struct mms_message       *msg,
                          const struct mms_service *service)
{
  const char *date = time_to_str (&msg->rc.date);
  const char *status = "received";
  const char *from_prefix;
  g_autofree char *from = NULL;

  g_variant_builder_add_parsed (message_builder,
                                "{'Status', <%s>}",
                                status);

  g_variant_builder_add_parsed (message_builder,
                                "{'Date', <%s>}",
                                date);

  append_msg_subject (message_builder, msg->rc.subject);

  from = g_strdup (msg->rc.from);

  if (from != NULL)
    {
      g_autofree char *formatted_from = NULL;

      from_prefix = mms_address_to_string (from);
      formatted_from = phone_utils_format_number_e164 (from_prefix,
                                                       service->country_code,
                                                       TRUE);
      g_variant_builder_add_parsed (message_builder,
                                    "{'Sender', <%s>}",
                                    formatted_from);
    }

  if (msg->rc.to != NULL)
    append_msg_recipients (message_builder, msg, service);

  if (service->own_number != NULL)
    g_variant_builder_add_parsed (message_builder,
                                  "{'Modem Number', <%s>}",
                                  service->own_number);
  else
    g_variant_builder_add_parsed (message_builder,
                                  "{'Modem Number', <''>}");
}


static void
append_sr_msg_properties (GVariantBuilder          *message_builder,
                          struct mms_message       *msg,
                          const struct mms_service *service)
{
  const char *status = mms_message_status_get_string (msg->sr.status);

  g_variant_builder_add_parsed (message_builder,
                                "{'Status', <%s>}",
                                status);
  g_variant_builder_add_parsed (message_builder,
                                "{'Date', <%s>}",
                                msg->sr.datestamp);

  append_msg_subject (message_builder, msg->sr.subject);

  g_variant_builder_add_parsed (message_builder,
                                "{'Delivery Report', <%b>}",
                                msg->sr.dr);
  if (msg->sr.dr)
    {
      char **tos;
      tos = g_strsplit (msg->sr.delivery_recipients, ",", 0);
      GString *to_concat = g_string_new (NULL);
      GKeyFile *meta = mms_store_meta_open (service->identity, msg->uuid);

      for (int i = 0; tos[i] != NULL; i++)
        {
          g_autofree char *delivery_status = NULL;
          delivery_status = g_key_file_get_string (meta, "delivery_status", tos[i], NULL);

          to_concat = g_string_append (to_concat, tos[i]);
          to_concat = g_string_append (to_concat, "=");
          to_concat = g_string_append (to_concat, delivery_status);
          to_concat = g_string_append (to_concat, ",");
        }
      to_concat = g_string_truncate (to_concat, (strlen (to_concat->str) - 1));
      g_variant_builder_add_parsed (message_builder,
                                    "{'Delivery Status', <%s>}",
                                    to_concat->str);

      g_string_free (to_concat, TRUE);
      g_strfreev (tos);
      mms_store_meta_close (service->identity, msg->uuid, meta, FALSE);
    }
  if (service->own_number != NULL)
    g_variant_builder_add_parsed (message_builder,
                                  "{'Modem Number', <%s>}",
                                  service->own_number);
  else
    g_variant_builder_add_parsed (message_builder,
                                  "{'Modem Number', <''>}");


  if (msg->sr.to != NULL)
    append_msg_recipients (message_builder, msg, service);
}

static void
append_message (const char               *path,
                const struct mms_service *service,
                struct mms_message       *msg,
                GVariantBuilder          *message_builder)
{
  g_variant_builder_add (message_builder, "o", msg->path);

  g_variant_builder_open (message_builder, G_VARIANT_TYPE ("a{sv}"));

  switch (msg->type)
    {
    case MMS_MESSAGE_TYPE_SEND_REQ:
      append_sr_msg_properties (message_builder, msg, service);
      break;
    case MMS_MESSAGE_TYPE_SEND_CONF:
      break;
    case MMS_MESSAGE_TYPE_NOTIFICATION_IND:
      append_ni_msg_properties (message_builder, msg, service);
      break;
    case MMS_MESSAGE_TYPE_NOTIFYRESP_IND:
      break;
    case MMS_MESSAGE_TYPE_RETRIEVE_CONF:
      append_rc_msg_properties (message_builder, msg, service);
      break;
    case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND:
      break;
    case MMS_MESSAGE_TYPE_DELIVERY_IND:
      break;
    }

  if (msg->attachments != NULL)
    {
      char *pdu_path = mms_store_get_path (service->identity,
                                           msg->uuid);
      DBG ("appending pdu path %s", pdu_path);
      append_msg_attachments (message_builder, pdu_path, msg);
      g_free (pdu_path);
    }

  g_variant_builder_close (message_builder);
}

static void
emit_message_added (const struct mms_service *service,
                    struct mms_message       *msg)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  GVariantBuilder message_builder;
  GVariant        *message;
  g_autoptr(GError) error = NULL;

  g_variant_builder_init (&message_builder, G_VARIANT_TYPE ("(oa{sv})"));

  append_message (msg->path, service, msg, &message_builder);

  message = g_variant_builder_end (&message_builder);

  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 service->path,
                                 MMS_SERVICE_INTERFACE,
                                 "MessageAdded",
                                 message,
                                 &error);

  if (error != NULL)
    g_warning ("Error in Proxy call: %s\n", error->message);
}


int
mms_message_register (struct mms_service *service,
                      struct mms_message *msg)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  GDBusNodeInfo   *introspection_data = mms_dbus_get_introspection_data ();
  g_autoptr(GError)  error = NULL;

  //This builds the path for the message, do not disturb!
  msg->path = g_strdup_printf ("%s/%s", service->path, msg->uuid);
  if (msg->path == NULL)
    return -ENOMEM;

  msg->message_registration_id = g_dbus_connection_register_object (connection,
                                                                    msg->path,
                                                                    introspection_data->interfaces[2],
                                                                    &interface_vtable_message,
                                                                    service,    // user_data
                                                                    NULL, // user_data_free_func
                                                                    &error); // GError**
  if (error)
    g_critical ("Error Registering Message %s: %s", msg->path, error->message);


  g_assert (msg->message_registration_id > 0);

  g_hash_table_replace (service->messages, msg->path, msg);

  DBG ("message registered %s", msg->path);

  return 0;
}


static void
emit_message_removed (const char *svc_path,
                      const char *msg_path)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  GVariant *msgpathvariant;
  g_autoptr(GError) error = NULL;

  msgpathvariant = g_variant_new ("(o)", msg_path);
  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 svc_path,
                                 MMS_SERVICE_INTERFACE,
                                 "MessageRemoved",
                                 msgpathvariant,
                                 &error);

  if (error != NULL)
    g_warning ("Error in Proxy call: %s\n", error->message);
}


int
mms_message_unregister (const struct mms_service *service,
                        const char               *msg_path,
                        guint                     message_registration_id)
{
  emit_message_removed (service->path, msg_path);

  mms_message_dbus_unregister (message_registration_id);

  DBG ("message unregistered %s", msg_path);

  g_hash_table_remove (service->messages, msg_path);

  return 0;
}

int
mms_service_set_identity (struct mms_service *service,
                          const char         *identity)
{
  DBG ("service %p identity %s", service, identity);

  if (service == NULL)
    return -EINVAL;

  if (service->path != NULL)
    return -EBUSY;

  g_free (service->identity);
  service->identity = g_strdup (identity);

  return 0;
}

int
mms_service_set_country_code (struct mms_service *service,
                              const char         *imsi)
{
  const char *country_code;
  if (imsi == NULL)
    {
      g_warning ("mms_service_set_country_code(): IMSI is NULL!");
      return FALSE;
    }
  country_code = get_country_iso_for_mcc (imsi);
  if (country_code == NULL)
    {
      g_warning ("mms_service_set_country_code(): Country Code is NULL!");
      return FALSE;
    }

  g_free (service->country_code);
  service->country_code = g_strdup (country_code);
  DBG ("Service Country Code set to %s", service->country_code);

  return TRUE;
}

int
mms_service_set_own_number (struct mms_service *service,
                            const char         *own_number)
{
  if (own_number == NULL)
    {
      g_warning ("mms_service_set_own_number(): Number is NULL!");
      return FALSE;
    }
  if (service->country_code == NULL)
    {
      g_warning ("Country Code is NULL!");
      return FALSE;
    }

  g_clear_pointer (&service->own_number, g_free);
  service->own_number = phone_utils_format_number_e164 (own_number,
                                                        service->country_code, FALSE);

  if (service->own_number)
    DBG ("Service own number set");
  else {
      g_warning ("Error setting own number!");
      return FALSE;
    }

  return TRUE;
}

void
mms_service_set_apn (struct mms_service *service,
                     const char         *apn)
{
  g_clear_pointer (&service->apn, g_free);
  if (apn == NULL)
    return;
  service->apn = g_strdup (apn);
  DBG ("Service APN Set to %s", service->apn);
  return;
}

int
mms_service_set_mmsc (struct mms_service *service,
                      const gchar        *mmsc)
{
  DBG ("service %p mmsc %s", service, mmsc);

  if (service == NULL)
    return -EINVAL;

  g_free (service->mmsc);
  service->mmsc = g_strdup (mmsc);

  return 0;
}

int
mms_service_set_resolvers (struct mms_service *service,
                           const gchar        *ipv4_csv,
                           const gchar        *ipv6_csv)
{
  DBG ("service %p resolvers: ipv4: %s, ipv6: %s", service, ipv4_csv, ipv6_csv);

  if (service == NULL)
    return -EINVAL;

  g_clear_pointer (&service->resolvers_ipv4_csv, g_free);
  if (ipv4_csv && *ipv4_csv)
    service->resolvers_ipv4_csv = g_strdup (ipv4_csv);

  g_clear_pointer (&service->resolvers_ipv6_csv, g_free);
  if (ipv6_csv && *ipv6_csv)
    service->resolvers_ipv6_csv = g_strdup (ipv6_csv);

  return 0;
}

int
mms_service_set_bearer_handler (struct mms_service               *service,
                                mms_service_bearer_handler_func_t handler,
                                void                             *user_data)
{
  DBG ("service %p handler %p", service, handler);

  if (service == NULL)
    return -EINVAL;

  service->bearer_handler = handler;
  service->bearer_data = user_data;

  return 0;
}

static inline gboolean
bearer_is_active (struct mms_service *service)
{
  if (service->bearer_setup == TRUE)
    return FALSE;

  if (service->bearer_handler == NULL)
    return FALSE;

  return service->bearer_active;
}

static void
deactivate_bearer (struct mms_service *service)
{
  DBG ("service %p", service);

  if (bearer_is_active (service) == FALSE)
    return;

  DBG ("service %p", service);

  service->bearer_setup = TRUE;
  service->bearer_timeout = g_timeout_add_seconds (BEARER_SETUP_TIMEOUT,
                                                   bearer_setup_timeout, service);

  service->bearer_handler (FALSE, service->bearer_data);
}

static gboolean
bearer_idle_timeout (gpointer user_data)
{
  struct mms_service *service = user_data;

  DBG ("service %p", service);

  service->bearer_timeout = 0;

  deactivate_bearer (service);

  return FALSE;
}

static gboolean
result_request_notify_resp (struct mms_request *request)
{
  struct mms_message *msg;
  GKeyFile *meta;

  unlink (request->data_path);

  if (request->status != 200)
    {
      g_warning ("POST m.notify.resp.ind failed with status %d",
                 request->status);
      return FALSE;
    }

  /*
   * Now that the MMS Server awknowledged our response, we can safely
   * delete the original request.
   */
  if (request->type == MMS_REQUEST_TYPE_POST_TMP)
    {
      DBG ("Destroying post data from Notify Resp");
      unlink (request->data_to_post_path);
    }

  if (request->msg == NULL)
    {
      g_critical ("POST m.notify.resp.ind provided no message to register");
      return FALSE;
    }

  msg = mms_request_steal_message (request);

  if (mms_message_register (request->service, msg) != 0)
    {
      mms_message_free (msg);
      return FALSE;
    }

  emit_message_added (request->service, msg);

  meta = mms_store_meta_open (request->service->identity,
                              msg->uuid);
  if (meta == NULL)
    return FALSE;

  const char *datestr = time_to_str (&msg->rc.date);
  g_free (msg->rc.datestamp);
  msg->rc.datestamp = g_strdup (datestr);
  g_key_file_set_string (meta, "info", "date", msg->rc.datestamp);
  g_key_file_set_string (meta, "info", "state", "received");

  mms_store_meta_close (request->service->identity,
                        msg->uuid, meta, TRUE);

  return TRUE;
}

static gboolean
result_request_retrieve_conf (struct mms_request *request)
{
  struct mms_message *msg;
  struct mms_service *service = request->service;
  g_autofree char *uuid = NULL;
  GKeyFile *meta;
  void *pdu;
  size_t len;
  struct mms_request *notify_request;
  gboolean decode_success;

  if (request->status != 200)
    return FALSE;

  if (mmap_file (request->data_path, &pdu, &len) == FALSE)
    return FALSE;

  uuid = mms_store_file (service->identity, request->data_path);
  if (uuid == NULL)
    goto exit;

  msg = g_try_new0 (struct mms_message, 1);
  if (msg == NULL)
    goto exit;

  decode_success = mms_message_decode (pdu, len, msg);

  msg->transaction_id = g_strdup (request->msg->transaction_id);

  if (decode_success == TRUE)
    {
      msg->uuid = g_strdup (uuid);

      meta = mms_store_meta_open (service->identity, uuid);
      if (meta == NULL)
        goto error;

      g_key_file_set_boolean (meta, "info", "read", FALSE);
      g_key_file_set_string (meta, "info", "state", "downloaded");

      mms_store_meta_close (service->identity, uuid, meta, TRUE);

      notify_request = build_notify_resp_ind (service,
                                              MMS_MESSAGE_NOTIFY_STATUS_RETRIEVED,
                                              msg);
    }
  else {
      g_critical ("Failed to decode %s", request->data_path);

      notify_request = NULL;
    }

  /* Remove notify.ind pdu */
  mms_store_remove (service->identity, request->msg->uuid);
  mms_message_free (request->msg);

  service->notification_ind = service->notification_ind - 1;
  DBG ("Number of notifications: %i", service->notification_ind);

  if (notify_request == NULL)
    goto error;

  g_queue_push_tail (service->request_queue, notify_request);
  activate_bearer (service);

  if (decode_success == TRUE)
    goto exit;

 error:
  mms_store_remove (service->identity, uuid);
  mms_message_free (msg);

 exit:
  munmap (pdu, len);
  return TRUE;
}

static gboolean
mms_requeue_request (struct mms_request *request)
{
  request->attempt += 1;

  if (request->type == MMS_REQUEST_TYPE_GET)
    {
      if (request->fd == -1)
        {
          DBG ("Reopening file....");
          request->fd = open (request->data_path,
                              O_WRONLY | O_TRUNC,
                              S_IWUSR | S_IRUSR);
        }
      if (request->fd < 0)
        return FALSE;
    }
  else if (request->type == MMS_REQUEST_TYPE_POST ||
           request->type == MMS_REQUEST_TYPE_POST_TMP)
    {
      if (request->fd != -1)
        close (request->fd);
      request->fd = -1;

      /*
       * If request->data_to_post_path is not NULL, then we need to delete
       * the temp file in request->data_path and put request->data_to_post_path
       * back into request->data_path
       */
      DBG ("Data Path is %s", request->data_path);
      DBG ("Data to POST Path is %s", request->data_to_post_path);
      if (request->data_to_post_path)
        {
          DBG ("Putting Data to POST Path to Data path");
          unlink (request->data_path);
          g_free (request->data_path);
          request->data_path = g_strdup (request->data_to_post_path);
          g_clear_pointer (&request->data_to_post_path, g_free);
        }
    }

  g_queue_push_tail (request->service->request_queue, request);

  return TRUE;
}

static gboolean
service_activate_bearer (gpointer user_data)
{
  struct mms_service *service = user_data;
  DBG ("Retrying Modem Bearer");
  activate_bearer (service);
  return FALSE;
}

static void
emit_message_tx_rx_error (struct mms_request  *request,
                          const char          *host,
                          enum mms_tx_rx_error error_type)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  g_autoptr(GError) error = NULL;
  GVariant        *message_error;

  if (request->msg->type == MMS_MESSAGE_TYPE_SEND_REQ)
    {
      GVariantBuilder          error_builder;
      GVariant                *incomplete_error;

      g_variant_builder_init (&error_builder, G_VARIANT_TYPE ("a{sv}"));

      g_variant_builder_add_parsed (&error_builder,
                                    "{'ErrorType', <%u>}",
                                    error_type);

      g_variant_builder_add_parsed (&error_builder,
                                    "{'Host', <%s>}",
                                    host);

      g_variant_builder_add_parsed (&error_builder,
                                    "{'MessagePath', <%s>}",
                                    request->msg->path);

      incomplete_error = g_variant_builder_end (&error_builder);

      message_error = g_variant_new ("(*)", incomplete_error);

      g_dbus_connection_emit_signal (connection,
                                     NULL,
                                     request->service->path,
                                     MMS_SERVICE_INTERFACE,
                                     "MessageSendError",
                                     message_error,
                                     &error);
    }
  else if (request->msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND ||
           request->msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF)
    {
      GVariantBuilder          error_builder;
      GVariant                *incomplete_error;
      const char *from_prefix = NULL;
      g_autofree char *formatted_from = NULL;

      if (request->msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND &&
          request->msg->ni.from != NULL)
        {
          from_prefix = mms_address_to_string (request->msg->ni.from);
          formatted_from = phone_utils_format_number_e164 (from_prefix,
                                                           request->service->country_code,
                                                           TRUE);
        }
      else if (request->msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF &&
               request->msg->rc.from != NULL)
        {
          from_prefix = mms_address_to_string (request->msg->rc.from);
          formatted_from = phone_utils_format_number_e164 (from_prefix,
                                                           request->service->country_code,
                                                           TRUE);
        }
      else {
          g_critical ("Did not find who this is from, this should not happen!");
          formatted_from = g_strdup ("");
        }

      g_variant_builder_init (&error_builder, G_VARIANT_TYPE ("a{sv}"));

      g_variant_builder_add_parsed (&error_builder,
                                    "{'ErrorType', <%u>}",
                                    error_type);

      g_variant_builder_add_parsed (&error_builder,
                                    "{'Host', <%s>}",
                                    host);

      g_variant_builder_add_parsed (&error_builder,
                                    "{'From', <%s>}",
                                    formatted_from);

      incomplete_error = g_variant_builder_end (&error_builder);

      message_error = g_variant_new ("(*)", incomplete_error);

      g_dbus_connection_emit_signal (connection,
                                     NULL,
                                     request->service->path,
                                     MMS_SERVICE_INTERFACE,
                                     "MessageReceiveError",
                                     message_error,
                                     &error);
    }
  else
    g_critical ("Confused....This was not a send or receive request.");

  if (error != NULL)
    g_warning ("Error in Proxy call: %s\n", error->message);
}

static gboolean
service_retry_process_request_queue (gpointer user_data)
{
  struct mms_service *service = user_data;
  DBG ("Retrying Queue");
  process_request_queue (service);
  return FALSE;
}

void
on_message_done (SoupSession  *session,
                 GAsyncResult *result,
                 gpointer      user_data)
{
  struct mms_request *request = user_data;
  struct mms_service *service = request->service;
  gsize written = 0;
  gsize body_length = 0;
  SoupMessage *msg;
  GBytes *body = NULL;
  g_autoptr(GError) error = NULL;

  msg = soup_session_get_async_result_message (session, result);
  body = soup_session_send_and_read_finish (session, result, &error);
  body_length = g_bytes_get_size (body);
  g_clear_object (&service->cancel_current_msg);

  request->status = soup_message_get_status (msg);
  if (error)
    {
      g_warning ("Error in sending/receiving: %s", error->message);
      request->status = 007;
    }
  if (!SOUP_STATUS_IS_SUCCESSFUL (request->status))
    g_warning ("Fail to get data (http status = %03u)", request->status);

  DBG ("status: %03u", request->status);
  DBG ("data size = %zd", body_length);

  written = write (request->fd, g_bytes_get_data (body, &body_length), body_length);

  if (written != g_bytes_get_size (body))
    g_warning ("only %zd/%zd bytes written\n", written, body_length);

  g_bytes_unref (body);
  close (request->fd);
  request->fd = -1;

  DBG ("request->result_cb=%p vs. retrieve_conf=%p/send_conf=%p/notify_resp=%p",
       request->result_cb,
       result_request_retrieve_conf,
       result_request_send_conf,
       result_request_notify_resp);

  service->current_request_msg = NULL;

  if (request->result_cb == NULL || request->result_cb (request) == TRUE)
    mms_request_destroy (request);
  else {
      g_warning ("Fail to get data (http status = %03u)", request->status);
      if (mms_requeue_request (request) == TRUE)
        {
          DBG ("On attempt %d", request->attempt);
          if (request->attempt >= MAX_ATTEMPTS)
            {
              const char *host = NULL;
              g_autoptr(GUri) uri = NULL;

              DBG ("Had Max attempts");
              request->attempt = 0;
              if (request->type == MMS_REQUEST_TYPE_GET ||
                  request->type == MMS_REQUEST_TYPE_POST_TMP)
                uri = g_uri_parse (request->location, SOUP_HTTP_URI_FLAGS, NULL);
              else if (request->type == MMS_REQUEST_TYPE_POST)
                uri = g_uri_parse (request->service->mmsc, SOUP_HTTP_URI_FLAGS, NULL);

              if (uri != NULL)
                host = g_uri_get_host (uri);
              else
                host = "";

              emit_message_tx_rx_error (request,
                                        host,
                                        MMS_TX_RX_ERROR_HTTP);
              g_timeout_add_seconds (120, service_activate_bearer, service);
              deactivate_bearer (service);
              return;
            }
          else {
              DBG ("Requeued Message");
              g_timeout_add_seconds (5, service_retry_process_request_queue, service);
              return;
            }
        }
      else {
          unlink (request->data_path);
          mms_request_destroy (request);
        }
    }

  process_request_queue (service);
}

static void
soupmessage_network_event_cb (SoupMessage       *msg,
                              GSocketClientEvent event,
                              GSocketConnection *connection,
                              gpointer           user_data)
{
  struct mms_request *request = user_data;
  GSocket *msg_socket;
  int socket_fd;

  /* https://mail.gnome.org/archives/libsoup-list/2012-December/msg00011.html */
  /* Bind the SoupMessage to service->interface */
  if (event == G_SOCKET_CLIENT_CONNECTING)
    {
      int return_code, interface_length;
      msg_socket = g_socket_connection_get_socket (connection);
      socket_fd = g_socket_get_fd (msg_socket);

      interface_length = strlen (request->service->interface);

      DBG ("Socket %d Binding to %s length %d",
           socket_fd, request->service->interface, interface_length);

      return_code = setsockopt (socket_fd, SOL_SOCKET, SO_BINDTODEVICE,
                                request->service->interface, interface_length);

      if (return_code == 0)
        DBG ("Socket is bound to %s", request->service->interface);
      else
        DBG ("Socket could not be bound! Error: %d", return_code);

      g_signal_handler_disconnect (msg,
                                   request->soupmessage_network_event_signal_id);
    }
}

static int
check_verizon_apn (const char *apn)
{
  g_autofree char *apn_to_test = NULL;

  apn_to_test = g_utf8_strdown (apn, -1);
  if ((g_strcmp0 (apn_to_test, "vzwapp") == 0) ||
      (g_strcmp0 (apn_to_test, "vzwinternet") == 0) ||
      (g_strcmp0 (apn_to_test, "vzwims") == 0))
    return TRUE;
  else
    return FALSE;
  return FALSE;
}

static void
message_add_headers (struct mms_request *request,
                     SoupMessage       **msg)
{
  SoupMessageHeaders *message_headers = NULL;

  if (check_verizon_apn (request->service->apn))
    {
      if (request->service->own_number == NULL)
        {
          g_critical ("Cannot add number to headers!");
          return;
        }
      char *number = request->service->own_number + 1;
      message_headers = soup_message_get_request_headers ((*msg));
      soup_message_headers_replace (message_headers, "X-VzW-MDN", number);
    }
}

/* Verizon send either a wap push with an incomplete url and we need to concat
 * the transaction id, or send 2 wap push with a full url.
 * We need testers on US Cellular and Xfinity mobile, as it *may* be needed too for some of them.
 * We may need to check the MNC/MCC instead of the apn as us cellular apn is very generic.
 */
static void
append_transaction_id (struct mms_request *request)
{
  char *new_location = NULL;

  if (request->location == NULL)
    return;

  if (check_verizon_apn (request->service->apn) && (g_str_has_suffix (request->location, "message-id=")))
    {
      new_location = g_strconcat (request->location, request->msg->transaction_id, NULL);
      DBG ("Changing Location from %s to %s", request->location, new_location);
      g_clear_pointer (&request->location, g_free);
      request->location = new_location;
    }
}

static void
create_new_web_message (struct mms_request *request,
                        gchar              *type,
                        gchar              *location,
                        SoupMessage       **msg)
{
  SoupMessageHeaders *message_headers = NULL;
  g_autofree char *host_header = NULL;
  g_autofree char *uri = NULL;
  g_autoptr(GUri) guri = NULL;
  const char *host = NULL;
  int port;

  uri = resolve_host (request->service, location);
  if (uri == NULL)
    {
      g_warning ("Failed to resolve DNS");
      if (mms_requeue_request (request) == TRUE)
        {
          DBG ("On attempt %d", request->attempt);
          if (request->attempt >= MAX_ATTEMPTS)
            {
              const gchar *host = NULL;
              g_autoptr(GUri) uri = NULL;

              DBG ("Had Max attempts");
              request->attempt = 0;
              if (request->type == MMS_REQUEST_TYPE_GET ||
                  request->type == MMS_REQUEST_TYPE_POST_TMP)
                uri = g_uri_parse (request->location, SOUP_HTTP_URI_FLAGS, NULL);
              else if (request->type == MMS_REQUEST_TYPE_POST)
                uri = g_uri_parse (request->service->mmsc, SOUP_HTTP_URI_FLAGS, NULL);

              if (uri != NULL)
                host = g_uri_get_host (uri);
              else
                host = "";

              emit_message_tx_rx_error (request,
                                        host,
                                        MMS_TX_RX_ERROR_DNS);
              g_timeout_add_seconds (120, service_activate_bearer, request->service);
              deactivate_bearer (request->service);
              return;
            }
          else {
              DBG ("Requeued Message");
              return;
            }
        }
      else {
          unlink (request->data_path);
          mms_request_destroy (request);
          return;
        }
    }

  if (*msg != NULL)
    g_object_unref (*msg);

  if (g_strcmp0 (type, "GET") == 0)
    {
      *msg = soup_message_new ("GET", uri);
      SoupMessageHeaders *message_headers_get = NULL;
      if (*msg == NULL)
        {
          g_critical ("unable to create new libsoup GET message\n");
          return;
        }
      message_headers_get = soup_message_get_request_headers ((*msg));
      soup_message_headers_replace (message_headers_get, "Accept", "*/*");
    }
  else if (g_strcmp0 (type, "POST") == 0)
    {
      *msg = soup_message_new ("POST", uri);
      if (*msg == NULL)
        {
          g_critical ("unable to create new libsoup POST message\n");
          return;
        }
    }
  else {
      g_critical ("Unknown message type: %s", type);
      return;
    }

  /* Make sure that the mesage connects on new socket */
  soup_message_set_flags (*msg, SOUP_MESSAGE_NEW_CONNECTION);

  /*
   * Some carriers depend on the Host: header being set to the
   * location. resolve_host returns an IP
   */
  guri = g_uri_parse (location, SOUP_HTTP_URI_FLAGS, NULL);

  port = g_uri_get_port (guri);
  host = g_uri_get_host (guri);
  DBG ("host: %s, port: %d", host, port);

  message_headers = soup_message_get_request_headers ((*msg));
  if (port == 80 || port == -1)
    soup_message_headers_replace (message_headers, "Host", host);
  else {
      host_header = g_strdup_printf ("%s:%d", host, port);
      soup_message_headers_replace (message_headers, "Host", host_header);
    }

  if (request->service->proxy_active == TRUE)
    soup_message_headers_replace (message_headers, "Proxy-Connection", "Keep-Alive");

  /*
   * Fake the user agent to look like Android
   * https://cs.android.com/android/platform/superproject/+/master:packages/apps/Messaging/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java;drc=f38a1bdc3c5b47024fe4c605e6a487e5353dfedf;l=29
   */
  soup_message_headers_replace (message_headers, "User-Agent", "Android MmsLib/1.0");

  message_add_headers (request, msg);

  /* Monitor "network-event" for G_SOCKET_CLIENT_CONNECTING */
  request->soupmessage_network_event_signal_id = g_signal_connect (
    *msg, "network-event",
    G_CALLBACK (soupmessage_network_event_cb), request);
}

SoupMessage *
process_request (struct mms_request *request)
{
  struct mms_service *service = request->service;
  SoupMessage *msg = NULL;

  if (request->data_path == NULL)
    return NULL;

  append_transaction_id (request);

  switch (request->type)
    {
    case MMS_REQUEST_TYPE_GET:
      create_new_web_message (request, "GET", request->location, &msg);
      if (msg == NULL)
        {
          g_critical ("Unable to create GET message");
          return NULL;
        }
      service->cancel_current_msg = g_cancellable_new ();
      soup_session_send_and_read_async (request->service->web, msg, 0, service->cancel_current_msg,
                                        (GAsyncReadyCallback)on_message_done, request);
      DBG ("GET from <%s>", request->location);
      return msg;

    case MMS_REQUEST_TYPE_POST:
    case MMS_REQUEST_TYPE_POST_TMP:
      /* Collect contents of file to POST */
      gsize length;
      g_autofree gchar *contents = NULL;
      GBytes *request_body = NULL;
      SoupMessageHeaders *message_headers = NULL;

      create_new_web_message (request, "POST", service->mmsc, &msg);
      if (msg == NULL)
        {
          g_critical ("Unable to create POST message");
          return NULL;
        }
      g_file_get_contents (request->data_path, &contents, &length,
                           NULL);
      if (contents == NULL)
        {
          g_critical ("Unable to read contents of file: %s\n",
                      request->data_path);
          return NULL;
        }

      request_body = g_bytes_new (contents, length);
      request->data_to_post_path = g_strdup (request->data_path);

      if (request->fd == -1)
        {
          /* Prepare fd for response reception */
          g_free (request->data_path);

          request->data_path = g_strdup_printf ("%spost-rsp.XXXXXX.mms",
                                                service->request_path);

          request->fd = g_mkstemp_full (request->data_path,
                                        O_WRONLY | O_CREAT | O_TRUNC,
                                        S_IWUSR | S_IRUSR);
        }

      message_headers = soup_message_get_request_headers (msg);
      soup_message_headers_replace (message_headers,
                                    "Content-Type",
                                    DEFAULT_CONTENT_TYPE);

      soup_message_set_request_body_from_bytes (msg, NULL, request_body);
      g_bytes_unref (request_body);
      service->cancel_current_msg = g_cancellable_new ();
      soup_session_send_and_read_async (request->service->web, msg, 0, service->cancel_current_msg,
                                        (GAsyncReadyCallback)on_message_done, request);
      DBG ("POST %lu bytes to %s", length, service->mmsc);
      DBG ("Sending <%s>", request->data_to_post_path);

      return msg;
    }

  g_critical ("Cannot process request (request type: %d)", request->type);

  unlink (request->data_path);

  mms_request_destroy (request);

  return NULL;
}

static void
process_request_queue (struct mms_service *service)
{
  struct mms_request *request;

  DBG ("service %p", service);

  /* If there is an idle bearer timeout, remove it */
  if (service->bearer_timeout > 0)
    {
      g_source_remove (service->bearer_timeout);
      service->bearer_timeout = 0;
    }

  /* Service is already processing a request, do nothing */
  if (service->current_request_msg)
    return;

  /* Bearer is not active, do nothing */
  if (bearer_is_active (service) == FALSE)
    return;

  request = g_queue_pop_head (service->request_queue);

  if (request != NULL && request->msg == NULL)
    {
      DBG ("request is NULL!");
      mms_request_destroy (request);
      request = g_queue_pop_head (service->request_queue);
    }

  if (request != NULL &&
      request->msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND &&
      check_message_expiration (service, request->msg) == FALSE)
    {
      mms_request_destroy (request);
      request = g_queue_pop_head (service->request_queue);
    }

  /* We have no requests in the queue */
  if (request == NULL)
    {
      /* We have no requests in the queue, start an idle timeout */
      service->bearer_timeout = g_timeout_add_seconds (BEARER_IDLE_TIMEOUT,
                                                       bearer_idle_timeout, service);
      return;
    }

  DBG ("location %s", request->location);

  service->current_request_msg = process_request (request);

  /* We have an active request now */
  if (service->current_request_msg)
    return;

  /* There was an error processing the request */
  g_timeout_add_seconds (5, service_retry_process_request_queue, service);
}

static void
dump_delivery_ind (struct mms_message *msg)
{
  char buf[128];

  strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (&msg->di.date));
  buf[127] = '\0';

  g_message ("MMS version: %u.%u\n", (msg->version & 0x70) >> 4,
             msg->version & 0x0f);
  g_message ("Msg ID: %s\n", msg->di.msgid);
  mms_address_to_string (msg->di.to);
  //g_message("To: %s\n", msg->di.to);
  g_message ("Date: %s\n", buf);
  g_message ("Delivery Report status: %d\n", msg->di.dr_status);
}

static void
dump_notification_ind (struct mms_message *msg)
{
  char buf[128];

  strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (&msg->ni.expiry));
  buf[127] = '\0';

  g_message ("MMS transaction id: %s\n", msg->transaction_id);
  g_message ("MMS version: %u.%u\n", (msg->version & 0x70) >> 4,
             msg->version & 0x0f);
  //g_message("From: %s\n", msg->ni.from);
  g_message ("Subject: %s\n", msg->ni.subject);
  g_message ("Class: %s\n", msg->ni.cls);
  g_message ("Size: %d\n", msg->ni.size);
  g_message ("Expiry: %s\n", buf);
  g_message ("Location: %s\n", msg->ni.location);
}

static gboolean
mms_push_notify (const unsigned char *pdu,
                 unsigned int         len,
                 unsigned int        *offset)
{
  unsigned int headerslen;
  unsigned int param_len;
  const void *ct;
  const void *aid;
  struct wsp_header_iter iter;
  unsigned int nread;
  unsigned int consumed;
  unsigned int i;
  GString *hex;

  DBG ("pdu %p len %d", pdu, len);

  hex = g_string_sized_new (len * 2);

  for (i = 0; i < len; i++)
    g_string_append_printf (hex, "%02X", pdu[i]);

  DBG ("%s", hex->str);

  g_string_free (hex, TRUE);

  /* PUSH pdu ? */
  if (pdu[1] != 0x06)
    return FALSE;

  /* Consume TID and Type */
  nread = 2;

  if (wsp_decode_uintvar (pdu + nread, len,
                          &headerslen, &consumed) == FALSE)
    return FALSE;

  /* Consume uintvar bytes */
  nread += consumed;

  /* Try to decode content-type */
  if (wsp_decode_content_type (pdu + nread, headerslen, &ct,
                               &consumed, &param_len) == FALSE)
    return FALSE;

  if (ct == NULL)
    return FALSE;

  /* Consume Content Type bytes, including parameters */
  consumed += param_len;
  nread += consumed;

  /* Parse header to decode application_id */
  wsp_header_iter_init (&iter, pdu + nread, headerslen - consumed, 0);

  aid = NULL;

  while (wsp_header_iter_next (&iter))
    {
      const unsigned char *wk;

      /* Skip application headers */
      if (wsp_header_iter_get_hdr_type (&iter) !=
          WSP_HEADER_TYPE_WELL_KNOWN)
        continue;

      wk = wsp_header_iter_get_hdr (&iter);

      if ((wk[0] & 0x7f) != WSP_HEADER_TOKEN_APP_ID)
        continue;

      if (wsp_decode_application_id (&iter, &aid) == FALSE)
        return FALSE;
    }

  if (wsp_header_iter_at_end (&iter) == FALSE)
    return FALSE;

  nread += headerslen - consumed;

  g_message ("Body Length: %d\n", len - nread);

  DBG ("Content Type: %s", (char *) ct);
  if (g_str_equal (ct, MMS_CONTENT_TYPE) == TRUE)
    {
      if (offset != NULL)
        *offset = nread;
      return TRUE;
    }

  return FALSE;
}

void
mms_service_push_notify (struct mms_service  *service,
                         const unsigned char *data,
                         int                  len)
{
  struct mms_request *request;
  struct mms_message *msg;
  unsigned int nread;
  g_autofree char *uuid = NULL;
  const char *expirystr;
  GKeyFile *meta;

  DBG ("Processing push notify");

  msg = g_try_new0 (struct mms_message, 1);
  if (msg == NULL)
    {
      g_critical ("Failed to allocate message");
      return;
    }

  if (mms_push_notify (data, len, &nread) == FALSE)
    goto out;

  uuid = mms_store (service->identity, data + nread, len - nread);
  if (uuid == NULL)
    goto out;

  if (mms_message_decode (data + nread, len - nread, msg) == FALSE)
    goto error;

  if (msg->type == MMS_MESSAGE_TYPE_DELIVERY_IND)
    {
      msg->uuid = g_strdup (uuid);

      dump_delivery_ind (msg);

      meta = mms_store_meta_open (service->identity, uuid);
      if (meta == NULL)
        goto error;

      g_key_file_set_string (meta, "info", "state", "notification");

      mms_store_meta_close (service->identity, uuid, meta, TRUE);
      if (process_delivery_ind (service, msg) == FALSE)
        DBG ("There was an issue finding the message to go with the delivery Notification. Was it deleted?");

      mms_store_remove (service->identity, msg->uuid);
      mms_message_free (msg);
      return;
    }

  if (msg->type != MMS_MESSAGE_TYPE_NOTIFICATION_IND)
    goto error;

  msg->uuid = g_strdup (uuid);

  dump_notification_ind (msg);

  meta = mms_store_meta_open (service->identity, uuid);
  if (meta == NULL)
    goto error;

  g_key_file_set_boolean (meta, "info", "read", FALSE);

  g_key_file_set_string (meta, "info", "state", "notification");

  expirystr = time_to_str (&msg->ni.expiry);
  g_key_file_set_string (meta, "info", "expiration", expirystr);

  mms_store_meta_close (service->identity, uuid, meta, TRUE);

  request = create_request (MMS_REQUEST_TYPE_GET,
                            result_request_retrieve_conf,
                            msg->ni.location, service, msg);
  if (request == NULL)
    goto out;

  g_queue_push_tail (service->request_queue, request);

  service->notification_ind = service->notification_ind + 1;
  DBG ("Number of notifications: %i", service->notification_ind);

  activate_bearer (service);

  return;

 error:
  mms_store_remove (service->identity, uuid);

 out:
  mms_message_free (msg);

  g_critical ("Failed to handle incoming notification");
}

void
debug_print (const char *s,
             void       *data)
{
  printf ("%s\n", s);
}

void
mms_service_bearer_notify (struct mms_service *service,
                           mms_bool_t          active,
                           const char         *interface,
                           const char         *proxy)
{
  int ifindex;
  g_autoptr(GError) error = NULL;

  DBG ("service=%p active=%d iface=%s proxy=%s", service, active, interface, proxy);

  if (service == NULL)
    return;

  if (service->bearer_timeout > 0)
    {
      g_source_remove (service->bearer_timeout);
      service->bearer_timeout = 0;
    }

  service->bearer_setup = FALSE;
  service->bearer_active = active;
  service->proxy_active = FALSE;

  if (active == FALSE)
    goto interface_down;

  DBG ("interface %s proxy %s", interface, proxy);

  if (interface == NULL)
    goto interface_down;

  ifindex = if_nametoindex (interface);
  if (ifindex == 0)
    goto interface_down;

  if (service->interface != NULL)
    g_free (service->interface);
  service->interface = g_strdup (interface);

  if (service->current_request_msg)
    {
      DBG ("Cancelling active message request!");
      g_cancellable_cancel (service->cancel_current_msg);
      g_clear_object (&service->cancel_current_msg);
      g_clear_object (&service->current_request_msg);
    }
  g_clear_object (&service->web);
  service->web = soup_session_new ();
  if (service->web == NULL)
    return;

  if (global_debug)
    {
      SoupLogger *logger;
      logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
      soup_session_add_feature (service->web,
                                SOUP_SESSION_FEATURE (logger));
      g_object_unref (logger);
    }

  if (proxy && *proxy)
    {
      g_autofree char *uri = NULL;
      g_autofree char *new_uri = NULL;
      g_autofree char *uri_to_use_temp = NULL;
      g_autofree char *uri_to_use = NULL;
      gchar **tokens;
      g_autoptr(GUri) proxy_uri = NULL;
      GProxyResolver *default_proxy = NULL;
      int port;

      if (g_str_has_prefix (proxy, "http://"))
        {
          uri = g_strdup (proxy);
          tokens = g_strsplit (proxy + 7, ":", 2);
        }
      else {
          uri = g_strdup_printf ("http://%s", proxy);
          tokens = g_strsplit (proxy, ":", 2);
        }

      if (!g_hostname_is_ip_address (tokens[0]))
        {
          DBG ("Hostname is not an IP address!");
          new_uri = resolve_host (service, uri);
          DBG ("Proxy URL resolved: %s", new_uri);
        }
      else
        new_uri = g_strdup (uri);

      g_strfreev (tokens);

      if (new_uri == NULL)
        {
          g_critical ("Failed to resolve proxy");
          goto interface_down;
        }

      proxy_uri = g_uri_parse (new_uri, SOUP_HTTP_URI_FLAGS, NULL);
      if (!proxy_uri)
        {
          g_critical ("unable to set proxy: %s", new_uri);
          goto interface_down;
        }

      uri_to_use_temp = g_uri_to_string (proxy_uri);

      if (g_str_has_suffix (uri_to_use_temp, "/"))
        uri_to_use = g_strndup (uri_to_use_temp, strlen (uri_to_use_temp) - 1);
      else
        uri_to_use = g_strdup (uri_to_use_temp);

      port = g_uri_get_port (proxy_uri);

      /* g_simple_proxy_resolver_new() seems to require the port enumerated */
      if (port == 80 || port == -1)
        {
          g_clear_pointer (&uri_to_use_temp, g_free);
          uri_to_use_temp = uri_to_use;
          uri_to_use = g_strdup_printf ("%s:80", uri_to_use_temp);
        }

      DBG ("Proxy URL: %s", uri_to_use);
      default_proxy = g_simple_proxy_resolver_new (uri_to_use, NULL);
      soup_session_set_proxy_resolver (service->web, default_proxy);
      g_object_unref (default_proxy);
      service->proxy_active = TRUE;
    }
  else
    soup_session_set_proxy_resolver (service->web, NULL);

  DBG ("Proxy is set to %d", service->proxy_active);
  process_request_queue (service);

  return;

 interface_down:
  if (service->current_request_msg)
    {
      DBG ("Cancelling active message request!");
      g_cancellable_cancel (service->cancel_current_msg);
      g_clear_object (&service->cancel_current_msg);
      g_clear_object (&service->current_request_msg);
    }
  g_clear_object (&service->web);
  service->bearer_active = FALSE;
}

void
systemd_resolved_appeared (GDBusConnection *connection,
                           const gchar     *name,
                           const gchar     *name_owner,
                           gpointer         user_data)
{
  DBG ("systemd-resolved is on the system bus, creating proxy");

  g_autoptr(GError) error = NULL;

  systemd_resolved_proxy = g_dbus_proxy_new_sync (
    connection,
    G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
    NULL,
    RESOLVED_SERVICE,
    RESOLVED_PATH,
    RESOLVED_MANAGER_INTERFACE,
    NULL,
    &error
    );

  if (systemd_resolved_proxy == NULL)
    g_warning ("Error while acquiring DBus proxy for systemd-resolved: %s\n", error->message);
}

void
systemd_resolved_vanished (GDBusConnection *connection,
                           const gchar     *name,
                           gpointer         user_data)
{
  DBG ("Lost systemd-resolved on the system bus");
  g_clear_object (&systemd_resolved_proxy);
}

int
__mms_service_init (gboolean enable_debug)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  GDBusNodeInfo   *introspection_data = mms_dbus_get_introspection_data ();
  g_autoptr(GError)  error = NULL;
  global_debug = enable_debug;

  DBG ("Starting Up MMSD Service Manager");
  manager_registration_id = g_dbus_connection_register_object (connection,
                                                               MMS_PATH,
                                                               introspection_data->interfaces[1],
                                                               &interface_vtable_manager,
                                                               NULL,    // user_data
                                                               NULL,    // user_data_free_func
                                                               &error);    // GError**

  systemd_resolved_watcher_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
                                                  RESOLVED_SERVICE,
                                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                  systemd_resolved_appeared,
                                                  systemd_resolved_vanished,
                                                  NULL,
                                                  NULL);

  if (error)
    g_critical ("Error Registering Manager: %s", error->message);
  return 0;
}

void
__mms_service_cleanup (void)
{
  GDBusConnection *connection = mms_dbus_get_connection ();

  g_bus_unwatch_name (systemd_resolved_watcher_id);
  systemd_resolved_vanished (connection, NULL, NULL);

  DBG ("Cleaning Up MMSD Service Manager");

  //Disconnect the manager dbus interface
  g_dbus_connection_unregister_object (connection,
                                       manager_registration_id);
}
