gint er_dtls_connection_send(ErDtlsConnection *self, gpointer data, gint len)
{
    g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), 0);
    int ret = 0;

    g_return_val_if_fail(self->priv->ssl, 0);
    g_return_val_if_fail(self->priv->bio, 0);

    LOG_TRACE(self, "locking @ send");
    g_mutex_lock(&self->priv->mutex);
    LOG_TRACE(self, "locked @ send");

    if (SSL_is_init_finished(self->priv->ssl)) {
        ret = SSL_write(self->priv->ssl, data, len);
        LOG_DEBUG(self, "data sent: input was %d B, output is %d B", len, ret);
    } else {
        LOG_WARNING(self, "tried to send data before handshake was complete");
        ret = 0;
    }

    LOG_TRACE(self, "unlocking @ send");
    g_mutex_unlock(&self->priv->mutex);

    return ret;
}
void er_dtls_connection_close(ErDtlsConnection *self)
{
    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
    g_return_if_fail(self->priv->ssl);
    g_return_if_fail(self->priv->bio);

    LOG_DEBUG(self, "closing connection");

    LOG_TRACE(self, "locking @ close");
    g_mutex_lock(&self->priv->mutex);
    LOG_TRACE(self, "locked @ close");

    if (self->priv->is_alive) {
        self->priv->is_alive = FALSE;
        g_cond_signal(&self->priv->condition);
    }

    LOG_TRACE(self, "unlocking @ close");
    g_mutex_unlock(&self->priv->mutex);

    if (self->priv->thread) {
        g_thread_join(self->priv->thread);
        self->priv->thread = NULL;
    }

    LOG_DEBUG(self, "closed connection");
}
void er_dtls_connection_start_timeout(ErDtlsConnection *self)
{
    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));

    ErDtlsConnectionPrivate *priv = self->priv;
    GError *error = NULL;
    gchar *thread_name = g_strdup_printf("connection_thread_%p", self);

    LOG_TRACE(self, "locking @ start_timeout");
    g_mutex_lock(&priv->mutex);
    LOG_TRACE(self, "locked @ start_timeout");

    LOG_INFO(self, "starting connection timeout");
    priv->thread = g_thread_try_new(thread_name,
            (GThreadFunc) connection_timeout_thread_func, self, &error);
    if (error) {
        LOG_WARNING(self, "error creating connection thread: %s (%d)",
                error->message, error->code);
        g_clear_error(&error);
    }

    g_free(thread_name);

    LOG_TRACE(self, "unlocking @ start_timeout");
    g_mutex_unlock(&priv->mutex);
}
void er_dtls_connection_start(ErDtlsConnection *self, gboolean is_client)
{
    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
    ErDtlsConnectionPrivate *priv = self->priv;
    g_return_if_fail(priv->send_closure);
    g_return_if_fail(priv->ssl);
    g_return_if_fail(priv->bio);

    LOG_TRACE(self, "locking @ start");
    g_mutex_lock(&priv->mutex);
    LOG_TRACE(self, "locked @ start");

    priv->is_alive = TRUE;
    priv->timeout_set = FALSE;
    priv->bio_buffer = NULL;
    priv->bio_buffer_len = 0;
    priv->bio_buffer_offset = 0;
    priv->keys_exported = FALSE;

    priv->is_client = is_client;
    if (priv->is_client) {
        SSL_set_connect_state(priv->ssl);
    } else {
        SSL_set_accept_state(priv->ssl);
    }
    log_state(self, "initial state set");

    openssl_poll(self);

    log_state(self, "first poll done");
    priv->thread = NULL;

    LOG_TRACE(self, "unlocking @ start");
    g_mutex_unlock(&priv->mutex);
}
void er_dtls_connection_set_send_callback(ErDtlsConnection *self, GClosure *closure)
{
    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));

    LOG_TRACE(self, "locking @ set_send_callback");
    g_mutex_lock(&self->priv->mutex);
    LOG_TRACE(self, "locked @ set_send_callback");

    self->priv->send_closure = closure;

    if (closure && G_CLOSURE_NEEDS_MARSHAL(closure)) {
        g_closure_set_marshal(closure, g_cclosure_marshal_generic);
    }

    LOG_TRACE(self, "unlocking @ set_send_callback");
    g_mutex_unlock(&self->priv->mutex);
}
static void on_key_received(ErDtlsConnection *connection, gpointer key, guint cipher, guint auth, GstErDtlsEnc *self)
{
    gpointer key_dup;
    gchar *key_str;

    g_return_if_fail(GST_IS_ER_DTLS_ENC(self));
    g_return_if_fail(ER_IS_DTLS_CONNECTION(connection));

    self->srtp_cipher = cipher;
    self->srtp_auth = auth;

    key_dup = g_memdup(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
    self->encoder_key = gst_buffer_new_wrapped(key_dup, ER_DTLS_SRTP_MASTER_KEY_LENGTH);

    key_str = g_base64_encode(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH);
    GST_INFO_OBJECT(self, "received key: %s", key_str);
    g_free(key_str);

    g_signal_emit(self, signals[SIGNAL_ON_KEY_RECEIVED], 0);
}
gint er_dtls_connection_process(ErDtlsConnection *self, gpointer data, gint len)
{
    g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), 0);
    ErDtlsConnectionPrivate *priv = self->priv;
    gint result;

    g_return_val_if_fail(self->priv->ssl, 0);
    g_return_val_if_fail(self->priv->bio, 0);

    LOG_TRACE(self, "locking @ process");
    g_mutex_lock(&priv->mutex);
    LOG_TRACE(self, "locked @ process");

    g_warn_if_fail(!priv->bio_buffer);

    priv->bio_buffer = data;
    priv->bio_buffer_len = len;
    priv->bio_buffer_offset = 0;

    log_state(self, "process start");

    if (SSL_want_write(priv->ssl)) {
        openssl_poll(self);
        log_state(self, "process want write, after poll");
    }

    result = SSL_read(priv->ssl, data, len);

    log_state(self, "process after read");

    openssl_poll(self);

    log_state(self, "process after poll");

    LOG_DEBUG(self, "read result: %d", result);

    LOG_TRACE(self, "unlocking @ process");
    g_mutex_unlock(&priv->mutex);

    return result;
}
void er_dtls_connection_stop(ErDtlsConnection *self)
{
    g_return_if_fail(ER_IS_DTLS_CONNECTION(self));
    g_return_if_fail(self->priv->ssl);
    g_return_if_fail(self->priv->bio);

    LOG_DEBUG(self, "stopping connection");

    LOG_TRACE(self, "locking @ stop");
    g_mutex_lock(&self->priv->mutex);
    LOG_TRACE(self, "locked @ stop");

    self->priv->is_alive = FALSE;
    LOG_TRACE(self, "signaling @ stop");
    g_cond_signal(&self->priv->condition);
    LOG_TRACE(self, "signaled @ stop");

    LOG_TRACE(self, "unlocking @ stop");
    g_mutex_unlock(&self->priv->mutex);

    LOG_DEBUG(self, "stopped connection");
}
static int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
    ErDtlsConnection *self;
    SSL *ssl;
    BIO *bio;
    gchar *pem = NULL;
    gboolean accepted = FALSE;

    ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
    self = SSL_get_ex_data(ssl, connection_ex_index);
    g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), FALSE);

    pem = _er_dtls_x509_to_pem(x509_ctx->cert);

    if (!pem) {
        LOG_WARNING(self, "failed to convert received certificate to pem format");
    } else {
        bio = BIO_new(BIO_s_mem());
        if (bio) {
            gchar buffer[2048];
            gint len;

            len = X509_NAME_print_ex(bio, X509_get_subject_name(x509_ctx->cert), 1, XN_FLAG_MULTILINE);
            BIO_read(bio, buffer, len);
            buffer[len] = '\0';
            LOG_DEBUG(self, "Peer certificate received:\n%s", buffer);
            BIO_free(bio);
        } else {
            LOG_DEBUG(self, "failed to create certificate print membio");
        }

        g_signal_emit(self, signals[SIGNAL_ON_PEER_CERTIFICATE], 0, pem, &accepted);
        g_free(pem);
    }

    return accepted;
}