/*
 * Copyright (C) 2023 Pascal Nowack
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "config.h"

#include "grd-rdp-connect-time-autodetection.h"

#define CT_BW_MAX_TOLERABLE_RESPONSE_LATENCY_US (400 * 1000)
#define CT_BW_MAX_TOLERABLE_TIME_DELTA_MS 100

#define CT_N_PINGS 10
#define CT_PING_INTERVAL_MS 10

#define BW_MEASURE_SEQUENCE_NUMBER 0
#define CT_NON_RTT_SEQUENCE_NUMBER 0

typedef enum
{
  CT_AUTODETECT_STATE_NONE,
  CT_AUTODETECT_STATE_MEASURE_BW_1,
  CT_AUTODETECT_STATE_MEASURE_BW_2,
  CT_AUTODETECT_STATE_MEASURE_BW_3,
  CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1,
  CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2,
  CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3,
  CT_AUTODETECT_STATE_START_RTT_DETECTION,
  CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE,
  CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT,
  CT_AUTODETECT_STATE_COMPLETE,
} CtAutodetectState;

struct _GrdRdpConnectTimeAutodetection
{
  GObject parent;

  GrdRdpNetworkAutodetection *network_autodetection;
  rdpAutoDetect *rdp_autodetect;
  gboolean protocol_stopped;

  GMutex ct_autodetection_mutex;
  CtAutodetectState state;

  GSource *sync_source;
  GCond net_char_sync_cond;
  GMutex net_char_sync_mutex;
  gboolean pending_net_char_sync;

  GSource *ping_source;
  uint32_t pending_pings;
  gboolean pending_last_sequence_number;
  uint16_t last_sequence_number;

  int64_t base_round_trip_time_us;
  int64_t avg_round_trip_time_us;

  int64_t bw_measure_time_us;

  uint32_t last_time_delta_ms;
  uint32_t last_byte_count;
};

G_DEFINE_TYPE (GrdRdpConnectTimeAutodetection, grd_rdp_connect_time_autodetection,
               G_TYPE_OBJECT)

gboolean
grd_rdp_connect_time_autodetection_is_complete (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  g_autoptr (GMutexLocker) locker = NULL;

  locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
  return ct_autodetection->state == CT_AUTODETECT_STATE_COMPLETE;
}

static void
determine_bw_measure_payloads (GrdRdpConnectTimeAutodetection *ct_autodetection,
                               uint16_t                       *payload_lengths,
                               uint32_t                       *n_payloads)
{
  switch (ct_autodetection->state)
    {
    case CT_AUTODETECT_STATE_MEASURE_BW_1:
      *payload_lengths = 15 * 1024 + 512 + 256 + 128 + 64;
      *n_payloads = 1;
      break;
    case CT_AUTODETECT_STATE_MEASURE_BW_2:
      *payload_lengths = 15 * 1024 + 512 + 256 + 128 + 64;
      *n_payloads = 4;
      break;
    case CT_AUTODETECT_STATE_MEASURE_BW_3:
      *payload_lengths = 15 * 1024 + 512 + 256 + 128 + 64;
      *n_payloads = 16;
      break;
    default:
      g_assert_not_reached ();
    }
}

static CtAutodetectState
get_next_bw_measure_state (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  switch (ct_autodetection->state)
    {
    case CT_AUTODETECT_STATE_MEASURE_BW_1:
      return CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1;
    case CT_AUTODETECT_STATE_MEASURE_BW_2:
      return CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2;
    case CT_AUTODETECT_STATE_MEASURE_BW_3:
      return CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3;
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
      return CT_AUTODETECT_STATE_MEASURE_BW_2;
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
      return CT_AUTODETECT_STATE_MEASURE_BW_3;
    default:
      g_assert_not_reached ();
    }

  return CT_AUTODETECT_STATE_NONE;
}

static void
measure_connect_time_bandwidth (GrdRdpConnectTimeAutodetection *ct_autodetection,
                                uint16_t                        payload_lengths,
                                uint32_t                        n_payloads)
{
  rdpAutoDetect *rdp_autodetect = ct_autodetection->rdp_autodetect;
  uint32_t i;

  g_assert (payload_lengths > 0);
  g_assert (n_payloads >= 1);

  rdp_autodetect->BandwidthMeasureStart (rdp_autodetect, RDP_TRANSPORT_TCP,
                                         BW_MEASURE_SEQUENCE_NUMBER);
  for (i = 0; i < n_payloads - 1; ++i)
    {
      rdp_autodetect->BandwidthMeasurePayload (rdp_autodetect,
                                               RDP_TRANSPORT_TCP,
                                               BW_MEASURE_SEQUENCE_NUMBER,
                                               payload_lengths);
    }
  rdp_autodetect->BandwidthMeasureStop (rdp_autodetect, RDP_TRANSPORT_TCP,
                                        BW_MEASURE_SEQUENCE_NUMBER,
                                        payload_lengths);
}

static const char *
ct_autodetect_state_to_string (CtAutodetectState state)
{
  switch (state)
    {
    case CT_AUTODETECT_STATE_NONE:
      return "NONE";
    case CT_AUTODETECT_STATE_MEASURE_BW_1:
      return "MEASURE_BW_1";
    case CT_AUTODETECT_STATE_MEASURE_BW_2:
      return "MEASURE_BW_2";
    case CT_AUTODETECT_STATE_MEASURE_BW_3:
      return "MEASURE_BW_3";
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
      return "AWAIT_BW_RESULT_1";
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
      return "AWAIT_BW_RESULT_2";
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
      return "AWAIT_BW_RESULT_3";
    case CT_AUTODETECT_STATE_START_RTT_DETECTION:
      return "START_RTT_DETECTION";
    case CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE:
      return "AWAIT_LAST_RTT_RESPONSE";
    case CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT:
      return "SEND_NET_CHAR_RESULT";
    case CT_AUTODETECT_STATE_COMPLETE:
      return "COMPLETE";
    }

  g_assert_not_reached ();
}

static void
transition_to_state (GrdRdpConnectTimeAutodetection *ct_autodetection,
                     CtAutodetectState               state)
{
  g_assert (ct_autodetection->state != state);

  g_debug ("[RDP] Connect-Time Autodetect: Updating state from %s to %s",
           ct_autodetect_state_to_string (ct_autodetection->state),
           ct_autodetect_state_to_string (state));

  ct_autodetection->state = state;
}

static void
last_sequence_number_ready (gpointer user_data,
                            uint16_t sequence_number)
{
  GrdRdpConnectTimeAutodetection *ct_autodetection = user_data;

  g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
  ct_autodetection->last_sequence_number = sequence_number;
  ct_autodetection->pending_last_sequence_number = FALSE;
  g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);
}

static gboolean
emit_ping (gpointer user_data)
{
  GrdRdpConnectTimeAutodetection *ct_autodetection = user_data;
  GrdRdpNetworkAutodetection *network_autodetection =
    ct_autodetection->network_autodetection;
  GrdRdpNwAutodetectSequenceNumberReadyCallback sequence_number_ready = NULL;

  g_assert (!ct_autodetection->protocol_stopped);

  g_assert (ct_autodetection->pending_pings > 0);
  --ct_autodetection->pending_pings;

  if (ct_autodetection->pending_pings == 0)
    {
      g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
      g_clear_pointer (&ct_autodetection->ping_source, g_source_unref);
      g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);

      sequence_number_ready = last_sequence_number_ready;
    }

  grd_rdp_network_autodetection_emit_ping (network_autodetection,
                                           sequence_number_ready,
                                           ct_autodetection);

  if (ct_autodetection->pending_pings == 0)
    return G_SOURCE_REMOVE;

  return G_SOURCE_CONTINUE;
}

static void
start_ping_emission (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  emit_ping (ct_autodetection);

  g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
  ct_autodetection->ping_source = g_timeout_source_new (CT_PING_INTERVAL_MS);

  g_source_set_callback (ct_autodetection->ping_source, emit_ping,
                         ct_autodetection, NULL);
  g_source_attach (ct_autodetection->ping_source, NULL);
  g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);
}

static void
notify_network_characteristics_result (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  rdpAutoDetect *rdp_autodetect = ct_autodetection->rdp_autodetect;
  rdpNetworkCharacteristicsResult result = {};
  uint64_t bit_count;
  uint32_t bandwidth_kbits;

  bit_count = ((uint64_t) ct_autodetection->last_byte_count) * UINT64_C (8);
  bandwidth_kbits = bit_count / MAX (ct_autodetection->last_time_delta_ms, 1);

  result.type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT;
  result.baseRTT = ct_autodetection->base_round_trip_time_us / 1000;
  result.averageRTT = ct_autodetection->avg_round_trip_time_us / 1000;
  result.bandwidth = bandwidth_kbits;

  rdp_autodetect->NetworkCharacteristicsResult (rdp_autodetect,
                                                RDP_TRANSPORT_TCP,
                                                CT_NON_RTT_SEQUENCE_NUMBER,
                                                &result);
}

static FREERDP_AUTODETECT_STATE
detect_network_characteristics (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  CtAutodetectState new_state = CT_AUTODETECT_STATE_NONE;
  g_autoptr (GMutexLocker) locker = NULL;
  gboolean pending_bw_request = FALSE;
  gboolean pending_ping_emission = FALSE;
  gboolean pending_net_char_result = FALSE;
  uint16_t payload_lengths = 0;
  uint32_t n_payloads = 1;

  locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
  switch (ct_autodetection->state)
    {
    case CT_AUTODETECT_STATE_NONE:
      g_assert_not_reached ();
    case CT_AUTODETECT_STATE_MEASURE_BW_1:
    case CT_AUTODETECT_STATE_MEASURE_BW_2:
    case CT_AUTODETECT_STATE_MEASURE_BW_3:
      determine_bw_measure_payloads (ct_autodetection,
                                     &payload_lengths, &n_payloads);
      ct_autodetection->bw_measure_time_us = g_get_monotonic_time ();
      new_state = get_next_bw_measure_state (ct_autodetection);
      pending_bw_request = TRUE;
      break;
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
      return FREERDP_AUTODETECT_STATE_REQUEST;
    case CT_AUTODETECT_STATE_START_RTT_DETECTION:
      g_assert (!ct_autodetection->ping_source);
      g_assert (ct_autodetection->pending_pings >= 2);

      new_state = CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE;
      pending_ping_emission = TRUE;
      break;
    case CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE:
      return FREERDP_AUTODETECT_STATE_REQUEST;
    case CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT:
      new_state = CT_AUTODETECT_STATE_COMPLETE;
      pending_net_char_result = TRUE;
      break;
    case CT_AUTODETECT_STATE_COMPLETE:
      return FREERDP_AUTODETECT_STATE_COMPLETE;
    }

  g_assert (new_state != CT_AUTODETECT_STATE_NONE);
  transition_to_state (ct_autodetection, new_state);
  g_clear_pointer (&locker, g_mutex_locker_free);

  if (pending_bw_request)
    {
      measure_connect_time_bandwidth (ct_autodetection, payload_lengths,
                                      n_payloads);
    }

  if (pending_ping_emission)
    start_ping_emission (ct_autodetection);

  if (pending_net_char_result)
    {
      notify_network_characteristics_result (ct_autodetection);
      return FREERDP_AUTODETECT_STATE_COMPLETE;
    }

  return FREERDP_AUTODETECT_STATE_REQUEST;
}

void
grd_rdp_connect_time_autodetection_start_detection (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
  g_assert (ct_autodetection->state == CT_AUTODETECT_STATE_NONE);

  transition_to_state (ct_autodetection, CT_AUTODETECT_STATE_MEASURE_BW_1);
  g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);

  detect_network_characteristics (ct_autodetection);
}

void
grd_rdp_connect_time_autodetection_invoke_shutdown (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  g_mutex_lock (&ct_autodetection->net_char_sync_mutex);
  ct_autodetection->protocol_stopped = TRUE;
  g_cond_signal (&ct_autodetection->net_char_sync_cond);
  g_mutex_unlock (&ct_autodetection->net_char_sync_mutex);
}

static uint32_t
get_bw_measure_phase (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  switch (ct_autodetection->state)
    {
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
      return 1;
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
      return 2;
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
      return 3;
    default:
      g_assert_not_reached ();
    }

  return 0;
}

BOOL
grd_rdp_connect_time_autodetection_notify_bw_measure_results (GrdRdpConnectTimeAutodetection *ct_autodetection,
                                                              uint32_t                        time_delta_ms,
                                                              uint32_t                        byte_count)
{
  CtAutodetectState new_state = CT_AUTODETECT_STATE_NONE;
  g_autoptr (GMutexLocker) locker = NULL;
  int64_t current_time_us;
  int64_t response_latency_us;

  locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
  switch (ct_autodetection->state)
    {
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
      current_time_us = g_get_monotonic_time ();
      response_latency_us = current_time_us - ct_autodetection->bw_measure_time_us;

      if (response_latency_us < CT_BW_MAX_TOLERABLE_RESPONSE_LATENCY_US &&
          time_delta_ms < CT_BW_MAX_TOLERABLE_TIME_DELTA_MS)
        new_state = get_next_bw_measure_state (ct_autodetection);
      else
        new_state = CT_AUTODETECT_STATE_START_RTT_DETECTION;
      break;
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
      new_state = CT_AUTODETECT_STATE_START_RTT_DETECTION;
      break;
    default:
      g_warning ("[RDP] Connect-Time Autodetect: Received stray Bandwidth "
                 "Measure Results PDU (current state: %s)",
                 ct_autodetect_state_to_string (ct_autodetection->state));
      return FALSE;
    }

  g_debug ("[RDP] Connect-Time Autodetect: BW Measure Phase %u: "
           "time delta: %ums, byte count: %u => %uKB/s",
           get_bw_measure_phase (ct_autodetection), time_delta_ms, byte_count,
           byte_count / MAX (time_delta_ms, 1));

  ct_autodetection->last_time_delta_ms = time_delta_ms;
  ct_autodetection->last_byte_count = byte_count;

  g_assert (new_state != CT_AUTODETECT_STATE_NONE);
  transition_to_state (ct_autodetection, new_state);

  return TRUE;
}

void
grd_rdp_connect_time_autodetection_notify_rtt_measure_response (GrdRdpConnectTimeAutodetection *ct_autodetection,
                                                                uint16_t                        sequence_number,
                                                                int64_t                         base_round_trip_time_us,
                                                                int64_t                         avg_round_trip_time_us)
{
  g_autoptr (GMutexLocker) locker = NULL;

  locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
  if (ct_autodetection->pending_last_sequence_number ||
      ct_autodetection->last_sequence_number != sequence_number)
    return;

  g_assert (ct_autodetection->state == CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE);

  g_debug ("[RDP] Connect-Time Autodetect: base RTT: %lims, average RTT: %lims",
           base_round_trip_time_us / 1000, avg_round_trip_time_us / 1000);

  ct_autodetection->base_round_trip_time_us = base_round_trip_time_us;
  ct_autodetection->avg_round_trip_time_us = avg_round_trip_time_us;

  transition_to_state (ct_autodetection, CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT);
}

static gboolean
sync_with_main_context (gpointer user_data)
{
  GrdRdpConnectTimeAutodetection *ct_autodetection = user_data;

  g_mutex_lock (&ct_autodetection->ct_autodetection_mutex);
  if (ct_autodetection->ping_source)
    {
      g_source_destroy (ct_autodetection->ping_source);
      g_clear_pointer (&ct_autodetection->ping_source, g_source_unref);
    }
  g_mutex_unlock (&ct_autodetection->ct_autodetection_mutex);

  g_mutex_lock (&ct_autodetection->net_char_sync_mutex);
  g_clear_pointer (&ct_autodetection->sync_source, g_source_unref);

  ct_autodetection->pending_net_char_sync = FALSE;
  g_cond_signal (&ct_autodetection->net_char_sync_cond);
  g_mutex_unlock (&ct_autodetection->net_char_sync_mutex);

  return G_SOURCE_REMOVE;
}

static void
sync_ct_autodetect_handling (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  g_mutex_lock (&ct_autodetection->net_char_sync_mutex);
  ct_autodetection->pending_net_char_sync = TRUE;

  ct_autodetection->sync_source = g_idle_source_new ();
  g_source_set_callback (ct_autodetection->sync_source, sync_with_main_context,
                         ct_autodetection, NULL);
  g_source_attach (ct_autodetection->sync_source, NULL);

  while (!ct_autodetection->protocol_stopped &&
         ct_autodetection->pending_net_char_sync)
    {
      g_cond_wait (&ct_autodetection->net_char_sync_cond,
                   &ct_autodetection->net_char_sync_mutex);
    }
  g_mutex_unlock (&ct_autodetection->net_char_sync_mutex);
}

static BOOL
autodetect_network_characteristics_sync (rdpAutoDetect      *rdp_autodetect,
                                         RDP_TRANSPORT_TYPE  transport_type,
                                         uint16_t            sequenceNumber,
                                         uint32_t            bandwidth,
                                         uint32_t            rtt)
{
  GrdRdpNetworkAutodetection *network_autodetection = rdp_autodetect->custom;
  GrdRdpConnectTimeAutodetection *ct_autodetection =
    grd_rdp_network_autodetection_get_ct_handler (network_autodetection);
  g_autoptr (GMutexLocker) locker = NULL;
  gboolean pending_sync = FALSE;

  locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
  switch (ct_autodetection->state)
    {
    case CT_AUTODETECT_STATE_NONE:
    case CT_AUTODETECT_STATE_MEASURE_BW_1:
    case CT_AUTODETECT_STATE_MEASURE_BW_2:
    case CT_AUTODETECT_STATE_MEASURE_BW_3:
      g_assert_not_reached ();
      break;
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_1:
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_2:
    case CT_AUTODETECT_STATE_AWAIT_BW_RESULT_3:
      g_assert (!pending_sync);
      break;
    case CT_AUTODETECT_STATE_START_RTT_DETECTION:
      g_assert_not_reached ();
      break;
    case CT_AUTODETECT_STATE_AWAIT_LAST_RTT_RESPONSE:
      pending_sync = TRUE;
      break;
    case CT_AUTODETECT_STATE_SEND_NET_CHAR_RESULT:
      g_assert_not_reached ();
      break;
    case CT_AUTODETECT_STATE_COMPLETE:
      g_warning ("[RDP] Connect-Time Autodetect: Received stray Network "
                 "Characteristics Sync PDU (current state: %s). Ignoring PDU...",
                 ct_autodetect_state_to_string (ct_autodetection->state));
      return TRUE;
    }
  g_clear_pointer (&locker, g_mutex_locker_free);

  if (pending_sync)
    sync_ct_autodetect_handling (ct_autodetection);

  g_debug ("[RDP] Connect-Time Autodetect: Received Sync PDU: "
           "RTT: %ums, bandwidth: %uKBit/s", rtt, bandwidth);

  locker = g_mutex_locker_new (&ct_autodetection->ct_autodetection_mutex);
  transition_to_state (ct_autodetection, CT_AUTODETECT_STATE_COMPLETE);

  return TRUE;
}

static FREERDP_AUTODETECT_STATE
autodetect_on_connect_time_autodetect_progress (rdpAutoDetect *rdp_autodetect)
{
  GrdRdpNetworkAutodetection *network_autodetection = rdp_autodetect->custom;
  GrdRdpConnectTimeAutodetection *ct_autodetection =
    grd_rdp_network_autodetection_get_ct_handler (network_autodetection);

  return detect_network_characteristics (ct_autodetection);
}

GrdRdpConnectTimeAutodetection *
grd_rdp_connect_time_autodetection_new (GrdRdpNetworkAutodetection *network_autodetection,
                                        rdpAutoDetect              *rdp_autodetect)
{
  GrdRdpConnectTimeAutodetection *ct_autodetection;

  ct_autodetection = g_object_new (GRD_TYPE_RDP_CONNECT_TIME_AUTODETECTION, NULL);
  ct_autodetection->network_autodetection = network_autodetection;
  ct_autodetection->rdp_autodetect = rdp_autodetect;

  rdp_autodetect->NetworkCharacteristicsSync = autodetect_network_characteristics_sync;
  rdp_autodetect->OnConnectTimeAutoDetectProgress = autodetect_on_connect_time_autodetect_progress;

  return ct_autodetection;
}

static void
grd_rdp_connect_time_autodetection_dispose (GObject *object)
{
  GrdRdpConnectTimeAutodetection *ct_autodetection = GRD_RDP_CONNECT_TIME_AUTODETECTION (object);

  g_assert (ct_autodetection->protocol_stopped);

  if (ct_autodetection->sync_source)
    {
      g_source_destroy (ct_autodetection->sync_source);
      g_clear_pointer (&ct_autodetection->sync_source, g_source_unref);
    }
  if (ct_autodetection->ping_source)
    {
      g_source_destroy (ct_autodetection->ping_source);
      g_clear_pointer (&ct_autodetection->ping_source, g_source_unref);
    }

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

static void
grd_rdp_connect_time_autodetection_finalize (GObject *object)
{
  GrdRdpConnectTimeAutodetection *ct_autodetection = GRD_RDP_CONNECT_TIME_AUTODETECTION (object);

  g_cond_clear (&ct_autodetection->net_char_sync_cond);
  g_mutex_clear (&ct_autodetection->net_char_sync_mutex);
  g_mutex_clear (&ct_autodetection->ct_autodetection_mutex);

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

static void
grd_rdp_connect_time_autodetection_init (GrdRdpConnectTimeAutodetection *ct_autodetection)
{
  ct_autodetection->state = CT_AUTODETECT_STATE_NONE;

  ct_autodetection->pending_pings = CT_N_PINGS;
  ct_autodetection->pending_last_sequence_number = TRUE;

  g_mutex_init (&ct_autodetection->ct_autodetection_mutex);
  g_mutex_init (&ct_autodetection->net_char_sync_mutex);
  g_cond_init (&ct_autodetection->net_char_sync_cond);
}

static void
grd_rdp_connect_time_autodetection_class_init (GrdRdpConnectTimeAutodetectionClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = grd_rdp_connect_time_autodetection_dispose;
  object_class->finalize = grd_rdp_connect_time_autodetection_finalize;
}
