closes: #77
The C consumer story lives downstream now: logos-chat-module wraps the
client crate and exposes its own C API. The in-tree client-ffi crate has
no consumers left, and the nim bindings still target the removed
Context-based C API.
- delete crates/client-ffi (including the message-exchange C example)
and nim-bindings
- drop core/conversations' unused safer-ffi dependency plus the leftover
C artifact crate-types: staticlib on core/conversations, cdylib on
double-ratchets (neither crate has extern "C" exports)
- flake.nix: drop the default package (it built libclient_ffi.a plus its
header); keep the logos-delivery package and the dev shell
- ci.yml: drop the C FFI smoketest steps (valgrind included), the rustup
install the smoketest no longer needs, and the nix-build job that
built the removed default package
- ADR 0001: point the FFI-compatibility driver at the downstream C API
boundary instead of crates/client-ffi
* feat: account to device store
* feat: accout traits and codec
* feat: integrate accounts abstraction
* chore: clean docs and naming
* remove account public key from payload
* chore: fix clippy
* feat: lamport check before update account store
* chore: rebase to core
* chore: register account in new core
* chore: rebase changes and use account pub for index account store
* chore: move chat store outside of libchat
* chore: use account pub for registry
The client, not the app, now drives the transport; events are delivered
asynchronously, per ADR 0001.
- ChatClient owns Arc<Mutex<Core>> + a worker thread.
- The worker select!s over the inbound and shutdown channels; Drop joins it.
Outbound runs on the caller's thread.
- A single Transport (DeliveryService + inbound()) owns both directions of the
boundary, so the client takes one transport rather than a (delivery, inbound)
pair. InProcessDelivery::new, CDelivery, and chat-cli's transports implement it.
- FFI replaces client_receive with client_push_inbound + client_poll_events.
- chat-cli drains Receiver<Event>; inbound and event channels are both crossbeam.
- Corrects ADR 0001's inbound sequence to push — the worker parks on select!,
it never polls.
Make the conversations core Send so the threaded client can own it behind an
Arc<Mutex<Core>>: a background worker polls the transport and handles inbound
payloads while the application thread issues outbound calls (send, create
conversation). Sharing the core across those two threads means moving it into
the spawned worker, which is only legal if it is Send. Access stays serialized
by the client's Mutex (one thread at a time), so the core needs Send but not
Sync and carries no lock of its own. See
docs/adr/0001-client-event-system.md for the background-poller design.
The Rc<RefCell> service-sharing is what made the core !Send. Context is de-Rc'd
and renamed to Core, owning its services outright and driving the inbox and
conversation primitives with plain &mut self.
- Services (identity, delivery, store, registry, MLS context, causal history)
are bundled into a ServiceContext<S> behind an ExternalServices trait, with
S = (DS, RS, CS). Constructors live on the (DS, RS, CS) form because S cannot
be inferred backwards through S::DS.
- Inbox, InboxV2, PrivateV1Convo, and GroupV1Convo become non-generic and
receive the ServiceContext bundle as a &mut/& parameter; no Rc or
RefCell-as-shared-state remains, so Core is Send whenever its injected
services are.
- Dispatch branches on ConversationKind in one place: Core rebuilds the target
as a Convo<S>/GroupConvo<S> trait object bound to the service bundle, so
conversations never escape the orchestrator.
- CausalHistoryStore drops its Rc, keeping a plain RefCell.
* feat: http server based key package registry
* chore: instructions on running the registration service
* chore: remove duplicate post param
* chore: revert out sourced account id for multi devices support
* feat: signature on account id and key packages
* chore: include http registry in contact registry module
* refactor: use device id for retrieve key package
* chore: use string for device id
* feat: server verification on the register
* chore: doc the smoke test
* chore: fix data folder non exist
* chore: use payload for register and retrieve
* chore: fix clippy
Update InboxV2 to use IdentProvider
Create Full featured Provider
Introduce MlsIdentityProvider
Flatten MLSContext
Cleanup warnings until future integration PR
remove duplicate
Update account_id comments
* feat: prefix sender id
* chore: add message struct for sender info
* chore: refactor struct name for frontier
* chore: reuse duplicate test
* chore: fix clippy
* feat: use sender_id in wire
* chore: remove result
* chore: fix nix build
* chore: bump chat_proto version
* chore(flake): accept extra system attr; add perl for openssl-sys build
forAllSystems calls the lambda with {system, pkgs}; strict
destructuring requires `..` to ignore the system attribute.
`pkgs.perl` is needed because openssl-sys is pulled vendored via
libsqlite3-sys / rusqlite / chat-sqlite, and its `perl Configure`
step needs FindBin.pm, which Fedora's system perl doesn't ship.
* feat: introduce client event system
- Core processing yields a `PayloadOutcome` enum — `Empty`, `Convo`, or
`Inbox`. `ConvoOutcome` carries a conversation id and an optional
decrypted `Content`; `InboxOutcome` adds a `NewConversation`
(id + `ConversationClass`) for a peer-initiated conversation.
- Client translates `PayloadOutcome` into app-facing `Vec<Event>`
(`ConversationStarted`, `MessageReceived`) at the boundary, so the
application loop sees discrete events rather than core types.
- MLS group welcomes produce a `ConversationStarted` event with no
initial content, fixing the silent-group-join case where the inbox
layer dropped the observation.
- C FFI exposes an `EventList` opaque type with indexed accessors and
an `Invalid` sentinel for out-of-bounds / non-applicable reads.
- Symmetric `Inbox` / `InboxV2` handlers: both return
`Result<InboxOutcome, _>` and own the persistence + ephemeral-key
cleanup for the conversations they create.
- Updated and simplified `docs/adr/0001-client-event-system.md`.
* chore(flake): bump nixpkgs to nixos-unstable-small
Temporary. The two crates.io UA fixes (NixOS/nixpkgs#512735 for
fetchCargoVendor's python-requests UA, NixOS/nixpkgs#524985 for
importCargoLock's curl UA) haven't propagated to nixos-unstable yet.
Switch to nixos-unstable-small and force logos-delivery to follow so
the smoketest gets the same fix. Revert once nixos-unstable catches up.
Refs:
- https://github.com/rust-lang/crates.io/issues/13482
- https://github.com/rust-lang/crates.io/issues/13783
- https://crates.io/data-access
Both file and logos-delivery transports are now compiled into a single
binary and selected at runtime (default: logos-delivery), replacing the
env-var-driven build-time cfg.
Replace the direct use of `conversations::Context` with `client::ChatClient`,
which is the intended public API for library consumers.
Remove `MessageEnvelope` and the username-keyed session model. The envelope
was never part of the wire protocol — sender identity was only tracked in the
CLI's local state. Chats are now keyed by conversation ID; add `/nickname` as
the user-facing replacement for named sessions.
Add a logos-delivery (Waku) transport alongside the existing file transport.
The active transport is selected at compile time: set `LOGOS_DELIVERY_LIB_DIR`
to link liblogosdelivery, otherwise the file transport is used.
Add logos-delivery as a Nix flake input and expose `.#logos-delivery` so the
library can be built with `nix build` and referenced by `LOGOS_DELIVERY_LIB_DIR`.
CI: rename `c-ffi-smoketest` to `smoketest`; add logos-delivery build step
and a `--smoketest` invocation of chat-cli to verify startup.
* chore: remove ffi from double ratchet
* chore: format
* feat: chat cli demo app via file transport
* chore: fix the compile issues
* chore: fix long intro copy to clipboard
* chore: move chat cli to bin folder
* chore: use tmp data folder
* chore: update doc
* chore: use encrypted db with default db pass
* chore: fmt and clippy
* chore: fix clippy and refactor
* chore: utils for helper funcs
* chore: rename sessions to chats
* feat: move private store out of context
* feat: move convo store to private v1
* feat: clean context
* chore: params postion
* chore: use git exclue for justfile
Implement a `client` crate that wraps the `libchat` context behind a
simple `ChatClient<D>` API. The delivery strategy is pluggable via a
`DeliveryService` trait, with two implementations provided:
- `InProcessDelivery` — shared `MessageBus` for single-process tests
- `CDelivery` — C function-pointer callback for the FFI layer
Add a `client-ffi` crate that exposes the client as a C API via
`safer-ffi`. A `generate-headers` binary produces the companion C
header.
Include two runnable examples:
- `examples/in-process` — Alice/Bob exchange using in-process delivery
- `examples/c-ffi` — same exchange written entirely in C; smoketested
under valgrind (to catch memory leaks) in CI
iterates: #71
Add nim-bindings/tests/test_all_endpoints.nim which imports bindings
directly and calls every FFI proc, forcing the linker to include all
symbols. This catches link-time and runtime issues that the pingpong
example missed because unused symbols were optimised out.
Running the new test revealed an ABI mismatch in installation_name:
the Rust function used an explicit out-parameter but ReprCString has
only flat fields, so Nim emits it as a C return value.
CI now runs nimble test next to nimble pingpong.
* chore(nim-bindings): replace dynlib dlopen with plain importc
The dynlib pragma hard-coded a library path and resolved it via dlopen() at
runtime, preventing static linking and forcing a specific load-time path.
Using bare {.importc.} lets consumers choose: link liblibchat dynamically
at link time (--passL:-llibchat) or link it statically into their binary.
* Rust -> Nim ABI (#62)
* Use correct build hook
* force sret like return from rust code for nim compatibility
* Fix target mismatch
* Update usages
* ci: add nim-bindings-test
* fix(nim-bindings): fix ABI mismatch in destroy_* FFI functions and add defer-based cleanup
Nim's C backend silently transforms large struct parameters (>16 bytes) into
pointer parameters when calling importc functions. The destroy_* functions were
declared taking T by value in Rust, but Nim always passed &T — causing Rust to
read garbage from the stack on x86-64 (SIGILL on CI) while accidentally working
on ARM64 macOS due to that ABI coincidentally also using pointers for large structs.
Fix by changing all destroy_* functions to take &mut T and using drop_in_place,
which is the correct idiom for dropping a value through a pointer.
On the Nim side, replace scattered manual destroy calls with defer, which
guarantees cleanup on all exit paths and prevents use-after-destroy bugs.
---------
Co-authored-by: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com>