From ecd1be3b9e7b9032f6c42b62aaa6ebef5963aa9c Mon Sep 17 00:00:00 2001 From: pablo Date: Fri, 27 Mar 2026 10:11:57 +0100 Subject: [PATCH 1/3] feat: prepare for logos module consumption - add flake.nix for Nix builds (cdylib + header generation) - switch crate-type from dylib to cdylib for C FFI - replace placeholder delivery addresses with real derived addresses - switch FFI inputs from owned to borrowed - expose local_delivery_address() so caller knows where to subscribe --- .gitignore | 4 + core/conversations/Cargo.toml | 9 +- core/conversations/src/api.rs | 18 +++- .../src/bin/generate-libchat-headers.rs | 3 + core/conversations/src/context.rs | 4 + .../src/conversation/privatev1.rs | 13 ++- core/conversations/src/inbox/handler.rs | 24 ++--- core/conversations/src/lib.rs | 11 ++- flake.nix | 95 +++++++++++++++++++ 9 files changed, 154 insertions(+), 27 deletions(-) create mode 100644 core/conversations/src/bin/generate-libchat-headers.rs create mode 100644 flake.nix diff --git a/.gitignore b/.gitignore index 1dc0b45..7e66fdf 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,8 @@ target # Temporary data folder tmp +# Auto-generated C headers (regenerated by flake.nix build) +libchat.h +double_ratchet.h + .DS_Store diff --git a/core/conversations/Cargo.toml b/core/conversations/Cargo.toml index 4ea9408..7df95b2 100644 --- a/core/conversations/Cargo.toml +++ b/core/conversations/Cargo.toml @@ -4,7 +4,14 @@ version = "0.1.0" edition = "2024" [lib] -crate-type = ["rlib","staticlib","dylib"] +crate-type = ["rlib","staticlib","cdylib"] + +[features] +headers = ["safer-ffi/headers"] + +[[bin]] +name = "generate-libchat-headers" +required-features = ["headers"] [dependencies] base64 = "0.22" diff --git a/core/conversations/src/api.rs b/core/conversations/src/api.rs index bd1e300..3041b1f 100644 --- a/core/conversations/src/api.rs +++ b/core/conversations/src/api.rs @@ -49,9 +49,9 @@ pub struct ContextHandle(pub(crate) Context); /// # Returns /// Opaque handle to the store. Must be freed with destroy_context() #[ffi_export] -pub fn create_context(name: repr_c::String) -> repr_c::Box { - // Deference name to to `str` and then borrow to &str - Box::new(ContextHandle(Context::new_with_name(&*name))).into() +pub fn create_context(name: c_slice::Ref<'_, u8>) -> repr_c::Box { + let name_str = std::str::from_utf8(name.as_slice()).unwrap_or("default"); + Box::new(ContextHandle(Context::new_with_name(name_str))).into() } /// Returns the friendly name of the contexts installation. @@ -61,6 +61,13 @@ pub fn installation_name(ctx: &ContextHandle) -> repr_c::String { ctx.0.installation_name().to_string().into() } +/// Returns the local delivery address (hex-encoded Blake2b-512 of the installation public key). +/// This is the address other installations use to send messages to this context. +#[ffi_export] +pub fn local_delivery_address(ctx: &ContextHandle) -> repr_c::String { + ctx.0.local_delivery_address().to_string().into() +} + /// Destroys a conversation store and frees its memory /// /// # Safety @@ -182,11 +189,12 @@ pub fn list_conversations(ctx: &mut ContextHandle) -> ListConvoResult { #[ffi_export] pub fn send_content( ctx: &mut ContextHandle, - convo_id: repr_c::String, + convo_id: c_slice::Ref<'_, u8>, content: c_slice::Ref<'_, u8>, out: &mut SendContentResult, ) { - let payloads = match ctx.0.send_content(&convo_id, &content) { + let convo_id_str = std::str::from_utf8(convo_id.as_slice()).unwrap_or(""); + let payloads = match ctx.0.send_content(convo_id_str, &content) { Ok(p) => p, Err(_) => { *out = SendContentResult { diff --git a/core/conversations/src/bin/generate-libchat-headers.rs b/core/conversations/src/bin/generate-libchat-headers.rs new file mode 100644 index 0000000..ac480ce --- /dev/null +++ b/core/conversations/src/bin/generate-libchat-headers.rs @@ -0,0 +1,3 @@ +fn main() -> std::io::Result<()> { + libchat::generate_headers() +} diff --git a/core/conversations/src/context.rs b/core/conversations/src/context.rs index bec37a5..70a52cd 100644 --- a/core/conversations/src/context.rs +++ b/core/conversations/src/context.rs @@ -65,6 +65,10 @@ impl Context { self._identity.get_name() } + pub fn local_delivery_address(&self) -> &str { + self.inbox.id() + } + pub fn create_private_convo( &mut self, remote_bundle: &Introduction, diff --git a/core/conversations/src/conversation/privatev1.rs b/core/conversations/src/conversation/privatev1.rs index 0b8042e..0289be5 100644 --- a/core/conversations/src/conversation/privatev1.rs +++ b/core/conversations/src/conversation/privatev1.rs @@ -55,11 +55,12 @@ impl BaseConvoId { pub struct PrivateV1Convo { local_convo_id: String, remote_convo_id: String, + remote_delivery_address: String, dr_state: RatchetState, } impl PrivateV1Convo { - pub fn new_initiator(seed_key: SymmetricKey32, remote: PublicKey) -> Self { + pub fn new_initiator(seed_key: SymmetricKey32, remote: PublicKey, remote_delivery_address: String) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Initiator); let remote_convo_id = base_convo_id.id_for_participant(Role::Responder); @@ -73,11 +74,12 @@ impl PrivateV1Convo { Self { local_convo_id, remote_convo_id, + remote_delivery_address, dr_state, } } - pub fn new_responder(seed_key: SymmetricKey32, dh_self: &PrivateKey) -> Self { + pub fn new_responder(seed_key: SymmetricKey32, dh_self: &PrivateKey, remote_delivery_address: String) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Responder); let remote_convo_id = base_convo_id.id_for_participant(Role::Initiator); @@ -92,6 +94,7 @@ impl PrivateV1Convo { Self { local_convo_id, remote_convo_id, + remote_delivery_address, dr_state, } } @@ -179,7 +182,7 @@ impl Convo for PrivateV1Convo { let data = self.encrypt(frame); Ok(vec![AddressedEncryptedPayload { - delivery_address: "delivery_address".into(), + delivery_address: self.remote_delivery_address.clone(), data, }]) } @@ -236,8 +239,8 @@ mod tests { let seed_key_saro = SymmetricKey32::from(seed_key); let seed_key_raya = SymmetricKey32::from(seed_key); let send_content_bytes = vec![0, 2, 4, 6, 8]; - let mut sr_convo = PrivateV1Convo::new_initiator(seed_key_saro, pub_raya); - let mut rs_convo = PrivateV1Convo::new_responder(seed_key_raya, &raya); + let mut sr_convo = PrivateV1Convo::new_initiator(seed_key_saro, pub_raya, "test_addr".into()); + let mut rs_convo = PrivateV1Convo::new_responder(seed_key_raya, &raya, "test_addr".into()); let send_frame = PrivateV1Frame { conversation_id: "_".into(), diff --git a/core/conversations/src/inbox/handler.rs b/core/conversations/src/inbox/handler.rs index 278ae16..6752b3a 100644 --- a/core/conversations/src/inbox/handler.rs +++ b/core/conversations/src/inbox/handler.rs @@ -16,12 +16,6 @@ use crate::inbox::handshake::InboxHandshake; use crate::proto; use crate::types::{AddressedEncryptedPayload, ContentData}; -/// Compute the deterministic Delivery_address for an installation -fn delivery_address_for_installation(_: PublicKey) -> String { - // TODO: Implement Delivery Address - "delivery_address".into() -} - pub struct Inbox { ident: Rc, local_convo_id: String, @@ -78,13 +72,12 @@ impl Inbox { let (seed_key, ephemeral_pub) = InboxHandshake::perform_as_initiator(self.ident.secret(), &pkb, &mut rng); - let mut convo = PrivateV1Convo::new_initiator(seed_key, *remote_bundle.ephemeral_key()); + let remote_delivery_addr = Inbox::inbox_identifier_for_key(*remote_bundle.installation_key()); + let mut convo = PrivateV1Convo::new_initiator(seed_key, *remote_bundle.ephemeral_key(), remote_delivery_addr.clone()); let mut payloads = convo.send_message(initial_message)?; - // Wrap First payload in Invite if let Some(first_message) = payloads.get_mut(0) { - // Take the the value of .data - it's being replaced at the end of this block let frame = Self::wrap_in_invite(std::mem::take(&mut first_message.data)); // TODO: Encrypt frame @@ -102,10 +95,7 @@ impl Inbox { payload: Bytes::from_owner(ciphertext), }; - // Update the address field with the Inbox delivery_Address - first_message.delivery_address = - delivery_address_for_installation(*remote_bundle.installation_key()); - // Update the data field with new Payload + first_message.delivery_address = remote_delivery_addr; first_message.data = proto::EncryptedPayload { encryption: Some(proto::Encryption::InboxHandshake(handshake)), }; @@ -128,12 +118,18 @@ impl Inbox { let key_index = hex::encode(header.responder_ephemeral.as_ref()); let ephemeral_key = self.lookup_ephemeral_key(&key_index)?; + // Extract initiator's identity key for delivery address before header is consumed + let initiator_static_bytes: [u8; 32] = header.initiator_static.as_ref() + .try_into() + .map_err(|_| ChatError::BadBundleValue("wrong size - initiator static".into()))?; + let remote_delivery_addr = Inbox::inbox_identifier_for_key(PublicKey::from(initiator_static_bytes)); + // Perform handshake and decrypt frame let (seed_key, frame) = self.perform_handshake(ephemeral_key, header, handshake.payload)?; match frame.frame_type.unwrap() { proto::inbox_v1_frame::FrameType::InvitePrivateV1(_invite_private_v1) => { - let mut convo = PrivateV1Convo::new_responder(seed_key, ephemeral_key); + let mut convo = PrivateV1Convo::new_responder(seed_key, ephemeral_key, remote_delivery_addr); let Some(enc_payload) = _invite_private_v1.initial_message else { return Err(ChatError::Protocol("missing initial encpayload".into())); diff --git a/core/conversations/src/lib.rs b/core/conversations/src/lib.rs index de0c023..0cb6bf6 100644 --- a/core/conversations/src/lib.rs +++ b/core/conversations/src/lib.rs @@ -14,6 +14,13 @@ pub use api::*; pub use context::{Context, Introduction}; pub use errors::ChatError; +#[cfg(feature = "headers")] +pub fn generate_headers() -> std::io::Result<()> { + safer_ffi::headers::builder() + .to_file("libchat.h")? + .generate() +} + #[cfg(test)] mod tests { @@ -24,8 +31,8 @@ mod tests { #[test] fn test_message_roundtrip() { - let mut saro = create_context("saro".into()); - let mut raya = create_context("raya".into()); + let mut saro = create_context(b"saro".as_slice().into()); + let mut raya = create_context(b"raya".as_slice().into()); // Raya Creates Bundle and Sends to Saro let mut intro_result = CreateIntroResult { diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7653b8c --- /dev/null +++ b/flake.nix @@ -0,0 +1,95 @@ +{ + description = "libchat - Logos Chat cryptographic library"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, rust-overlay }: + let + systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f { + pkgs = import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + }; + }); + in + { + packages = forAllSystems ({ pkgs }: + let + rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust_toolchain.toml; + rustPlatform = pkgs.makeRustPlatform { + cargo = rustToolchain; + rustc = rustToolchain; + }; + in + { + default = rustPlatform.buildRustPackage { + pname = "libchat"; + version = "0.1.0"; + 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-aCl80VOIkd/GK3gnmRuFoSAvPBfeE/FKCaNlLt5AbUU="; + }; + }; + + # perl: required by openssl-sys (transitive dep) + nativeBuildInputs = [ pkgs.perl pkgs.pkg-config pkgs.cmake ]; + doCheck = false; # tests require network access unavailable in nix sandbox + + postBuild = '' + cargo run --release --bin generate-libchat-headers --features headers + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out/lib $out/include + + # Copy shared library + cp target/release/liblibchat.so $out/lib/ 2>/dev/null || true + cp target/release/liblibchat.dylib $out/lib/ 2>/dev/null || true + cp target/release/liblibchat.a $out/lib/ 2>/dev/null || true + + # Copy generated header + cp libchat.h $out/include/ + + runHook postInstall + ''; + + meta = with pkgs.lib; { + description = "Logos Chat cryptographic library (C FFI)"; + platforms = platforms.unix; + }; + }; + } + ); + + devShells = forAllSystems ({ pkgs }: + let + rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust_toolchain.toml; + in + { + default = pkgs.mkShell { + nativeBuildInputs = [ + rustToolchain + pkgs.pkg-config + pkgs.cmake + ]; + }; + } + ); + }; +} From 14a403399edf821057b3e8cd3d975f581724c736 Mon Sep 17 00:00:00 2001 From: pablo Date: Fri, 27 Mar 2026 10:43:29 +0100 Subject: [PATCH 2/3] pr feedback and test fix --- core/conversations/src/api.rs | 19 +++++++++++++++---- .../src/conversation/privatev1.rs | 15 ++++++++++++--- core/conversations/src/inbox/handler.rs | 19 ++++++++++++++----- flake.nix | 10 ++++++++-- nim-bindings/src/bindings.nim | 4 ++-- nim-bindings/tests/test_all_endpoints.nim | 12 ++++++------ 6 files changed, 57 insertions(+), 22 deletions(-) diff --git a/core/conversations/src/api.rs b/core/conversations/src/api.rs index 3041b1f..a189627 100644 --- a/core/conversations/src/api.rs +++ b/core/conversations/src/api.rs @@ -47,11 +47,13 @@ pub struct ContextHandle(pub(crate) Context); /// Creates a new libchat Ctx /// /// # Returns -/// Opaque handle to the store. Must be freed with destroy_context() +/// Opaque handle to the store. Must be freed with destroy_context(). +/// Uses lossy UTF-8 conversion: invalid bytes are replaced with U+FFFD +/// so the caller always gets a deterministic name reflecting their input. #[ffi_export] pub fn create_context(name: c_slice::Ref<'_, u8>) -> repr_c::Box { - let name_str = std::str::from_utf8(name.as_slice()).unwrap_or("default"); - Box::new(ContextHandle(Context::new_with_name(name_str))).into() + let name_str = std::string::String::from_utf8_lossy(name.as_slice()); + Box::new(ContextHandle(Context::new_with_name(&name_str))).into() } /// Returns the friendly name of the contexts installation. @@ -193,7 +195,16 @@ pub fn send_content( content: c_slice::Ref<'_, u8>, out: &mut SendContentResult, ) { - let convo_id_str = std::str::from_utf8(convo_id.as_slice()).unwrap_or(""); + let convo_id_str = match std::str::from_utf8(convo_id.as_slice()) { + Ok(s) => s, + Err(_) => { + *out = SendContentResult { + error_code: ErrorCode::BadConvoId as i32, + payloads: safer_ffi::Vec::EMPTY, + }; + return; + } + }; let payloads = match ctx.0.send_content(convo_id_str, &content) { Ok(p) => p, Err(_) => { diff --git a/core/conversations/src/conversation/privatev1.rs b/core/conversations/src/conversation/privatev1.rs index 0289be5..080f315 100644 --- a/core/conversations/src/conversation/privatev1.rs +++ b/core/conversations/src/conversation/privatev1.rs @@ -60,7 +60,11 @@ pub struct PrivateV1Convo { } impl PrivateV1Convo { - pub fn new_initiator(seed_key: SymmetricKey32, remote: PublicKey, remote_delivery_address: String) -> Self { + pub fn new_initiator( + seed_key: SymmetricKey32, + remote: PublicKey, + remote_delivery_address: String, + ) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Initiator); let remote_convo_id = base_convo_id.id_for_participant(Role::Responder); @@ -79,7 +83,11 @@ impl PrivateV1Convo { } } - pub fn new_responder(seed_key: SymmetricKey32, dh_self: &PrivateKey, remote_delivery_address: String) -> Self { + pub fn new_responder( + seed_key: SymmetricKey32, + dh_self: &PrivateKey, + remote_delivery_address: String, + ) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Responder); let remote_convo_id = base_convo_id.id_for_participant(Role::Initiator); @@ -239,7 +247,8 @@ mod tests { let seed_key_saro = SymmetricKey32::from(seed_key); let seed_key_raya = SymmetricKey32::from(seed_key); let send_content_bytes = vec![0, 2, 4, 6, 8]; - let mut sr_convo = PrivateV1Convo::new_initiator(seed_key_saro, pub_raya, "test_addr".into()); + let mut sr_convo = + PrivateV1Convo::new_initiator(seed_key_saro, pub_raya, "test_addr".into()); let mut rs_convo = PrivateV1Convo::new_responder(seed_key_raya, &raya, "test_addr".into()); let send_frame = PrivateV1Frame { diff --git a/core/conversations/src/inbox/handler.rs b/core/conversations/src/inbox/handler.rs index 6752b3a..6b1862e 100644 --- a/core/conversations/src/inbox/handler.rs +++ b/core/conversations/src/inbox/handler.rs @@ -72,8 +72,13 @@ impl Inbox { let (seed_key, ephemeral_pub) = InboxHandshake::perform_as_initiator(self.ident.secret(), &pkb, &mut rng); - let remote_delivery_addr = Inbox::inbox_identifier_for_key(*remote_bundle.installation_key()); - let mut convo = PrivateV1Convo::new_initiator(seed_key, *remote_bundle.ephemeral_key(), remote_delivery_addr.clone()); + let remote_delivery_addr = + Inbox::inbox_identifier_for_key(*remote_bundle.installation_key()); + let mut convo = PrivateV1Convo::new_initiator( + seed_key, + *remote_bundle.ephemeral_key(), + remote_delivery_addr.clone(), + ); let mut payloads = convo.send_message(initial_message)?; @@ -119,17 +124,21 @@ impl Inbox { let ephemeral_key = self.lookup_ephemeral_key(&key_index)?; // Extract initiator's identity key for delivery address before header is consumed - let initiator_static_bytes: [u8; 32] = header.initiator_static.as_ref() + let initiator_static_bytes: [u8; 32] = header + .initiator_static + .as_ref() .try_into() .map_err(|_| ChatError::BadBundleValue("wrong size - initiator static".into()))?; - let remote_delivery_addr = Inbox::inbox_identifier_for_key(PublicKey::from(initiator_static_bytes)); + let remote_delivery_addr = + Inbox::inbox_identifier_for_key(PublicKey::from(initiator_static_bytes)); // Perform handshake and decrypt frame let (seed_key, frame) = self.perform_handshake(ephemeral_key, header, handshake.payload)?; match frame.frame_type.unwrap() { proto::inbox_v1_frame::FrameType::InvitePrivateV1(_invite_private_v1) => { - let mut convo = PrivateV1Convo::new_responder(seed_key, ephemeral_key, remote_delivery_addr); + let mut convo = + PrivateV1Convo::new_responder(seed_key, ephemeral_key, remote_delivery_addr); let Some(enc_payload) = _invite_private_v1.initial_message else { return Err(ChatError::Protocol("missing initial encpayload".into())); diff --git a/flake.nix b/flake.nix index 7653b8c..ea8d250 100644 --- a/flake.nix +++ b/flake.nix @@ -51,18 +51,24 @@ doCheck = false; # tests require network access unavailable in nix sandbox postBuild = '' - cargo run --release --bin generate-libchat-headers --features headers + cargo run --frozen --release --bin generate-libchat-headers --features headers ''; installPhase = '' runHook preInstall mkdir -p $out/lib $out/include - # Copy shared library + # Copy shared library (platform-dependent extension) cp target/release/liblibchat.so $out/lib/ 2>/dev/null || true cp target/release/liblibchat.dylib $out/lib/ 2>/dev/null || true cp target/release/liblibchat.a $out/lib/ 2>/dev/null || true + # Fail if no library was produced + if [ -z "$(ls $out/lib/liblibchat.* 2>/dev/null)" ]; then + echo "ERROR: No library artifact found in target/release/" + exit 1 + fi + # Copy generated header cp libchat.h $out/include/ diff --git a/nim-bindings/src/bindings.nim b/nim-bindings/src/bindings.nim index eb6053e..aa7310a 100644 --- a/nim-bindings/src/bindings.nim +++ b/nim-bindings/src/bindings.nim @@ -85,7 +85,7 @@ type ## Creates a new libchat Context ## Returns: Opaque handle to the context. Must be freed with destroy_context() -proc create_context*(name: ReprCString): ContextHandle {.importc.} +proc create_context*(name: SliceUint8): ContextHandle {.importc.} ## Returns the friendly name of the context's identity ## The result must be freed by the caller (repr_c::String ownership transfers) @@ -129,7 +129,7 @@ proc list_conversations*( ## The result must be freed with destroy_send_content_result() proc send_content*( ctx: ContextHandle, - convo_id: ReprCString, + convo_id: SliceUint8, content: SliceUint8, ): SendContentResult {.importc.} diff --git a/nim-bindings/tests/test_all_endpoints.nim b/nim-bindings/tests/test_all_endpoints.nim index e9e3f54..bc12f95 100644 --- a/nim-bindings/tests/test_all_endpoints.nim +++ b/nim-bindings/tests/test_all_endpoints.nim @@ -72,7 +72,7 @@ proc testHelperProcs() = proc testContextLifecycle() = echo "\n--- testContextLifecycle ---" - let ctx = create_context(toReprCString("lifecycle-test")) + let ctx = create_context(toSlice("lifecycle-test")) check(ctx != nil, "create_context: returns non-nil handle") let iname = installation_name(ctx) @@ -94,10 +94,10 @@ proc testContextLifecycle() = proc testFullConversationFlow() = echo "\n--- testFullConversationFlow ---" - let aliceCtx = create_context(toReprCString("alice")) + let aliceCtx = create_context(toSlice("alice")) check(aliceCtx != nil, "Alice: create_context non-nil") - let bobCtx = create_context(toReprCString("bob")) + let bobCtx = create_context(toSlice("bob")) check(bobCtx != nil, "Bob: create_context non-nil") # --- create_intro_bundle --- @@ -177,7 +177,7 @@ proc testFullConversationFlow() = # --- send_content --- var sendRes = send_content( aliceCtx, - toReprCString(aliceConvoId), + toSlice(aliceConvoId), toSlice("How are you, Bob?") ) check(sendRes.error_code == ErrNone, @@ -213,13 +213,13 @@ proc testFullConversationFlow() = proc testErrorCases() = echo "\n--- testErrorCases ---" - let ctx = create_context(toReprCString("error-tester")) + let ctx = create_context(toSlice("error-tester")) check(ctx != nil, "error-tester: create_context non-nil") # send_content with a nonexistent convo_id must fail var badSend = send_content( ctx, - toReprCString("00000000-0000-0000-0000-nonexistent"), + toSlice("00000000-0000-0000-0000-nonexistent"), toSlice("payload") ) check(badSend.error_code != ErrNone, From cdd58ec7f4f01db8ac439f1c2ab2a3f186d14eae Mon Sep 17 00:00:00 2001 From: pablo Date: Fri, 27 Mar 2026 10:51:45 +0100 Subject: [PATCH 3/3] fix: build --- core/conversations/src/api.rs | 4 ++-- nim-bindings/src/libchat.nim | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/conversations/src/api.rs b/core/conversations/src/api.rs index a189627..be0618a 100644 --- a/core/conversations/src/api.rs +++ b/core/conversations/src/api.rs @@ -52,8 +52,8 @@ pub struct ContextHandle(pub(crate) Context); /// so the caller always gets a deterministic name reflecting their input. #[ffi_export] pub fn create_context(name: c_slice::Ref<'_, u8>) -> repr_c::Box { - let name_str = std::string::String::from_utf8_lossy(name.as_slice()); - Box::new(ContextHandle(Context::new_with_name(&name_str))).into() + let name_str = std::string::String::from_utf8_lossy(name.as_slice()).into_owned(); + Box::new(ContextHandle(Context::new_with_name(name_str))).into() } /// Returns the friendly name of the contexts installation. diff --git a/nim-bindings/src/libchat.nim b/nim-bindings/src/libchat.nim index 90960c1..7368c73 100644 --- a/nim-bindings/src/libchat.nim +++ b/nim-bindings/src/libchat.nim @@ -15,7 +15,7 @@ type ## Create a new conversations context proc newConversationsContext*(name: string): LibChat = - result.handle = create_context(name.toReprCString) + result.handle = create_context(name.toSlice) result.buffer_size = 256 if result.handle.isNil: raise newException(IOError, "Failed to create context") @@ -95,7 +95,7 @@ proc sendContent*(ctx: LibChat, convoId: string, content: seq[byte]): Result[seq if content.len == 0: return err("content is zero length") - let res = bindings.send_content(ctx.handle, convoId.toReprCString, content.toSlice()) + let res = bindings.send_content(ctx.handle, convoId.toSlice, content.toSlice()) defer: destroy_send_content_result(res) if res.error_code != 0: