aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac1
-rw-r--r--facebook/Makefile.am6
-rw-r--r--facebook/facebook-api.c185
-rw-r--r--facebook/facebook-api.h23
-rw-r--r--facebook/facebook-mqtt.c1057
-rw-r--r--facebook/facebook-mqtt.h288
-rw-r--r--facebook/facebook-util.c221
-rw-r--r--facebook/facebook-util.h13
-rw-r--r--facebook/facebook.c44
-rw-r--r--facebook/facebook.h3
10 files changed, 1832 insertions, 9 deletions
diff --git a/configure.ac b/configure.ac
index a090e29..9bdf0fb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -84,6 +84,7 @@ AC_ARG_WITH(
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.32.0])
PKG_CHECK_MODULES([BITLBEE], [bitlbee >= 3.2.2])
+PKG_CHECK_MODULES([ZLIB], [zlib])
AS_IF(
[test -z "$plugindir"],
diff --git a/facebook/Makefile.am b/facebook/Makefile.am
index f183717..2cbfab1 100644
--- a/facebook/Makefile.am
+++ b/facebook/Makefile.am
@@ -1,18 +1,20 @@
libdir = $(plugindir)
lib_LTLIBRARIES = facebook.la
-facebook_la_CFLAGS = $(BITLBEE_CFLAGS) $(GLIB_CFLAGS)
-facebook_la_LDFLAGS = $(BITLBEE_LIBS) $(GLIB_LIBS)
+facebook_la_CFLAGS = $(BITLBEE_CFLAGS) $(GLIB_CFLAGS) $(ZLIB_CFLAGS)
+facebook_la_LDFLAGS = $(BITLBEE_LIBS) $(GLIB_LIBS) $(ZLIB_LIBS)
facebook_la_SOURCES = \
facebook.c \
facebook-api.c \
facebook-http.c \
facebook-json.c \
+ facebook-mqtt.c \
facebook-util.c \
facebook.h \
facebook-api.h
facebook-http.h \
facebook-json.h \
+ facebook-mqtt.h \
facebook-util.h
# Build the library as a module
diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c
index 817e19c..a5b601c 100644
--- a/facebook/facebook-api.c
+++ b/facebook/facebook-api.c
@@ -15,7 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <stdarg.h>
#include <string.h>
+#include <sha1.h>
#include "facebook-api.h"
@@ -35,11 +37,112 @@ GQuark fb_api_error_quark(void)
}
/**
+ * Implements #fb_mqtt_funcs->error().
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param err The #GError.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_mqtt_error(fb_mqtt_t *mqtt, GError *err, gpointer data)
+{
+ fb_api_t *api = data;
+
+ if (api->err == NULL) {
+ api->err = g_error_copy(err);
+ fb_api_error(api, 0, NULL);
+ }
+}
+
+/**
+ * Implements #fb_mqtt_funcs->open().
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_mqtt_open(fb_mqtt_t *mqtt, gpointer data)
+{
+ fb_api_t *api = data;
+ gchar *msg;
+
+ static guint8 flags =
+ FB_MQTT_CONNECT_FLAG_USER |
+ FB_MQTT_CONNECT_FLAG_PASS |
+ FB_MQTT_CONNECT_FLAG_CLR;
+
+ msg = g_strdup_printf("{"
+ "\"u\":\"%s\","
+ "\"a\":\"" FB_API_AGENT "\","
+ "\"mqtt_sid\":%s,"
+ "\"d\":\"%s\","
+ "\"chat_on\":true"
+ "}", api->uid, api->mid, api->cuid);
+
+ fb_mqtt_connect(mqtt,
+ flags, /* Flags */
+ api->cid, /* Client identifier */
+ msg, /* Will message */
+ api->token, /* Username */
+ NULL);
+
+ g_free(msg);
+}
+
+/**
+ * Implements #fb_mqtt_funcs->connack().
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_mqtt_connack(fb_mqtt_t *mqtt, gpointer data)
+{
+ fb_api_t *api = data;
+
+ FB_API_FUNC(api, connect);
+}
+
+/**
+ * Implements #fb_mqtt_funcs->publish(().
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param topic The message topic.
+ * @param pload The message payload.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic,
+ const GByteArray *pload, gpointer data)
+{
+ fb_api_t *api = data;
+ GByteArray *bytes;
+ gboolean comp;
+
+ comp = fb_util_zcompressed(pload);
+
+ if (G_LIKELY(comp)) {
+ bytes = fb_util_zuncompress(pload);
+
+ if (G_UNLIKELY(bytes == NULL)) {
+ fb_api_error(api, FB_API_ERROR, "Failed to decompress");
+ return;
+ }
+ } else {
+ bytes = (GByteArray*) pload;
+ }
+
+ fb_util_hexdump(bytes, 2, "Publishing:");
+
+ if (G_LIKELY(comp))
+ g_byte_array_free(bytes, TRUE);
+}
+
+/**
* Creates a new #fb_api. The returned #fb_api should be freed with
* #fb_api_free() when no longer needed.
*
* @param funcs The #fb_api_funcs.
* @param data The user-defined data or NULL.
+ * @param cid The client identifier or NULL.
+ * @param mid The MQTT identifier or NULL.
+ * @param cuid The client unique identifier or NULL.
*
* @return The #fb_api or NULL on error.
**/
@@ -47,17 +150,56 @@ fb_api_t *fb_api_new(const fb_api_funcs_t *funcs, gpointer data)
{
fb_api_t *api;
+ static const fb_mqtt_funcs_t muncs = {
+ .error = fb_api_cb_mqtt_error,
+ .open = fb_api_cb_mqtt_open,
+ .connack = fb_api_cb_mqtt_connack,
+ .publish = fb_api_cb_mqtt_publish
+ };
+
g_return_val_if_fail(funcs != NULL, NULL);
api = g_new0(fb_api_t, 1);
memcpy(&api->funcs, funcs, sizeof *funcs);
api->data = data;
api->http = fb_http_new(FB_API_AGENT);
+ api->mqtt = fb_mqtt_new(&muncs, api);
return api;
}
/**
+ * Rehashes the internal settings of a #fb_api.
+ *
+ * @param api The #fb_api.
+ **/
+void fb_api_rehash(fb_api_t *api)
+{
+ sha1_state_t sha;
+ guint8 rb[50];
+
+ if (api->cid == NULL) {
+ random_bytes(rb, sizeof rb);
+ api->cid = g_compute_checksum_for_data(G_CHECKSUM_MD5, rb, sizeof rb);
+ }
+
+ if (api->mid == NULL)
+ api->mid = g_strdup_printf("%" G_GUINT32_FORMAT, g_random_int());
+
+ if (api->cuid == NULL) {
+ sha1_init(&sha);
+ random_bytes(rb, sizeof rb);
+ sha1_append(&sha, rb, sizeof rb);
+ api->cuid = sha1_random_uuid(&sha);
+ }
+
+ if (strlen(api->cid) > 20) {
+ api->cid = g_realloc_n(api->cid , 21, sizeof *api->cid);
+ api->cid[20] = 0;
+ }
+}
+
+/**
* Frees all memory used by a #fb_api.
*
* @param api The #fb_api.
@@ -70,9 +212,15 @@ void fb_api_free(fb_api_t *api)
if (api->err != NULL)
g_error_free(api->err);
+ fb_mqtt_free(api->mqtt);
fb_http_free(api->http);
+ g_free(api->sid);
+ g_free(api->cuid);
+ g_free(api->mid);
+ g_free(api->cid);
g_free(api->token);
+ g_free(api->uid);
g_free(api);
}
@@ -246,17 +394,23 @@ static void fb_api_cb_auth(fb_http_req_t *req, gpointer data)
fb_api_t *api = data;
json_value *json;
const gchar *str;
+ gint64 in;
json = fb_api_json_new(api, req->body, req->body_size);
if (json == NULL)
return;
- if (!fb_json_str_chk(json, "access_token", &str)) {
- fb_api_error(api, FB_API_ERROR_GENERAL, "Failed to obtain token");
+ if (!fb_json_int_chk(json, "uid", &in) ||
+ !fb_json_str_chk(json, "access_token", &str))
+ {
+ fb_api_error(api, FB_API_ERROR_GENERAL, "Failed to obtain user info");
goto finish;
}
+ g_free(api->uid);
+ api->uid = g_strdup_printf("%" G_GINT64_FORMAT, in);
+
g_free(api->token);
api->token = g_strdup(str);
FB_API_FUNC(api, auth);
@@ -292,3 +446,30 @@ void fb_api_auth(fb_api_t *api, const gchar *user, const gchar *pass)
fb_api_req_send(api, req);
}
+
+/**
+ * Connects the #fb_api to the remote services. This is mainly for
+ * connecting and setting up the internal #fb_mqtt.
+ *
+ * @param The #fb_api.
+ **/
+void fb_api_connect(fb_api_t *api)
+{
+ g_return_if_fail(api != NULL);
+
+ fb_mqtt_open(api->mqtt, FB_MQTT_HOST, FB_MQTT_PORT);
+}
+
+/**
+ * Disconnects the #fb_api from the remote services. This is mainly for
+ * disconnecting the internal #fb_mqtt. This will close the internal
+ * #fb_mqtt via #fb_mqtt_close().
+ *
+ * @param The #fb_api.
+ **/
+void fb_api_disconnect(fb_api_t *api)
+{
+ g_return_if_fail(api != NULL);
+
+ fb_mqtt_disconnect(api->mqtt);
+}
diff --git a/facebook/facebook-api.h b/facebook/facebook-api.h
index f8711ad..7d61c8e 100644
--- a/facebook/facebook-api.h
+++ b/facebook/facebook-api.h
@@ -24,6 +24,7 @@
#include "facebook-http.h"
#include "facebook-json.h"
+#include "facebook-mqtt.h"
#define FB_API_HOST "api.facebook.com"
#define FB_API_BHOST "b-api.facebook.com"
@@ -91,6 +92,16 @@ struct fb_api_funcs
* @param data The user-defined data or NULL.
**/
void (*auth) (fb_api_t *api, gpointer data);
+
+ /**
+ * The connect function. This is called whenever the #fb_api has
+ * been successfully connected. This connects to the MQTT service.
+ * This is called as a result of #fb_api_connect().
+ *
+ * @param api The #fb_api.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*connect) (fb_api_t *api, gpointer data);
};
/**
@@ -102,9 +113,15 @@ struct fb_api
gpointer data; /** The user-defined data or NULL. **/
fb_http_t *http; /** The #fb_http. **/
+ fb_mqtt_t *mqtt; /** The #fb_mqtt. **/
GError *err; /** The #GError or NULL. **/
+ gchar *uid; /** The user identifier. **/
gchar *token; /** The session token. **/
+ gchar *cid; /** The client identifier. **/
+ gchar *mid; /** The MQTT identifier. **/
+ gchar *cuid; /** The client unique identifier. **/
+ gchar *sid; /** The sync identifier. **/
};
@@ -114,10 +131,16 @@ GQuark fb_api_error_quark(void);
fb_api_t *fb_api_new(const fb_api_funcs_t *funcs, gpointer data);
+void fb_api_rehash(fb_api_t *api);
+
void fb_api_free(fb_api_t *api);
void fb_api_error(fb_api_t *api, fb_api_error_t err, const gchar *fmt, ...);
void fb_api_auth(fb_api_t *api, const gchar *user, const gchar *pass);
+void fb_api_connect(fb_api_t *api);
+
+void fb_api_disconnect(fb_api_t *api);
+
#endif /* _FACEBOOK_API_H */
diff --git a/facebook/facebook-mqtt.c b/facebook/facebook-mqtt.c
new file mode 100644
index 0000000..88f9c94
--- /dev/null
+++ b/facebook/facebook-mqtt.c
@@ -0,0 +1,1057 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <bitlbee.h>
+#include <glib/gprintf.h>
+#include <ssl_client.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "facebook-mqtt.h"
+
+/**
+ * Gets the error domain for #fb_mqtt.
+ *
+ * @return The #GQuark of the error domain.
+ **/
+GQuark fb_mqtt_error_quark(void)
+{
+ static GQuark q;
+
+ if (G_UNLIKELY(q == 0))
+ q = g_quark_from_static_string("fb-mqtt-error-quark");
+
+ return q;
+}
+
+/**
+ * Creates a new #fb_mqtt. The returned #fb_mqtt should be freed with
+ * #fb_mqtt_free() when no longer needed.
+ *
+ * @param funcs The #fb_mqtt_funcs.
+ * @param data The user defined data or NULL.
+ *
+ * @return The #fb_mqtt or NULL on error.
+ **/
+fb_mqtt_t *fb_mqtt_new(const fb_mqtt_funcs_t *funcs, gpointer data)
+{
+ fb_mqtt_t *mqtt;
+
+ mqtt = g_new0(fb_mqtt_t, 1);
+ memcpy(&mqtt->funcs, funcs, sizeof *funcs);
+ mqtt->data = data;
+ mqtt->rbuf = g_byte_array_new();
+ mqtt->wbuf = g_byte_array_new();
+
+ return mqtt;
+};
+
+/**
+ * Frees all memory used by a #fb_mqtt.
+ *
+ * @param api The #fb_mqtt.
+ **/
+void fb_mqtt_free(fb_mqtt_t *mqtt)
+{
+ if (G_UNLIKELY(mqtt == NULL))
+ return;
+
+ fb_mqtt_close(mqtt);
+ g_clear_error(&mqtt->err);
+
+ g_byte_array_free(mqtt->wbuf, TRUE);
+ g_byte_array_free(mqtt->rbuf, TRUE);
+
+ g_free(mqtt);
+}
+
+/**
+ * Closes the #fb_mqtt connection.
+ *
+ * @param mqtt The #fb_mqtt.
+ **/
+void fb_mqtt_close(fb_mqtt_t *mqtt)
+{
+ g_return_if_fail(mqtt != NULL);
+
+ if (mqtt->wev > 0) {
+ b_event_remove(mqtt->wev);
+ mqtt->wev = 0;
+ }
+
+ if (mqtt->rev > 0) {
+ b_event_remove(mqtt->rev);
+ mqtt->rev = 0;
+ }
+
+ if (mqtt->tev > 0) {
+ b_event_remove(mqtt->tev);
+ mqtt->tev = 0;
+ }
+
+ if (mqtt->ssl != NULL) {
+ ssl_disconnect(mqtt->ssl);
+ mqtt->ssl = NULL;
+ }
+
+#ifdef DEBUG_FACEBOOK
+ if (mqtt->wbuf->len > 0)
+ FB_UTIL_DEBUGLN("Closing with unwritten data");
+#endif /* DEBUG_FACEBOOK */
+
+ g_clear_error(&mqtt->err);
+ g_byte_array_set_size(mqtt->rbuf, 0);
+ g_byte_array_set_size(mqtt->wbuf, 0);
+}
+
+/**
+ * Handles an error with the #fb_mqtt. This sets #fb_mqtt->err, calls
+ * the error function, and closes the connection.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param error The #fb_mqtt_error.
+ * @param fmt The format string.
+ * @param ... The arguments for the format string.
+ **/
+void fb_mqtt_error(fb_mqtt_t *mqtt, fb_mqtt_error_t err,
+ const gchar *fmt, ...)
+{
+ gchar *str;
+ va_list ap;
+
+ g_return_if_fail(mqtt != NULL);
+
+ if (fmt != NULL) {
+ va_start(ap, fmt);
+ str = g_strdup_vprintf(fmt, ap);
+ va_end(ap);
+
+ g_clear_error(&mqtt->err);
+ g_set_error_literal(&mqtt->err, FB_MQTT_ERROR, err, str);
+ g_free(str);
+ }
+
+ if (mqtt->err != NULL)
+ FB_MQTT_FUNC(mqtt, error, mqtt->err);
+}
+
+/**
+ * Implemented #b_event_handler for #fb_mqtt_timeout().
+ *
+ * @param data The user defined data, which is #fb_mqtt.
+ * @param fd The event file descriptor.
+ * @param cond The #b_input_condition.
+ *
+ * @return FALSE to prevent continued event handling.
+ **/
+static gboolean fb_mqtt_cb_timeout(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ fb_mqtt_t *mqtt = data;
+
+ mqtt->tev = 0;
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Connection timed out");
+ return FALSE;
+}
+
+/**
+ * Clears an enacted connection timeout.
+ *
+ * @param mqtt The #fb_mqtt.
+ **/
+static void fb_mqtt_timeout_clear(fb_mqtt_t *mqtt)
+{
+ g_return_if_fail(mqtt != NULL);
+
+ if (mqtt->tev > 0) {
+ b_event_remove(mqtt->tev);
+ mqtt->tev = 0;
+ }
+}
+
+/**
+ * Enacts a timeout on the connection. This clears any timeout which
+ * currently exists.
+ *
+ * @param mqtt The #fb_mqtt.
+ **/
+static void fb_mqtt_timeout(fb_mqtt_t *mqtt)
+{
+ g_return_if_fail(mqtt != NULL);
+
+ fb_mqtt_timeout_clear(mqtt);
+ mqtt->tev = b_timeout_add(FB_MQTT_TIMEOUT, fb_mqtt_cb_timeout, mqtt);
+}
+
+/**
+ * Implemented #b_event_handler for sending a PING request.
+ *
+ * @param data The user defined data, which is #fb_mqtt.
+ * @param fd The event file descriptor.
+ * @param cond The #b_input_condition.
+ *
+ * @return FALSE to prevent continued event handling.
+ **/
+static gboolean fb_mqtt_cb_ping(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ fb_mqtt_t *mqtt = data;
+ fb_mqtt_msg_t *msg;
+
+ msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_PINGREQ, 0);
+ fb_mqtt_write(mqtt, msg);
+ fb_mqtt_msg_free(msg);
+
+ mqtt->tev = 0;
+ fb_mqtt_timeout(mqtt);
+ return FALSE;
+}
+
+/**
+ * Sends a PING after #FB_MQTT_KA seconds. This clears any timeout which
+ * currently exists.
+ *
+ * @param mqtt The #fb_mqtt.
+ **/
+static void fb_mqtt_ping(fb_mqtt_t *mqtt)
+{
+ g_return_if_fail(mqtt != NULL);
+
+ fb_mqtt_timeout_clear(mqtt);
+ mqtt->tev = b_timeout_add(FB_MQTT_TIMEOUT, fb_mqtt_cb_ping, mqtt);
+}
+
+/**
+ * Implemented #b_event_handler for the read of #fb_mqtt->fd.
+ *
+ * @param data The user defined data, which is #fb_mqtt.
+ * @param fd The event file descriptor.
+ * @param cond The #b_input_condition.
+ *
+ * @return TRUE for continued event handling, otherwise FALSE.
+ **/
+static gboolean fb_mqtt_cb_read(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ fb_mqtt_t *mqtt = data;
+ fb_mqtt_msg_t *msg;
+ gchar buf[1024];
+ guint8 byte;
+ guint mult;
+ gssize rize;
+ gint res;
+
+ if (mqtt->remz < 1) {
+ /* Reset the read buffer */
+ g_byte_array_set_size(mqtt->rbuf, 0);
+
+ res = ssl_read(mqtt->ssl, (gchar*) &byte, sizeof byte);
+ g_byte_array_append(mqtt->rbuf, &byte, sizeof byte);
+
+ if (res != sizeof byte)
+ goto error;
+
+ mult = 1;
+
+ do {
+ res = ssl_read(mqtt->ssl, (gchar*) &byte, sizeof byte);
+ g_byte_array_append(mqtt->rbuf, &byte, sizeof byte);
+
+ if (res != sizeof byte)
+ goto error;
+
+ mqtt->remz += (byte & 127) * mult;
+ mult *= 128;
+ } while ((byte & 128) != 0);
+ }
+
+ if (mqtt->remz > 0) {
+ rize = ssl_read(mqtt->ssl, buf, MIN(mqtt->rbuf->len, sizeof buf));
+
+ if (rize < 1)
+ goto error;
+
+ g_byte_array_append(mqtt->rbuf, (guint8*) buf, rize);
+ mqtt->remz -= rize;
+ }
+
+ if (mqtt->remz < 1) {
+ msg = fb_mqtt_msg_new_bytes(mqtt->rbuf);
+ mqtt->remz = 0;
+
+ if (G_UNLIKELY(msg == NULL))
+ goto error;
+
+ fb_mqtt_read(mqtt, msg);
+ fb_mqtt_msg_free(msg);
+ }
+
+ return TRUE;
+
+error:
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Short read");
+ return FALSE;
+}
+
+/**
+ * Read a #GByteArray to the #fb_mqtt.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param bytes The #GByteArray.
+ **/
+void fb_mqtt_read(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg)
+{
+ fb_mqtt_msg_t *nsg;
+ GByteArray *wytes;
+ gchar *str;
+ guint8 chr;
+ guint16 mid;
+
+ g_return_if_fail(mqtt != NULL);
+ g_return_if_fail(msg != NULL);
+
+ fb_util_hexdump(msg->bytes, 2, "Reading %d (flags: 0x%0X)",
+ msg->type, msg->flags);
+
+ switch (msg->type) {
+ case FB_MQTT_MSG_TYPE_CONNACK:
+ if (!fb_mqtt_msg_read_byte(msg, NULL) ||
+ !fb_mqtt_msg_read_byte(msg, &chr))
+ {
+ break;
+ }
+
+ if (chr != FB_MQTT_ERROR_SUCCESS) {
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL,
+ "Connection failed (%u)", chr);
+ return;
+ }
+
+ mqtt->connected = TRUE;
+ fb_mqtt_ping(mqtt);
+ FB_MQTT_FUNC(mqtt, connack);
+ return;
+
+ case FB_MQTT_MSG_TYPE_PUBLISH:
+ if (!fb_mqtt_msg_read_str(msg, &str))
+ break;
+
+ if ((msg->flags & FB_MQTT_MSG_FLAG_QOS1) ||
+ (msg->flags & FB_MQTT_MSG_FLAG_QOS2))
+ {
+ if (msg->flags & FB_MQTT_MSG_FLAG_QOS1)
+ chr = FB_MQTT_MSG_TYPE_PUBACK;
+ else
+ chr = FB_MQTT_MSG_TYPE_PUBREC;
+
+ if (!fb_mqtt_msg_read_mid(msg, &mid))
+ break;
+
+ nsg = fb_mqtt_msg_new(chr, 0);
+ fb_mqtt_msg_write_u16(nsg, mid);
+ fb_mqtt_write(mqtt, nsg);
+ fb_mqtt_msg_free(nsg);
+ }
+
+ wytes = g_byte_array_new();
+ fb_mqtt_msg_read_r(msg, wytes);
+ FB_MQTT_FUNC(mqtt, publish, str, wytes);
+ g_byte_array_free(wytes, TRUE);
+ g_free(str);
+ return;
+
+ case FB_MQTT_MSG_TYPE_PUBREL:
+ if (!fb_mqtt_msg_read_mid(msg, &mid))
+ break;
+
+ nsg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_PUBCOMP, 0);
+ fb_mqtt_msg_write_u16(nsg, mid); /* Message identifier */
+ fb_mqtt_write(mqtt, nsg);
+ fb_mqtt_msg_free(nsg);
+ return;
+
+ case FB_MQTT_MSG_TYPE_PINGRESP:
+ fb_mqtt_ping(mqtt);
+ return;
+
+ case FB_MQTT_MSG_TYPE_PUBACK:
+ case FB_MQTT_MSG_TYPE_PUBCOMP:
+ case FB_MQTT_MSG_TYPE_SUBACK:
+ case FB_MQTT_MSG_TYPE_UNSUBACK:
+ return;
+
+ default:
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Unknown packet (%u)",
+ msg->type);
+ return;
+ }
+
+ /* Since no case returned, there was a parse error. */
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to parse message");
+}
+
+/**
+ * Implemented #b_event_handler for the writing of #fb_mqtt->fd.
+ *
+ * @param data The user defined data, which is #fb_mqtt.
+ * @param fd The event file descriptor.
+ * @param cond The #b_input_condition.
+ *
+ * @return TRUE for continued event handling, otherwise FALSE.
+ **/
+static gboolean fb_mqtt_cb_write(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ fb_mqtt_t *mqtt = data;
+ gssize wize;
+
+ wize = ssl_write(mqtt->ssl, (gchar*) mqtt->wbuf->data, mqtt->wbuf->len);
+
+ if (wize < 0) {
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to write data");
+ return FALSE;
+ }
+
+ if (wize > 0)
+ g_byte_array_remove_range(mqtt->wbuf, 0, wize);
+
+ if (mqtt->wbuf->len < 1) {
+ mqtt->wev = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Writes a #fb_mqtt_msg to the #fb_mqtt.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param msg The #fb_mqtt_msg.
+ **/
+void fb_mqtt_write(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg)
+{
+ const GByteArray *bytes;
+ gint fd;
+
+ g_return_if_fail(mqtt != NULL);
+
+ bytes = fb_mqtt_msg_bytes(msg);
+
+ if (G_UNLIKELY(bytes == NULL)) {
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to format data");
+ return;
+ }
+
+ fb_util_hexdump(bytes, 2, "Writing %d (flags: 0x%0X)",
+ msg->type, msg->flags);
+
+ fd = ssl_getfd(mqtt->ssl);
+ g_byte_array_append(mqtt->wbuf, bytes->data, bytes->len);
+
+ if ((mqtt->wev < 1) && fb_mqtt_cb_write(mqtt, fd, B_EV_IO_WRITE))
+ mqtt->wev = b_input_add(fd, B_EV_IO_WRITE, fb_mqtt_cb_write, mqtt);
+}
+
+/**
+ * Implemented #ssl_input_function for the connection of #fb_mqtt->ssl.
+ *
+ * @param data The user defined data, which is #fb_mqtt.
+ * @param error The SSL error. (0 on success)
+ * @param ssl The SSL source.
+ * @param cond The #b_input_condition.
+ *
+ * @return TRUE for continued event handling, otherwise FALSE.
+ **/
+static gboolean fb_mqtt_cb_open(gpointer data, gint error, gpointer ssl,
+ b_input_condition cond)
+{
+ fb_mqtt_t *mqtt = data;
+ gint fd;
+
+ if ((ssl == NULL) || (error != SSL_OK)) {
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to connect");
+ return FALSE;
+ }
+
+ fb_mqtt_timeout_clear(mqtt);
+ fd = ssl_getfd(mqtt->ssl);
+ mqtt->rev = b_input_add(fd, B_EV_IO_READ, fb_mqtt_cb_read, mqtt);
+
+ FB_MQTT_FUNC(mqtt, open);
+ return FALSE;
+}
+
+/**
+ * Opens the connection to the MQTT service.
+ *
+ * @param mqtt The #fb_mqtt.
+ **/
+void fb_mqtt_open(fb_mqtt_t *mqtt, const gchar *host, gint port)
+{
+ g_return_if_fail(mqtt != NULL);
+
+ fb_mqtt_close(mqtt);
+ mqtt->ssl = ssl_connect((gchar*) host, port, TRUE, fb_mqtt_cb_open, mqtt);
+
+ if (mqtt->ssl == NULL) {
+ fb_mqtt_cb_open(mqtt, 1, NULL, 0);
+ return;
+ }
+
+ fb_mqtt_timeout(mqtt);
+}
+
+/**
+ * Connects to the MQTT service. This first establishes an SSL based
+ * socket. Then it sends the initial connection packet with optional
+ * arguments, which correspond to the flags provided. The arguments
+ * must be passed in order: client identifier, will topic, will
+ * message, username, and password (not required). The arguments must
+ * be in a string format.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param timeout The keep-alive timeout (seconds).
+ * @param flags The #fb_mqtt_connect_flags.
+ * @param cid The client identifier.
+ * @param ... Additional arguments in order, NULL-terminated.
+ **/
+void fb_mqtt_connect(fb_mqtt_t *mqtt, guint8 flags, const gchar *cid, ...)
+{
+ fb_mqtt_msg_t *msg;
+ va_list ap;
+ const gchar *str;
+
+ g_return_if_fail(mqtt != NULL);
+
+ if (G_UNLIKELY(fb_mqtt_connected(mqtt, FALSE)))
+ return;
+
+ /* Facebook always sends a CONNACK, use QoS1 */
+ flags |= FB_MQTT_CONNECT_FLAG_QOS1;
+
+ msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_CONNECT, 0);
+ fb_mqtt_msg_write_str(msg, FB_MQTT_NAME); /* Protocol name */
+ fb_mqtt_msg_write_byte(msg, FB_MQTT_VERS); /* Protocol version */
+ fb_mqtt_msg_write_byte(msg, flags); /* Flags */
+ fb_mqtt_msg_write_u16(msg, FB_MQTT_KA); /* Keep alive */
+ fb_mqtt_msg_write_str(msg, cid); /* Client identifier */
+
+ va_start(ap, cid);
+
+ while ((str = va_arg(ap, const gchar*)) != NULL)
+ fb_mqtt_msg_write_str(msg, str);
+
+ va_end(ap);
+
+ fb_mqtt_write(mqtt, msg);
+ fb_mqtt_msg_free(msg);
+ fb_mqtt_timeout(mqtt);
+}
+
+/**
+ * Checks the #fb_mqtt connection.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param error TRUE to error upon no connection, FALSE otherwise.
+ *
+ * @return TRUE if the #fb_mqtt is connected, FALSE otherwise.
+ **/
+gboolean fb_mqtt_connected(fb_mqtt_t *mqtt, gboolean error)
+{
+ gboolean connected;
+
+ g_return_val_if_fail(mqtt != NULL, FALSE);
+
+ connected = (mqtt->ssl != NULL) && mqtt->connected;
+
+ if (!connected && error)
+ fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Not connected");
+
+ return connected;
+}
+
+/**
+ * Disconnects from the MQTT service. This cleanly disconnects from the
+ * MQTT services, rather than killing the socket stream. This closes
+ * the #fb_mqtt via #fb_mqtt_close().
+ *
+ * @param mqtt The #fb_mqtt.
+ **/
+void fb_mqtt_disconnect(fb_mqtt_t *mqtt)
+{
+ fb_mqtt_msg_t *msg;
+
+ g_return_if_fail(mqtt != NULL);
+
+ if (G_UNLIKELY(!fb_mqtt_connected(mqtt, FALSE)))
+ return;
+
+ msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_DISCONNECT, 0);
+ fb_mqtt_write(mqtt, msg);
+ fb_mqtt_msg_free(msg);
+ fb_mqtt_close(mqtt);
+}
+
+/**
+ * Publishes a message to MQTT service.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param topic The message topic.
+ * @param pload The #GByteArray payload or NULL.
+ **/
+void fb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic,
+ const GByteArray *pload)
+{
+ fb_mqtt_msg_t *msg;
+
+ g_return_if_fail(mqtt != NULL);
+
+ if (G_UNLIKELY(!fb_mqtt_connected(mqtt, TRUE)))
+ return;
+
+ /* Message identifier not required, but for consistency use QoS1 */
+ msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_PUBLISH, FB_MQTT_MSG_FLAG_QOS1);
+
+ fb_mqtt_msg_write_str(msg, topic); /* Message topic */
+ fb_mqtt_msg_write_mid(msg, &mqtt->mid); /* Message identifier */
+
+ if (pload != NULL)
+ fb_mqtt_msg_write(msg, pload->data, pload->len);
+
+ fb_mqtt_write(mqtt, msg);
+ fb_mqtt_msg_free(msg);
+}
+
+/**
+ * Subscribes to one or more topics.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param topic1 The first topic name.
+ * @param qos1 The first QoS value.
+ * @param ... Additional topic names and QoS values, NULL-terminated.
+ **/
+void fb_mqtt_subscribe(fb_mqtt_t *mqtt, const gchar *topic1, guint16 qos1, ...)
+{
+ fb_mqtt_msg_t *msg;
+ va_list ap;
+ const gchar *topic;
+ guint16 qos;
+
+ g_return_if_fail(mqtt != NULL);
+
+ if (G_UNLIKELY(!fb_mqtt_connected(mqtt, TRUE)))
+ return;
+
+ /* Facebook requires a message identifier, use QoS1 */
+ msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_SUBSCRIBE, FB_MQTT_MSG_FLAG_QOS1);
+
+ fb_mqtt_msg_write_mid(msg, &mqtt->mid); /* Message identifier */
+ fb_mqtt_msg_write_str(msg, topic1); /* First topics */
+ fb_mqtt_msg_write_byte(msg, qos1); /* First QoS value */
+
+ va_start(ap, qos1);
+
+ while ((topic = va_arg(ap, const gchar*)) != NULL) {
+ qos = va_arg(ap, guint);
+ fb_mqtt_msg_write_str(msg, topic); /* Remaining topics */
+ fb_mqtt_msg_write_byte(msg, qos); /* Remaining QoS values */
+ }
+
+ va_end(ap);
+
+ fb_mqtt_write(mqtt, msg);
+ fb_mqtt_msg_free(msg);
+}
+
+/**
+ * Unsubscribes from one or more topics.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param topic1 The first topic name.
+ * @param ... Additional topic names, NULL-terminated.
+ **/
+void fb_mqtt_unsubscribe(fb_mqtt_t *mqtt, const gchar *topic1, ...)
+{
+ fb_mqtt_msg_t *msg;
+ va_list ap;
+ const gchar *topic;
+
+ g_return_if_fail(mqtt != NULL);
+
+ if (G_UNLIKELY(!fb_mqtt_connected(mqtt, TRUE)))
+ return;
+
+ /* Facebook requires a message identifier, use QoS1 */
+ msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_UNSUBSCRIBE, FB_MQTT_MSG_FLAG_QOS1);
+
+ fb_mqtt_msg_write_mid(msg, &mqtt->mid); /* Message identifier */
+ fb_mqtt_msg_write_str(msg, topic1); /* First topic */
+
+ va_start(ap, topic1);
+
+ while ((topic = va_arg(ap, const gchar*)) != NULL)
+ fb_mqtt_msg_write_str(msg, topic); /* Remaining topics */
+
+ va_end(ap);
+
+ fb_mqtt_write(mqtt, msg);
+ fb_mqtt_msg_free(msg);
+}
+
+/**
+ * Creates a new #fb_mqtt_msg. The returned #fb_mqtt_msg should be
+ * freed with #fb_mqtt_msg_free() when no longer needed.
+ *
+ * @param type The #fb_mqtt_msg_type.
+ * @param flags The #fb_mqtt_msg_flags.
+ *
+ * @return The #fb_mqtt_msg or NULL on error.
+ **/
+fb_mqtt_msg_t *fb_mqtt_msg_new(fb_mqtt_msg_type_t type,
+ fb_mqtt_msg_flags_t flags)
+{
+ fb_mqtt_msg_t *msg;
+
+ msg = g_new0(fb_mqtt_msg_t, 1);
+ msg->type = type;
+ msg->flags = flags;
+ msg->bytes = g_byte_array_new();
+ msg->local = TRUE;
+
+ return msg;
+}
+
+/**
+ * Creates a new #fb_mqtt_msg from a #GByteArray containing a raw data.
+ * The returned #fb_mqtt_msg should be freed with #fb_mqtt_msg_free()
+ * when no longer needed. The GByteArray passed to this function MUST
+ * remain for the lifetime of the #fb_mqtt_msg.
+ *
+ * @param bytes The #GByteArray.
+ *
+ * @return The #fb_mqtt_msg or NULL on error.
+ **/
+fb_mqtt_msg_t *fb_mqtt_msg_new_bytes(GByteArray *bytes)
+{
+ fb_mqtt_msg_t *msg;
+ guint8 *byte;
+
+ g_return_val_if_fail(bytes != NULL, NULL);
+ g_return_val_if_fail(bytes->len >= 2, NULL);
+
+ msg = g_new0(fb_mqtt_msg_t, 1);
+ msg->bytes = bytes;
+ msg->local = FALSE;
+
+ if (bytes->len > 1) {
+ msg->type = (*bytes->data & 0xF0) >> 4;
+ msg->flags = *bytes->data & 0x0F;
+
+ /* Skip the fixed header */
+ for (byte = msg->bytes->data + 1; (*(byte++) & 128) != 0; );
+ msg->offset = byte - bytes->data;
+ msg->pos = msg->offset;
+ }
+
+ return msg;
+}
+
+/**
+ * Frees all memory used by a #fb_mqtt_msg.
+ *
+ * @param msg The #fb_mqtt_msg.
+ **/
+void fb_mqtt_msg_free(fb_mqtt_msg_t *msg)
+{
+ g_return_if_fail(msg != NULL);
+
+ if (msg->local)
+ g_byte_array_free(msg->bytes, TRUE);
+
+ g_free(msg);
+}
+
+/**
+ * Resets a #fb_mqtt_msg. This resets the cursor and removes any sort
+ * of fixed header.
+ *
+ * @param msg The #fb_mqtt_msg.
+ **/
+void fb_mqtt_msg_reset(fb_mqtt_msg_t *msg)
+{
+ if (G_UNLIKELY(msg == NULL))
+ return;
+
+ if (msg->offset > 0) {
+ g_byte_array_remove_range(msg->bytes, 0, msg->offset);
+ msg->offset = 0;
+ msg->pos = 0;
+ }
+}
+
+/**
+ * Formats the internal #GByteArray of a #fb_mqtt_msg with the required
+ * fixed header for sending over the wire. This set the cursor position
+ * to the start of the message data.
+ *
+ * @param msg The #fb_mqtt_msg.
+ *
+ * @return The internal #GByteArray.
+ **/
+const GByteArray *fb_mqtt_msg_bytes(fb_mqtt_msg_t *msg)
+{
+ guint8 sbuf[4];
+ guint8 byte;
+ guint32 size;
+ guint i;
+
+ g_return_val_if_fail(msg != NULL, NULL);
+
+ size = msg->bytes->len - msg->offset;
+ i = 0;
+
+ do {
+ if (G_UNLIKELY(i >= G_N_ELEMENTS(sbuf)))
+ return NULL;
+
+ byte = size % 128;
+ size /= 128;
+
+ if (size > 0)
+ byte |= 128;
+
+ sbuf[i++] = byte;
+ } while (size > 0);
+
+ fb_mqtt_msg_reset(msg);
+ g_byte_array_prepend(msg->bytes, sbuf, i);
+
+ byte = ((msg->type & 0x0F) << 4) | (msg->flags & 0x0F);
+ g_byte_array_prepend(msg->bytes, &byte, sizeof byte);
+
+ msg->pos = (i + 1) * (sizeof byte);
+ return msg->bytes;
+}
+
+/**
+ * Reads raw data from a #fb_mqtt_msg.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param data The data buffer or NULL.
+ * @param size The size of data to read.
+ *
+ * @return TRUE if the data was completely read, otherwise FALSE.
+ **/
+gboolean fb_mqtt_msg_read(fb_mqtt_msg_t *msg, gpointer data, guint size)
+{
+ g_return_val_if_fail(msg != NULL, FALSE);
+
+ if ((msg->pos + size) > msg->bytes->len)
+ return FALSE;
+
+ if ((data != NULL) && (size > 0))
+ memcpy(data, msg->bytes->data + msg->pos, size);
+
+ msg->pos += size;
+ return TRUE;
+}
+
+/**
+ * Reads the remaining bytes from a #fb_mqtt_msg into a #GByteArray.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param bytes The #GByteArray.
+ *
+ * @return TRUE if the byte string was read, otherwise FALSE.
+ **/
+gboolean fb_mqtt_msg_read_r(fb_mqtt_msg_t *msg, GByteArray *bytes)
+{
+ guint size;
+
+ g_return_val_if_fail(bytes != NULL, FALSE);
+
+ size = msg->bytes->len - msg->pos;
+
+ if (G_LIKELY(size > 0))
+ g_byte_array_append(bytes, msg->bytes->data + msg->pos, size);
+
+ return TRUE;
+}
+
+/**
+ * Reads a single byte from a #fb_mqtt_msg. If the return location is
+ * NULL, only the cursor is advanced.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param byte The return location for the byte or NULL.
+ *
+ * @return TRUE if the byte string was read, otherwise FALSE.
+ **/
+gboolean fb_mqtt_msg_read_byte(fb_mqtt_msg_t *msg, guint8 *byte)
+{
+ if (byte != NULL)
+ *byte = 0;
+
+ return fb_mqtt_msg_read(msg, byte, sizeof *byte);
+}
+
+/**
+ * Reads a message identifier from a #fb_mqtt_msg. If the return
+ * location is NULL, only the cursor is advanced.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param mid The return location for the message identifier or NULL.
+ *
+ * @return TRUE if the message identifier was read, otherwise FALSE.
+ **/
+gboolean fb_mqtt_msg_read_mid(fb_mqtt_msg_t *msg, guint16 *mid)
+{
+ return fb_mqtt_msg_read_u16(msg, mid);
+}
+
+/**
+ * Reads an unsigned 16-bit integer from a #fb_mqtt_msg. If the return
+ * location is NULL, only the cursor is advanced.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param u16 The return location for the integer or NULL.
+ *
+ * @return TRUE if the integer was read, otherwise FALSE.
+ **/
+gboolean fb_mqtt_msg_read_u16(fb_mqtt_msg_t *msg, guint16 *u16)
+{
+ if (!fb_mqtt_msg_read(msg, u16, sizeof *u16)) {
+ if (u16 != NULL)
+ *u16 = 0;
+
+ return FALSE;
+ }
+
+ if (u16 != NULL)
+ *u16 = g_ntohs(*u16);
+
+ return TRUE;
+}
+
+/**
+ * Reads a string from a #fb_mqtt_msg. If the return location is NULL,
+ * only the cursor is advanced. The returned string should be freed
+ * with #g_free() when no longer needed.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param str The return location for the string or NULL.
+ *
+ * @return TRUE if the string was read, otherwise FALSE.
+ **/
+gboolean fb_mqtt_msg_read_str(fb_mqtt_msg_t *msg, gchar **str)
+{
+ guint16 size;
+ guint8 *data;
+
+ if (str != NULL)
+ *str = NULL;
+
+ if (!fb_mqtt_msg_read_u16(msg, &size))
+ return FALSE;
+
+ if (str != NULL) {
+ data = g_new(guint8, size + 1);
+ data[size] = 0;
+ } else {
+ data = NULL;
+ }
+
+ if (!fb_mqtt_msg_read(msg, data, size)) {
+ g_free(data);
+ return FALSE;
+ }
+
+ if (str != NULL)
+ *str = (gchar*) data;
+
+ return TRUE;
+}
+
+/**
+ * Writes raw data to a #fb_mqtt_msg.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param data The data.
+ * @param size The size of the data.
+ **/
+void fb_mqtt_msg_write(fb_mqtt_msg_t *msg, gconstpointer data, guint size)
+{
+ g_return_if_fail(msg != NULL);
+
+ g_byte_array_append(msg->bytes, data, size);
+ msg->pos += size;
+}
+
+/**
+ * Writes a single byte to a #fb_mqtt_msg.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param byte The byte.
+ **/
+void fb_mqtt_msg_write_byte(fb_mqtt_msg_t *msg, guint8 byte)
+{
+ fb_mqtt_msg_write(msg, &byte, sizeof byte);
+}
+
+/**
+ * Writes a 16-bit message identifier to a #fb_mqtt_msg. This advances
+ * the message identifier by one before usage.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param mid The return location of the message identifier.
+ **/
+void fb_mqtt_msg_write_mid(fb_mqtt_msg_t *msg, guint16 *mid)
+{
+ g_return_if_fail(mid != NULL);
+
+ fb_mqtt_msg_write_u16(msg, ++(*mid));
+}
+
+/**
+ * Writes an unsigned 16-bit integer to a #fb_mqtt_msg.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param u16 Theinteger.
+ **/
+void fb_mqtt_msg_write_u16(fb_mqtt_msg_t *msg, guint16 u16)
+{
+ u16 = g_htons(u16);
+ fb_mqtt_msg_write(msg, &u16, sizeof u16);
+}
+
+/**
+ * Writes a string to a #fb_mqtt_msg.
+ *
+ * @param msg The #fb_mqtt_msg.
+ * @param str The string.
+ **/
+void fb_mqtt_msg_write_str(fb_mqtt_msg_t *msg, const gchar *str)
+{
+ gint16 size;
+
+ g_return_if_fail(str != NULL);
+
+ size = strlen(str);
+ fb_mqtt_msg_write_u16(msg, size);
+ fb_mqtt_msg_write(msg, str, size);
+}
diff --git a/facebook/facebook-mqtt.h b/facebook/facebook-mqtt.h
new file mode 100644
index 0000000..0d9b3f9
--- /dev/null
+++ b/facebook/facebook-mqtt.h
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2014 James Geboski <jgeboski@gmail.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file **/
+
+#ifndef _FACEBOOK_MQTT_H
+#define _FACEBOOK_MQTT_H
+
+#include <glib.h>
+#include <string.h>
+
+#include "facebook-util.h"
+
+#define FB_MQTT_NAME "MQIsdp"
+#define FB_MQTT_VERS 3
+#define FB_MQTT_KA 60
+#define FB_MQTT_HOST "mqtt.facebook.com"
+#define FB_MQTT_PORT 443
+#define FB_MQTT_TIMEOUT (FB_MQTT_KA * 1000)
+
+/**
+ * Executes one of the #fb_mqtt_funcs.
+ *
+ * @param m The #fb_mqtt.
+ * @param f The function to execute.
+ * @param ... The operational function arguments.
+ **/
+#define FB_MQTT_FUNC(m, f, ...) \
+ G_STMT_START { \
+ if (G_LIKELY(m->funcs.f != NULL)) { \
+ m->funcs.f(m, ##__VA_ARGS__, m->data); \
+ } \
+ } G_STMT_END
+
+
+/** The flags of #fb_mqtt CONNECT packets. **/
+typedef enum fb_mqtt_connect_flags fb_mqtt_connect_flags_t;
+
+/** The #GError codes of #fb_mqtt. **/
+typedef enum fb_mqtt_error fb_mqtt_error_t;
+
+/** The flags of #fb_mqtt messages. **/
+typedef enum fb_mqtt_msg_flags fb_mqtt_msg_flags_t;
+
+/** The type of #fb_mqtt messages. **/
+typedef enum fb_mqtt_msg_type fb_mqtt_msg_type_t;
+
+/** The main structure for #fb_mqtt callback functions. **/
+typedef struct fb_mqtt_funcs fb_mqtt_funcs_t;
+
+/** The structure for interacting with Facebook MQTT. **/
+typedef struct fb_mqtt fb_mqtt_t;
+
+/** The structure of a #fb_mqtt message. **/
+typedef struct fb_mqtt_msg fb_mqtt_msg_t;
+
+
+/**
+ * The flags of #fb_mqtt CONNECT packets.
+ **/
+enum fb_mqtt_connect_flags
+{
+ FB_MQTT_CONNECT_FLAG_CLR = 1 << 1, /** Clear session. **/
+ FB_MQTT_CONNECT_FLAG_WILL = 1 << 2, /** Will flag. **/
+ FB_MQTT_CONNECT_FLAG_RET = 1 << 5, /** Will retain. **/
+ FB_MQTT_CONNECT_FLAG_PASS = 1 << 6, /** Password. **/
+ FB_MQTT_CONNECT_FLAG_USER = 1 << 7, /** Username. **/
+ FB_MQTT_CONNECT_FLAG_QOS0 = 0 << 3, /** Fire and forget. **/
+ FB_MQTT_CONNECT_FLAG_QOS1 = 1 << 3, /** Acknowledge delivery. **/
+ FB_MQTT_CONNECT_FLAG_QOS2 = 2 << 3 /** Assure delivery. **/
+};
+
+/**
+ * The #GError codes of #fb_mqtt.
+ **/
+enum fb_mqtt_error
+{
+ FB_MQTT_ERROR_SUCCESS = 0, /** Success. **/
+ FB_MQTT_ERROR_PRTVERS = 1, /** Unacceptable protocol version. **/
+ FB_MQTT_ERROR_IDREJECT = 2, /** Identifier rejected. **/
+ FB_MQTT_ERROR_SRVGONE = 3, /** Server unavailable. **/
+ FB_MQTT_ERROR_USERPASS = 4, /** Bad username or password. **/
+ FB_MQTT_ERROR_UNAUTHORIZED = 5, /** Not authorized. **/
+ FB_MQTT_ERROR_GENERAL /** General. **/
+};
+
+/**
+ * The flags of #fb_mqtt messages.
+ **/
+enum fb_mqtt_msg_flags
+{
+ FB_MQTT_MSG_FLAG_RET = 1 << 0, /** Retain. **/
+ FB_MQTT_MSG_FLAG_DUP = 1 << 3, /** Duplicate delivery. **/
+ FB_MQTT_MSG_FLAG_QOS0 = 0 << 1, /** Fire and forget. **/
+ FB_MQTT_MSG_FLAG_QOS1 = 1 << 1, /** Acknowledge delivery. **/
+ FB_MQTT_MSG_FLAG_QOS2 = 2 << 1 /** Assure delivery. **/
+};
+
+/**
+ * The type of #fb_mqtt messages.
+ **/
+enum fb_mqtt_msg_type
+{
+ FB_MQTT_MSG_TYPE_CONNECT = 1, /** Connect to Server. **/
+ FB_MQTT_MSG_TYPE_CONNACK = 2, /** Connect Acknowledgment. **/
+ FB_MQTT_MSG_TYPE_PUBLISH = 3, /** Publish Message. **/
+ FB_MQTT_MSG_TYPE_PUBACK = 4, /** Publish Acknowledgment. **/
+ FB_MQTT_MSG_TYPE_PUBREC = 5, /** Publish Received. **/
+ FB_MQTT_MSG_TYPE_PUBREL = 6, /** Publish Release. **/
+ FB_MQTT_MSG_TYPE_PUBCOMP = 7, /** Publish Complete. **/
+ FB_MQTT_MSG_TYPE_SUBSCRIBE = 8, /** Client Subscribe request. **/
+ FB_MQTT_MSG_TYPE_SUBACK = 9, /** Subscribe Acknowledgment. **/
+ FB_MQTT_MSG_TYPE_UNSUBSCRIBE = 10, /** Client Unsubscribe request. **/
+ FB_MQTT_MSG_TYPE_UNSUBACK = 11, /** Unsubscribe Acknowledgment. **/
+ FB_MQTT_MSG_TYPE_PINGREQ = 12, /** PING Request. **/
+ FB_MQTT_MSG_TYPE_PINGRESP = 13, /** PING Response. **/
+ FB_MQTT_MSG_TYPE_DISCONNECT = 14 /** Client is Disconnecting. **/
+};
+
+/**
+ * The main structure for #fb_mqtt callback functions.
+ **/
+struct fb_mqtt_funcs
+{
+ /**
+ * The error function. This is called whenever an error occurs
+ * within the #fb_mqtt.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param err The #GError.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*error) (fb_mqtt_t *mqtt, GError *err, gpointer data);
+
+ /**
+ * The open function. This is called when the connection to the
+ * MQTT has been initialized. This is called as a result of
+ * #fb_mqtt_open(). This function should call #fb_mqtt_connect().
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*open) (fb_mqtt_t *mqtt, gpointer data);
+
+ /**
+ * The connack function. This is called when a CONNACK packet is
+ * received. This is called as a result of #fb_mqtt_connect().
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*connack) (fb_mqtt_t *mqtt, gpointer data);
+
+ /**
+ * The publish function. This is called when a PUBLISH packet is
+ * received.
+ *
+ * @param mqtt The #fb_mqtt.
+ * @param topic The message topic.
+ * @param pload The message payload.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*publish) (fb_mqtt_t *mqtt, const gchar *topic,
+ const GByteArray *pload, gpointer data);
+};
+
+/**
+ * The structure for interacting with Facebook MQTT.
+ **/
+struct fb_mqtt
+{
+ gboolean connected; /** TRUE if connected, otherwise FALSE. **/
+
+ fb_mqtt_funcs_t funcs; /** The #fb_mqtt_funcs. **/
+ gpointer data; /** The user defined data or NULL. **/
+
+ GError *err; /** The #GError or NULL. **/
+ gpointer ssl; /** The SSL connection or NULL. **/
+ gint tev; /** The timer event identifier. **/
+ gint rev; /** The read event identifier. **/
+ gint wev; /** The write event identifier. **/
+
+ GByteArray *rbuf; /** The read buffer. **/
+ GByteArray *wbuf; /** The write buffer. **/
+ gsize remz; /** The remaining read size. **/
+
+ guint16 mid; /** The message identifier. **/
+};
+
+/**
+ * The structure of a #fb_mqtt message.
+ **/
+struct fb_mqtt_msg
+{
+ fb_mqtt_msg_type_t type; /** The #fb_mqtt_msg_type. **/
+ fb_mqtt_msg_flags_t flags; /** The #fb_mqtt_msg_flags. **/
+
+ GByteArray *bytes; /** The #GByteArray of data. **/
+ guint offset; /** The offset of the data. **/
+ guint pos; /** The cursor position. **/
+
+ gboolean local; /** TRUE if the data is local. **/
+};
+
+
+#define FB_MQTT_ERROR fb_mqtt_error_quark()
+
+GQuark fb_mqtt_error_quark(void);
+
+fb_mqtt_t *fb_mqtt_new(const fb_mqtt_funcs_t *funcs, gpointer data);
+
+void fb_mqtt_free(fb_mqtt_t *mqtt);
+
+void fb_mqtt_close(fb_mqtt_t *mqtt);
+
+void fb_mqtt_error(fb_mqtt_t *mqtt, fb_mqtt_error_t err,
+ const gchar *fmt, ...);
+
+void fb_mqtt_read(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg);
+
+void fb_mqtt_write(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg);
+
+void fb_mqtt_open(fb_mqtt_t *mqtt, const gchar *host, gint port);
+
+void fb_mqtt_connect(fb_mqtt_t *mqtt, guint8 flags, const gchar *cid, ...)
+ G_GNUC_NULL_TERMINATED;
+
+gboolean fb_mqtt_connected(fb_mqtt_t *mqtt, gboolean error);
+
+void fb_mqtt_disconnect(fb_mqtt_t *mqtt);
+
+void fb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic,
+ const GByteArray *bytes);
+
+void fb_mqtt_subscribe(fb_mqtt_t *mqtt, const gchar *topic1, guint16 qos1, ...)
+ G_GNUC_NULL_TERMINATED;
+
+void fb_mqtt_unsubscribe(fb_mqtt_t *mqtt, const gchar *topic1, ...)
+ G_GNUC_NULL_TERMINATED;
+
+fb_mqtt_msg_t *fb_mqtt_msg_new(fb_mqtt_msg_type_t type,
+ fb_mqtt_msg_flags_t flags);
+
+fb_mqtt_msg_t *fb_mqtt_msg_new_bytes(GByteArray *bytes);
+
+void fb_mqtt_msg_free(fb_mqtt_msg_t *msg);
+
+void fb_mqtt_msg_reset(fb_mqtt_msg_t *msg);
+
+const GByteArray *fb_mqtt_msg_bytes(fb_mqtt_msg_t *msg);
+
+gboolean fb_mqtt_msg_read(fb_mqtt_msg_t *msg, gpointer data, guint size);
+
+gboolean fb_mqtt_msg_read_r(fb_mqtt_msg_t *msg, GByteArray *bytes);
+
+gboolean fb_mqtt_msg_read_byte(fb_mqtt_msg_t *msg, guint8 *byte);
+
+gboolean fb_mqtt_msg_read_mid(fb_mqtt_msg_t *msg, guint16 *mid);
+
+gboolean fb_mqtt_msg_read_u16(fb_mqtt_msg_t *msg, guint16 *u16);
+
+gboolean fb_mqtt_msg_read_str(fb_mqtt_msg_t *msg, gchar **str);
+
+void fb_mqtt_msg_write(fb_mqtt_msg_t *msg, gconstpointer data, guint size);
+
+void fb_mqtt_msg_write_byte(fb_mqtt_msg_t *msg, guint8 byte);
+
+void fb_mqtt_msg_write_mid(fb_mqtt_msg_t *msg, guint16 *mid);
+
+void fb_mqtt_msg_write_u16(fb_mqtt_msg_t *msg, guint16 u16);
+
+void fb_mqtt_msg_write_str(fb_mqtt_msg_t *msg, const gchar *str);
+
+#endif /* _FACEBOOK_MQTT_H */
diff --git a/facebook/facebook-util.c b/facebook/facebook-util.c
index 84cc581..5cea807 100644
--- a/facebook/facebook-util.c
+++ b/facebook/facebook-util.c
@@ -15,7 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <stdarg.h>
#include <string.h>
+#include <zlib.h>
#include "facebook-util.h"
@@ -41,6 +43,80 @@ gboolean fb_util_debugging(void)
#endif /* DEBUG_FACEBOOK */
/**
+ * Dumps a #GByteArray to the debugging stream. This formats the output
+ * similar to that of `hexdump -C`.
+ *
+ * @param bytes The #GByteArray.
+ * @param indent The indent width.
+ * @param fmt The format string or NULL.
+ * @param ... The format arguments.
+ **/
+#ifdef DEBUG_FACEBOOK
+void fb_util_hexdump(const GByteArray *bytes, guint indent,
+ const gchar *fmt, ...)
+{
+ GString *gstr;
+ va_list ap;
+ gchar *instr;
+ guint i;
+ guint j;
+ gchar c;
+
+ if (fmt != NULL) {
+ va_start(ap, fmt);
+ instr = g_strdup_vprintf(fmt, ap);
+ FB_UTIL_DEBUGLN("%s", instr);
+ g_free(instr);
+ va_end(ap);
+ }
+
+ instr = g_strnfill(indent, ' ');
+ gstr = g_string_sized_new(80);
+ i = 0;
+
+ if (G_UNLIKELY(bytes == NULL))
+ goto finish;
+
+ for (; i < bytes->len; i += 16) {
+ g_string_append_printf(gstr, "%s%08x ", instr, i);
+
+ for (j = 0; j < 16; j++) {
+ if ((i + j) < bytes->len) {
+ g_string_append_printf(gstr, "%02x ", bytes->data[i + j]);
+ } else {
+ g_string_append(gstr, " ");
+ }
+
+ if (j == 7)
+ g_string_append_c(gstr, ' ');
+ }
+
+ g_string_append(gstr, " |");
+
+ for (j = 0; (j < 16) && ((i + j) < bytes->len); j++) {
+ c = bytes->data[i + j];
+
+ if (!g_ascii_isprint(c) || g_ascii_isspace(c))
+ c = '.';
+
+ g_string_append_c(gstr, c);
+ }
+
+ g_string_append_c(gstr, '|');
+ FB_UTIL_DEBUGLN("%s", gstr->str);
+ g_string_erase(gstr, 0, -1);
+ }
+
+finish:
+ g_string_append_printf(gstr, "%s%08x", instr, i);
+ FB_UTIL_DEBUGLN("%s", gstr->str);
+
+ g_string_free(gstr, TRUE);
+ g_free(instr);
+}
+#endif /* DEBUG_FACEBOOK */
+
+/**
* Compare two strings case insensitively. This is useful for where
* the return value must be a boolean, such as with a #GEqualFunc.
*
@@ -53,3 +129,148 @@ gboolean fb_util_str_iequal(const gchar *s1, const gchar *s2)
{
return g_ascii_strcasecmp(s1, s2) == 0;
}
+
+/**
+ * Implemented #alloc_func for #g_malloc().
+ *
+ * @param opaque The user-defined data, which is NULL.
+ * @param items The number of items.
+ * @param size The size of each item.
+ *
+ * @return The pointer to the allocated memory.
+ **/
+static voidpf fb_util_zalloc(voidpf opaque, uInt items, uInt size)
+{
+ return g_malloc(size * items);
+}
+
+/**
+ * Implemented #free_func for #g_free().
+ *
+ * @param opaque The user-defined data, which is NULL.
+ * @param address The pointer address.
+ **/
+static void fb_util_zfree(voidpf opaque, voidpf address)
+{
+ g_free(address);
+}
+
+/**
+ * Determines if a #GByteArray is zlib compressed.
+ *
+ * @param bytes The #GByteArray.
+ *
+ * @return TRUE if the #GByteArray is compressed, otherwise FALSE.
+ **/
+gboolean fb_util_zcompressed(const GByteArray *bytes)
+{
+ guint8 b0;
+ guint8 b1;
+
+ g_return_if_fail(bytes != NULL);
+
+ if (bytes->len < 2)
+ return FALSE;
+
+ b0 = *(bytes->data + 0);
+ b1 = *(bytes->data + 1);
+
+ return ((((b0 << 8) | b1) % 31) == 0) && /* Check the header */
+ ((b0 & 0x0F) == Z_DEFLATED); /* Check the method */
+}
+
+/**
+ * Compresses a #GByteArray with zlib. The returned #GByteArray should
+ * be freed with #g_byte_array_free() when no longer needed.
+ *
+ * @param bytes The #GByteArray.
+ *
+ * @return The resulting #GByteArray, or NULL on error.
+ **/
+GByteArray *fb_util_zcompress(const GByteArray *bytes)
+{
+ GByteArray *ret;
+ z_stream zs;
+ gsize size;
+ gint res;
+
+ g_return_if_fail(bytes != NULL);
+
+ memset(&zs, 0, sizeof zs);
+ zs.zalloc = fb_util_zalloc;
+ zs.zfree = fb_util_zfree;
+ zs.next_in = bytes->data;
+ zs.avail_in = bytes->len;
+
+ if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK)
+ return NULL;
+
+ size = compressBound(bytes->len);
+ ret = g_byte_array_new();
+
+ g_byte_array_set_size(ret, size);
+
+ zs.next_out = ret->data;
+ zs.avail_out = size;
+
+ res = deflate(&zs, Z_FINISH);
+
+ if (res != Z_STREAM_END) {
+ deflateEnd(&zs);
+ g_byte_array_free(ret, TRUE);
+ return NULL;
+ }
+
+ size -= zs.avail_out;
+ g_byte_array_remove_range(ret, size, ret->len - size);
+
+ deflateEnd(&zs);
+ return ret;
+}
+
+/**
+ * Uncompresses a zlib compressed #GByteArray. The returned #GByteArray
+ * should be freed with #g_byte_array_free() when no longer needed.
+ *
+ * @param bytes The #GByteArray.
+ *
+ * @return The resulting #GByteArray, or NULL on error.
+ **/
+GByteArray *fb_util_zuncompress(const GByteArray *bytes)
+{
+ GByteArray *ret;
+ z_stream zs;
+ guint8 out[1024];
+ gint res;
+
+ g_return_if_fail(bytes != NULL);
+
+ memset(&zs, 0, sizeof zs);
+ zs.zalloc = fb_util_zalloc;
+ zs.zfree = fb_util_zfree;
+ zs.next_in = bytes->data;
+ zs.avail_in = bytes->len;
+
+ if (inflateInit(&zs) != Z_OK)
+ return NULL;
+
+ ret = g_byte_array_new();
+
+ do {
+ zs.next_out = out;
+ zs.avail_out = sizeof out;
+
+ res = inflate(&zs, Z_NO_FLUSH);
+
+ if ((res != Z_OK) && (res != Z_STREAM_END)) {
+ inflateEnd(&zs);
+ g_byte_array_free(ret, TRUE);
+ return NULL;
+ }
+
+ g_byte_array_append(ret, out, sizeof out - zs.avail_out);
+ } while (res != Z_STREAM_END);
+
+ inflateEnd(&zs);
+ return ret;
+}
diff --git a/facebook/facebook-util.h b/facebook/facebook-util.h
index 3385a6b..f1fda54 100644
--- a/facebook/facebook-util.h
+++ b/facebook/facebook-util.h
@@ -44,6 +44,19 @@
gboolean fb_util_debugging(void);
#endif /* DEBUG_FACEBOOK */
+#ifdef DEBUG_FACEBOOK
+void fb_util_hexdump(const GByteArray *bytes, guint indent,
+ const gchar *fmt, ...);
+#else /* DEBUG_FACEBOOK */
+#define fb_util_hexdump(bs, d, i)
+#endif /* DEBUG_FACEBOOK */
+
gboolean fb_util_str_iequal(const gchar *s1, const gchar *s2);
+gboolean fb_util_zcompressed(const GByteArray *bytes);
+
+GByteArray *fb_util_zcompress(const GByteArray *bytes);
+
+GByteArray *fb_util_zuncompress(const GByteArray *bytes);
+
#endif /* _FACEBOOK_UTIL_H */
diff --git a/facebook/facebook.c b/facebook/facebook.c
index f96b05b..ca2d687 100644
--- a/facebook/facebook.c
+++ b/facebook/facebook.c
@@ -45,6 +45,7 @@ static void fb_cb_api_auth(fb_api_t *api, gpointer data)
fb_data_t *fata = data;
account_t *acc = fata->ic->acc;
+ set_setstr(&acc->set, "uid", api->uid);
set_setstr(&acc->set, "token", api->token);
imcb_log(fata->ic, "Authentication finished");
@@ -53,6 +54,18 @@ static void fb_cb_api_auth(fb_api_t *api, gpointer data)
}
/**
+ * Implemented #fb_api_funcs->connect().
+ *
+ * @param api The #fb_api.
+ * @param data The user defined data, which is #fb_data.
+ **/
+static void fb_cb_api_connect(fb_api_t *api, gpointer data)
+{
+ fb_data_t *fata = data;
+ imcb_connected(fata->ic);
+}
+
+/**
* Creates a new #fb_data with an #account. The returned #fb_data
* should be freed with #fb_data_free() when no longer needed.
*
@@ -65,8 +78,9 @@ fb_data_t *fb_data_new(account_t *acc)
fb_data_t *fata;
static const fb_api_funcs_t funcs = {
- .error = fb_cb_api_error,
- .auth = fb_cb_api_auth
+ .error = fb_cb_api_error,
+ .auth = fb_cb_api_auth,
+ .connect = fb_cb_api_connect
};
g_return_val_if_fail(acc != NULL, NULL);
@@ -77,7 +91,17 @@ fb_data_t *fb_data_new(account_t *acc)
fata->ic = imcb_new(acc);
fata->ic->proto_data = fata;
+ fata->api->uid = g_strdup(set_getstr(&acc->set, "uid"));
fata->api->token = g_strdup(set_getstr(&acc->set, "token"));
+ fata->api->cid = g_strdup(set_getstr(&acc->set, "cid"));
+ fata->api->mid = g_strdup(set_getstr(&acc->set, "mid"));
+ fata->api->cuid = g_strdup(set_getstr(&acc->set, "cuid"));
+
+ fb_api_rehash(fata->api);
+
+ set_setstr(&acc->set, "cid", fata->api->cid);
+ set_setstr(&acc->set, "mid", fata->api->mid);
+ set_setstr(&acc->set, "cuid", fata->api->cuid);
return fata;
}
@@ -96,7 +120,6 @@ void fb_data_free(fb_data_t *fata)
g_free(fata);
}
-
/**
* Implements #prpl->init(). This initializes an account.
*
@@ -106,8 +129,20 @@ static void fb_init(account_t *acc)
{
set_t *s;
+ s = set_add(&acc->set, "cid", NULL, NULL, acc);
+ s->flags = SET_NULL_OK | SET_HIDDEN;
+
+ s = set_add(&acc->set, "cuid", NULL, NULL, acc);
+ s->flags = SET_NULL_OK | SET_HIDDEN;
+
+ s = set_add(&acc->set, "mid", NULL, NULL, acc);
+ s->flags = SET_NULL_OK | SET_HIDDEN;
+
s = set_add(&acc->set, "token", NULL, NULL, acc);
s->flags = SET_NULL_OK | SET_HIDDEN | SET_PASSWORD;
+
+ s = set_add(&acc->set, "uid", NULL, NULL, acc);
+ s->flags = SET_NULL_OK | SET_HIDDEN;
}
/**
@@ -128,7 +163,7 @@ static void fb_login(account_t *acc)
return;
}
- imcb_connected(fata->ic);
+ fb_api_connect(fata->api);
}
/**
@@ -140,6 +175,7 @@ static void fb_logout(struct im_connection *ic)
{
fb_data_t *fata = ic->proto_data;
+ fb_api_disconnect(fata->api);
fb_data_free(fata);
}
diff --git a/facebook/facebook.h b/facebook/facebook.h
index d22c567..e30ba26 100644
--- a/facebook/facebook.h
+++ b/facebook/facebook.h
@@ -23,6 +23,7 @@
#include <bitlbee.h>
#include "facebook-api.h"
+#include "facebook-mqtt.h"
/** The main structure for the plugin. **/
typedef struct fb_data fb_data_t;
@@ -34,7 +35,7 @@ typedef struct fb_data fb_data_t;
struct fb_data
{
struct im_connection *ic; /** The #im_connection. **/
- fb_api_t *api; /** The #fb_api. **/
+ fb_api_t *api; /** The #fb_api. **/
};