├── src ├── topic_type_methods.rs ├── alloc.rs ├── error.rs ├── common.rs ├── lib.rs ├── dds_publisher.rs ├── dds_subscriber.rs ├── dds_domain.rs ├── dds_waitset.rs ├── dds_participant.rs ├── dds_api.rs ├── dds_topic.rs ├── dds_qos.rs ├── dds_writer.rs ├── dds_reader.rs ├── dds_listener.rs └── serdes.rs ├── .gitignore ├── examples └── internal_topic_subscriber │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── main.rs ├── testdata └── helloworld_data │ ├── idl │ └── HelloWorldData.idl │ ├── src │ └── lib.rs │ ├── Cargo.toml │ └── build.rs ├── dds_derive ├── Cargo.toml ├── LICENSE └── src │ └── lib.rs ├── Cargo.toml ├── .github └── workflows │ └── rust.yml ├── README.md ├── tests └── basic_tests.rs └── LICENSE /src/topic_type_methods.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /examples/internal_topic_subscriber/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /testdata/helloworld_data/idl/HelloWorldData.idl: -------------------------------------------------------------------------------- 1 | module HelloWorldData 2 | { 3 | struct Msg 4 | { 5 | long userID; 6 | string message; 7 | }; 8 | #pragma keylist Msg userID 9 | }; 10 | -------------------------------------------------------------------------------- /testdata/helloworld_data/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | use std::env; 6 | 7 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 8 | 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | #[test] 13 | fn it_works() { 14 | assert_eq!(2 + 2, 4); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/internal_topic_subscriber/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "internal_topic_subscriber" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | cyclonedds-rs = { git = "https://github.com/sjames/cyclonedds-rs.git"} 10 | cyclonedds-sys = { git = "https://github.com/sjames/cyclonedds-sys.git"} 11 | 12 | -------------------------------------------------------------------------------- /testdata/helloworld_data/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "helloworld_data" 3 | version = "0.1.0" 4 | authors = ["Sojan James "] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | cyclonedds-sys = "0.1.0" 12 | 13 | [build-dependencies] 14 | cycloneddscodegen = { git = "https://github.com/sjames/cycloneddscodegen.git", features=["rust_codegen"]} 15 | 16 | -------------------------------------------------------------------------------- /dds_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cdds_derive" 3 | version = "0.1.1" 4 | edition = "2018" 5 | authors = ["Sojan James "] 6 | description = "Derive macros for cyclonedds-rs" 7 | license-file = "LICENSE" 8 | homepage = "https://github.com/sjames/cyclonedds-rs" 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | derive-syn-parse = "0.1.5" 13 | proc-macro2 = "1.0.27" 14 | quote = "1.0" 15 | syn = {version = "1.0", features = ["full", "extra-traits"]} 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cyclonedds-rs" 3 | version = "0.6.4" 4 | authors = ["Sojan James "] 5 | edition = "2018" 6 | description = "Safe Rust bindings for cyclonedds" 7 | license-file = "LICENSE" 8 | homepage = "https://github.com/sjames/cyclonedds-rs" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | cyclonedds-sys = "0.2" 14 | cdr = "0.2.4" 15 | serde = "1" 16 | serde_derive = "1" 17 | murmur3 = "0.5.1" 18 | 19 | thiserror = "1" 20 | rc-box = "1.2" 21 | 22 | [features] 23 | shm = [] 24 | default = ["shm"] 25 | 26 | [dev-dependencies] 27 | tokio = { version = "1", features = ["full"] } 28 | cdds_derive = {path = "dds_derive", version = "0.1"} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/alloc.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | pub use cyclonedds_sys::dds_alloc; 18 | pub use cyclonedds_sys::dds_free_op_t_DDS_FREE_ALL as DDS_FREE_ALL; 19 | pub use cyclonedds_sys::dds_sample_free; 20 | 21 | -------------------------------------------------------------------------------- /testdata/helloworld_data/build.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use cycloneddscodegen as codegen; 18 | 19 | fn main() { 20 | 21 | let idls = vec!["idl/HelloWorldData.idl"]; 22 | codegen::generate_and_compile_datatypes(idls); 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use thiserror::Error; 18 | 19 | #[derive(Error, Debug, Clone)] 20 | pub enum ReaderError { 21 | #[error("Missed a requested deadline")] 22 | RequestedDeadLineMissed, 23 | #[error("Reader is not async type")] 24 | ReaderNotAsync, 25 | #[error("DDS Binding error")] 26 | DdsError(#[from] crate::DDSError ) 27 | } -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use cyclonedds_sys::{DdsEntity}; 18 | 19 | /// An entity on which you can attach a DdsWriter 20 | pub trait DdsWritable { 21 | fn entity(&self) -> &DdsEntity; 22 | } 23 | 24 | /// An entity on which you can attach a DdsReader 25 | pub trait DdsReadable { 26 | fn entity(&self) -> &DdsEntity; 27 | } 28 | 29 | pub trait Entity { 30 | fn entity(&self) -> &DdsEntity; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: | 21 | sudo apt-get install libacl1-dev 22 | git clone https://github.com/eclipse-iceoryx/iceoryx.git 23 | cd iceoryx 24 | git checkout release_2.0 25 | pushd . 26 | mkdir build 27 | cd build 28 | cmake ../iceoryx_meta/ 29 | sudo make install 30 | popd 31 | git clone https://github.com/eclipse-cyclonedds/cyclonedds.git 32 | cd cyclonedds 33 | git checkout releases/0.10.x 34 | mkdir build 35 | cd build 36 | cmake .. 37 | sudo make install 38 | cargo build --verbose 39 | - name: Run tests 40 | run: | 41 | export LD_LIBRARY_PATH=/usr/local/lib 42 | cargo test --verbose 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cyclonedds-rs 2 | 3 | Rust bindings for cyclonedds https://github.com/eclipse-cyclonedds/cyclonedds. 4 | This create no longer depends on a code generator. The Cyclone serialization 5 | interface is used to implement the Rust interface. You can annotate a structure 6 | with the new derive macro and start subscribing and publishing right from Rust. 7 | 8 | # Introduction 9 | 10 | This crate allows you to use the cyclonedds library using safe Rust. It uses the 11 | cyclone serialization/deserialization interface for high performance and IDL free usage. 12 | 13 | # Features 14 | 15 | 1. Qos 16 | 2. Reader and Writer 17 | 3. Listener with closure callbacks 18 | 4. Async reader 19 | 5. multiple and nested keys 20 | 21 | # Roadmap Features 22 | 1. Shared memory support using iceoryx 23 | 24 | # Examples 25 | 26 | 1. https://github.com/sjames/demo-vehicle-speed-subscriber (Vehicle speed subscriber with async reader) 27 | 2. https://github.com/sjames/demo-vehicle-speed-publisher (Vehicle speed publisher) 28 | 29 | # Special Instructions 30 | 31 | The current release only supports the 0.10.X release branch. https://github.com/eclipse-cyclonedds/cyclonedds/tree/releases/0.10.x . 32 | Install this before building this crate or the examples. 33 | 34 | # Dependencies 35 | 36 | * iceoryx https://github.com/eclipse-iceoryx/iceoryx version 2.0.2. (https://github.com/eclipse-iceoryx/iceoryx/commit/f756b7c99ddf714d05929374492b34c5c69355bb) Do not install any other version. 37 | * cyclonedds 0.10.x branch (https://github.com/eclipse-cyclonedds/cyclonedds/commit/1be07de395e4ddf969db2b90328cdf4fb73e9a64) . Ensure that you build and install Cyclone with SHM feature enabled. (cmake -DENABLE_SHM=1 ..) 38 | * git 39 | * libclang 40 | * cmake 41 | * make 42 | * a C/C++ compiler for cmake to use 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright 2020 Sojan James 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | //! Safe Rust bindings to cyclonedds 19 | //! 20 | //! 21 | 22 | pub mod alloc; 23 | mod common; 24 | pub mod dds_api; 25 | pub mod dds_domain; 26 | pub mod dds_listener; 27 | pub mod dds_participant; 28 | pub mod dds_publisher; 29 | pub mod dds_qos; 30 | pub mod dds_reader; 31 | pub mod dds_subscriber; 32 | pub mod dds_topic; 33 | mod dds_waitset; 34 | pub mod dds_writer; 35 | pub mod error; 36 | pub mod serdes; 37 | pub mod topic_type_methods; 38 | 39 | pub use common::{DdsReadable, DdsWritable, Entity}; 40 | pub use dds_api::*; 41 | pub use dds_listener::{DdsListener,DdsListenerBuilder}; 42 | pub use dds_participant::{DdsParticipant, ParticipantBuilder}; 43 | pub use dds_publisher::{DdsPublisher,PublisherBuilder}; 44 | pub use dds_qos::*; 45 | pub use dds_reader::{DdsReadCondition, DdsReader, ReaderBuilder}; 46 | pub use dds_subscriber::{DdsSubscriber,SubscriberBuilder}; 47 | pub use dds_topic::{DdsTopic,TopicBuilder}; 48 | pub use dds_waitset::DdsWaitset; 49 | pub use dds_writer::{DdsWriter,WriterBuilder}; 50 | pub use serdes::{TopicType, SampleBuffer, Sample}; 51 | 52 | pub use cdr; 53 | pub use cyclonedds_sys::dds_error::DDSError; 54 | 55 | pub use serde_derive::{Deserialize, Serialize}; 56 | -------------------------------------------------------------------------------- /examples/internal_topic_subscriber/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_void, CString}, 3 | sync::Arc, 4 | thread::{self, sleep}, 5 | time::Duration, 6 | }; 7 | 8 | use cyclonedds_rs::*; 9 | 10 | use cyclonedds_sys::*; 11 | 12 | fn main() { 13 | println!("Subscribing to internal topics"); 14 | 15 | let participant = DdsParticipant::create(None, None, None).unwrap(); 16 | 17 | unsafe { 18 | 19 | 20 | 21 | let mut samples = [std::ptr::null_mut() as *mut dds_builtintopic_endpoint; 2]; 22 | let mut info = [dds_sample_info_t::default(); 2]; 23 | //assert!(reader > 0); 24 | 25 | let listener = DdsListener::new().on_data_available(move |entity|{ 26 | 27 | println!("Callback received"); 28 | 29 | let ret = dds_take( 30 | entity.entity(), 31 | samples.as_mut_ptr() as *mut *mut c_void, 32 | info.as_mut_ptr(), 33 | 2, 34 | 2, 35 | ); 36 | 37 | if ret > 0 { 38 | let read_samples = &samples[..ret as usize]; 39 | for sample in read_samples { 40 | let sample = **sample; 41 | 42 | if !sample.topic_name.is_null() && !sample.type_name.is_null() { 43 | let topic_name = CString::from_raw(sample.topic_name); 44 | let type_name = CString::from_raw(sample.type_name); 45 | println!("Topic:{:?} Type:{:?}", topic_name, type_name); 46 | let leak = topic_name.into_raw(); 47 | let leak = type_name.into_raw(); 48 | } 49 | 50 | } 51 | dds_return_loan(entity.entity(), samples.as_mut_ptr() as *mut *mut c_void, ret); 52 | } 53 | }).hook(); 54 | 55 | let listener_ptr : *const dds_listener_t = (&listener).into(); 56 | 57 | let reader = dds_create_reader( 58 | cyclonedds_rs::DdsReadable::entity(&participant).entity(), 59 | cyclonedds_sys::builtin_entity::BUILTIN_TOPIC_DCPSPUBLICATION_ENTITY.entity(), 60 | std::ptr::null_mut(), 61 | listener_ptr, 62 | ); 63 | 64 | 65 | println!("Reader is: {}", reader); 66 | 67 | 68 | loop { 69 | sleep(Duration::from_millis(1000)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/dds_publisher.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::{DdsListener, DdsParticipant, DdsQos, DdsWritable}; 18 | pub use cyclonedds_sys::{DDSError, DdsDomainId, DdsEntity}; 19 | use std::convert::From; 20 | 21 | pub struct PublisherBuilder { 22 | maybe_qos: Option, 23 | maybe_listener: Option, 24 | } 25 | 26 | impl PublisherBuilder { 27 | pub fn new() -> Self { 28 | Self { 29 | maybe_qos: None, 30 | maybe_listener: None, 31 | } 32 | } 33 | 34 | pub fn with_qos(mut self, qos : DdsQos) -> Self { 35 | self.maybe_qos = Some(qos); 36 | self 37 | } 38 | 39 | pub fn with_listener(mut self, listener : DdsListener) -> Self { 40 | self.maybe_listener = Some(listener); 41 | self 42 | } 43 | 44 | pub fn create(self,participant: &DdsParticipant) -> Result { 45 | DdsPublisher::create(participant, self.maybe_qos, self.maybe_listener) 46 | } 47 | } 48 | 49 | 50 | #[derive(Clone)] 51 | pub struct DdsPublisher(DdsEntity, Option); 52 | 53 | impl<'a> DdsPublisher { 54 | pub fn create( 55 | participant: &DdsParticipant, 56 | maybe_qos: Option, 57 | maybe_listener: Option, 58 | ) -> Result { 59 | unsafe { 60 | let p = cyclonedds_sys::dds_create_publisher( 61 | participant.entity().entity(), 62 | maybe_qos.map_or(std::ptr::null(), |d| d.into()), 63 | maybe_listener.as_ref().map_or(std::ptr::null(), |l| l.into()), 64 | ); 65 | if p > 0 { 66 | Ok(DdsPublisher(DdsEntity::new(p), maybe_listener)) 67 | } else { 68 | Err(DDSError::from(p)) 69 | } 70 | } 71 | } 72 | } 73 | 74 | impl<'a> DdsWritable for DdsPublisher { 75 | fn entity(&self) -> &DdsEntity { 76 | &self.0 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/dds_subscriber.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::{DdsListener, DdsParticipant, DdsQos, DdsReadable}; 18 | pub use cyclonedds_sys::{DDSError, DdsDomainId, DdsEntity}; 19 | use std::{convert::From}; 20 | 21 | pub struct SubscriberBuilder { 22 | maybe_qos: Option, 23 | maybe_listener: Option, 24 | } 25 | 26 | impl SubscriberBuilder { 27 | pub fn new() -> Self { 28 | Self { 29 | maybe_qos: None, 30 | maybe_listener: None, 31 | } 32 | } 33 | 34 | pub fn with_qos(mut self, qos : DdsQos) -> Self { 35 | self.maybe_qos = Some(qos); 36 | self 37 | } 38 | 39 | pub fn with_listener(mut self, listener : DdsListener) -> Self { 40 | self.maybe_listener = Some(listener); 41 | self 42 | } 43 | 44 | pub fn create(self,participant: &DdsParticipant) -> Result { 45 | DdsSubscriber::create(participant, self.maybe_qos, self.maybe_listener) 46 | } 47 | } 48 | 49 | 50 | #[derive(Clone)] 51 | pub struct DdsSubscriber(DdsEntity, Option); 52 | 53 | impl<'a> DdsSubscriber { 54 | pub fn create( 55 | participant: &DdsParticipant, 56 | maybe_qos: Option, 57 | maybe_listener: Option, 58 | ) -> Result { 59 | unsafe { 60 | let p = cyclonedds_sys::dds_create_subscriber( 61 | participant.entity().entity(), 62 | maybe_qos.map_or(std::ptr::null(), |d| d.into()), 63 | maybe_listener.as_ref().map_or(std::ptr::null(), |l| l.into()), 64 | ); 65 | if p > 0 { 66 | Ok(DdsSubscriber(DdsEntity::new(p),maybe_listener)) 67 | } else { 68 | Err(DDSError::from(p)) 69 | } 70 | } 71 | } 72 | } 73 | 74 | 75 | impl<'a> DdsReadable for DdsSubscriber { 76 | fn entity(&self) -> &DdsEntity { 77 | &self.0 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/dds_domain.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 18 | use cyclonedds_sys::{dds_error::DDSError, DdsDomainId, DdsEntity}; 19 | use std::convert::From; 20 | use std::ffi::CString; 21 | 22 | pub struct DdsDomain(DdsEntity); 23 | 24 | impl DdsDomain { 25 | ///Create a domain with a specified domain id 26 | pub fn create(domain: DdsDomainId, config: Option<&str>) -> Result { 27 | unsafe { 28 | if let Some(cfg) = config { 29 | let domain_name = CString::new(cfg).expect("Unable to create new config string"); 30 | let d = cyclonedds_sys::dds_create_domain(domain, domain_name.as_ptr()); 31 | // negative return value signify an error 32 | if d > 0 { 33 | Ok(DdsDomain(DdsEntity::new(d))) 34 | } else { 35 | Err(DDSError::from(d)) 36 | } 37 | } else { 38 | let d = cyclonedds_sys::dds_create_domain(domain, std::ptr::null()); 39 | 40 | if d > 0 { 41 | Ok(DdsDomain(DdsEntity::new(d))) 42 | } else { 43 | Err(DDSError::from(d)) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | impl PartialEq for DdsDomain { 51 | fn eq(&self, other: &Self) -> bool { 52 | unsafe { self.0.entity() == other.0.entity() } 53 | } 54 | } 55 | 56 | impl Eq for DdsDomain {} 57 | 58 | impl Drop for DdsDomain { 59 | fn drop(&mut self) { 60 | unsafe { 61 | let ret: DDSError = cyclonedds_sys::dds_delete(self.0.entity()).into(); 62 | if DDSError::DdsOk != ret { 63 | panic!("cannot delete domain: {}", ret); 64 | } 65 | } 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | mod dds_domain_tests { 71 | use cyclonedds_sys::{DDSError}; 72 | use crate::dds_domain::DdsDomain; 73 | 74 | #[test] 75 | fn test_create_domain_with_bad_config() { 76 | assert!(Err(DDSError::DdsOk) != DdsDomain::create(0, Some("blah"))); 77 | } 78 | 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/dds_waitset.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::{DdsParticipant, Entity}; 18 | use cyclonedds_sys::size_t; 19 | pub use cyclonedds_sys::{DDSError, DdsDomainId, DdsEntity}; 20 | use std::convert::From; 21 | use std::marker::PhantomData; 22 | 23 | pub struct DdsWaitset(DdsEntity, PhantomData); 24 | 25 | impl<'a, T> DdsWaitset { 26 | pub fn create(participant: &DdsParticipant) -> Result { 27 | unsafe { 28 | let p = cyclonedds_sys::dds_create_waitset(participant.entity().entity()); 29 | if p >= 0 { 30 | Ok(DdsWaitset(DdsEntity::new(p), PhantomData)) 31 | } else { 32 | Err(DDSError::from(p)) 33 | } 34 | } 35 | } 36 | 37 | pub fn attach(&mut self, entity: &dyn Entity, x: &'a T) -> Result<(), DDSError> { 38 | unsafe { 39 | let p = cyclonedds_sys::dds_waitset_attach( 40 | self.0.entity(), 41 | entity.entity().entity(), 42 | x as *const T as isize, 43 | ); 44 | if p == 0 { 45 | Ok(()) 46 | } else { 47 | Err(DDSError::from(p)) 48 | } 49 | } 50 | } 51 | pub fn detach(&mut self, entity: &dyn Entity) -> Result<(), DDSError> { 52 | unsafe { 53 | let p = cyclonedds_sys::dds_waitset_detach(self.0.entity(), entity.entity().entity()); 54 | if p == 0 { 55 | Ok(()) 56 | } else { 57 | Err(DDSError::from(p)) 58 | } 59 | } 60 | } 61 | pub fn set_trigger(&mut self, trigger: bool) -> Result<(), DDSError> { 62 | unsafe { 63 | let p = cyclonedds_sys::dds_waitset_set_trigger(self.0.entity(), trigger); 64 | if p == 0 { 65 | Ok(()) 66 | } else { 67 | Err(DDSError::from(p)) 68 | } 69 | } 70 | } 71 | pub fn wait<'b>( 72 | &mut self, 73 | xs: &'b mut Vec<&'b T>, 74 | timeout_us: i64, 75 | ) -> Result<&'b [&'b T], DDSError> { 76 | let capacity = xs.capacity(); 77 | unsafe { 78 | let p = cyclonedds_sys::dds_waitset_wait( 79 | self.0.entity(), 80 | xs.as_mut_ptr() as *mut isize, 81 | capacity as size_t, 82 | timeout_us, 83 | ); 84 | if p == 0 { 85 | // timeout, empty slice back 86 | Ok(&xs[0..0]) 87 | } else if p > 0 { 88 | let p = p as usize; 89 | xs.set_len(p); 90 | Ok(&xs[0..p]) 91 | } else { 92 | Err(DDSError::from(p)) 93 | } 94 | } 95 | } 96 | } 97 | 98 | impl Entity for DdsWaitset { 99 | fn entity(&self) -> &DdsEntity { 100 | &self.0 101 | } 102 | } 103 | 104 | impl Drop for DdsWaitset { 105 | fn drop(&mut self) { 106 | unsafe { 107 | let _ret: DDSError = cyclonedds_sys::dds_delete(self.0.entity()).into(); 108 | //if DDSError::DdsOk != ret { 109 | // //we ignore the error here as the waitset may be deleted by cyclone 110 | // //panic!("cannot delete DdsWaitset: {}", ret); 111 | //} else { 112 | // 113 | //} 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/dds_participant.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use std::convert::From; 18 | pub use cyclonedds_sys::{DDSError, DdsDomainId, DdsEntity}; 19 | use crate::{DdsReadable, DdsWritable, Entity, dds_listener::DdsListener, dds_qos::DdsQos}; 20 | 21 | /// Builder struct for a Participant. 22 | /// #Example 23 | /// ``` 24 | /// use cyclonedds_rs::{DdsListener, ParticipantBuilder}; 25 | /// let listener = DdsListener::new() 26 | /// .on_subscription_matched(|a,b| { 27 | /// println!("Subscription matched!"); 28 | /// }).on_publication_matched(|a,b|{ 29 | /// println!("Publication matched"); 30 | /// }). 31 | /// hook(); 32 | /// let participant = ParticipantBuilder::new() 33 | /// .with_listener(listener) 34 | /// .create() 35 | /// .expect("Unable to create participant"); 36 | /// 37 | ///``` 38 | /// 39 | pub struct ParticipantBuilder { 40 | maybe_domain : Option, 41 | maybe_qos : Option, 42 | maybe_listener : Option, 43 | } 44 | 45 | impl ParticipantBuilder { 46 | 47 | pub fn new() -> Self { 48 | ParticipantBuilder { 49 | maybe_domain: None, 50 | maybe_qos: None, 51 | maybe_listener: None, 52 | } 53 | } 54 | 55 | pub fn with_domain(mut self, domain: DdsDomainId) -> Self { 56 | self.maybe_domain = Some(domain); 57 | self 58 | } 59 | 60 | pub fn with_qos(mut self, qos : DdsQos) -> Self { 61 | self.maybe_qos = Some(qos); 62 | self 63 | } 64 | 65 | pub fn with_listener(mut self, listener: DdsListener) -> Self { 66 | self.maybe_listener = Some(listener); 67 | self 68 | } 69 | 70 | pub fn create(self) -> Result { 71 | DdsParticipant::create(self.maybe_domain, self.maybe_qos, self.maybe_listener) 72 | } 73 | } 74 | 75 | 76 | pub struct DdsParticipant(DdsEntity, Option); 77 | 78 | impl DdsParticipant { 79 | pub fn create( 80 | maybe_domain: Option, 81 | maybe_qos: Option, 82 | maybe_listener: Option, 83 | ) -> Result { 84 | 85 | unsafe { 86 | let p = cyclonedds_sys::dds_create_participant( 87 | maybe_domain.unwrap_or(0xFFFF_FFFF), 88 | maybe_qos.map_or(std::ptr::null(), |d| d.into()), 89 | maybe_listener.as_ref().map_or(std::ptr::null(), |l| l.into()), 90 | ); 91 | if p > 0 { 92 | Ok(DdsParticipant(DdsEntity::new(p), maybe_listener)) 93 | } else { 94 | Err(DDSError::from(p)) 95 | } 96 | } 97 | } 98 | } 99 | 100 | /* 101 | impl Drop for DdsParticipant { 102 | fn drop(&mut self) { 103 | unsafe { 104 | let ret: DDSError = cyclonedds_sys::dds_delete(self.0.entity()).into(); 105 | if DDSError::DdsOk != ret { 106 | panic!("cannot delete participant: {}", ret); 107 | } else { 108 | //println!("Participant dropped"); 109 | } 110 | } 111 | } 112 | } 113 | */ 114 | 115 | impl DdsWritable for DdsParticipant { 116 | fn entity(&self) -> &DdsEntity { 117 | &self.0 118 | } 119 | } 120 | 121 | impl DdsReadable for DdsParticipant { 122 | fn entity(&self) -> &DdsEntity { 123 | &self.0 124 | } 125 | } 126 | 127 | impl Entity for DdsParticipant { 128 | fn entity(&self) -> &DdsEntity { 129 | &self.0 130 | } 131 | } 132 | 133 | #[cfg(test)] 134 | mod dds_participant_tests { 135 | use super::*; 136 | 137 | #[test] 138 | fn test_create() { 139 | let mut qos = DdsQos::create().unwrap(); 140 | qos.set_lifespan(std::time::Duration::from_nanos(1000)); 141 | let _par = DdsParticipant::create(None, Some(qos), None); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/dds_api.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use std::convert::From; 18 | 19 | use crate::common::Entity; 20 | pub use cyclonedds_sys::dds_error::DDSError; 21 | use cyclonedds_sys::DdsEntity; 22 | 23 | //use crate::dds_writer::DdsWriter; 24 | pub use cyclonedds_sys::{dds_attach_t, dds_duration_t}; 25 | pub use cyclonedds_sys::{dds_status_id}; 26 | 27 | // re-export constants 28 | pub use cyclonedds_sys::dds_status_id_DDS_DATA_AVAILABLE_STATUS_ID as DDS_DATA_AVAILABLE_STATUS_ID; 29 | pub use cyclonedds_sys::dds_status_id_DDS_DATA_ON_READERS_STATUS_ID as DDS_DATA_ON_READERS_STATUS_ID; 30 | pub use cyclonedds_sys::dds_status_id_DDS_INCONSISTENT_TOPIC_STATUS_ID as DDS_INCONSISTENT_TOPIC_STATUS_ID; 31 | pub use cyclonedds_sys::dds_status_id_DDS_LIVELINESS_CHANGED_STATUS_ID as DDS_LIVELINESS_CHANGED_STATUS_ID; 32 | pub use cyclonedds_sys::dds_status_id_DDS_LIVELINESS_LOST_STATUS_ID as DDS_LIVELINESS_LOST_STATUS_ID; 33 | pub use cyclonedds_sys::dds_status_id_DDS_OFFERED_DEADLINE_MISSED_STATUS_ID as DDS_OFFERED_DEADLINE_MISSED_STATUS_ID; 34 | pub use cyclonedds_sys::dds_status_id_DDS_OFFERED_INCOMPATIBLE_QOS_STATUS_ID as DDS_OFFERED_INCOMPATIBLE_QOS_STATUS_ID; 35 | pub use cyclonedds_sys::dds_status_id_DDS_PUBLICATION_MATCHED_STATUS_ID as DDS_PUBLICATION_MATCHED_STATUS_ID; 36 | pub use cyclonedds_sys::dds_status_id_DDS_REQUESTED_DEADLINE_MISSED_STATUS_ID as DDS_REQUESTED_DEADLINE_MISSED_STATUS_ID; 37 | pub use cyclonedds_sys::dds_status_id_DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS_ID as DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS_ID; 38 | pub use cyclonedds_sys::dds_status_id_DDS_SAMPLE_LOST_STATUS_ID as DDS_SAMPLE_LOST_STATUS_ID; 39 | pub use cyclonedds_sys::dds_status_id_DDS_SAMPLE_REJECTED_STATUS_ID as DDS_SAMPLE_REJECTED_STATUS_ID; 40 | pub use cyclonedds_sys::dds_status_id_DDS_SUBSCRIPTION_MATCHED_STATUS_ID as DDS_SUBSCRIPTION_MATCHED_STATUS_ID; 41 | pub use cyclonedds_sys::State; 42 | pub use cyclonedds_sys::StateMask; 43 | 44 | #[derive(Default)] 45 | pub struct DdsStatus(u32); 46 | 47 | impl DdsStatus { 48 | pub fn set(mut self, id: dds_status_id) -> Self { 49 | let mask = 1 << id; 50 | self.0 |= mask; 51 | self 52 | } 53 | 54 | pub fn is_set(&self, id: dds_status_id) -> bool { 55 | let mask = 1 << id; 56 | mask & self.0 != 0 57 | } 58 | } 59 | 60 | 61 | 62 | impl From for u32 { 63 | fn from(status: DdsStatus) -> Self { 64 | status.0 65 | } 66 | } 67 | 68 | pub fn dds_set_status_mask(entity: &DdsEntity, status_mask: DdsStatus) -> Result<(), DDSError> { 69 | unsafe { 70 | let err = cyclonedds_sys::dds_set_status_mask(entity.entity(), status_mask.into()); 71 | 72 | if err < 0 { 73 | Err(DDSError::from(err)) 74 | } else { 75 | Ok(()) 76 | } 77 | } 78 | } 79 | 80 | pub fn dds_get_status_changes(entity: &DdsEntity) -> Result { 81 | unsafe { 82 | let mut status = DdsStatus::default(); 83 | let err = cyclonedds_sys::dds_get_status_changes(entity.entity(), &mut status.0); 84 | 85 | if err < 0 { 86 | Err(DDSError::from(err)) 87 | } else { 88 | Ok(status) 89 | } 90 | } 91 | } 92 | 93 | pub fn dds_triggered(entity: &dyn Entity) -> Result<(), DDSError> { 94 | unsafe { 95 | let err = cyclonedds_sys::dds_triggered(entity.entity().entity()); 96 | 97 | if err < 0 { 98 | Err(DDSError::from(err)) 99 | } else { 100 | Ok(()) 101 | } 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod dds_qos_tests { 107 | use super::*; 108 | #[test] 109 | fn test_dds_status() { 110 | let status = DdsStatus::default(); 111 | assert_eq!(false, status.is_set(0)); 112 | let status = status 113 | .set(DDS_INCONSISTENT_TOPIC_STATUS_ID) 114 | .set(DDS_OFFERED_DEADLINE_MISSED_STATUS_ID) 115 | .set(DDS_SUBSCRIPTION_MATCHED_STATUS_ID); 116 | 117 | assert_eq!(true, status.is_set(DDS_INCONSISTENT_TOPIC_STATUS_ID)); 118 | assert_eq!(true, status.is_set(DDS_OFFERED_DEADLINE_MISSED_STATUS_ID)); 119 | assert_eq!(true, status.is_set(DDS_SUBSCRIPTION_MATCHED_STATUS_ID)); 120 | assert_eq!(false, status.is_set(DDS_SAMPLE_REJECTED_STATUS_ID)); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/basic_tests.rs: -------------------------------------------------------------------------------- 1 | /* 2 | use cyclonedds_rs::{ 3 | self, dds_api, dds_topic::DdsTopic, DdsListener, DdsQos, DdsReader, DdsStatus, DdsWriter, 4 | Entity, 5 | }; 6 | 7 | use helloworld_data; 8 | 9 | use std::ffi::{CStr, CString}; 10 | 11 | use std::sync::mpsc; 12 | use std::sync::mpsc::{Receiver, Sender}; 13 | 14 | /// Simple hello world test. Sending and receiving one message 15 | #[test] 16 | fn hello_world_idl_test() { 17 | let receiver = std::thread::spawn(|| subscriber()); 18 | 19 | let message_string = CString::new("Hello from DDS Cyclone Rust") 20 | .expect("Unable to create CString") 21 | .into_raw(); 22 | 23 | let participant = cyclonedds_rs::DdsParticipant::create(None, None, None).unwrap(); 24 | 25 | // The topic is typed by the generated types in the IDL crate. 26 | let topic: DdsTopic = 27 | DdsTopic::create(&participant, "HelloWorldData_Msg", None, None) 28 | .expect("Unable to create topic"); 29 | 30 | let mut qos = DdsQos::create().unwrap(); 31 | qos.set_history( 32 | cyclonedds_rs::dds_qos::dds_history_kind::DDS_HISTORY_KEEP_LAST, 33 | 1, 34 | ); 35 | qos.set_resource_limits(10, 1, 10); 36 | 37 | let mut writer = DdsWriter::create(&participant, &topic, Some(&qos), None).unwrap(); 38 | 39 | let mut count = 0; 40 | 41 | if let Ok(()) = dds_api::dds_set_status_mask( 42 | writer.entity(), 43 | DdsStatus::default().set(dds_api::DDS_PUBLICATION_MATCHED_STATUS_ID), 44 | ) { 45 | loop { 46 | count = count + 1; 47 | if count > 500 { 48 | panic!("timeout waiting for publication matched") 49 | } 50 | if let Ok(status) = dds_api::dds_get_status_changes(writer.entity()) { 51 | if status.is_set(dds_api::DDS_PUBLICATION_MATCHED_STATUS_ID) { 52 | println!("Publication matched"); 53 | break; 54 | } 55 | 56 | std::thread::sleep(std::time::Duration::from_millis(20)); 57 | } else { 58 | panic!("dds_get_status failed"); 59 | } 60 | } 61 | } else { 62 | panic!("Unable to set status mask"); 63 | } 64 | 65 | let msg = helloworld_data::HelloWorldData::Msg { 66 | userID: 1, 67 | message: message_string, 68 | }; 69 | println!("Writing: {}", msg.userID); 70 | writer.write(&msg).unwrap(); 71 | 72 | receiver.join().unwrap(); 73 | } 74 | 75 | fn subscriber() { 76 | let participant = cyclonedds_rs::DdsParticipant::create(None, None, None).unwrap(); 77 | // The topic is typed by the generated types in the IDL crate. 78 | let topic: DdsTopic = 79 | DdsTopic::create(&participant, "HelloWorldData_Msg", None, None) 80 | .expect("Unable to create topic"); 81 | 82 | let (tx, rx): (Sender, Receiver) = mpsc::channel(); 83 | 84 | let mut qos = DdsQos::create().unwrap(); 85 | qos.set_history( 86 | cyclonedds_rs::dds_qos::dds_history_kind::DDS_HISTORY_KEEP_LAST, 87 | 1, 88 | ); 89 | 90 | let listener = DdsListener::new() 91 | .on_subscription_matched(move |_, _| { 92 | println!("Subscription matched"); 93 | }) 94 | .on_data_available(move |entity| { 95 | println!("Data on reader"); 96 | tx.send(0).unwrap(); 97 | // you could call read here, but then you need to use the unsafe read function exported 98 | // by cyclonedds-sys. 99 | /* 100 | // cyclonedds_sys::read is unsafe. 101 | unsafe { 102 | if let Ok(msg) = 103 | cyclonedds_sys::read::(&entity) 104 | { 105 | let msg = msg.as_slice(); 106 | println!("Received {} messages", msg.len()); 107 | 108 | println!("Received message : {}", msg[0].userID); 109 | assert_eq!(1, msg[0].userID); 110 | assert_eq!( 111 | CStr::from_ptr(msg[0].message), 112 | CStr::from_bytes_with_nul("Hello from DDS Cyclone Rust\0".as_bytes()) 113 | .unwrap() 114 | ); 115 | tx.send(0).unwrap(); 116 | } else { 117 | println!("Error reading"); 118 | } 119 | } 120 | */ 121 | }) 122 | .hook(); 123 | 124 | if let Ok(mut reader) = DdsReader::create(&participant, &topic, Some(&qos), None) { 125 | reader 126 | .set_listener(listener) 127 | .expect("Unable to set listener"); 128 | 129 | let id = rx.recv().unwrap(); 130 | if let Ok(msg) = reader.take() { 131 | let msg = msg.as_slice(); 132 | println!("Received {} messages", msg.len()); 133 | 134 | println!("Received message : {}", msg[0].userID); 135 | assert_eq!(1, msg[0].userID); 136 | assert_eq!( 137 | unsafe { CStr::from_ptr(msg[0].message) }, 138 | CStr::from_bytes_with_nul("Hello from DDS Cyclone Rust\0".as_bytes()).unwrap() 139 | ); 140 | } else { 141 | println!("Error reading"); 142 | } 143 | println!("Received :{} completed", id); 144 | let ten_millis = std::time::Duration::from_millis(100); 145 | std::thread::sleep(ten_millis); 146 | } else { 147 | panic!("Unable to create reader"); 148 | }; 149 | } 150 | */ 151 | -------------------------------------------------------------------------------- /src/dds_topic.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use crate::{dds_listener::DdsListener, dds_participant::DdsParticipant, dds_qos::DdsQos, Entity}; 18 | 19 | use std::convert::From; 20 | use std::ffi::CString; 21 | use std::marker::PhantomData; 22 | 23 | use crate::serdes::{SerType, TopicType}; 24 | pub use cyclonedds_sys::{ddsi_sertype, DDSError, DdsEntity}; 25 | 26 | pub struct TopicBuilder { 27 | maybe_qos: Option, 28 | maybe_listener: Option, 29 | topic_name: String, 30 | phantom: PhantomData, 31 | } 32 | 33 | impl TopicBuilder 34 | where 35 | T: TopicType, 36 | { 37 | pub fn new() -> Self { 38 | Self { 39 | maybe_qos: None, 40 | maybe_listener: None, 41 | topic_name: T::topic_name(None), 42 | phantom: PhantomData, 43 | } 44 | } 45 | 46 | pub fn with_name(mut self, name: String) -> Self { 47 | self.topic_name = name; 48 | self 49 | } 50 | 51 | pub fn with_name_prefix(mut self, mut prefix_name: String) -> Self { 52 | prefix_name.push_str(self.topic_name.as_str()); 53 | self.topic_name = prefix_name; 54 | self 55 | } 56 | 57 | pub fn with_qos(mut self, qos: DdsQos) -> Self { 58 | self.maybe_qos = Some(qos); 59 | self 60 | } 61 | 62 | pub fn with_listener(mut self, listener: DdsListener) -> Self { 63 | self.maybe_listener = Some(listener); 64 | self 65 | } 66 | 67 | pub fn create(self, participant: &DdsParticipant) -> Result, DDSError> { 68 | DdsTopic::::create( 69 | participant, 70 | self.topic_name.as_str(), 71 | self.maybe_qos, 72 | self.maybe_listener, 73 | ) 74 | } 75 | } 76 | 77 | pub struct DdsTopic(DdsEntity, PhantomData, Option); 78 | 79 | impl DdsTopic 80 | where 81 | T: std::marker::Sized + TopicType, 82 | { 83 | pub fn create( 84 | participant: &DdsParticipant, 85 | name: &str, 86 | maybe_qos: Option, 87 | maybe_listener: Option, 88 | ) -> Result { 89 | let t = SerType::::new(); 90 | let mut t = SerType::into_sertype(t); 91 | let tt = &mut t as *mut *mut ddsi_sertype; 92 | 93 | unsafe { 94 | let strname = CString::new(name).expect("CString::new failed"); 95 | let topic = cyclonedds_sys::dds_create_topic_sertype( 96 | participant.entity().entity(), 97 | strname.as_ptr(), 98 | tt, 99 | maybe_qos.map_or(std::ptr::null(), |q| q.into()), 100 | maybe_listener 101 | .as_ref() 102 | .map_or(std::ptr::null(), |l| l.into()), 103 | std::ptr::null_mut(), 104 | ); 105 | 106 | if topic >= 0 { 107 | Ok(DdsTopic(DdsEntity::new(topic), PhantomData, maybe_listener)) 108 | } else { 109 | Err(DDSError::from(topic)) 110 | } 111 | } 112 | } 113 | } 114 | 115 | impl Entity for DdsTopic 116 | where 117 | T: std::marker::Sized + TopicType, 118 | { 119 | fn entity(&self) -> &DdsEntity { 120 | &self.0 121 | } 122 | } 123 | 124 | impl Clone for DdsTopic 125 | where 126 | T: std::marker::Sized + TopicType, 127 | { 128 | fn clone(&self) -> Self { 129 | Self(self.0.clone(), PhantomData, self.2.clone()) 130 | } 131 | } 132 | 133 | #[cfg(test)] 134 | mod test { 135 | use super::*; 136 | use crate::SampleBuffer; 137 | use crate::{DdsPublisher, DdsWriter}; 138 | use cdds_derive::Topic; 139 | use serde_derive::{Deserialize, Serialize}; 140 | use std::sync::Arc; 141 | #[test] 142 | fn test_topic_creation() { 143 | #[derive(Default, Deserialize, Serialize, Topic)] 144 | struct MyTopic { 145 | #[topic_key] 146 | a: u32, 147 | b: u32, 148 | c: String, 149 | d: u32, 150 | } 151 | 152 | assert_eq!( 153 | MyTopic::topic_name(None), 154 | String::from("/dds_topic/test/test_topic_creation/MyTopic") 155 | ); 156 | assert_eq!( 157 | MyTopic::topic_name(Some("prefix")), 158 | String::from("prefix/dds_topic/test/test_topic_creation/MyTopic") 159 | ); 160 | 161 | let participant = DdsParticipant::create(None, None, None).unwrap(); 162 | let topic = MyTopic::create_topic(&participant, None, None, None).unwrap(); 163 | let publisher = 164 | DdsPublisher::create(&participant, None, None).expect("Unable to create publisher"); 165 | let mut writer = DdsWriter::create(&publisher, topic, None, None).unwrap(); 166 | 167 | // MyTopic::create_writer() 168 | 169 | let data = Arc::new(MyTopic { 170 | a: 1, 171 | b: 32, 172 | c: "my_data_sample".to_owned(), 173 | d: 546, 174 | }); 175 | 176 | writer.write(data).unwrap(); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/dds_qos.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use cyclonedds_sys::{dds_qos_t, *}; 18 | use std::clone::Clone; 19 | use std::convert::From; 20 | 21 | pub use cyclonedds_sys::{ 22 | dds_destination_order_kind, dds_durability_kind, dds_duration_t, dds_history_kind, 23 | dds_ignorelocal_kind, dds_liveliness_kind, dds_ownership_kind, 24 | dds_presentation_access_scope_kind, dds_reliability_kind, 25 | }; 26 | 27 | /// Safety Check: 28 | /// The dds_qos_t pointer is not accesible externally. I'm assuming the Qos structure created 29 | /// by Cyclone is Sendable here. 30 | unsafe impl Send for DdsQos{} 31 | 32 | #[derive(Debug)] 33 | pub struct DdsQos(*mut dds_qos_t); 34 | 35 | impl DdsQos { 36 | pub fn create() -> Result { 37 | unsafe { 38 | let p = cyclonedds_sys::dds_create_qos(); 39 | if !p.is_null() { 40 | Ok(DdsQos(p)) 41 | } else { 42 | Err(DDSError::OutOfResources) 43 | } 44 | } 45 | } 46 | 47 | pub fn merge(&mut self, src: &Self) { 48 | unsafe { 49 | dds_merge_qos(self.0, src.0); 50 | } 51 | } 52 | 53 | pub fn set_durability( &mut self, durability: dds_durability_kind) -> &mut Self { 54 | unsafe { 55 | dds_qset_durability(self.0, durability); 56 | } 57 | self 58 | } 59 | 60 | pub fn set_history(&mut self, history: dds_history_kind, depth: i32) -> &mut Self { 61 | unsafe { 62 | dds_qset_history(self.0, history, depth); 63 | } 64 | self 65 | } 66 | 67 | pub fn set_resource_limits( 68 | &mut self, 69 | max_samples: i32, 70 | max_instances: i32, 71 | max_samples_per_instance: i32, 72 | ) -> &mut Self { 73 | unsafe { 74 | dds_qset_resource_limits(self.0, max_samples, max_instances, max_samples_per_instance); 75 | } 76 | self 77 | } 78 | 79 | pub fn set_presentation( 80 | &mut self, 81 | access_scope: dds_presentation_access_scope_kind, 82 | coherent_access: bool, 83 | ordered_access: bool, 84 | ) -> &mut Self { 85 | unsafe { 86 | dds_qset_presentation(self.0, access_scope, coherent_access, ordered_access); 87 | } 88 | self 89 | } 90 | 91 | pub fn set_lifespan(&mut self, lifespan: std::time::Duration) -> &mut Self { 92 | unsafe { 93 | dds_qset_lifespan(self.0, lifespan.as_nanos() as i64); 94 | } 95 | self 96 | } 97 | 98 | pub fn set_deadline(&mut self, deadline: std::time::Duration) -> &mut Self { 99 | unsafe { 100 | dds_qset_deadline(self.0, deadline.as_nanos() as i64); 101 | } 102 | self 103 | } 104 | 105 | pub fn set_latency_budget( &mut self, duration: dds_duration_t) -> &mut Self { 106 | unsafe { 107 | dds_qset_latency_budget(self.0, duration); 108 | } 109 | self 110 | } 111 | 112 | pub fn set_ownership(&mut self, kind: dds_ownership_kind) -> &mut Self { 113 | unsafe { 114 | dds_qset_ownership(self.0, kind); 115 | } 116 | self 117 | } 118 | 119 | pub fn set_ownership_strength(&mut self, value: i32) -> &mut Self { 120 | unsafe { 121 | dds_qset_ownership_strength(self.0, value); 122 | } 123 | self 124 | } 125 | 126 | pub fn set_liveliness( &mut self, kind: dds_liveliness_kind, lease_duration: dds_duration_t) -> &mut Self { 127 | unsafe { 128 | dds_qset_liveliness(self.0, kind, lease_duration); 129 | } 130 | self 131 | } 132 | 133 | pub fn set_time_based_filter( &mut self, minimum_separation: dds_duration_t) -> &mut Self { 134 | unsafe { 135 | dds_qset_time_based_filter(self.0, minimum_separation); 136 | } 137 | self 138 | } 139 | 140 | pub fn set_reliability( 141 | &mut self, 142 | kind: dds_reliability_kind, 143 | max_blocking_time: std::time::Duration, 144 | ) -> &mut Self { 145 | unsafe { 146 | dds_qset_reliability(self.0, kind, max_blocking_time.as_nanos() as i64); 147 | } 148 | self 149 | } 150 | 151 | pub fn set_transport_priority(&mut self, value: i32) -> &mut Self { 152 | unsafe { 153 | dds_qset_transport_priority(self.0, value); 154 | } 155 | self 156 | } 157 | 158 | pub fn set_destination_order( &mut self, kind: dds_destination_order_kind) -> &mut Self { 159 | unsafe { 160 | dds_qset_destination_order(self.0, kind); 161 | } 162 | self 163 | } 164 | 165 | pub fn set_writer_data_lifecycle(&mut self, autodispose: bool) -> &mut Self { 166 | unsafe { 167 | dds_qset_writer_data_lifecycle(self.0, autodispose); 168 | } 169 | self 170 | } 171 | 172 | pub fn set_reader_data_lifecycle( 173 | &mut self, 174 | autopurge_nowriter_samples_delay: dds_duration_t, 175 | autopurge_disposed_samples_delay: dds_duration_t, 176 | ) -> &mut Self { 177 | unsafe { 178 | dds_qset_reader_data_lifecycle( 179 | self.0, 180 | autopurge_nowriter_samples_delay, 181 | autopurge_disposed_samples_delay, 182 | ); 183 | } 184 | self 185 | } 186 | 187 | pub fn set_durability_service( 188 | &mut self, 189 | service_cleanup_delay: dds_duration_t, 190 | history_kind: dds_history_kind, 191 | history_depth: i32, 192 | max_samples: i32, 193 | max_instances: i32, 194 | max_samples_per_instance: i32, 195 | ) -> &mut Self { 196 | unsafe { 197 | dds_qset_durability_service( 198 | self.0, 199 | service_cleanup_delay, 200 | history_kind, 201 | history_depth, 202 | max_samples, 203 | max_instances, 204 | max_samples_per_instance, 205 | ); 206 | } 207 | self 208 | } 209 | 210 | pub fn set_ignorelocal(&mut self, ignore: dds_ignorelocal_kind) -> &mut Self { 211 | unsafe { 212 | dds_qset_ignorelocal(self.0, ignore); 213 | } 214 | self 215 | } 216 | 217 | pub fn set_partition( &mut self, name: &std::ffi::CStr) -> &mut Self { 218 | unsafe { dds_qset_partition1(self.0, name.as_ptr()) } 219 | self 220 | } 221 | //TODO: Not implementing any getters for now 222 | } 223 | 224 | impl Default for DdsQos { 225 | fn default() -> Self { 226 | DdsQos::create().expect("Unable to create DdsQos") 227 | } 228 | } 229 | 230 | impl Drop for DdsQos { 231 | fn drop(&mut self) { 232 | if !self.0.is_null() { 233 | unsafe { dds_delete_qos(self.0) } 234 | } 235 | } 236 | } 237 | 238 | impl PartialEq for DdsQos { 239 | fn eq(&self, other: &Self) -> bool { 240 | unsafe { 241 | println!("Dropping"); 242 | dds_qos_equal(self.0, other.0) 243 | } 244 | } 245 | } 246 | 247 | impl Eq for DdsQos {} 248 | 249 | impl Clone for DdsQos { 250 | fn clone(&self) -> Self { 251 | unsafe { 252 | let q = dds_create_qos(); 253 | let err: DDSError = dds_copy_qos(q, self.0).into(); 254 | if let DDSError::DdsOk = err { 255 | DdsQos(q) 256 | } else { 257 | panic!("dds_copy_qos failed. Panicing as Clone should not fail"); 258 | } 259 | } 260 | } 261 | } 262 | 263 | impl From for *const dds_qos_t { 264 | fn from(mut qos: DdsQos) -> Self { 265 | let q = qos.0; 266 | // we need to forget the pointer here 267 | qos.0 = std::ptr::null_mut(); 268 | // setting to zero will ensure drop will not deallocate it 269 | q 270 | } 271 | } 272 | /* 273 | impl From<&mut DdsQos> for *const dds_qos_t { 274 | fn from(qos: &mut DdsQos) -> Self { 275 | let q = qos.0; 276 | // we need to forget the pointer here 277 | qos.0 = std::ptr::null_mut(); 278 | // setting to zero will ensure drop will not deallocate it 279 | q 280 | } 281 | } 282 | */ 283 | 284 | #[cfg(test)] 285 | mod dds_qos_tests { 286 | use super::*; 287 | 288 | #[test] 289 | fn test_create_qos() { 290 | if let Ok(_qos) = DdsQos::create() { 291 | } else { 292 | assert!(false); 293 | } 294 | } 295 | #[test] 296 | fn test_clone_qos() { 297 | if let Ok(qos) = DdsQos::create() { 298 | let _c = qos; 299 | } else { 300 | assert!(false); 301 | } 302 | } 303 | 304 | #[test] 305 | fn test_merge_qos() { 306 | if let Ok(mut qos) = DdsQos::create() { 307 | let c = qos.clone(); 308 | qos.merge(&c); 309 | } else { 310 | assert!(false); 311 | } 312 | } 313 | 314 | #[test] 315 | fn test_set() { 316 | if let Ok(mut qos) = DdsQos::create() { 317 | let _qos = qos.set_durability(dds_durability_kind::DDS_DURABILITY_VOLATILE) 318 | .set_history(dds_history_kind::DDS_HISTORY_KEEP_LAST, 3) 319 | .set_resource_limits(10, 1, 10) 320 | .set_presentation( 321 | dds_presentation_access_scope_kind::DDS_PRESENTATION_INSTANCE, 322 | false, 323 | false, 324 | ) 325 | .set_lifespan(std::time::Duration::from_nanos(100)) 326 | .set_deadline(std::time::Duration::from_nanos(100)) 327 | .set_latency_budget(1000) 328 | .set_ownership(dds_ownership_kind::DDS_OWNERSHIP_EXCLUSIVE) 329 | .set_ownership_strength(1000) 330 | .set_liveliness(dds_liveliness_kind::DDS_LIVELINESS_AUTOMATIC, 10000) 331 | .set_time_based_filter(1000) 332 | .set_reliability(dds_reliability_kind::DDS_RELIABILITY_RELIABLE, std::time::Duration::from_nanos(100)) 333 | .set_transport_priority(1000) 334 | .set_destination_order( 335 | dds_destination_order_kind::DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP, 336 | ) 337 | .set_writer_data_lifecycle(true) 338 | .set_reader_data_lifecycle(100, 100) 339 | .set_durability_service(0, dds_history_kind::DDS_HISTORY_KEEP_LAST, 3, 3, 3, 3) 340 | .set_partition(&std::ffi::CString::new("partition1").unwrap()); 341 | } else { 342 | assert!(false); 343 | } 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Sojan James 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /dds_derive/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Sojan James 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /dds_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Rust deserializer for CycloneDDS. (proc macro) 18 | // See discussion at https://github.com/eclipse-cyclonedds/cyclonedds/issues/830 19 | 20 | use proc_macro::TokenStream; 21 | use proc_macro2::{Span,}; 22 | use quote::quote; 23 | use syn::{Field, Ident, parse_macro_input}; 24 | 25 | #[proc_macro_derive(TopicFixedSize, attributes(topic_key, topic_key_enum))] 26 | pub fn derive_topic_fixed_size(item: TokenStream) -> TokenStream { 27 | derive_topic_impl(item, true) 28 | } 29 | 30 | #[proc_macro_derive(Topic, attributes(topic_key, topic_key_enum))] 31 | pub fn derive_topic(item: TokenStream) -> TokenStream { 32 | derive_topic_impl(item, false) 33 | } 34 | 35 | 36 | 37 | fn derive_topic_impl(item: TokenStream, is_fixed_size: bool) -> TokenStream { 38 | let topic_struct = parse_macro_input!(item as syn::ItemStruct); 39 | 40 | let mut ts = build_key_holder_struct(&topic_struct); 41 | let ts2 = create_keyhash_functions(&topic_struct, is_fixed_size); 42 | let ts3 = create_topic_functions(&topic_struct); 43 | 44 | ts.extend(ts2); 45 | ts.extend(ts3); 46 | 47 | //println!("KEYHOLDER:{:?}",ts.clone().to_string()); 48 | ts 49 | } 50 | 51 | ///Create a key holder struct from the given struct. The key 52 | ///fields will be included in this structure. The structure 53 | ///will be empty if there are no key fields. 54 | fn build_key_holder_struct(item : &syn::ItemStruct) -> TokenStream { 55 | let key_holder_struct = item; 56 | 57 | let mut holder_name = key_holder_struct.ident.to_string(); 58 | let fields = &key_holder_struct.fields; 59 | holder_name.push_str("KeyHolder_"); 60 | let holder_name = Ident::new(&holder_name, Span::call_site()); 61 | //key_holder_struct.ident = Ident::new(&holder_name,Span::call_site()); 62 | 63 | let mut field_idents = Vec::new(); 64 | let mut field_types = Vec::new(); 65 | let mut clone_or_into = Vec::new(); 66 | let mut ref_or_value = Vec::new(); 67 | let mut contained_types = Vec::new(); 68 | let mut variable_length = false; 69 | 70 | for field in fields { 71 | if is_key(field) { 72 | field_idents.push(field.ident.as_ref().unwrap().clone()); 73 | if is_primitive(field) || is_key_enum(field) { 74 | field_types.push(field.ty.clone()); 75 | clone_or_into.push(quote!{clone()}); 76 | ref_or_value.push(quote!{ }); 77 | if !variable_length { 78 | variable_length = is_variable_length(field); 79 | } 80 | } else { 81 | match field.ty.clone() { 82 | syn::Type::Path(mut type_path) => { 83 | // if the key is another structure (not a primitive), 84 | // there should be a key holder structure for it. 85 | // Change the type 86 | let last_segment= type_path.path.segments.last_mut().unwrap(); 87 | let mut ident_string = last_segment.ident.to_string(); 88 | ident_string.push_str("KeyHolder_"); 89 | let new_ident = Ident::new(&ident_string,Span::call_site()); 90 | //replace the ident with the new name 91 | last_segment.ident = new_ident; 92 | contained_types.push(syn::Type::Path(type_path.clone())); 93 | field_types.push(syn::Type::Path(type_path)); 94 | clone_or_into.push(quote!{into()}); 95 | ref_or_value.push(quote!{ &}); 96 | } 97 | syn::Type::Array( type_arr) => { 98 | if let syn::Type::Path( array_type_path) = *type_arr.elem { 99 | if is_primitive_type_path(&array_type_path) { 100 | field_types.push(field.ty.clone()); 101 | clone_or_into.push(quote!{clone()}); 102 | ref_or_value.push(quote!{ }); 103 | } else { 104 | panic!("Only primitive arrays are supported as keys"); 105 | } 106 | 107 | } else { 108 | panic!("Unsupported type for array"); 109 | } 110 | } 111 | _ => { 112 | panic!("Keys need to be primitives, Path or array of primitives or Path"); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | let item_ident = &item.ident; 120 | //println!("Filtered fields:{:?}", &filtered_fields); 121 | 122 | let ts = quote! { 123 | #[derive(Default, Deserialize, Serialize, PartialEq, Clone)] 124 | struct #holder_name { 125 | #(#field_idents:#field_types,)* 126 | } 127 | 128 | impl From<& #item_ident> for #holder_name { 129 | fn from(source: & #item_ident) -> Self { 130 | Self { 131 | #(#field_idents : (#ref_or_value source.#field_idents). #clone_or_into ,)* 132 | } 133 | } 134 | } 135 | 136 | impl #holder_name { 137 | const fn is_variable_length() -> bool { 138 | if !#variable_length { 139 | #(#contained_types :: is_variable_length()||)* false 140 | } else { 141 | true 142 | } 143 | } 144 | } 145 | 146 | }; 147 | 148 | ts.into() 149 | } 150 | 151 | // create the keyhash methods for this type 152 | fn create_keyhash_functions(item : &syn::ItemStruct, is_fixed_size: bool) -> TokenStream { 153 | let topic_key_ident = &item.ident; 154 | let topic_key_holder_ident = quote::format_ident!("{}KeyHolder_",&item.ident); 155 | 156 | let ts = quote!{ 157 | impl TopicType for #topic_key_ident { 158 | /// return the cdr encoding for the key. The encoded string includes the four byte 159 | /// encapsulation string. 160 | fn key_cdr(&self) -> Vec { 161 | let holder_struct : #topic_key_holder_ident = self.into(); 162 | 163 | let encoded = cdr::serialize::<_, _, cdr::CdrBe>(&holder_struct, cdr::Infinite).expect("Unable to serialize key"); 164 | encoded 165 | } 166 | 167 | fn is_fixed_size() -> bool { 168 | #is_fixed_size 169 | } 170 | 171 | fn has_key() -> bool { 172 | if std::mem::size_of::<#topic_key_holder_ident>() > 0 { 173 | true 174 | } else { 175 | false 176 | } 177 | } 178 | 179 | fn force_md5_keyhash() -> bool { 180 | #topic_key_holder_ident::is_variable_length() 181 | } 182 | } 183 | }; 184 | 185 | ts.into() 186 | 187 | } 188 | 189 | fn create_topic_functions(item : &syn::ItemStruct) -> TokenStream { 190 | let topic_key_ident = &item.ident; 191 | 192 | let ts = quote!{ 193 | impl #topic_key_ident { 194 | /// Create a topic using of this Type specifying the topic name 195 | /// 196 | /// # Arguments 197 | /// 198 | /// * `participant` - The participant handle onto which this topic should be created 199 | /// * `name` - The name of the topic 200 | /// * `maybe_qos` - A QoS structure for this topic. The Qos is optional 201 | /// * `maybe_listener` - A listener to use on this topic. The listener is optional 202 | /// 203 | pub fn create_topic_with_name( 204 | participant: &DdsParticipant, 205 | name: &str, 206 | maybe_qos: Option, 207 | maybe_listener: Option, 208 | ) -> Result, DDSError> { 209 | DdsTopic::::create(participant,name, maybe_qos,maybe_listener) 210 | } 211 | 212 | /// Create a topic of this Type using the default topic name. The default topic 213 | /// name is provided by the Self::topic_name function. 214 | /// # Arguments 215 | /// 216 | /// * `participant` - The participant handle onto which this topic should be created 217 | /// * `maybe_topic_prefix` - An additional prefix to be added to the topic name. This can be None 218 | /// * `maybe_qos` - A QoS structure for this topic. The Qos is optional 219 | /// * `maybe_listener` - A listener to use on this topic. The listener is optional 220 | /// 221 | pub fn create_topic( 222 | participant: &DdsParticipant, 223 | maybe_topic_prefix: Option<&str>, 224 | maybe_qos: Option, 225 | maybe_listener: Option, 226 | ) -> Result, DDSError> { 227 | let name = #topic_key_ident::topic_name(maybe_topic_prefix); 228 | DdsTopic::::create(participant,&name, maybe_qos,maybe_listener) 229 | } 230 | 231 | /// Create a sample buffer for storing an array of samples 232 | /// You can pass the sample buffer into a read to read multiple 233 | /// samples. Multiple samples are useful when you have one or more 234 | /// keys in your topic structure. Each value of the key will result in 235 | /// the storage of another sample. 236 | pub fn create_sample_buffer(len: usize) -> SampleBuffer<#topic_key_ident> { 237 | SampleBuffer::new(len) 238 | } 239 | } 240 | }; 241 | 242 | ts.into() 243 | } 244 | 245 | /* 246 | fn struct_has_key(it: &ItemStruct) -> bool { 247 | for field in &it.fields { 248 | if is_key(field) { 249 | return true 250 | } 251 | } 252 | false 253 | } 254 | */ 255 | 256 | fn is_key(field : &Field) -> bool { 257 | for attr in &field.attrs { 258 | if let Some(ident) = attr.path.get_ident() { 259 | if ident == "topic_key" || ident == "topic_key_enum"{ 260 | return true 261 | } 262 | } 263 | } 264 | false 265 | } 266 | 267 | // There is no way to find out if the field is an enum or a struct, 268 | // so we need a special marker to indicate key enums 269 | // which we will treat like primitives. 270 | fn is_key_enum(field : &Field) -> bool { 271 | for attr in &field.attrs { 272 | if let Some(ident) = attr.path.get_ident() { 273 | if ident == "topic_key_enum" { 274 | return true 275 | } 276 | } 277 | } 278 | false 279 | } 280 | 281 | fn is_primitive_type_path(type_path : &syn::TypePath) -> bool { 282 | if type_path.path.is_ident("bool") || 283 | type_path.path.is_ident("i8") || 284 | type_path.path.is_ident("i16") || 285 | type_path.path.is_ident("i32") || 286 | type_path.path.is_ident("i64") || 287 | type_path.path.is_ident("i128") || 288 | type_path.path.is_ident("isize") || 289 | type_path.path.is_ident("u8") || 290 | type_path.path.is_ident("u16") || 291 | type_path.path.is_ident("u32") || 292 | type_path.path.is_ident("u64") || 293 | type_path.path.is_ident("u128") || 294 | type_path.path.is_ident("usize") || 295 | type_path.path.is_ident("f32") || 296 | type_path.path.is_ident("f64") || 297 | type_path.path.is_ident("String") 298 | { 299 | true 300 | } else { 301 | false 302 | } 303 | } 304 | 305 | // check if a field is of a primitive type. We assume anything not primitive 306 | // is a struct 307 | fn is_primitive(field:&Field) -> bool { 308 | if let syn::Type::Path(type_path) = &field.ty { 309 | is_primitive_type_path(type_path) 310 | } else { 311 | false 312 | } 313 | } 314 | 315 | 316 | // Is the length of the underlying type variable. This is needed 317 | // According to the DDSI RTPS spec, the potential length of a field 318 | // must be checked to decide whether to use md5 checksum for the key 319 | // hash. If a String (or Vec) is used as a key_field, then the 320 | // length is variable. 321 | fn is_variable_length(field:&Field) -> bool { 322 | if let syn::Type::Path(type_path) = &field.ty { 323 | if type_path.path.is_ident("Vec") || 324 | type_path.path.is_ident("String") 325 | { 326 | true 327 | } else { 328 | false 329 | } 330 | } else { 331 | false 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/dds_writer.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use cyclonedds_sys::*; 18 | use std::convert::From; 19 | use std::ffi::c_void; 20 | use std::ptr::NonNull; 21 | 22 | pub use cyclonedds_sys::{ DdsEntity}; 23 | use std::marker::PhantomData; 24 | use crate::SampleBuffer; 25 | 26 | use crate::{dds_listener::DdsListener, dds_qos::DdsQos, dds_topic::DdsTopic, DdsWritable, Entity}; 27 | use crate::serdes::{Sample, TopicType}; 28 | 29 | pub struct WriterBuilder { 30 | maybe_qos: Option, 31 | maybe_listener: Option, 32 | phantom : PhantomData, 33 | } 34 | 35 | impl WriterBuilder where T: TopicType { 36 | pub fn new() -> Self { 37 | Self { 38 | maybe_qos: None, 39 | maybe_listener: None, 40 | phantom: PhantomData, 41 | } 42 | } 43 | 44 | pub fn with_qos(mut self, qos : DdsQos) -> Self { 45 | self.maybe_qos = Some(qos); 46 | self 47 | } 48 | 49 | pub fn with_listener(mut self, listener : DdsListener) -> Self { 50 | self.maybe_listener = Some(listener); 51 | self 52 | } 53 | 54 | pub fn create(self, 55 | entity: &dyn DdsWritable, 56 | topic: DdsTopic) -> Result, DDSError> { 57 | DdsWriter::create(entity, topic, self.maybe_qos, self.maybe_listener) 58 | } 59 | } 60 | 61 | pub enum LoanedInner { 62 | Uninitialized(NonNull, DdsEntity), 63 | Initialized(NonNull, DdsEntity), 64 | Empty, 65 | } 66 | 67 | pub struct Loaned { 68 | inner : LoanedInner 69 | } 70 | 71 | impl Loaned 72 | where T: Sized + TopicType { 73 | pub fn as_mut_ptr(&mut self) -> Option<*mut T> { 74 | match self.inner { 75 | LoanedInner::Uninitialized(p, _) => Some(p.as_ptr()), 76 | LoanedInner::Initialized(p, _) => Some(p.as_ptr()), 77 | LoanedInner::Empty => None, 78 | } 79 | } 80 | 81 | pub fn assume_init(mut self) -> Self { 82 | match &mut self.inner { 83 | LoanedInner::Uninitialized(p, e) => Self{inner : LoanedInner::Initialized(*p, e.clone())}, 84 | LoanedInner::Initialized(p, e) => Self{inner : LoanedInner::Initialized(*p, e.clone())}, 85 | LoanedInner::Empty => Self{inner : LoanedInner::Empty}, 86 | } 87 | } 88 | } 89 | 90 | impl Drop for Loaned 91 | where T : Sized + TopicType { 92 | fn drop(&mut self) { 93 | let (mut p_sample, entity) = match &mut self.inner { 94 | LoanedInner::Uninitialized(p, entity) => (p.as_ptr(),Some(entity)), 95 | LoanedInner::Initialized(p, entity) => (p.as_ptr(),Some(entity)), 96 | LoanedInner::Empty => (std::ptr::null_mut(), None), 97 | }; 98 | 99 | if let Some(entity) = entity { 100 | let voidpp:*mut *mut T= &mut p_sample; 101 | let voidpp = voidpp as *mut *mut c_void; 102 | unsafe {dds_return_loan(entity.entity(),voidpp,1)}; 103 | } 104 | } 105 | } 106 | 107 | #[derive(Clone)] 108 | pub struct DdsWriter( 109 | DdsEntity, 110 | Option, 111 | PhantomData, 112 | ); 113 | 114 | impl<'a, T> DdsWriter 115 | where 116 | T: Sized + TopicType, 117 | { 118 | pub fn create( 119 | entity: &dyn DdsWritable, 120 | topic: DdsTopic, 121 | maybe_qos: Option, 122 | maybe_listener: Option, 123 | ) -> Result { 124 | unsafe { 125 | let w = dds_create_writer( 126 | entity.entity().entity(), 127 | topic.entity().entity(), 128 | maybe_qos.map_or(std::ptr::null(), |q| q.into()), 129 | maybe_listener 130 | .as_ref() 131 | .map_or(std::ptr::null(), |l| l.into()), 132 | ); 133 | 134 | if w >= 0 { 135 | Ok(DdsWriter( 136 | DdsEntity::new(w), 137 | maybe_listener, 138 | PhantomData, 139 | )) 140 | } else { 141 | Err(DDSError::from(w)) 142 | } 143 | } 144 | } 145 | 146 | pub fn write_to_entity(entity: &DdsEntity, msg: std::sync::Arc) -> Result<(), DDSError> { 147 | unsafe { 148 | let sample = Sample::::from(msg); 149 | let sample = &sample as *const Sample; 150 | let sample = sample as *const ::std::os::raw::c_void; 151 | let ret = dds_write(entity.entity(), sample); 152 | if ret >= 0 { 153 | Ok(()) 154 | } else { 155 | Err(DDSError::from(ret)) 156 | } 157 | } 158 | } 159 | 160 | pub fn write(&mut self, msg: std::sync::Arc) -> Result<(), DDSError> { 161 | Self::write_to_entity(&self.0, msg) 162 | 163 | } 164 | 165 | // Loan memory buffers for zero copy operation. Only supported for fixed size types 166 | pub fn loan(&mut self) -> Result, DDSError> { 167 | 168 | if !T::is_fixed_size() { 169 | // Loaning is not supported for types that are not fixed size 170 | return Err(DDSError::Unsupported) 171 | } 172 | 173 | let mut p_sample : *mut T = std::ptr::null_mut(); 174 | let voidpp:*mut *mut T= &mut p_sample; 175 | let voidpp = voidpp as *mut *mut c_void; 176 | let res = unsafe { 177 | dds_loan_sample(self.0.entity(), voidpp) 178 | }; 179 | if res == 0 { 180 | Ok(Loaned { inner: LoanedInner::Uninitialized( NonNull::new(p_sample).unwrap(), self.entity().clone()) }) 181 | } else { 182 | Err(DDSError::from(res)) 183 | } 184 | } 185 | 186 | // Return the loaned buffer. If the buffer was initialized, then write the data to be published 187 | pub fn return_loan(&mut self, mut buffer: Loaned) -> Result<(),DDSError> { 188 | let res = match &mut buffer.inner { 189 | 190 | LoanedInner::Uninitialized(p,entity) => { 191 | let mut p_sample = p.as_ptr(); 192 | let voidpp:*mut *mut T= &mut p_sample; 193 | let voidpp = voidpp as *mut *mut c_void; 194 | unsafe {dds_return_loan(entity.entity(),voidpp,1)} 195 | }, 196 | LoanedInner::Initialized(p, entity) => { 197 | let p_sample = p.as_ptr(); 198 | unsafe {dds_write(entity.entity(), p_sample as * const c_void)} 199 | } 200 | LoanedInner::Empty => 0, 201 | }; 202 | 203 | if res == 0 { 204 | Ok(()) 205 | } else { 206 | Err(DDSError::from(res)) 207 | } 208 | 209 | } 210 | 211 | pub fn set_listener(&mut self, listener: DdsListener) -> Result<(), DDSError> { 212 | unsafe { 213 | let refl = &listener; 214 | let rc = dds_set_listener(self.0.entity(), refl.into()); 215 | if rc == 0 { 216 | self.1 = Some(listener); 217 | Ok(()) 218 | } else { 219 | Err(DDSError::from(rc)) 220 | } 221 | } 222 | } 223 | } 224 | 225 | impl<'a, T> Entity for DdsWriter 226 | where 227 | T: std::marker::Sized + TopicType, 228 | { 229 | fn entity(&self) -> &DdsEntity { 230 | &self.0 231 | } 232 | } 233 | 234 | impl<'a, T> Drop for DdsWriter 235 | where 236 | T: std::marker::Sized + TopicType, 237 | { 238 | fn drop(&mut self) { 239 | unsafe { 240 | let ret: DDSError = cyclonedds_sys::dds_delete(self.0.entity()).into(); 241 | if DDSError::DdsOk != ret && DDSError::AlreadyDeleted != ret { 242 | //panic!("cannot delete Writer: {}", ret); 243 | } 244 | } 245 | } 246 | } 247 | 248 | #[cfg(test)] 249 | mod test { 250 | use core::panic; 251 | use std::{time::Duration, sync::Arc, ops::Deref}; 252 | 253 | use crate::{DdsParticipant, DdsSubscriber, DdsReader}; 254 | use super::*; 255 | use crate::{DdsPublisher, DdsWriter}; 256 | 257 | use cdds_derive::{Topic, TopicFixedSize}; 258 | use serde_derive::{Deserialize, Serialize}; 259 | use tokio::runtime::Runtime; 260 | 261 | const cyclone_shm_config : &str = r###" 262 | 265 | 266 | 267 | true 268 | info 269 | 270 | 271 | "###; 272 | 273 | 274 | #[repr(C)] 275 | #[derive(Serialize,Deserialize,Debug, PartialEq, Clone)] 276 | enum Position { 277 | Front, 278 | Back, 279 | } 280 | 281 | impl Default for Position { 282 | fn default() -> Self { 283 | Self::Front 284 | } 285 | } 286 | 287 | #[derive(Serialize,Deserialize,TopicFixedSize, Debug, PartialEq)] 288 | struct TestTopic { 289 | a : u32, 290 | b : u16, 291 | c: [u8;10], 292 | d : [u8;15], 293 | #[topic_key] 294 | e : u32, 295 | #[topic_key_enum] 296 | pos : Position, 297 | } 298 | 299 | impl Default for TestTopic { 300 | fn default() -> Self { 301 | Self { 302 | a : 10, 303 | b : 20, 304 | c : [0,0,0,0,0,0,0,0,0,0], 305 | d : [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5], 306 | e : 0, 307 | pos : Position::default(), 308 | } 309 | } 310 | } 311 | 312 | #[derive(Serialize,Deserialize,Topic, Debug, PartialEq)] 313 | struct AnotherTopic { 314 | pub value : u32, 315 | pub name : String, 316 | pub arr : [String;2], 317 | pub vec : Vec, 318 | #[topic_key] 319 | pub key : u32, 320 | } 321 | 322 | impl Default for AnotherTopic { 323 | fn default() -> Self { 324 | assert!(Self::has_key()); 325 | Self { 326 | value : 42, 327 | name : "the answer".to_owned(), 328 | arr : ["one".to_owned(), "two".to_owned()], 329 | vec : vec!["Hello".to_owned(), "world".to_owned()], 330 | key : 0, 331 | } 332 | } 333 | } 334 | 335 | //#[test] 336 | fn test_loan() { 337 | // Make sure iox-roudi is running 338 | std::env::set_var("CYCLONEDDS_URI", cyclone_shm_config); 339 | 340 | let participant = DdsParticipant::create(None, None, None).unwrap(); 341 | 342 | let topic = TestTopic::create_topic(&participant, Some("test_topic"), None, None).unwrap(); 343 | let another_topic = AnotherTopic::create_topic(&participant, None, None, None).unwrap(); 344 | 345 | let publisher = DdsPublisher::create(&participant, None, None).unwrap(); 346 | 347 | let mut writer = DdsWriter::create(&publisher, topic.clone(), None, None).unwrap(); 348 | let mut another_writer = DdsWriter::create(&publisher, another_topic.clone(), None, None).unwrap(); 349 | 350 | // this writer does not have a fixed size. Loan should fail 351 | 352 | if let Ok(r) = another_writer.loan() { 353 | panic!("This must fail"); 354 | } 355 | 356 | let subscriber = DdsSubscriber::create(&participant, None, None).unwrap(); 357 | let reader = DdsReader::create_async(&subscriber, topic, None).unwrap(); 358 | let another_reader = DdsReader::create_async(&subscriber, another_topic, None).unwrap(); 359 | 360 | let rt = Runtime::new().unwrap(); 361 | 362 | let _result = rt.block_on(async { 363 | 364 | 365 | 366 | let _another_task = tokio::spawn(async move { 367 | let mut samples = TestTopic::create_sample_buffer(5); 368 | if let Ok(t) = reader.take(&mut samples).await { 369 | assert_eq!(t,1); 370 | for s in samples.iter() { 371 | 372 | println!("Got sample {:?}", s); 373 | } 374 | 375 | } else { 376 | panic!("reader get failed"); 377 | } 378 | }); 379 | 380 | // add a delay to make sure the data is not ready immediately 381 | tokio::time::sleep(std::time::Duration::from_millis(100)).await; 382 | 383 | let mut loaned = writer.loan().unwrap(); 384 | 385 | let ptr = loaned.as_mut_ptr().unwrap(); 386 | let topic = TestTopic::default(); 387 | 388 | unsafe {ptr.write(topic)}; 389 | let loaned = loaned.assume_init(); 390 | writer.return_loan(loaned).unwrap(); 391 | 392 | tokio::time::sleep(std::time::Duration::from_millis(300)).await; 393 | 394 | }); 395 | 396 | } 397 | 398 | 399 | 400 | } 401 | -------------------------------------------------------------------------------- /src/dds_reader.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | use cyclonedds_sys::*; 18 | use std::convert::From; 19 | use std::future::Future; 20 | use std::os::raw::c_void; 21 | use std::pin::Pin; 22 | use std::sync::{Arc, Mutex}; 23 | use std::task::{Context, Poll, Waker}; 24 | //use std::convert::TryInto; 25 | 26 | pub use cyclonedds_sys::{DdsDomainId, DdsEntity}; 27 | 28 | use std::marker::PhantomData; 29 | 30 | 31 | use crate::dds_listener::DdsListenerBuilder; 32 | use crate::error::ReaderError; 33 | use crate::{dds_listener::DdsListener, dds_qos::DdsQos, dds_topic::DdsTopic, DdsReadable, Entity}; 34 | use crate::serdes::{TopicType, SampleBuffer}; 35 | 36 | /// Builder structure for reader 37 | pub struct ReaderBuilder { 38 | maybe_qos: Option, 39 | maybe_listener: Option, 40 | is_async : bool, 41 | phantom : PhantomData, 42 | } 43 | 44 | impl ReaderBuilder where T: TopicType { 45 | pub fn new() -> Self { 46 | Self { 47 | maybe_qos: None, 48 | maybe_listener: None, 49 | is_async : false, 50 | phantom: PhantomData, 51 | } 52 | } 53 | /// Create a reader with async support. If this is enabled, 54 | /// the builder creates listeners internally. Any listener 55 | /// passed separately via the `with_listener` api will be 56 | /// ignored. 57 | pub fn as_async(mut self) -> Self { 58 | self.is_async = true; 59 | self 60 | } 61 | 62 | /// Create a reader with the specified Qos 63 | pub fn with_qos(mut self, qos : DdsQos) -> Self { 64 | self.maybe_qos = Some(qos); 65 | self 66 | } 67 | 68 | /// Created a reader with the specified listener. 69 | /// Note that this is ignored if an async reader 70 | /// is created. 71 | pub fn with_listener(mut self, listener : DdsListener) -> Self { 72 | self.maybe_listener = Some(listener); 73 | self 74 | } 75 | 76 | pub fn create(self, 77 | entity: &dyn DdsReadable, 78 | topic: DdsTopic) -> Result, DDSError> { 79 | if self.is_async { 80 | DdsReader::create_async(entity,topic,self.maybe_qos) 81 | } else { 82 | DdsReader::create_sync_or_async(entity, topic, self.maybe_qos, self.maybe_listener, ReaderType::Sync) 83 | } 84 | 85 | } 86 | } 87 | 88 | 89 | enum ReaderType { 90 | Async(Arc,Result<(),crate::error::ReaderError>)>>), 91 | Sync, 92 | } 93 | 94 | 95 | struct Inner { 96 | entity: DdsEntity, 97 | _listener: Option, 98 | reader_type : ReaderType, 99 | _phantom: PhantomData, 100 | // The callback closures that can be attached to a reader 101 | } 102 | 103 | pub struct DdsReader { 104 | inner : Arc>, 105 | } 106 | 107 | impl<'a, T> DdsReader 108 | where 109 | T: Sized + TopicType, 110 | { 111 | 112 | pub fn create( 113 | entity: &dyn DdsReadable, 114 | topic: DdsTopic, 115 | maybe_qos: Option, 116 | maybe_listener: Option, 117 | ) -> Result { 118 | Self::create_sync_or_async(entity, topic, maybe_qos, maybe_listener, ReaderType::Sync) 119 | } 120 | 121 | fn create_sync_or_async( 122 | entity: &dyn DdsReadable, 123 | topic: DdsTopic, 124 | maybe_qos: Option, 125 | maybe_listener: Option, 126 | reader_type : ReaderType, 127 | ) -> Result { 128 | unsafe { 129 | let w = dds_create_reader( 130 | entity.entity().entity(), 131 | topic.entity().entity(), 132 | maybe_qos.map_or(std::ptr::null(), |q| q.into()), 133 | maybe_listener 134 | .as_ref() 135 | .map_or(std::ptr::null(), |l| l.into()), 136 | ); 137 | 138 | if w >= 0 { 139 | Ok(DdsReader { 140 | inner : Arc::new(Inner {entity: DdsEntity::new(w), 141 | _listener: maybe_listener, 142 | reader_type, 143 | _phantom: PhantomData,}) 144 | }) 145 | } else { 146 | Err(DDSError::from(w)) 147 | } 148 | } 149 | } 150 | 151 | /// Create an async reader. This constructor must be used if using any of the async functions. 152 | pub fn create_async( 153 | entity: &dyn DdsReadable, 154 | topic: DdsTopic, 155 | maybe_qos: Option, 156 | ) -> Result { 157 | 158 | let waker = Arc::new(,Result<(),crate::error::ReaderError>)>>::new((None,Ok(())))); 159 | let waker_cb = waker.clone(); 160 | let requested_deadline_waker = waker.clone(); 161 | 162 | let listener = DdsListenerBuilder::new() 163 | .on_data_available(move|_entity| { 164 | //println!("Data available "); 165 | let mut maybe_waker = waker_cb.lock().unwrap(); 166 | if let Some(waker) = maybe_waker.0.take() { 167 | waker.wake(); 168 | } 169 | }) 170 | .on_requested_deadline_missed(move |entity, status| { 171 | println!("Deadline missed: Entity:{:?} Status:{:?}", unsafe {entity.entity()}, status); 172 | let mut maybe_waker = requested_deadline_waker.lock().unwrap(); 173 | maybe_waker.1 = Err(ReaderError::RequestedDeadLineMissed); 174 | if let Some(waker) = maybe_waker.0.take() { 175 | waker.wake(); 176 | } 177 | }) 178 | .build(); 179 | 180 | match Self::create_sync_or_async(entity, topic, maybe_qos, Some(listener),ReaderType::Async(waker) ) { 181 | Ok(reader) => { 182 | Ok(reader) 183 | }, 184 | Err(e) => Err(e), 185 | } 186 | 187 | } 188 | 189 | /// read synchronously 190 | pub fn read_now(&self,buf: &mut SampleBuffer) -> Result { 191 | Self::readn_from_entity_now(self.entity(),buf,false) 192 | } 193 | 194 | /// take synchronously 195 | pub fn take_now(&self,buf: &mut SampleBuffer) -> Result { 196 | Self::readn_from_entity_now(self.entity(),buf,true) 197 | } 198 | 199 | /// Read multiple samples from the reader synchronously. The buffer for the sampes must be passed in. 200 | /// On success, returns the number of samples read. 201 | pub fn readn_from_entity_now(entity: &DdsEntity, buf: &mut SampleBuffer, take: bool) -> Result { 202 | 203 | let (voidp, info_ptr) = unsafe {buf.as_mut_ptr()}; 204 | let voidpp = voidp as *mut *mut c_void; 205 | //println!("Infoptr:{:?}",info_ptr); 206 | 207 | let ret = unsafe { 208 | if take { 209 | dds_take(entity.entity(), voidpp , info_ptr as *mut _, buf.len() as size_t, buf.len() as u32) 210 | } else { 211 | dds_read(entity.entity(), voidpp , info_ptr as *mut _, buf.len() as size_t, buf.len() as u32) 212 | } 213 | }; 214 | if ret > 0 { 215 | // If first sample is value we assume all are 216 | if buf.is_valid_sample(0) { 217 | Ok(ret as usize) 218 | } else { 219 | Err(DDSError::NoData) 220 | } 221 | } else { 222 | Err(DDSError::OutOfResources) 223 | } 224 | } 225 | 226 | /// Read samples asynchronously. The number of samples actually read is returned. 227 | pub async fn read(&self, samples : &mut SampleBuffer) -> Result { 228 | if let ReaderType::Async(waker) = &self.inner.reader_type { 229 | let future_sample = SampleArrayFuture::new(self.inner.entity.clone(), waker.clone(),samples ,FutureType::Read); 230 | future_sample.await 231 | } else { 232 | Err(ReaderError::ReaderNotAsync) 233 | } 234 | } 235 | 236 | /// Get samples asynchronously. The number of samples actually read is returned. 237 | pub async fn take(&self, samples : &mut SampleBuffer) -> Result { 238 | if let ReaderType::Async(waker) = &self.inner.reader_type { 239 | let future_sample = SampleArrayFuture::new(self.inner.entity.clone(), waker.clone(),samples ,FutureType::Take); 240 | future_sample.await 241 | } else { 242 | Err(ReaderError::ReaderNotAsync) 243 | } 244 | } 245 | 246 | pub fn create_readcondition( 247 | &'a mut self, 248 | mask: StateMask, 249 | ) -> Result, DDSError> { 250 | DdsReadCondition::create(self, mask) 251 | } 252 | } 253 | 254 | impl<'a, T> Entity for DdsReader 255 | where 256 | T: std::marker::Sized + TopicType, 257 | { 258 | fn entity(&self) -> &DdsEntity { 259 | &self.inner.entity 260 | } 261 | } 262 | 263 | impl<'a, T> Drop for DdsReader 264 | where 265 | T: Sized + TopicType, 266 | { 267 | fn drop(&mut self) { 268 | unsafe { 269 | //println!("Drop reader:{:?}", self.entity().entity()); 270 | let ret: DDSError = cyclonedds_sys::dds_delete(self.inner.entity.entity()).into(); 271 | if DDSError::DdsOk != ret { 272 | //panic!("cannot delete Reader: {}", ret); 273 | println!("Ignoring dds_delete failure for DdsReader"); 274 | } else { 275 | //println!("Reader dropped"); 276 | } 277 | } 278 | } 279 | } 280 | 281 | pub struct DdsReadCondition<'a, T: Sized + TopicType>(DdsEntity, &'a DdsReader); 282 | 283 | impl<'a, T> DdsReadCondition<'a, T> 284 | where 285 | T: Sized + TopicType, 286 | { 287 | fn create(reader: &'a DdsReader, mask: StateMask) -> Result { 288 | unsafe { 289 | let mask: u32 = *mask; 290 | let p = cyclonedds_sys::dds_create_readcondition(reader.entity().entity(), mask); 291 | if p > 0 { 292 | Ok(DdsReadCondition(DdsEntity::new(p), reader)) 293 | } else { 294 | Err(DDSError::from(p)) 295 | } 296 | } 297 | } 298 | } 299 | 300 | impl<'a, T> Entity for DdsReadCondition<'a, T> 301 | where 302 | T: std::marker::Sized + TopicType, 303 | { 304 | fn entity(&self) -> &DdsEntity { 305 | &self.0 306 | } 307 | } 308 | 309 | enum FutureType { 310 | Take, 311 | Read, 312 | } 313 | 314 | impl FutureType { 315 | fn is_take(&self) -> bool { 316 | match self { 317 | FutureType::Take => true, 318 | FutureType::Read => false, 319 | } 320 | } 321 | } 322 | 323 | 324 | struct SampleArrayFuture<'a,T> { 325 | entity : DdsEntity, 326 | waker : Arc,Result<(),crate::error::ReaderError>)>>, 327 | take_or_read : FutureType, 328 | buffer : &'a mut SampleBuffer, 329 | } 330 | 331 | 332 | impl <'a,T>SampleArrayFuture<'a,T> { 333 | fn new(entity: DdsEntity, waker : Arc,Result<(),crate::error::ReaderError>)>>, buffer: &'a mut SampleBuffer, ty : FutureType) -> Self { 334 | Self { 335 | entity, 336 | waker, 337 | take_or_read : ty, 338 | buffer, 339 | } 340 | } 341 | } 342 | 343 | impl <'a,T>Future for SampleArrayFuture<'a,T> where T: TopicType { 344 | type Output = Result; 345 | 346 | fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll { 347 | 348 | // Lock the waker first in case a callback for read complete happens and we miss it 349 | // clone to avoid the lifetime problem with self 350 | let waker = self.waker.clone(); 351 | let mut waker = waker.lock().unwrap(); 352 | let is_take = self.take_or_read.is_take(); 353 | let entity = self.entity.clone(); 354 | 355 | // check if we have an error from any of the callbacks 356 | if let Err(e) = &waker.1 { 357 | return Poll::Ready(Err(e.clone())) 358 | } 359 | 360 | 361 | match DdsReader::::readn_from_entity_now(&entity, self.buffer, is_take) { 362 | Ok(len) => Poll::Ready(Ok(len)), 363 | Err(DDSError::NoData) | Err(DDSError::OutOfResources) => { 364 | let _ = waker.0.replace(ctx.waker().clone()); 365 | Poll::Pending 366 | }, 367 | Err(e) => { 368 | //println!("Error:{}",e); 369 | // Some other error happened 370 | Poll::Ready(Err(ReaderError::DdsError(e))) 371 | } 372 | } 373 | } 374 | } 375 | 376 | 377 | 378 | #[cfg(test)] 379 | mod test { 380 | use core::panic; 381 | use std::time::Duration; 382 | 383 | use crate::{DdsParticipant, DdsSubscriber}; 384 | use super::*; 385 | use crate::{DdsPublisher, DdsWriter}; 386 | 387 | use cdds_derive::Topic; 388 | use serde_derive::{Deserialize, Serialize}; 389 | use tokio::runtime::Runtime; 390 | 391 | 392 | #[repr(C)] 393 | #[derive(Serialize,Deserialize,Debug, PartialEq, Clone)] 394 | enum Position { 395 | Front, 396 | Back, 397 | } 398 | 399 | impl Default for Position { 400 | fn default() -> Self { 401 | Self::Front 402 | } 403 | } 404 | 405 | #[derive(Serialize,Deserialize,Topic, Debug, PartialEq)] 406 | struct TestTopic { 407 | a : u32, 408 | b : u16, 409 | c: String, 410 | d : Vec, 411 | #[topic_key] 412 | e : u32, 413 | #[topic_key_enum] 414 | pos : Position, 415 | } 416 | 417 | impl Default for TestTopic { 418 | fn default() -> Self { 419 | Self { 420 | a : 10, 421 | b : 20, 422 | c : "TestTopic".to_owned(), 423 | d : vec![1,2,3,4,5], 424 | e : 0, 425 | pos : Position::default(), 426 | } 427 | } 428 | } 429 | 430 | #[derive(Serialize,Deserialize,Topic, Debug, PartialEq)] 431 | struct AnotherTopic { 432 | pub value : u32, 433 | pub name : String, 434 | pub arr : [String;2], 435 | pub vec : Vec, 436 | #[topic_key] 437 | pub key : u32, 438 | } 439 | 440 | impl Default for AnotherTopic { 441 | fn default() -> Self { 442 | assert!(Self::has_key()); 443 | Self { 444 | value : 42, 445 | name : "the answer".to_owned(), 446 | arr : ["one".to_owned(), "two".to_owned()], 447 | vec : vec!["Hello".to_owned(), "world".to_owned()], 448 | key : 0, 449 | } 450 | } 451 | } 452 | 453 | #[test] 454 | fn test_reader_async() { 455 | 456 | let participant = DdsParticipant::create(None, None, None).unwrap(); 457 | 458 | let topic = TestTopic::create_topic(&participant, Some("test_topic"), None, None).unwrap(); 459 | let another_topic = AnotherTopic::create_topic(&participant, None, None, None).unwrap(); 460 | 461 | let publisher = DdsPublisher::create(&participant, None, None).unwrap(); 462 | 463 | let mut writer = DdsWriter::create(&publisher, topic.clone(), None, None).unwrap(); 464 | let mut another_writer = DdsWriter::create(&publisher, another_topic.clone(), None, None).unwrap(); 465 | 466 | 467 | let subscriber = DdsSubscriber::create(&participant, None, None).unwrap(); 468 | let reader = DdsReader::create_async(&subscriber, topic, None).unwrap(); 469 | let another_reader = DdsReader::create_async(&subscriber, another_topic, None).unwrap(); 470 | 471 | let rt = Runtime::new().unwrap(); 472 | 473 | let _result = rt.block_on(async { 474 | 475 | let _task = tokio::spawn(async move { 476 | let mut samplebuffer = SampleBuffer::new(1); 477 | if let Ok(t) = reader.take(&mut samplebuffer).await { 478 | let sample = samplebuffer.iter().take(1).next().unwrap(); 479 | assert!(*sample == TestTopic::default()); 480 | } else { 481 | panic!("reader get failed"); 482 | } 483 | }); 484 | 485 | let _another_task = tokio::spawn(async move { 486 | let mut samples = AnotherTopic::create_sample_buffer(5); 487 | if let Ok(t) = another_reader.read(&mut samples).await { 488 | assert_eq!(t,1); 489 | for s in samples.iter() { 490 | 491 | println!("Got sample {}", s.key); 492 | } 493 | 494 | } else { 495 | panic!("reader get failed"); 496 | } 497 | }); 498 | 499 | // add a delay to make sure the data is not ready immediately 500 | tokio::time::sleep(std::time::Duration::from_millis(100)).await; 501 | let data = Arc::new(TestTopic::default()); 502 | writer.write(data).unwrap(); 503 | 504 | another_writer.write(Arc::new(AnotherTopic::default())).unwrap(); 505 | 506 | 507 | tokio::time::sleep(std::time::Duration::from_millis(300)).await; 508 | 509 | }); 510 | } 511 | /* 512 | #[test] 513 | fn test_requested_deadline_miss() { 514 | let participant = DdsParticipant::create(None, None, None).unwrap(); 515 | let topic = TestTopic::create_topic(&participant, Some("test_topic"), None, None).unwrap(); 516 | let publisher = DdsPublisher::create(&participant, None, None).unwrap(); 517 | 518 | let writer_qos = DdsQos::create().unwrap().set_deadline(std::time::Duration::from_millis(50)); 519 | let mut writer = DdsWriter::create(&publisher, topic.clone(), Some(writer_qos), None).unwrap(); 520 | 521 | 522 | 523 | 524 | let reader_qos = DdsQos::create().unwrap().set_deadline(std::time::Duration::from_millis(500)); 525 | let subscriber = DdsSubscriber::create(&participant, None, None).unwrap(); 526 | let reader = DdsReader::create_async(&subscriber, topic, None).unwrap(); 527 | 528 | let rt = Runtime::new().unwrap(); 529 | 530 | let _result = rt.block_on(async { 531 | 532 | 533 | let t = tokio::spawn(async move { 534 | 535 | loop { 536 | let d = writer.write(Arc::new(TestTopic::default())).unwrap(); 537 | tokio::time::sleep(Duration::from_millis(1000)).await; 538 | } 539 | 540 | }); 541 | 542 | loop { 543 | 544 | let d = reader.read1().await; 545 | tokio::time::sleep(Duration::from_millis(700)).await; 546 | println!("reader returned:{:?}",d); 547 | } 548 | 549 | 550 | 551 | }); 552 | 553 | } 554 | */ 555 | 556 | 557 | } 558 | -------------------------------------------------------------------------------- /src/dds_listener.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! A Listner can be attached to different types of entities. The callbacks that 18 | //! are supported depends on the type of entity. There is no checking for whether 19 | //! an entity supports the callback. 20 | //! # Example 21 | //! ``` 22 | //! use cyclonedds_rs::DdsListener; 23 | //! let listener = DdsListener::new() 24 | //! .on_subscription_matched(|a,b| { 25 | //! println!("Subscription matched!"); 26 | //! }).on_publication_matched(|a,b|{ 27 | //! println!("Publication matched"); 28 | //! }). 29 | //! hook(); // The hook call will finalize the listener. No more callbacks can be attached after this. 30 | //! ``` 31 | 32 | use cyclonedds_sys::dds_listener_t; 33 | use cyclonedds_sys::*; 34 | use std::convert::From; 35 | 36 | /* 37 | Each listener has its own set of callbacks. 38 | */ 39 | 40 | /// The callbacks are in a different structure that is always 41 | /// heap allocated. 42 | #[derive(Default)] 43 | struct Callbacks { 44 | // Callbacks for readers 45 | on_sample_lost: Option>, 46 | on_data_available: Option>, 47 | on_sample_rejected: Option>, 48 | on_liveliness_changed: 49 | Option>, 50 | on_requested_deadline_missed: 51 | Option>, 52 | on_requested_incompatible_qos: 53 | Option>, 54 | on_subscription_matched: 55 | Option>, 56 | 57 | //callbacks for writers 58 | on_liveliness_lost: Option>, 59 | on_offered_deadline_missed: 60 | Option>, 61 | on_offered_incompatible_qos: 62 | Option>, 63 | on_publication_matched: 64 | Option>, 65 | 66 | on_inconsistent_topic: 67 | Option>, 68 | on_data_on_readers: Option>, 69 | } 70 | 71 | unsafe impl Send for Inner {} 72 | struct Inner { 73 | listener: Option<*mut dds_listener_t>, 74 | callbacks: Option>, 75 | raw_ptr: Option<*mut Callbacks>, 76 | } 77 | 78 | #[derive(Clone)] 79 | pub struct DdsListener { 80 | inner: std::sync::Arc>, 81 | } 82 | 83 | impl<'a> DdsListener { 84 | pub fn new() -> Self { 85 | Self { 86 | inner: std::sync::Arc::new(std::sync::Mutex::new(Inner { 87 | listener: None, 88 | callbacks: Some(Box::default()), 89 | raw_ptr: None, 90 | })), 91 | } 92 | } 93 | } 94 | 95 | impl<'a> Default for DdsListener { 96 | fn default() -> Self { 97 | DdsListener::new() 98 | } 99 | } 100 | 101 | impl<'a> From<&DdsListener> for *const dds_listener_t { 102 | fn from(listener: &DdsListener) -> Self { 103 | if let Some(listener) = listener.inner.lock().unwrap().listener { 104 | listener 105 | } else { 106 | panic!("Attempt to convert from unitialized &listener"); 107 | } 108 | } 109 | } 110 | 111 | impl<'a> DdsListener { 112 | // take ownership as we're going to do some bad stuff here. 113 | pub fn hook(self) -> Self { 114 | // we're going to grab the Boxed callbacks and keep them separately as 115 | // we will send a pointer to the callback array into C. We convert the 116 | // pointer back to a box in the Drop function. 117 | 118 | // free the previous pointer if present 119 | if let Some(raw) = self.inner.lock().unwrap().raw_ptr.take() { 120 | unsafe { 121 | // take ownership and free when out of scope 122 | Box::from_raw(raw); 123 | } 124 | } 125 | 126 | let inner = &self.inner; 127 | 128 | { 129 | let mut inner = inner.lock().unwrap(); 130 | if let Some(b) = inner.callbacks.take() { 131 | let raw = Box::into_raw(b); 132 | unsafe { 133 | let l = dds_create_listener(raw as *mut std::ffi::c_void); 134 | if !l.is_null() { 135 | let callbacks_ptr = raw as *mut Callbacks; 136 | let callbacks = &*callbacks_ptr; 137 | self.register_callbacks(l, callbacks); 138 | inner.raw_ptr = Some(raw); 139 | inner.listener = Some(l); 140 | } else { 141 | panic!("Error creating listener"); 142 | } 143 | } 144 | } else { 145 | println!("No callbacks to take"); 146 | } 147 | } 148 | self 149 | } 150 | 151 | /// register the callbacks for the closures that have been set.DdsListener 152 | unsafe fn register_callbacks(&self, listener: *mut dds_listener_t, callbacks: &Callbacks) { 153 | if callbacks.on_data_available.is_some() { 154 | //println!("Listener hooked for data available"); 155 | dds_lset_data_available(listener, Some(Self::call_data_available_closure)); 156 | } 157 | if callbacks.on_sample_lost.is_some() { 158 | dds_lset_sample_lost(listener, Some(Self::call_sample_lost_closure)); 159 | } 160 | 161 | if callbacks.on_sample_rejected.is_some() { 162 | dds_lset_sample_rejected(listener, Some(Self::call_sample_rejected_closure)); 163 | } 164 | 165 | if callbacks.on_liveliness_changed.is_some() { 166 | dds_lset_liveliness_changed(listener, Some(Self::call_liveliness_changed_closure)); 167 | } 168 | 169 | if callbacks.on_requested_deadline_missed.is_some() { 170 | dds_lset_requested_deadline_missed( 171 | listener, 172 | Some(Self::call_requested_deadline_missed_closure), 173 | ); 174 | } 175 | 176 | if callbacks.on_requested_incompatible_qos.is_some() { 177 | dds_lset_requested_incompatible_qos( 178 | listener, 179 | Some(Self::call_requested_incompatible_qos_closure), 180 | ); 181 | } 182 | 183 | if callbacks.on_subscription_matched.is_some() { 184 | dds_lset_subscription_matched(listener, Some(Self::call_subscription_matched_closure)); 185 | } 186 | if callbacks.on_liveliness_lost.is_some() { 187 | dds_lset_liveliness_lost(listener, Some(Self::call_liveliness_lost_closure)); 188 | } 189 | if callbacks.on_offered_deadline_missed.is_some() { 190 | dds_lset_offered_deadline_missed( 191 | listener, 192 | Some(Self::call_offered_deadline_missed_closure), 193 | ); 194 | } 195 | if callbacks.on_offered_incompatible_qos.is_some() { 196 | dds_lset_offered_incompatible_qos( 197 | listener, 198 | Some(Self::call_offered_incompatible_qos_closure), 199 | ); 200 | } 201 | if callbacks.on_publication_matched.is_some() { 202 | dds_lset_publication_matched(listener, Some(Self::call_publication_matched_closure)); 203 | } 204 | if callbacks.on_inconsistent_topic.is_some() { 205 | dds_lset_inconsistent_topic(listener, Some(Self::call_inconsistent_topic_closure)); 206 | } 207 | if callbacks.on_data_on_readers.is_some() { 208 | dds_lset_data_on_readers(listener, Some(Self::call_data_on_readers_closure)); 209 | } 210 | } 211 | } 212 | 213 | ////// 214 | impl DdsListener { 215 | #[deprecated] 216 | pub fn on_data_available(self, callback: F) -> Self 217 | where 218 | F: FnMut(DdsEntity) + 'static, 219 | { 220 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 221 | callbacks.on_data_available = Some(Box::new(callback)); 222 | } 223 | 224 | self 225 | } 226 | 227 | unsafe extern "C" fn call_data_available_closure( 228 | reader: dds_entity_t, 229 | data: *mut std::ffi::c_void, 230 | ) { 231 | let callbacks_ptr = data as *mut Callbacks; 232 | let callbacks = &mut *callbacks_ptr; 233 | // println!("C Callback!"); 234 | if let Some(avail) = &mut callbacks.on_data_available { 235 | avail(DdsEntity::new(reader)); 236 | } 237 | } 238 | } 239 | 240 | impl<'a> DdsListener { 241 | ///// 242 | #[deprecated] 243 | pub fn on_sample_lost(self, callback: F) -> Self 244 | where 245 | F: FnMut(DdsEntity, dds_sample_lost_status_t) + 'static, 246 | { 247 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 248 | callbacks.on_sample_lost = Some(Box::new(callback)); 249 | } 250 | self 251 | } 252 | 253 | unsafe extern "C" fn call_sample_lost_closure( 254 | reader: dds_entity_t, 255 | status: dds_sample_lost_status_t, 256 | data: *mut std::ffi::c_void, 257 | ) { 258 | let callbacks_ptr = data as *mut Callbacks; 259 | let callbacks = &mut *callbacks_ptr; 260 | //println!("C Callback - sample lost"); 261 | if let Some(lost) = &mut callbacks.on_sample_lost { 262 | lost(DdsEntity::new(reader), status); 263 | } 264 | } 265 | } 266 | 267 | impl<'a> DdsListener { 268 | ////// 269 | #[deprecated] 270 | pub fn on_sample_rejected(self, callback: F) -> Self 271 | where 272 | F: FnMut(DdsEntity, dds_sample_rejected_status_t) + 'static, 273 | { 274 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 275 | callbacks.on_sample_rejected = Some(Box::new(callback)); 276 | } 277 | self 278 | } 279 | 280 | unsafe extern "C" fn call_sample_rejected_closure( 281 | reader: dds_entity_t, 282 | status: dds_sample_rejected_status_t, 283 | data: *mut std::ffi::c_void, 284 | ) { 285 | let callbacks_ptr = data as *mut Callbacks; 286 | let callbacks = &mut *callbacks_ptr; 287 | //println!("C Callback - sample rejected"); 288 | if let Some(rejected) = &mut callbacks.on_sample_rejected { 289 | rejected(DdsEntity::new(reader), status); 290 | } 291 | } 292 | } 293 | 294 | // Liveliness changed 295 | impl<'a> DdsListener { 296 | #[deprecated] 297 | pub fn on_liveliness_changed(self, callback: F) -> Self 298 | where 299 | F: FnMut(DdsEntity, dds_liveliness_changed_status_t) + 'static, 300 | { 301 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 302 | callbacks.on_liveliness_changed = Some(Box::new(callback)); 303 | } 304 | self 305 | } 306 | 307 | unsafe extern "C" fn call_liveliness_changed_closure( 308 | entity: dds_entity_t, 309 | status: dds_liveliness_changed_status_t, 310 | data: *mut std::ffi::c_void, 311 | ) { 312 | let callbacks_ptr = data as *mut Callbacks; 313 | let callbacks = &mut *callbacks_ptr; 314 | //println!("C Callback - Liveliness changed"); 315 | if let Some(changed) = &mut callbacks.on_liveliness_changed { 316 | changed(DdsEntity::new(entity), status); 317 | } 318 | } 319 | } 320 | 321 | impl<'a> DdsListener { 322 | #[deprecated] 323 | pub fn on_requested_deadline_missed(self, callback: F) -> Self 324 | where 325 | F: FnMut(DdsEntity, dds_requested_deadline_missed_status_t) + 'static, 326 | { 327 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 328 | callbacks.on_requested_deadline_missed = Some(Box::new(callback)); 329 | } 330 | self 331 | } 332 | 333 | unsafe extern "C" fn call_requested_deadline_missed_closure( 334 | entity: dds_entity_t, 335 | status: dds_requested_deadline_missed_status_t, 336 | data: *mut std::ffi::c_void, 337 | ) { 338 | let callbacks_ptr = data as *mut Callbacks; 339 | let callbacks = &mut *callbacks_ptr; 340 | //println!("C Callback - requested deadline missed"); 341 | if let Some(missed) = &mut callbacks.on_requested_deadline_missed { 342 | missed(DdsEntity::new(entity), status); 343 | } 344 | } 345 | } 346 | 347 | impl<'a> DdsListener { 348 | #[deprecated] 349 | pub fn on_requested_incompatible_qos(self, callback: F) -> Self 350 | where 351 | F: FnMut(DdsEntity, dds_requested_incompatible_qos_status_t) + 'static, 352 | { 353 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 354 | callbacks.on_requested_incompatible_qos = Some(Box::new(callback)); 355 | } 356 | self 357 | } 358 | 359 | unsafe extern "C" fn call_requested_incompatible_qos_closure( 360 | entity: dds_entity_t, 361 | status: dds_requested_incompatible_qos_status_t, 362 | data: *mut std::ffi::c_void, 363 | ) { 364 | let callbacks_ptr = data as *mut Callbacks; 365 | let callbacks = &mut *callbacks_ptr; 366 | //println!("C Callback - requested incompatible QOS"); 367 | if let Some(incompatible_qos) = &mut callbacks.on_requested_incompatible_qos { 368 | incompatible_qos(DdsEntity::new(entity), status); 369 | } 370 | } 371 | } 372 | 373 | impl<'a> DdsListener { 374 | #[deprecated] 375 | pub fn on_subscription_matched(self, callback: F) -> Self 376 | where 377 | F: FnMut(DdsEntity, dds_subscription_matched_status_t) + 'static, 378 | { 379 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 380 | callbacks.on_subscription_matched = Some(Box::new(callback)); 381 | } 382 | self 383 | } 384 | 385 | unsafe extern "C" fn call_subscription_matched_closure( 386 | entity: dds_entity_t, 387 | status: dds_subscription_matched_status_t, 388 | data: *mut std::ffi::c_void, 389 | ) { 390 | let callbacks_ptr = data as *mut Callbacks; 391 | let callbacks = &mut *callbacks_ptr; 392 | //println!("C Callback - subscription matched"); 393 | if let Some(matched) = &mut callbacks.on_subscription_matched { 394 | matched(DdsEntity::new(entity), status); 395 | } 396 | } 397 | } 398 | 399 | impl<'a> DdsListener { 400 | #[deprecated] 401 | pub fn on_liveliness_lost(self, callback: F) -> Self 402 | where 403 | F: FnMut(DdsEntity, dds_liveliness_lost_status_t) + 'static, 404 | { 405 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 406 | callbacks.on_liveliness_lost = Some(Box::new(callback)); 407 | } 408 | self 409 | } 410 | 411 | unsafe extern "C" fn call_liveliness_lost_closure( 412 | entity: dds_entity_t, 413 | status: dds_liveliness_lost_status_t, 414 | data: *mut std::ffi::c_void, 415 | ) { 416 | let callbacks_ptr = data as *mut Callbacks; 417 | let callbacks = &mut *callbacks_ptr; 418 | //println!("C Callback - liveliness lost"); 419 | if let Some(lost) = &mut callbacks.on_liveliness_lost { 420 | lost(DdsEntity::new(entity), status); 421 | } 422 | } 423 | } 424 | 425 | impl<'a> DdsListener { 426 | #[deprecated] 427 | pub fn on_offered_deadline_missed(self, callback: F) -> Self 428 | where 429 | F: FnMut(DdsEntity, dds_offered_deadline_missed_status_t) + 'static, 430 | { 431 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 432 | callbacks.on_offered_deadline_missed = Some(Box::new(callback)); 433 | } 434 | self 435 | } 436 | 437 | unsafe extern "C" fn call_offered_deadline_missed_closure( 438 | entity: dds_entity_t, 439 | status: dds_offered_deadline_missed_status_t, 440 | data: *mut std::ffi::c_void, 441 | ) { 442 | let callbacks_ptr = data as *mut Callbacks; 443 | let callbacks = &mut *callbacks_ptr; 444 | //println!("C Callback - offered deadline missed"); 445 | if let Some(missed) = &mut callbacks.on_offered_deadline_missed { 446 | missed(DdsEntity::new(entity), status); 447 | } 448 | } 449 | } 450 | 451 | impl<'a> DdsListener { 452 | #[deprecated] 453 | pub fn on_offered_incompatible_qos(self, callback: F) -> Self 454 | where 455 | F: FnMut(DdsEntity, dds_offered_incompatible_qos_status_t) + 'static, 456 | { 457 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 458 | callbacks.on_offered_incompatible_qos = Some(Box::new(callback)); 459 | } 460 | self 461 | } 462 | 463 | unsafe extern "C" fn call_offered_incompatible_qos_closure( 464 | entity: dds_entity_t, 465 | status: dds_offered_incompatible_qos_status_t, 466 | data: *mut std::ffi::c_void, 467 | ) { 468 | let callbacks_ptr = data as *mut Callbacks; 469 | let callbacks = &mut *callbacks_ptr; 470 | //println!("C Callback - offered incompatible QOS"); 471 | if let Some(incompatible) = &mut callbacks.on_offered_incompatible_qos { 472 | incompatible(DdsEntity::new(entity), status); 473 | } 474 | } 475 | } 476 | 477 | impl<'a> DdsListener { 478 | #[deprecated] 479 | pub fn on_publication_matched(self, callback: F) -> Self 480 | where 481 | F: FnMut(DdsEntity, dds_publication_matched_status_t) + 'static, 482 | { 483 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 484 | callbacks.on_publication_matched = Some(Box::new(callback)); 485 | } 486 | self 487 | } 488 | 489 | unsafe extern "C" fn call_publication_matched_closure( 490 | entity: dds_entity_t, 491 | status: dds_publication_matched_status_t, 492 | data: *mut std::ffi::c_void, 493 | ) { 494 | let callbacks_ptr = data as *mut Callbacks; 495 | let callbacks = &mut *callbacks_ptr; 496 | //println!("C Callback - publication matched"); 497 | if let Some(matched) = &mut callbacks.on_publication_matched { 498 | matched(DdsEntity::new(entity), status); 499 | } 500 | } 501 | } 502 | 503 | impl<'a> DdsListener { 504 | #[deprecated] 505 | pub fn on_inconsistent_topic(self, callback: F) -> Self 506 | where 507 | F: FnMut(DdsEntity, dds_inconsistent_topic_status_t) + 'static, 508 | { 509 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 510 | callbacks.on_inconsistent_topic = Some(Box::new(callback)); 511 | } 512 | self 513 | } 514 | 515 | unsafe extern "C" fn call_inconsistent_topic_closure( 516 | entity: dds_entity_t, 517 | status: dds_inconsistent_topic_status_t, 518 | data: *mut std::ffi::c_void, 519 | ) { 520 | let callbacks_ptr = data as *mut Callbacks; 521 | let callbacks = &mut *callbacks_ptr; 522 | //println!("C Callback - inconsistent topic"); 523 | if let Some(inconsistant) = &mut callbacks.on_inconsistent_topic { 524 | inconsistant(DdsEntity::new(entity), status); 525 | } 526 | } 527 | } 528 | 529 | impl<'a> DdsListener { 530 | #[deprecated] 531 | pub fn on_data_on_readers(self, callback: F) -> Self 532 | where 533 | F: FnMut(DdsEntity) + 'static, 534 | { 535 | if let Some(callbacks) = &mut self.inner.lock().unwrap().callbacks { 536 | callbacks.on_data_on_readers = Some(Box::new(callback)); 537 | } 538 | self 539 | } 540 | 541 | unsafe extern "C" fn call_data_on_readers_closure( 542 | entity: dds_entity_t, 543 | data: *mut std::ffi::c_void, 544 | ) { 545 | let callbacks_ptr = data as *mut Callbacks; 546 | let callbacks = &mut *callbacks_ptr; 547 | //println!("C Callback - data on readers"); 548 | if let Some(data) = &mut callbacks.on_data_on_readers { 549 | data(DdsEntity::new(entity)); 550 | } 551 | } 552 | } 553 | 554 | impl<'a> Drop for DdsListener { 555 | fn drop(&mut self) { 556 | // delete the listener so we are sure of not 557 | // getting any callbacks 558 | if let Some(listener) = &self.inner.lock().unwrap().listener { 559 | unsafe { 560 | dds_reset_listener(*listener); 561 | dds_delete_listener(*listener); 562 | } 563 | } 564 | // gain back control of the Callback structure 565 | if let Some(raw) = self.inner.lock().unwrap().raw_ptr.take() { 566 | unsafe { 567 | // take ownership and free when out of scope 568 | let _ = Box::from_raw(raw); 569 | } 570 | } 571 | } 572 | } 573 | 574 | #[derive(Default)] 575 | pub struct DdsListenerBuilder { 576 | listener: Option, 577 | } 578 | 579 | impl DdsListenerBuilder { 580 | pub fn new() -> Self { 581 | Self { 582 | listener : Some(DdsListener::new()) 583 | } 584 | } 585 | 586 | pub fn build(&mut self) -> DdsListener { 587 | self.listener.take().unwrap().hook() 588 | } 589 | 590 | pub fn on_data_available(&mut self, callback: F) -> &mut Self 591 | where 592 | F: FnMut(DdsEntity) + 'static, 593 | { 594 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 595 | callbacks.on_data_available = Some(Box::new(callback)); 596 | } 597 | 598 | self 599 | } 600 | 601 | ///// 602 | pub fn on_sample_lost(&mut self, callback: F) -> &mut Self 603 | where 604 | F: FnMut(DdsEntity, dds_sample_lost_status_t) + 'static, 605 | { 606 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 607 | callbacks.on_sample_lost = Some(Box::new(callback)); 608 | } 609 | self 610 | } 611 | 612 | ////// 613 | pub fn on_sample_rejected(&mut self, callback: F) -> &mut Self 614 | where 615 | F: FnMut(DdsEntity, dds_sample_rejected_status_t) + 'static, 616 | { 617 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 618 | callbacks.on_sample_rejected = Some(Box::new(callback)); 619 | } 620 | self 621 | } 622 | 623 | // Liveliness changed 624 | pub fn on_liveliness_changed(&mut self, callback: F) -> &mut Self 625 | where 626 | F: FnMut(DdsEntity, dds_liveliness_changed_status_t) + 'static, 627 | { 628 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 629 | callbacks.on_liveliness_changed = Some(Box::new(callback)); 630 | } 631 | self 632 | } 633 | 634 | pub fn on_requested_deadline_missed(&mut self, callback: F) -> &mut Self 635 | where 636 | F: FnMut(DdsEntity, dds_requested_deadline_missed_status_t) + 'static, 637 | { 638 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 639 | callbacks.on_requested_deadline_missed = Some(Box::new(callback)); 640 | } 641 | self 642 | } 643 | 644 | pub fn on_requested_incompatible_qos(&mut self, callback: F) -> &mut Self 645 | where 646 | F: FnMut(DdsEntity, dds_requested_incompatible_qos_status_t) + 'static, 647 | { 648 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 649 | callbacks.on_requested_incompatible_qos = Some(Box::new(callback)); 650 | } 651 | self 652 | } 653 | 654 | pub fn on_subscription_matched(&mut self, callback: F) -> &mut Self 655 | where 656 | F: FnMut(DdsEntity, dds_subscription_matched_status_t) + 'static, 657 | { 658 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 659 | callbacks.on_subscription_matched = Some(Box::new(callback)); 660 | } 661 | self 662 | } 663 | 664 | pub fn on_liveliness_lost(&mut self, callback: F) -> &mut Self 665 | where 666 | F: FnMut(DdsEntity, dds_liveliness_lost_status_t) + 'static, 667 | { 668 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 669 | callbacks.on_liveliness_lost = Some(Box::new(callback)); 670 | } 671 | self 672 | } 673 | 674 | pub fn on_offered_deadline_missed(&mut self, callback: F) -> &mut Self 675 | where 676 | F: FnMut(DdsEntity, dds_offered_deadline_missed_status_t) + 'static, 677 | { 678 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 679 | callbacks.on_offered_deadline_missed = Some(Box::new(callback)); 680 | } 681 | self 682 | } 683 | 684 | pub fn on_offered_incompatible_qos(&mut self, callback: F) -> &mut Self 685 | where 686 | F: FnMut(DdsEntity, dds_offered_incompatible_qos_status_t) + 'static, 687 | { 688 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 689 | callbacks.on_offered_incompatible_qos = Some(Box::new(callback)); 690 | } 691 | self 692 | } 693 | 694 | pub fn on_publication_matched(&mut self, callback: F) -> &mut Self 695 | where 696 | F: FnMut(DdsEntity, dds_publication_matched_status_t) + 'static, 697 | { 698 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 699 | callbacks.on_publication_matched = Some(Box::new(callback)); 700 | } 701 | self 702 | } 703 | 704 | pub fn on_inconsistent_topic(&mut self, callback: F) -> &mut Self 705 | where 706 | F: FnMut(DdsEntity, dds_inconsistent_topic_status_t) + 'static, 707 | { 708 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 709 | callbacks.on_inconsistent_topic = Some(Box::new(callback)); 710 | } 711 | self 712 | } 713 | 714 | pub fn on_data_on_readers(&mut self, callback: F) -> &mut Self 715 | where 716 | F: FnMut(DdsEntity) + 'static, 717 | { 718 | if let Some(callbacks) = &mut self.listener.as_ref().unwrap().inner.lock().unwrap().callbacks { 719 | callbacks.on_data_on_readers = Some(Box::new(callback)); 720 | } 721 | self 722 | } 723 | } 724 | -------------------------------------------------------------------------------- /src/serdes.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Sojan James 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Rust deserializer for CycloneDDS. 18 | // See discussion at https://github.com/eclipse-cyclonedds/cyclonedds/issues/830 19 | 20 | use cdr::{Bounded, CdrBe, Infinite}; 21 | 22 | 23 | use serde::{de::DeserializeOwned, Serialize}; 24 | use std::io::prelude::*; 25 | 26 | use std::ptr::NonNull; 27 | 28 | use std::{ 29 | ffi::{c_void, CStr}, 30 | marker::PhantomData, 31 | ops::Deref, 32 | sync::Arc, 33 | }; 34 | 35 | use cyclonedds_sys::*; 36 | //use fasthash::{murmur3::Hasher32, FastHasher}; 37 | use murmur3::murmur3_32; 38 | use std::io::Cursor; 39 | 40 | #[repr(C)] 41 | pub struct SerType { 42 | sertype: ddsi_sertype, 43 | _phantom: PhantomData, 44 | } 45 | 46 | pub trait TopicType: Serialize + DeserializeOwned { 47 | // generate a non-cryptographic hash of the key values to be used internally 48 | // in cyclonedds 49 | fn hash(&self, basehash : u32) -> u32 { 50 | let cdr = self.key_cdr(); 51 | let mut cursor = Cursor::new(cdr.as_slice()); 52 | murmur3_32(&mut cursor, 0).unwrap() ^ basehash 53 | } 54 | 55 | fn is_fixed_size() -> bool { 56 | false 57 | } 58 | /// The type name for this topic 59 | fn typename() -> std::ffi::CString { 60 | let ty_name_parts: String = std::any::type_name::() 61 | .split("::") 62 | .skip(1) 63 | .collect::>() 64 | .join("::"); 65 | 66 | 67 | //println!("Typename:{:?}", &typename); 68 | std::ffi::CString::new(ty_name_parts).expect("Unable to create CString for type name") 69 | } 70 | 71 | /// The default topic_name to use when creating a topic of this type. The default 72 | /// implementation uses '/' instead of '::' to form a unix like path. 73 | /// A prefix can optionally be added 74 | fn topic_name(maybe_prefix: Option<&str>) -> String { 75 | let topic_name_parts: String = format!( 76 | "/{}", 77 | std::any::type_name::() 78 | .to_string() 79 | .split("::") 80 | .skip(1) 81 | .collect::>() 82 | .join("/") 83 | ); 84 | 85 | if let Some(prefix) = maybe_prefix { 86 | let mut path = String::from(prefix); 87 | path.push_str(&topic_name_parts); 88 | path 89 | } else { 90 | topic_name_parts 91 | } 92 | } 93 | 94 | fn has_key() -> bool; 95 | // this is the key as defined in the DDS-RTPS spec. 96 | // KeyHash (PID_KEY_HASH). This function does not 97 | // hash the key. Use the force_md5_keyhash to know 98 | // whether to use md5 even if the the key cdr is 16 bytes 99 | // or shorter. 100 | fn key_cdr(&self) -> Vec; 101 | 102 | // force the use of md5 even if the serialized size is less than 16 103 | // as per the standard, we need to check the potential field size and not the actual. 104 | fn force_md5_keyhash() -> bool; 105 | } 106 | 107 | impl<'a, T> SerType { 108 | pub fn new() -> Box> 109 | where 110 | T: DeserializeOwned + Serialize + TopicType, 111 | { 112 | Box::>::new(SerType { 113 | sertype: { 114 | let mut sertype = std::mem::MaybeUninit::uninit(); 115 | unsafe { 116 | let type_name = T::typename(); 117 | ddsi_sertype_init( 118 | sertype.as_mut_ptr(), 119 | type_name.as_ptr(), 120 | Box::into_raw(create_sertype_ops::()), 121 | Box::into_raw(create_serdata_ops::()), 122 | !T::has_key(), 123 | ); 124 | let mut sertype = sertype.assume_init(); 125 | sertype.set_fixed_size(if T::is_fixed_size() { 1 } else { 0 }); 126 | sertype.iox_size = std::mem::size_of::() as u32; 127 | sertype 128 | } 129 | }, 130 | _phantom: PhantomData, 131 | }) 132 | } 133 | 134 | // cast into cyclone dds sertype. Rust relinquishes ownership here. 135 | // Cyclone DDS will free this. But if you need to free this pointer 136 | // before handing it over to cyclone, make sure you explicitly free it 137 | pub fn into_sertype(sertype: Box>) -> *mut ddsi_sertype { 138 | Box::>::into_raw(sertype) as *mut ddsi_sertype 139 | } 140 | 141 | pub fn try_from_sertype(sertype: *const ddsi_sertype) -> Option>> { 142 | let ptr = sertype as *mut SerType; 143 | if !ptr.is_null() { 144 | Some(unsafe { Box::from_raw(ptr) }) 145 | } else { 146 | None 147 | } 148 | } 149 | } 150 | 151 | #[derive(Clone)] 152 | pub enum SampleStorage { 153 | Owned(Arc), 154 | Loaned(Arc>), 155 | } 156 | 157 | impl Deref for SampleStorage { 158 | type Target = T; 159 | 160 | fn deref(&self) -> &Self::Target { 161 | match self { 162 | SampleStorage::Owned(t) => t.deref(), 163 | SampleStorage::Loaned(t) => unsafe { t.as_ref().as_ref() }, 164 | } 165 | } 166 | } 167 | 168 | impl Drop for SampleStorage { 169 | fn drop(&mut self) { 170 | match self { 171 | SampleStorage::Loaned(_t) => { 172 | } 173 | _ => { 174 | 175 | } 176 | } 177 | } 178 | } 179 | 180 | 181 | pub struct Sample { 182 | //Serdata is used for incoming samples. We hold a reference to the ddsi_serdata which contains 183 | // the sample 184 | serdata: Option<*mut ddsi_serdata>, 185 | // sample is used for outgoing samples. 186 | sample: Option>, 187 | } 188 | 189 | impl<'a,T> Sample 190 | where 191 | T: TopicType 192 | { 193 | pub fn try_deref<>(&self) -> Option<&T> { 194 | if let Some(serdata) = self.serdata { 195 | let serdata = SerData::::mut_ref_from_serdata(serdata); 196 | match &serdata.sample { 197 | SampleData::Uninitialized => None, 198 | SampleData::SDKKey => None, 199 | SampleData::SDKData(it) => Some(it.as_ref()), 200 | SampleData::SHMData(it) => unsafe { Some(it.as_ref())}, 201 | } 202 | } else { 203 | None 204 | } 205 | 206 | } 207 | 208 | pub fn get_sample(&self) -> Option> { 209 | //if let Ok(t) = self.sample.write() { 210 | match self.sample.as_ref() { 211 | Some(s) => match s { 212 | SampleStorage::Owned(s) => Some(SampleStorage::Owned(s.clone())), 213 | SampleStorage::Loaned(s) => Some(SampleStorage::Loaned(s.clone())), 214 | }, 215 | None => None, 216 | } 217 | } 218 | 219 | // Deprecated as this function can panic 220 | #[deprecated] 221 | pub (crate)fn get(&self) -> Option> { 222 | //let t = self.sample; 223 | match &self.sample { 224 | Some(SampleStorage::Owned(t)) => Some(t.clone()), 225 | Some(SampleStorage::Loaned(_t)) => { 226 | None 227 | } 228 | None => { 229 | None 230 | } 231 | } 232 | } 233 | 234 | pub(crate) fn set_serdata(&mut self,serdata:*mut ddsi_serdata) { 235 | // Increment the reference count 236 | unsafe {ddsi_serdata_addref(serdata);} 237 | self.serdata = Some(serdata) 238 | } 239 | 240 | pub fn set(&mut self, t: Arc) { 241 | //let mut sample = self.sample.write().unwrap(); 242 | self.sample.replace(SampleStorage::Owned(t)); 243 | } 244 | 245 | pub fn set_loaned(&mut self, t: NonNull) { 246 | //let mut sample = self.sample.write().unwrap(); 247 | self.sample.replace(SampleStorage::Loaned(Arc::new(t))); 248 | } 249 | 250 | pub fn clear(&mut self) { 251 | //let mut sample = self.sample.write().unwrap(); 252 | let t = self.sample.take(); 253 | 254 | match &t { 255 | Some(SampleStorage::Owned(_o)) => {} 256 | Some(SampleStorage::Loaned(_o)) => {} 257 | None => {} 258 | } 259 | } 260 | 261 | pub fn from(it: Arc) -> Self { 262 | Self { 263 | serdata : None, 264 | sample: Some(SampleStorage::Owned(it)), 265 | } 266 | } 267 | } 268 | 269 | impl Default for Sample { 270 | fn default() -> Self { 271 | Self { 272 | serdata : None, 273 | sample: None, 274 | } 275 | } 276 | } 277 | 278 | impl Drop for Sample { 279 | fn drop(&mut self) { 280 | if let Some(serdata) = self.serdata { 281 | unsafe {ddsi_serdata_removeref(serdata)}; 282 | } 283 | } 284 | } 285 | 286 | 287 | 288 | 289 | /// 290 | /// TODO: UNSAFE WARNING Review needed. Forcing SampleBuffer to be Send 291 | /// DDS read API uses an array of void* pointers. The SampleBuffer structure 292 | /// is used to create the sample array in the necessary format. 293 | /// We allocate the Sample structure and set it to deallocated here. 294 | /// Cyclone does not allocate the sample, it only sets the value of the Arc 295 | /// inside the Sample::Value>. 296 | /// So this structure always points to a valid sample memory, but the serdes callbacks 297 | /// can change the value of the sample under us. 298 | /// To be absolutely sure, I think we must put each sample into an RwLock> instead of 299 | /// an Arc, I guess this is the cost we pay for zero copy. 300 | 301 | unsafe impl Send for SampleBuffer {} 302 | pub struct SampleBuffer { 303 | /// This is !Send. This is the only way to punch through the Cyclone API as we need an array of pointers 304 | pub(crate) buffer: Vec<*mut Sample>, 305 | pub(crate) sample_info: Vec, 306 | } 307 | 308 | impl<'a, T:TopicType> SampleBuffer { 309 | pub fn new(len: usize) -> Self { 310 | let mut buf = Self { 311 | buffer: Vec::new(), 312 | sample_info: vec![cyclonedds_sys::dds_sample_info::default(); len], 313 | }; 314 | 315 | for _i in 0..len { 316 | let p = Box::into_raw(Box::default()); 317 | buf.buffer.push(p); 318 | } 319 | buf 320 | } 321 | 322 | /// Check if sample is valid. Will panic if out of 323 | /// bounds. 324 | pub fn is_valid_sample(&self, index: usize) -> bool { 325 | self.sample_info[index].valid_data 326 | } 327 | 328 | pub fn len(&self) -> usize { 329 | self.buffer.len() 330 | } 331 | 332 | pub fn iter(&'a self) -> impl Iterator { 333 | let p = self.buffer.iter().filter_map(|p| { 334 | let sample = unsafe { &*(*p) }; 335 | sample.try_deref() 336 | 337 | }); 338 | p 339 | } 340 | 341 | /// Get a sample 342 | pub fn get(&self, index: usize) -> &Sample { 343 | let p_sample = self.buffer[index]; 344 | unsafe { &*p_sample } 345 | } 346 | 347 | /// return a raw pointer to the buffer and the sample info 348 | /// to be used in unsafe code that calls the CycloneDDS 349 | /// API 350 | pub unsafe fn as_mut_ptr(&mut self) -> (*mut *mut Sample, *mut dds_sample_info) { 351 | (self.buffer.as_mut_ptr(), self.sample_info.as_mut_ptr()) 352 | } 353 | } 354 | 355 | impl<'a, T> Drop for SampleBuffer { 356 | fn drop(&mut self) { 357 | for p in &self.buffer { 358 | unsafe { 359 | let _it = Box::from_raw(*p); 360 | } 361 | } 362 | } 363 | } 364 | /* 365 | impl <'a,T>Index for SampleBuffer { 366 | type Output = &'a Sample; 367 | fn index<'a>(&'a self, i: usize) -> &'a Sample { 368 | &self.e[i] 369 | } 370 | } 371 | */ 372 | 373 | #[allow(dead_code)] 374 | unsafe extern "C" fn zero_samples( 375 | _sertype: *const ddsi_sertype, 376 | _ptr: *mut std::ffi::c_void, 377 | _len: size_t, 378 | ) { 379 | } // empty implementation 380 | 381 | #[allow(dead_code)] 382 | extern "C" fn realloc_samples( 383 | ptrs: *mut *mut std::ffi::c_void, 384 | _sertype: *const ddsi_sertype, 385 | old: *mut std::ffi::c_void, 386 | old_count: size_t, 387 | new_count: size_t, 388 | ) { 389 | //println!("realloc"); 390 | let old = unsafe { 391 | Vec::<*mut Sample>::from_raw_parts( 392 | old as *mut *mut Sample, 393 | old_count as usize, 394 | old_count as usize, 395 | ) 396 | }; 397 | let mut new = Vec::<*mut Sample>::with_capacity(new_count as usize); 398 | 399 | if new_count >= old_count { 400 | for entry in old { 401 | new.push(entry); 402 | } 403 | 404 | for _i in 0..(new_count - old_count) { 405 | new.push(Box::into_raw(Box::default())); 406 | } 407 | } else { 408 | for e in old.into_iter().take(new_count as usize) { 409 | new.push(e) 410 | } 411 | } 412 | 413 | let leaked = new.leak(); 414 | 415 | let (raw, _length) = (leaked.as_ptr(), leaked.len()); 416 | // if the length and allocated length are not equal, we messed up above. 417 | //assert_eq!(length, allocated_length); 418 | unsafe { 419 | *ptrs = raw as *mut std::ffi::c_void; 420 | } 421 | } 422 | 423 | #[allow(dead_code)] 424 | extern "C" fn free_samples( 425 | _sertype: *const ddsi_sertype, 426 | ptrs: *mut *mut std::ffi::c_void, 427 | len: size_t, 428 | op: dds_free_op_t, 429 | ) where 430 | T: TopicType, 431 | { 432 | let ptrs_v: *mut *mut Sample = ptrs as *mut *mut Sample; 433 | 434 | if (op & DDS_FREE_ALL_BIT) != 0 { 435 | let _samples = 436 | unsafe { Vec::>::from_raw_parts(*ptrs_v, len as usize, len as usize) }; 437 | // all samples will get freed when samples goes out of scope 438 | } else { 439 | assert_ne!(op & DDS_FREE_CONTENTS_BIT, 0); 440 | let mut samples = 441 | unsafe { Vec::>::from_raw_parts(*ptrs_v, len as usize, len as usize) }; 442 | for sample in samples.iter_mut() { 443 | //let _old_sample = std::mem::take(sample); 444 | sample.clear() 445 | //_old_sample goes out of scope and the content is freed. The pointer is replaced with a default constructed sample 446 | } 447 | let _intentional_leak = samples.leak(); 448 | } 449 | } 450 | 451 | #[allow(dead_code)] 452 | unsafe extern "C" fn free_sertype(sertype: *mut cyclonedds_sys::ddsi_sertype) { 453 | ddsi_sertype_fini(sertype); 454 | 455 | let _sertype_ops = Box::::from_raw((*sertype).ops as *mut ddsi_sertype_ops); 456 | let _serdata_ops = 457 | Box::::from_raw((*sertype).serdata_ops as *mut ddsi_serdata_ops); 458 | // this sertype is always constructed in Rust. During destruction, 459 | // the Box takes over the pointer and frees it when it goes out 460 | // of scope. 461 | let sertype = sertype as *mut SerType; 462 | let _it = Box::>::from_raw(sertype); 463 | } 464 | 465 | // create ddsi_serdata from a fragchain 466 | #[allow(dead_code)] 467 | unsafe extern "C" fn serdata_from_fragchain( 468 | sertype: *const ddsi_sertype, 469 | kind: u32, 470 | mut fragchain: *const nn_rdata, 471 | size: size_t, 472 | ) -> *mut ddsi_serdata 473 | where 474 | T: DeserializeOwned + TopicType, 475 | { 476 | //println!("serdata_from_fragchain"); 477 | let mut off: u32 = 0; 478 | let size = size as usize; 479 | let fragchain_ref = &*fragchain; 480 | 481 | let mut serdata = SerData::::new(sertype, kind); 482 | 483 | assert_eq!(fragchain_ref.min, 0); 484 | assert!(fragchain_ref.maxp1 >= off); 485 | 486 | // The scatter gather list 487 | let mut sg_list = Vec::new(); 488 | 489 | while !fragchain.is_null() { 490 | let fragchain_ref = &*fragchain; 491 | if fragchain_ref.maxp1 > off { 492 | let payload = 493 | nn_rmsg_payload_offset(fragchain_ref.rmsg, nn_rdata_payload_offset(fragchain)); 494 | let src = payload.add((off - fragchain_ref.min) as usize); 495 | let n_bytes = fragchain_ref.maxp1 - off; 496 | sg_list.push(std::slice::from_raw_parts(src, n_bytes as usize)); 497 | off = fragchain_ref.maxp1; 498 | assert!(off as usize <= size); 499 | } 500 | fragchain = fragchain_ref.nextfrag; 501 | } 502 | // make a reader out of the sg_list 503 | let reader = SGReader::new(&sg_list); 504 | if let Ok(decoded) = cdr::deserialize_from::<_, T, _>(reader, Bounded(size as u64)) { 505 | if T::has_key() { 506 | // compute the 16byte key hash 507 | let key_cdr = decoded.key_cdr(); 508 | // skip the four byte header 509 | let key_cdr = &key_cdr[4..]; 510 | compute_key_hash(key_cdr, &mut serdata); 511 | } 512 | serdata.serdata.hash = decoded.hash((*sertype).serdata_basehash); 513 | let sample = std::sync::Arc::new(decoded); 514 | //store the deserialized sample in the serdata. We don't need to deserialize again 515 | serdata.sample = SampleData::SDKData(sample); 516 | } else { 517 | println!("Deserialization error!"); 518 | return std::ptr::null_mut(); 519 | } 520 | 521 | //store the hash into the serdata 522 | 523 | // convert into raw pointer and forget about it (for now). Cyclone will take ownership. 524 | let ptr = Box::into_raw(serdata); 525 | // only we know this ddsi_serdata is really of type SerData 526 | ptr as *mut ddsi_serdata 527 | } 528 | 529 | fn copy_raw_key_hash(key: &[u8], serdata: &mut Box>) { 530 | let mut raw_key = [0u8; 16]; 531 | for (i, data) in key.iter().enumerate() { 532 | raw_key[i] = *data; 533 | } 534 | serdata.key_hash = KeyHash::RawKey(raw_key) 535 | } 536 | 537 | fn compute_key_hash(key_cdr: &[u8], serdata: &mut SerData) 538 | where 539 | T: TopicType, 540 | { 541 | let mut cdr_key = [0u8; 20]; 542 | 543 | if T::force_md5_keyhash() || key_cdr.len() > 16 { 544 | let mut md5st = ddsrt_md5_state_t::default(); 545 | let md5set = &mut md5st as *mut ddsrt_md5_state_s; 546 | unsafe { 547 | ddsrt_md5_init(md5set); 548 | ddsrt_md5_append(md5set, key_cdr.as_ptr(), key_cdr.len() as u32); 549 | ddsrt_md5_finish(md5set, cdr_key.as_mut_ptr()); 550 | } 551 | } else { 552 | for (i, data) in key_cdr.iter().enumerate() { 553 | cdr_key[i] = *data; 554 | } 555 | } 556 | serdata.key_hash = KeyHash::CdrKey(cdr_key) 557 | } 558 | 559 | #[allow(dead_code)] 560 | unsafe extern "C" fn serdata_from_keyhash( 561 | sertype: *const ddsi_sertype, 562 | keyhash: *const ddsi_keyhash, 563 | ) -> *mut ddsi_serdata 564 | where 565 | T: TopicType, 566 | { 567 | let keyhash = (*keyhash).value; 568 | //println!("serdata_from_keyhash"); 569 | 570 | if T::force_md5_keyhash() { 571 | // this means keyhas fits in 16 bytes 572 | std::ptr::null_mut() 573 | } else { 574 | let mut serdata = SerData::::new(sertype, ddsi_serdata_kind_SDK_KEY); 575 | serdata.sample = SampleData::SDKKey; 576 | 577 | let mut key_hash_buffer = [0u8; 20]; 578 | let key_hash = &mut key_hash_buffer[4..]; 579 | 580 | for (i, b) in keyhash.iter().enumerate() { 581 | key_hash[i] = *b; 582 | } 583 | 584 | serdata.key_hash = KeyHash::CdrKey(key_hash_buffer); 585 | 586 | let ptr = Box::into_raw(serdata); 587 | // only we know this ddsi_serdata is really of type SerData 588 | ptr as *mut ddsi_serdata 589 | } 590 | } 591 | 592 | #[allow(dead_code)] 593 | #[allow(non_upper_case_globals)] 594 | unsafe extern "C" fn serdata_from_sample( 595 | sertype: *const ddsi_sertype, 596 | kind: u32, 597 | sample: *const c_void, 598 | ) -> *mut ddsi_serdata 599 | where 600 | T: TopicType, 601 | { 602 | //println!("Serdata from sample {:?}", sample); 603 | let mut serdata = SerData::::new(sertype, kind); 604 | let sample = sample as *const Sample; 605 | let sample = &*sample; 606 | 607 | match kind { 608 | #[allow(non_upper_case_globals)] 609 | ddsi_serdata_kind_SDK_DATA => { 610 | let sample = sample.get().unwrap(); 611 | serdata.serdata.hash = sample.hash((*sertype).serdata_basehash); 612 | serdata.sample = SampleData::SDKData(sample); 613 | } 614 | ddsi_serdata_kind_SDK_KEY => { 615 | panic!("Don't know how to create serdata from sample for SDK_KEY"); 616 | } 617 | _ => panic!("Unexpected kind"), 618 | } 619 | 620 | let ptr = Box::into_raw(serdata); 621 | // only we know this ddsi_serdata is really of type SerData 622 | ptr as *mut ddsi_serdata 623 | } 624 | 625 | #[allow(dead_code)] 626 | unsafe extern "C" fn serdata_from_iov( 627 | sertype: *const ddsi_sertype, 628 | kind: u32, 629 | niov: size_t, 630 | iov: *const iovec, 631 | size: size_t, 632 | ) -> *mut ddsi_serdata 633 | where 634 | T: DeserializeOwned + TopicType, 635 | { 636 | let size = size as usize; 637 | let niov = niov as usize; 638 | //println!("serdata_from_iov"); 639 | 640 | let mut serdata = SerData::::new(sertype, kind); 641 | 642 | let iovs = std::slice::from_raw_parts(iov as *const cyclonedds_sys::iovec, niov); 643 | 644 | let iov_slices: Vec<&[u8]> = iovs 645 | .iter() 646 | .map(|iov| { 647 | let iov = iov; 648 | 649 | std::slice::from_raw_parts(iov.iov_base as *const u8, iov.iov_len as usize) 650 | }) 651 | .collect(); 652 | 653 | // make a reader out of the sg_list 654 | let reader = SGReader::new(&iov_slices); 655 | 656 | if let Ok(decoded) = cdr::deserialize_from::<_, T, _>(reader, Bounded(size as u64)) { 657 | if T::has_key() { 658 | // compute the 16byte key hash 659 | let key_cdr = decoded.key_cdr(); 660 | // skip the four byte header 661 | let key_cdr = &key_cdr[4..]; 662 | compute_key_hash(key_cdr, &mut serdata); 663 | } 664 | serdata.serdata.hash = decoded.hash((*sertype).serdata_basehash); 665 | let sample = std::sync::Arc::new(decoded); 666 | //store the deserialized sample in the serdata. We don't need to deserialize again 667 | serdata.sample = SampleData::SDKData(sample); 668 | } else { 669 | //println!("Deserialization error!"); 670 | return std::ptr::null_mut(); 671 | } 672 | 673 | // convert into raw pointer and forget about it as ownership is passed into cyclonedds 674 | let ptr = Box::into_raw(serdata); 675 | // only we know this ddsi_serdata is really of type SerData 676 | ptr as *mut ddsi_serdata 677 | } 678 | 679 | #[allow(dead_code)] 680 | unsafe extern "C" fn free_serdata(serdata: *mut ddsi_serdata) { 681 | //println!("free_serdata"); 682 | // the pointer is really a *mut SerData 683 | let ptr = serdata as *mut SerData; 684 | 685 | let serdata = &mut *ptr; 686 | 687 | if !serdata.serdata.iox_subscriber.is_null() { 688 | let iox_subscriber: *mut iox_sub_t = serdata.serdata.iox_subscriber as *mut iox_sub_t; 689 | let chunk = &mut serdata.serdata.iox_chunk; 690 | let chunk = chunk as *mut *mut c_void; 691 | //println!("Free iox chunk"); 692 | free_iox_chunk(iox_subscriber, chunk); 693 | } 694 | 695 | let _data = Box::from_raw(ptr); 696 | // _data goes out of scope and frees the SerData. Nothing more to do here. 697 | } 698 | 699 | #[allow(dead_code)] 700 | unsafe extern "C" fn get_size(serdata: *const ddsi_serdata) -> u32 701 | where 702 | T: Serialize + TopicType, 703 | { 704 | let serdata = SerData::::mut_ref_from_serdata(serdata); 705 | let size = match &serdata.sample { 706 | SampleData::Uninitialized => 0, 707 | SampleData::SDKKey => serdata.key_hash.key_length() as u32, 708 | // This function asks for the serialized size so we do this even for SHM Data 709 | SampleData::SDKData(sample) => { 710 | serdata.serialized_size = 711 | Some((cdr::calc_serialized_size::(sample.deref())) as u32); 712 | *serdata.serialized_size.as_ref().unwrap() 713 | } 714 | SampleData::SHMData(_sample) => { 715 | // we refuse to serialize SHM data so return 0 716 | 0 717 | /* 718 | serdata.serialized_size = Some((cdr::calc_serialized_size::(sample.as_ref())) as u32); 719 | *serdata.serialized_size.as_ref().unwrap() 720 | */ 721 | } 722 | }; 723 | size 724 | } 725 | 726 | #[allow(dead_code)] 727 | unsafe extern "C" fn eqkey( 728 | serdata_a: *const ddsi_serdata, 729 | serdata_b: *const ddsi_serdata, 730 | ) -> bool { 731 | let a = SerData::::mut_ref_from_serdata(serdata_a); 732 | let b = SerData::::mut_ref_from_serdata(serdata_b); 733 | a.key_hash == b.key_hash 734 | } 735 | 736 | #[allow(dead_code)] 737 | unsafe extern "C" fn serdata_to_ser( 738 | serdata: *const ddsi_serdata, 739 | size: size_t, 740 | offset: size_t, 741 | buf: *mut c_void, 742 | ) where 743 | T: Serialize + TopicType, 744 | { 745 | //println!("serdata_to_ser"); 746 | let serdata = SerData::::const_ref_from_serdata(serdata); 747 | let buf = buf as *mut u8; 748 | let buf = buf.add(offset as usize); 749 | 750 | if size == 0 { 751 | return; 752 | } 753 | 754 | match &serdata.sample { 755 | SampleData::Uninitialized => { 756 | panic!("Attempt to serialize uninitialized serdata") 757 | } 758 | SampleData::SDKKey => match &serdata.key_hash { 759 | KeyHash::None => {} 760 | KeyHash::CdrKey(k) => std::ptr::copy_nonoverlapping(k.as_ptr(), buf, size as usize), 761 | KeyHash::RawKey(k) => std::ptr::copy_nonoverlapping(k.as_ptr(), buf, size as usize), 762 | }, 763 | // We may serialize both SDK data as well as SHM Data 764 | SampleData::SDKData(serdata) => { 765 | let buf_slice = std::slice::from_raw_parts_mut(buf, size as usize); 766 | if let Err(e) = cdr::serialize_into::<_, T, _, CdrBe>( 767 | buf_slice, 768 | serdata.deref(), 769 | Bounded(size), 770 | ) { 771 | panic!("Unable to serialize type {:?} due to {}", T::typename(), e); 772 | } 773 | } 774 | SampleData::SHMData(serdata) => { 775 | let buf_slice = std::slice::from_raw_parts_mut(buf, size as usize); 776 | if let Err(e) = cdr::serialize_into::<_, T, _, CdrBe>( 777 | buf_slice, 778 | serdata.as_ref(), 779 | Bounded(size), 780 | ) { 781 | panic!("Unable to serialize type {:?} due to {}", T::typename(), e); 782 | } 783 | } 784 | } 785 | } 786 | 787 | #[allow(dead_code)] 788 | unsafe extern "C" fn serdata_to_ser_ref( 789 | serdata: *const ddsi_serdata, 790 | offset: size_t, 791 | size: size_t, 792 | iov: *mut iovec, 793 | ) -> *mut ddsi_serdata 794 | where 795 | T: Serialize + TopicType, 796 | { 797 | //println!("serdata_to_ser_ref"); 798 | let serdata = SerData::::mut_ref_from_serdata(serdata); 799 | let iov = &mut *iov; 800 | 801 | match &serdata.sample { 802 | SampleData::Uninitialized => panic!("Attempt to serialize uninitialized Sample"), 803 | SampleData::SDKKey => { 804 | let (p, len) = match &serdata.key_hash { 805 | KeyHash::None => (std::ptr::null(), 0), 806 | KeyHash::CdrKey(k) => (k.as_ptr(), k.len()), 807 | KeyHash::RawKey(k) => (k.as_ptr(), k.len()), 808 | }; 809 | 810 | iov.iov_base = p as *mut c_void; 811 | iov.iov_len = len as size_t; 812 | } 813 | SampleData::SDKData(sample) => { 814 | if serdata.cdr.is_none() { 815 | serdata.cdr = serialize_type::(sample, serdata.serialized_size).ok(); 816 | } 817 | if let Some(cdr) = &serdata.cdr { 818 | let offset = offset as usize; 819 | let mut last = offset + size as usize; 820 | if last > cdr.len() - 1 { 821 | last = cdr.len() - 1; 822 | } 823 | let cdr = &cdr[offset..last]; 824 | // cdds rounds up the length into multiple of 4. We mirror that by allocating extra in the 825 | // ``serialize_type`` function. 826 | iov.iov_base = cdr.as_ptr() as *mut c_void; 827 | iov.iov_len = size; //cdr.len() as size_t; 828 | } else { 829 | println!("Serialization error!"); 830 | return std::ptr::null_mut(); 831 | } 832 | } 833 | 834 | SampleData::SHMData(sample) => { 835 | if serdata.cdr.is_none() { 836 | serdata.cdr = serialize_type::(sample.as_ref(), serdata.serialized_size).ok(); 837 | } 838 | if let Some(cdr) = &serdata.cdr { 839 | let offset = offset as usize; 840 | let last = offset + size as usize; 841 | let cdr = &cdr[offset..last]; 842 | iov.iov_base = cdr.as_ptr() as *mut c_void; 843 | iov.iov_len = cdr.len() as size_t; 844 | } else { 845 | println!("Serialization error (SHM)!"); 846 | return std::ptr::null_mut(); 847 | } 848 | } 849 | } 850 | ddsi_serdata_addref(&serdata.serdata) 851 | } 852 | 853 | fn serialize_type(sample: &T, maybe_size: Option) -> Result, ()> { 854 | if let Some(size) = maybe_size { 855 | // Round up allocation to multiple of four 856 | let size = (size + 3) & !3u32; 857 | let mut buffer = Vec::::with_capacity(size as usize); 858 | if let Ok(()) = cdr::serialize_into::<_, T, _, CdrBe>(&mut buffer, sample, Infinite) { 859 | Ok(buffer) 860 | } else { 861 | Err(()) 862 | } 863 | } else if let Ok(data) = cdr::serialize::(sample, Infinite) { 864 | Ok(data) 865 | } else { 866 | Err(()) 867 | } 868 | } 869 | 870 | #[allow(dead_code)] 871 | unsafe extern "C" fn serdata_to_ser_unref(serdata: *mut ddsi_serdata, _iov: *const iovec) { 872 | //println!("serdata_to_ser_unref"); 873 | let serdata = SerData::::mut_ref_from_serdata(serdata); 874 | ddsi_serdata_removeref(&mut serdata.serdata) 875 | } 876 | 877 | fn deserialize_type(data:&[u8]) -> Result,()> 878 | where 879 | T: DeserializeOwned { 880 | cdr::deserialize::>(data).map(Arc::from).map_err(|_e|()) 881 | } 882 | 883 | #[allow(dead_code)] 884 | unsafe extern "C" fn serdata_to_sample( 885 | serdata_ptr: *const ddsi_serdata, 886 | sample: *mut c_void, 887 | _bufptr: *mut *mut c_void, 888 | _buflim: *mut c_void, 889 | ) -> bool 890 | where 891 | T: DeserializeOwned + TopicType, 892 | { 893 | //println!( 894 | // "serdata to sample serdata:{:?} sample:{:?} bufptr:{:?} buflim:{:?}", 895 | // serdata, sample, _bufptr, _buflim 896 | //); 897 | let mut serdata = SerData::::mut_ref_from_serdata(serdata_ptr); 898 | let mut s = Box::>::from_raw(sample as *mut Sample); 899 | assert!(!sample.is_null()); 900 | 901 | //#[cfg(shm)] 902 | let ret = if !serdata.serdata.iox_chunk.is_null() { 903 | // We got data from Iceoryx, deal with it 904 | let hdr = iceoryx_header_from_chunk(serdata.serdata.iox_chunk); 905 | if (*hdr).shm_data_state == iox_shm_data_state_t_IOX_CHUNK_CONTAINS_SERIALIZED_DATA { 906 | // we have to deserialize the data now 907 | let reader = std::slice::from_raw_parts( 908 | serdata.serdata.iox_chunk as *const u8, 909 | (*hdr).data_size as usize, 910 | ); 911 | if serdata.serdata.kind == ddsi_serdata_kind_SDK_KEY { 912 | compute_key_hash(reader, serdata); 913 | serdata.sample = SampleData::SDKKey; 914 | Ok(()) 915 | } else if let Ok(decoded) = deserialize_type::(reader) { 916 | if T::has_key() { 917 | // compute the 16byte key hash 918 | let key_cdr = decoded.key_cdr(); 919 | // skip the four byte header 920 | let key_cdr = &key_cdr[4..]; 921 | compute_key_hash(key_cdr, serdata); 922 | } 923 | //let sample = std::sync::Arc::new(decoded); 924 | //store the deserialized sample in the serdata. We don't need to deserialize again 925 | s.set(decoded.clone()); 926 | serdata.sample = SampleData::SDKData(decoded); 927 | 928 | Ok(()) 929 | } else { 930 | println!("Deserialization error!"); 931 | Err(()) 932 | } 933 | } else { 934 | // Not serialized data, we make a sample out of the data and store it in our sample 935 | assert_eq!((*hdr).data_size as usize, std::mem::size_of::()); 936 | if std::mem::size_of::() == (*hdr).data_size as usize { 937 | // Pay Attention here 938 | // 939 | // 940 | let p: *mut T = serdata.serdata.iox_chunk as *mut T; 941 | serdata.sample = SampleData::SHMData(NonNull::new_unchecked(p)); 942 | Ok(()) 943 | } else { 944 | Err(()) 945 | } 946 | } 947 | } else { 948 | Ok(()) 949 | }; 950 | 951 | let ret = if let Ok(()) = ret { 952 | match &serdata.sample { 953 | SampleData::Uninitialized => true, 954 | SampleData::SDKKey => true, 955 | SampleData::SDKData(_data) => { 956 | s.set_serdata(serdata_ptr as *mut ddsi_serdata); 957 | //s.set(data.clone()); 958 | false 959 | } 960 | SampleData::SHMData(_data) => { 961 | s.set_serdata(serdata_ptr as *mut ddsi_serdata); 962 | //s.set_loaned(data.clone()); 963 | false 964 | } 965 | } 966 | } else { 967 | true 968 | }; 969 | 970 | // leak the sample intentionally so it doesn't get deallocated here 971 | let _intentional_leak = Box::into_raw(s); 972 | ret 973 | } 974 | 975 | #[allow(dead_code)] 976 | unsafe extern "C" fn serdata_to_untyped(serdata: *const ddsi_serdata) -> *mut ddsi_serdata { 977 | //println!("serdata_to_untyped {:?}", serdata); 978 | let serdata = SerData::::mut_ref_from_serdata(serdata); 979 | 980 | //if let SampleData::::SDKData(_d) = &serdata.sample { 981 | let mut untyped_serdata = SerData::::new(serdata.serdata.type_, ddsi_serdata_kind_SDK_KEY); 982 | // untype it 983 | untyped_serdata.serdata.type_ = std::ptr::null_mut(); 984 | untyped_serdata.sample = SampleData::SDKKey; 985 | 986 | //copy the hashes 987 | untyped_serdata.key_hash = serdata.key_hash.clone(); 988 | untyped_serdata.serdata.hash = serdata.serdata.hash; 989 | 990 | let ptr = Box::into_raw(untyped_serdata); 991 | 992 | ptr as *mut ddsi_serdata 993 | //} else { 994 | // println!("Error: Cannot convert from untyped to untyped"); 995 | // std::ptr::null_mut() 996 | //} 997 | } 998 | 999 | #[allow(dead_code)] 1000 | unsafe extern "C" fn untyped_to_sample( 1001 | _sertype: *const ddsi_sertype, 1002 | _serdata: *const ddsi_serdata, 1003 | sample: *mut c_void, 1004 | _buf: *mut *mut c_void, 1005 | _buflim: *mut c_void, 1006 | ) -> bool 1007 | where 1008 | T: TopicType, 1009 | { 1010 | //println!("untyped to sample!"); 1011 | if !sample.is_null() { 1012 | let mut sample = Box::>::from_raw(sample as *mut Sample); 1013 | // hmm. We don't store serialized data in serdata. I'm not really sure how 1014 | // to implement this. For now, invalidate the sample. 1015 | sample.clear(); 1016 | // leak this as we don't want to deallocate it. 1017 | let _leaked = Box::>::into_raw(sample); 1018 | true 1019 | } else { 1020 | false 1021 | } 1022 | } 1023 | 1024 | #[allow(dead_code)] 1025 | unsafe extern "C" fn get_keyhash( 1026 | serdata: *const ddsi_serdata, 1027 | keyhash: *mut ddsi_keyhash, 1028 | _force_md5: bool, 1029 | ) { 1030 | let serdata = SerData::::mut_ref_from_serdata(serdata); 1031 | let keyhash = &mut *keyhash; 1032 | 1033 | let src = match &serdata.key_hash { 1034 | KeyHash::None => &[], 1035 | KeyHash::CdrKey(k) => &k[4..], 1036 | KeyHash::RawKey(k) => &k[..], 1037 | }; 1038 | 1039 | //let source_key_hash = &serdata.key_hash[4..]; 1040 | for (i, b) in src.iter().enumerate() { 1041 | keyhash.value[i] = *b; 1042 | } 1043 | } 1044 | 1045 | #[allow(dead_code)] 1046 | unsafe extern "C" fn print( 1047 | _sertype: *const ddsi_sertype, 1048 | _serdata: *const ddsi_serdata, 1049 | _buf: *mut std::os::raw::c_char, 1050 | _bufsize: size_t, 1051 | ) -> size_t { 1052 | 0 1053 | } 1054 | 1055 | fn create_sertype_ops() -> Box 1056 | where 1057 | T: TopicType, 1058 | { 1059 | Box::new(ddsi_sertype_ops { 1060 | version: Some(ddsi_sertype_v0), 1061 | arg: std::ptr::null_mut(), 1062 | free: Some(free_sertype::), 1063 | zero_samples: Some(zero_samples::), 1064 | realloc_samples: Some(realloc_samples::), 1065 | free_samples: Some(free_samples::), 1066 | equal: Some(equal::), 1067 | hash: Some(hash::), 1068 | ..Default::default() 1069 | }) 1070 | } 1071 | 1072 | #[cfg(feature = "shm")] 1073 | #[allow(dead_code)] 1074 | unsafe extern "C" fn get_sample_size(serdata: *const ddsi_serdata) -> u32 { 1075 | let serdata = *serdata; 1076 | (*serdata.type_).iox_size 1077 | } 1078 | 1079 | #[cfg(feature = "shm")] 1080 | #[allow(dead_code)] 1081 | unsafe extern "C" fn from_iox_buffer( 1082 | sertype: *const ddsi_sertype, 1083 | kind: ddsi_serdata_kind, 1084 | /*_deserialize_hint : bool,*/ 1085 | sub: *mut ::std::os::raw::c_void, 1086 | buffer: *mut ::std::os::raw::c_void, 1087 | ) -> *mut ddsi_serdata { 1088 | //println!("from_iox_buffer"); 1089 | 1090 | if sertype.is_null() { 1091 | return std::ptr::null::() as *mut ddsi_serdata; 1092 | } 1093 | 1094 | let mut d = SerData::::new(sertype, kind); 1095 | 1096 | // from loaned sample, just take the pointer 1097 | if sub.is_null() { 1098 | d.serdata.iox_chunk = buffer; 1099 | } else { 1100 | //println!("from_iox_buffer: take pointer {:?}from iox", buffer); 1101 | // from iox buffer 1102 | d.serdata.iox_chunk = buffer; 1103 | d.serdata.iox_subscriber = sub; 1104 | let hdr = iceoryx_header_from_chunk(buffer); 1105 | // Copy the key hash (TODO: Check this) 1106 | copy_raw_key_hash(&(*hdr).keyhash.value, &mut d); 1107 | } 1108 | 1109 | // we don't deserialize right away 1110 | d.sample = SampleData::SHMData(NonNull::new_unchecked(buffer as *mut T)); 1111 | 1112 | let ptr = Box::into_raw(d); 1113 | // only we know this ddsi_serdata is really of type SerData 1114 | ptr as *mut ddsi_serdata 1115 | } 1116 | 1117 | fn create_serdata_ops() -> Box 1118 | where 1119 | T: DeserializeOwned + TopicType + Serialize , 1120 | { 1121 | Box::new(ddsi_serdata_ops { 1122 | eqkey: Some(eqkey::), 1123 | get_size: Some(get_size::), 1124 | from_ser: Some(serdata_from_fragchain::), 1125 | from_ser_iov: Some(serdata_from_iov::), 1126 | from_keyhash: Some(serdata_from_keyhash::), 1127 | from_sample: Some(serdata_from_sample::), 1128 | to_ser: Some(serdata_to_ser::), 1129 | to_ser_ref: Some(serdata_to_ser_ref::), 1130 | to_ser_unref: Some(serdata_to_ser_unref::), 1131 | to_sample: Some(serdata_to_sample::), 1132 | to_untyped: Some(serdata_to_untyped::), 1133 | untyped_to_sample: Some(untyped_to_sample::), 1134 | free: Some(free_serdata::), 1135 | print: Some(print::), 1136 | get_keyhash: Some(get_keyhash::), 1137 | #[cfg(feature = "shm")] 1138 | get_sample_size: Some(get_sample_size), 1139 | #[cfg(feature = "shm")] 1140 | from_iox_buffer: Some(from_iox_buffer::), 1141 | ..Default::default() 1142 | }) 1143 | } 1144 | 1145 | // not sure what this needs to do. The C++ implementation at 1146 | // https://github.com/eclipse-cyclonedds/cyclonedds-cxx/blob/templated-streaming/src/ddscxx/include/org/eclipse/cyclonedds/topic/datatopic.hpp 1147 | // just returns 0 1148 | // Update! : Now I understand this after debugging crashes when stress testing 1149 | // with a large number of types being published. This hash is used as the hash 1150 | // lookup in hopscotch.c. 1151 | // /* 1152 | // * The hopscotch hash table is dependent on a proper functioning hash. 1153 | // * If the hash function generates a lot of hash collisions, then it will 1154 | // * not be able to handle that by design. 1155 | // * It is capable of handling some collisions, but not more than 32 per 1156 | // * bucket (less, when other hash values are clustered around the 1157 | // * collision value). 1158 | // * When proper distributed hash values are generated, then hopscotch 1159 | // * works nice and quickly. 1160 | // */ 1161 | 1162 | unsafe extern "C" fn hash(tp: *const ddsi_sertype) -> u32 1163 | { 1164 | if let Some(ser_type) = SerType::::try_from_sertype(tp) { 1165 | let type_name = CStr::from_ptr(ser_type.sertype.type_name); 1166 | let type_name_bytes = type_name.to_bytes(); 1167 | let type_size = core::mem::size_of::().to_ne_bytes(); 1168 | let sg_list = [type_name_bytes,&type_size]; 1169 | let mut sg_buffer = SGReader::new(&sg_list); 1170 | 1171 | let hash = murmur3_32(&mut sg_buffer, 0); 1172 | 1173 | let _intentional_leak = SerType::::into_sertype(ser_type); 1174 | hash.unwrap_or(0) 1175 | 1176 | } else { 1177 | 0 1178 | } 1179 | } 1180 | 1181 | unsafe extern "C" fn equal(acmn: *const ddsi_sertype, bcmn: *const ddsi_sertype) -> bool { 1182 | let acmn = CStr::from_ptr((*acmn).type_name as *mut std::os::raw::c_char); 1183 | let bcmn = CStr::from_ptr((*bcmn).type_name as *mut std::os::raw::c_char); 1184 | acmn == bcmn 1185 | } 1186 | 1187 | #[derive(Clone)] 1188 | enum SampleData { 1189 | Uninitialized, 1190 | SDKKey, 1191 | SDKData(std::sync::Arc), 1192 | SHMData(NonNull), 1193 | } 1194 | 1195 | impl Default for SampleData { 1196 | fn default() -> Self { 1197 | Self::Uninitialized 1198 | } 1199 | } 1200 | 1201 | 1202 | #[derive(PartialEq, Clone)] 1203 | enum KeyHash { 1204 | None, 1205 | CdrKey([u8; 20]), 1206 | RawKey([u8; 16]), 1207 | } 1208 | 1209 | impl Default for KeyHash { 1210 | fn default() -> Self { 1211 | Self::None 1212 | } 1213 | } 1214 | 1215 | impl KeyHash { 1216 | fn get_key_hash(&self) -> &[u8] { 1217 | match self { 1218 | KeyHash::None => &[], 1219 | KeyHash::CdrKey(cdr_key_hash) => cdr_key_hash, 1220 | KeyHash::RawKey(raw_key_hash) => raw_key_hash, 1221 | } 1222 | } 1223 | fn key_length(&self) -> usize { 1224 | match self { 1225 | KeyHash::CdrKey(k) => k.len(), 1226 | KeyHash::RawKey(k) => k.len(), 1227 | _ => 0, 1228 | } 1229 | } 1230 | } 1231 | 1232 | /// A representation for the serialized data. 1233 | #[repr(C)] 1234 | pub (crate)struct SerData { 1235 | serdata: ddsi_serdata, 1236 | sample: SampleData, 1237 | //data in CDR format. This is put into an option as we only create 1238 | //the serialized version when we need it 1239 | cdr: Option>, 1240 | //key_hash: ddsi_keyhash, 1241 | // include 4 bytes of CDR encapsulation header 1242 | //key_hash: [u8; 20], 1243 | key_hash: KeyHash, 1244 | // We store the serialized size here if available 1245 | serialized_size: Option, 1246 | } 1247 | 1248 | impl<'a, T> SerData { 1249 | fn new(sertype: *const ddsi_sertype, kind: u32) -> Box> { 1250 | Box::>::new(SerData { 1251 | serdata: { 1252 | let mut data = std::mem::MaybeUninit::uninit(); 1253 | unsafe { 1254 | ddsi_serdata_init(data.as_mut_ptr(), sertype, kind); 1255 | data.assume_init() 1256 | } 1257 | }, 1258 | sample: SampleData::default(), 1259 | cdr: None, 1260 | key_hash: KeyHash::default(), 1261 | serialized_size: None, 1262 | }) 1263 | } 1264 | 1265 | fn const_ref_from_serdata(serdata: *const ddsi_serdata) -> &'a Self { 1266 | let ptr = serdata as *const SerData; 1267 | unsafe { &*ptr } 1268 | } 1269 | 1270 | fn mut_ref_from_serdata(serdata: *const ddsi_serdata) -> &'a mut Self { 1271 | let ptr = serdata as *mut SerData; 1272 | unsafe { &mut *ptr } 1273 | } 1274 | } 1275 | 1276 | impl Clone for SerData { 1277 | fn clone(&self) -> Self { 1278 | Self { 1279 | serdata: { 1280 | let mut newdata = self.serdata; 1281 | unsafe {ddsi_serdata_addref(&mut newdata)}; 1282 | newdata 1283 | }, sample: match &self.sample { 1284 | SampleData::Uninitialized => SampleData::Uninitialized, 1285 | SampleData::SDKKey => SampleData::SDKKey, 1286 | SampleData::SDKData(d) => SampleData::SDKData(d.clone()), 1287 | SampleData::SHMData(d) => SampleData::SHMData(*d), 1288 | }, cdr: self.cdr.clone(), key_hash: self.key_hash.clone(), serialized_size: self.serialized_size } 1289 | } 1290 | } 1291 | 1292 | 1293 | 1294 | /* These functions are created from the macros in 1295 | https://github.com/eclipse-cyclonedds/cyclonedds/blob/f879dc0ef56eb00857c0cbb66ee87c577ff527e8/src/core/ddsi/include/dds/ddsi/q_radmin.h#L108 1296 | Bad things will happen if these macros change. 1297 | Some discussions here: https://github.com/eclipse-cyclonedds/cyclonedds/issues/830 1298 | */ 1299 | fn nn_rdata_payload_offset(rdata: *const nn_rdata) -> usize { 1300 | unsafe { (*rdata).payload_zoff as usize } 1301 | } 1302 | 1303 | fn nn_rmsg_payload(rmsg: *const nn_rmsg) -> *const u8 { 1304 | unsafe { rmsg.add(1) as *const u8 } 1305 | } 1306 | 1307 | fn nn_rmsg_payload_offset(rmsg: *const nn_rmsg, offset: usize) -> *const u8 { 1308 | unsafe { nn_rmsg_payload(rmsg).add(offset) } 1309 | } 1310 | 1311 | /// A reader for a list of scatter gather buffers 1312 | struct SGReader<'a> { 1313 | sc_list: Option< &'a[&'a [u8]]>, 1314 | //the current slice that is used 1315 | slice_cursor: usize, 1316 | //the current offset within the slice 1317 | slice_offset: usize, 1318 | } 1319 | 1320 | impl<'a> SGReader<'a> { 1321 | pub fn new(sc_list: &'a[&'a [u8]]) -> Self { 1322 | SGReader { 1323 | sc_list: Some(sc_list), 1324 | slice_cursor: 0, 1325 | slice_offset: 0, 1326 | } 1327 | } 1328 | } 1329 | 1330 | impl<'a> Read for SGReader<'a> { 1331 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 1332 | let read_buf_len = buf.len(); 1333 | if self.sc_list.is_some() { 1334 | let source_slice = self.sc_list.as_ref().unwrap()[self.slice_cursor]; 1335 | let num_slices = self.sc_list.as_ref().unwrap().len(); 1336 | let source_slice_rem = source_slice.len() - self.slice_offset; 1337 | let source_slice = &source_slice[self.slice_offset..]; 1338 | 1339 | let copy_length = std::cmp::min(source_slice_rem, read_buf_len); 1340 | 1341 | //copy the bytes, lengths have to be the same 1342 | buf[..copy_length].copy_from_slice(&source_slice[..copy_length]); 1343 | 1344 | if copy_length == source_slice_rem { 1345 | // we have completed this slice. move to the next 1346 | self.slice_cursor += 1; 1347 | self.slice_offset = 0; 1348 | 1349 | if self.slice_cursor >= num_slices { 1350 | //no more slices, invalidate the sc_list 1351 | let _ = self.sc_list.take(); 1352 | } 1353 | } else { 1354 | // we have not completed the current slice, just bump up the slice offset 1355 | self.slice_offset += copy_length; 1356 | } 1357 | 1358 | Ok(copy_length) 1359 | } else { 1360 | // No more data 1361 | Ok(0) 1362 | } 1363 | } 1364 | } 1365 | 1366 | #[cfg(test)] 1367 | mod test { 1368 | use super::*; 1369 | use crate::{DdsListener, DdsParticipant, DdsQos, DdsTopic}; 1370 | use cdds_derive::Topic; 1371 | use serde_derive::{Deserialize, Serialize}; 1372 | use std::ffi::CString; 1373 | 1374 | #[test] 1375 | fn scatter_gather() { 1376 | let a = vec![1, 2, 3, 4, 5, 6]; 1377 | let b = vec![7, 8, 9, 10, 11]; 1378 | let c = vec![12, 13, 14, 15]; 1379 | let d = vec![16, 17, 18, 19, 20, 21]; 1380 | 1381 | let sla = unsafe { std::slice::from_raw_parts(a.as_ptr(), a.len()) }; 1382 | let slb = unsafe { std::slice::from_raw_parts(b.as_ptr(), b.len()) }; 1383 | let slc = unsafe { std::slice::from_raw_parts(c.as_ptr(), c.len()) }; 1384 | let sld = unsafe { std::slice::from_raw_parts(d.as_ptr(), d.len()) }; 1385 | 1386 | let sc_list = vec![sla, slb, slc, sld]; 1387 | 1388 | let mut reader = SGReader::new(&sc_list); 1389 | 1390 | let mut buf = vec![0, 0, 0, 0, 0]; 1391 | if let Ok(n) = reader.read(&mut buf) { 1392 | assert_eq!(&buf[..n], vec![1, 2, 3, 4, 5]); 1393 | } else { 1394 | panic!("should not panic"); 1395 | } 1396 | if let Ok(n) = reader.read(&mut buf) { 1397 | assert_eq!(&buf[..n], vec![6]); 1398 | } else { 1399 | panic!("should not panic"); 1400 | } 1401 | } 1402 | 1403 | #[test] 1404 | fn keyhash_basic() { 1405 | #[derive(Serialize, Deserialize, Topic, Default)] 1406 | struct Foo { 1407 | #[topic_key] 1408 | id: i32, 1409 | x: u32, 1410 | y: u32, 1411 | } 1412 | let foo = Foo { 1413 | id: 0x12345678, 1414 | x: 10, 1415 | y: 20, 1416 | }; 1417 | let key_cdr = foo.key_cdr(); 1418 | assert_eq!(key_cdr, vec![0, 0, 0, 0, 0x12u8, 0x34u8, 0x56u8, 0x78u8]); 1419 | } 1420 | #[test] 1421 | fn keyhash_simple() { 1422 | #[derive(Serialize, Deserialize, Topic, Default)] 1423 | struct Foo { 1424 | #[topic_key] 1425 | id: i32, 1426 | x: u32, 1427 | #[topic_key] 1428 | s: String, 1429 | y: u32, 1430 | } 1431 | let foo = Foo { 1432 | id: 0x12345678, 1433 | x: 10, 1434 | s: String::from("boo"), 1435 | y: 20, 1436 | }; 1437 | let key_cdr = foo.key_cdr(); 1438 | assert_eq!( 1439 | key_cdr, 1440 | vec![0, 0, 0, 0, 18, 52, 86, 120, 0, 0, 0, 4, 98, 111, 111, 0] 1441 | ); 1442 | } 1443 | 1444 | #[test] 1445 | fn keyhash_nested() { 1446 | #[derive(Serialize, Deserialize, Topic, Default)] 1447 | struct NestedFoo { 1448 | name: String, 1449 | val: u64, 1450 | #[topic_key] 1451 | instance: u32, 1452 | } 1453 | 1454 | assert_eq!( 1455 | NestedFoo::typename(), 1456 | CString::new("serdes::test::keyhash_nested::NestedFoo").unwrap() 1457 | ); 1458 | 1459 | impl NestedFoo { 1460 | fn new() -> Self { 1461 | Self { 1462 | name: "my name".to_owned(), 1463 | val: 42, 1464 | instance: 25, 1465 | } 1466 | } 1467 | } 1468 | 1469 | #[derive(Serialize, Deserialize, Topic, Default)] 1470 | struct Foo { 1471 | #[topic_key] 1472 | id: i32, 1473 | x: u32, 1474 | #[topic_key] 1475 | s: String, 1476 | y: u32, 1477 | #[topic_key] 1478 | inner: NestedFoo, 1479 | } 1480 | let foo = Foo { 1481 | id: 0x12345678, 1482 | x: 10, 1483 | s: String::from("boo"), 1484 | y: 20, 1485 | inner: NestedFoo::new(), 1486 | }; 1487 | let key_cdr = foo.key_cdr(); 1488 | assert_eq!( 1489 | key_cdr, 1490 | vec![0, 0, 0, 0, 18, 52, 86, 120, 0, 0, 0, 4, 98, 111, 111, 0, 0, 0, 0, 25] 1491 | ); 1492 | } 1493 | 1494 | #[test] 1495 | fn primitive_array_as_key() { 1496 | #[derive(Serialize, Deserialize, Topic, Default)] 1497 | struct Foo { 1498 | #[topic_key] 1499 | a: [u8; 8], 1500 | b: u32, 1501 | c: String, 1502 | } 1503 | 1504 | let foo = Foo { 1505 | a: [0, 0, 0, 0, 0, 0, 0, 0], 1506 | b: 42, 1507 | c: "foo".to_owned(), 1508 | }; 1509 | 1510 | let key_cdr = foo.key_cdr(); 1511 | assert_eq!(key_cdr, vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); 1512 | assert_eq!(false, Foo::force_md5_keyhash()); 1513 | } 1514 | 1515 | #[test] 1516 | fn primitive_array_and_string_as_key() { 1517 | #[derive(Serialize, Deserialize, Topic, Default)] 1518 | struct Foo { 1519 | #[topic_key] 1520 | a: [u8; 8], 1521 | b: u32, 1522 | #[topic_key] 1523 | c: String, 1524 | } 1525 | 1526 | let foo = Foo { 1527 | a: [0, 0, 0, 0, 0, 0, 0, 0], 1528 | b: 42, 1529 | c: "foo".to_owned(), 1530 | }; 1531 | 1532 | let key_cdr = foo.key_cdr(); 1533 | assert_eq!( 1534 | key_cdr, 1535 | vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 102, 111, 111, 0] 1536 | ); 1537 | assert_eq!(true, Foo::force_md5_keyhash()); 1538 | } 1539 | 1540 | #[test] 1541 | fn basic() { 1542 | #[derive(Serialize, Deserialize, Topic, Default)] 1543 | struct NestedFoo { 1544 | name: String, 1545 | val: u64, 1546 | #[topic_key] 1547 | instance: u32, 1548 | } 1549 | 1550 | impl NestedFoo { 1551 | fn new() -> Self { 1552 | Self { 1553 | name: "my name".to_owned(), 1554 | val: 42, 1555 | instance: 25, 1556 | } 1557 | } 1558 | } 1559 | 1560 | #[derive(Serialize, Deserialize, Topic, Default)] 1561 | struct Foo { 1562 | #[topic_key] 1563 | id: i32, 1564 | x: u32, 1565 | #[topic_key] 1566 | s: String, 1567 | y: u32, 1568 | #[topic_key] 1569 | inner: NestedFoo, 1570 | } 1571 | let _foo = Foo { 1572 | id: 0x12345678, 1573 | x: 10, 1574 | s: String::from("boo"), 1575 | y: 20, 1576 | inner: NestedFoo::new(), 1577 | }; 1578 | let t = SerType::::new(); 1579 | let mut t = SerType::into_sertype(t); 1580 | let tt = &mut t as *mut *mut ddsi_sertype; 1581 | unsafe { 1582 | let p = dds_create_participant(0, std::ptr::null_mut(), std::ptr::null_mut()); 1583 | let topic_name = CString::new("topic_name").unwrap(); 1584 | let topic = dds_create_topic_sertype( 1585 | p, 1586 | topic_name.as_ptr(), 1587 | tt, 1588 | std::ptr::null_mut(), 1589 | std::ptr::null_mut(), 1590 | std::ptr::null_mut(), 1591 | ); 1592 | 1593 | dds_delete(topic); 1594 | } 1595 | } 1596 | } 1597 | --------------------------------------------------------------------------------