12 Commits

Author SHA1 Message Date
Stephen Paul Weber
f8cda0f65c Actually set the tlscert conn reference
Previously it was always NULL
2025-08-12 12:25:13 +02:00
Steffen Jaeckel
649a3b4fca Bump ubuntu version of release-test CI job
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2025-07-15 16:22:30 +02:00
Steffen Jaeckel
a2b3b6b9e2 Add xmpp_tlscert_get_userdata()
This fixes #249

Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2025-04-02 10:17:21 +02:00
Steffen Jaeckel
dfb3e86824 Use void* to store function pointer when compiled as C23.
[0] decided that `rettype (*foo)();` must now be interpreted as
`rettype (*foo)(void);`. Luckily it also allows now to store function
pointers in a `void*` (c.f. Ch. J.5.7).

[0]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf

Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2025-03-26 17:24:12 +01:00
Steffen Jaeckel
a81f0a8a5f Auto-format according to new clang-format.
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2025-03-26 15:18:52 +01:00
Steffen Jaeckel
5509ec3b35 Update CI
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2025-03-26 14:17:54 +01:00
Steffen Jaeckel
67b1a2f3f4 Add support for DragonFlyBSD in configure.ac
Hinted to by gnemmi in profanity@rooms.dismail.de

Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2025-03-13 19:49:14 +01:00
Steffen Jaeckel
7c2e78937d Release libstrophe-0.14.0
Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
2025-03-13 14:44:37 +01:00
Steffen Jaeckel
b7bae1502f Update docs of SM-serialization.
Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
2025-03-13 14:44:08 +01:00
Steffen Jaeckel
e3d734c5ff Fix SM-serialization testcase
`xmpp_run_once()` uses `FD_SET()` to determine which sockets to `select()`
on. The socket gets initialized to `INVALID_SOCKET = -1` inside
`xmpp_conn_new()` which leads to a buffer overflow once `FD_SET()` is
called.

This is only exposed when enabling a higher optimization level, which
was only done in the `release-test` CI job.

* Fix this testcase by initializing the socket to a possible value.
* Build the Valgrind CI jobs with `-O2`.
* Make the output of the `release-test` more verbose.

Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2024-11-19 16:26:50 +01:00
Steffen Jaeckel
e7b08faaa7 Clean up SM state serialization
* Refactor `xmpp_sm_state_restore()`

Add new internal functions to load u32 and string values. This also brings
length checks in order to verify we don't read more sm_state than there
potentially is and type checks to ensure we have the correct CBOR types.

Each element is added to the queue right after creation, so it won't leak
in case creating its content fails.

* Refactor `sm_state_serialize()`

* Store & restore ints as/from big endian, CBOR requires that

* Bring API names in line with the usual naming

* Auto-format sources

Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
2024-11-19 16:26:50 +01:00
Stephen Paul Weber
f3fa9f557b Allow serializing SM state
It is useful in some applications to get a snapshot of the stream
management state for storage outside the process, in order to recover
from crashes and other things.  The get_sm_state already present is not
suitable for this because it returns a live object tied in to the
current context and such, and containing much unneeded internal-api
data.

Introduce xmpp_sm_state_set_callback which is called every time the
state changes with a serialized state, and a dual xmpp_sm_state_restore
which takes in this serialized state and sets up a new sm_state based on
that.

The serialization is considered opaque from the PoV of the API, but is
based on CBOR to facilitate easy debugging.
2024-11-19 16:26:49 +01:00
23 changed files with 644 additions and 51 deletions

View File

@@ -10,12 +10,12 @@ on:
jobs: jobs:
linux-tests: linux-tests:
runs-on: ubuntu-20.04 runs-on: ubuntu-24.04
strategy: strategy:
matrix: matrix:
valgrind: valgrind:
- { configure: '' , make: 'check' } - { configure: '' , cflags: '', make: 'check' }
- { configure: '--enable-valgrind' , make: 'check-valgrind' } - { configure: '--enable-valgrind' , cflags: '-O2', make: 'check-valgrind' }
options: options:
- { configure: '' } - { configure: '' }
- { configure: '--without-libxml2' } - { configure: '--without-libxml2' }
@@ -35,7 +35,7 @@ jobs:
- name: Build the library - name: Build the library
run: | run: |
./bootstrap.sh ./bootstrap.sh
./configure ${{ matrix.options.configure }} ${{ matrix.valgrind.configure }} CFLAGS="-Werror -g3" ./configure ${{ matrix.options.configure }} ${{ matrix.valgrind.configure }} CFLAGS="-Werror -g3 ${{ matrix.valgrind.cflags }}"
make -j$(nproc) make -j$(nproc)
- name: Run tests - name: Run tests
run: | run: |
@@ -46,7 +46,7 @@ jobs:
cat test-suite*.log || true cat test-suite*.log || true
xssl-tests: xssl-tests:
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
strategy: strategy:
matrix: matrix:
xssl_versions: xssl_versions:
@@ -111,7 +111,7 @@ jobs:
cat test-suite*.log || true cat test-suite*.log || true
release-test: release-test:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
name: Check if release would work name: Check if release would work
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -135,9 +135,14 @@ jobs:
if: ${{ !failure() }} if: ${{ !failure() }}
run: | run: |
cat testbuild.log cat testbuild.log
- name: Error logs
if: ${{ failure() }}
run: |
cat testbuild.log || true
cat testerr.log || true
code-style: code-style:
runs-on: ubuntu-20.04 runs-on: ubuntu-24.04
name: Check coding style name: Check coding style
continue-on-error: true continue-on-error: true
steps: steps:

2
.gitignore vendored
View File

@@ -46,6 +46,7 @@ examples/roster
examples/uuid examples/uuid
examples/vcard examples/vcard
testbuild*.log testbuild*.log
testerr*.log
test-release/ test-release/
test_stamp test_stamp
test-suite*.log test-suite*.log
@@ -65,6 +66,7 @@ tests/test_resolver
tests/test_sasl tests/test_sasl
tests/test_scram tests/test_scram
tests/test_send_queue tests/test_send_queue
tests/test_serialize_sm
tests/test_sha1 tests/test_sha1
tests/test_sha256 tests/test_sha256
tests/test_sha512 tests/test_sha512

View File

@@ -1,3 +1,13 @@
0.14.0
- Add SM-state serialization mechanism (#239)
- Add support for `SCRAM-SHA-512-PLUS` (da122981)
- Add support for `SSLKEYLOGFILE` with OpenSSL (d82df127)
- Fix some issues related to Stream Management (03d43132, 4359536a)
- Fix a potential segfault (9fef4b7d)
- New API:
- xmpp_conn_set_sm_callback()
- xmpp_conn_restore_sm_state()
0.13.1 0.13.1
- Fix SCRAM-*-PLUS SASL mechanisms with OpenSSL and TLS < v1.3 (40f2452) - Fix SCRAM-*-PLUS SASL mechanisms with OpenSSL and TLS < v1.3 (40f2452)
- Only signal "stream negotiation success" once (1cf09b1) - Only signal "stream negotiation success" once (1cf09b1)

View File

@@ -38,7 +38,7 @@ PROJECT_NAME = Strophe
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 0.13 PROJECT_NUMBER = 0.14
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a

View File

@@ -190,6 +190,7 @@ STATIC_TESTS = \
tests/test_jid \ tests/test_jid \
tests/test_ctx \ tests/test_ctx \
tests/test_send_queue \ tests/test_send_queue \
tests/test_serialize_sm \
tests/test_string \ tests/test_string \
tests/test_resolver tests/test_resolver
@@ -285,6 +286,11 @@ tests_test_send_queue_CFLAGS = -I$(top_srcdir)/src
tests_test_send_queue_LDADD = $(STROPHE_LIBS) tests_test_send_queue_LDADD = $(STROPHE_LIBS)
tests_test_send_queue_LDFLAGS = -static tests_test_send_queue_LDFLAGS = -static
tests_test_serialize_sm_SOURCES = tests/test_serialize_sm.c tests/test.c tests/test.h
tests_test_serialize_sm_CFLAGS = -I$(top_srcdir)/src
tests_test_serialize_sm_LDADD = $(STROPHE_LIBS)
tests_test_serialize_sm_LDFLAGS = -static
tests_test_snprintf_SOURCES = tests/test_snprintf.c tests_test_snprintf_SOURCES = tests/test_snprintf.c
tests_test_snprintf_CFLAGS = -I$(top_srcdir)/src tests_test_snprintf_CFLAGS = -I$(top_srcdir)/src
@@ -327,6 +333,7 @@ dist-archives:
test-release: dist test-release: dist
@touch testbuild-$(PACKAGE_VERSION).log && ln -sf testbuild-$(PACKAGE_VERSION).log testbuild.log @touch testbuild-$(PACKAGE_VERSION).log && ln -sf testbuild-$(PACKAGE_VERSION).log testbuild.log
@touch testerr-$(PACKAGE_VERSION).log && ln -sf testerr-$(PACKAGE_VERSION).log testerr.log
@mkdir -p test-release && cp $(PACKAGE_TARNAME)-$(PACKAGE_VERSION).tar.* test-release && pushd test-release && \ @mkdir -p test-release && cp $(PACKAGE_TARNAME)-$(PACKAGE_VERSION).tar.* test-release && pushd test-release && \
tar xzf $(PACKAGE_TARNAME)-$(PACKAGE_VERSION).tar.gz && pushd $(PACKAGE_TARNAME)-$(PACKAGE_VERSION) && ./testbuild.sh && popd && rm -rf $(PACKAGE_TARNAME)-$(PACKAGE_VERSION) && \ tar xzf $(PACKAGE_TARNAME)-$(PACKAGE_VERSION).tar.gz && pushd $(PACKAGE_TARNAME)-$(PACKAGE_VERSION) && ./testbuild.sh && popd && rm -rf $(PACKAGE_TARNAME)-$(PACKAGE_VERSION) && \
echo "Success" && popd echo "Success" && popd

View File

@@ -1,6 +1,6 @@
m4_define([v_maj], [0]) m4_define([v_maj], [0])
m4_define([v_min], [13]) m4_define([v_min], [14])
m4_define([v_patch], [1]) m4_define([v_patch], [0])
m4_define([project_version], [v_maj.v_min.v_patch]) m4_define([project_version], [v_maj.v_min.v_patch])
m4_define([lt_cur], m4_eval(v_maj + v_min)) m4_define([lt_cur], m4_eval(v_maj + v_min))
@@ -18,9 +18,10 @@ PKG_PROG_PKG_CONFIG
AC_CANONICAL_HOST AC_CANONICAL_HOST
AS_CASE([$host_os], AS_CASE([$host_os],
[dragonfly*], [PLATFORM="bsd"],
[freebsd*], [PLATFORM="bsd"], [freebsd*], [PLATFORM="bsd"],
[openbsd*], [PLATFORM="bsd"],
[netbsd*], [PLATFORM="bsd"], [netbsd*], [PLATFORM="bsd"],
[openbsd*], [PLATFORM="bsd"],
[*nto*|*qnx*], [PLATFORM="qnx"], [*nto*|*qnx*], [PLATFORM="qnx"],
[*solaris*], [PLATFORM="solaris"], [*solaris*], [PLATFORM="solaris"],
[*android*], [PLATFORM="android"], [*android*], [PLATFORM="android"],

View File

@@ -1232,6 +1232,7 @@ static void _sm_enable(xmpp_conn_t *conn)
send_stanza(conn, enable, XMPP_QUEUE_SM_STROPHE); send_stanza(conn, enable, XMPP_QUEUE_SM_STROPHE);
conn->sm_state->sm_sent_nr = 0; conn->sm_state->sm_sent_nr = 0;
conn->sm_state->sm_enabled = 1; conn->sm_state->sm_enabled = 1;
trigger_sm_callback(conn);
} }
static int static int
@@ -1486,6 +1487,8 @@ static int _handle_sm(xmpp_conn_t *const conn,
name = NULL; name = NULL;
} }
trigger_sm_callback(conn);
err_sm: err_sm:
if (!name) { if (!name) {
char *err = "Couldn't convert stanza to text!"; char *err = "Couldn't convert stanza to text!";

View File

@@ -30,12 +30,17 @@
#include "snprintf.h" #include "snprintf.h"
/** handlers **/ /** handlers **/
#if (__STDC_VERSION__ >= 202000L)
typedef void *xmpp_void_handler;
#else
typedef int (*xmpp_void_handler)();
#endif
typedef struct _xmpp_handlist_t xmpp_handlist_t; typedef struct _xmpp_handlist_t xmpp_handlist_t;
struct _xmpp_handlist_t { struct _xmpp_handlist_t {
/* common members */ /* common members */
int user_handler; int user_handler;
int (*handler)(); xmpp_void_handler handler;
void *userdata; void *userdata;
int enabled; /* handlers are added disabled and enabled after the int enabled; /* handlers are added disabled and enabled after the
* handler chain is processed to prevent stanzas from * handler chain is processed to prevent stanzas from
@@ -323,6 +328,8 @@ struct _xmpp_conn_t {
hash_t *id_handlers; hash_t *id_handlers;
xmpp_handlist_t *handlers; xmpp_handlist_t *handlers;
xmpp_sockopt_callback sockopt_cb; xmpp_sockopt_callback sockopt_cb;
xmpp_sm_callback sm_callback;
void *sm_callback_ctx;
}; };
void conn_disconnect(xmpp_conn_t *conn); void conn_disconnect(xmpp_conn_t *conn);
@@ -376,6 +383,7 @@ void handler_add(xmpp_conn_t *conn,
void handler_system_delete_all(xmpp_conn_t *conn); void handler_system_delete_all(xmpp_conn_t *conn);
/* utility functions */ /* utility functions */
void trigger_sm_callback(xmpp_conn_t *conn);
void reset_sm_state(xmpp_sm_state_t *sm_state); void reset_sm_state(xmpp_sm_state_t *sm_state);
void disconnect_mem_error(xmpp_conn_t *conn); void disconnect_mem_error(xmpp_conn_t *conn);

View File

@@ -21,6 +21,7 @@
*/ */
#include <errno.h> #include <errno.h>
#include <netinet/in.h>
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
@@ -1254,34 +1255,334 @@ int xmpp_conn_is_disconnected(xmpp_conn_t *conn)
} }
/** /**
* This returns the Stream Management state of a connection object after * This sets the Stream Management callback function
* it has been disconnected.
* One can then initialise a fresh connection object and set this Stream
* Management state by calling \ref xmpp_conn_set_sm_state
* *
* In case one wants to dispose of the state w/o setting it into a fresh * After setting this API, the library will call the given callback function
* connection object, one can call \ref xmpp_free_sm_state * each time when the internal SM state is updated.
* *
* After calling this function to retrieve the state, only call one of the * This can be used in conjunction with \ref xmpp_conn_restore_sm_state to
* other two. * e.g. implement a mechanism that retains an SM state over potential
* application terminations.
* *
* @param conn a Strophe connection object * @param conn a Strophe connection object
* @return The Stream Management state of the connection or NULL on error * @param cb a callback function or NULL to disable
* @param ctx a context that will be passed on invocation of the callback
* function
* *
* @ingroup Connections * @ingroup Connections
*/ */
xmpp_sm_state_t *xmpp_conn_get_sm_state(xmpp_conn_t *conn) void xmpp_conn_set_sm_callback(xmpp_conn_t *conn,
xmpp_sm_callback cb,
void *ctx)
{ {
xmpp_sm_state_t *ret; conn->sm_callback = cb;
conn->sm_callback_ctx = ctx;
}
/* We can only return the SM state when we're disconnected */ struct sm_restore {
if (conn->state != XMPP_STATE_DISCONNECTED) xmpp_conn_t *conn;
return NULL; const unsigned char *state;
const unsigned char *const state_end, *const orig;
};
ret = conn->sm_state; static int sm_load_u32(struct sm_restore *sm, uint8_t type, uint32_t *val)
conn->sm_state = NULL; {
if (*sm->state != type) {
strophe_error(
sm->conn->ctx, "conn",
"Invalid CBOR type at position %u: 0x%02x, expected: 0x%02x",
sm->state - sm->orig, *sm->state, type);
return XMPP_EINVOP;
}
sm->state++;
if ((sm->state + 4) > sm->state_end) {
strophe_error(sm->conn->ctx, "conn",
"Provided sm_state data is too short");
return XMPP_EINVOP;
}
uint32_t v;
memcpy(&v, sm->state, 4);
sm->state += 4;
*val = ntohl(v);
return 0;
}
static int sm_load_string(struct sm_restore *sm, char **val, size_t *len)
{
uint32_t l;
int ret = sm_load_u32(sm, 0x7a, &l);
if (ret)
return ret; return ret;
if ((sm->state + l) > sm->state_end) {
strophe_error(sm->conn->ctx, "conn",
"Provided sm_state data is too short");
return XMPP_EINVOP;
}
*val = strophe_alloc(sm->conn->ctx, l + 1);
if (!*val)
return XMPP_EMEM;
memcpy(*val, sm->state, l);
(*val)[l] = '\0';
sm->state += l;
*len = l;
return 0;
}
/**
* This restores the serialized Stream Management state
*
* After setting this API, the library will call the given callback function
* each time when the internal SM state is updated.
*
* This can be used in conjunction with \ref xmpp_conn_restore_sm_state to
* e.g. implement a mechanism that retains an SM state over potential
* application terminations.
*
* @param conn a Strophe connection object
* @param sm_state a buffer as passed to the SM callback
* @param sm_state_len the length of `sm_state`
*
* @ingroup Connections
*/
int xmpp_conn_restore_sm_state(xmpp_conn_t *conn,
const unsigned char *sm_state,
size_t sm_state_len)
{
/* We can only set the SM state when we're disconnected */
if (conn->state != XMPP_STATE_DISCONNECTED) {
strophe_error(conn->ctx, "conn",
"SM state can only be set the when we're disconnected");
return XMPP_EINVOP;
}
if (conn->sm_state) {
strophe_error(conn->ctx, "conn", "SM state is already set!");
return XMPP_EINVOP;
}
if (sm_state_len < 5 * 6) {
strophe_error(conn->ctx, "conn", "Provided sm_state data is too short");
return XMPP_EINVOP;
}
struct sm_restore sm = {.conn = conn,
.state = sm_state,
.state_end = sm_state + sm_state_len,
.orig = sm_state};
/* Check for pointer wrap-around, which should never happen */
if (sm.state_end < sm.state) {
strophe_error(conn->ctx, "conn",
"Internal error, pointer wrapped around");
return XMPP_EINVOP;
}
if (memcmp(sm.state, "\x1a\x00\x00\x00\x00", 5) != 0) {
strophe_error(conn->ctx, "conn", "Unknown sm_state version");
return XMPP_EINVOP;
}
sm.state += 5;
conn->sm_state = strophe_alloc(conn->ctx, sizeof(*conn->sm_state));
if (!conn->sm_state)
return XMPP_EMEM;
memset(conn->sm_state, 0, sizeof(*conn->sm_state));
conn->sm_state->ctx = conn->ctx;
conn->sm_state->sm_support = 1;
conn->sm_state->sm_enabled = 1;
conn->sm_state->can_resume = 1;
conn->sm_state->resume = 1;
int ret;
ret = sm_load_u32(&sm, 0x1a, &conn->sm_state->sm_sent_nr);
if (ret)
goto err_reload;
ret = sm_load_u32(&sm, 0x1a, &conn->sm_state->sm_handled_nr);
if (ret)
goto err_reload;
size_t id_len;
ret = sm_load_string(&sm, &conn->sm_state->id, &id_len);
if (ret)
goto err_reload;
uint32_t len, i;
ret = sm_load_u32(&sm, 0x9a, &len);
if (ret)
goto err_reload;
conn->send_queue_user_len = conn->send_queue_len = len;
for (i = 0; i < len; i++) {
xmpp_send_queue_t *item = strophe_alloc(conn->ctx, sizeof(*item));
if (!item) {
ret = XMPP_EMEM;
goto err_reload;
}
memset(item, 0, sizeof(*item));
if (!conn->send_queue_tail) {
conn->send_queue_head = item;
conn->send_queue_tail = item;
} else {
conn->send_queue_tail->next = item;
conn->send_queue_tail = item;
}
ret = sm_load_string(&sm, &item->data, &item->len);
if (ret)
goto err_reload;
item->owner = XMPP_QUEUE_USER;
}
ret = sm_load_u32(&sm, 0xba, &len);
if (ret)
goto err_reload;
for (i = 0; i < len; i++) {
xmpp_send_queue_t *item = strophe_alloc(conn->ctx, sizeof(*item));
if (!item) {
ret = XMPP_EMEM;
goto err_reload;
}
memset(item, 0, sizeof(*item));
add_queue_back(&conn->sm_state->sm_queue, item);
ret = sm_load_u32(&sm, 0x1a, &item->sm_h);
if (ret)
goto err_reload;
ret = sm_load_string(&sm, &item->data, &item->len);
if (ret)
goto err_reload;
item->owner = XMPP_QUEUE_USER;
}
return XMPP_EOK;
err_reload:
xmpp_free_sm_state(conn->sm_state);
return ret;
}
static int sm_store_u32(unsigned char **next_,
const unsigned char *const end,
uint8_t type,
uint32_t val)
{
unsigned char *next = *next_;
if (next + 5 > end)
return 1;
*next++ = type;
uint32_t v = htonl(val);
memcpy(next, &v, 4);
next += 4;
*next_ = next;
return 0;
}
static size_t sm_state_serialize(xmpp_conn_t *conn, unsigned char **buf)
{
if (!conn->sm_state->sm_support || !conn->sm_state->sm_enabled ||
!conn->sm_state->can_resume) {
*buf = NULL;
return 0;
}
uint32_t id_len = strlen(conn->sm_state->id);
xmpp_send_queue_t *peek = conn->sm_state->sm_queue.head;
size_t sm_queue_len = 0;
size_t sm_queue_size = 0;
while (peek) {
sm_queue_len++;
sm_queue_size += 10 + peek->len;
peek = peek->next;
}
uint32_t send_queue_len = 0;
size_t send_queue_size = 0;
peek = conn->send_queue_head;
while (peek) {
send_queue_len++;
send_queue_size += 5 + peek->len;
peek = peek->next;
}
size_t buf_size =
5 + 5 + 5 + 5 + id_len + 5 + send_queue_size + 5 + sm_queue_size;
*buf = strophe_alloc(conn->ctx, buf_size);
if (*buf == NULL)
return 0;
unsigned char *next = *buf;
const unsigned char *const end = next + buf_size;
/* Check for pointer wrap-around, which should never happen */
if (end < next) {
strophe_error(conn->ctx, "conn",
"Internal error, pointer wrapped around");
return 0;
}
memcpy(next, "\x1a\x00\x00\x00\x00", 5); // Version
next += 5;
if (sm_store_u32(&next, end, 0x1a, conn->sm_state->sm_sent_nr))
goto err_serialize;
if (sm_store_u32(&next, end, 0x1a, conn->sm_state->sm_handled_nr))
goto err_serialize;
if (sm_store_u32(&next, end, 0x7a, id_len))
goto err_serialize;
memcpy(next, conn->sm_state->id, id_len);
next += id_len;
if (sm_store_u32(&next, end, 0x9a, send_queue_len))
goto err_serialize;
peek = conn->send_queue_head;
while (peek) {
if (sm_store_u32(&next, end, 0x7a, (uint32_t)peek->len))
goto err_serialize;
if (next + peek->len > end)
goto err_serialize;
memcpy(next, peek->data, peek->len);
next += peek->len;
peek = peek->next;
}
if (sm_store_u32(&next, end, 0xba, sm_queue_len))
goto err_serialize;
peek = conn->sm_state->sm_queue.head;
while (peek) {
if (sm_store_u32(&next, end, 0x1a, peek->sm_h))
goto err_serialize;
if (sm_store_u32(&next, end, 0x7a, (uint32_t)peek->len))
goto err_serialize;
if (next + peek->len > end)
goto err_serialize;
memcpy(next, peek->data, peek->len);
next += peek->len;
peek = peek->next;
}
return buf_size;
err_serialize:
strophe_error(conn->ctx, "conn", "Can't serialize more data, buffer full");
strophe_free(conn->ctx, buf);
return 0;
}
void trigger_sm_callback(xmpp_conn_t *conn)
{
if (!conn || !conn->sm_callback)
return;
unsigned char *buf;
size_t size = sm_state_serialize(conn, &buf);
conn->sm_callback(conn, conn->sm_callback_ctx, buf, size);
strophe_free(conn->ctx, buf);
} }
static void _reset_sm_state_for_reconnect(xmpp_conn_t *conn) static void _reset_sm_state_for_reconnect(xmpp_conn_t *conn)
@@ -1312,6 +1613,37 @@ static void _reset_sm_state_for_reconnect(xmpp_conn_t *conn)
} }
} }
/**
* This returns the Stream Management state of a connection object after
* it has been disconnected.
* One can then initialise a fresh connection object and set this Stream
* Management state by calling \ref xmpp_conn_set_sm_state
*
* In case one wants to dispose of the state w/o setting it into a fresh
* connection object, one can call \ref xmpp_free_sm_state
*
* After calling this function to retrieve the state, only call one of the
* other two.
*
* @param conn a Strophe connection object
* @return The Stream Management state of the connection or NULL on error
*
* @ingroup Connections
*/
xmpp_sm_state_t *xmpp_conn_get_sm_state(xmpp_conn_t *conn)
{
xmpp_sm_state_t *ret;
/* We can only return the SM state when we're disconnected */
if (conn->state != XMPP_STATE_DISCONNECTED)
return NULL;
ret = conn->sm_state;
conn->sm_state = NULL;
return ret;
}
/** /**
* @param conn a Strophe connection object * @param conn a Strophe connection object
* @param sm_state A Stream Management state returned from a call to * @param sm_state A Stream Management state returned from a call to
@@ -1490,7 +1822,9 @@ char *xmpp_conn_send_queue_drop_element(xmpp_conn_t *conn,
conn->sm_state->r_sent = 0; conn->sm_state->r_sent = 0;
} }
/* Finally drop the element */ /* Finally drop the element */
return _drop_send_queue_element(conn, t); char *r = _drop_send_queue_element(conn, t);
trigger_sm_callback(conn);
return r;
} }
/* timed handler for cleanup if normal disconnect procedure takes too long */ /* timed handler for cleanup if normal disconnect procedure takes too long */
@@ -1684,6 +2018,7 @@ static void _handle_stream_end(char *name, void *userdata)
strophe_debug(conn->ctx, "xmpp", "RECV: </stream:stream>"); strophe_debug(conn->ctx, "xmpp", "RECV: </stream:stream>");
/* the session has been terminated properly, i.e. it can't be resumed */ /* the session has been terminated properly, i.e. it can't be resumed */
conn->sm_state->can_resume = 0; conn->sm_state->can_resume = 0;
trigger_sm_callback(conn);
conn_disconnect_clean(conn); conn_disconnect_clean(conn);
} }
@@ -1759,6 +2094,7 @@ static void _conn_sm_handle_stanza(xmpp_conn_t *const conn,
conn->sm_state->r_sent = 0; conn->sm_state->r_sent = 0;
} }
} }
trigger_sm_callback(conn);
} }
static unsigned short _conn_default_port(xmpp_conn_t *conn, static unsigned short _conn_default_port(xmpp_conn_t *conn,
@@ -2045,8 +2381,10 @@ static int _send_raw(xmpp_conn_t *conn,
strophe_debug_verbose(1, conn->ctx, "conn", "Q_ADD: %p", item); strophe_debug_verbose(1, conn->ctx, "conn", "Q_ADD: %p", item);
if (!(owner & XMPP_QUEUE_SM) && conn->sm_state->sm_enabled && if (!(owner & XMPP_QUEUE_SM) && conn->sm_state->sm_enabled &&
!conn->sm_state->r_sent) { !conn->sm_state->r_sent) {
send_raw(conn, req_ack, strlen(req_ack), XMPP_QUEUE_SM_STROPHE, item);
conn->sm_state->r_sent = 1; conn->sm_state->r_sent = 1;
send_raw(conn, req_ack, strlen(req_ack), XMPP_QUEUE_SM_STROPHE, item);
} else {
trigger_sm_callback(conn);
} }
return XMPP_EOK; return XMPP_EOK;
} }

View File

@@ -39,7 +39,7 @@
#include <sys/select.h> #include <sys/select.h>
#include <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#define _sleep(x) usleep((x)*1000) #define _sleep(x) usleep((x) * 1000)
#else #else
#include <winsock2.h> #include <winsock2.h>
#ifndef ETIMEDOUT #ifndef ETIMEDOUT
@@ -174,6 +174,7 @@ void xmpp_run_once(xmpp_ctx_t *ctx, unsigned long timeout)
/* if we've sent everything update the tail */ /* if we've sent everything update the tail */
if (!sq) if (!sq)
conn->send_queue_tail = NULL; conn->send_queue_tail = NULL;
trigger_sm_callback(conn);
} }
intf->flush(intf); intf->flush(intf);

