/* * 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 #include #include #include #include /* ------------------------------------------------------------------ * 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; }