diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19fd5bf..fb50e64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,3 +72,15 @@ jobs: env: RUSTDOCFLAGS: "--deny warnings" run: cargo doc --no-deps --all-features --document-private-items + + build-cxx: + name: cxx (build) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Build and sanity test + run: | + cd dcsctp-cxx/ + make clean all run diff --git a/.gitignore b/.gitignore index a17a46b..af24138 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ +/dcsctp-cxx/examples/main /fuzz/artifacts/ /fuzz/corpus/ /fuzz/coverage/ /fuzz/target/ /target/ +*.o diff --git a/Cargo.lock b/Cargo.lock index b093eb1..345d469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,24 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + [[package]] name = "anyhow" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "bitflags" version = "2.9.1" @@ -20,12 +32,60 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "crc-any" version = "2.5.0" @@ -35,6 +95,68 @@ dependencies = [ "debug-helper", ] +[[package]] +name = "cxx" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2722cab56883f16c0c24bbb4e5e1ba12a84da2cc0c35a0fd71c68e10c191885c" +dependencies = [ + "cc", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86a2c65409086e3aa87dceefa608dee59e8b5091edfe25a12e7a0ca0b639b36" +dependencies = [ + "cc", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00fcff13f8198c499f21412ce5a2fb2171905ddc9340f745c7e795cbc5b18f9" +dependencies = [ + "clap", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4efe5cd204b7fef69b824821276b21523a354e4362fec04efc6614cb2eba23" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3dc1caaebce3df14e8527f08db145814b02b6d27b8cc1972c012a843adc3a7" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "dcsctp" version = "0.1.1" @@ -47,6 +169,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "dcsctp-cxx" +version = "0.1.0" +dependencies = [ + "cxx", + "cxx-build", + "dcsctp", +] + +[[package]] +name = "dcsctp-fuzz" +version = "0.0.0" +dependencies = [ + "dcsctp", + "libfuzzer-sys", +] + [[package]] name = "debug-helper" version = "0.3.13" @@ -59,6 +198,18 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "getrandom" version = "0.3.3" @@ -71,6 +222,22 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "indexmap" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itertools" version = "0.14.0" @@ -80,12 +247,40 @@ dependencies = [ "either", ] +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212" +dependencies = [ + "cc", +] + [[package]] name = "log" version = "0.4.27" @@ -154,6 +349,50 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.95" @@ -165,6 +404,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "2.0.14" @@ -191,6 +439,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -200,6 +454,95 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "winapi-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index c8d8070..df73c7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,10 @@ +[workspace] +members = [ + ".", + "dcsctp-cxx", + "fuzz", +] + [package] name = "dcsctp" description = "An SCTP implementation for WebRTC Data Channels" diff --git a/dcsctp-cxx/Cargo.toml b/dcsctp-cxx/Cargo.toml new file mode 100644 index 0000000..c3b6d57 --- /dev/null +++ b/dcsctp-cxx/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "dcsctp-cxx" +version = "0.1.0" +publish = false +description = "C++ FFI for dcsctp" +edition = "2021" + +[dependencies] +dcsctp = { path = ".." } +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" + +[lib] +crate-type = ["staticlib"] diff --git a/dcsctp-cxx/Makefile b/dcsctp-cxx/Makefile new file mode 100644 index 0000000..127a5cd --- /dev/null +++ b/dcsctp-cxx/Makefile @@ -0,0 +1,22 @@ +.PHONY: all clean run + +all: examples/main + +examples/main: examples/main.cpp Cargo.toml src/lib.rs src/lib.cpp include/dcsctp.h + @echo "Building rust library..." + @cargo build + @echo "Building C++ example..." + @CXX_INCLUDE_PATH=$$(find ../target/debug/build/dcsctp-cxx-*/out/cxxbridge/include -print -quit) && \ + c++ -std=c++14 \ + -Iinclude \ + -I$${CXX_INCLUDE_PATH} \ + examples/main.cpp \ + -o examples/main \ + ../target/debug/libdcsctp_cxx.a + +run: examples/main + ./examples/main + +clean: + @cargo clean + @rm -f examples/main diff --git a/dcsctp-cxx/build.rs b/dcsctp-cxx/build.rs new file mode 100644 index 0000000..b434b45 --- /dev/null +++ b/dcsctp-cxx/build.rs @@ -0,0 +1,7 @@ +fn main() { + cxx_build::bridge("src/lib.rs").file("src/lib.cpp").std("c++14").compile("dcsctp-cxx"); + + println!("cargo:rerun-if-changed=src/lib.rs"); + println!("cargo:rerun-if-changed=src/lib.cpp"); + println!("cargo:rerun-if-changed=include/dcsctp.h"); +} diff --git a/dcsctp-cxx/examples/main.cpp b/dcsctp-cxx/examples/main.cpp new file mode 100644 index 0000000..de86fbf --- /dev/null +++ b/dcsctp-cxx/examples/main.cpp @@ -0,0 +1,26 @@ +#include "dcsctp.h" +#include + +int main() { + std::cout << "dcsctp version: " << dcsctp_cxx::version().c_str() << std::endl; + + dcsctp_cxx::DcSctpSocket *socket = dcsctp_cxx::new_socket(); + + if (socket) { + std::cout << "Successfully created a socket." << std::endl; + } else { + std::cout << "Failed to create a socket." << std::endl; + return 1; + } + + if (dcsctp_cxx::state(*socket) == dcsctp_cxx::SocketState::Closed) { + std::cout << "Socket is initially closed" << std::endl; + } else { + std::cout << "Socket is in an unexpected state" << std::endl; + return 1; + } + + std::cout << "Socket is about to be deleted." << std::endl; + dcsctp_cxx::delete_socket(socket); + return 0; +} diff --git a/dcsctp-cxx/include/dcsctp.h b/dcsctp-cxx/include/dcsctp.h new file mode 100644 index 0000000..3f3c59a --- /dev/null +++ b/dcsctp-cxx/include/dcsctp.h @@ -0,0 +1,3 @@ +#pragma once + +#include "dcsctp-cxx/src/lib.rs.h" diff --git a/dcsctp-cxx/src/lib.cpp b/dcsctp-cxx/src/lib.cpp new file mode 100644 index 0000000..5dfa8b9 --- /dev/null +++ b/dcsctp-cxx/src/lib.cpp @@ -0,0 +1 @@ +#include "dcsctp-cxx/src/lib.rs.h" diff --git a/dcsctp-cxx/src/lib.rs b/dcsctp-cxx/src/lib.rs new file mode 100644 index 0000000..2d1aa01 --- /dev/null +++ b/dcsctp-cxx/src/lib.rs @@ -0,0 +1,54 @@ +use dcsctp::api::DcSctpSocket as DcSctpSocketTrait; +use dcsctp::api::Options; +use dcsctp::api::SocketState as DcSctpSocketState; +use std::time::Instant; + +#[cxx::bridge(namespace = "dcsctp_cxx")] +mod ffi { + #[derive(Debug)] + enum SocketState { + Closed, + Connecting, + Connected, + ShuttingDown, + } + + extern "Rust" { + type DcSctpSocket; + + fn version() -> String; + fn new_socket() -> *mut DcSctpSocket; + unsafe fn delete_socket(socket: *mut DcSctpSocket); + fn state(socket: &DcSctpSocket) -> SocketState; + } +} + +pub struct DcSctpSocket(Box); + +fn version() -> String { + dcsctp::version().to_string() +} + +fn new_socket() -> *mut DcSctpSocket { + let options = Options::default(); + let socket = dcsctp::new_socket("cxx-socket", Instant::now(), &options); + let boxed_socket = Box::new(DcSctpSocket(socket)); + Box::into_raw(boxed_socket) +} + +unsafe fn delete_socket(socket: *mut DcSctpSocket) { + if !socket.is_null() { + // SAFETY: The `socket` pointer must have been obtained from `new_socket` and must not be + // used after this call. + drop(Box::from_raw(socket)); + } +} + +fn state(socket: &DcSctpSocket) -> ffi::SocketState { + match socket.0.state() { + DcSctpSocketState::Closed => ffi::SocketState::Closed, + DcSctpSocketState::Connecting => ffi::SocketState::Connecting, + DcSctpSocketState::Connected => ffi::SocketState::Connected, + DcSctpSocketState::ShuttingDown => ffi::SocketState::ShuttingDown, + } +} diff --git a/src/lib.rs b/src/lib.rs index 330499e..c39e350 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,10 @@ #![deny(warnings)] #![forbid(unsafe_code)] +use crate::api::DcSctpSocket; +use crate::api::Options; +use std::time::Instant; + pub mod api; pub(crate) mod events; pub(crate) mod packet; @@ -37,6 +41,19 @@ trait EventSink { #[cfg(test)] pub(crate) mod testing; +/// Returns the version of this crate. +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + +/// Creates a new `Socket`. +/// +/// The provided `name` is only used for logging to identify this socket, and `start_time` +/// is the initial time, used as a basline for all time-based operations. +pub fn new_socket(name: &str, start_time: Instant, options: &Options) -> Box { + Box::new(socket::Socket::new(name, start_time, options)) +} + // Fuzzers, who are defined in a separate crate, need to access internal (non-public) functions that // they will fuzz. Expose these only for the fuzzing configuration. #[cfg(feature = "fuzz-internals")]