View File

@@ -25,8 +25,6 @@
#include "common.h" #include "common.h"
#include "ostypes.h" #include "ostypes.h"
typedef int (*xmpp_void_handler)();
/* Remove item from the list pointed by head, but don't free it. /* Remove item from the list pointed by head, but don't free it.
* There can be a situation when user's handler deletes another handler which * There can be a situation when user's handler deletes another handler which
* is the previous in the list. handler_fire_stanza() and handler_fire_timed() * is the previous in the list. handler_fire_stanza() and handler_fire_timed()

View File

@@ -37,7 +37,7 @@
#define PUT_32BIT_LSB_FIRST(cp, value) \ #define PUT_32BIT_LSB_FIRST(cp, value) \
do { \ do { \
(cp)[0] = (value)&0xFF; \ (cp)[0] = (value) & 0xFF; \
(cp)[1] = ((value) >> 8) & 0xFF; \ (cp)[1] = ((value) >> 8) & 0xFF; \
(cp)[2] = ((value) >> 16) & 0xFF; \ (cp)[2] = ((value) >> 16) & 0xFF; \
(cp)[3] = ((value) >> 24) & 0xFF; \ (cp)[3] = ((value) >> 24) & 0xFF; \

View File

@@ -68,9 +68,9 @@ struct _xmpp_rand_t {
}; };
/* returns smallest number mupliple of y that not less than x */ /* returns smallest number mupliple of y that not less than x */
#define round_up(x, y) (((x) + (y)-1) / (y) * (y)) #define round_up(x, y) (((x) + (y) - 1) / (y) * (y))
/* returns smallest integer number that not less than x/y */ /* returns smallest integer number that not less than x/y */
#define div_round_up(x, y) (((x) + (y)-1) / (y)) #define div_round_up(x, y) (((x) + (y) - 1) / (y))
/* adds two arrays as numbers in big-endian representation and stores /* adds two arrays as numbers in big-endian representation and stores
* result in the first one. * result in the first one.

View File

@@ -630,7 +630,7 @@ static void netbuf_add_16bitnum(unsigned char *buf,
/* assuming big endian */ /* assuming big endian */
*p++ = (num >> 8) & 0xff; *p++ = (num >> 8) & 0xff;
*p++ = (num)&0xff; *p++ = (num) & 0xff;
*offset += 2; *offset += 2;
} }

