/* * c-client: Alice-Bob message exchange written entirely in C. * * Demonstrates that the client-ffi C API is straightforward to consume * directly — no Rust glue required. Build with the provided Makefile. */ #include "client_ffi.h" #include #include #include #include #include #include /* ------------------------------------------------------------------ * Convenience macros for building slice_ref_uint8_t values. * SLICE(p, n) — arbitrary pointer + length. * STR(s) — string literal (length computed at compile time). * ------------------------------------------------------------------ */ #define SLICE(p, n) ((slice_ref_uint8_t){ .ptr = (const uint8_t *)(p), .len = (n) }) #define STR(s) SLICE(s, sizeof(s) - 1) /* ------------------------------------------------------------------ * In-memory delivery bus (shared by all clients, like InProcessDelivery) * ------------------------------------------------------------------ */ #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 Queue bus; 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: all clients share one bus. * ------------------------------------------------------------------ */ static int32_t deliver_cb( const uint8_t *addr_ptr, size_t addr_len, const uint8_t *data_ptr, size_t data_len) { (void)addr_ptr; (void)addr_len; queue_push(&bus, data_ptr, data_len); return 0; } /* ------------------------------------------------------------------ * Helper: pop one envelope from the bus and push it into receiver. * Returns a heap-allocated result; caller frees with * push_inbound_result_free(). * ------------------------------------------------------------------ */ static PushInboundResult_t *route(ClientHandle_t *receiver) { const uint8_t *data; size_t len; int ok = queue_pop(&bus, &data, &len); assert(ok && "expected an envelope in the bus"); PushInboundResult_t *r = client_receive(receiver, SLICE(data, len)); assert(push_inbound_result_error_code(r) == 0 && "push_inbound failed"); return r; } /* ------------------------------------------------------------------ * Main * ------------------------------------------------------------------ */ int main(void) { queue_init(&bus); /* Create clients — both share the same delivery bus */ ClientHandle_t *alice = client_create(STR("alice"), deliver_cb); ClientHandle_t *bob = client_create(STR("bob"), deliver_cb); assert(alice && "client_create returned NULL for alice"); assert(bob && "client_create returned NULL for bob"); /* Bob generates an intro bundle */ CreateIntroResult_t *bob_intro = client_create_intro_bundle(bob); assert(create_intro_result_error_code(bob_intro) == 0); slice_ref_uint8_t intro_bytes = create_intro_result_bytes(bob_intro); /* Alice initiates a conversation with Bob */ CreateConvoResult_t *alice_convo = client_create_conversation( alice, intro_bytes, STR("hello bob")); assert(create_convo_result_error_code(alice_convo) == 0); create_intro_result_free(bob_intro); /* Route alice -> bob */ PushInboundResult_t *recv = route(bob); assert(push_inbound_result_has_content(recv) && "expected content from alice"); assert(push_inbound_result_is_new_convo(recv) && "expected new-conversation flag"); slice_ref_uint8_t content = push_inbound_result_content(recv); assert(content.len == 9); assert(memcmp(content.ptr, "hello bob", 9) == 0); printf("Bob received: \"%.*s\"\n", (int)content.len, content.ptr); /* Copy Bob's convo_id before freeing recv */ slice_ref_uint8_t cid_ref = push_inbound_result_convo_id(recv); uint8_t bob_cid[256]; size_t bob_cid_len = cid_ref.len; if (bob_cid_len >= sizeof(bob_cid)) { fprintf(stderr, "conversation id too long (%zu bytes)\n", bob_cid_len); return 1; } memcpy(bob_cid, cid_ref.ptr, bob_cid_len); push_inbound_result_free(recv); /* Bob replies */ ErrorCode_t rc = client_send_message( bob, SLICE(bob_cid, bob_cid_len), STR("hi alice")); assert(rc == ERROR_CODE_NONE); recv = route(alice); assert(push_inbound_result_has_content(recv) && "expected content from bob"); assert(!push_inbound_result_is_new_convo(recv) && "unexpected new-convo flag"); content = push_inbound_result_content(recv); assert(content.len == 8); assert(memcmp(content.ptr, "hi alice", 8) == 0); printf("Alice received: \"%.*s\"\n", (int)content.len, content.ptr); push_inbound_result_free(recv); /* Multiple back-and-forth rounds */ slice_ref_uint8_t alice_cid = create_convo_result_id(alice_convo); for (int i = 0; i < 3; i++) { char msg[32]; int mlen = snprintf(msg, sizeof(msg), "msg %d", i); rc = client_send_message(alice, alice_cid, SLICE(msg, (size_t)mlen)); assert(rc == ERROR_CODE_NONE); recv = route(bob); assert(push_inbound_result_has_content(recv)); content = push_inbound_result_content(recv); assert((int)content.len == mlen); assert(memcmp(content.ptr, msg, (size_t)mlen) == 0); push_inbound_result_free(recv); char reply[32]; int rlen = snprintf(reply, sizeof(reply), "reply %d", i); rc = client_send_message( bob, SLICE(bob_cid, bob_cid_len), SLICE(reply, (size_t)rlen)); assert(rc == ERROR_CODE_NONE); recv = route(alice); assert(push_inbound_result_has_content(recv)); content = push_inbound_result_content(recv); assert((int)content.len == rlen); assert(memcmp(content.ptr, reply, (size_t)rlen) == 0); push_inbound_result_free(recv); } /* Cleanup */ create_convo_result_free(alice_convo); client_destroy(alice); client_destroy(bob); printf("Message exchange complete.\n"); return 0; }