mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-03-27 22:53:07 +00:00
207 lines
7.0 KiB
C
207 lines
7.0 KiB
C
|
|
/*
|
||
|
|
* c-test platform: Alice-Bob message exchange written entirely in C.
|
||
|
|
*
|
||
|
|
* Delivery (queue + callback) is implemented here in C.
|
||
|
|
* Protocol operations are called through a ClientOps vtable supplied by
|
||
|
|
* the Rust test harness. This keeps the C archive free of direct references
|
||
|
|
* to Rust symbols, avoiding linker-ordering issues.
|
||
|
|
* Persistence is in-memory ephemeral (same as the Rust TestPlatform).
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "client_ffi.h"
|
||
|
|
|
||
|
|
#include <assert.h>
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------
|
||
|
|
* In-memory delivery queue
|
||
|
|
* ------------------------------------------------------------------ */
|
||
|
|
|
||
|
|
/* Protocol messages (intro bundles, ratchet envelopes) are well under 2 KB;
|
||
|
|
* using modest constants keeps both queues inside a few hundred KB of stack. */
|
||
|
|
#define MAX_ENVELOPES 32
|
||
|
|
#define MAX_ENVELOPE_SZ 2048
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
uint8_t data[MAX_ENVELOPE_SZ];
|
||
|
|
size_t len;
|
||
|
|
} Envelope;
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
Envelope items[MAX_ENVELOPES];
|
||
|
|
int head;
|
||
|
|
int tail;
|
||
|
|
int count;
|
||
|
|
} Queue;
|
||
|
|
|
||
|
|
static void queue_init(Queue *q)
|
||
|
|
{
|
||
|
|
memset(q, 0, sizeof(*q));
|
||
|
|
}
|
||
|
|
|
||
|
|
static void queue_push(Queue *q, const uint8_t *data, size_t len)
|
||
|
|
{
|
||
|
|
assert(q->count < MAX_ENVELOPES && "delivery queue overflow");
|
||
|
|
assert(len <= MAX_ENVELOPE_SZ && "envelope too large");
|
||
|
|
memcpy(q->items[q->tail].data, data, len);
|
||
|
|
q->items[q->tail].len = len;
|
||
|
|
q->tail = (q->tail + 1) % MAX_ENVELOPES;
|
||
|
|
q->count++;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int queue_pop(Queue *q, const uint8_t **data_out, size_t *len_out)
|
||
|
|
{
|
||
|
|
if (q->count == 0) return 0;
|
||
|
|
*data_out = q->items[q->head].data;
|
||
|
|
*len_out = q->items[q->head].len;
|
||
|
|
q->head = (q->head + 1) % MAX_ENVELOPES;
|
||
|
|
q->count--;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------
|
||
|
|
* Delivery callback: userdata is the sending client's Queue*.
|
||
|
|
* ------------------------------------------------------------------ */
|
||
|
|
|
||
|
|
static int32_t deliver_cb(
|
||
|
|
const uint8_t *addr_ptr, size_t addr_len,
|
||
|
|
const uint8_t *data_ptr, size_t data_len,
|
||
|
|
void *userdata)
|
||
|
|
{
|
||
|
|
(void)addr_ptr; (void)addr_len; /* routing is done manually below */
|
||
|
|
queue_push((Queue *)userdata, data_ptr, data_len);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------
|
||
|
|
* Helper: pop one envelope from `outbox` and push it into `receiver`.
|
||
|
|
* ------------------------------------------------------------------ */
|
||
|
|
|
||
|
|
static void route(
|
||
|
|
const ClientOps *ops,
|
||
|
|
Queue *outbox,
|
||
|
|
ClientHandle_t *receiver,
|
||
|
|
PushInboundResult_t *out)
|
||
|
|
{
|
||
|
|
const uint8_t *data;
|
||
|
|
size_t len;
|
||
|
|
int ok = queue_pop(outbox, &data, &len);
|
||
|
|
assert(ok && "expected an envelope in the outbox");
|
||
|
|
ops->push_inbound(receiver, data, len, out);
|
||
|
|
assert(out->error_code == CLIENT_FFI_OK && "push_inbound failed");
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------
|
||
|
|
* Entry point called from the Rust test harness.
|
||
|
|
* Returns 0 on success, non-zero on failure.
|
||
|
|
* ------------------------------------------------------------------ */
|
||
|
|
|
||
|
|
int run_alice_bob_test(const ClientOps *ops)
|
||
|
|
{
|
||
|
|
Queue alice_outbox, bob_outbox;
|
||
|
|
queue_init(&alice_outbox);
|
||
|
|
queue_init(&bob_outbox);
|
||
|
|
|
||
|
|
/* ---- Create clients ------------------------------------------------- */
|
||
|
|
/* Each client's delivery callback pushes to its own outbox. */
|
||
|
|
ClientHandle_t *alice = ops->create(
|
||
|
|
(const uint8_t *)"alice", 5, deliver_cb, &alice_outbox);
|
||
|
|
ClientHandle_t *bob = ops->create(
|
||
|
|
(const uint8_t *)"bob", 3, deliver_cb, &bob_outbox);
|
||
|
|
|
||
|
|
assert(alice && "create returned NULL for alice");
|
||
|
|
assert(bob && "create returned NULL for bob");
|
||
|
|
|
||
|
|
/* ---- Bob generates an intro bundle ---------------------------------- */
|
||
|
|
CreateIntroResult_t bob_intro;
|
||
|
|
memset(&bob_intro, 0, sizeof(bob_intro));
|
||
|
|
ops->create_intro_bundle(bob, &bob_intro);
|
||
|
|
assert(bob_intro.error_code == CLIENT_FFI_OK);
|
||
|
|
|
||
|
|
/* ---- Alice initiates a conversation with Bob ------------------------ */
|
||
|
|
CreateConvoResult_t alice_convo;
|
||
|
|
memset(&alice_convo, 0, sizeof(alice_convo));
|
||
|
|
ops->create_conversation(
|
||
|
|
alice,
|
||
|
|
bob_intro.intro_bytes_ptr, bob_intro.intro_bytes_len,
|
||
|
|
(const uint8_t *)"hello bob", 9,
|
||
|
|
&alice_convo);
|
||
|
|
assert(alice_convo.error_code == CLIENT_FFI_OK);
|
||
|
|
ops->destroy_intro_result(&bob_intro);
|
||
|
|
|
||
|
|
/* ---- Route alice -> bob --------------------------------------------- */
|
||
|
|
PushInboundResult_t recv;
|
||
|
|
memset(&recv, 0, sizeof(recv));
|
||
|
|
route(ops, &alice_outbox, bob, &recv);
|
||
|
|
|
||
|
|
assert(recv.has_content && "expected content from alice");
|
||
|
|
assert(recv.is_new_convo && "expected new-conversation flag");
|
||
|
|
assert(recv.content_len == 9);
|
||
|
|
assert(memcmp(recv.content_ptr, "hello bob", 9) == 0);
|
||
|
|
|
||
|
|
/* Save Bob's convo_id for replies */
|
||
|
|
uint8_t bob_cid[256];
|
||
|
|
size_t bob_cid_len = recv.convo_id_len;
|
||
|
|
assert(bob_cid_len < sizeof(bob_cid));
|
||
|
|
memcpy(bob_cid, recv.convo_id_ptr, bob_cid_len);
|
||
|
|
ops->destroy_inbound_result(&recv);
|
||
|
|
|
||
|
|
/* ---- Bob replies ---------------------------------------------------- */
|
||
|
|
int32_t rc = ops->send_message(
|
||
|
|
bob, bob_cid, bob_cid_len,
|
||
|
|
(const uint8_t *)"hi alice", 8);
|
||
|
|
assert(rc == CLIENT_FFI_OK);
|
||
|
|
|
||
|
|
/* Route bob -> alice */
|
||
|
|
memset(&recv, 0, sizeof(recv));
|
||
|
|
route(ops, &bob_outbox, alice, &recv);
|
||
|
|
assert(recv.has_content && "expected content from bob");
|
||
|
|
assert(!recv.is_new_convo && "unexpected new-convo flag");
|
||
|
|
assert(recv.content_len == 8);
|
||
|
|
assert(memcmp(recv.content_ptr, "hi alice", 8) == 0);
|
||
|
|
ops->destroy_inbound_result(&recv);
|
||
|
|
|
||
|
|
/* ---- Multiple back-and-forth rounds --------------------------------- */
|
||
|
|
for (int i = 0; i < 3; i++) {
|
||
|
|
char msg[32];
|
||
|
|
int mlen = snprintf(msg, sizeof(msg), "msg %d", i);
|
||
|
|
|
||
|
|
rc = ops->send_message(
|
||
|
|
alice,
|
||
|
|
alice_convo.convo_id_ptr, alice_convo.convo_id_len,
|
||
|
|
(const uint8_t *)msg, (size_t)mlen);
|
||
|
|
assert(rc == CLIENT_FFI_OK);
|
||
|
|
|
||
|
|
memset(&recv, 0, sizeof(recv));
|
||
|
|
route(ops, &alice_outbox, bob, &recv);
|
||
|
|
assert(recv.has_content);
|
||
|
|
assert((int)recv.content_len == mlen);
|
||
|
|
assert(memcmp(recv.content_ptr, msg, (size_t)mlen) == 0);
|
||
|
|
|
||
|
|
char reply[32];
|
||
|
|
int rlen = snprintf(reply, sizeof(reply), "reply %d", i);
|
||
|
|
|
||
|
|
rc = ops->send_message(
|
||
|
|
bob, bob_cid, bob_cid_len,
|
||
|
|
(const uint8_t *)reply, (size_t)rlen);
|
||
|
|
assert(rc == CLIENT_FFI_OK);
|
||
|
|
ops->destroy_inbound_result(&recv);
|
||
|
|
|
||
|
|
memset(&recv, 0, sizeof(recv));
|
||
|
|
route(ops, &bob_outbox, alice, &recv);
|
||
|
|
assert(recv.has_content);
|
||
|
|
assert((int)recv.content_len == rlen);
|
||
|
|
assert(memcmp(recv.content_ptr, reply, (size_t)rlen) == 0);
|
||
|
|
ops->destroy_inbound_result(&recv);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- Cleanup -------------------------------------------------------- */
|
||
|
|
ops->destroy_convo_result(&alice_convo);
|
||
|
|
ops->destroy(alice);
|
||
|
|
ops->destroy(bob);
|
||
|
|
return 0;
|
||
|
|
}
|