View File

@@ -53,7 +53,7 @@ extern "C" {
(y)[0] = (uint8_t)(((x) >> 24) & 255); \ (y)[0] = (uint8_t)(((x) >> 24) & 255); \
(y)[1] = (uint8_t)(((x) >> 16) & 255); \ (y)[1] = (uint8_t)(((x) >> 16) & 255); \
(y)[2] = (uint8_t)(((x) >> 8) & 255); \ (y)[2] = (uint8_t)(((x) >> 8) & 255); \
(y)[3] = (uint8_t)((x)&255); \ (y)[3] = (uint8_t)((x) & 255); \
} while (0) } while (0)
#define LOAD32H(x, y) \ #define LOAD32H(x, y) \
@@ -72,7 +72,7 @@ extern "C" {
(y)[4] = (uint8_t)(((x) >> 24) & 255); \ (y)[4] = (uint8_t)(((x) >> 24) & 255); \
(y)[5] = (uint8_t)(((x) >> 16) & 255); \ (y)[5] = (uint8_t)(((x) >> 16) & 255); \
(y)[6] = (uint8_t)(((x) >> 8) & 255); \ (y)[6] = (uint8_t)(((x) >> 8) & 255); \
(y)[7] = (uint8_t)((x)&255); \ (y)[7] = (uint8_t)((x) & 255); \
} while (0) } while (0)
#define LOAD64H(x, y) \ #define LOAD64H(x, y) \
@@ -97,12 +97,12 @@ extern "C" {
#endif #endif
#define RORc(x, y) \ #define RORc(x, y) \
(((((uint32_t)(x)&0xFFFFFFFFUL) >> (uint32_t)((y)&31)) | \ (((((uint32_t)(x) & 0xFFFFFFFFUL) >> (uint32_t)((y) & 31)) | \
((uint32_t)(x) << (uint32_t)((32 - ((y)&31)) & 31))) & \ ((uint32_t)(x) << (uint32_t)((32 - ((y) & 31)) & 31))) & \
0xFFFFFFFFUL) 0xFFFFFFFFUL)
#define ROR64c(x, y) \ #define ROR64c(x, y) \
(((((x)&CONST64(0xFFFFFFFFFFFFFFFF)) >> ((uint64_t)(y)&CONST64(63))) | \ (((((x) & CONST64(0xFFFFFFFFFFFFFFFF)) >> ((uint64_t)(y) & CONST64(63))) | \
((x) << (((uint64_t)64 - ((y)&63)) & 63))) & \ ((x) << (((uint64_t)64 - ((y) & 63)) & 63))) & \
CONST64(0xFFFFFFFFFFFFFFFF)) CONST64(0xFFFFFFFFFFFFFFFF))
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -9,7 +9,7 @@
#define Ch(x, y, z) (z ^ (x & (y ^ z))) #define Ch(x, y, z) (z ^ (x & (y ^ z)))
#define Maj(x, y, z) (((x | y) & z) | (x & y)) #define Maj(x, y, z) (((x | y) & z) | (x & y))
#define S(x, n) RORc((x), (n)) #define S(x, n) RORc((x), (n))
#define R(x, n) (((x)&0xFFFFFFFFUL) >> (n)) #define R(x, n) (((x) & 0xFFFFFFFFUL) >> (n))
#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) #define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22))
#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) #define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25))
#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) #define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3))

View File

@@ -52,7 +52,7 @@ static const uint64_t K[80] = {
#define Ch(x, y, z) (z ^ (x & (y ^ z))) #define Ch(x, y, z) (z ^ (x & (y ^ z)))
#define Maj(x, y, z) (((x | y) & z) | (x & y)) #define Maj(x, y, z) (((x | y) & z) | (x & y))
#define S(x, n) ROR64c(x, n) #define S(x, n) ROR64c(x, n)
#define R(x, n) (((x)&CONST64(0xFFFFFFFFFFFFFFFF)) >> ((uint64_t)n)) #define R(x, n) (((x) & CONST64(0xFFFFFFFFFFFFFFFF)) >> ((uint64_t)n))
#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39)) #define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39))
#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41)) #define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41))
#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7)) #define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7))

View File

@@ -74,6 +74,23 @@ xmpp_conn_t *xmpp_tlscert_get_conn(const xmpp_tlscert_t *cert)
return cert->conn; return cert->conn;
} }
/** Get the userdata of a Strophe connection which is assigned to this
* certificate.
*
* @param cert a Strophe TLS certificate object
*
* @return the userdata of a Strophe connection object where this certificate
* originates from
*
* @ingroup TLS
*/
void *xmpp_tlscert_get_userdata(const xmpp_tlscert_t *cert)
{
if (cert->conn == NULL)
return NULL;
return cert->conn->userdata;
}
/** Get the complete PEM of this certificate. /** Get the complete PEM of this certificate.
* *
* @param cert a Strophe TLS certificate object * @param cert a Strophe TLS certificate object

View File

@@ -422,6 +422,8 @@ static int _tls_verify(gnutls_session_t session)
return -1; return -1;
} }
tlscert->conn = tls->conn;
if (tls->conn->certfail_handler(tlscert, (char *)out.data) == 0) { if (tls->conn->certfail_handler(tlscert, (char *)out.data) == 0) {
xmpp_tlscert_free(tlscert); xmpp_tlscert_free(tlscert);
gnutls_x509_crt_deinit(cert); gnutls_x509_crt_deinit(cert);

View File

@@ -540,6 +540,8 @@ static int _tls_verify(int preverify_ok, X509_STORE_CTX *x509_ctx)
if (!tlscert) if (!tlscert)
return 0; return 0;
tlscert->conn = conn;
strophe_debug(conn->ctx, "tls", "preverify_ok:%d\nSubject: %s\nIssuer: %s", strophe_debug(conn->ctx, "tls", "preverify_ok:%d\nSubject: %s\nIssuer: %s",
preverify_ok, tlscert->elements[XMPP_CERT_SUBJECT], preverify_ok, tlscert->elements[XMPP_CERT_SUBJECT],
tlscert->elements[XMPP_CERT_ISSUER]); tlscert->elements[XMPP_CERT_ISSUER]);

View File

@@ -18,6 +18,7 @@
#define __LIBSTROPHE_STROPHE_H__ #define __LIBSTROPHE_STROPHE_H__
#include <stddef.h> /* size_t */ #include <stddef.h> /* size_t */
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -414,8 +415,38 @@ typedef enum {
char *xmpp_conn_send_queue_drop_element(xmpp_conn_t *conn, char *xmpp_conn_send_queue_drop_element(xmpp_conn_t *conn,
xmpp_queue_element_t which); xmpp_queue_element_t which);
xmpp_sm_state_t *xmpp_conn_get_sm_state(xmpp_conn_t *conn);
int xmpp_conn_set_sm_state(xmpp_conn_t *conn, xmpp_sm_state_t *sm_state); int xmpp_conn_set_sm_state(xmpp_conn_t *conn, xmpp_sm_state_t *sm_state);
xmpp_sm_state_t *xmpp_conn_get_sm_state(xmpp_conn_t *conn);
/** The function which will be called when Strophe updates its internal SM
* state.
*
* Please note that you have to create a copy of the buffer, since the library
* will free the buffer right after return of the callback function.
*
*
* @param conn The Strophe connection object this callback
* originates from.
* @param ctx The `ctx` pointer as passed to
* \ref xmpp_conn_set_sm_callback
* @param sm_state A pointer to a buffer containing the serialized SM
* state.
* @param sm_state_len The length of `sm_state`.
*
* @return 0 on success, -1 on error
*
* @ingroup Connections
*/
typedef void (*xmpp_sm_callback)(xmpp_conn_t *conn,
void *ctx,
const unsigned char *sm_state,
size_t sm_state_len);
void xmpp_conn_set_sm_callback(xmpp_conn_t *conn,
xmpp_sm_callback cb,
void *ctx);
int xmpp_conn_restore_sm_state(xmpp_conn_t *conn,
const unsigned char *sm_state,
size_t sm_state_len);
void xmpp_free_sm_state(xmpp_sm_state_t *sm_state); void xmpp_free_sm_state(xmpp_sm_state_t *sm_state);
@@ -611,6 +642,7 @@ void xmpp_ctx_set_timeout(xmpp_ctx_t *ctx, unsigned long timeout);
xmpp_ctx_t *xmpp_tlscert_get_ctx(const xmpp_tlscert_t *cert); xmpp_ctx_t *xmpp_tlscert_get_ctx(const xmpp_tlscert_t *cert);
xmpp_conn_t *xmpp_tlscert_get_conn(const xmpp_tlscert_t *cert); xmpp_conn_t *xmpp_tlscert_get_conn(const xmpp_tlscert_t *cert);
void *xmpp_tlscert_get_userdata(const xmpp_tlscert_t *cert);
const char *xmpp_tlscert_get_pem(const xmpp_tlscert_t *cert); const char *xmpp_tlscert_get_pem(const xmpp_tlscert_t *cert);
const char *xmpp_tlscert_get_dnsname(const xmpp_tlscert_t *cert, size_t n); const char *xmpp_tlscert_get_dnsname(const xmpp_tlscert_t *cert, size_t n);
const char *xmpp_tlscert_get_string(const xmpp_tlscert_t *cert, const char *xmpp_tlscert_get_string(const xmpp_tlscert_t *cert,

View File

@@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
logfile="../../testbuild.log" logfile="../../testbuild.log"
errfile="../../testerr.log"
err_out() { err_out() {
tail $logfile tail $logfile
@@ -8,6 +9,6 @@ err_out() {
} }
./bootstrap.sh ./bootstrap.sh
./configure >> $logfile || err_out ./configure >> $logfile 2>> $errfile || err_out
make -j$(( `nproc` * 2 + 1 )) >> $logfile || err_out make -j$(( `nproc` * 2 + 1 )) >> $logfile 2>> $errfile || err_out
make check >> $logfile || err_out make check >> $logfile 2>> $errfile || err_out

166
tests/test_serialize_sm.c Normal file
View File

@@ -0,0 +1,166 @@
/* SPDX-License-Identifier: MIT OR GPL-3.0-only */
/* test_serialize_sm.c
** libstrophe XMPP client library -- test routines for the send queue
**
** Copyright (C) 2024 Stephen Paul Weber
**
** This software is provided AS-IS with no warranty, either express
** or implied.
**
** This program is dual licensed under the MIT or GPLv3 licenses.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "strophe.h"
#include "common.h"
#include "test.h"
void callback(xmpp_conn_t *conn,
void *ctx,
const unsigned char *sm_state,
size_t sm_state_len)
{
int *callback_count = ctx;
(*callback_count)++;
COMPARE_BUF("\x1a\x00\x00\x00\x00", 5, sm_state, 5);
COMPARE_BUF("\x1a\x00\x00\x00\x00", 5, sm_state + 10, 5);
COMPARE_BUF("\x7a\x00\x00\x00\x04SMID", 9, sm_state + 15, 9);
if (*callback_count == 1) {
COMPARE_BUF("\x1a\x00\x00\x00\x00", 5, sm_state + 5, 5);
COMPARE_BUF("\x9a\x00\x00\x00\x02", 5, sm_state + 24, 5);
COMPARE_BUF("\x7a\x00\x00\x00\x03"
"foo",
8, sm_state + 29, 8);
COMPARE_BUF("\x7a\x00\x00\x00\x1a<r xmlns='urn:xmpp:sm:3'/>", 31,
sm_state + 37, 31);
COMPARE_BUF("\xba\x00\x00\x00\x00", 5, sm_state + 68, 5);
ENSURE_EQ(sm_state_len, 73);
xmpp_conn_t *newconn = xmpp_conn_new(conn->ctx);
xmpp_conn_restore_sm_state(newconn, sm_state, sm_state_len);
ENSURE_EQ(newconn->sm_state->sm_sent_nr, 0);
ENSURE_EQ(newconn->sm_state->sm_handled_nr, 0);
COMPARE(newconn->sm_state->id, "SMID");
ENSURE_EQ(newconn->send_queue_len, 2);
ENSURE_EQ(newconn->send_queue_user_len, 2);
ENSURE_EQ((size_t)(newconn->sm_state->sm_queue.head), 0);
xmpp_conn_release(newconn);
}
if (*callback_count == 2) {
COMPARE_BUF("\x1a\x00\x00\x00\x01", 5, sm_state + 5, 5);
COMPARE_BUF("\x9a\x00\x00\x00\x01", 5, sm_state + 24, 5);
COMPARE_BUF("\x7a\x00\x00\x00\x1a<r xmlns='urn:xmpp:sm:3'/>", 31,
sm_state + 29, 31);
COMPARE_BUF("\xba\x00\x00\x00\x01", 5, sm_state + 60, 5);
COMPARE_BUF("\x1a\x00\x00\x00\x00", 5, sm_state + 65, 5);
COMPARE_BUF("\x7a\x00\x00\x00\x03"
"foo",
8, sm_state + 70, 8);
ENSURE_EQ(sm_state_len, 78);
xmpp_conn_t *newconn = xmpp_conn_new(conn->ctx);
xmpp_conn_restore_sm_state(newconn, sm_state, sm_state_len);
ENSURE_EQ(newconn->sm_state->sm_sent_nr, 1);
ENSURE_EQ(newconn->sm_state->sm_handled_nr, 0);
COMPARE(newconn->sm_state->id, "SMID");
ENSURE_EQ(newconn->send_queue_len, 1);
ENSURE_EQ(newconn->send_queue_user_len, 1);
ENSURE_EQ(newconn->sm_state->sm_queue.head->sm_h, 0);
xmpp_conn_release(newconn);
}
if (*callback_count == 3) {
COMPARE_BUF("\x1a\x00\x00\x00\x01", 5, sm_state + 5, 5);
COMPARE_BUF("\x9a\x00\x00\x00\x00", 5, sm_state + 24, 5);
COMPARE_BUF("\xba\x00\x00\x00\x01", 5, sm_state + 29, 5);
COMPARE_BUF("\x1a\x00\x00\x00\x00", 5, sm_state + 34, 5);
COMPARE_BUF("\x7a\x00\x00\x00\x03"
"foo",
8, sm_state + 39, 8);
ENSURE_EQ(sm_state_len, 47);
xmpp_conn_t *newconn = xmpp_conn_new(conn->ctx);
xmpp_conn_restore_sm_state(newconn, sm_state, sm_state_len);
ENSURE_EQ(newconn->sm_state->sm_sent_nr, 1);
ENSURE_EQ(newconn->sm_state->sm_handled_nr, 0);
COMPARE(newconn->sm_state->id, "SMID");
ENSURE_EQ(newconn->send_queue_len, 0);
ENSURE_EQ(newconn->send_queue_user_len, 0);
ENSURE_EQ(newconn->sm_state->sm_queue.head->sm_h, 0);
xmpp_conn_release(newconn);
}
}
int fake_read(struct conn_interface *intf, void *buff, size_t len)
{
return 0;
}
int fake_write(struct conn_interface *intf, const void *buff, size_t len)
{
return len;
}
int fake_flush(struct conn_interface *intf)
{
return 0;
}
int fake_error_is_recoverable(struct conn_interface *intf, int err)
{
return 0;
}
int main()
{
xmpp_ctx_t *ctx;
xmpp_conn_t *conn;
xmpp_log_t *log;
xmpp_conn_state_t state;
xmpp_sm_state_t *sm_state;
char *ret;
unsigned int n;
int callback_count = 0;
xmpp_initialize();
log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG);
ctx = xmpp_ctx_new(NULL, log);
conn = xmpp_conn_new(ctx);
sm_state = strophe_alloc(ctx, sizeof(*sm_state));
memset(sm_state, 0, sizeof(*sm_state));
sm_state->ctx = ctx;
sm_state->sm_support = sm_state->sm_enabled = sm_state->can_resume = 1;
sm_state->id = strophe_strdup(ctx, "SMID");
xmpp_conn_set_sm_state(conn, sm_state);
xmpp_conn_set_sm_callback(conn, callback, &callback_count);
ENSURE_EQ(xmpp_conn_send_queue_len(conn), 0);
struct conn_interface intf = {fake_read, fake_write,
fake_flush, fake_flush,
fake_flush, fake_error_is_recoverable,
conn};
conn->intf = intf;
state = conn->state;
conn->state = XMPP_STATE_CONNECTED;
conn->sock = 123;
xmpp_send_raw(conn, "foo", 3);
ENSURE_EQ(xmpp_conn_send_queue_len(conn), 1);
ENSURE_EQ(callback_count, 1);
xmpp_run_once(ctx, 0);
ENSURE_EQ(callback_count, 3);
conn->state = state;
xmpp_conn_release(conn);
xmpp_ctx_free(ctx);
xmpp_shutdown();
return 0;
}