--- a/xeme/meson.build Thu Dec 07 21:44:47 2023 -0600
+++ b/xeme/meson.build Fri Dec 08 00:05:46 2023 -0600
@@ -1,5 +1,6 @@
@@ -10,6 +11,8 @@
@@ -54,12 +57,28 @@
XEME_BUILT_HEADERS += xeme_h
###############################################################################
+############################################################################### +xeme_enums = gnome.mkenums_simple( + sources : XEME_ENUM_HEADERS, + install_dir : get_option('includedir') / 'xeme-1.0' / 'xeme') +XEME_GENERATED_SOURCES += xeme_enums[0] +XEME_BUILT_HEADERS += xeme_enums[1] +############################################################################### ###############################################################################
xeme_inc = include_directories('.')
xeme_lib = library('xeme',
- XEME_SOURCES + XEME_HEADERS,
+ XEME_SOURCES + XEME_HEADERS + XEME_GENERATED_SOURCES + XEME_BUILT_HEADERS, c_args : ['-DXEME_COMPILATION', '-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Xeme"'],
gnu_symbol_visibility : 'hidden',
dependencies : [gio_dep, glib_dep, gobject_dep],
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xeme/xemeconnection.c Fri Dec 08 00:05:46 2023 -0600
@@ -0,0 +1,558 @@
+ * Copyright (C) 2023 Xeme Developers + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <https://www.gnu.org/licenses/>. +#include "xemeconnection.h" +#include "xemeaddress.h" +#include "xemeconstants.h" +static GParamSpec *properties[N_PROPERTIES] = {NULL, 0}; +struct _XemeConnection { + char *domain; /* This is parsed out of the jid when it is set. */ + GSocketConnection *socket_connection; + XemeInputStream *input; + GCancellable *cancellable; +G_DEFINE_TYPE(XemeConnection, xeme_connection, G_TYPE_OBJECT) +/****************************************************************************** + *****************************************************************************/ +xeme_connection_set_jid(XemeConnection *connection, const char *jid) { + if(!xeme_address_parse(jid, &bare_jid, NULL, &domain, &resource)) { + g_critical("invalid jid '%s'", jid); + g_free(connection->jid); + connection->jid = bare_jid; + connection->domain = domain; + xeme_connection_set_resource(connection, resource); +xeme_connection_connect_success(XemeConnection *connection, GTask *task) { + XemeOutputStream *output = NULL; + GIOStream *iostream = NULL; + GInputStream *istream = NULL; + GOutputStream *ostream = NULL; + iostream = G_IO_STREAM(connection->socket_connection); + istream = g_io_stream_get_input_stream(iostream); + if(!xeme_input_stream_start(connection->input, istream, &error)) { + g_task_return_error(task, error); + xeme_connection_close(connection, NULL); + ostream = g_io_stream_get_output_stream(iostream); + output = xeme_output_stream_new(connection->domain, connection->jid, + connection->cancellable); + if(!xeme_output_stream_start(output, ostream, &error)) { + g_task_return_error(task, error); + xeme_connection_close(connection, NULL); + g_task_return_pointer(task, output, g_object_unref); +/****************************************************************************** + * connect_to_host functions + *****************************************************************************/ +xeme_connection_connect_to_host_cb(GObject *source, GAsyncResult *result, + XemeConnection *connection = NULL; + GSocketClient *client = G_SOCKET_CLIENT(source); + GSocketConnection *socket_connection = NULL; + connection = g_task_get_source_object(task); + socket_connection = g_socket_client_connect_to_host_finish(client, result, + g_task_return_error(task, error); + connection->socket_connection = socket_connection; + xeme_connection_connect_success(connection, task); +xeme_connection_connect_to_host(XemeConnection *connection, GTask *task) { + gboolean use_tls = FALSE; + const char *server = NULL; + g_return_if_fail(XEME_IS_CONNECTION(connection)); + g_return_if_fail(G_IS_TASK(task)); + use_tls = (connection->tls_mode == XEME_TLS_MODE_DIRECT_TLS); + g_socket_client_set_tls(connection->client, use_tls); + server = connection->server; + if(xeme_str_is_empty(server)) { + server = connection->domain; + g_socket_client_connect_to_host_async(connection->client, + connection->cancellable, + xeme_connection_connect_to_host_cb, +/****************************************************************************** + * connect_to_service functions + *****************************************************************************/ +xeme_connection_connect_to_service_cb(GObject *source, GAsyncResult *result, + XemeConnection *connection = NULL; + GSocketClient *client = G_SOCKET_CLIENT(source); + GSocketConnection *socket_connection = NULL; + connection = g_task_get_source_object(task); + socket_connection = g_socket_client_connect_to_service_finish(client, + g_warning("failed to connect via srv record, attempting direct " + xeme_connection_connect_to_host(connection, task); + connection->socket_connection = socket_connection; + xeme_connection_connect_success(connection, task); +xeme_connection_connect_to_service(XemeConnection *connection, GTask *task) { + gboolean use_tls = TRUE; + const char *service = XEME_SERVICE_XMPPS_CLIENT; + g_return_if_fail(XEME_IS_CONNECTION(connection)); + g_return_if_fail(G_IS_TASK(task)); + if(connection->tls_mode != XEME_TLS_MODE_DIRECT_TLS) { + service = XEME_SERVICE_XMPP_CLIENT; + g_socket_client_set_tls(connection->client, use_tls); + g_socket_client_connect_to_service_async(connection->client, + connection->cancellable, + xeme_connection_connect_to_service_cb, +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +xeme_connection_finalize(GObject *obj) { + XemeConnection *connection = XEME_CONNECTION(obj); + xeme_connection_close(connection, NULL); + g_clear_pointer(&connection->jid, g_free); + g_clear_pointer(&connection->domain, g_free); + g_clear_pointer(&connection->resource, g_free); + g_clear_pointer(&connection->server, g_free); + g_clear_object(&connection->client); + g_clear_object(&connection->socket_connection); + g_clear_object(&connection->cancellable); + G_OBJECT_CLASS(xeme_connection_parent_class)->finalize(obj); +xeme_connection_get_property(GObject *obj, guint param_id, GValue *value, + XemeConnection *connection = XEME_CONNECTION(obj); + g_value_set_string(value, xeme_connection_get_jid(connection)); + g_value_set_string(value, xeme_connection_get_resource(connection)); + g_value_set_string(value, xeme_connection_get_server(connection)); + g_value_set_uint(value, xeme_connection_get_port(connection)); + g_value_set_enum(value, xeme_connection_get_tls_mode(connection)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +xeme_connection_set_property(GObject *obj, guint param_id, const GValue *value, + XemeConnection *connection = XEME_CONNECTION(obj); + xeme_connection_set_jid(connection, g_value_get_string(value)); + xeme_connection_set_resource(connection, g_value_get_string(value)); + xeme_connection_set_server(connection, g_value_get_string(value)); + xeme_connection_set_port(connection, g_value_get_uint(value)); + xeme_connection_set_tls_mode(connection, g_value_get_enum(value)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +xeme_connection_init(G_GNUC_UNUSED XemeConnection *connection) { +xeme_connection_class_init(XemeConnectionClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->finalize = xeme_connection_finalize; + obj_class->get_property = xeme_connection_get_property; + obj_class->set_property = xeme_connection_set_property; + * The "Jabber ID" which this connection originates from. + properties[PROP_JID] = g_param_spec_string( + "The jabber id of the originator of this connection.", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + * XemeConnection:resource: + * The "resource" for the originator of this connection. + * If the `jid` passed to [ctor@Connection.new] includes a resource, it will be + * parsed out and saved here. + * If this %NULL during connection, @connection will request one from the + * server and store it here which should be saved by the application. + * Resources are mostly deprecated, but this property can be modify to set + * it to whatever you like. + properties[PROP_RESOURCE] = g_param_spec_string( + "resource", "resource", + "The resource for the originator of this connection.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + * XemeConnection:server: + * An explicit server to connect to. + * If this is set, it will be the only connection attempt made. See + * [method@Connection.connect_async] for more information. + properties[PROP_SERVER] = g_param_spec_string( + "An explicit server to connect to.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + * The explicit port to use. + * This will only be used if [property@Connection:server] is not %NULL. + properties[PROP_PORT] = g_param_spec_uint( + "An explicit port to use.", + 0, G_MAXUINT16, XEME_PORT_TLS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + * XemeConnection:tls-mode: + * The TLS mode to use for this connection. + * This determines whether or not a TLS connection is attempted at all or + * if STARTTLS is used to upgrade a clear connection. + properties[PROP_TLS_MODE] = g_param_spec_enum( + "tls-mode", "tls-mode", + "The mode of TLS to use for this connection.", + XEME_TLS_MODE_DIRECT_TLS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); +/****************************************************************************** + *****************************************************************************/ +xeme_connection_new(const char *jid) { + g_return_val_if_fail(!xeme_str_is_empty(jid), NULL); +xeme_connection_get_jid(XemeConnection *connection) { + g_return_val_if_fail(XEME_IS_CONNECTION(connection), NULL); + return connection->jid; +xeme_connection_get_resource(XemeConnection *connection) { + g_return_val_if_fail(XEME_IS_CONNECTION(connection), NULL); + return connection->resource; +xeme_connection_set_resource(XemeConnection *connection, const char *resource) + g_return_if_fail(XEME_IS_CONNECTION(connection)); + if(!xeme_str_equal(connection->resource, resource)) { + g_free(connection->resource); + connection->resource = g_strdup(resource); + g_object_notify_by_pspec(G_OBJECT(connection), + properties[PROP_RESOURCE]); +xeme_connection_get_server(XemeConnection *connection) { + g_return_val_if_fail(XEME_IS_CONNECTION(connection), NULL); + return connection->server; +xeme_connection_set_server(XemeConnection *connection, const char *server) { + g_return_if_fail(XEME_IS_CONNECTION(connection)); + if(!xeme_str_equal(connection->server, server)) { + g_free(connection->server); + connection->server = g_strdup(server); + g_object_notify_by_pspec(G_OBJECT(connection), + properties[PROP_SERVER]); +xeme_connection_get_port(XemeConnection *connection) { + g_return_val_if_fail(XEME_IS_CONNECTION(connection), 0); + return connection->port; +xeme_connection_set_port(XemeConnection *connection, guint16 port) { + g_return_if_fail(XEME_IS_CONNECTION(connection)); + if(connection->port != port) { + connection->port = port; + g_object_notify_by_pspec(G_OBJECT(connection), properties[PROP_PORT]); +xeme_connection_get_tls_mode(XemeConnection *connection) { + g_return_val_if_fail(XEME_IS_CONNECTION(connection), XEME_TLS_MODE_DIRECT_TLS); + return connection->tls_mode; +xeme_connection_set_tls_mode(XemeConnection *connection, XemeTlsMode tls_mode) + g_return_if_fail(XEME_IS_CONNECTION(connection)); + if(connection->tls_mode != tls_mode) { + connection->tls_mode = tls_mode; + g_object_notify_by_pspec(G_OBJECT(connection), + properties[PROP_TLS_MODE]); +xeme_connection_connect_async(XemeConnection *connection, + XemeInputStream *input, + GProxyResolver *resolver, + GCancellable *cancellable, + GAsyncReadyCallback callback, + g_return_if_fail(XEME_IS_CONNECTION(connection)); + g_return_if_fail(XEME_IS_INPUT_STREAM(input)); + if(G_IS_CANCELLABLE(cancellable)) { + connection->cancellable = g_object_ref(cancellable); + connection->input = g_object_ref(input); + connection->client = g_socket_client_new(); + if(G_IS_PROXY_RESOLVER(resolver)) { + g_socket_client_set_proxy_resolver(connection->client, resolver); + g_clear_object(&resolver); + task = g_task_new(connection, cancellable, callback, data); + /* We're doing a direct connect to a server. */ + if(!xeme_str_is_empty(connection->server)) { + xeme_connection_connect_to_host(connection, task); + xeme_connection_connect_to_service(connection, task); +xeme_connection_connect_finish(XemeConnection *connection, + g_return_val_if_fail(XEME_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE); + return g_task_propagate_pointer(G_TASK(result), error); +xeme_connection_close(XemeConnection *connection, GError **error) { + g_return_val_if_fail(XEME_IS_CONNECTION(connection), FALSE); + if(G_IS_IO_STREAM(connection->socket_connection)) { + /* We're shutting down so we ignore errors. */ + g_io_stream_close(G_IO_STREAM(connection->socket_connection), NULL, + g_clear_object(&connection->socket_connection); + g_clear_object(&connection->input); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xeme/xemeconnection.h Fri Dec 08 00:05:46 2023 -0600
@@ -0,0 +1,281 @@
+ * Copyright (C) 2023 Xeme Developers + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <https://www.gnu.org/licenses/>. +#ifndef XEME_CONNECTION_H +#define XEME_CONNECTION_H +#include <glib-object.h> +#include "xemeinputstream.h" +#include "xemeoutputstream.h" +#include "xemeversion.h" +#define XEME_TYPE_CONNECTION (xeme_connection_get_type()) +G_DECLARE_FINAL_TYPE(XemeConnection, xeme_connection, XEME, CONNECTION, + * @XEME_TLS_MODE_DIRECT_TLS: Use direct TLS. + * @XEME_TLS_MODE_START_TLS: Use start TLS. + * @XEME_TLS_MODE_NONE: Don't use TLS. This should really only be used in rare + * circumstances like development. + * An enumeration representing which mode of TLS to use. + XEME_TLS_MODE_DIRECT_TLS, + XEME_TLS_MODE_START_TLS, + * @jid: (not nullable): The JID of the originator of this connection. + * Creates a new input stream. + * [resource](https://www.rfc-editor.org/rfc/rfc6122#section-2), it will be + * parsed out and stored in [property@Connection:resource]. + * Returns: (transfer full): The new instance. +XemeConnection *xeme_connection_new(const char *jid); + * xeme_connection_get_jid: + * @connection: The instance. + * Gets the bare [JID](https://www.rfc-editor.org/rfc/rfc6122#section-2) from + * Returns: The bare jid. +const char *xeme_connection_get_jid(XemeConnection *connection); + * xeme_connection_get_resource: + * @connection: The instance. + * Gets the resource from @connection. + * This may be %NULL in rare circumstances. However, @connection will request a + * JID from the server during connection if this is %NULL. + * Returns: (transfer none) (nullable): The resource if set otherwise %NULL. +const char *xeme_connection_get_resource(XemeConnection *connection); + * xeme_connection_set_resource: + * @connection: The instance. + * @resource: (not nullable): The new resource. + * Sets the resource of @connection to @resource. +void xeme_connection_set_resource(XemeConnection *connection, const char *resource); + * xeme_connection_get_cancellable: + * @connection: The instance. + * Gets the [class@Gio.Cancellable] for @connection. + * Returns: (transfer none) (nullable): The canncellable if one is set, +GCancellable *xeme_connection_get_cancellable(XemeConnection *connection); + * xeme_connection_get_server: + * @connection: The instance. + * Gets the explicit server that @connection should connect to. + * See [method@Connection.connect_async] for more information. + * Returns: (nullable): The explicit server to connect to or %NULL. +const char *xeme_connection_get_server(XemeConnection *connection); + * xeme_connection_set_server: + * @connection: The instance. + * @server: (nullable): The new explicit server to connect to. + * Sets the explicit server to connect to. + * Passing %NULL for @server will unset any previous value. +void xeme_connection_set_server(XemeConnection *connection, const char *server); + * xeme_connection_get_port: + * @connection: The instance. + * Gets the port from @connection. + * Returns: The port to use. +guint16 xeme_connection_get_port(XemeConnection *connection); + * xeme_connection_set_port: + * @connection: The instance. + * @port: The new port value. + * Sets the port to use for @connection to @port. +void xeme_connection_set_port(XemeConnection *connection, guint16 port); + * xeme_connection_get_tls_mode: + * @connection: The instance. + * Gets the [enum@TlsMode] from @connection. + * Returns: The TLS mode to use. +XemeTlsMode xeme_connection_get_tls_mode(XemeConnection *connection); + * xeme_connection_set_tls_mode: + * @connection: The instance. + * @tls_mode: The new TLS mode. + * Sets the [enum@TlsMode] of @connection to @tls_mode. +void xeme_connection_set_tls_mode(XemeConnection *connection, XemeTlsMode tls_mode); + * xeme_connection_connection_async: + * @connection: The instance. + * @input: The [class@InputStream] to use. + * @resolver: (transfer full) (nullable): An optional proxy resolver. + * @cancellable: (transfer none) (nullable): An optional cancellable. + * @callback: (scope async) (nullable): A callback function. + * @data: (nullable): User data pass to @callback. + * Starts the connection process. + * If [property@Connection:server] is not %NULL, then this will try to connect + * directly to that server on the port specified by [property@Connection:port]. + * If [property@Connection:server] is %NULL then DNS SRV records will be used + * [RFC 6120](https://www.rfc-editor.org/rfc/rfc6120#section-3.2.4). + * If [property@Connection:tls-mode] is set to `direct-tls`, then the + * `_xmpps-client` service will be queried. If the DNS SRV lookup does not + * return a record, an attempt will be made to connect directly to the domain + * part of [property@Connection:jid] on the port specified by + * [property@Connection:port]. + * If [property@Connection:tls-mode] is set to `starttls` or `none` then the + * `_xmpp-client` service will be queried. If the DNS SRV lookup does not + * return a record, an attempt will be made to connect directly to the domain + * part of [property@Connection:jid] on the port specified by + * [property@Connection:port]. + * If [property@Connection:tls-mode] is set to `starttls` then the connection + * will attempt to upgrade to TLS during the connection process. If this fails, + * the connection will be terminated. +void xeme_connection_connect_async(XemeConnection *connection, XemeInputStream *input, GProxyResolver *resolver, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data); + * xeme_connection_connect_finish: + * @connection: The instance. + * @result: The result passed to the callback. + * @error: A return address for a #GError. + * Finishes a previous call to [method@Connection.connect_async]. + * This should be call from the callback function of + * [method@Connection.connect_async] to get the result of the connection + * Returns: (transfer full): A new [class@OutputStream] or %NULL if the +XemeOutputStream *xeme_connection_connect_finish(XemeConnection *connection, GAsyncResult *result, GError **error); + * xeme_connection_close: + * @connection: The instance. + * @error: (nullable): A return address for a #GError. + * Attempts to close the connection. + * Returns: %TRUE if sucessfull, otherwise %FALSE with @error possibly set. +gboolean xeme_connection_close(XemeConnection *connection, GError **error); +#endif /* XEME_CONNECTION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xeme/xemeconstants.h Fri Dec 08 00:05:46 2023 -0600
@@ -0,0 +1,61 @@
+ * Copyright (C) 2023 Xeme Developers + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <https://www.gnu.org/licenses/>. +#ifndef XEME_CONSTANTS_H +#define XEME_CONSTANTS_H +#include <xeme/xemeversion.h> + * A constant representing the default clear text port. +#define XEME_PORT_CLEAR (5222) XEME_AVAILABLE_MACRO_IN_0_1 + * A constant representing the default TLS port. +#define XEME_PORT_TLS (5223) XEME_AVAILABLE_MACRO_IN_0_1 + * XEME_SERVICE_XMPP_CLIENT: + * A constant representing the xmpp-client service DNS record for clear text +#define XEME_SERVICE_XMPP_CLIENT ("xmpp-client") XEME_AVAILABLE_MACRO_IN_0_1 + * XEME_SERVICE_XMPPS_CLIENT: + * A constant representing the xmpps-client service DNS record for TLS +#define XEME_SERVICE_XMPPS_CLIENT ("xmpps-client") XEME_AVAILABLE_MACRO_IN_0_1 +#endif /* XEME_CONSTANTS_H */