Add rust bindings (#98)
* add rust bindings to norm * modify PR description * remove PR description since it is redundant * remove weird character * remove __pycache__ and add __pycache__ to gitignore --------- Co-authored-by: cbowers <cbowers@anduril.com>pull/100/head
parent
b0c38b4184
commit
fcd2554051
|
|
@ -20,6 +20,7 @@ norp/makefiles/norp
|
||||||
|
|
||||||
# python build files
|
# python build files
|
||||||
src/pynorm.egg-info
|
src/pynorm.egg-info
|
||||||
|
**/__pycache__/
|
||||||
|
|
||||||
# build java files
|
# build java files
|
||||||
*.class
|
*.class
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
# NORM Rust Bindings API Guide
|
||||||
|
|
||||||
|
This guide provides a detailed overview of the main components of the NORM Rust bindings API.
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
The NORM Rust API follows a hierarchical structure similar to the underlying C API but with
|
||||||
|
Rust idioms and safety guarantees:
|
||||||
|
|
||||||
|
```
|
||||||
|
Instance
|
||||||
|
└── Session
|
||||||
|
├── Object (Data)
|
||||||
|
├── Object (File)
|
||||||
|
└── Object (Stream)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instance
|
||||||
|
|
||||||
|
`Instance` is the top-level object that represents a NORM protocol instance. It's the starting point
|
||||||
|
for all NORM operations.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use norm::Instance;
|
||||||
|
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?; // false = no priority boost
|
||||||
|
|
||||||
|
// Set cache directory for receiving files
|
||||||
|
instance.set_cache_directory("/tmp/norm")?;
|
||||||
|
|
||||||
|
// Process events
|
||||||
|
for event in instance.events() {
|
||||||
|
// Handle events
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session
|
||||||
|
|
||||||
|
A `Session` represents a NORM protocol session, which can operate in sender mode, receiver mode, or both.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use norm::{Instance, Session};
|
||||||
|
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Create a session with address, port, and node ID
|
||||||
|
let session = instance.create_session("224.1.2.3", 6003, 1)?;
|
||||||
|
|
||||||
|
// Configure session
|
||||||
|
session
|
||||||
|
.set_tx_rate(1_000_000.0) // 1 Mbps
|
||||||
|
.set_ttl(64)?
|
||||||
|
.set_loopback(true)?;
|
||||||
|
|
||||||
|
// Start sender
|
||||||
|
session.start_sender(
|
||||||
|
rand::random(), // Session ID
|
||||||
|
1024 * 1024, // Buffer space (1 MB)
|
||||||
|
1400, // Segment size
|
||||||
|
64, // Data segments per block
|
||||||
|
16, // Parity segments per block
|
||||||
|
None, // FEC ID (default = 0)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Or start receiver
|
||||||
|
session.start_receiver(1024 * 1024)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Objects
|
||||||
|
|
||||||
|
NORM supports three types of objects for data transfer:
|
||||||
|
|
||||||
|
1. **Data Objects**: For memory buffer transfers
|
||||||
|
2. **File Objects**: For file transfers
|
||||||
|
3. **Stream Objects**: For continuous data streaming
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Send data
|
||||||
|
let data = b"Hello, NORM!";
|
||||||
|
let data_obj = session.data_enqueue(data, Some(b"Info"))?;
|
||||||
|
|
||||||
|
// Send file
|
||||||
|
let file_obj = session.file_enqueue("/path/to/file.txt", Some(b"file.txt"))?;
|
||||||
|
|
||||||
|
// Open stream
|
||||||
|
let stream_obj = session.stream_open(64 * 1024, Some(b"Stream info"))?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
NORM uses an event-based model for signaling state changes. The Rust bindings provide an iterator-based approach for event handling.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Process events using iterator
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::RxObjectCompleted => {
|
||||||
|
let object = Object::from_handle_unowned(event.object);
|
||||||
|
|
||||||
|
// Handle based on object type
|
||||||
|
match object.get_type() {
|
||||||
|
ObjectType::Data => {
|
||||||
|
if let Ok(data) = object.access_data() {
|
||||||
|
println!("Received data: {:?}", data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ObjectType::File => {
|
||||||
|
if let Ok(info) = object.get_info() {
|
||||||
|
println!("Received file: {}", String::from_utf8_lossy(&info));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EventType::TxObjectSent => println!("Object sent"),
|
||||||
|
_ => {} // Handle other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multicast Configuration
|
||||||
|
|
||||||
|
The Rust bindings provide an ergonomic API for configuring multicast:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use norm::{multicast, MulticastExt};
|
||||||
|
|
||||||
|
// Configure multicast with builder pattern
|
||||||
|
let config = multicast!("224.1.2.3", 6003, {
|
||||||
|
ttl: 64,
|
||||||
|
interface: "eth0",
|
||||||
|
loopback: true,
|
||||||
|
ssm_source: "192.168.1.1",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply configuration to session
|
||||||
|
session.with_multicast(&config)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
All operations that might fail return a `Result<T, Error>` type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
match instance.set_cache_directory("/nonexistent/path") {
|
||||||
|
Ok(()) => println!("Cache directory set"),
|
||||||
|
Err(e) => match e {
|
||||||
|
Error::FileError(msg) => println!("File error: {}", msg),
|
||||||
|
Error::InvalidParameter => println!("Invalid parameter"),
|
||||||
|
_ => println!("Other error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ownership and Lifetimes
|
||||||
|
|
||||||
|
The Rust bindings use RAII (Resource Acquisition Is Initialization) to ensure proper resource management:
|
||||||
|
|
||||||
|
- `Instance`, `Session`, and `Object` implement `Drop` to automatically clean up resources
|
||||||
|
- Objects created directly have ownership and will be freed when dropped
|
||||||
|
- Objects obtained from events are not owned and are marked as such
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Owned object from direct creation
|
||||||
|
let data_obj = session.data_enqueue(data, None)?;
|
||||||
|
// Will be released when data_obj goes out of scope
|
||||||
|
|
||||||
|
// Non-owned object from event
|
||||||
|
let object = Object::from_handle_unowned(event.object);
|
||||||
|
// Will NOT be released when object goes out of scope
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utility Functions
|
||||||
|
|
||||||
|
The API provides several utility functions:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Check if an address is a multicast address
|
||||||
|
let is_mcast = is_multicast_address("224.1.2.3");
|
||||||
|
|
||||||
|
// Get NORM version
|
||||||
|
let (major, minor, patch) = norm::version();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Custom Memory Allocation
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe {
|
||||||
|
instance.set_allocation_functions(
|
||||||
|
my_alloc_function,
|
||||||
|
my_free_function
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Transfers
|
||||||
|
|
||||||
|
When receiving files, you must set a cache directory:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
instance.set_cache_directory("/tmp/norm_files")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream Management
|
||||||
|
|
||||||
|
For stream objects, additional methods are available on the `Object` type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Open a stream
|
||||||
|
let stream = session.stream_open(64 * 1024, Some(b"Stream info"))?;
|
||||||
|
|
||||||
|
// Write to stream
|
||||||
|
let bytes_written = stream.stream_write(b"Hello, stream!")?;
|
||||||
|
|
||||||
|
// Check if stream has space for more data
|
||||||
|
if stream.stream_has_vacancy()? {
|
||||||
|
stream.stream_write(b"More data")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark end of message
|
||||||
|
stream.stream_mark_eom()?;
|
||||||
|
|
||||||
|
// Flush stream with end-of-message marker
|
||||||
|
stream.stream_flush(true, FlushMode::Passive)?;
|
||||||
|
|
||||||
|
// Read from stream (receiver side)
|
||||||
|
let mut buffer = vec![0u8; 1024];
|
||||||
|
let bytes_read = stream.stream_read(&mut buffer)?;
|
||||||
|
|
||||||
|
// Seek to next message start
|
||||||
|
if stream.stream_seek_msg_start()? {
|
||||||
|
println!("Found next message");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stream gracefully
|
||||||
|
stream.stream_close(true)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always check return values** - Use the `?` operator or explicitly handle errors
|
||||||
|
2. **Process all events** - Use the event iterator to process all events
|
||||||
|
3. **Close resources properly** - Let RAII handle cleanup or explicitly call `stop_sender()`/`stop_receiver()`
|
||||||
|
4. **Configure multicast correctly** - Use the ergonomic multicast API
|
||||||
|
5. **Use appropriate buffer sizes** - Match buffer sizes to your application's needs
|
||||||
|
|
@ -0,0 +1,704 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bindgen"
|
||||||
|
version = "0.69.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cexpr",
|
||||||
|
"clang-sys",
|
||||||
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
|
"lazycell",
|
||||||
|
"log",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
|
"shlex",
|
||||||
|
"syn",
|
||||||
|
"which",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cexpr"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clang-sys"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"libc",
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.5.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazycell"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.182"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "norm"
|
||||||
|
version = "1.5.10"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"norm-sys",
|
||||||
|
"rand",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "norm-sys"
|
||||||
|
version = "1.5.10"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.115"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.49.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "which"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"home",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.53.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.52.6",
|
||||||
|
"windows_aarch64_msvc 0.52.6",
|
||||||
|
"windows_i686_gnu 0.52.6",
|
||||||
|
"windows_i686_gnullvm 0.52.6",
|
||||||
|
"windows_i686_msvc 0.52.6",
|
||||||
|
"windows_x86_64_gnu 0.52.6",
|
||||||
|
"windows_x86_64_gnullvm 0.52.6",
|
||||||
|
"windows_x86_64_msvc 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.53.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
"windows_aarch64_gnullvm 0.53.1",
|
||||||
|
"windows_aarch64_msvc 0.53.1",
|
||||||
|
"windows_i686_gnu 0.53.1",
|
||||||
|
"windows_i686_gnullvm 0.53.1",
|
||||||
|
"windows_i686_msvc 0.53.1",
|
||||||
|
"windows_x86_64_gnu 0.53.1",
|
||||||
|
"windows_x86_64_gnullvm 0.53.1",
|
||||||
|
"windows_x86_64_msvc 0.53.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"norm-sys",
|
||||||
|
"norm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "1.5.10" # Matching NORM's version
|
||||||
|
edition = "2021"
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
repository = "https://github.com/USNavalResearchLaboratory/norm"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
norm-sys = { path = "norm-sys", version = "1.5.10" }
|
||||||
|
rand = "0.8"
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
# NORM Rust Bindings
|
||||||
|
|
||||||
|
Safe, idiomatic Rust bindings for the [NORM](https://github.com/USNavalResearchLaboratory/norm) (NACK-Oriented Reliable Multicast) protocol library.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
These Rust bindings provide a safe and ergonomic interface to the NORM C library. NORM is a reliable multicast protocol developed by the US Naval Research Laboratory that provides end-to-end reliable transport of data over IP multicast or unicast.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **RAII Resource Management**: Safe wrappers ensure proper cleanup of NORM resources
|
||||||
|
- **Ergonomic API**: Builder patterns for multicast configuration
|
||||||
|
- **Iterator-based Event Handling**: Simple event loops using Rust iterators
|
||||||
|
- **Type Safety**: Rust enums for NORM constants and options
|
||||||
|
- **Error Handling**: Rich error types with descriptive messages
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Rust 1.70 or later
|
||||||
|
- NORM library (installed or available for linking)
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the NORM repository
|
||||||
|
git clone https://github.com/USNavalResearchLaboratory/norm.git
|
||||||
|
cd norm
|
||||||
|
|
||||||
|
# Build NORM with Rust bindings
|
||||||
|
./waf configure --build-rust
|
||||||
|
./waf build
|
||||||
|
|
||||||
|
# Or for release mode
|
||||||
|
./waf configure --build-rust --rust-release
|
||||||
|
./waf build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using as a Dependency
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Cargo.toml
|
||||||
|
[dependencies]
|
||||||
|
norm = "1.5.10" # Replace with actual version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Data Transfer
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use norm::{Instance, multicast, MulticastExt, EventType, Result};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Create a session
|
||||||
|
let session = instance.create_session("224.1.2.3", 6003, 1)?;
|
||||||
|
|
||||||
|
// Configure multicast with the ergonomic API
|
||||||
|
let config = multicast!("224.1.2.3", 6003, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&config)?;
|
||||||
|
|
||||||
|
// Start sender
|
||||||
|
session
|
||||||
|
.set_tx_rate(1_000_000.0)
|
||||||
|
.start_sender(rand::random(), 1024*1024, 1400, 64, 16, None)?;
|
||||||
|
|
||||||
|
// Send data
|
||||||
|
let data = b"Hello, NORM!";
|
||||||
|
session.data_enqueue(data, None)?;
|
||||||
|
|
||||||
|
// Process events
|
||||||
|
for event in instance.events() {
|
||||||
|
if event.event_type == EventType::TxQueueEmpty {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream Transfer
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use norm::{Instance, multicast, MulticastExt, FlushMode, Result};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
let session = instance.create_session("224.1.2.3", 6003, 1)?;
|
||||||
|
|
||||||
|
let config = multicast!("224.1.2.3", 6003, { ttl: 64, loopback: true });
|
||||||
|
session.with_multicast(&config)?;
|
||||||
|
|
||||||
|
session.set_tx_rate(1_000_000.0)
|
||||||
|
.start_sender(rand::random(), 1024*1024, 1400, 64, 16, None)?;
|
||||||
|
|
||||||
|
// Open a stream
|
||||||
|
let stream = session.stream_open(64 * 1024, Some(b"My stream"))?;
|
||||||
|
|
||||||
|
// Write multiple messages
|
||||||
|
stream.stream_write(b"Message 1")?;
|
||||||
|
stream.stream_mark_eom()?;
|
||||||
|
|
||||||
|
stream.stream_write(b"Message 2")?;
|
||||||
|
stream.stream_mark_eom()?;
|
||||||
|
|
||||||
|
// Flush and close
|
||||||
|
stream.stream_flush(true, FlushMode::Active)?;
|
||||||
|
stream.stream_close(true)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multicast Configuration
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Method 1: Builder pattern
|
||||||
|
let config = MulticastConfig::new("224.1.2.3", 6003)
|
||||||
|
.ttl(64)
|
||||||
|
.interface("eth0")
|
||||||
|
.loopback(true);
|
||||||
|
|
||||||
|
// Method 2: Convenient macro
|
||||||
|
let config = multicast!("224.1.2.3", 6003, {
|
||||||
|
ttl: 64,
|
||||||
|
interface: "eth0",
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply to session
|
||||||
|
session.with_multicast(&config)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event Handling
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Iterator-based event handling
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::RxObjectCompleted => {
|
||||||
|
let object = Object::from_handle_unowned(event.object);
|
||||||
|
if let Ok(data) = object.access_data() {
|
||||||
|
println!("Received: {:?}", data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EventType::TxObjectSent => println!("Object sent"),
|
||||||
|
_ => {} // Ignore other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The bindings are split into two crates:
|
||||||
|
|
||||||
|
- **norm-sys**: Low-level FFI bindings generated with bindgen
|
||||||
|
- **norm**: Safe, idiomatic Rust wrappers with RAII and error handling
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
For full API documentation, build the docs with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/rust
|
||||||
|
cargo doc --open
|
||||||
|
```
|
||||||
|
|
||||||
|
Or when using waf:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./waf configure --build-rust --rust-docs
|
||||||
|
./waf build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
Due to the way the NORM library is built, you may need to set the library path when running tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On macOS
|
||||||
|
export DYLD_LIBRARY_PATH=/path/to/norm/build:/path/to/norm/build/protolib:$DYLD_LIBRARY_PATH
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# On Linux
|
||||||
|
export LD_LIBRARY_PATH=/path/to/norm/build:/path/to/norm/build/protolib:$LD_LIBRARY_PATH
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Or run directly
|
||||||
|
DYLD_LIBRARY_PATH=/path/to/norm/build:/path/to/norm/build/protolib cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, use the waf build system which handles this automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./waf configure --build-rust
|
||||||
|
./waf build
|
||||||
|
./waf test # If test target is configured
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
These bindings are distributed under the same BSD-3-Clause license as the NORM library itself.
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
use norm::{Instance, multicast, MulticastExt, EventType, ObjectType, Result};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Parse command line arguments
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let (address, port) = if args.len() > 2 {
|
||||||
|
(args[1].as_str(), args[2].parse::<u16>().unwrap_or(6003))
|
||||||
|
} else {
|
||||||
|
println!("Using default multicast address 224.1.2.3:6003");
|
||||||
|
println!("Usage: {} <address> <port>", args[0]);
|
||||||
|
("224.1.2.3", 6003)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Set the cache directory for file reception (required for file objects)
|
||||||
|
instance.set_cache_directory("/tmp/norm")?;
|
||||||
|
|
||||||
|
// Create a session with the specified address and port
|
||||||
|
let session = instance.create_session(address, port, 2)?;
|
||||||
|
|
||||||
|
// Configure multicast settings using our ergonomic API
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Start the receiver with 1MB buffer
|
||||||
|
session.start_receiver(1024 * 1024)?;
|
||||||
|
|
||||||
|
println!("NORM receiver started on {}:{}", address, port);
|
||||||
|
println!("Waiting for data...");
|
||||||
|
|
||||||
|
// Event loop - wait for data to be received
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::RemoteSenderNew => {
|
||||||
|
println!("New sender connected");
|
||||||
|
}
|
||||||
|
EventType::RxObjectNew => {
|
||||||
|
println!("New object received, waiting for completion...");
|
||||||
|
}
|
||||||
|
EventType::RxObjectCompleted => {
|
||||||
|
// Check if it's a data object
|
||||||
|
let obj_handle = event.object;
|
||||||
|
let object = norm::Object::from_handle_unowned(obj_handle);
|
||||||
|
|
||||||
|
if object.get_type() == ObjectType::Data {
|
||||||
|
// Access the data
|
||||||
|
if let Ok(data) = object.access_data() {
|
||||||
|
println!("Received data: {:?}", String::from_utf8_lossy(data));
|
||||||
|
|
||||||
|
// Check if there's info data
|
||||||
|
if object.has_info() {
|
||||||
|
if let Ok(info) = object.get_info() {
|
||||||
|
println!("Info data: {:?}", String::from_utf8_lossy(&info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit after receiving one data object
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
println!("Failed to access data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up (this happens automatically via Drop, but being explicit)
|
||||||
|
session.stop_receiver();
|
||||||
|
|
||||||
|
println!("Data receive example completed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
use norm::{Instance, multicast, MulticastExt, EventType, Result};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{thread, env};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Parse command line arguments
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let (address, port) = if args.len() > 2 {
|
||||||
|
(args[1].as_str(), args[2].parse::<u16>().unwrap_or(6003))
|
||||||
|
} else {
|
||||||
|
println!("Using default multicast address 224.1.2.3:6003");
|
||||||
|
println!("Usage: {} <address> <port>", args[0]);
|
||||||
|
("224.1.2.3", 6003)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Create a session with the specified address and port
|
||||||
|
let session = instance.create_session(address, port, 1)?;
|
||||||
|
|
||||||
|
// Configure multicast settings using our ergonomic API
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Set the transmission rate (in bits per second)
|
||||||
|
session.set_tx_rate(1_000_000.0);
|
||||||
|
|
||||||
|
// Start the sender with 1MB buffer, 1400 byte segments, 64 data segments per block, 16 parity segments
|
||||||
|
let session_id = rand::random::<u16>();
|
||||||
|
session.start_sender(session_id, 1024 * 1024, 1400, 64, 16, None)?;
|
||||||
|
|
||||||
|
// Prepare some data to send
|
||||||
|
let data = b"Hello, NORM! This is a test message sent using the Rust bindings.";
|
||||||
|
let info = b"Example data message";
|
||||||
|
|
||||||
|
println!("Sending data: {:?}", String::from_utf8_lossy(data));
|
||||||
|
|
||||||
|
// Enqueue the data for transmission
|
||||||
|
let _data_obj = session.data_enqueue(data, Some(info))?;
|
||||||
|
|
||||||
|
println!("Data enqueued, waiting for transmission to complete...");
|
||||||
|
|
||||||
|
// Event loop - wait for transmission to complete
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::TxObjectSent => {
|
||||||
|
println!("Object sent successfully");
|
||||||
|
}
|
||||||
|
EventType::TxQueueEmpty => {
|
||||||
|
println!("Transmission queue empty");
|
||||||
|
// Short delay before exiting to allow any final processing
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up (this happens automatically via Drop, but being explicit)
|
||||||
|
session.stop_sender();
|
||||||
|
|
||||||
|
println!("Data send example completed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
use norm::{Instance, Session, multicast, MulticastExt, EventType, ObjectType, Result};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{thread, env, path::Path};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Parse command line arguments
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let (address, port) = if args.len() > 2 {
|
||||||
|
(args[1].as_str(), args[2].parse::<u16>().unwrap_or(6003))
|
||||||
|
} else {
|
||||||
|
println!("Using default multicast address 224.1.2.3:6003");
|
||||||
|
println!("Usage: {} <address> <port>", args[0]);
|
||||||
|
("224.1.2.3", 6003)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Set the cache directory for file reception
|
||||||
|
// IMPORTANT: This is required for receiving file objects
|
||||||
|
let cache_dir = "/tmp/norm";
|
||||||
|
println!("Setting cache directory to: {}", cache_dir);
|
||||||
|
instance.set_cache_directory(cache_dir)?;
|
||||||
|
|
||||||
|
// Create a session with the specified address and port
|
||||||
|
let session = instance.create_session(address, port, 2)?;
|
||||||
|
|
||||||
|
// Configure multicast settings using our ergonomic API
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Start the receiver with 4MB buffer
|
||||||
|
session.start_receiver(4 * 1024 * 1024)?;
|
||||||
|
|
||||||
|
println!("NORM receiver started on {}:{}", address, port);
|
||||||
|
println!("Waiting for file...");
|
||||||
|
|
||||||
|
// Event loop - wait for file to be received
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::RemoteSenderNew => {
|
||||||
|
println!("New sender connected");
|
||||||
|
}
|
||||||
|
EventType::RxObjectNew => {
|
||||||
|
let object = norm::Object::from_handle_unowned(event.object);
|
||||||
|
if object.get_type() == ObjectType::File {
|
||||||
|
println!("New file being received...");
|
||||||
|
|
||||||
|
// Try to get info data which should contain the file name
|
||||||
|
if let Ok(info) = object.get_info() {
|
||||||
|
if !info.is_empty() {
|
||||||
|
println!("File name: {}", String::from_utf8_lossy(&info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType::RxObjectCompleted => {
|
||||||
|
let object = norm::Object::from_handle_unowned(event.object);
|
||||||
|
if object.get_type() == ObjectType::File {
|
||||||
|
println!("File received successfully!");
|
||||||
|
|
||||||
|
// The file will be in the cache directory
|
||||||
|
// You can find out its name from the info data
|
||||||
|
if let Ok(info) = object.get_info() {
|
||||||
|
if !info.is_empty() {
|
||||||
|
let filename = String::from_utf8_lossy(&info);
|
||||||
|
let filepath = format!("{}/{}", cache_dir, filename);
|
||||||
|
println!("File saved to: {}", filepath);
|
||||||
|
|
||||||
|
// Report file size
|
||||||
|
println!("File size: {} bytes", object.size());
|
||||||
|
|
||||||
|
// Exit after receiving one file
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
session.stop_receiver();
|
||||||
|
|
||||||
|
println!("File receive example completed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
use norm::{Instance, Session, multicast, MulticastExt, EventType, Result};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{thread, env, path::Path};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Parse command line arguments
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
println!("Usage: {} <file_path> [address] [port]", args[0]);
|
||||||
|
return Err(norm::Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = &args[1];
|
||||||
|
let address = if args.len() > 2 { &args[2] } else { "224.1.2.3" };
|
||||||
|
let port = if args.len() > 3 { args[3].parse::<u16>().unwrap_or(6003) } else { 6003 };
|
||||||
|
|
||||||
|
// Verify the file exists
|
||||||
|
if !Path::new(file_path).exists() {
|
||||||
|
println!("File not found: {}", file_path);
|
||||||
|
return Err(norm::Error::FileError("File not found".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Create a session with the specified address and port
|
||||||
|
let session = instance.create_session(address, port, 1)?;
|
||||||
|
|
||||||
|
// Configure multicast settings using our ergonomic API
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Set the transmission rate (in bits per second)
|
||||||
|
session.set_tx_rate(25_000_000.0); // 25 Mbps
|
||||||
|
|
||||||
|
// Start the sender
|
||||||
|
let session_id = rand::random::<u16>();
|
||||||
|
session.start_sender(session_id, 4 * 1024 * 1024, 1400, 64, 16, None)?;
|
||||||
|
|
||||||
|
// Get just the file name to use as info data
|
||||||
|
let file_name = Path::new(file_path)
|
||||||
|
.file_name()
|
||||||
|
.map(|name| name.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
|
||||||
|
println!("Sending file: {} ({})", file_path, file_name);
|
||||||
|
|
||||||
|
// Enqueue the file for transmission with the file name as info
|
||||||
|
let file_obj = session.file_enqueue(file_path, Some(file_name.as_bytes()))?;
|
||||||
|
|
||||||
|
println!("File enqueued, waiting for transmission to complete...");
|
||||||
|
|
||||||
|
// Event loop - wait for transmission to complete
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::TxObjectSent => {
|
||||||
|
println!("File sent successfully");
|
||||||
|
}
|
||||||
|
EventType::TxFlushCompleted => {
|
||||||
|
println!("Transmission flush completed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EventType::RemoteSenderNew => {
|
||||||
|
println!("Receiver joined");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
session.stop_sender();
|
||||||
|
|
||||||
|
println!("File send example completed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
use norm::{Instance, Session, multicast, MulticastExt, EventType, ObjectType, FlushMode, Result};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{thread, env};
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
// This example demonstrates a simple stream sender and receiver in one program
|
||||||
|
// It creates both sender and receiver threads for demonstration purposes
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let address = "224.1.2.3";
|
||||||
|
let port = 6003;
|
||||||
|
|
||||||
|
println!("NORM Stream Example");
|
||||||
|
println!("-------------------");
|
||||||
|
println!("Using multicast address: {}:{}", address, port);
|
||||||
|
|
||||||
|
// Spawn receiver thread
|
||||||
|
let receiver_thread = thread::spawn(move || {
|
||||||
|
if let Err(e) = run_receiver(address, port) {
|
||||||
|
eprintln!("Receiver error: {:?}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give the receiver a moment to start
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
|
// Run sender in the main thread
|
||||||
|
if let Err(e) = run_sender(address, port) {
|
||||||
|
eprintln!("Sender error: {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for receiver to finish
|
||||||
|
receiver_thread.join().expect("Receiver thread panicked");
|
||||||
|
|
||||||
|
println!("Stream example completed");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_sender(address: &str, port: u16) -> Result<()> {
|
||||||
|
// Create NORM instance and session for sender
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
let session = instance.create_session(address, port, 1)?;
|
||||||
|
|
||||||
|
// Configure multicast
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Set transmission rate
|
||||||
|
session.set_tx_rate(1_000_000.0);
|
||||||
|
|
||||||
|
// Start sender
|
||||||
|
let session_id = rand::random::<u16>();
|
||||||
|
session.start_sender(session_id, 1024 * 1024, 1400, 64, 16, None)?;
|
||||||
|
|
||||||
|
// Open a stream with 64KB buffer
|
||||||
|
let stream_buffer_size = 64 * 1024;
|
||||||
|
let info = b"Example stream";
|
||||||
|
let stream = session.stream_open(stream_buffer_size, Some(info))?;
|
||||||
|
|
||||||
|
println!("Stream opened, sending messages...");
|
||||||
|
|
||||||
|
// Send 10 messages through the stream
|
||||||
|
for i in 1..=10 {
|
||||||
|
let message = format!("Stream message #{}", i);
|
||||||
|
println!("Sending: {}", message);
|
||||||
|
|
||||||
|
// Write message to stream
|
||||||
|
let bytes_written = unsafe {
|
||||||
|
// Using the raw API here for simplicity
|
||||||
|
// In a real implementation, you'd want to create a safer wrapper
|
||||||
|
norm_sys::NormStreamWrite(
|
||||||
|
stream.handle(),
|
||||||
|
message.as_ptr() as *const i8,
|
||||||
|
message.len() as u32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if bytes_written < message.len() as u32 {
|
||||||
|
println!("Warning: Only wrote {} of {} bytes", bytes_written, message.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark end of message and flush passively
|
||||||
|
unsafe {
|
||||||
|
norm_sys::NormStreamMarkEom(stream.handle());
|
||||||
|
norm_sys::NormStreamFlush(stream.handle(), false, norm_sys::NormFlushMode_NORM_FLUSH_PASSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay between messages
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the stream gracefully
|
||||||
|
unsafe {
|
||||||
|
norm_sys::NormStreamClose(stream.handle(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Stream closed, waiting for transmission to complete...");
|
||||||
|
|
||||||
|
// Wait for a short time to ensure all data is flushed
|
||||||
|
thread::sleep(Duration::from_secs(2));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
session.stop_sender();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_receiver(address: &str, port: u16) -> Result<()> {
|
||||||
|
// Create NORM instance and session for receiver
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
let session = instance.create_session(address, port, 2)?;
|
||||||
|
|
||||||
|
// Configure multicast
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Start receiver
|
||||||
|
session.start_receiver(1024 * 1024)?;
|
||||||
|
|
||||||
|
println!("Receiver started, waiting for stream data...");
|
||||||
|
|
||||||
|
// Variables to track the current stream object
|
||||||
|
let mut current_stream = None;
|
||||||
|
let mut message_count = 0;
|
||||||
|
let mut timeout_count = 0;
|
||||||
|
|
||||||
|
// Event loop
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::RemoteSenderNew => {
|
||||||
|
println!("New stream sender detected");
|
||||||
|
}
|
||||||
|
EventType::RxObjectNew => {
|
||||||
|
let object = norm::Object::from_handle_unowned(event.object);
|
||||||
|
if object.get_type() == ObjectType::Stream {
|
||||||
|
println!("New stream object received");
|
||||||
|
|
||||||
|
// Store the stream object
|
||||||
|
current_stream = Some(event.object);
|
||||||
|
|
||||||
|
// Check for info
|
||||||
|
if object.has_info() {
|
||||||
|
if let Ok(info) = object.get_info() {
|
||||||
|
println!("Stream info: {}", String::from_utf8_lossy(&info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType::RxObjectUpdated => {
|
||||||
|
// Stream data is available to read
|
||||||
|
if let Some(stream) = current_stream {
|
||||||
|
if event.object == stream {
|
||||||
|
// Read from the stream
|
||||||
|
let mut buffer = vec![0u8; 1024];
|
||||||
|
let mut bytes_read = 0u32;
|
||||||
|
|
||||||
|
let success = unsafe {
|
||||||
|
norm_sys::NormStreamRead(
|
||||||
|
stream,
|
||||||
|
buffer.as_mut_ptr() as *mut i8,
|
||||||
|
&mut bytes_read as *mut u32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if success && bytes_read > 0 {
|
||||||
|
buffer.truncate(bytes_read as usize);
|
||||||
|
println!("Received: {}", String::from_utf8_lossy(&buffer));
|
||||||
|
message_count += 1;
|
||||||
|
|
||||||
|
// Reset timeout counter when we get data
|
||||||
|
timeout_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType::RxObjectCompleted => {
|
||||||
|
if let Some(stream) = current_stream {
|
||||||
|
if event.object == stream {
|
||||||
|
println!("Stream completed, received {} messages", message_count);
|
||||||
|
|
||||||
|
// Exit when stream is completed
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Check for timeout
|
||||||
|
timeout_count += 1;
|
||||||
|
if timeout_count > 10000 {
|
||||||
|
// If we haven't seen any activity for a while, exit
|
||||||
|
if message_count > 0 {
|
||||||
|
println!("Stream timeout after receiving {} messages", message_count);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
timeout_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
use norm::{Instance, multicast, MulticastExt, EventType, ObjectType, Result};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Parse command line arguments
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let (address, port) = if args.len() > 2 {
|
||||||
|
(args[1].as_str(), args[2].parse::<u16>().unwrap_or(6003))
|
||||||
|
} else {
|
||||||
|
println!("Using default multicast address 224.1.2.3:6003");
|
||||||
|
println!("Usage: {} <address> <port>", args[0]);
|
||||||
|
("224.1.2.3", 6003)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Set the cache directory for file reception (required for file objects)
|
||||||
|
instance.set_cache_directory("/tmp/norm")?;
|
||||||
|
|
||||||
|
// Create a session with the specified address and port
|
||||||
|
let session = instance.create_session(address, port, 2)?;
|
||||||
|
|
||||||
|
// Configure multicast settings using our ergonomic API
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Start the receiver with 1MB buffer
|
||||||
|
session.start_receiver(1024 * 1024)?;
|
||||||
|
|
||||||
|
println!("NORM stream receiver started on {}:{}", address, port);
|
||||||
|
println!("Waiting for stream data...");
|
||||||
|
|
||||||
|
let mut message_count = 0;
|
||||||
|
let mut buffer = vec![0u8; 4096];
|
||||||
|
|
||||||
|
// Event loop - wait for data to be received
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::RemoteSenderNew => {
|
||||||
|
println!("New sender connected");
|
||||||
|
}
|
||||||
|
EventType::RxObjectNew => {
|
||||||
|
let obj_handle = event.object;
|
||||||
|
let object = norm::Object::from_handle_unowned(obj_handle);
|
||||||
|
|
||||||
|
if object.get_type() == ObjectType::Stream {
|
||||||
|
println!("New stream object received");
|
||||||
|
|
||||||
|
// Get info data if available
|
||||||
|
if object.has_info() {
|
||||||
|
if let Ok(info) = object.get_info() {
|
||||||
|
println!("Stream info: {:?}", String::from_utf8_lossy(&info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType::RxObjectUpdated => {
|
||||||
|
let obj_handle = event.object;
|
||||||
|
let object = norm::Object::from_handle_unowned(obj_handle);
|
||||||
|
|
||||||
|
if object.get_type() == ObjectType::Stream {
|
||||||
|
// Try to read data from the stream
|
||||||
|
match object.stream_read(&mut buffer) {
|
||||||
|
Ok(bytes_read) if bytes_read > 0 => {
|
||||||
|
message_count += 1;
|
||||||
|
let data = &buffer[..bytes_read];
|
||||||
|
println!("Message #{}: {:?}", message_count, String::from_utf8_lossy(data));
|
||||||
|
|
||||||
|
// Check if we've received all expected messages
|
||||||
|
if message_count >= 5 {
|
||||||
|
println!("Received all expected messages");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
// No data available yet
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error reading from stream: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType::RxObjectCompleted => {
|
||||||
|
println!("Stream object completed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
session.stop_receiver();
|
||||||
|
|
||||||
|
println!("Stream receive example completed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
use norm::{Instance, multicast, MulticastExt, EventType, Result, FlushMode};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{thread, env};
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Parse command line arguments
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let (address, port) = if args.len() > 2 {
|
||||||
|
(args[1].as_str(), args[2].parse::<u16>().unwrap_or(6003))
|
||||||
|
} else {
|
||||||
|
println!("Using default multicast address 224.1.2.3:6003");
|
||||||
|
println!("Usage: {} <address> <port>", args[0]);
|
||||||
|
("224.1.2.3", 6003)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new NORM instance
|
||||||
|
let instance = Instance::new(false)?;
|
||||||
|
|
||||||
|
// Create a session with the specified address and port
|
||||||
|
let session = instance.create_session(address, port, 1)?;
|
||||||
|
|
||||||
|
// Configure multicast settings using our ergonomic API
|
||||||
|
let mc_config = multicast!(address, port, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
session.with_multicast(&mc_config)?;
|
||||||
|
|
||||||
|
// Set the transmission rate (in bits per second)
|
||||||
|
session.set_tx_rate(1_000_000.0);
|
||||||
|
|
||||||
|
// Start the sender with 1MB buffer, 1400 byte segments, 64 data segments per block, 16 parity segments
|
||||||
|
let session_id = rand::random::<u16>();
|
||||||
|
session.start_sender(session_id, 1024 * 1024, 1400, 64, 16, None)?;
|
||||||
|
|
||||||
|
println!("Opening NORM stream...");
|
||||||
|
|
||||||
|
// Open a stream with 64KB buffer
|
||||||
|
let stream = session.stream_open(64 * 1024, Some(b"Example stream"))?;
|
||||||
|
|
||||||
|
// Send multiple messages through the stream
|
||||||
|
for i in 1..=5 {
|
||||||
|
let message = format!("Stream message #{}", i);
|
||||||
|
println!("Sending: {}", message);
|
||||||
|
|
||||||
|
// Write to stream
|
||||||
|
let bytes_written = stream.stream_write(message.as_bytes())?;
|
||||||
|
println!("Wrote {} bytes", bytes_written);
|
||||||
|
|
||||||
|
// Mark end of message
|
||||||
|
stream.stream_mark_eom()?;
|
||||||
|
|
||||||
|
// Small delay between messages
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Flushing stream...");
|
||||||
|
|
||||||
|
// Flush the stream with end-of-message marker
|
||||||
|
stream.stream_flush(true, FlushMode::Active)?;
|
||||||
|
|
||||||
|
// Wait for transmission to complete
|
||||||
|
let mut tx_complete = false;
|
||||||
|
for event in instance.events() {
|
||||||
|
match event.event_type {
|
||||||
|
EventType::TxObjectSent => {
|
||||||
|
println!("Stream object sent");
|
||||||
|
}
|
||||||
|
EventType::TxFlushCompleted => {
|
||||||
|
println!("Stream flush completed");
|
||||||
|
tx_complete = true;
|
||||||
|
}
|
||||||
|
EventType::TxQueueEmpty => {
|
||||||
|
if tx_complete {
|
||||||
|
println!("Transmission queue empty");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Closing stream gracefully...");
|
||||||
|
stream.stream_close(true)?;
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
session.stop_sender();
|
||||||
|
|
||||||
|
println!("Stream send example completed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "norm-sys"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
links = "norm"
|
||||||
|
description = "Raw FFI bindings to the NORM (NACK-Oriented Reliable Multicast) library"
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
bindgen = "0.69"
|
||||||
|
cc = "1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Tell cargo to invalidate the built crate whenever the wrapper.h changes
|
||||||
|
println!("cargo:rerun-if-changed=wrapper.h");
|
||||||
|
|
||||||
|
// First, try to find the NORM header directory from an environment variable
|
||||||
|
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
let norm_include_dir = env::var("NORM_INCLUDE_DIR").unwrap_or_else(|_| {
|
||||||
|
// Default to looking for include files in the main repository directory
|
||||||
|
format!("{}/../../../include", manifest_dir)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tell cargo to look for libnorm in the build directory
|
||||||
|
let lib_path = format!("{}/../../../build", manifest_dir);
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-search={}", lib_path);
|
||||||
|
println!("cargo:rustc-link-lib=norm");
|
||||||
|
|
||||||
|
// Add rpath so the dynamic library can be found at runtime
|
||||||
|
// This is especially important for development and testing
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_path);
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_path);
|
||||||
|
|
||||||
|
// Add linkage to ProtoKit if needed
|
||||||
|
let protolib_path = format!("{}/protolib", lib_path);
|
||||||
|
println!("cargo:rustc-link-search={}", protolib_path);
|
||||||
|
println!("cargo:rustc-link-lib=protokit");
|
||||||
|
|
||||||
|
// Add rpath for protolib as well
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", protolib_path);
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", protolib_path);
|
||||||
|
|
||||||
|
// Link pthread on Unix
|
||||||
|
if cfg!(unix) {
|
||||||
|
println!("cargo:rustc-link-lib=pthread");
|
||||||
|
}
|
||||||
|
|
||||||
|
// On macOS, we might need additional system libraries
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
println!("cargo:rustc-link-lib=resolv");
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Solaris, add these libraries
|
||||||
|
if cfg!(target_os = "solaris") {
|
||||||
|
println!("cargo:rustc-link-lib=nsl");
|
||||||
|
println!("cargo:rustc-link-lib=socket");
|
||||||
|
println!("cargo:rustc-link-lib=resolv");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate bindings for the NORM API
|
||||||
|
let bindings = bindgen::Builder::default()
|
||||||
|
.header("wrapper.h")
|
||||||
|
// IMPORTANT: Tell clang to treat this as C++ code
|
||||||
|
.clang_arg("-x")
|
||||||
|
.clang_arg("c++")
|
||||||
|
.clang_arg(format!("-I{}", norm_include_dir))
|
||||||
|
// Whitelist NORM functions, types, and constants
|
||||||
|
.allowlist_function("Norm.*")
|
||||||
|
.allowlist_type("Norm.*")
|
||||||
|
.allowlist_var("NORM_.*")
|
||||||
|
// Make the generated bindings derive common traits
|
||||||
|
.derive_default(true)
|
||||||
|
.derive_debug(true)
|
||||||
|
.derive_copy(true)
|
||||||
|
.derive_eq(true)
|
||||||
|
// Generate documentation from C comments
|
||||||
|
.generate_comments(true)
|
||||||
|
// Parse callbacks for cargo build info (updated to new API)
|
||||||
|
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||||
|
.generate()
|
||||||
|
.expect("Unable to generate NORM bindings");
|
||||||
|
|
||||||
|
// Write the bindings to the $OUT_DIR/bindings.rs file
|
||||||
|
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
|
bindings
|
||||||
|
.write_to_file(out_path.join("bindings.rs"))
|
||||||
|
.expect("Couldn't write NORM bindings!");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
//! Raw FFI bindings to the NORM (NACK-Oriented Reliable Multicast) library.
|
||||||
|
//!
|
||||||
|
//! This crate provides raw FFI bindings to the NORM C API, generated using bindgen.
|
||||||
|
//! It is not intended to be used directly, but rather through the `norm` crate which
|
||||||
|
//! provides safe, idiomatic Rust wrappers.
|
||||||
|
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(clippy::all)]
|
||||||
|
|
||||||
|
// The bindings will be included here by the build script
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_version_constants() {
|
||||||
|
// Verify that we can access the version constants
|
||||||
|
assert!(NORM_VERSION_MAJOR > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/* Wrapper header for bindgen to generate bindings for NORM API */
|
||||||
|
|
||||||
|
/* Include the main NORM API header */
|
||||||
|
#include "normApi.h"
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
[package]
|
||||||
|
name = "norm"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
description = "Safe Rust bindings for NORM (NACK-Oriented Reliable Multicast)"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
norm-sys = { workspace = true }
|
||||||
|
libc = "0.2"
|
||||||
|
thiserror = "1.0"
|
||||||
|
tokio = { version = "1.0", features = ["full"], optional = true }
|
||||||
|
rand = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
tokio = ["dep:tokio"]
|
||||||
|
async-std = []
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "data_send"
|
||||||
|
path = "../examples/data_send.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "data_recv"
|
||||||
|
path = "../examples/data_recv.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "stream_send"
|
||||||
|
path = "../examples/stream_send.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "stream_recv"
|
||||||
|
path = "../examples/stream_recv.rs"
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fn main() {
|
||||||
|
// The norm-sys crate already handles linking to libnorm
|
||||||
|
// This build script is included for future expansion if needed
|
||||||
|
|
||||||
|
// We might want to add conditional compilation flags, feature detection, etc.
|
||||||
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
|
/// Error type for NORM operations
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An invalid handle was provided to a NORM function
|
||||||
|
InvalidHandle,
|
||||||
|
|
||||||
|
/// An invalid network address was provided
|
||||||
|
InvalidAddress,
|
||||||
|
|
||||||
|
/// An invalid parameter was provided to a NORM function
|
||||||
|
InvalidParameter,
|
||||||
|
|
||||||
|
/// Memory allocation failed
|
||||||
|
AllocationFailed,
|
||||||
|
|
||||||
|
/// A socket-related error occurred
|
||||||
|
SocketError(String),
|
||||||
|
|
||||||
|
/// A file-related error occurred
|
||||||
|
FileError(String),
|
||||||
|
|
||||||
|
/// An operation timed out
|
||||||
|
Timeout,
|
||||||
|
|
||||||
|
/// A resource was not ready for the requested operation
|
||||||
|
NotReady,
|
||||||
|
|
||||||
|
/// An operation failed for an unspecified reason
|
||||||
|
OperationFailed(String),
|
||||||
|
|
||||||
|
/// A system error occurred (with errno)
|
||||||
|
#[cfg(unix)]
|
||||||
|
SystemError {
|
||||||
|
/// The error message
|
||||||
|
message: String,
|
||||||
|
/// The errno value
|
||||||
|
errno: i32,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A system error occurred (with Win32 error code)
|
||||||
|
#[cfg(windows)]
|
||||||
|
SystemError {
|
||||||
|
/// The error message
|
||||||
|
message: String,
|
||||||
|
/// The Win32 error code
|
||||||
|
error_code: u32,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A string conversion error occurred
|
||||||
|
StringConversionError,
|
||||||
|
|
||||||
|
/// A null pointer was encountered where a valid pointer was expected
|
||||||
|
NullPointer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::InvalidHandle => write!(f, "Invalid NORM handle"),
|
||||||
|
Error::InvalidAddress => write!(f, "Invalid network address"),
|
||||||
|
Error::InvalidParameter => write!(f, "Invalid parameter"),
|
||||||
|
Error::AllocationFailed => write!(f, "Memory allocation failed"),
|
||||||
|
Error::SocketError(s) => write!(f, "Socket error: {}", s),
|
||||||
|
Error::FileError(s) => write!(f, "File error: {}", s),
|
||||||
|
Error::Timeout => write!(f, "Operation timed out"),
|
||||||
|
Error::NotReady => write!(f, "Resource not ready"),
|
||||||
|
Error::OperationFailed(s) => write!(f, "Operation failed: {}", s),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Error::SystemError { message, errno } => {
|
||||||
|
write!(f, "System error: {} (errno: {})", message, errno)
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
Error::SystemError { message, error_code } => {
|
||||||
|
write!(f, "System error: {} (error code: {})", message, error_code)
|
||||||
|
}
|
||||||
|
Error::StringConversionError => write!(f, "String conversion error"),
|
||||||
|
Error::NullPointer => write!(f, "Null pointer encountered"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for Error {}
|
||||||
|
|
||||||
|
/// Result type for NORM operations
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Helper function to convert a C string to a Rust string result
|
||||||
|
pub(crate) fn c_string_to_string(c_str: *const libc::c_char) -> Result<String> {
|
||||||
|
if c_str.is_null() {
|
||||||
|
return Err(Error::NullPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let c_str = std::ffi::CStr::from_ptr(c_str);
|
||||||
|
c_str.to_str()
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.map_err(|_| Error::StringConversionError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to convert a Rust string to a C string result
|
||||||
|
pub(crate) fn string_to_c_string(s: &str) -> Result<std::ffi::CString> {
|
||||||
|
std::ffi::CString::new(s)
|
||||||
|
.map_err(|_| Error::StringConversionError)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a boolean result from NORM API to a Result
|
||||||
|
pub(crate) fn bool_result(success: bool, error_message: &str) -> Result<()> {
|
||||||
|
if success {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::OperationFailed(error_message.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to check if a handle is valid
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is unsafe because it may compare with extern static values
|
||||||
|
/// that are not controlled by the Rust type system.
|
||||||
|
pub(crate) unsafe fn check_handle<T>(handle: T, invalid: T) -> Result<T>
|
||||||
|
where
|
||||||
|
T: PartialEq
|
||||||
|
{
|
||||||
|
if handle == invalid {
|
||||||
|
Err(Error::InvalidHandle)
|
||||||
|
} else {
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_result_success() {
|
||||||
|
assert!(bool_result(true, "Test error").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_result_failure() {
|
||||||
|
let result = bool_result(false, "Test error");
|
||||||
|
assert!(result.is_err());
|
||||||
|
match result {
|
||||||
|
Err(Error::OperationFailed(msg)) => assert_eq!(msg, "Test error"),
|
||||||
|
_ => panic!("Expected OperationFailed error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string_to_c_string() {
|
||||||
|
let result = string_to_c_string("test");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let c_str = result.unwrap();
|
||||||
|
assert_eq!(c_str.to_str().unwrap(), "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string_to_c_string_with_null() {
|
||||||
|
// Build a string with an embedded null byte without a literal null in source
|
||||||
|
let s = format!("test{}embedded", '\0');
|
||||||
|
let result = string_to_c_string(&s);
|
||||||
|
assert!(result.is_err());
|
||||||
|
match result {
|
||||||
|
Err(Error::StringConversionError) => {},
|
||||||
|
_ => panic!("Expected StringConversionError"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_display() {
|
||||||
|
let err = Error::InvalidHandle;
|
||||||
|
assert_eq!(err.to_string(), "Invalid NORM handle");
|
||||||
|
|
||||||
|
let err = Error::OperationFailed("test failure".to_string());
|
||||||
|
assert_eq!(err.to_string(), "Operation failed: test failure");
|
||||||
|
|
||||||
|
let err = Error::FileError("file not found".to_string());
|
||||||
|
assert_eq!(err.to_string(), "File error: file not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
use crate::types::EventType;
|
||||||
|
use norm_sys::*;
|
||||||
|
|
||||||
|
/// A NORM event
|
||||||
|
///
|
||||||
|
/// This struct represents an event from the NORM protocol. It wraps the raw
|
||||||
|
/// C event struct and provides accessors for the event data.
|
||||||
|
///
|
||||||
|
/// Events are generated by the NORM protocol and can be retrieved using
|
||||||
|
/// `Instance::next_event` or `Instance::events`.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Event {
|
||||||
|
/// The type of event
|
||||||
|
pub event_type: EventType,
|
||||||
|
/// The session handle
|
||||||
|
pub session: NormSessionHandle,
|
||||||
|
/// The sender handle
|
||||||
|
pub sender: NormNodeHandle,
|
||||||
|
/// The object handle
|
||||||
|
pub object: NormObjectHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
/// Create a new event from a raw NORM event
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `raw_event` - The raw NORM event from the C API
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new event struct
|
||||||
|
pub(crate) fn from_raw(raw_event: NormEvent) -> Self {
|
||||||
|
Event {
|
||||||
|
event_type: EventType::from(raw_event.type_),
|
||||||
|
session: raw_event.session,
|
||||||
|
sender: raw_event.sender,
|
||||||
|
object: raw_event.object,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this event is a specific event type
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `event_type` - The event type to check for
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if the event is of the specified type, `false` otherwise
|
||||||
|
pub fn is(&self, event_type: EventType) -> bool {
|
||||||
|
self.event_type == event_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will be expanded later to provide additional helper methods and convenience functions
|
||||||
|
// for working with different event types
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
use crate::error::{Result, bool_result, check_handle, string_to_c_string};
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::event::Event;
|
||||||
|
use crate::session::Session;
|
||||||
|
use norm_sys::*;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
/// NORM instance handle with RAII semantics.
|
||||||
|
///
|
||||||
|
/// This is the top-level object for interacting with the NORM library. It provides
|
||||||
|
/// functionality for creating sessions, handling events, and configuring global options.
|
||||||
|
///
|
||||||
|
/// When dropped, it will automatically clean up resources by calling `NormDestroyInstance`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Instance {
|
||||||
|
/// The raw NORM instance handle
|
||||||
|
handle: NormInstanceHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
/// Create a new NORM instance
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `priority_boost` - Whether to boost the priority of the NORM thread
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new NORM instance or an error if the instance could not be created
|
||||||
|
pub fn new(priority_boost: bool) -> Result<Self> {
|
||||||
|
let handle = unsafe { NormCreateInstance(priority_boost) };
|
||||||
|
unsafe { check_handle(handle, NORM_INSTANCE_INVALID)? };
|
||||||
|
Ok(Instance { handle })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the NORM instance
|
||||||
|
///
|
||||||
|
/// This stops any running NORM threads but doesn't destroy the instance.
|
||||||
|
pub fn stop(&self) {
|
||||||
|
unsafe { NormStopInstance(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restart the NORM instance
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` if the instance was successfully restarted, `Err` otherwise
|
||||||
|
pub fn restart(&self) -> Result<()> {
|
||||||
|
let success = unsafe { NormRestartInstance(self.handle) };
|
||||||
|
bool_result(success, "Failed to restart NORM instance")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Suspend the NORM instance
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` if the instance was successfully suspended, `Err` otherwise
|
||||||
|
pub fn suspend(&self) -> Result<()> {
|
||||||
|
let success = unsafe { NormSuspendInstance(self.handle) };
|
||||||
|
bool_result(success, "Failed to suspend NORM instance")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume a suspended NORM instance
|
||||||
|
pub fn resume(&self) {
|
||||||
|
unsafe { NormResumeInstance(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the cache directory for file reception
|
||||||
|
///
|
||||||
|
/// This MUST be set to enable NORM_OBJECT_FILE reception!
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `cache_path` - The path to the cache directory
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` if the cache directory was successfully set, `Err` otherwise
|
||||||
|
pub fn set_cache_directory<P: AsRef<str>>(&self, cache_path: P) -> Result<()> {
|
||||||
|
let c_path = string_to_c_string(cache_path.as_ref())?;
|
||||||
|
let success = unsafe { NormSetCacheDirectory(self.handle, c_path.as_ptr()) };
|
||||||
|
bool_result(success, "Failed to set cache directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the next NORM event
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `wait` - Whether to wait for an event or return immediately if none is available
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(Some(Event))` if an event was available, `Ok(None)` if no event was available
|
||||||
|
/// and `wait` was `false`, or `Err` if an error occurred
|
||||||
|
pub fn next_event(&self, wait: bool) -> Result<Option<Event>> {
|
||||||
|
let mut raw_event = unsafe { mem::zeroed::<NormEvent>() };
|
||||||
|
let success = unsafe { NormGetNextEvent(self.handle, &mut raw_event, wait) };
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(Event::from_raw(raw_event)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an iterator over NORM events
|
||||||
|
///
|
||||||
|
/// This is a convenience wrapper around `next_event` that returns an iterator
|
||||||
|
/// that will continuously yield events. The iterator will block until an event
|
||||||
|
/// is available.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An iterator over NORM events
|
||||||
|
pub fn events(&self) -> EventIterator<'_> {
|
||||||
|
EventIterator { instance: self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new NORM session
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `session_address` - The multicast address for the session
|
||||||
|
/// * `session_port` - The port number for the session
|
||||||
|
/// * `local_node_id` - The local node ID (use `NORM_NODE_ANY` for automatic)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new NORM session or an error if the session could not be created
|
||||||
|
pub fn create_session<A: AsRef<str>>(
|
||||||
|
&self,
|
||||||
|
session_address: A,
|
||||||
|
session_port: u16,
|
||||||
|
local_node_id: NodeId,
|
||||||
|
) -> Result<Session> {
|
||||||
|
Session::new(self.handle, session_address, session_port, local_node_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the file descriptor for the NORM instance
|
||||||
|
///
|
||||||
|
/// This descriptor can be used with `select()` or similar APIs to wait for
|
||||||
|
/// NORM events.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The file descriptor for the NORM instance
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn descriptor(&self) -> RawFd {
|
||||||
|
unsafe { NormGetDescriptor(self.handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a debug log file
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `path` - The path to the log file
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` if the log file was successfully opened, `Err` otherwise
|
||||||
|
pub fn open_debug_log<P: AsRef<str>>(&self, path: P) -> Result<()> {
|
||||||
|
let c_path = string_to_c_string(path.as_ref())?;
|
||||||
|
let success = unsafe { NormOpenDebugLog(self.handle, c_path.as_ptr()) };
|
||||||
|
bool_result(success, "Failed to open debug log file")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close the debug log file
|
||||||
|
pub fn close_debug_log(&self) {
|
||||||
|
unsafe { NormCloseDebugLog(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set custom allocation functions for NORM_OBJECT_DATA
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `alloc_func` - Function to allocate memory for NORM_OBJECT_DATA
|
||||||
|
/// * `free_func` - Function to free memory allocated by `alloc_func`
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This function is unsafe because it takes raw function pointers that
|
||||||
|
/// must adhere to the expected memory allocation/deallocation semantics.
|
||||||
|
pub unsafe fn set_allocation_functions(
|
||||||
|
&self,
|
||||||
|
alloc_func: NormAllocFunctionHandle,
|
||||||
|
free_func: NormFreeFunctionHandle,
|
||||||
|
) {
|
||||||
|
NormSetAllocationFunctions(self.handle, alloc_func, free_func);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the raw NORM instance handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The raw NORM instance handle
|
||||||
|
pub(crate) fn handle(&self) -> NormInstanceHandle {
|
||||||
|
self.handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Instance {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { NormDestroyInstance(self.handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
impl AsRawFd for Instance {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.descriptor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over NORM events
|
||||||
|
///
|
||||||
|
/// This iterator will continuously yield events from a NORM instance.
|
||||||
|
/// It will block until an event is available.
|
||||||
|
pub struct EventIterator<'a> {
|
||||||
|
instance: &'a Instance,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for EventIterator<'a> {
|
||||||
|
type Item = Event;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.instance.next_event(true) {
|
||||||
|
Ok(Some(event)) => Some(event),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
//! Safe, idiomatic Rust bindings for the NORM (NACK-Oriented Reliable Multicast) protocol.
|
||||||
|
//!
|
||||||
|
//! NORM is a protocol for reliable multicast transmission developed by the US Naval Research
|
||||||
|
//! Laboratory. This crate provides safe Rust wrappers around the C API, handling resource
|
||||||
|
//! management and providing an idiomatic interface.
|
||||||
|
//!
|
||||||
|
//! # Features
|
||||||
|
//!
|
||||||
|
//! - RAII-based resource management for NORM handles
|
||||||
|
//! - Idiomatic error handling
|
||||||
|
//! - Iterator-based event handling
|
||||||
|
//! - Ergonomic multicast configuration
|
||||||
|
//! - Optional async support with tokio (feature = "tokio")
|
||||||
|
|
||||||
|
// Re-export norm-sys for advanced users
|
||||||
|
pub use norm_sys;
|
||||||
|
|
||||||
|
// Module declarations
|
||||||
|
mod error;
|
||||||
|
mod types;
|
||||||
|
mod instance;
|
||||||
|
mod session;
|
||||||
|
mod object;
|
||||||
|
mod node;
|
||||||
|
mod event;
|
||||||
|
mod multicast;
|
||||||
|
|
||||||
|
// Optional modules
|
||||||
|
// Note: Async support with tokio is planned for future implementation
|
||||||
|
// #[cfg(feature = "tokio")]
|
||||||
|
// pub mod tokio;
|
||||||
|
|
||||||
|
// Public re-exports
|
||||||
|
pub use error::{Error, Result};
|
||||||
|
pub use types::*;
|
||||||
|
pub use instance::Instance;
|
||||||
|
pub use session::Session;
|
||||||
|
pub use object::Object;
|
||||||
|
pub use node::Node;
|
||||||
|
pub use event::Event;
|
||||||
|
pub use multicast::{MulticastConfig, MulticastExt, is_multicast_address};
|
||||||
|
|
||||||
|
// Version information
|
||||||
|
pub const VERSION_MAJOR: u32 = norm_sys::NORM_VERSION_MAJOR as u32;
|
||||||
|
pub const VERSION_MINOR: u32 = norm_sys::NORM_VERSION_MINOR as u32;
|
||||||
|
pub const VERSION_PATCH: u32 = norm_sys::NORM_VERSION_PATCH as u32;
|
||||||
|
|
||||||
|
/// Get the version of the NORM library as a tuple (major, minor, patch)
|
||||||
|
pub fn version() -> (u32, u32, u32) {
|
||||||
|
(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,339 @@
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::session::Session;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// Ergonomic multicast configuration for NORM sessions.
|
||||||
|
///
|
||||||
|
/// This struct provides a builder-style API for configuring multicast options
|
||||||
|
/// for a NORM session. It handles the details of setting up the various multicast
|
||||||
|
/// options and applying them to a session.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MulticastConfig {
|
||||||
|
/// The multicast address
|
||||||
|
address: String,
|
||||||
|
/// The port
|
||||||
|
port: u16,
|
||||||
|
/// The network interface to use for multicast
|
||||||
|
interface: Option<String>,
|
||||||
|
/// The time-to-live (TTL) for multicast packets
|
||||||
|
ttl: Option<u8>,
|
||||||
|
/// Whether to enable loopback
|
||||||
|
loopback: Option<bool>,
|
||||||
|
/// The source address for SSM (Source-Specific Multicast)
|
||||||
|
ssm_source: Option<String>,
|
||||||
|
/// The type of service (TOS) value for IP packets
|
||||||
|
tos: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MulticastConfig {
|
||||||
|
/// Create a new multicast configuration
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `address` - The multicast address
|
||||||
|
/// * `port` - The port number
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new multicast configuration
|
||||||
|
pub fn new(address: impl Into<String>, port: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
address: address.into(),
|
||||||
|
port,
|
||||||
|
interface: None,
|
||||||
|
ttl: None,
|
||||||
|
loopback: None,
|
||||||
|
ssm_source: None,
|
||||||
|
tos: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the network interface for multicast
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `interface` - The name of the interface to use
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Self for method chaining
|
||||||
|
pub fn interface(mut self, interface: impl Into<String>) -> Self {
|
||||||
|
self.interface = Some(interface.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the time-to-live (TTL) for multicast packets
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `ttl` - The TTL value (0-255)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Self for method chaining
|
||||||
|
pub fn ttl(mut self, ttl: u8) -> Self {
|
||||||
|
self.ttl = Some(ttl);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable multicast loopback
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `enable` - Whether to enable loopback
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Self for method chaining
|
||||||
|
pub fn loopback(mut self, enable: bool) -> Self {
|
||||||
|
self.loopback = Some(enable);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the source address for SSM (Source-Specific Multicast)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `source` - The source address
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Self for method chaining
|
||||||
|
pub fn ssm_source(mut self, source: impl Into<String>) -> Self {
|
||||||
|
self.ssm_source = Some(source.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the type of service (TOS) value for IP packets
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `tos` - The TOS value (0-255)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Self for method chaining
|
||||||
|
pub fn tos(mut self, tos: u8) -> Self {
|
||||||
|
self.tos = Some(tos);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the configuration to a session
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `session` - The session to apply the configuration to
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if any configuration option could not be applied
|
||||||
|
pub fn apply(&self, session: &Session) -> Result<()> {
|
||||||
|
if let Some(ref interface) = self.interface {
|
||||||
|
session.set_multicast_interface(interface)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ttl) = self.ttl {
|
||||||
|
session.set_ttl(ttl)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(loopback) = self.loopback {
|
||||||
|
session.set_multicast_loopback(loopback)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref source) = self.ssm_source {
|
||||||
|
session.set_ssm(source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tos) = self.tos {
|
||||||
|
session.set_tos(tos)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the multicast address
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The multicast address as a string
|
||||||
|
pub fn address(&self) -> &str {
|
||||||
|
&self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the port number
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The port number
|
||||||
|
pub fn port(&self) -> u16 {
|
||||||
|
self.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MulticastConfig {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.address, self.port)?;
|
||||||
|
|
||||||
|
if let Some(ref interface) = self.interface {
|
||||||
|
write!(f, " on {}", interface)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ttl) = self.ttl {
|
||||||
|
write!(f, " ttl={}", ttl)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(loopback) = self.loopback {
|
||||||
|
write!(f, " loopback={}", loopback)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref source) = self.ssm_source {
|
||||||
|
write!(f, " ssm_source={}", source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tos) = self.tos {
|
||||||
|
write!(f, " tos={}", tos)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenient macro for multicast configuration
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use norm::multicast;
|
||||||
|
/// let config = multicast!("224.1.2.3", 6003, {
|
||||||
|
/// ttl: 64,
|
||||||
|
/// interface: "eth0",
|
||||||
|
/// loopback: true,
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! multicast {
|
||||||
|
($addr:expr, $port:expr) => {
|
||||||
|
$crate::MulticastConfig::new($addr, $port)
|
||||||
|
};
|
||||||
|
($addr:expr, $port:expr, { $($key:ident: $value:expr),* $(,)? }) => {{
|
||||||
|
let mut config = $crate::MulticastConfig::new($addr, $port);
|
||||||
|
$(
|
||||||
|
config = config.$key($value);
|
||||||
|
)*
|
||||||
|
config
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait for NORM sessions to apply multicast configurations
|
||||||
|
pub trait MulticastExt {
|
||||||
|
/// Apply a multicast configuration to the session
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `config` - The multicast configuration to apply
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the configuration could not be applied
|
||||||
|
fn with_multicast(&self, config: &MulticastConfig) -> Result<&Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MulticastExt for Session {
|
||||||
|
fn with_multicast(&self, config: &MulticastConfig) -> Result<&Self> {
|
||||||
|
config.apply(self)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if an IP address is a multicast address
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `addr` - The IP address to check
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if the address is a multicast address, `false` otherwise
|
||||||
|
pub fn is_multicast_address(addr: &str) -> bool {
|
||||||
|
if let Ok(ip) = IpAddr::from_str(addr) {
|
||||||
|
match ip {
|
||||||
|
IpAddr::V4(ipv4) => ipv4.is_multicast(),
|
||||||
|
IpAddr::V6(ipv6) => ipv6.is_multicast(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multicast_config_builder() {
|
||||||
|
let config = MulticastConfig::new("224.1.2.3", 6003)
|
||||||
|
.ttl(64)
|
||||||
|
.interface("eth0")
|
||||||
|
.loopback(true)
|
||||||
|
.ssm_source("192.168.1.1")
|
||||||
|
.tos(0x10);
|
||||||
|
|
||||||
|
assert_eq!(config.address(), "224.1.2.3");
|
||||||
|
assert_eq!(config.port(), 6003);
|
||||||
|
assert_eq!(config.ttl, Some(64));
|
||||||
|
assert_eq!(config.interface.as_deref(), Some("eth0"));
|
||||||
|
assert_eq!(config.loopback, Some(true));
|
||||||
|
assert_eq!(config.ssm_source.as_deref(), Some("192.168.1.1"));
|
||||||
|
assert_eq!(config.tos, Some(0x10));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multicast_config_display() {
|
||||||
|
let config = MulticastConfig::new("224.1.2.3", 6003)
|
||||||
|
.ttl(64)
|
||||||
|
.loopback(true);
|
||||||
|
|
||||||
|
let display = format!("{}", config);
|
||||||
|
assert!(display.contains("224.1.2.3:6003"));
|
||||||
|
assert!(display.contains("ttl=64"));
|
||||||
|
assert!(display.contains("loopback=true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_multicast_address_ipv4() {
|
||||||
|
// Valid IPv4 multicast addresses (224.0.0.0 to 239.255.255.255)
|
||||||
|
assert!(is_multicast_address("224.0.0.0"));
|
||||||
|
assert!(is_multicast_address("224.1.2.3"));
|
||||||
|
assert!(is_multicast_address("239.255.255.255"));
|
||||||
|
|
||||||
|
// Invalid IPv4 multicast addresses
|
||||||
|
assert!(!is_multicast_address("192.168.1.1"));
|
||||||
|
assert!(!is_multicast_address("10.0.0.1"));
|
||||||
|
assert!(!is_multicast_address("127.0.0.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_multicast_address_ipv6() {
|
||||||
|
// Valid IPv6 multicast addresses (start with ff00::/8)
|
||||||
|
assert!(is_multicast_address("ff02::1"));
|
||||||
|
assert!(is_multicast_address("ff05::1:3"));
|
||||||
|
|
||||||
|
// Invalid IPv6 multicast addresses
|
||||||
|
assert!(!is_multicast_address("fe80::1"));
|
||||||
|
assert!(!is_multicast_address("::1"));
|
||||||
|
assert!(!is_multicast_address("2001:db8::1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_multicast_address_invalid() {
|
||||||
|
// Invalid IP addresses
|
||||||
|
assert!(!is_multicast_address("not.an.ip"));
|
||||||
|
assert!(!is_multicast_address(""));
|
||||||
|
assert!(!is_multicast_address("999.999.999.999"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multicast_macro() {
|
||||||
|
let config = multicast!("224.1.2.3", 6003, {
|
||||||
|
ttl: 64,
|
||||||
|
loopback: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(config.address(), "224.1.2.3");
|
||||||
|
assert_eq!(config.port(), 6003);
|
||||||
|
assert_eq!(config.ttl, Some(64));
|
||||||
|
assert_eq!(config.loopback, Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multicast_macro_simple() {
|
||||||
|
let config = multicast!("224.1.2.3", 6003);
|
||||||
|
|
||||||
|
assert_eq!(config.address(), "224.1.2.3");
|
||||||
|
assert_eq!(config.port(), 6003);
|
||||||
|
assert_eq!(config.ttl, None);
|
||||||
|
assert_eq!(config.interface, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
use crate::types::*;
|
||||||
|
use norm_sys::*;
|
||||||
|
|
||||||
|
/// NORM node handle with RAII semantics.
|
||||||
|
///
|
||||||
|
/// A Node represents a participant in a NORM session, either as a sender or receiver.
|
||||||
|
///
|
||||||
|
/// When dropped, the node will be released by calling `NormNodeRelease`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Node {
|
||||||
|
/// The raw NORM node handle
|
||||||
|
handle: NormNodeHandle,
|
||||||
|
/// Whether this node is owned by us
|
||||||
|
owned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
/// Create a new Node from a raw NORM node handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `handle` - The raw NORM node handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new Node
|
||||||
|
pub(crate) fn from_handle(handle: NormNodeHandle) -> Self {
|
||||||
|
Node { handle, owned: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Node from a raw NORM node handle, without taking ownership
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `handle` - The raw NORM node handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new Node that will not release the handle when dropped
|
||||||
|
pub fn from_handle_unowned(handle: NormNodeHandle) -> Self {
|
||||||
|
Node { handle, owned: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the ID of the node
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The node ID
|
||||||
|
pub fn id(&self) -> NodeId {
|
||||||
|
unsafe { NormNodeGetId(self.handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the raw NORM node handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The raw NORM node handle
|
||||||
|
pub(crate) fn handle(&self) -> NormNodeHandle {
|
||||||
|
self.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retain a node handle, incrementing its reference count
|
||||||
|
pub fn retain(&self) {
|
||||||
|
unsafe { NormNodeRetain(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicitly release a node handle, decrementing its reference count
|
||||||
|
///
|
||||||
|
/// This is called automatically when the node is dropped, if it is owned.
|
||||||
|
pub fn release(&self) {
|
||||||
|
unsafe { NormNodeRelease(self.handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Node {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.owned {
|
||||||
|
unsafe { NormNodeRelease(self.handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// More methods will be added later
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
use crate::error::{Error, Result, check_handle};
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::node::Node;
|
||||||
|
use norm_sys::*;
|
||||||
|
use std::slice;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
/// NORM object handle with RAII semantics.
|
||||||
|
///
|
||||||
|
/// A Object represents a NORM transmission object, which can be a file, data, or stream.
|
||||||
|
///
|
||||||
|
/// When dropped, the object will be released by calling `NormObjectRelease`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Object {
|
||||||
|
/// The raw NORM object handle
|
||||||
|
handle: NormObjectHandle,
|
||||||
|
/// Whether this object is owned by us
|
||||||
|
owned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object {
|
||||||
|
/// Create a new Object from a raw NORM object handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `handle` - The raw NORM object handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new Object
|
||||||
|
pub(crate) fn from_handle(handle: NormObjectHandle) -> Self {
|
||||||
|
Object { handle, owned: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Object from a raw NORM object handle, without taking ownership
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `handle` - The raw NORM object handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new Object that will not release the handle when dropped
|
||||||
|
pub fn from_handle_unowned(handle: NormObjectHandle) -> Self {
|
||||||
|
Object { handle, owned: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the type of the object
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The object type
|
||||||
|
pub fn get_type(&self) -> ObjectType {
|
||||||
|
let obj_type = unsafe { NormObjectGetType(self.handle) };
|
||||||
|
ObjectType::from(obj_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the object has info data
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if the object has info data, `false` otherwise
|
||||||
|
pub fn has_info(&self) -> bool {
|
||||||
|
unsafe { NormObjectHasInfo(self.handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the info data associated with the object
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The info data as a byte vector, or an error if the info data could not be retrieved
|
||||||
|
pub fn get_info(&self) -> Result<Vec<u8>> {
|
||||||
|
if !self.has_info() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let info_len = unsafe { NormObjectGetInfoLength(self.handle) };
|
||||||
|
if info_len == 0 {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; info_len as usize];
|
||||||
|
let bytes_read = unsafe {
|
||||||
|
NormObjectGetInfo(
|
||||||
|
self.handle,
|
||||||
|
buffer.as_mut_ptr() as *mut c_char,
|
||||||
|
info_len,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if bytes_read == 0 {
|
||||||
|
Err(Error::OperationFailed("Failed to get info data".to_string()))
|
||||||
|
} else {
|
||||||
|
buffer.truncate(bytes_read as usize);
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size of the object in bytes
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The size of the object in bytes
|
||||||
|
pub fn size(&self) -> Size {
|
||||||
|
unsafe { NormObjectGetSize(self.handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of bytes still pending transmission
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The number of bytes still pending transmission
|
||||||
|
pub fn bytes_pending(&self) -> Size {
|
||||||
|
unsafe { NormObjectGetBytesPending(self.handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the data of a NORM_OBJECT_DATA object
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A slice containing the data, or an error if the data could not be accessed
|
||||||
|
pub fn access_data(&self) -> Result<&[u8]> {
|
||||||
|
if self.get_type() != ObjectType::Data {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data_ptr = unsafe { NormDataAccessData(self.handle) };
|
||||||
|
if data_ptr.is_null() {
|
||||||
|
return Err(Error::NullPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = self.size() as usize;
|
||||||
|
let data_slice = unsafe { slice::from_raw_parts(data_ptr as *const u8, size) };
|
||||||
|
Ok(data_slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel the object
|
||||||
|
///
|
||||||
|
/// This aborts transmission or reception of the object.
|
||||||
|
pub fn cancel(&self) {
|
||||||
|
unsafe { NormObjectCancel(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the sender of the object
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The sender node, or an error if the sender could not be retrieved
|
||||||
|
pub fn get_sender(&self) -> Result<Node> {
|
||||||
|
let sender = unsafe { NormObjectGetSender(self.handle) };
|
||||||
|
unsafe { check_handle(sender, NORM_NODE_INVALID) }
|
||||||
|
.map(Node::from_handle_unowned)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the raw NORM object handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The raw NORM object handle
|
||||||
|
pub(crate) fn handle(&self) -> NormObjectHandle {
|
||||||
|
self.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retain an object handle, incrementing its reference count
|
||||||
|
pub fn retain(&self) {
|
||||||
|
unsafe { NormObjectRetain(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicitly release an object handle, decrementing its reference count
|
||||||
|
///
|
||||||
|
/// This is called automatically when the object is dropped, if it is owned.
|
||||||
|
pub fn release(&self) {
|
||||||
|
unsafe { NormObjectRelease(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream-specific operations
|
||||||
|
|
||||||
|
/// Write data to a stream object
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `data` - The data to write to the stream
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The number of bytes written, or an error if the operation failed
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `Error::InvalidParameter` if this is not a stream object
|
||||||
|
pub fn stream_write(&self, data: &[u8]) -> Result<usize> {
|
||||||
|
if self.get_type() != ObjectType::Stream {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes_written = unsafe {
|
||||||
|
NormStreamWrite(
|
||||||
|
self.handle,
|
||||||
|
data.as_ptr() as *const c_char,
|
||||||
|
data.len() as u32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(bytes_written as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flush a stream object
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `eom` - Whether to mark this as end-of-message
|
||||||
|
/// * `flush_mode` - The flush mode to use
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `Error::InvalidParameter` if this is not a stream object
|
||||||
|
pub fn stream_flush(&self, eom: bool, flush_mode: crate::types::FlushMode) -> Result<()> {
|
||||||
|
if self.get_type() != ObjectType::Stream {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
NormStreamFlush(
|
||||||
|
self.handle,
|
||||||
|
eom,
|
||||||
|
flush_mode.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark end-of-message on a stream object
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `Error::InvalidParameter` if this is not a stream object
|
||||||
|
pub fn stream_mark_eom(&self) -> Result<()> {
|
||||||
|
if self.get_type() != ObjectType::Stream {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { NormStreamMarkEom(self.handle) };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close a stream object
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `graceful` - Whether to close gracefully (wait for pending data) or immediately
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `Error::InvalidParameter` if this is not a stream object
|
||||||
|
pub fn stream_close(&self, graceful: bool) -> Result<()> {
|
||||||
|
if self.get_type() != ObjectType::Stream {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { NormStreamClose(self.handle, graceful) };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read data from a stream object
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `buffer` - The buffer to read data into
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A tuple of (bytes_read, data_slice) or an error if the operation failed
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `Error::InvalidParameter` if this is not a stream object
|
||||||
|
/// Returns `Error::OperationFailed` if the read operation failed
|
||||||
|
pub fn stream_read(&self, buffer: &mut [u8]) -> Result<usize> {
|
||||||
|
if self.get_type() != ObjectType::Stream {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut num_bytes = buffer.len() as u32;
|
||||||
|
let success = unsafe {
|
||||||
|
NormStreamRead(
|
||||||
|
self.handle,
|
||||||
|
buffer.as_mut_ptr() as *mut c_char,
|
||||||
|
&mut num_bytes,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if success {
|
||||||
|
Ok(num_bytes as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::OperationFailed("Failed to read from stream".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a stream has available space for writing
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if the stream has vacancy for writing, `false` otherwise
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `Error::InvalidParameter` if this is not a stream object
|
||||||
|
pub fn stream_has_vacancy(&self) -> Result<bool> {
|
||||||
|
if self.get_type() != ObjectType::Stream {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_vacancy = unsafe { NormStreamHasVacancy(self.handle) };
|
||||||
|
Ok(has_vacancy)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seek to the start of the next message in a stream
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if a message start was found, `false` otherwise
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `Error::InvalidParameter` if this is not a stream object
|
||||||
|
pub fn stream_seek_msg_start(&self) -> Result<bool> {
|
||||||
|
if self.get_type() != ObjectType::Stream {
|
||||||
|
return Err(Error::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = unsafe { NormStreamSeekMsgStart(self.handle) };
|
||||||
|
Ok(found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Object {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.owned {
|
||||||
|
unsafe { NormObjectRelease(self.handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,459 @@
|
||||||
|
use crate::error::{Error, Result, bool_result, check_handle, string_to_c_string};
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::object::Object;
|
||||||
|
use norm_sys::*;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
/// NORM session handle with RAII semantics.
|
||||||
|
///
|
||||||
|
/// A Session represents a NORM protocol session, which can be used to send and receive data.
|
||||||
|
/// Sessions can operate in multicast or unicast mode, and can act as a sender, receiver, or both.
|
||||||
|
///
|
||||||
|
/// When dropped, the session will automatically clean up resources by calling `NormDestroySession`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Session {
|
||||||
|
/// The raw NORM session handle
|
||||||
|
handle: NormSessionHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
/// Create a new NORM session.
|
||||||
|
///
|
||||||
|
/// This is typically called through `Instance::create_session` rather than directly.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `instance` - The NORM instance to create the session on
|
||||||
|
/// * `session_address` - The multicast or unicast address for the session
|
||||||
|
/// * `session_port` - The port number for the session
|
||||||
|
/// * `local_node_id` - The local node ID (use `NORM_NODE_ANY` for automatic)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new NORM session or an error if the session could not be created
|
||||||
|
pub(crate) fn new<A: AsRef<str>>(
|
||||||
|
instance: NormInstanceHandle,
|
||||||
|
session_address: A,
|
||||||
|
session_port: u16,
|
||||||
|
local_node_id: NodeId,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let c_addr = string_to_c_string(session_address.as_ref())?;
|
||||||
|
let handle = unsafe {
|
||||||
|
NormCreateSession(instance, c_addr.as_ptr(), session_port, local_node_id)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { check_handle(handle, NORM_SESSION_INVALID)? };
|
||||||
|
Ok(Session { handle })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the session as a NORM sender
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `instance_id` - The instance ID (session identifier)
|
||||||
|
/// * `buffer_space` - Buffer size in bytes for the sender's transmission queue
|
||||||
|
/// * `segment_size` - Size in bytes of the FEC payload segments
|
||||||
|
/// * `num_data` - Number of data segments per FEC block
|
||||||
|
/// * `num_parity` - Number of parity segments per FEC block
|
||||||
|
/// * `fec_id` - FEC encoding identifier (usually 0)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the sender could not be started
|
||||||
|
pub fn start_sender(
|
||||||
|
&self,
|
||||||
|
instance_id: SessionId,
|
||||||
|
buffer_space: u32,
|
||||||
|
segment_size: u16,
|
||||||
|
num_data: u16,
|
||||||
|
num_parity: u16,
|
||||||
|
fec_id: Option<u8>,
|
||||||
|
) -> Result<&Self> {
|
||||||
|
let fec_id = fec_id.unwrap_or(0);
|
||||||
|
let success = unsafe {
|
||||||
|
NormStartSender(
|
||||||
|
self.handle,
|
||||||
|
instance_id,
|
||||||
|
buffer_space,
|
||||||
|
segment_size,
|
||||||
|
num_data,
|
||||||
|
num_parity,
|
||||||
|
fec_id,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
bool_result(success, "Failed to start NORM sender")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the session as a sender
|
||||||
|
pub fn stop_sender(&self) -> &Self {
|
||||||
|
unsafe { NormStopSender(self.handle) };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the session as a NORM receiver
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `buffer_space` - Buffer size in bytes for the receiver
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the receiver could not be started
|
||||||
|
pub fn start_receiver(&self, buffer_space: u32) -> Result<&Self> {
|
||||||
|
let success = unsafe { NormStartReceiver(self.handle, buffer_space) };
|
||||||
|
bool_result(success, "Failed to start NORM receiver")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the session as a receiver
|
||||||
|
pub fn stop_receiver(&self) -> &Self {
|
||||||
|
unsafe { NormStopReceiver(self.handle) };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the transmission rate in bits per second
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `bits_per_second` - The transmission rate in bits per second
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A reference to self for method chaining
|
||||||
|
pub fn set_tx_rate(&self, bits_per_second: f64) -> &Self {
|
||||||
|
unsafe { NormSetTxRate(self.handle, bits_per_second) };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current transmission rate in bits per second
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The current transmission rate in bits per second
|
||||||
|
pub fn tx_rate(&self) -> f64 {
|
||||||
|
unsafe { NormGetTxRate(self.handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the socket buffer size for transmission
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `buffer_size` - The socket buffer size in bytes
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the buffer size could not be set
|
||||||
|
pub fn set_tx_socket_buffer(&self, buffer_size: u32) -> Result<&Self> {
|
||||||
|
let success = unsafe { NormSetTxSocketBuffer(self.handle, buffer_size) };
|
||||||
|
bool_result(success, "Failed to set TX socket buffer size")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the socket buffer size for reception
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `buffer_size` - The socket buffer size in bytes
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the buffer size could not be set
|
||||||
|
pub fn set_rx_socket_buffer(&self, buffer_size: u32) -> Result<&Self> {
|
||||||
|
let success = unsafe { NormSetRxSocketBuffer(self.handle, buffer_size) };
|
||||||
|
bool_result(success, "Failed to set RX socket buffer size")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the flow control factor
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `flow_control_factor` - The flow control factor
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A reference to self for method chaining
|
||||||
|
pub fn set_flow_control(&self, flow_control_factor: f64) -> &Self {
|
||||||
|
unsafe { NormSetFlowControl(self.handle, flow_control_factor) };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable congestion control
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `enable` - Whether to enable congestion control
|
||||||
|
/// * `adjust_rate` - Whether to adjust the rate based on congestion control
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A reference to self for method chaining
|
||||||
|
pub fn set_congestion_control(&self, enable: bool, adjust_rate: bool) -> &Self {
|
||||||
|
unsafe { NormSetCongestionControl(self.handle, enable, adjust_rate) };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the transmission rate bounds
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `rate_min` - The minimum transmission rate in bits per second
|
||||||
|
/// * `rate_max` - The maximum transmission rate in bits per second
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A reference to self for method chaining
|
||||||
|
pub fn set_tx_rate_bounds(&self, rate_min: f64, rate_max: f64) -> &Self {
|
||||||
|
unsafe { NormSetTxRateBounds(self.handle, rate_min, rate_max) };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the multicast interface
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `interface_name` - The name of the interface to use for multicast
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the interface could not be set
|
||||||
|
pub fn set_multicast_interface<I: AsRef<str>>(&self, interface_name: I) -> Result<&Self> {
|
||||||
|
let c_iface = string_to_c_string(interface_name.as_ref())?;
|
||||||
|
let success = unsafe { NormSetMulticastInterface(self.handle, c_iface.as_ptr()) };
|
||||||
|
bool_result(success, "Failed to set multicast interface")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set source-specific multicast (SSM) source address
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `source_address` - The source address for SSM
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the SSM source could not be set
|
||||||
|
pub fn set_ssm<A: AsRef<str>>(&self, source_address: A) -> Result<&Self> {
|
||||||
|
let c_addr = string_to_c_string(source_address.as_ref())?;
|
||||||
|
let success = unsafe { NormSetSSM(self.handle, c_addr.as_ptr()) };
|
||||||
|
bool_result(success, "Failed to set SSM source address")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the time-to-live (TTL) for multicast packets
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `ttl` - The TTL value (0-255)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the TTL could not be set
|
||||||
|
pub fn set_ttl(&self, ttl: u8) -> Result<&Self> {
|
||||||
|
let success = unsafe { NormSetTTL(self.handle, ttl) };
|
||||||
|
bool_result(success, "Failed to set TTL")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the TOS (Type of Service) value for packets
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `tos` - The TOS value (0-255)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the TOS could not be set
|
||||||
|
pub fn set_tos(&self, tos: u8) -> Result<&Self> {
|
||||||
|
let success = unsafe { NormSetTOS(self.handle, tos) };
|
||||||
|
bool_result(success, "Failed to set TOS")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable loopback
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `enable` - Whether to enable loopback
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if loopback could not be set
|
||||||
|
pub fn set_loopback(&self, enable: bool) -> Result<&Self> {
|
||||||
|
let success = unsafe { NormSetLoopback(self.handle, enable) };
|
||||||
|
bool_result(success, "Failed to set loopback")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable multicast loopback
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `enable` - Whether to enable multicast loopback
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if multicast loopback could not be set
|
||||||
|
pub fn set_multicast_loopback(&self, enable: bool) -> Result<&Self> {
|
||||||
|
let success = unsafe { NormSetMulticastLoopback(self.handle, enable) };
|
||||||
|
bool_result(success, "Failed to set multicast loopback")?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if an address is a unicast address
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `address` - The address to check
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `true` if the address is a unicast address, `false` otherwise
|
||||||
|
pub fn is_unicast_address<A: AsRef<str>>(address: A) -> bool {
|
||||||
|
if let Ok(c_addr) = string_to_c_string(address.as_ref()) {
|
||||||
|
unsafe { NormIsUnicastAddress(c_addr.as_ptr()) }
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the GRTT (Group Round Trip Time) estimate
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `grtt_estimate` - The GRTT estimate in seconds
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A reference to self for method chaining
|
||||||
|
pub fn set_grtt_estimate(&self, grtt_estimate: f64) -> &Self {
|
||||||
|
unsafe { NormSetGrttEstimate(self.handle, grtt_estimate) };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current GRTT estimate
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The current GRTT estimate in seconds
|
||||||
|
pub fn grtt_estimate(&self) -> f64 {
|
||||||
|
unsafe { NormGetGrttEstimate(self.handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enqueue a file for transmission
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `file_path` - The path to the file to send
|
||||||
|
/// * `info` - Optional info data to associate with the file
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A handle to the enqueued file object or an error if the file could not be enqueued
|
||||||
|
pub fn file_enqueue<P: AsRef<str>>(&self, file_path: P, info: Option<&[u8]>) -> Result<Object> {
|
||||||
|
let c_path = string_to_c_string(file_path.as_ref())?;
|
||||||
|
|
||||||
|
let (info_ptr, info_len) = match info {
|
||||||
|
Some(i) => (i.as_ptr() as *const c_char, i.len()),
|
||||||
|
None => (ptr::null(), 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = unsafe {
|
||||||
|
NormFileEnqueue(self.handle, c_path.as_ptr(), info_ptr, info_len as u32)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
check_handle(handle, NORM_OBJECT_INVALID)
|
||||||
|
.map_err(|_| Error::FileError("Failed to enqueue file".to_string()))
|
||||||
|
.map(|h| Object::from_handle(h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enqueue data for transmission
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `data` - The data to send
|
||||||
|
/// * `info` - Optional info data to associate with the data
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A handle to the enqueued data object or an error if the data could not be enqueued
|
||||||
|
pub fn data_enqueue(&self, data: &[u8], info: Option<&[u8]>) -> Result<Object> {
|
||||||
|
let (info_ptr, info_len) = match info {
|
||||||
|
Some(i) => (i.as_ptr() as *const c_char, i.len()),
|
||||||
|
None => (ptr::null(), 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = unsafe {
|
||||||
|
NormDataEnqueue(
|
||||||
|
self.handle,
|
||||||
|
data.as_ptr() as *const c_char,
|
||||||
|
data.len() as u32,
|
||||||
|
info_ptr,
|
||||||
|
info_len as u32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
check_handle(handle, NORM_OBJECT_INVALID)
|
||||||
|
.map_err(|_| Error::OperationFailed("Failed to enqueue data".to_string()))
|
||||||
|
.map(|h| Object::from_handle(h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a stream for transmission
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `buffer_size` - The size of the stream buffer in bytes
|
||||||
|
/// * `info` - Optional info data to associate with the stream
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A handle to the stream object or an error if the stream could not be opened
|
||||||
|
pub fn stream_open(&self, buffer_size: u32, info: Option<&[u8]>) -> Result<Object> {
|
||||||
|
let (info_ptr, info_len) = match info {
|
||||||
|
Some(i) => (i.as_ptr() as *const c_char, i.len()),
|
||||||
|
None => (ptr::null(), 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = unsafe {
|
||||||
|
NormStreamOpen(self.handle, buffer_size, info_ptr, info_len as u32)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
check_handle(handle, NORM_OBJECT_INVALID)
|
||||||
|
.map_err(|_| Error::OperationFailed("Failed to open stream".to_string()))
|
||||||
|
.map(|h| Object::from_handle(h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a watermark for acknowledgment
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `object` - The object to set as a watermark
|
||||||
|
/// * `override_flush` - Whether to override any active flush
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the watermark could not be set
|
||||||
|
pub fn set_watermark(&self, object: &Object, override_flush: bool) -> Result<()> {
|
||||||
|
let success = unsafe {
|
||||||
|
NormSetWatermark(self.handle, object.handle(), override_flush)
|
||||||
|
};
|
||||||
|
bool_result(success, "Failed to set watermark")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the watermark
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the watermark could not be reset
|
||||||
|
pub fn reset_watermark(&self) -> Result<()> {
|
||||||
|
let success = unsafe { NormResetWatermark(self.handle) };
|
||||||
|
bool_result(success, "Failed to reset watermark")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel the watermark
|
||||||
|
pub fn cancel_watermark(&self) {
|
||||||
|
unsafe { NormCancelWatermark(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a command to all receivers
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `command` - The command data to send
|
||||||
|
/// * `robust` - Whether to send the command robustly (with more reliability)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Ok(())` on success or an `Err` if the command could not be sent
|
||||||
|
pub fn send_command(&self, command: &[u8], robust: bool) -> Result<()> {
|
||||||
|
let success = unsafe {
|
||||||
|
NormSendCommand(
|
||||||
|
self.handle,
|
||||||
|
command.as_ptr() as *const c_char,
|
||||||
|
command.len() as u32,
|
||||||
|
robust,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
bool_result(success, "Failed to send command")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel a command in progress
|
||||||
|
pub fn cancel_command(&self) {
|
||||||
|
unsafe { NormCancelCommand(self.handle) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the raw NORM session handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The raw NORM session handle
|
||||||
|
pub(crate) fn handle(&self) -> NormSessionHandle {
|
||||||
|
self.handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Session {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { NormDestroySession(self.handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,525 @@
|
||||||
|
// Allow non-uppercase globals from FFI bindings
|
||||||
|
// The bindgen-generated constants use lowercase prefixes, which is acceptable for FFI code
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
|
use norm_sys::*;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// NORM node identifier type
|
||||||
|
pub type NodeId = u32;
|
||||||
|
|
||||||
|
/// NORM session identifier type
|
||||||
|
pub type SessionId = u16;
|
||||||
|
|
||||||
|
/// NORM object transport identifier type
|
||||||
|
pub type ObjectTransportId = u16;
|
||||||
|
|
||||||
|
/// NORM size type for file and object sizes
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub type Size = i64;
|
||||||
|
|
||||||
|
/// NORM size type for file and object sizes
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub type Size = i64;
|
||||||
|
|
||||||
|
/// NORM object types
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum ObjectType {
|
||||||
|
/// Placeholder for no object type
|
||||||
|
None = NormObjectType_NORM_OBJECT_NONE as u32,
|
||||||
|
/// Data object (memory buffer)
|
||||||
|
Data = NormObjectType_NORM_OBJECT_DATA as u32,
|
||||||
|
/// File object
|
||||||
|
File = NormObjectType_NORM_OBJECT_FILE as u32,
|
||||||
|
/// Stream object
|
||||||
|
Stream = NormObjectType_NORM_OBJECT_STREAM as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormObjectType> for ObjectType {
|
||||||
|
fn from(t: NormObjectType) -> Self {
|
||||||
|
match t {
|
||||||
|
NormObjectType_NORM_OBJECT_NONE => ObjectType::None,
|
||||||
|
NormObjectType_NORM_OBJECT_DATA => ObjectType::Data,
|
||||||
|
NormObjectType_NORM_OBJECT_FILE => ObjectType::File,
|
||||||
|
NormObjectType_NORM_OBJECT_STREAM => ObjectType::Stream,
|
||||||
|
_ => ObjectType::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ObjectType> for NormObjectType {
|
||||||
|
fn from(t: ObjectType) -> Self {
|
||||||
|
match t {
|
||||||
|
ObjectType::None => NormObjectType_NORM_OBJECT_NONE,
|
||||||
|
ObjectType::Data => NormObjectType_NORM_OBJECT_DATA,
|
||||||
|
ObjectType::File => NormObjectType_NORM_OBJECT_FILE,
|
||||||
|
ObjectType::Stream => NormObjectType_NORM_OBJECT_STREAM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM flush modes
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum FlushMode {
|
||||||
|
/// No flush
|
||||||
|
None = NormFlushMode_NORM_FLUSH_NONE as u32,
|
||||||
|
/// Passive flush - minimal delay
|
||||||
|
Passive = NormFlushMode_NORM_FLUSH_PASSIVE as u32,
|
||||||
|
/// Active flush - actively seek acknowledgment
|
||||||
|
Active = NormFlushMode_NORM_FLUSH_ACTIVE as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormFlushMode> for FlushMode {
|
||||||
|
fn from(m: NormFlushMode) -> Self {
|
||||||
|
match m {
|
||||||
|
NormFlushMode_NORM_FLUSH_NONE => FlushMode::None,
|
||||||
|
NormFlushMode_NORM_FLUSH_PASSIVE => FlushMode::Passive,
|
||||||
|
NormFlushMode_NORM_FLUSH_ACTIVE => FlushMode::Active,
|
||||||
|
_ => FlushMode::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FlushMode> for NormFlushMode {
|
||||||
|
fn from(m: FlushMode) -> Self {
|
||||||
|
match m {
|
||||||
|
FlushMode::None => NormFlushMode_NORM_FLUSH_NONE,
|
||||||
|
FlushMode::Passive => NormFlushMode_NORM_FLUSH_PASSIVE,
|
||||||
|
FlushMode::Active => NormFlushMode_NORM_FLUSH_ACTIVE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM nacking modes
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum NackingMode {
|
||||||
|
/// No NACKs
|
||||||
|
None = NormNackingMode_NORM_NACK_NONE as u32,
|
||||||
|
/// Info-only NACKs
|
||||||
|
InfoOnly = NormNackingMode_NORM_NACK_INFO_ONLY as u32,
|
||||||
|
/// Normal NACKs
|
||||||
|
Normal = NormNackingMode_NORM_NACK_NORMAL as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormNackingMode> for NackingMode {
|
||||||
|
fn from(m: NormNackingMode) -> Self {
|
||||||
|
match m {
|
||||||
|
NormNackingMode_NORM_NACK_NONE => NackingMode::None,
|
||||||
|
NormNackingMode_NORM_NACK_INFO_ONLY => NackingMode::InfoOnly,
|
||||||
|
NormNackingMode_NORM_NACK_NORMAL => NackingMode::Normal,
|
||||||
|
_ => NackingMode::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NackingMode> for NormNackingMode {
|
||||||
|
fn from(m: NackingMode) -> Self {
|
||||||
|
match m {
|
||||||
|
NackingMode::None => NormNackingMode_NORM_NACK_NONE,
|
||||||
|
NackingMode::InfoOnly => NormNackingMode_NORM_NACK_INFO_ONLY,
|
||||||
|
NackingMode::Normal => NormNackingMode_NORM_NACK_NORMAL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM acking status
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum AckingStatus {
|
||||||
|
/// Invalid ack status
|
||||||
|
Invalid = NormAckingStatus_NORM_ACK_INVALID as u32,
|
||||||
|
/// Ack failure
|
||||||
|
Failure = NormAckingStatus_NORM_ACK_FAILURE as u32,
|
||||||
|
/// Ack pending
|
||||||
|
Pending = NormAckingStatus_NORM_ACK_PENDING as u32,
|
||||||
|
/// Ack success
|
||||||
|
Success = NormAckingStatus_NORM_ACK_SUCCESS as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormAckingStatus> for AckingStatus {
|
||||||
|
fn from(s: NormAckingStatus) -> Self {
|
||||||
|
match s {
|
||||||
|
NormAckingStatus_NORM_ACK_INVALID => AckingStatus::Invalid,
|
||||||
|
NormAckingStatus_NORM_ACK_FAILURE => AckingStatus::Failure,
|
||||||
|
NormAckingStatus_NORM_ACK_PENDING => AckingStatus::Pending,
|
||||||
|
NormAckingStatus_NORM_ACK_SUCCESS => AckingStatus::Success,
|
||||||
|
_ => AckingStatus::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AckingStatus> for NormAckingStatus {
|
||||||
|
fn from(s: AckingStatus) -> Self {
|
||||||
|
match s {
|
||||||
|
AckingStatus::Invalid => NormAckingStatus_NORM_ACK_INVALID,
|
||||||
|
AckingStatus::Failure => NormAckingStatus_NORM_ACK_FAILURE,
|
||||||
|
AckingStatus::Pending => NormAckingStatus_NORM_ACK_PENDING,
|
||||||
|
AckingStatus::Success => NormAckingStatus_NORM_ACK_SUCCESS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM tracking status
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum TrackingStatus {
|
||||||
|
/// No tracking
|
||||||
|
None = NormTrackingStatus_NORM_TRACK_NONE as u32,
|
||||||
|
/// Track receivers
|
||||||
|
Receivers = NormTrackingStatus_NORM_TRACK_RECEIVERS as u32,
|
||||||
|
/// Track senders
|
||||||
|
Senders = NormTrackingStatus_NORM_TRACK_SENDERS as u32,
|
||||||
|
/// Track all
|
||||||
|
All = NormTrackingStatus_NORM_TRACK_ALL as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormTrackingStatus> for TrackingStatus {
|
||||||
|
fn from(s: NormTrackingStatus) -> Self {
|
||||||
|
match s {
|
||||||
|
NormTrackingStatus_NORM_TRACK_NONE => TrackingStatus::None,
|
||||||
|
NormTrackingStatus_NORM_TRACK_RECEIVERS => TrackingStatus::Receivers,
|
||||||
|
NormTrackingStatus_NORM_TRACK_SENDERS => TrackingStatus::Senders,
|
||||||
|
NormTrackingStatus_NORM_TRACK_ALL => TrackingStatus::All,
|
||||||
|
_ => TrackingStatus::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TrackingStatus> for NormTrackingStatus {
|
||||||
|
fn from(s: TrackingStatus) -> Self {
|
||||||
|
match s {
|
||||||
|
TrackingStatus::None => NormTrackingStatus_NORM_TRACK_NONE,
|
||||||
|
TrackingStatus::Receivers => NormTrackingStatus_NORM_TRACK_RECEIVERS,
|
||||||
|
TrackingStatus::Senders => NormTrackingStatus_NORM_TRACK_SENDERS,
|
||||||
|
TrackingStatus::All => NormTrackingStatus_NORM_TRACK_ALL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM probing mode
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum ProbingMode {
|
||||||
|
/// No probing
|
||||||
|
None = NormProbingMode_NORM_PROBE_NONE as u32,
|
||||||
|
/// Passive probing
|
||||||
|
Passive = NormProbingMode_NORM_PROBE_PASSIVE as u32,
|
||||||
|
/// Active probing
|
||||||
|
Active = NormProbingMode_NORM_PROBE_ACTIVE as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormProbingMode> for ProbingMode {
|
||||||
|
fn from(m: NormProbingMode) -> Self {
|
||||||
|
match m {
|
||||||
|
NormProbingMode_NORM_PROBE_NONE => ProbingMode::None,
|
||||||
|
NormProbingMode_NORM_PROBE_PASSIVE => ProbingMode::Passive,
|
||||||
|
NormProbingMode_NORM_PROBE_ACTIVE => ProbingMode::Active,
|
||||||
|
_ => ProbingMode::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProbingMode> for NormProbingMode {
|
||||||
|
fn from(m: ProbingMode) -> Self {
|
||||||
|
match m {
|
||||||
|
ProbingMode::None => NormProbingMode_NORM_PROBE_NONE,
|
||||||
|
ProbingMode::Passive => NormProbingMode_NORM_PROBE_PASSIVE,
|
||||||
|
ProbingMode::Active => NormProbingMode_NORM_PROBE_ACTIVE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM sync policy
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum SyncPolicy {
|
||||||
|
/// Sync to current data (join mid-stream)
|
||||||
|
Current = NormSyncPolicy_NORM_SYNC_CURRENT as u32,
|
||||||
|
/// Sync to stream from beginning
|
||||||
|
Stream = NormSyncPolicy_NORM_SYNC_STREAM as u32,
|
||||||
|
/// Sync to all data, old and new
|
||||||
|
All = NormSyncPolicy_NORM_SYNC_ALL as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormSyncPolicy> for SyncPolicy {
|
||||||
|
fn from(p: NormSyncPolicy) -> Self {
|
||||||
|
match p {
|
||||||
|
NormSyncPolicy_NORM_SYNC_CURRENT => SyncPolicy::Current,
|
||||||
|
NormSyncPolicy_NORM_SYNC_STREAM => SyncPolicy::Stream,
|
||||||
|
NormSyncPolicy_NORM_SYNC_ALL => SyncPolicy::All,
|
||||||
|
_ => SyncPolicy::Current,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SyncPolicy> for NormSyncPolicy {
|
||||||
|
fn from(p: SyncPolicy) -> Self {
|
||||||
|
match p {
|
||||||
|
SyncPolicy::Current => NormSyncPolicy_NORM_SYNC_CURRENT,
|
||||||
|
SyncPolicy::Stream => NormSyncPolicy_NORM_SYNC_STREAM,
|
||||||
|
SyncPolicy::All => NormSyncPolicy_NORM_SYNC_ALL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM repair boundary
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum RepairBoundary {
|
||||||
|
/// Block boundary
|
||||||
|
Block = NormRepairBoundary_NORM_BOUNDARY_BLOCK as u32,
|
||||||
|
/// Object boundary
|
||||||
|
Object = NormRepairBoundary_NORM_BOUNDARY_OBJECT as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormRepairBoundary> for RepairBoundary {
|
||||||
|
fn from(b: NormRepairBoundary) -> Self {
|
||||||
|
match b {
|
||||||
|
NormRepairBoundary_NORM_BOUNDARY_BLOCK => RepairBoundary::Block,
|
||||||
|
NormRepairBoundary_NORM_BOUNDARY_OBJECT => RepairBoundary::Object,
|
||||||
|
_ => RepairBoundary::Block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RepairBoundary> for NormRepairBoundary {
|
||||||
|
fn from(b: RepairBoundary) -> Self {
|
||||||
|
match b {
|
||||||
|
RepairBoundary::Block => NormRepairBoundary_NORM_BOUNDARY_BLOCK,
|
||||||
|
RepairBoundary::Object => NormRepairBoundary_NORM_BOUNDARY_OBJECT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_object_type_conversions() {
|
||||||
|
// Test From<NormObjectType> for ObjectType
|
||||||
|
let data_type: ObjectType = NormObjectType_NORM_OBJECT_DATA.into();
|
||||||
|
assert_eq!(data_type, ObjectType::Data);
|
||||||
|
|
||||||
|
let file_type: ObjectType = NormObjectType_NORM_OBJECT_FILE.into();
|
||||||
|
assert_eq!(file_type, ObjectType::File);
|
||||||
|
|
||||||
|
let stream_type: ObjectType = NormObjectType_NORM_OBJECT_STREAM.into();
|
||||||
|
assert_eq!(stream_type, ObjectType::Stream);
|
||||||
|
|
||||||
|
// Test From<ObjectType> for NormObjectType
|
||||||
|
let norm_data: NormObjectType = ObjectType::Data.into();
|
||||||
|
assert_eq!(norm_data, NormObjectType_NORM_OBJECT_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_flush_mode_conversions() {
|
||||||
|
assert_eq!(FlushMode::from(NormFlushMode_NORM_FLUSH_NONE), FlushMode::None);
|
||||||
|
assert_eq!(FlushMode::from(NormFlushMode_NORM_FLUSH_PASSIVE), FlushMode::Passive);
|
||||||
|
assert_eq!(FlushMode::from(NormFlushMode_NORM_FLUSH_ACTIVE), FlushMode::Active);
|
||||||
|
|
||||||
|
let norm_active: NormFlushMode = FlushMode::Active.into();
|
||||||
|
assert_eq!(norm_active, NormFlushMode_NORM_FLUSH_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_event_type_conversions() {
|
||||||
|
let tx_event: EventType = NormEventType_NORM_TX_QUEUE_EMPTY.into();
|
||||||
|
assert_eq!(tx_event, EventType::TxQueueEmpty);
|
||||||
|
|
||||||
|
let rx_event: EventType = NormEventType_NORM_RX_OBJECT_COMPLETED.into();
|
||||||
|
assert_eq!(rx_event, EventType::RxObjectCompleted);
|
||||||
|
|
||||||
|
let norm_event: NormEventType = EventType::TxObjectSent.into();
|
||||||
|
assert_eq!(norm_event, NormEventType_NORM_TX_OBJECT_SENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nacking_mode_conversions() {
|
||||||
|
assert_eq!(NackingMode::from(NormNackingMode_NORM_NACK_NONE), NackingMode::None);
|
||||||
|
assert_eq!(NackingMode::from(NormNackingMode_NORM_NACK_NORMAL), NackingMode::Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_acking_status_conversions() {
|
||||||
|
assert_eq!(AckingStatus::from(NormAckingStatus_NORM_ACK_SUCCESS), AckingStatus::Success);
|
||||||
|
assert_eq!(AckingStatus::from(NormAckingStatus_NORM_ACK_FAILURE), AckingStatus::Failure);
|
||||||
|
assert_eq!(AckingStatus::from(NormAckingStatus_NORM_ACK_PENDING), AckingStatus::Pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NORM event type
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum EventType {
|
||||||
|
/// Invalid event
|
||||||
|
Invalid = NormEventType_NORM_EVENT_INVALID as u32,
|
||||||
|
/// Transmit queue vacancy
|
||||||
|
TxQueueVacancy = NormEventType_NORM_TX_QUEUE_VACANCY as u32,
|
||||||
|
/// Transmit queue empty
|
||||||
|
TxQueueEmpty = NormEventType_NORM_TX_QUEUE_EMPTY as u32,
|
||||||
|
/// Transmit flush completed
|
||||||
|
TxFlushCompleted = NormEventType_NORM_TX_FLUSH_COMPLETED as u32,
|
||||||
|
/// Transmit watermark completed
|
||||||
|
TxWatermarkCompleted = NormEventType_NORM_TX_WATERMARK_COMPLETED as u32,
|
||||||
|
/// Transmit command sent
|
||||||
|
TxCmdSent = NormEventType_NORM_TX_CMD_SENT as u32,
|
||||||
|
/// Transmit object sent
|
||||||
|
TxObjectSent = NormEventType_NORM_TX_OBJECT_SENT as u32,
|
||||||
|
/// Transmit object purged
|
||||||
|
TxObjectPurged = NormEventType_NORM_TX_OBJECT_PURGED as u32,
|
||||||
|
/// Transmit rate changed
|
||||||
|
TxRateChanged = NormEventType_NORM_TX_RATE_CHANGED as u32,
|
||||||
|
/// Local sender closed
|
||||||
|
LocalSenderClosed = NormEventType_NORM_LOCAL_SENDER_CLOSED as u32,
|
||||||
|
/// Remote sender new
|
||||||
|
RemoteSenderNew = NormEventType_NORM_REMOTE_SENDER_NEW as u32,
|
||||||
|
/// Remote sender reset
|
||||||
|
RemoteSenderReset = NormEventType_NORM_REMOTE_SENDER_RESET as u32,
|
||||||
|
/// Remote sender address
|
||||||
|
RemoteSenderAddress = NormEventType_NORM_REMOTE_SENDER_ADDRESS as u32,
|
||||||
|
/// Remote sender active
|
||||||
|
RemoteSenderActive = NormEventType_NORM_REMOTE_SENDER_ACTIVE as u32,
|
||||||
|
/// Remote sender inactive
|
||||||
|
RemoteSenderInactive = NormEventType_NORM_REMOTE_SENDER_INACTIVE as u32,
|
||||||
|
/// Remote sender purged
|
||||||
|
RemoteSenderPurged = NormEventType_NORM_REMOTE_SENDER_PURGED as u32,
|
||||||
|
/// Receive command new
|
||||||
|
RxCmdNew = NormEventType_NORM_RX_CMD_NEW as u32,
|
||||||
|
/// Receive object new
|
||||||
|
RxObjectNew = NormEventType_NORM_RX_OBJECT_NEW as u32,
|
||||||
|
/// Receive object info
|
||||||
|
RxObjectInfo = NormEventType_NORM_RX_OBJECT_INFO as u32,
|
||||||
|
/// Receive object updated
|
||||||
|
RxObjectUpdated = NormEventType_NORM_RX_OBJECT_UPDATED as u32,
|
||||||
|
/// Receive object completed
|
||||||
|
RxObjectCompleted = NormEventType_NORM_RX_OBJECT_COMPLETED as u32,
|
||||||
|
/// Receive object aborted
|
||||||
|
RxObjectAborted = NormEventType_NORM_RX_OBJECT_ABORTED as u32,
|
||||||
|
/// Receive ack request
|
||||||
|
RxAckRequest = NormEventType_NORM_RX_ACK_REQUEST as u32,
|
||||||
|
/// GRTT updated
|
||||||
|
GrttUpdated = NormEventType_NORM_GRTT_UPDATED as u32,
|
||||||
|
/// CC active
|
||||||
|
CcActive = NormEventType_NORM_CC_ACTIVE as u32,
|
||||||
|
/// CC inactive
|
||||||
|
CcInactive = NormEventType_NORM_CC_INACTIVE as u32,
|
||||||
|
/// Acking node new
|
||||||
|
AckingNodeNew = NormEventType_NORM_ACKING_NODE_NEW as u32,
|
||||||
|
/// Send error
|
||||||
|
SendError = NormEventType_NORM_SEND_ERROR as u32,
|
||||||
|
/// User timeout
|
||||||
|
UserTimeout = NormEventType_NORM_USER_TIMEOUT as u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NormEventType> for EventType {
|
||||||
|
fn from(t: NormEventType) -> Self {
|
||||||
|
match t {
|
||||||
|
NormEventType_NORM_EVENT_INVALID => EventType::Invalid,
|
||||||
|
NormEventType_NORM_TX_QUEUE_VACANCY => EventType::TxQueueVacancy,
|
||||||
|
NormEventType_NORM_TX_QUEUE_EMPTY => EventType::TxQueueEmpty,
|
||||||
|
NormEventType_NORM_TX_FLUSH_COMPLETED => EventType::TxFlushCompleted,
|
||||||
|
NormEventType_NORM_TX_WATERMARK_COMPLETED => EventType::TxWatermarkCompleted,
|
||||||
|
NormEventType_NORM_TX_CMD_SENT => EventType::TxCmdSent,
|
||||||
|
NormEventType_NORM_TX_OBJECT_SENT => EventType::TxObjectSent,
|
||||||
|
NormEventType_NORM_TX_OBJECT_PURGED => EventType::TxObjectPurged,
|
||||||
|
NormEventType_NORM_TX_RATE_CHANGED => EventType::TxRateChanged,
|
||||||
|
NormEventType_NORM_LOCAL_SENDER_CLOSED => EventType::LocalSenderClosed,
|
||||||
|
NormEventType_NORM_REMOTE_SENDER_NEW => EventType::RemoteSenderNew,
|
||||||
|
NormEventType_NORM_REMOTE_SENDER_RESET => EventType::RemoteSenderReset,
|
||||||
|
NormEventType_NORM_REMOTE_SENDER_ADDRESS => EventType::RemoteSenderAddress,
|
||||||
|
NormEventType_NORM_REMOTE_SENDER_ACTIVE => EventType::RemoteSenderActive,
|
||||||
|
NormEventType_NORM_REMOTE_SENDER_INACTIVE => EventType::RemoteSenderInactive,
|
||||||
|
NormEventType_NORM_REMOTE_SENDER_PURGED => EventType::RemoteSenderPurged,
|
||||||
|
NormEventType_NORM_RX_CMD_NEW => EventType::RxCmdNew,
|
||||||
|
NormEventType_NORM_RX_OBJECT_NEW => EventType::RxObjectNew,
|
||||||
|
NormEventType_NORM_RX_OBJECT_INFO => EventType::RxObjectInfo,
|
||||||
|
NormEventType_NORM_RX_OBJECT_UPDATED => EventType::RxObjectUpdated,
|
||||||
|
NormEventType_NORM_RX_OBJECT_COMPLETED => EventType::RxObjectCompleted,
|
||||||
|
NormEventType_NORM_RX_OBJECT_ABORTED => EventType::RxObjectAborted,
|
||||||
|
NormEventType_NORM_RX_ACK_REQUEST => EventType::RxAckRequest,
|
||||||
|
NormEventType_NORM_GRTT_UPDATED => EventType::GrttUpdated,
|
||||||
|
NormEventType_NORM_CC_ACTIVE => EventType::CcActive,
|
||||||
|
NormEventType_NORM_CC_INACTIVE => EventType::CcInactive,
|
||||||
|
NormEventType_NORM_ACKING_NODE_NEW => EventType::AckingNodeNew,
|
||||||
|
NormEventType_NORM_SEND_ERROR => EventType::SendError,
|
||||||
|
NormEventType_NORM_USER_TIMEOUT => EventType::UserTimeout,
|
||||||
|
_ => EventType::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EventType> for NormEventType {
|
||||||
|
fn from(t: EventType) -> Self {
|
||||||
|
match t {
|
||||||
|
EventType::Invalid => NormEventType_NORM_EVENT_INVALID,
|
||||||
|
EventType::TxQueueVacancy => NormEventType_NORM_TX_QUEUE_VACANCY,
|
||||||
|
EventType::TxQueueEmpty => NormEventType_NORM_TX_QUEUE_EMPTY,
|
||||||
|
EventType::TxFlushCompleted => NormEventType_NORM_TX_FLUSH_COMPLETED,
|
||||||
|
EventType::TxWatermarkCompleted => NormEventType_NORM_TX_WATERMARK_COMPLETED,
|
||||||
|
EventType::TxCmdSent => NormEventType_NORM_TX_CMD_SENT,
|
||||||
|
EventType::TxObjectSent => NormEventType_NORM_TX_OBJECT_SENT,
|
||||||
|
EventType::TxObjectPurged => NormEventType_NORM_TX_OBJECT_PURGED,
|
||||||
|
EventType::TxRateChanged => NormEventType_NORM_TX_RATE_CHANGED,
|
||||||
|
EventType::LocalSenderClosed => NormEventType_NORM_LOCAL_SENDER_CLOSED,
|
||||||
|
EventType::RemoteSenderNew => NormEventType_NORM_REMOTE_SENDER_NEW,
|
||||||
|
EventType::RemoteSenderReset => NormEventType_NORM_REMOTE_SENDER_RESET,
|
||||||
|
EventType::RemoteSenderAddress => NormEventType_NORM_REMOTE_SENDER_ADDRESS,
|
||||||
|
EventType::RemoteSenderActive => NormEventType_NORM_REMOTE_SENDER_ACTIVE,
|
||||||
|
EventType::RemoteSenderInactive => NormEventType_NORM_REMOTE_SENDER_INACTIVE,
|
||||||
|
EventType::RemoteSenderPurged => NormEventType_NORM_REMOTE_SENDER_PURGED,
|
||||||
|
EventType::RxCmdNew => NormEventType_NORM_RX_CMD_NEW,
|
||||||
|
EventType::RxObjectNew => NormEventType_NORM_RX_OBJECT_NEW,
|
||||||
|
EventType::RxObjectInfo => NormEventType_NORM_RX_OBJECT_INFO,
|
||||||
|
EventType::RxObjectUpdated => NormEventType_NORM_RX_OBJECT_UPDATED,
|
||||||
|
EventType::RxObjectCompleted => NormEventType_NORM_RX_OBJECT_COMPLETED,
|
||||||
|
EventType::RxObjectAborted => NormEventType_NORM_RX_OBJECT_ABORTED,
|
||||||
|
EventType::RxAckRequest => NormEventType_NORM_RX_ACK_REQUEST,
|
||||||
|
EventType::GrttUpdated => NormEventType_NORM_GRTT_UPDATED,
|
||||||
|
EventType::CcActive => NormEventType_NORM_CC_ACTIVE,
|
||||||
|
EventType::CcInactive => NormEventType_NORM_CC_INACTIVE,
|
||||||
|
EventType::AckingNodeNew => NormEventType_NORM_ACKING_NODE_NEW,
|
||||||
|
EventType::SendError => NormEventType_NORM_SEND_ERROR,
|
||||||
|
EventType::UserTimeout => NormEventType_NORM_USER_TIMEOUT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for EventType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
EventType::Invalid => write!(f, "Invalid"),
|
||||||
|
EventType::TxQueueVacancy => write!(f, "TxQueueVacancy"),
|
||||||
|
EventType::TxQueueEmpty => write!(f, "TxQueueEmpty"),
|
||||||
|
EventType::TxFlushCompleted => write!(f, "TxFlushCompleted"),
|
||||||
|
EventType::TxWatermarkCompleted => write!(f, "TxWatermarkCompleted"),
|
||||||
|
EventType::TxCmdSent => write!(f, "TxCmdSent"),
|
||||||
|
EventType::TxObjectSent => write!(f, "TxObjectSent"),
|
||||||
|
EventType::TxObjectPurged => write!(f, "TxObjectPurged"),
|
||||||
|
EventType::TxRateChanged => write!(f, "TxRateChanged"),
|
||||||
|
EventType::LocalSenderClosed => write!(f, "LocalSenderClosed"),
|
||||||
|
EventType::RemoteSenderNew => write!(f, "RemoteSenderNew"),
|
||||||
|
EventType::RemoteSenderReset => write!(f, "RemoteSenderReset"),
|
||||||
|
EventType::RemoteSenderAddress => write!(f, "RemoteSenderAddress"),
|
||||||
|
EventType::RemoteSenderActive => write!(f, "RemoteSenderActive"),
|
||||||
|
EventType::RemoteSenderInactive => write!(f, "RemoteSenderInactive"),
|
||||||
|
EventType::RemoteSenderPurged => write!(f, "RemoteSenderPurged"),
|
||||||
|
EventType::RxCmdNew => write!(f, "RxCmdNew"),
|
||||||
|
EventType::RxObjectNew => write!(f, "RxObjectNew"),
|
||||||
|
EventType::RxObjectInfo => write!(f, "RxObjectInfo"),
|
||||||
|
EventType::RxObjectUpdated => write!(f, "RxObjectUpdated"),
|
||||||
|
EventType::RxObjectCompleted => write!(f, "RxObjectCompleted"),
|
||||||
|
EventType::RxObjectAborted => write!(f, "RxObjectAborted"),
|
||||||
|
EventType::RxAckRequest => write!(f, "RxAckRequest"),
|
||||||
|
EventType::GrttUpdated => write!(f, "GrttUpdated"),
|
||||||
|
EventType::CcActive => write!(f, "CcActive"),
|
||||||
|
EventType::CcInactive => write!(f, "CcInactive"),
|
||||||
|
EventType::AckingNodeNew => write!(f, "AckingNodeNew"),
|
||||||
|
EventType::SendError => write!(f, "SendError"),
|
||||||
|
EventType::UserTimeout => write!(f, "UserTimeout"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"rustc_fingerprint":18041410539700412860,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.90.0 (1159e78c4 2025-09-14)\nbinary: rustc\ncommit-hash: 1159e78c4747b02ef996e55082b704c09b970588\ncommit-date: 2025-09-14\nhost: aarch64-apple-darwin\nrelease: 1.90.0\nLLVM version: 20.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/cbowers/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
Signature: 8a477f597d28d172789f06886806bc55
|
||||||
|
# This file is a cache directory tag created by cargo.
|
||||||
|
# For information about cache directory tags see https://bford.info/cachedir/
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
# Helper module for integrating Rust bindings with NORM's waf build system
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from waflib import TaskGen, Task, Logs, Errors, Context
|
||||||
|
|
||||||
|
def options(ctx):
|
||||||
|
"""Add options for Rust integration to waf"""
|
||||||
|
ctx.add_option('--build-rust', action='store_true', default=False,
|
||||||
|
help='Build Rust bindings for NORM')
|
||||||
|
ctx.add_option('--rust-release', action='store_true', default=False,
|
||||||
|
help='Build Rust bindings in release mode')
|
||||||
|
ctx.add_option('--rust-docs', action='store_true', default=False,
|
||||||
|
help='Generate documentation for Rust bindings')
|
||||||
|
|
||||||
|
def configure(ctx):
|
||||||
|
"""Configure Rust toolchain if requested"""
|
||||||
|
if ctx.options.build_rust:
|
||||||
|
# Check for Rust toolchain
|
||||||
|
try:
|
||||||
|
ctx.find_program('cargo', var='CARGO', mandatory=True)
|
||||||
|
rustc_cmd = ctx.find_program('rustc', var='RUSTC', mandatory=True)
|
||||||
|
|
||||||
|
# Get Rust version
|
||||||
|
rustc_ver = subprocess.check_output([rustc_cmd[0], '--version']).decode('utf-8')
|
||||||
|
ctx.msg('Checking for rustc version', rustc_ver.strip())
|
||||||
|
|
||||||
|
# Set rust environment variables
|
||||||
|
ctx.env.BUILD_RUST = True
|
||||||
|
ctx.env.RUST_RELEASE = ctx.options.rust_release
|
||||||
|
ctx.env.RUST_DOCS = ctx.options.rust_docs
|
||||||
|
|
||||||
|
except Errors.WafError:
|
||||||
|
ctx.fatal('Rust toolchain not found. Install Rust from https://rustup.rs')
|
||||||
|
|
||||||
|
def build_rust_bindings(ctx):
|
||||||
|
"""Build the Rust bindings using cargo"""
|
||||||
|
if not ctx.env.BUILD_RUST:
|
||||||
|
return
|
||||||
|
|
||||||
|
rust_dir = ctx.path.find_dir('src/rust')
|
||||||
|
if not rust_dir:
|
||||||
|
ctx.fatal('Rust directory not found. Expected at: ' + ctx.path.abspath() + '/src/rust')
|
||||||
|
|
||||||
|
# Set environment variables for cargo
|
||||||
|
cargo_env = os.environ.copy()
|
||||||
|
cargo_env['NORM_INCLUDE_DIR'] = os.path.abspath('include')
|
||||||
|
cargo_env['NORM_LIB_DIR'] = os.path.abspath('lib')
|
||||||
|
|
||||||
|
# Prepare cargo command
|
||||||
|
cargo_cmd = [ctx.env.CARGO[0], 'build']
|
||||||
|
if ctx.env.RUST_RELEASE:
|
||||||
|
cargo_cmd.append('--release')
|
||||||
|
|
||||||
|
# Build Rust bindings
|
||||||
|
cwd = rust_dir.abspath()
|
||||||
|
try:
|
||||||
|
Logs.info("Building Rust bindings in: " + cwd)
|
||||||
|
ret = subprocess.call(cargo_cmd, env=cargo_env, cwd=cwd)
|
||||||
|
if ret != 0:
|
||||||
|
ctx.fatal('Failed to build Rust bindings')
|
||||||
|
|
||||||
|
# Build documentation if requested
|
||||||
|
if ctx.env.RUST_DOCS:
|
||||||
|
Logs.info("Building Rust documentation")
|
||||||
|
doc_cmd = [ctx.env.CARGO[0], 'doc', '--no-deps']
|
||||||
|
if ctx.env.RUST_RELEASE:
|
||||||
|
doc_cmd.append('--release')
|
||||||
|
|
||||||
|
ret = subprocess.call(doc_cmd, env=cargo_env, cwd=cwd)
|
||||||
|
if ret != 0:
|
||||||
|
Logs.warn('Failed to build Rust documentation')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
ctx.fatal('Error building Rust bindings: ' + str(e))
|
||||||
|
|
||||||
|
def install_rust_bindings(ctx):
|
||||||
|
"""Install the Rust bindings if requested"""
|
||||||
|
if not ctx.env.BUILD_RUST:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Define installation directories
|
||||||
|
rust_dir = ctx.path.find_dir('src/rust')
|
||||||
|
build_type = 'release' if ctx.env.RUST_RELEASE else 'debug'
|
||||||
|
target_dir = rust_dir.find_dir('target')
|
||||||
|
|
||||||
|
# Install libraries
|
||||||
|
if target_dir:
|
||||||
|
lib_dir = target_dir.find_dir(build_type)
|
||||||
|
if lib_dir:
|
||||||
|
# Install .rlib files
|
||||||
|
ctx.install_files(
|
||||||
|
'${LIBDIR}/rust',
|
||||||
|
lib_dir.ant_glob('*.rlib'),
|
||||||
|
postpone=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install dynamic libraries
|
||||||
|
if ctx.env.DEST_OS == 'win32':
|
||||||
|
ctx.install_files(
|
||||||
|
'${BINDIR}',
|
||||||
|
lib_dir.ant_glob('*.dll'),
|
||||||
|
postpone=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ctx.install_files(
|
||||||
|
'${LIBDIR}',
|
||||||
|
lib_dir.ant_glob('*.so') + lib_dir.ant_glob('*.dylib'),
|
||||||
|
postpone=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install documentation
|
||||||
|
if ctx.env.RUST_DOCS and target_dir:
|
||||||
|
doc_dir = target_dir.find_dir('doc')
|
||||||
|
if doc_dir:
|
||||||
|
ctx.install_files(
|
||||||
|
'${PREFIX}/share/doc/norm-rust',
|
||||||
|
doc_dir.ant_glob('**/*'),
|
||||||
|
cwd=doc_dir,
|
||||||
|
relative_trick=True,
|
||||||
|
postpone=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install Cargo.toml and source files for developers
|
||||||
|
ctx.install_files(
|
||||||
|
'${PREFIX}/share/norm/rust',
|
||||||
|
rust_dir.ant_glob('**/*.rs') + rust_dir.ant_glob('**/*.toml'),
|
||||||
|
cwd=rust_dir,
|
||||||
|
relative_trick=True,
|
||||||
|
postpone=False
|
||||||
|
)
|
||||||
30
wscript
30
wscript
|
|
@ -19,6 +19,8 @@ To build examples, use the --target directive. For example:
|
||||||
|
|
||||||
import platform
|
import platform
|
||||||
import waflib
|
import waflib
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
# Fetch VERSION from include/normVersion.h file
|
# Fetch VERSION from include/normVersion.h file
|
||||||
VERSION = None
|
VERSION = None
|
||||||
|
|
@ -47,9 +49,27 @@ def options(ctx):
|
||||||
ctx.recurse('protolib')
|
ctx.recurse('protolib')
|
||||||
build_opts = ctx.parser.add_argument_group('Compile/install Options', 'Use during build/install step.')
|
build_opts = ctx.parser.add_argument_group('Compile/install Options', 'Use during build/install step.')
|
||||||
|
|
||||||
|
# Add Rust binding options
|
||||||
|
ctx.add_option('--build-rust', action='store_true', default=False,
|
||||||
|
help='Build Rust bindings for NORM')
|
||||||
|
ctx.add_option('--rust-release', action='store_true', default=False,
|
||||||
|
help='Build Rust bindings in release mode')
|
||||||
|
ctx.add_option('--rust-docs', action='store_true', default=False,
|
||||||
|
help='Generate documentation for Rust bindings')
|
||||||
|
|
||||||
def configure(ctx):
|
def configure(ctx):
|
||||||
ctx.recurse('protolib')
|
ctx.recurse('protolib')
|
||||||
|
|
||||||
|
# Configure Rust bindings if requested
|
||||||
|
if ctx.options.build_rust:
|
||||||
|
try:
|
||||||
|
ctx.find_program('cargo', var='CARGO', mandatory=True)
|
||||||
|
ctx.env.BUILD_RUST = True
|
||||||
|
ctx.env.RUST_RELEASE = ctx.options.rust_release
|
||||||
|
ctx.env.RUST_DOCS = ctx.options.rust_docs
|
||||||
|
except ctx.errors.ConfigurationError:
|
||||||
|
ctx.fatal('Rust toolchain not found. Install Rust from https://rustup.rs')
|
||||||
|
|
||||||
# Use this USE variable to add flags to NORM's compilation
|
# Use this USE variable to add flags to NORM's compilation
|
||||||
ctx.env.USE_BUILD_NORM += ['BUILD_NORM']
|
ctx.env.USE_BUILD_NORM += ['BUILD_NORM']
|
||||||
|
|
||||||
|
|
@ -71,6 +91,16 @@ def configure(ctx):
|
||||||
def build(ctx):
|
def build(ctx):
|
||||||
ctx.recurse('protolib')
|
ctx.recurse('protolib')
|
||||||
|
|
||||||
|
# Build Rust bindings if configured
|
||||||
|
if hasattr(ctx.env, 'BUILD_RUST') and ctx.env.BUILD_RUST:
|
||||||
|
# Import our custom Rust build module
|
||||||
|
sys.path.insert(0, os.path.join(ctx.path.abspath(), 'src/rust'))
|
||||||
|
try:
|
||||||
|
import waf_rust
|
||||||
|
waf_rust.build_rust_bindings(ctx)
|
||||||
|
except ImportError:
|
||||||
|
ctx.fatal('Failed to import Rust waf module. Check src/rust/waf_rust.py exists.')
|
||||||
|
|
||||||
# Setup to install NORM header file
|
# Setup to install NORM header file
|
||||||
ctx.install_files("${PREFIX}/include/", "include/normApi.h")
|
ctx.install_files("${PREFIX}/include/", "include/normApi.h")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue