mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-06-29 04:29:57 +00:00
refactor: remove client-ffi and legacy nim bindings (#133)
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
This commit is contained in:
parent
78d6b6c47a
commit
9d9a691fe3
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
@ -50,7 +50,6 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup update stable && rustup default stable
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_version: 2.34.6
|
||||
@ -60,19 +59,6 @@ jobs:
|
||||
with:
|
||||
primary-key: nix-${{ runner.os }}-fixtest-${{ hashFiles('flake.nix', 'flake.lock') }}
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||
- name: Install valgrind
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get install -y valgrind
|
||||
- name: Build C FFI example
|
||||
run: make
|
||||
working-directory: crates/client-ffi/examples/message-exchange
|
||||
- name: Run C FFI smoketest
|
||||
run: ./c-client
|
||||
working-directory: crates/client-ffi/examples/message-exchange
|
||||
- name: Run C FFI smoketest under valgrind
|
||||
if: runner.os == 'Linux'
|
||||
run: make valgrind
|
||||
working-directory: crates/client-ffi/examples/message-exchange
|
||||
- name: Build logos-delivery
|
||||
# Build through a patched nixpkgs (kaichaosun/nixpkgs fix-gitfetch),
|
||||
# whose nix-prefetch-git disables git background auto-maintenance so the
|
||||
@ -92,24 +78,3 @@ jobs:
|
||||
run: nix develop -c bash -c 'LOGOS_DELIVERY_LIB_DIR=./result/lib cargo build --release -p chat-cli'
|
||||
- name: Run chat-cli smoketest
|
||||
run: nix develop -c ./target/release/chat-cli --name ci-test --smoketest
|
||||
|
||||
nix-build:
|
||||
name: Nix Build
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_version: 2.34.6
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
- uses: nix-community/cache-nix-action@v6
|
||||
with:
|
||||
primary-key: nix-${{ runner.os }}-fixtest-${{ hashFiles('flake.nix', 'flake.lock') }}
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||
# Same patched-nixpkgs override; the default package pulls in
|
||||
# logos-delivery-lib, so it exercises the same nim-zlib fetch.
|
||||
- run: nix build --override-input nixpkgs github:kaichaosun/nixpkgs/fix-gitfetch --print-build-logs
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@ -22,11 +22,6 @@ target
|
||||
|
||||
*/.DS_Store
|
||||
|
||||
# Compiled binary
|
||||
**/ffi_nim_example
|
||||
/nim-bindings/examples/pingpong
|
||||
/nim-bindings/libchat
|
||||
|
||||
# Temporary data folder
|
||||
tmp
|
||||
|
||||
@ -34,9 +29,3 @@ tmp
|
||||
result
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Generated C headers (produced by `make` in examples/c-ffi; do not commit)
|
||||
crates/client-ffi/client_ffi.h
|
||||
|
||||
# Compiled C FFI example binary
|
||||
crates/client-ffi/examples/message-exchange/c-client
|
||||
|
||||
268
Cargo.lock
generated
268
Cargo.lock
generated
@ -363,7 +363,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -372,16 +372,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "client-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"libchat",
|
||||
"logos-chat",
|
||||
"safer-ffi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.1"
|
||||
@ -624,7 +614,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -647,7 +637,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -658,7 +648,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -680,7 +670,7 @@ checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -702,7 +692,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -735,7 +725,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -853,41 +843,6 @@ version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||
|
||||
[[package]]
|
||||
name = "ext-trait"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5"
|
||||
dependencies = [
|
||||
"ext-trait-proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ext-trait-proc_macros"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extension-traits"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537"
|
||||
dependencies = [
|
||||
"ext-trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extern-c"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
@ -923,7 +878,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1012,7 +967,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1206,7 +1161,7 @@ dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1588,7 +1543,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1603,15 +1558,6 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
@ -1706,7 +1652,6 @@ dependencies = [
|
||||
"openmls_traits 0.5.0",
|
||||
"prost",
|
||||
"rand_core 0.6.4",
|
||||
"safer-ffi",
|
||||
"shared-traits",
|
||||
"storage",
|
||||
"tempfile",
|
||||
@ -1874,7 +1819,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2067,22 +2012,6 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "macro_rules_attribute"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862"
|
||||
dependencies = [
|
||||
"macro_rules_attribute-proc_macro",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macro_rules_attribute-proc_macro"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
@ -2385,7 +2314,7 @@ dependencies = [
|
||||
"quote",
|
||||
"rstest",
|
||||
"rstest_reuse",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2578,16 +2507,6 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
@ -2595,7 +2514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2635,7 +2554,7 @@ dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2667,7 +2586,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3018,7 +2937,7 @@ dependencies = [
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@ -3030,7 +2949,7 @@ checksum = "b3a8fb4672e840a587a66fc577a5491375df51ddb88f2a2c2a792598c326fe14"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"rand 0.8.6",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3135,38 +3054,6 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "safer-ffi"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd"
|
||||
dependencies = [
|
||||
"extern-c",
|
||||
"inventory",
|
||||
"libc",
|
||||
"macro_rules_attribute",
|
||||
"paste",
|
||||
"safer_ffi-proc_macros",
|
||||
"scopeguard",
|
||||
"stabby",
|
||||
"uninit",
|
||||
"unwind_safe",
|
||||
"with_builtin_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "safer_ffi-proc_macros"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156"
|
||||
dependencies = [
|
||||
"macro_rules_attribute",
|
||||
"prettyplease 0.1.25",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@ -3230,7 +3117,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3269,12 +3156,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2-const-stable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9"
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@ -3376,41 +3257,6 @@ dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stabby"
|
||||
version = "36.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b7e94eaf470c2e76b5f15fb2fb49714471a36cc512df5ee231e62e82ec79f8"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
"stabby-abi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stabby-abi"
|
||||
version = "36.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dc7a63b8276b54e51bfffe3d85da56e7906b2dcfcb29018a8ab666c06734c1a"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"rustversion",
|
||||
"sha2-const-stable",
|
||||
"stabby-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stabby-macros"
|
||||
version = "36.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eecb7ec5611ec93ec79d120fbe55f31bea234dc1bed1001d4a071bb688651615"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rand 0.8.6",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
@ -3456,7 +3302,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3465,17 +3311,6 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@ -3504,7 +3339,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3537,7 +3372,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3607,7 +3442,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3728,7 +3563,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3823,15 +3658,6 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "uninit"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6"
|
||||
dependencies = [
|
||||
"extension-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
@ -3848,12 +3674,6 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "unwind_safe"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@ -3982,7 +3802,7 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -4301,8 +4121,8 @@ dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease 0.2.37",
|
||||
"syn 2.0.117",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
@ -4315,10 +4135,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease 0.2.37",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
@ -4360,26 +4180,6 @@ dependencies = [
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "with_builtin_macros"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da"
|
||||
dependencies = [
|
||||
"with_builtin_macros-proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "with_builtin_macros-proc_macros"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
@ -4450,7 +4250,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -4471,7 +4271,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4491,7 +4291,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -4512,7 +4312,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4545,7 +4345,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -12,7 +12,6 @@ members = [
|
||||
"core/shared-traits",
|
||||
"core/sqlite",
|
||||
"core/storage",
|
||||
"crates/client-ffi",
|
||||
"crates/client",
|
||||
"extensions/components",
|
||||
]
|
||||
@ -26,7 +25,6 @@ default-members = [
|
||||
"core/shared-traits",
|
||||
"core/sqlite",
|
||||
"core/storage",
|
||||
"crates/client-ffi",
|
||||
"crates/client",
|
||||
]
|
||||
|
||||
@ -45,7 +43,7 @@ storage = { path = "core/storage" }
|
||||
blake2 = "0.10"
|
||||
crossbeam-channel = "0.5"
|
||||
|
||||
# Panicking across FFI boundaries is UB; abort is the correct strategy for a
|
||||
# C FFI library.
|
||||
# Panicking across FFI boundaries is UB; chat-cli registers Rust callbacks
|
||||
# that liblogosdelivery invokes, so abort instead of unwinding.
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib","staticlib"]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace dependencies (sorted)
|
||||
@ -25,7 +25,6 @@ openmls_memory_storage = "0.5.0"
|
||||
openmls_traits = "0.5.0"
|
||||
prost = "0.14.1"
|
||||
rand_core = { version = "0.6" }
|
||||
safer-ffi = "0.1.13"
|
||||
thiserror = "2.0.17"
|
||||
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "reusable_secrets", "getrandom"] }
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ use std::fmt::Debug;
|
||||
|
||||
use crate::proto::{self, Message};
|
||||
|
||||
// FFI Type definitions
|
||||
// Public type definitions
|
||||
|
||||
// This struct represents Outbound data.
|
||||
// It wraps an encoded payload with a delivery address, so it can be handled by the delivery service.
|
||||
|
||||
@ -4,7 +4,7 @@ version = "0.0.1"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace dependencies (sorted)
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "client-ffi"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[[bin]]
|
||||
name = "generate-headers"
|
||||
required-features = ["headers"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace dependencies (sorted)
|
||||
crossbeam-channel = { workspace = true }
|
||||
libchat = { workspace = true }
|
||||
logos-chat = { workspace = true }
|
||||
|
||||
# External dependencies (sorted)
|
||||
safer-ffi = "0.1.13"
|
||||
|
||||
[features]
|
||||
headers = ["safer-ffi/headers"]
|
||||
@ -1,39 +0,0 @@
|
||||
REPO_ROOT := $(shell cd ../../../.. && pwd)
|
||||
CARGO_PROFILE ?= debug
|
||||
LIB_DIR := $(REPO_ROOT)/target/$(CARGO_PROFILE)
|
||||
INCLUDE_DIR := $(REPO_ROOT)/crates/client-ffi
|
||||
HEADER := $(INCLUDE_DIR)/client_ffi.h
|
||||
|
||||
CC ?= cc
|
||||
CFLAGS := -Wall -Wextra -std=c11 -I$(INCLUDE_DIR)
|
||||
LIBS := -L$(LIB_DIR) -lclient_ffi -lpthread -ldl -lm
|
||||
|
||||
.PHONY: all run valgrind clean generate-headers _cargo
|
||||
|
||||
all: c-client
|
||||
|
||||
generate-headers:
|
||||
cargo run --manifest-path $(REPO_ROOT)/Cargo.toml \
|
||||
-p client-ffi --bin generate-headers --features headers \
|
||||
-- $(HEADER)
|
||||
|
||||
_cargo:
|
||||
cargo build --manifest-path $(REPO_ROOT)/Cargo.toml -p client-ffi \
|
||||
$(if $(filter release,$(CARGO_PROFILE)),--release,)
|
||||
|
||||
c-client: src/main.c generate-headers _cargo
|
||||
$(CC) $(CFLAGS) src/main.c $(LIBS) -o c-client
|
||||
|
||||
run: c-client
|
||||
./c-client
|
||||
|
||||
valgrind: c-client
|
||||
valgrind \
|
||||
--error-exitcode=1 \
|
||||
--leak-check=full \
|
||||
--errors-for-leak-kinds=definite,indirect \
|
||||
--track-origins=yes \
|
||||
./c-client
|
||||
|
||||
clean:
|
||||
rm -f c-client $(HEADER)
|
||||
@ -1,21 +0,0 @@
|
||||
# message-exchange
|
||||
|
||||
An example C application built on top of [`crates/client-ffi`](../../).
|
||||
|
||||
It demonstrates that the C ABI exposed by `crates/client-ffi` is straightforward to
|
||||
consume from plain C — or from any language that can call into a C ABI. No Rust code,
|
||||
no Cargo project: just a C source file linked against the pre-built static library.
|
||||
|
||||
## Building and running
|
||||
|
||||
```sh
|
||||
make # builds client-ffi with Cargo, then compiles src/main.c
|
||||
make run # build + execute
|
||||
make clean # remove the compiled binary
|
||||
```
|
||||
|
||||
For a release build:
|
||||
|
||||
```sh
|
||||
make CARGO_PROFILE=release
|
||||
```
|
||||
@ -1,228 +0,0 @@
|
||||
/*
|
||||
* message-exchange: Saro-Raya 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 <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 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, hand it to receiver's worker,
|
||||
* then wait for the worker to produce events. Returns a heap-allocated
|
||||
* event list; caller frees with event_list_free().
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static EventList_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");
|
||||
client_push_inbound(receiver, SLICE(data, len));
|
||||
|
||||
/* Block until the worker decrypts the payload and produces events. */
|
||||
EventList_t *evs = client_wait_events(receiver, 5000);
|
||||
assert(event_list_len(evs) > 0 && "timed out waiting for events");
|
||||
return evs;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Helper: locate the first MessageReceived event in a list and copy
|
||||
* its content into the caller-supplied buffer. Returns -1 if not found.
|
||||
* ------------------------------------------------------------------ */
|
||||
static int find_message(EventList_t *evs, char *out, size_t out_cap, size_t *out_len)
|
||||
{
|
||||
size_t n = event_list_len(evs);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (event_list_kind_at(evs, i) == EVENT_KIND_MESSAGE_RECEIVED) {
|
||||
slice_ref_uint8_t s = event_list_content_at(evs, i);
|
||||
assert(s.len <= out_cap && "content buffer too small");
|
||||
memcpy(out, s.ptr, s.len);
|
||||
*out_len = s.len;
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Main
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
queue_init(&bus);
|
||||
|
||||
/* Create clients — both share the same delivery bus */
|
||||
ClientHandle_t *saro = client_create(STR("saro"), deliver_cb);
|
||||
ClientHandle_t *raya = client_create(STR("raya"), deliver_cb);
|
||||
|
||||
assert(saro && "client_create returned NULL for saro");
|
||||
assert(raya && "client_create returned NULL for raya");
|
||||
|
||||
/* Raya generates an intro bundle */
|
||||
CreateIntroResult_t *raya_intro = client_create_intro_bundle(raya);
|
||||
assert(create_intro_result_error_code(raya_intro) == 0);
|
||||
slice_ref_uint8_t intro_bytes = create_intro_result_bytes(raya_intro);
|
||||
|
||||
/* Saro initiates a conversation with Raya */
|
||||
CreateConvoResult_t *saro_convo = client_create_conversation(
|
||||
saro, intro_bytes, STR("hello raya"));
|
||||
assert(create_convo_result_error_code(saro_convo) == 0);
|
||||
create_intro_result_free(raya_intro);
|
||||
|
||||
/* Route saro -> raya: expect [ConversationStarted, MessageReceived] */
|
||||
EventList_t *evs = route(raya);
|
||||
assert(event_list_len(evs) == 2 && "expected 2 events for invite");
|
||||
assert(event_list_kind_at(evs, 0) == EVENT_KIND_CONVERSATION_STARTED
|
||||
&& "first event should be ConversationStarted");
|
||||
assert(event_list_conversation_class_at(evs, 0) == FFI_CONVERSATION_CLASS_PRIVATE
|
||||
&& "expected Private convo class");
|
||||
|
||||
char msg[64];
|
||||
size_t msg_len;
|
||||
int idx = find_message(evs, msg, sizeof(msg), &msg_len);
|
||||
assert(idx >= 0 && "expected MessageReceived from saro");
|
||||
assert(msg_len == 10 && memcmp(msg, "hello raya", 10) == 0);
|
||||
printf("Raya received: \"%.*s\"\n", (int)msg_len, msg);
|
||||
|
||||
/* Copy Raya's convo_id from the ConversationStarted event */
|
||||
slice_ref_uint8_t cid_ref = event_list_convo_id_at(evs, 0);
|
||||
uint8_t raya_cid[256];
|
||||
size_t raya_cid_len = cid_ref.len;
|
||||
if (raya_cid_len >= sizeof(raya_cid)) {
|
||||
fprintf(stderr, "conversation id too long (%zu bytes)\n", raya_cid_len);
|
||||
return 1;
|
||||
}
|
||||
memcpy(raya_cid, cid_ref.ptr, raya_cid_len);
|
||||
event_list_free(evs);
|
||||
|
||||
/* Raya replies */
|
||||
ErrorCode_t rc = client_send_message(
|
||||
raya, SLICE(raya_cid, raya_cid_len), STR("hi saro"));
|
||||
assert(rc == ERROR_CODE_NONE);
|
||||
|
||||
evs = route(saro);
|
||||
assert(event_list_len(evs) == 1 && "expected MessageReceived only");
|
||||
assert(event_list_kind_at(evs, 0) == EVENT_KIND_MESSAGE_RECEIVED);
|
||||
idx = find_message(evs, msg, sizeof(msg), &msg_len);
|
||||
assert(idx >= 0);
|
||||
assert(msg_len == 7 && memcmp(msg, "hi saro", 7) == 0);
|
||||
printf("Saro received: \"%.*s\"\n", (int)msg_len, msg);
|
||||
event_list_free(evs);
|
||||
|
||||
/* Multiple back-and-forth rounds */
|
||||
slice_ref_uint8_t saro_cid = create_convo_result_id(saro_convo);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char text[32];
|
||||
int tlen = snprintf(text, sizeof(text), "msg %d", i);
|
||||
|
||||
rc = client_send_message(saro, saro_cid, SLICE(text, (size_t)tlen));
|
||||
assert(rc == ERROR_CODE_NONE);
|
||||
|
||||
evs = route(raya);
|
||||
idx = find_message(evs, msg, sizeof(msg), &msg_len);
|
||||
assert(idx >= 0);
|
||||
assert((int)msg_len == tlen);
|
||||
assert(memcmp(msg, text, (size_t)tlen) == 0);
|
||||
event_list_free(evs);
|
||||
|
||||
char reply[32];
|
||||
int rlen = snprintf(reply, sizeof(reply), "reply %d", i);
|
||||
|
||||
rc = client_send_message(
|
||||
raya, SLICE(raya_cid, raya_cid_len), SLICE(reply, (size_t)rlen));
|
||||
assert(rc == ERROR_CODE_NONE);
|
||||
|
||||
evs = route(saro);
|
||||
idx = find_message(evs, msg, sizeof(msg), &msg_len);
|
||||
assert(idx >= 0);
|
||||
assert((int)msg_len == rlen);
|
||||
assert(memcmp(msg, reply, (size_t)rlen) == 0);
|
||||
event_list_free(evs);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
create_convo_result_free(saro_convo);
|
||||
client_destroy(saro);
|
||||
client_destroy(raya);
|
||||
|
||||
printf("Message exchange complete.\n");
|
||||
return 0;
|
||||
}
|
||||
@ -1,416 +0,0 @@
|
||||
use safer_ffi::prelude::*;
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
|
||||
use crate::delivery::{CDelivery, DeliverFn};
|
||||
use libchat::ChatError;
|
||||
use logos_chat::{ChatClient, ClientError, ConversationClass, Event};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Opaque client handle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive_ReprC]
|
||||
#[repr(opaque)]
|
||||
pub struct ClientHandle {
|
||||
client: ChatClient<CDelivery>,
|
||||
events: Receiver<Event>,
|
||||
inbound: Sender<Vec<u8>>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error codes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive_ReprC]
|
||||
#[repr(i32)]
|
||||
pub enum ErrorCode {
|
||||
None = 0,
|
||||
BadUtf8 = -1,
|
||||
/// Failure parsing or processing an introduction bundle.
|
||||
BadIntro = -2,
|
||||
DeliveryFail = -3,
|
||||
UnknownError = -4,
|
||||
/// Failure decoding, decrypting, or processing an inbound payload.
|
||||
BadPayload = -5,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event taxonomy (C-side view of Event)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive_ReprC]
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum EventKind {
|
||||
/// Sentinel returned by `event_list_kind_at` for out-of-bounds indices.
|
||||
/// Never the kind of a real event row.
|
||||
Invalid = -1,
|
||||
ConversationStarted = 0,
|
||||
MessageReceived = 1,
|
||||
}
|
||||
|
||||
#[derive_ReprC]
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FfiConversationClass {
|
||||
/// Sentinel for accessor calls that don't apply to the queried row
|
||||
/// (out-of-bounds, or a non-`ConversationStarted` event).
|
||||
Invalid = -1,
|
||||
Private = 0,
|
||||
Group = 1,
|
||||
}
|
||||
|
||||
impl From<ConversationClass> for FfiConversationClass {
|
||||
fn from(c: ConversationClass) -> Self {
|
||||
match c {
|
||||
ConversationClass::Private => FfiConversationClass::Private,
|
||||
ConversationClass::Group => FfiConversationClass::Group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Result types (opaque, heap-allocated via repr_c::Box)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive_ReprC]
|
||||
#[repr(opaque)]
|
||||
pub struct CreateIntroResult {
|
||||
error_code: i32,
|
||||
data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive_ReprC]
|
||||
#[repr(opaque)]
|
||||
pub struct CreateConvoResult {
|
||||
error_code: i32,
|
||||
convo_id: Option<String>,
|
||||
}
|
||||
|
||||
/// An ordered list of events with a status code. Inspect `error_code` (zero
|
||||
/// on success) before iterating with `event_list_len` and the indexed
|
||||
/// accessors.
|
||||
#[derive_ReprC]
|
||||
#[repr(opaque)]
|
||||
pub struct EventList {
|
||||
error_code: i32,
|
||||
events: Vec<EventRow>,
|
||||
}
|
||||
|
||||
enum EventRow {
|
||||
ConversationStarted {
|
||||
convo_id: String,
|
||||
class: FfiConversationClass,
|
||||
},
|
||||
MessageReceived {
|
||||
convo_id: String,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
impl EventRow {
|
||||
/// Translate an [`Event`] into the FFI row shape, or `None` for variants
|
||||
/// without an FFI representation.
|
||||
fn from_event(event: Event) -> Option<Self> {
|
||||
match event {
|
||||
Event::ConversationStarted {
|
||||
convo_id, class, ..
|
||||
} => Some(EventRow::ConversationStarted {
|
||||
convo_id: convo_id.to_string(),
|
||||
class: class.into(),
|
||||
}),
|
||||
Event::MessageReceived {
|
||||
convo_id, content, ..
|
||||
} => Some(EventRow::MessageReceived {
|
||||
convo_id: convo_id.to_string(),
|
||||
content,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn convo_id(&self) -> &str {
|
||||
match self {
|
||||
EventRow::ConversationStarted { convo_id, .. }
|
||||
| EventRow::MessageReceived { convo_id, .. } => convo_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn content(&self) -> &[u8] {
|
||||
match self {
|
||||
EventRow::MessageReceived { content, .. } => content,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Create an ephemeral in-memory client. Returns NULL if `callback` is None or
|
||||
/// `name` is not valid UTF-8. Free with `client_destroy`.
|
||||
#[ffi_export]
|
||||
fn client_create(
|
||||
name: c_slice::Ref<'_, u8>,
|
||||
callback: DeliverFn,
|
||||
) -> Option<repr_c::Box<ClientHandle>> {
|
||||
let name_str = match std::str::from_utf8(name.as_slice()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return None,
|
||||
};
|
||||
callback?;
|
||||
let (inbound_tx, inbound_rx) = crossbeam_channel::unbounded();
|
||||
let delivery = CDelivery::new(callback, inbound_rx);
|
||||
let (client, events) = ChatClient::new(name_str, delivery);
|
||||
Some(
|
||||
Box::new(ClientHandle {
|
||||
client,
|
||||
events,
|
||||
inbound: inbound_tx,
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Free a client handle. Must not be used after this call.
|
||||
#[ffi_export]
|
||||
fn client_destroy(handle: repr_c::Box<ClientHandle>) {
|
||||
drop(handle)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Identity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Return the installation name as an owned byte slice.
|
||||
/// Free with `client_installation_name_free`.
|
||||
#[ffi_export]
|
||||
fn client_installation_name(handle: &ClientHandle) -> c_slice::Box<u8> {
|
||||
handle
|
||||
.client
|
||||
.installation_name()
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
.into_boxed_slice()
|
||||
.into()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn client_installation_name_free(name: c_slice::Box<u8>) {
|
||||
drop(name)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Intro bundle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Produce a serialised introduction bundle for out-of-band sharing.
|
||||
/// Free with `create_intro_result_free`.
|
||||
#[ffi_export]
|
||||
fn client_create_intro_bundle(handle: &mut ClientHandle) -> repr_c::Box<CreateIntroResult> {
|
||||
let result = match handle.client.create_intro_bundle() {
|
||||
Ok(bytes) => CreateIntroResult {
|
||||
error_code: ErrorCode::None as i32,
|
||||
data: Some(bytes),
|
||||
},
|
||||
Err(_) => CreateIntroResult {
|
||||
error_code: ErrorCode::UnknownError as i32,
|
||||
data: None,
|
||||
},
|
||||
};
|
||||
Box::new(result).into()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn create_intro_result_error_code(r: &CreateIntroResult) -> i32 {
|
||||
r.error_code
|
||||
}
|
||||
|
||||
/// Returns an empty slice when error_code != 0.
|
||||
/// The slice is valid only while `r` is alive.
|
||||
#[ffi_export]
|
||||
fn create_intro_result_bytes(r: &CreateIntroResult) -> c_slice::Ref<'_, u8> {
|
||||
r.data.as_deref().unwrap_or(&[]).into()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn create_intro_result_free(r: repr_c::Box<CreateIntroResult>) {
|
||||
drop(r)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Create conversation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Parse an intro bundle and initiate a private conversation.
|
||||
/// Outbound envelopes are dispatched through the delivery callback.
|
||||
/// Free with `create_convo_result_free`.
|
||||
#[ffi_export]
|
||||
fn client_create_conversation(
|
||||
handle: &mut ClientHandle,
|
||||
bundle: c_slice::Ref<'_, u8>,
|
||||
content: c_slice::Ref<'_, u8>,
|
||||
) -> repr_c::Box<CreateConvoResult> {
|
||||
let result = match handle
|
||||
.client
|
||||
.create_conversation(bundle.as_slice(), content.as_slice())
|
||||
{
|
||||
Ok(convo_id) => CreateConvoResult {
|
||||
error_code: ErrorCode::None as i32,
|
||||
convo_id: Some(convo_id),
|
||||
},
|
||||
Err(ClientError::Chat(ChatError::Delivery(_))) => CreateConvoResult {
|
||||
error_code: ErrorCode::DeliveryFail as i32,
|
||||
convo_id: None,
|
||||
},
|
||||
Err(ClientError::Chat(_)) => CreateConvoResult {
|
||||
error_code: ErrorCode::BadIntro as i32,
|
||||
convo_id: None,
|
||||
},
|
||||
};
|
||||
Box::new(result).into()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn create_convo_result_error_code(r: &CreateConvoResult) -> i32 {
|
||||
r.error_code
|
||||
}
|
||||
|
||||
/// Returns an empty slice when error_code != 0.
|
||||
/// The slice is valid only while `r` is alive.
|
||||
#[ffi_export]
|
||||
fn create_convo_result_id(r: &CreateConvoResult) -> c_slice::Ref<'_, u8> {
|
||||
r.convo_id.as_deref().unwrap_or("").as_bytes().into()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn create_convo_result_free(r: repr_c::Box<CreateConvoResult>) {
|
||||
drop(r)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Send message
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Encrypt `content` and dispatch outbound envelopes. Returns an `ErrorCode`.
|
||||
#[ffi_export]
|
||||
fn client_send_message(
|
||||
handle: &mut ClientHandle,
|
||||
convo_id: c_slice::Ref<'_, u8>,
|
||||
content: c_slice::Ref<'_, u8>,
|
||||
) -> ErrorCode {
|
||||
let id_str = match std::str::from_utf8(convo_id.as_slice()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return ErrorCode::BadUtf8,
|
||||
};
|
||||
match handle.client.send_message(id_str, content.as_slice()) {
|
||||
Ok(()) => ErrorCode::None,
|
||||
Err(ClientError::Chat(ChatError::Delivery(_))) => ErrorCode::DeliveryFail,
|
||||
Err(_) => ErrorCode::UnknownError,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inbound (push wire payloads in, drain events out)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Feed an inbound payload (read off the wire by the host) to the client's
|
||||
/// worker, which decrypts it and produces events for `client_poll_events`.
|
||||
#[ffi_export]
|
||||
fn client_push_inbound(handle: &ClientHandle, payload: c_slice::Ref<'_, u8>) {
|
||||
// Disconnected only if the worker has stopped; nothing to do then.
|
||||
let _ = handle.inbound.send(payload.as_slice().to_vec());
|
||||
}
|
||||
|
||||
/// Drain every event the worker has produced since the last call. The list may
|
||||
/// be empty. Free with `event_list_free`.
|
||||
#[ffi_export]
|
||||
fn client_poll_events(handle: &ClientHandle) -> repr_c::Box<EventList> {
|
||||
let events = handle
|
||||
.events
|
||||
.try_iter()
|
||||
.filter_map(EventRow::from_event)
|
||||
.collect();
|
||||
Box::new(EventList {
|
||||
error_code: ErrorCode::None as i32,
|
||||
events,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Block until the worker produces an event or `timeout_ms` elapses, then drain
|
||||
/// everything available. Parks on the channel (no busy-wait); an empty list
|
||||
/// means timeout or a stopped worker. Free with `event_list_free`.
|
||||
#[ffi_export]
|
||||
fn client_wait_events(handle: &ClientHandle, timeout_ms: u64) -> repr_c::Box<EventList> {
|
||||
let timeout = std::time::Duration::from_millis(timeout_ms);
|
||||
let mut events = Vec::new();
|
||||
if let Ok(first) = handle.events.recv_timeout(timeout) {
|
||||
events.extend(EventRow::from_event(first));
|
||||
events.extend(handle.events.try_iter().filter_map(EventRow::from_event));
|
||||
}
|
||||
Box::new(EventList {
|
||||
error_code: ErrorCode::None as i32,
|
||||
events,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn event_list_error_code(list: &EventList) -> i32 {
|
||||
list.error_code
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn event_list_len(list: &EventList) -> usize {
|
||||
list.events.len()
|
||||
}
|
||||
|
||||
/// Returns `EventKind::Invalid` for out-of-bounds indices.
|
||||
#[ffi_export]
|
||||
fn event_list_kind_at(list: &EventList, idx: usize) -> EventKind {
|
||||
match list.events.get(idx) {
|
||||
Some(EventRow::ConversationStarted { .. }) => EventKind::ConversationStarted,
|
||||
Some(EventRow::MessageReceived { .. }) => EventKind::MessageReceived,
|
||||
None => EventKind::Invalid,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an empty slice for out-of-bounds indices.
|
||||
/// The slice is valid only while `list` is alive.
|
||||
#[ffi_export]
|
||||
fn event_list_convo_id_at(list: &EventList, idx: usize) -> c_slice::Ref<'_, u8> {
|
||||
list.events
|
||||
.get(idx)
|
||||
.map(|r| r.convo_id().as_bytes())
|
||||
.unwrap_or(&[])
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Returns an empty slice for non-`MessageReceived` events or out-of-bounds.
|
||||
/// The slice is valid only while `list` is alive.
|
||||
#[ffi_export]
|
||||
fn event_list_content_at(list: &EventList, idx: usize) -> c_slice::Ref<'_, u8> {
|
||||
list.events
|
||||
.get(idx)
|
||||
.map(EventRow::content)
|
||||
.unwrap_or(&[])
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Returns `FfiConversationClass::Invalid` for non-`ConversationStarted`
|
||||
/// events or out-of-bounds.
|
||||
#[ffi_export]
|
||||
fn event_list_conversation_class_at(list: &EventList, idx: usize) -> FfiConversationClass {
|
||||
match list.events.get(idx) {
|
||||
Some(EventRow::ConversationStarted { class, .. }) => *class,
|
||||
_ => FfiConversationClass::Invalid,
|
||||
}
|
||||
}
|
||||
|
||||
#[ffi_export]
|
||||
fn event_list_free(list: repr_c::Box<EventList>) {
|
||||
drop(list)
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
fn main() -> std::io::Result<()> {
|
||||
let path = std::env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "client_ffi.h".into());
|
||||
client_ffi::generate_headers(&path)
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
use crossbeam_channel::Receiver;
|
||||
use libchat::AddressedEnvelope;
|
||||
use logos_chat::{DeliveryService, Transport};
|
||||
|
||||
/// C callback invoked for each outbound envelope. Return 0 or positive on success, negative on
|
||||
/// error. `addr_ptr/addr_len` is the delivery address; `data_ptr/data_len` is the encrypted
|
||||
/// payload. Both pointers are borrowed for the duration of the call only; the callee must not
|
||||
/// retain or free them.
|
||||
pub type DeliverFn = Option<
|
||||
unsafe extern "C" fn(
|
||||
addr_ptr: *const u8,
|
||||
addr_len: usize,
|
||||
data_ptr: *const u8,
|
||||
data_len: usize,
|
||||
) -> i32,
|
||||
>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CDelivery {
|
||||
pub callback: DeliverFn,
|
||||
inbound_rx: Option<Receiver<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl CDelivery {
|
||||
pub fn new(callback: DeliverFn, inbound: Receiver<Vec<u8>>) -> Self {
|
||||
Self {
|
||||
callback,
|
||||
inbound_rx: Some(inbound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeliveryService for CDelivery {
|
||||
type Error = i32;
|
||||
|
||||
fn publish(&mut self, envelope: AddressedEnvelope) -> Result<(), i32> {
|
||||
let cb = self.callback.expect("callback must be non-null");
|
||||
let addr = envelope.delivery_address.as_bytes();
|
||||
let data = envelope.data.as_slice();
|
||||
let rc = unsafe { cb(addr.as_ptr(), addr.len(), data.as_ptr(), data.len()) };
|
||||
if rc < 0 { Err(rc) } else { Ok(()) }
|
||||
}
|
||||
|
||||
fn subscribe(&mut self, _delivery_address: &str) -> Result<(), Self::Error> {
|
||||
// TODO: (P1) CDelivery does not support delivery_address filtering
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Transport for CDelivery {
|
||||
fn inbound(&mut self) -> Receiver<Vec<u8>> {
|
||||
self.inbound_rx
|
||||
.take()
|
||||
.expect("CDelivery::inbound called more than once")
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
mod api;
|
||||
mod delivery;
|
||||
|
||||
#[cfg(feature = "headers")]
|
||||
pub fn generate_headers(path: &str) -> std::io::Result<()> {
|
||||
safer_ffi::headers::builder().to_file(path)?.generate()
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
| Status | Accepted |
|
||||
| Issue | https://github.com/logos-messaging/libchat/issues/97 |
|
||||
| Date | 2026-05-19 |
|
||||
| Last revised | 2026-06-09 |
|
||||
| Last revised | 2026-06-11 |
|
||||
|
||||
## Context and Problem
|
||||
|
||||
@ -17,7 +17,7 @@ Issue #97 captures the requirement for an observation surface that does not pigg
|
||||
|
||||
- **Simplicity of the core.** Fully synchronous and caller-driven: no background work, no callbacks out. External effects flow through services injected as method parameters.
|
||||
- **Asynchronous delivery at the client.** Applications consume events on their own schedule. Observations from sync-triggered processing and observations from background work share a single delivery surface, so the application sees one notification stream and does not care which path produced any given event.
|
||||
- **FFI compatibility.** Payloads crossing the `safer-ffi` boundary in `crates/client-ffi` are limited to owned, concrete data — no closures, generics, or non-`'static` references — so any delivery mechanism must degrade to a sync drain on that side.
|
||||
- **FFI compatibility.** Downstream modules wrap the client behind a C API; payloads crossing that boundary are limited to owned, concrete data — no closures, generics, or non-`'static` references — so any delivery mechanism must degrade to a sync drain on that side.
|
||||
|
||||
## Architecture
|
||||
|
||||
@ -35,7 +35,7 @@ flowchart TB
|
||||
B == "Event (async channel)" ==> A
|
||||
```
|
||||
|
||||
Crates: **app** — `bin/chat-cli`, future `logos-chat-module`; **client** — `crates/client`, `crates/client-ffi`; **core** — `core/conversations` and friends in libchat.
|
||||
Crates: **app** — `bin/chat-cli`, future `logos-chat-module`; **client** — `crates/client`; **core** — `core/conversations` and friends in libchat.
|
||||
|
||||
## Decisions
|
||||
|
||||
|
||||
53
flake.nix
53
flake.nix
@ -27,60 +27,13 @@
|
||||
});
|
||||
in
|
||||
{
|
||||
packages = forAllSystems ({ pkgs, system }:
|
||||
let
|
||||
rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust_toolchain.toml;
|
||||
rustPlatform = pkgs.makeRustPlatform {
|
||||
cargo = rustToolchain;
|
||||
rustc = rustToolchain;
|
||||
};
|
||||
logos-delivery-lib = logos-delivery.packages.${system}.liblogosdelivery.override {
|
||||
packages = forAllSystems ({ system, ... }:
|
||||
{
|
||||
logos-delivery = logos-delivery.packages.${system}.liblogosdelivery.override {
|
||||
enablePostgres = false;
|
||||
enableNimDebugDlOpen = false;
|
||||
chroniclesLogLevel = "FATAL";
|
||||
};
|
||||
in
|
||||
{
|
||||
logos-delivery = logos-delivery-lib;
|
||||
default = rustPlatform.buildRustPackage {
|
||||
pname = "libchat";
|
||||
version = (builtins.fromTOML (builtins.readFile ./crates/client-ffi/Cargo.toml)).package.version;
|
||||
src = pkgs.lib.cleanSourceWith {
|
||||
src = ./.;
|
||||
filter = path: type:
|
||||
let base = builtins.baseNameOf path;
|
||||
in !(builtins.elem base [ "target" "nim-bindings" ".git" ".github" "tmp" ]);
|
||||
};
|
||||
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
outputHashes = {
|
||||
"chat-proto-0.1.0" = "sha256-hiFH/EwTpJd9RLtq1uF2CilzinedfR2o4jvqFaDhk+g=";
|
||||
};
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkgs.perl pkgs.pkg-config pkgs.cmake ];
|
||||
buildType = "release";
|
||||
doCheck = false;
|
||||
cargoBuildFlags = [ "--workspace" "--exclude" "chat-cli" ];
|
||||
|
||||
postBuild = ''
|
||||
cargo run --frozen --release --bin generate-headers --features headers -p client-ffi -- crates/client-ffi/client_ffi.h
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/lib $out/include
|
||||
cp target/${pkgs.stdenv.hostPlatform.rust.rustcTarget}/release/libclient_ffi.a $out/lib/
|
||||
cp crates/client-ffi/client_ffi.h $out/include/
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "Logos Chat library (C FFI)";
|
||||
platforms = platforms.unix;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -1,221 +0,0 @@
|
||||
# Nim FFI bindings for libchat conversations library
|
||||
|
||||
# Error codes (must match Rust ErrorCode enum)
|
||||
const
|
||||
ErrNone* = 0'i32
|
||||
ErrBadPtr* = -1'i32
|
||||
ErrBadConvoId* = -2'i32
|
||||
ErrBadIntro* = -3'i32
|
||||
ErrNotImplemented* = -4'i32
|
||||
ErrBufferExceeded* = -5'i32
|
||||
ErrUnknownError* = -6'i32
|
||||
|
||||
# Opaque handle type for Context
|
||||
type ContextHandle* = pointer
|
||||
|
||||
type
|
||||
## Slice for passing byte arrays to safer_ffi functions
|
||||
SliceUint8* = object
|
||||
`ptr`*: ptr uint8
|
||||
len*: csize_t
|
||||
|
||||
## Vector type returned by safer_ffi functions (must be freed)
|
||||
VecUint8* = object
|
||||
`ptr`*: ptr uint8
|
||||
len*: csize_t
|
||||
cap*: csize_t
|
||||
|
||||
## repr_c::String type from safer_ffi
|
||||
ReprCString* = object
|
||||
`ptr`*: ptr char
|
||||
len*: csize_t
|
||||
cap*: csize_t
|
||||
|
||||
## Payload structure for FFI (matches Rust Payload struct)
|
||||
Payload* = object
|
||||
address*: ReprCString
|
||||
data*: VecUint8
|
||||
|
||||
## Vector of Payloads returned by safer_ffi functions
|
||||
VecPayload* = object
|
||||
`ptr`*: ptr Payload
|
||||
len*: csize_t
|
||||
cap*: csize_t ## Vector of Payloads returned by safer_ffi functions
|
||||
|
||||
VecString* = object
|
||||
`ptr`*: ptr ReprCString
|
||||
len*: csize_t
|
||||
cap*: csize_t
|
||||
|
||||
## Result structure for create_intro_bundle
|
||||
## error_code is 0 on success, negative on error (see ErrorCode)
|
||||
CreateIntroResult* = object
|
||||
error_code*: int32
|
||||
intro_bytes*: VecUint8
|
||||
|
||||
## Result structure for send_content
|
||||
## error_code is 0 on success, negative on error (see ErrorCode)
|
||||
SendContentResult* = object
|
||||
error_code*: int32
|
||||
payloads*: VecPayload
|
||||
|
||||
## Result structure for handle_payload
|
||||
## error_code is 0 on success, negative on error (see ErrorCode)
|
||||
HandlePayloadResult* = object
|
||||
error_code*: int32
|
||||
convo_id*: ReprCString
|
||||
content*: VecUint8
|
||||
is_new_convo*: bool
|
||||
|
||||
## Result from create_new_private_convo
|
||||
## error_code is 0 on success, negative on error (see ErrorCode)
|
||||
NewConvoResult* = object
|
||||
error_code*: int32
|
||||
convo_id*: ReprCString
|
||||
payloads*: VecPayload
|
||||
|
||||
## Result from list_conversations
|
||||
## error_code is 0 on success, negative on error (see ErrorCode)
|
||||
ListConvoResult* = object
|
||||
error_code*: int32
|
||||
convo_ids*: VecString
|
||||
|
||||
# FFI function imports
|
||||
|
||||
## Creates a new libchat Context
|
||||
## Returns: Opaque handle to the context. Must be freed with destroy_context()
|
||||
proc create_context*(name: ReprCString): ContextHandle {.importc.}
|
||||
|
||||
## Returns the friendly name of the context's identity
|
||||
## The result must be freed by the caller (repr_c::String ownership transfers)
|
||||
proc installation_name*(ctx: ContextHandle): ReprCString {.importc.}
|
||||
|
||||
## Destroys a context and frees its memory
|
||||
## - handle must be a valid pointer from create_context()
|
||||
## - handle must not be used after this call
|
||||
proc destroy_context*(ctx: ContextHandle) {.importc.}
|
||||
|
||||
## Free a ReprCString returned by any of the FFI functions
|
||||
## - s must be an owned ReprCString value returned from an FFI function
|
||||
## - s must not be used after this call
|
||||
proc destroy_string*(s: ReprCString) {.importc.}
|
||||
|
||||
## Creates an intro bundle for sharing with other users
|
||||
## Returns: CreateIntroResult struct - check error_code field (0 = success, negative = error)
|
||||
## The result must be freed with destroy_intro_result()
|
||||
proc create_intro_bundle*(ctx: ContextHandle): CreateIntroResult {.importc.}
|
||||
|
||||
## Creates a new private conversation
|
||||
## Returns: NewConvoResult struct - check error_code field (0 = success, negative = error)
|
||||
## The result must be freed with destroy_convo_result()
|
||||
proc create_new_private_convo*(
|
||||
ctx: ContextHandle, bundle: SliceUint8, content: SliceUint8
|
||||
): NewConvoResult {.importc.}
|
||||
|
||||
## Get the available conversation identifers.
|
||||
## Returns: ListConvoResult struct - check error_code field (0 = success, negative = error)
|
||||
## The result must be freed with destroy_list_result()
|
||||
proc list_conversations*(ctx: ContextHandle): ListConvoResult {.importc.}
|
||||
|
||||
## Sends content to an existing conversation
|
||||
## Returns: SendContentResult struct - check error_code field (0 = success, negative = error)
|
||||
## The result must be freed with destroy_send_content_result()
|
||||
proc send_content*(
|
||||
ctx: ContextHandle, convo_id: ReprCString, content: SliceUint8
|
||||
): SendContentResult {.importc.}
|
||||
|
||||
## Handles an incoming payload
|
||||
## Returns: HandlePayloadResult struct - check error_code field (0 = success, negative = error)
|
||||
## This call does not always generate content. If content is zero bytes long then there
|
||||
## is no data, and the convo_id should be ignored.
|
||||
## The result must be freed with destroy_handle_payload_result()
|
||||
proc handle_payload*(
|
||||
ctx: ContextHandle, payload: SliceUint8
|
||||
): HandlePayloadResult {.importc.}
|
||||
|
||||
## Free the result from create_intro_bundle
|
||||
proc destroy_intro_result*(result: CreateIntroResult) {.importc.}
|
||||
|
||||
## Free the result from create_new_private_convo
|
||||
proc destroy_convo_result*(result: NewConvoResult) {.importc.}
|
||||
|
||||
## Free the result from list_conversation
|
||||
proc destroy_list_result*(result: ListConvoResult) {.importc.}
|
||||
|
||||
## Free the result from send_content
|
||||
proc destroy_send_content_result*(result: SendContentResult) {.importc.}
|
||||
|
||||
## Free the result from handle_payload
|
||||
proc destroy_handle_payload_result*(result: HandlePayloadResult) {.importc.}
|
||||
|
||||
# ============================================================================
|
||||
# Helper functions
|
||||
# ============================================================================
|
||||
|
||||
## Create a SliceRefUint8 from a string
|
||||
proc toSlice*(s: string): SliceUint8 =
|
||||
if s.len == 0:
|
||||
SliceUint8(`ptr`: nil, len: 0)
|
||||
else:
|
||||
SliceUint8(`ptr`: cast[ptr uint8](unsafeAddr s[0]), len: csize_t(s.len))
|
||||
|
||||
## Create a SliceRefUint8 from a seq[byte]
|
||||
proc toSlice*(s: seq[byte]): SliceUint8 =
|
||||
if s.len == 0:
|
||||
SliceUint8(`ptr`: nil, len: 0)
|
||||
else:
|
||||
SliceUint8(`ptr`: cast[ptr uint8](unsafeAddr s[0]), len: csize_t(s.len))
|
||||
|
||||
## Convert a ReprCString to a Nim string
|
||||
proc `$`*(s: ReprCString): string =
|
||||
if s.ptr == nil or s.len == 0:
|
||||
return ""
|
||||
result = newString(s.len)
|
||||
copyMem(addr result[0], s.ptr, s.len)
|
||||
|
||||
## Create a ReprCString from a Nim string for passing to FFI functions.
|
||||
## WARNING: The returned ReprCString borrows from the input string.
|
||||
## The input string must remain valid for the duration of the FFI call.
|
||||
## cap is set to 0 to prevent Rust from attempting to deallocate Nim memory.
|
||||
proc toReprCString*(s: string): ReprCString =
|
||||
if s.len == 0:
|
||||
ReprCString(`ptr`: nil, len: 0, cap: 0)
|
||||
else:
|
||||
ReprCString(`ptr`: cast[ptr char](unsafeAddr s[0]), len: csize_t(s.len), cap: 0)
|
||||
|
||||
## Convert a VecUint8 to a seq[string]
|
||||
proc toSeq*(v: VecString): seq[string] =
|
||||
if v.ptr == nil or v.len == 0:
|
||||
return @[]
|
||||
result = newSeq[string](v.len)
|
||||
let arr = cast[ptr UncheckedArray[ReprCString]](v.ptr)
|
||||
for i in 0 ..< int(v.len):
|
||||
result[i] = $arr[i]
|
||||
|
||||
## Convert a VecUint8 to a seq[byte]
|
||||
proc toSeq*(v: VecUint8): seq[byte] =
|
||||
if v.ptr == nil or v.len == 0:
|
||||
return @[]
|
||||
result = newSeq[byte](v.len)
|
||||
copyMem(addr result[0], v.ptr, v.len)
|
||||
|
||||
## Access payloads from VecPayload
|
||||
proc `[]`*(v: VecPayload, i: int): Payload =
|
||||
assert i >= 0 and csize_t(i) < v.len
|
||||
cast[ptr UncheckedArray[Payload]](v.ptr)[i]
|
||||
|
||||
## Get length of VecPayload
|
||||
proc len*(v: VecPayload): int =
|
||||
int(v.len)
|
||||
|
||||
## Iterator for VecPayload
|
||||
iterator items*(v: VecPayload): Payload =
|
||||
for i in 0 ..< v.len:
|
||||
yield v[int(i)]
|
||||
|
||||
## Convert a string to seq[byte]
|
||||
proc toBytes*(s: string): seq[byte] =
|
||||
if s.len == 0:
|
||||
return @[]
|
||||
result = newSeq[byte](s.len)
|
||||
copyMem(addr result[0], unsafeAddr s[0], s.len)
|
||||
Loading…
x
Reference in New Issue
Block a user